mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-04 20:55:19 +02:00
Merge pull request #23 from seanmorley15/adventureServiceMigration
Adventure service migration
This commit is contained in:
commit
ed54f7a97c
54 changed files with 3552 additions and 141 deletions
|
@ -1,26 +1,26 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
|
||||
{
|
||||
"name": "Ubuntu",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/base:jammy",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/node-asdf:0": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
||||
}
|
||||
"name": "AdventureLog",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/base:jammy",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/node-asdf:0": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
||||
}
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "uname -a",
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "uname -a",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
|
|
1
.env.example
Normal file
1
.env.example
Normal file
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=
|
91
CONTRIBUTING.md
Normal file
91
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,91 @@
|
|||
# Contributing to AdventureLog
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Please make sure you create an issue first for your change so you can link any pull requests to this issue. There should be a clear relationship between pull requests and issues.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. Increase the version numbers in any examples files and the README.md to the new version that this
|
||||
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
|
||||
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
|
||||
do not have permission to do that, you may request the second reviewer to merge it for you.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,6 +1,6 @@
|
|||
# AdventureLog: Embark, Explore, Remember. 🌍
|
||||
|
||||
_**⚠️ AdvenutreLog is in early development and is not at all deployable in the current form!**_
|
||||
_**⚠️ AdventureLog is in early development and is not recommended for production use until version 1.0!**_
|
||||
### *"Never forget an adventure with AdventureLog - Your ultimate travel companion!"*
|
||||
-----
|
||||
## Installation
|
||||
|
|
|
@ -5,6 +5,8 @@ services:
|
|||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=postgres://adventurelog:PO24VjITwGgk@db:5432/adventurelog
|
||||
# ORIGIN is only necessary when not using a reverse proxy or hosting that includes https
|
||||
- ORIGIN=http://localhost:3000
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
|
|
15
migrations/0004_smart_maelstrom.sql
Normal file
15
migrations/0004_smart_maelstrom.sql
Normal file
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE IF NOT EXISTS "session" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
"id" text PRIMARY KEY NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
2
migrations/0005_glamorous_pixie.sql
Normal file
2
migrations/0005_glamorous_pixie.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "user" ADD COLUMN "username" text NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN "hashed_password" text NOT NULL;
|
1
migrations/0006_melted_leech.sql
Normal file
1
migrations/0006_melted_leech.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "user" ALTER COLUMN "hashed_password" SET DATA TYPE varchar;
|
2
migrations/0007_nervous_scalphunter.sql
Normal file
2
migrations/0007_nervous_scalphunter.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "user" ADD COLUMN "first_name" text NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN "last_name" text NOT NULL;
|
12
migrations/0008_romantic_maria_hill.sql
Normal file
12
migrations/0008_romantic_maria_hill.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS "userVisitedAdventures" (
|
||||
"user_id" text NOT NULL,
|
||||
"adventure_name" text NOT NULL,
|
||||
"location" text,
|
||||
"adventure_visited" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "userVisitedAdventures" ADD CONSTRAINT "userVisitedAdventures_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
5
migrations/0009_spotty_madame_web.sql
Normal file
5
migrations/0009_spotty_madame_web.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE "userVisitedAdventures" RENAME COLUMN "adventure_visited" TO "adventure_id";--> statement-breakpoint
|
||||
ALTER TABLE "userVisitedAdventures" ADD PRIMARY KEY ("adventure_id");--> statement-breakpoint
|
||||
ALTER TABLE "userVisitedAdventures" ALTER COLUMN "adventure_id" SET DATA TYPE serial;--> statement-breakpoint
|
||||
ALTER TABLE "userVisitedAdventures" ALTER COLUMN "adventure_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "userVisitedAdventures" ADD COLUMN "visited_date" text;
|
123
migrations/meta/0004_snapshot.json
Normal file
123
migrations/meta/0004_snapshot.json
Normal file
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"id": "ccf7c336-c61f-452f-822b-b3b039bb20f9",
|
||||
"prevId": "45d98527-f0a9-44fc-9658-d3c461afed95",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"featuredAdventures": {
|
||||
"name": "featuredAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sharedAdventures": {
|
||||
"name": "sharedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
135
migrations/meta/0005_snapshot.json
Normal file
135
migrations/meta/0005_snapshot.json
Normal file
|
@ -0,0 +1,135 @@
|
|||
{
|
||||
"id": "e91dda33-e04e-4e99-a297-21a34aa35493",
|
||||
"prevId": "ccf7c336-c61f-452f-822b-b3b039bb20f9",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"featuredAdventures": {
|
||||
"name": "featuredAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sharedAdventures": {
|
||||
"name": "sharedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
135
migrations/meta/0006_snapshot.json
Normal file
135
migrations/meta/0006_snapshot.json
Normal file
|
@ -0,0 +1,135 @@
|
|||
{
|
||||
"id": "b0849b3e-02e1-42e1-b07c-6fa613c98e82",
|
||||
"prevId": "e91dda33-e04e-4e99-a297-21a34aa35493",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"featuredAdventures": {
|
||||
"name": "featuredAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sharedAdventures": {
|
||||
"name": "sharedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
147
migrations/meta/0007_snapshot.json
Normal file
147
migrations/meta/0007_snapshot.json
Normal file
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
"id": "2039600b-1f5f-4f37-84dd-bb40636855e7",
|
||||
"prevId": "b0849b3e-02e1-42e1-b07c-6fa613c98e82",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"featuredAdventures": {
|
||||
"name": "featuredAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sharedAdventures": {
|
||||
"name": "sharedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
195
migrations/meta/0008_snapshot.json
Normal file
195
migrations/meta/0008_snapshot.json
Normal file
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"id": "ec5af5e4-8522-4383-af47-412fb43c7cc3",
|
||||
"prevId": "2039600b-1f5f-4f37-84dd-bb40636855e7",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"featuredAdventures": {
|
||||
"name": "featuredAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sharedAdventures": {
|
||||
"name": "sharedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"userVisitedAdventures": {
|
||||
"name": "userVisitedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"adventure_name": {
|
||||
"name": "adventure_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"adventure_visited": {
|
||||
"name": "adventure_visited",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"userVisitedAdventures_user_id_user_id_fk": {
|
||||
"name": "userVisitedAdventures_user_id_user_id_fk",
|
||||
"tableFrom": "userVisitedAdventures",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
201
migrations/meta/0009_snapshot.json
Normal file
201
migrations/meta/0009_snapshot.json
Normal file
|
@ -0,0 +1,201 @@
|
|||
{
|
||||
"id": "1d83805d-6ee2-4dae-87a6-5af6f2d5add3",
|
||||
"prevId": "ec5af5e4-8522-4383-af47-412fb43c7cc3",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"featuredAdventures": {
|
||||
"name": "featuredAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_user_id_user_id_fk": {
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sharedAdventures": {
|
||||
"name": "sharedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"hashed_password": {
|
||||
"name": "hashed_password",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"userVisitedAdventures": {
|
||||
"name": "userVisitedAdventures",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"adventure_id": {
|
||||
"name": "adventure_id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"adventure_name": {
|
||||
"name": "adventure_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"visited_date": {
|
||||
"name": "visited_date",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"userVisitedAdventures_user_id_user_id_fk": {
|
||||
"name": "userVisitedAdventures_user_id_user_id_fk",
|
||||
"tableFrom": "userVisitedAdventures",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,48 @@
|
|||
"when": 1712083977580,
|
||||
"tag": "0003_clammy_goblin_queen",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "5",
|
||||
"when": 1712103855532,
|
||||
"tag": "0004_smart_maelstrom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "5",
|
||||
"when": 1712104331399,
|
||||
"tag": "0005_glamorous_pixie",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "5",
|
||||
"when": 1712105206127,
|
||||
"tag": "0006_melted_leech",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "5",
|
||||
"when": 1712167204757,
|
||||
"tag": "0007_nervous_scalphunter",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "5",
|
||||
"when": 1712186591227,
|
||||
"tag": "0008_romantic_maria_hill",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "5",
|
||||
"when": 1712407140727,
|
||||
"tag": "0009_spotty_madame_web",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
1193
package-lock.json
generated
1193
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -21,11 +21,13 @@
|
|||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@tailwindcss/typography": "^0.5.12",
|
||||
"@types/pg": "^8.11.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"daisyui": "^4.9.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"pg": "^8.11.4",
|
||||
"lucia": "^3.1.1",
|
||||
"pg": "^8.11.5",
|
||||
"postcss": "^8.4.38",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
|
@ -36,7 +38,9 @@
|
|||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@lucia-auth/adapter-drizzle": "^1.0.7",
|
||||
"drizzle-orm": "^0.30.6",
|
||||
"oslo": "^1.2.0",
|
||||
"postgres": "^3.4.4"
|
||||
}
|
||||
}
|
||||
|
|
11
src/app.d.ts
vendored
11
src/app.d.ts
vendored
|
@ -1,12 +1,9 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
interface Locals {
|
||||
user: import("lucia").User | null;
|
||||
session: import("lucia").Session | null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
32
src/hooks.server.ts
Normal file
32
src/hooks.server.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { lucia } from "$lib/server/auth";
|
||||
import type { Handle } from "@sveltejs/kit";
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const sessionId = event.cookies.get(lucia.sessionCookieName);
|
||||
if (!sessionId) {
|
||||
event.locals.user = null;
|
||||
event.locals.session = null;
|
||||
return resolve(event);
|
||||
}
|
||||
|
||||
const { session, user } = await lucia.validateSession(sessionId);
|
||||
if (session && session.fresh) {
|
||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
// sveltekit types deviates from the de-facto standard
|
||||
// you can use 'as any' too
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
}
|
||||
if (!session) {
|
||||
const sessionCookie = lucia.createBlankSessionCookie();
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
}
|
||||
event.locals.user = user;
|
||||
event.locals.session = session;
|
||||
return resolve(event);
|
||||
};
|
1
src/lib/assets/info.svg
Normal file
1
src/lib/assets/info.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#ffffff" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
After Width: | Height: | Size: 521 B |
60
src/lib/components/InfoModal.svelte
Normal file
60
src/lib/components/InfoModal.svelte
Normal file
|
@ -0,0 +1,60 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Adventure } from "$lib/utils/types";
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from "svelte";
|
||||
let modal: HTMLDialogElement;
|
||||
import { appVersion, copyrightYear } from "$lib/config";
|
||||
|
||||
onMount(() => {
|
||||
modal = document.getElementById("my_modal_1") as HTMLDialogElement;
|
||||
if (modal) {
|
||||
modal.showModal();
|
||||
}
|
||||
});
|
||||
|
||||
function close() {
|
||||
dispatch("close");
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") {
|
||||
close();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog id="my_modal_1" class="modal">
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">About AdventureLog</h3>
|
||||
<p class="py-1">
|
||||
AdventureLog {appVersion}
|
||||
</p>
|
||||
<p class="py-1">
|
||||
© {copyrightYear}
|
||||
<a
|
||||
href="https://github.com/seanmorley15"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-500 underline">Sean Morley</a
|
||||
>
|
||||
</p>
|
||||
<p class="py-1">Liscensed under the GPL-3.0 License.</p>
|
||||
<p class="py-1">
|
||||
<a
|
||||
href="https://github.com/seanmorley15/AdventureLog"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-500 underline">Source Code</a
|
||||
>
|
||||
</p>
|
||||
<p class="py-1">Made with ❤️ in Connecticut.</p>
|
||||
<div
|
||||
class="modal-action items-center"
|
||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
||||
></div>
|
||||
<button class="btn btn-primary" on:click={close}>Close</button>
|
||||
</div>
|
||||
</dialog>
|
|
@ -1,7 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import { visitCount } from "$lib/utils/stores/visitCountStore";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
import type { DatabaseUser } from "$lib/server/auth";
|
||||
export let user: any;
|
||||
import UserAvatar from "./UserAvatar.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import InfoModal from "./InfoModal.svelte";
|
||||
import infoIcon from "$lib/assets/info.svg";
|
||||
async function goHome() {
|
||||
goto("/");
|
||||
}
|
||||
|
@ -11,8 +17,35 @@
|
|||
async function goToFeatured() {
|
||||
goto("/featured");
|
||||
}
|
||||
async function toToLogin() {
|
||||
goto("/login");
|
||||
}
|
||||
async function toToSignup() {
|
||||
goto("/signup");
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
|
||||
let infoModalOpen = false;
|
||||
|
||||
function showModal() {
|
||||
infoModalOpen = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
infoModalOpen = false;
|
||||
}
|
||||
|
||||
// get value from fetch /api/visitcount
|
||||
|
||||
$: if (user) {
|
||||
onMount(async () => {
|
||||
const res = await fetch("/api/visitcount");
|
||||
const data = await res.json();
|
||||
visitCount.set(data.visitCount);
|
||||
});
|
||||
}
|
||||
|
||||
visitCount.subscribe((value) => {
|
||||
count = value;
|
||||
});
|
||||
|
@ -21,10 +54,6 @@
|
|||
const isBrowser = typeof window !== "undefined";
|
||||
if (isBrowser) {
|
||||
const storedAdventures = localStorage.getItem("adventures");
|
||||
if (storedAdventures) {
|
||||
let parsed = JSON.parse(storedAdventures);
|
||||
visitCount.set(parsed.length);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -34,10 +63,11 @@
|
|||
class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2"
|
||||
on:click={goHome}>Home</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2"
|
||||
on:click={goToLog}>My Log</button
|
||||
>
|
||||
{#if user}
|
||||
<button class="btn btn-primary my-2 md:my-0 md:mr-4" on:click={goToLog}
|
||||
>My Log</button
|
||||
>
|
||||
{/if}
|
||||
<button class="btn btn-primary my-2 md:my-0" on:click={goToFeatured}
|
||||
>Featured</button
|
||||
>
|
||||
|
@ -45,7 +75,20 @@
|
|||
<div class="navbar-center flex justify-center md:justify-center">
|
||||
<a class="btn btn-ghost text-xl" href="/">AdventureLog 🗺️</a>
|
||||
</div>
|
||||
|
||||
{#if infoModalOpen}
|
||||
<InfoModal on:close={closeModal} />
|
||||
{/if}
|
||||
<div class="navbar-end flex justify-around md:justify-end mr-4">
|
||||
<p>Adventures: {count}</p>
|
||||
{#if !user}
|
||||
<button class="btn btn-primary ml-4" on:click={toToLogin}>Login</button>
|
||||
<button class="btn btn-primary ml-4" on:click={toToSignup}>Signup</button>
|
||||
{/if}
|
||||
|
||||
{#if user}
|
||||
<p class="font-bold">Adventures: {count}</p>
|
||||
<UserAvatar {user} />
|
||||
{/if}
|
||||
<button class="btn btn-neutral ml-4" on:click={showModal}>About</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
36
src/lib/components/UserAvatar.svelte
Normal file
36
src/lib/components/UserAvatar.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import { goto } from "$app/navigation";
|
||||
export let user: any;
|
||||
let firstLetter = user.first_name.charAt(0);
|
||||
|
||||
async function navToSettings() {
|
||||
goto("/settings");
|
||||
}
|
||||
async function navToLog() {
|
||||
goto("/log");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="dropdown dropdown-bottom dropdown-end" tabindex="0" role="button">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-neutral text-neutral-content rounded-full w-10 ml-4">
|
||||
<span class="text-2xl -mt-0.5">{firstLetter}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-neutral mt-2 rounded-box w-52"
|
||||
>
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<li><a>Profile</a></li>
|
||||
<li><button on:click={navToLog}>My Log</button></li>
|
||||
<li><button on:click={navToSettings}>Settings</button></li>
|
||||
<form method="post" action="/" use:enhance>
|
||||
<li><button>Logout</button></li>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
3
src/lib/config.ts
Normal file
3
src/lib/config.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export let appVersion = "Web 0.0.1";
|
||||
export let appTitle = "AdventureLog";
|
||||
export let copyrightYear = "2024"
|
|
@ -1,8 +1,9 @@
|
|||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import dotenv from "dotenv";
|
||||
import * as schema from "$lib/db/schema";
|
||||
dotenv.config();
|
||||
const { DATABASE_URL } = process.env;
|
||||
|
||||
const client = postgres(DATABASE_URL);
|
||||
export const db = drizzle(client, {});
|
||||
const client = postgres(DATABASE_URL || ""); // Pass DATABASE_URL as a string argument
|
||||
export const db = drizzle(client, { schema });
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { pgTable, json, text, serial } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
json,
|
||||
serial,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const featuredAdventures = pgTable("featuredAdventures", {
|
||||
id: serial("id").primaryKey(),
|
||||
|
@ -10,3 +17,34 @@ export const sharedAdventures = pgTable("sharedAdventures", {
|
|||
id: text("id").primaryKey(),
|
||||
data: json("data").notNull(),
|
||||
});
|
||||
|
||||
export const userTable = pgTable("user", {
|
||||
id: text("id").primaryKey(),
|
||||
username: text("username").notNull(),
|
||||
first_name: text("first_name").notNull(),
|
||||
last_name: text("last_name").notNull(),
|
||||
hashed_password: varchar("hashed_password").notNull(),
|
||||
});
|
||||
|
||||
// export type SelectUser = typeof userTable.$inferSelect;
|
||||
|
||||
export const sessionTable = pgTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => userTable.id),
|
||||
expiresAt: timestamp("expires_at", {
|
||||
withTimezone: true,
|
||||
mode: "date",
|
||||
}).notNull(),
|
||||
});
|
||||
|
||||
export const userVisitedAdventures = pgTable("userVisitedAdventures", {
|
||||
adventureID: serial("adventure_id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => userTable.id),
|
||||
adventureName: text("adventure_name").notNull(),
|
||||
location: text("location"),
|
||||
visitedDate: text("visited_date"),
|
||||
});
|
||||
|
|
|
@ -10,3 +10,77 @@ export function generateRandomString() {
|
|||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
const inspirationalQuotes = [
|
||||
"Believe you can and you're halfway there. - Theodore Roosevelt",
|
||||
"The only way to do great work is to love what you do. - Steve Jobs",
|
||||
"In the middle of every difficulty lies opportunity. - Albert Einstein",
|
||||
"The future belongs to those who believe in the beauty of their dreams. - Eleanor Roosevelt",
|
||||
"It does not matter how slowly you go as long as you do not stop. - Confucius",
|
||||
"Success is not final, failure is not fatal: It is the courage to continue that counts. - Winston Churchill",
|
||||
"The only limit to our realization of tomorrow will be our doubts of today. - Franklin D. Roosevelt",
|
||||
"Don't watch the clock; do what it does. Keep going. - Sam Levenson",
|
||||
"You are never too old to set another goal or to dream a new dream. - C.S. Lewis",
|
||||
"The only person you are destined to become is the person you decide to be. - Ralph Waldo Emerson",
|
||||
"Happiness is not something ready-made. It comes from your own actions. - Dalai Lama",
|
||||
"Life is what happens when you're busy making other plans. - John Lennon",
|
||||
"You miss 100% of the shots you don't take. - Wayne Gretzky",
|
||||
"The best time to plant a tree was 20 years ago. The second best time is now. - Chinese Proverb",
|
||||
"The only way to achieve the impossible is to believe it is possible. - Charles Kingsleigh",
|
||||
"Don't count the days, make the days count. - Muhammad Ali",
|
||||
"You don't have to be great to start, but you have to start to be great. - Zig Ziglar",
|
||||
"You can't go back and change the beginning, but you can start where you are and change the ending. - C.S. Lewis",
|
||||
"Dream big and dare to fail. - Norman Vaughan",
|
||||
"The secret of getting ahead is getting started. - Mark Twain",
|
||||
"Everything you can imagine is real. - Pablo Picasso",
|
||||
"You must be the change you wish to see in the world. - Mahatma Gandhi",
|
||||
"If you want to lift yourself up, lift up someone else. - Booker T. Washington",
|
||||
"Believe in yourself and all that you are. Know that there is something inside you that is greater than any obstacle. - Christian D. Larson",
|
||||
"The journey of a thousand miles begins with one step. - Lao Tzu",
|
||||
"Life isn't about waiting for the storm to pass, it's about learning to dance in the rain. - Vivian Greene",
|
||||
"You are never too old to set another goal or to dream a new dream. - Les Brown",
|
||||
"Your time is limited, don't waste it living someone else's life. - Steve Jobs",
|
||||
"Don't let yesterday take up too much of today. - Will Rogers",
|
||||
"The only thing standing between you and your goal is the story you keep telling yourself as to why you can't achieve it. - Jordan Belfort",
|
||||
"The future belongs to those who prepare for it today. - Malcolm X",
|
||||
"The greatest glory in living lies not in never falling, but in rising every time we fall. - Nelson Mandela",
|
||||
"It's not what happens to you, but how you react to it that matters. - Epictetus",
|
||||
"The only way to do great work is to love what you do. - Steve Jobs",
|
||||
"When one door of happiness closes, another opens, but often we look so long at the closed door that we do not see the one that has been opened for us. - Helen Keller",
|
||||
"The only thing that stands between you and your dream is the will to try and the belief that it is actually possible. - Joel Brown",
|
||||
"Success is walking from failure to failure with no loss of enthusiasm. - Winston Churchill",
|
||||
"Believe in yourself! Have faith in your abilities! Without a humble but reasonable confidence in your own powers you cannot be successful or happy. - Norman Vincent Peale",
|
||||
"The greatest adventure is what lies ahead. - J.R.R. Tolkien",
|
||||
"The only way to do great work is to love what you do. - Steve Jobs",
|
||||
"What you get by achieving your goals is not as important as what you become by achieving your goals. - Zig Ziglar",
|
||||
"To be yourself in a world that is constantly trying to make you something else is the greatest accomplishment. - Ralph Waldo Emerson",
|
||||
"What lies behind us and what lies before us are tiny matters compared to what lies within us. - Ralph Waldo Emerson",
|
||||
"The only person you are destined to become is the person you decide to be. - Ralph Waldo Emerson",
|
||||
"The best and most beautiful things in the world cannot be seen or even touched - they must be felt with the heart. - Helen Keller",
|
||||
"The only limit to our realization of tomorrow will be our doubts of today. - Franklin D. Roosevelt",
|
||||
"It always seems impossible until it is done. - Nelson Mandela",
|
||||
"I can't change the direction of the wind, but I can adjust my sails to always reach my destination. - Jimmy Dean",
|
||||
"Believe you can and you're halfway there. - Theodore Roosevelt",
|
||||
"The only way to achieve the impossible is to believe it is possible. - Charles Kingsleigh",
|
||||
"If you're going through hell, keep going. - Winston Churchill",
|
||||
"Nothing is impossible, the word itself says 'I'm possible'! - Audrey Hepburn",
|
||||
"The only thing standing in the way between you and your goal is the story you keep telling yourself as to why you can't achieve it. - Jordan Belfort",
|
||||
"The future belongs to those who believe in the beauty of their dreams. - Eleanor Roosevelt",
|
||||
"Success is not final, failure is not fatal: It is the courage to continue that counts. - Winston Churchill",
|
||||
"Keep your face always toward the sunshine - and shadows will fall behind you. - Walt Whitman",
|
||||
"Success is not the key to happiness. Happiness is the key to success. If you love what you are doing, you will be successful. - Albert Schweitzer",
|
||||
"Don't watch the clock; do what it does. Keep going. - Sam Levenson",
|
||||
"You are never too old to set another goal or to dream a new dream. - C.S. Lewis",
|
||||
"You are never too old to set another goal or to dream a new dream. - C.S. Lewis",
|
||||
"The only person you are destined to become is the person you decide to be. - Ralph Waldo Emerson",
|
||||
"Happiness is not something ready-made. It comes from your own actions. - Dalai Lama",
|
||||
"Life is what happens when you're busy making other plans. - John Lennon",
|
||||
"You miss 100% of the shots you don't take. - Wayne Gretzky",
|
||||
"The best time to plant a tree was 20 years ago. The second best time is now. - Chinese Proverb",
|
||||
"The only way to achieve the impossible is to believe it is possible. - Charles Kings",
|
||||
];
|
||||
|
||||
export function getRandomQuote() {
|
||||
const randomIndex = Math.floor(Math.random() * inspirationalQuotes.length);
|
||||
return inspirationalQuotes[randomIndex];
|
||||
}
|
||||
|
|
39
src/lib/server/auth.ts
Normal file
39
src/lib/server/auth.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
|
||||
import { Lucia, TimeSpan } from "lucia";
|
||||
import { dev } from "$app/environment";
|
||||
import { userTable, sessionTable } from "$lib/db/schema";
|
||||
import { db } from "$lib/db/db.server";
|
||||
|
||||
const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable);
|
||||
|
||||
export const lucia = new Lucia(adapter, {
|
||||
sessionCookie: {
|
||||
attributes: {
|
||||
secure: !dev,
|
||||
},
|
||||
},
|
||||
getUserAttributes: (attributes) => {
|
||||
return {
|
||||
// attributes has the type of DatabaseUserAttributes
|
||||
username: attributes.username,
|
||||
id: attributes.id,
|
||||
first_name: attributes.first_name,
|
||||
last_name: attributes.last_name,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
declare module "lucia" {
|
||||
interface Register {
|
||||
Lucia: typeof lucia;
|
||||
DatabaseUserAttributes: DatabaseUser;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DatabaseUser {
|
||||
id: string;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
hashed_password: string;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export const visitCount = writable(0);
|
||||
let value = 0;
|
||||
export const visitCount = writable(value);
|
||||
|
||||
|
|
18
src/routes/+error.svelte
Normal file
18
src/routes/+error.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
// @ts-nocheck
|
||||
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
async function goHome() {
|
||||
goto("/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<article class="text-center font-extrabold text-4xl mt-16">
|
||||
<h1>{$page.error.message}</h1>
|
||||
</article>
|
||||
|
||||
<div class="flex justify-center items-center">
|
||||
<button class="btn btn-primary" on:click={goHome}>Go Home</button>
|
||||
</div>
|
12
src/routes/+layout.server.ts
Normal file
12
src/routes/+layout.server.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { LayoutServerLoad, PageServerLoad } from "./$types";
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
return {
|
||||
user: event.locals.user,
|
||||
};
|
||||
}
|
||||
return {
|
||||
user: null,
|
||||
};
|
||||
};
|
|
@ -1,12 +1,13 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
export let data;
|
||||
import Footer from "$lib/components/Footer.svelte";
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import "../app.css";
|
||||
|
||||
// only show footer if scrolled to the bottom
|
||||
</script>
|
||||
|
||||
<Navbar />
|
||||
<!-- passes the user object to the navbar component -->
|
||||
<Navbar user={data.user} />
|
||||
<section>
|
||||
<slot />
|
||||
</section>
|
||||
|
|
31
src/routes/+page.server.ts
Normal file
31
src/routes/+page.server.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { lucia } from "$lib/server/auth";
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user)
|
||||
return {
|
||||
user: event.locals.user,
|
||||
};
|
||||
return {
|
||||
user: null,
|
||||
};
|
||||
};
|
||||
|
||||
// handle the logout action
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
if (!event.locals.session) {
|
||||
return fail(401);
|
||||
}
|
||||
|
||||
await lucia.invalidateSession(event.locals.session.id);
|
||||
const sessionCookie = lucia.createBlankSessionCookie();
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
return redirect(302, "/login");
|
||||
},
|
||||
};
|
|
@ -1,4 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
export let data: PageData;
|
||||
import { goto } from "$app/navigation";
|
||||
import campingDrawing from "$lib/assets/camping.svg";
|
||||
import { visitCount } from "$lib/utils/stores/visitCountStore";
|
||||
|
@ -9,9 +13,14 @@
|
|||
</script>
|
||||
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<article class="prose">
|
||||
<h1 class="mb-4">Welcome. Let's get Exploring!</h1>
|
||||
</article>
|
||||
{#if data.user && data.user.username != ""}
|
||||
<h1 class="mb-6 text-4xl font-extrabold">
|
||||
Welcome {data.user.first_name}. Let's get Exploring!
|
||||
</h1>
|
||||
{:else}
|
||||
<h1 class="mb-6 text-4xl font-extrabold">Welcome. Let's get Exploring!</h1>
|
||||
{/if}
|
||||
|
||||
<img src={campingDrawing} class="w-1/4 mb-4" alt="Logo" />
|
||||
<button on:click={navToLog} class="btn btn-primary">Open Log</button>
|
||||
|
||||
|
|
27
src/routes/api/clearvisits/+server.ts
Normal file
27
src/routes/api/clearvisits/+server.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import type { RequestEvent } from "@sveltejs/kit";
|
||||
import { db } from "$lib/db/db.server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { userVisitedAdventures } from "$lib/db/schema";
|
||||
|
||||
export async function DELETE(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
let res = await db
|
||||
.delete(userVisitedAdventures)
|
||||
.where(
|
||||
eq(userVisitedAdventures.userId, event.locals.user.id),
|
||||
)
|
||||
.execute();
|
||||
return new Response(JSON.stringify({ res: res }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
39
src/routes/api/userinfo/+server.ts
Normal file
39
src/routes/api/userinfo/+server.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { lucia } from "$lib/server/auth";
|
||||
import type { RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: "Welcome user info page!",
|
||||
userId: event.locals.user.id,
|
||||
username: event.locals.user.username,
|
||||
firstName: event.locals.user.first_name,
|
||||
lastName: event.locals.user.last_name,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
||||
status: 500,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
34
src/routes/api/visitcount/+server.ts
Normal file
34
src/routes/api/visitcount/+server.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import type { RequestEvent } from "@sveltejs/kit";
|
||||
import { count } from 'drizzle-orm';
|
||||
import { eq } from "drizzle-orm";
|
||||
import { userVisitedAdventures } from "$lib/db/schema";
|
||||
import { db } from "$lib/db/db.server";
|
||||
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
// get the count of the number of adventures the user has visited
|
||||
let result = await db
|
||||
.select({ count: count() })
|
||||
.from(userVisitedAdventures)
|
||||
.where(eq(userVisitedAdventures.userId,event.locals.user.id))
|
||||
.execute();
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
visitCount: result[0].count,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
177
src/routes/api/visits/+server.ts
Normal file
177
src/routes/api/visits/+server.ts
Normal file
|
@ -0,0 +1,177 @@
|
|||
import { lucia } from "$lib/server/auth";
|
||||
import type { RequestEvent } from "@sveltejs/kit";
|
||||
import { userVisitedAdventures } from "$lib/db/schema";
|
||||
import { db } from "$lib/db/db.server";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { Adventure } from "$lib/utils/types";
|
||||
|
||||
// Gets all the adventures that the user has visited
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
let result = await db
|
||||
.select()
|
||||
.from(userVisitedAdventures)
|
||||
.where(eq(userVisitedAdventures.userId, event.locals.user.id))
|
||||
.execute();
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
adventures: result.map((item) => ({
|
||||
id: item.adventureID,
|
||||
name: item.adventureName,
|
||||
location: item.location,
|
||||
created: item.visitedDate,
|
||||
})),
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// deletes the adventure given the adventure id and the user object
|
||||
export async function DELETE(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// get id from the body
|
||||
const { id } = await event.request.json();
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({ error: "No id found" }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let res = await db
|
||||
.delete(userVisitedAdventures)
|
||||
.where(
|
||||
and(
|
||||
eq(userVisitedAdventures.userId, event.locals.user.id),
|
||||
eq(userVisitedAdventures.adventureID, Number(id))
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
|
||||
return new Response(JSON.stringify({ id: id, res: res }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// add the adventure to the user's visited list
|
||||
export async function POST(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// get properties from the body
|
||||
const { name, location, created } = await event.request.json();
|
||||
|
||||
// insert the adventure to the user's visited list
|
||||
await db
|
||||
.insert(userVisitedAdventures)
|
||||
.values({
|
||||
userId: event.locals.user.id,
|
||||
adventureName: name,
|
||||
location: location,
|
||||
visitedDate: created,
|
||||
})
|
||||
.execute();
|
||||
let res = await db
|
||||
.select()
|
||||
.from(userVisitedAdventures)
|
||||
.where(
|
||||
and(
|
||||
eq(userVisitedAdventures.userId, event.locals.user.id),
|
||||
eq(userVisitedAdventures.adventureName, name),
|
||||
eq(userVisitedAdventures.location, location),
|
||||
eq(userVisitedAdventures.visitedDate, created)
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
|
||||
// return a response with the adventure object values
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
adventure: { name, location, created },
|
||||
message: { message: "Adventure added" },
|
||||
id: res[0].adventureID
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// put route to update existing adventure
|
||||
export async function PUT(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// get properties from the body
|
||||
const { id, name, location, created } = await event.request.json();
|
||||
|
||||
// update the adventure in the user's visited list
|
||||
await db
|
||||
.update(userVisitedAdventures)
|
||||
.set({
|
||||
adventureName: name,
|
||||
location: location,
|
||||
visitedDate: created,
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(userVisitedAdventures.userId, event.locals.user.id),
|
||||
eq(userVisitedAdventures.adventureID, Number(id))
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
adventure: { id, name, location, created },
|
||||
message: { message: "Adventure updated" },
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,19 +1,34 @@
|
|||
<script lang="ts">
|
||||
export let data;
|
||||
console.log(data.result);
|
||||
import { goto } from "$app/navigation";
|
||||
import AdventureCard from "$lib/components/AdventureCard.svelte";
|
||||
import { visitCount } from "$lib/utils/stores/visitCountStore.js";
|
||||
import type { Adventure } from "$lib/utils/types.js";
|
||||
import { addAdventure, getNextId } from "../../services/adventureService.js";
|
||||
|
||||
function add(event: CustomEvent<{ name: string; location: string }>) {
|
||||
console.log(event.detail);
|
||||
let newAdventure: Adventure = {
|
||||
id: getNextId(),
|
||||
name: event.detail.name,
|
||||
location: event.detail.location,
|
||||
created: "",
|
||||
};
|
||||
addAdventure(newAdventure);
|
||||
let count = 0;
|
||||
visitCount.subscribe((value) => {
|
||||
count = value;
|
||||
});
|
||||
|
||||
async function add(event: CustomEvent<{ name: string; location: string }>) {
|
||||
const response = await fetch("/api/visits", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: event.detail.name,
|
||||
location: event.detail.location,
|
||||
created: "",
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
goto("/login");
|
||||
} else {
|
||||
visitCount.update((n) => n + 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
15
src/routes/log/+page.server.ts
Normal file
15
src/routes/log/+page.server.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { redirect } from "@sveltejs/kit";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import type { Adventure } from "$lib/utils/types";
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (!event.locals.user) {
|
||||
return redirect(302, "/login");
|
||||
}
|
||||
const response = await event.fetch("/api/visits");
|
||||
const result = await response.json();
|
||||
// let array = result.adventures as Adventure[];
|
||||
return {
|
||||
result,
|
||||
};
|
||||
};
|
|
@ -1,4 +1,7 @@
|
|||
<script lang="ts">
|
||||
export let data;
|
||||
let adventures: Adventure[] = [];
|
||||
|
||||
import AdventureCard from "$lib/components/AdventureCard.svelte";
|
||||
import type { Adventure } from "$lib/utils/types";
|
||||
import {
|
||||
|
@ -6,8 +9,6 @@
|
|||
clearAdventures,
|
||||
getAdventures,
|
||||
getNextId,
|
||||
removeAdventure,
|
||||
saveEdit,
|
||||
} from "../../services/adventureService";
|
||||
import { onMount } from "svelte";
|
||||
import { exportData } from "../../services/export";
|
||||
|
@ -18,6 +19,7 @@
|
|||
import mapDrawing from "$lib/assets/adventure_map.svg";
|
||||
import EditModal from "$lib/components/EditModal.svelte";
|
||||
import { generateRandomString } from "$lib";
|
||||
import { visitCount } from "$lib/utils/stores/visitCountStore";
|
||||
|
||||
let newName = "";
|
||||
let newLocation = "";
|
||||
|
@ -27,11 +29,20 @@
|
|||
let editLocation: string = "";
|
||||
let editCreated: string = "";
|
||||
|
||||
let adventures: Adventure[] = [];
|
||||
|
||||
let isShowingToast: boolean = false;
|
||||
let toastAction: string = "";
|
||||
|
||||
// Sets the adventures array to the data from the server
|
||||
onMount(async () => {
|
||||
console.log(data);
|
||||
adventures = data.result.adventures;
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
visitCount.subscribe((value) => {
|
||||
count = value;
|
||||
});
|
||||
|
||||
function showToast(action: string) {
|
||||
toastAction = action;
|
||||
isShowingToast = true;
|
||||
|
@ -47,43 +58,77 @@
|
|||
const createNewAdventure = () => {
|
||||
let currentDate = new Date();
|
||||
let dateString = currentDate.toISOString().slice(0, 10); // Get date in "yyyy-mm-dd" format
|
||||
const newAdventure: Adventure = {
|
||||
id: getNextId(),
|
||||
name: newName,
|
||||
location: newLocation,
|
||||
created: dateString,
|
||||
};
|
||||
addAdventure(newAdventure);
|
||||
newName = ""; // Reset newName and newLocation after adding adventure
|
||||
newLocation = "";
|
||||
adventures = getAdventures(); // add to local array
|
||||
showToast("added");
|
||||
// post to /api/visits
|
||||
fetch("/api/visits", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: newName,
|
||||
location: newLocation,
|
||||
created: dateString,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
let newId = data.id;
|
||||
// add to local array for instant view update
|
||||
adventures = [
|
||||
...adventures,
|
||||
{
|
||||
id: newId,
|
||||
name: newName,
|
||||
location: newLocation,
|
||||
created: dateString,
|
||||
},
|
||||
];
|
||||
newName = ""; // Reset newName and newLocation after adding adventure
|
||||
newLocation = "";
|
||||
showToast("added");
|
||||
visitCount.update((n) => n + 1);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
adventures = getAdventures();
|
||||
});
|
||||
|
||||
function triggerRemoveAdventure(event: { detail: number }) {
|
||||
removeAdventure(event);
|
||||
showToast("removed");
|
||||
adventures = getAdventures();
|
||||
}
|
||||
|
||||
function saveAdventure(event: { detail: Adventure }) {
|
||||
console.log("Event" + event.detail);
|
||||
saveEdit(event.detail);
|
||||
editId = NaN;
|
||||
editName = "";
|
||||
editLocation = "";
|
||||
editCreated = "";
|
||||
adventures = getAdventures();
|
||||
showToast("edited");
|
||||
// put request to /api/visits with id and advneture data
|
||||
fetch("/api/visits", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: event.detail.id,
|
||||
name: event.detail.name,
|
||||
location: event.detail.location,
|
||||
created: event.detail.created,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Success:", data);
|
||||
// update local array with new data
|
||||
adventures = adventures.map((adventure) =>
|
||||
adventure.id === event.detail.id ? event.detail : adventure,
|
||||
);
|
||||
editId = NaN;
|
||||
editName = "";
|
||||
editLocation = "";
|
||||
editCreated = "";
|
||||
showToast("edited");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function editAdventure(event: { detail: number }) {
|
||||
const adventure = adventures.find(
|
||||
(adventure) => adventure.id === event.detail
|
||||
(adventure) => adventure.id === event.detail,
|
||||
);
|
||||
if (adventure) {
|
||||
editId = adventure.id;
|
||||
|
@ -122,9 +167,48 @@
|
|||
}
|
||||
|
||||
function deleteData() {
|
||||
clearAdventures();
|
||||
adventures = getAdventures();
|
||||
showToast("deleted");
|
||||
fetch("/api/clearvisits", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Success:", data);
|
||||
// remove adventure from array where id matches
|
||||
adventures = [];
|
||||
showToast("removed");
|
||||
visitCount.set(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function removeAdventure(event: { detail: number }) {
|
||||
console.log("Event ID " + event.detail);
|
||||
// send delete request to server at /api/visits
|
||||
fetch("/api/visits", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ id: event.detail }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Success:", data);
|
||||
// remove adventure from array where id matches
|
||||
adventures = adventures.filter(
|
||||
(adventure) => adventure.id !== event.detail,
|
||||
);
|
||||
showToast("removed");
|
||||
visitCount.update((n) => n - 1);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -184,8 +268,8 @@
|
|||
name={adventure.name}
|
||||
location={adventure.location}
|
||||
created={adventure.created}
|
||||
on:remove={triggerRemoveAdventure}
|
||||
on:edit={editAdventure}
|
||||
on:remove={removeAdventure}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
82
src/routes/login/+page.server.ts
Normal file
82
src/routes/login/+page.server.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { lucia } from "$lib/server/auth";
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
import { Argon2id } from "oslo/password";
|
||||
import { db } from "$lib/db/db.server";
|
||||
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import type { DatabaseUser } from "$lib/server/auth";
|
||||
import { userTable } from "$lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
return redirect(302, "/");
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const username = formData.get("username");
|
||||
const password = formData.get("password");
|
||||
|
||||
if (!username || !password) {
|
||||
return fail(400, {
|
||||
message: "Invalid request",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof username !== "string" ||
|
||||
username.length < 3 ||
|
||||
username.length > 31 ||
|
||||
!/^[a-z0-9_-]+$/.test(username)
|
||||
) {
|
||||
return fail(400, {
|
||||
message: "Invalid username",
|
||||
});
|
||||
}
|
||||
if (
|
||||
typeof password !== "string" ||
|
||||
password.length < 6 ||
|
||||
password.length > 255
|
||||
) {
|
||||
return fail(400, {
|
||||
message: "Invalid password",
|
||||
});
|
||||
}
|
||||
|
||||
const existingUser = await db
|
||||
.select()
|
||||
.from(userTable)
|
||||
.where(eq(userTable.username, username))
|
||||
.limit(1)
|
||||
.then((results) => results[0] as unknown as DatabaseUser | undefined);
|
||||
|
||||
if (!existingUser) {
|
||||
return fail(400, {
|
||||
message: "Incorrect username or password",
|
||||
});
|
||||
}
|
||||
|
||||
const validPassword = await new Argon2id().verify(
|
||||
existingUser.hashed_password,
|
||||
password
|
||||
);
|
||||
if (!validPassword) {
|
||||
return fail(400, {
|
||||
message: "Incorrect username or password",
|
||||
});
|
||||
}
|
||||
|
||||
const session = await lucia.createSession(existingUser.id, {});
|
||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
|
||||
return redirect(302, "/");
|
||||
},
|
||||
};
|
42
src/routes/login/+page.svelte
Normal file
42
src/routes/login/+page.svelte
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!-- routes/login/+page.svelte -->
|
||||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import { getRandomQuote } from "$lib";
|
||||
import { onMount } from "svelte";
|
||||
let quote: string = "";
|
||||
onMount(async () => {
|
||||
quote = getRandomQuote();
|
||||
});
|
||||
</script>
|
||||
|
||||
<article class="text-center text-4xl font-extrabold">
|
||||
<h1>Sign in</h1>
|
||||
</article>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<form method="post" use:enhance class="w-full max-w-xs">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
name="username"
|
||||
id="username"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<button class="py-2 px-4 btn btn-primary">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-12 mr-25 ml-25">
|
||||
<blockquote class="w-80 text-center text-lg break-words">
|
||||
{#if quote != ""}
|
||||
"{quote}"
|
||||
{/if}
|
||||
<!-- <footer class="text-sm">- Steve Jobs</footer> -->
|
||||
</blockquote>
|
||||
</div>
|
61
src/routes/settings/+page.server.ts
Normal file
61
src/routes/settings/+page.server.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { redirect, type Actions } from "@sveltejs/kit";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { db } from "$lib/db/db.server";
|
||||
import { userTable } from "$lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Argon2id } from "oslo/password";
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user)
|
||||
return {
|
||||
user: event.locals.user,
|
||||
};
|
||||
return redirect(302, "/login");
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event: { request: { formData: () => any; }; }) => {
|
||||
const formData = await event.request.formData();
|
||||
let userId = formData.get("user_id");
|
||||
let username = formData.get("username");
|
||||
let firstName = formData.get("first_name");
|
||||
let lastName = formData.get("last_name");
|
||||
|
||||
let password = formData.get("password");
|
||||
|
||||
if (!userId) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: "User ID is required"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (password) {
|
||||
let hashedPassword = await new Argon2id().hash(password);
|
||||
console.log(hashedPassword)
|
||||
await db.update(userTable)
|
||||
.set({
|
||||
hashed_password: hashedPassword
|
||||
})
|
||||
.where(eq(userTable.id, userId));
|
||||
}
|
||||
|
||||
await db.update(userTable)
|
||||
.set({
|
||||
username: username,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
})
|
||||
.where(eq(userTable.id, userId));
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
message: "User updated"
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
59
src/routes/settings/+page.svelte
Normal file
59
src/routes/settings/+page.svelte
Normal file
|
@ -0,0 +1,59 @@
|
|||
<script>
|
||||
import { enhance } from "$app/forms";
|
||||
|
||||
export let data;
|
||||
let username = data.user?.username;
|
||||
let first_name = data.user?.first_name;
|
||||
let last_name = data.user?.last_name;
|
||||
let user_id = data.user?.id;
|
||||
</script>
|
||||
|
||||
<h1 class="text-center font-extrabold text-4xl mb-6">Settings Page</h1>
|
||||
|
||||
<h1 class="text-center font-extrabold text-xl">User Account Settings</h1>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<form method="post" use:enhance class="w-full max-w-xs">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
bind:value={username}
|
||||
name="username"
|
||||
id="username"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<label for="first_name">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={first_name}
|
||||
name="first_name"
|
||||
id="first_name"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<label for="last_name">Last Name</label>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={last_name}
|
||||
name="last_name"
|
||||
id="last_name"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<!-- make hidden input where the user id is -->
|
||||
<input
|
||||
type="hidden"
|
||||
bind:value={user_id}
|
||||
name="user_id"
|
||||
id="user_id"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/>
|
||||
<button class="py-2 px-4 btn btn-primary">Update</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<small class="text-center">For Debug Use: UUID={user_id}</small>
|
105
src/routes/signup/+page.server.ts
Normal file
105
src/routes/signup/+page.server.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
// routes/signup/+page.server.ts
|
||||
import { lucia } from "$lib/server/auth";
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
import { generateId } from "lucia";
|
||||
import { Argon2id } from "oslo/password";
|
||||
import { db } from "$lib/db/db.server";
|
||||
import type { DatabaseUser } from "$lib/server/auth";
|
||||
|
||||
import type { Actions } from "./$types";
|
||||
import { userTable } from "$lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const username = formData.get("username");
|
||||
const password = formData.get("password");
|
||||
const firstName = formData.get("first_name");
|
||||
const lastName = formData.get("last_name");
|
||||
// username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _
|
||||
// keep in mind some database (e.g. mysql) are case insensitive
|
||||
|
||||
// check all to make sure all fields are provided
|
||||
if (!username || !password || !firstName || !lastName) {
|
||||
return fail(400, {
|
||||
message: "All fields are required",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof username !== "string" ||
|
||||
username.length < 3 ||
|
||||
username.length > 31 ||
|
||||
!/^[a-z0-9_-]+$/.test(username)
|
||||
) {
|
||||
return fail(400, {
|
||||
message: "Invalid username",
|
||||
});
|
||||
}
|
||||
if (
|
||||
typeof password !== "string" ||
|
||||
password.length < 6 ||
|
||||
password.length > 255
|
||||
) {
|
||||
return fail(400, {
|
||||
message: "Invalid password",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof firstName !== "string" ||
|
||||
firstName.length < 1 ||
|
||||
firstName.length > 255
|
||||
) {
|
||||
return fail(400, {
|
||||
message: "Invalid first name",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof lastName !== "string" ||
|
||||
lastName.length < 1 ||
|
||||
lastName.length > 255
|
||||
) {
|
||||
return fail(400, {
|
||||
message: "Invalid last name",
|
||||
});
|
||||
}
|
||||
|
||||
const userId = generateId(15);
|
||||
const hashedPassword = await new Argon2id().hash(password);
|
||||
|
||||
const usernameTaken = await db
|
||||
.select()
|
||||
.from(userTable)
|
||||
.where(eq(userTable.username, username))
|
||||
.limit(1)
|
||||
.then((results) => results[0] as unknown as DatabaseUser | undefined);
|
||||
|
||||
if (usernameTaken) {
|
||||
return fail(400, {
|
||||
message: "Username already taken",
|
||||
});
|
||||
}
|
||||
await db
|
||||
.insert(userTable)
|
||||
.values({
|
||||
id: userId,
|
||||
username: username,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
hashed_password: hashedPassword,
|
||||
} as DatabaseUser)
|
||||
.execute();
|
||||
|
||||
const session = await lucia.createSession(userId, {});
|
||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes,
|
||||
});
|
||||
|
||||
redirect(302, "/");
|
||||
},
|
||||
};
|
17
src/routes/signup/+page.svelte
Normal file
17
src/routes/signup/+page.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!-- routes/signup/+page.svelte -->
|
||||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
</script>
|
||||
|
||||
<h1>Sign up</h1>
|
||||
<form method="post" use:enhance>
|
||||
<label for="username">Username</label>
|
||||
<label for="first_name">First Name</label>
|
||||
<input name="first_name" id="first_name" /><br />
|
||||
<label for="last_name">Last Name</label>
|
||||
<input name="last_name" id="last_name" /><br />
|
||||
<input name="username" id="username" /><br />
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" /><br />
|
||||
<button>Continue</button>
|
||||
</form>
|
|
@ -7,13 +7,7 @@ import { visitCount } from "$lib/utils/stores/visitCountStore";
|
|||
// Check if localStorage is available (browser environment)
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
// Load adventures from localStorage on startup (only in the browser)
|
||||
if (isBrowser) {
|
||||
const storedAdventures = localStorage.getItem("adventures");
|
||||
if (storedAdventures) {
|
||||
adventures = JSON.parse(storedAdventures);
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
export function getNextId() {
|
||||
let nextId = Math.max(0, ...adventures.map((adventure) => adventure.id)) + 1;
|
||||
|
@ -37,36 +31,6 @@ export function getAdventures(): Adventure[] {
|
|||
return adventures;
|
||||
}
|
||||
|
||||
export function removeAdventure(event: { detail: number }) {
|
||||
adventures = adventures.filter((adventure) => adventure.id !== event.detail);
|
||||
if (isBrowser) {
|
||||
localStorage.setItem("adventures", JSON.stringify(adventures));
|
||||
visitCount.update((n) => n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveEdit(adventure: Adventure) {
|
||||
let editId = adventure.id;
|
||||
console.log("saving edit");
|
||||
let editName = adventure.name;
|
||||
let editLocation = adventure.location;
|
||||
let editCreated = adventure.created;
|
||||
let oldAdventure: Adventure | undefined = adventures.find(
|
||||
(adventure) => adventure.id === editId
|
||||
);
|
||||
console.log("old" + oldAdventure);
|
||||
if (oldAdventure) {
|
||||
oldAdventure.name = editName;
|
||||
oldAdventure.location = editLocation;
|
||||
oldAdventure.created = editCreated;
|
||||
}
|
||||
editId = NaN;
|
||||
console.log("done");
|
||||
if (isBrowser) {
|
||||
localStorage.setItem("adventures", JSON.stringify(adventures));
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAdventures() {
|
||||
adventures = [];
|
||||
if (isBrowser) {
|
||||
|
|
|
@ -17,8 +17,12 @@ echo "Starting AdventureLog"
|
|||
# Wait for the database to start up
|
||||
wait_for_db
|
||||
|
||||
# generate the schema
|
||||
npm run generate
|
||||
|
||||
# Run database migration
|
||||
npm run migrate
|
||||
|
||||
echo "The orgin to be set is: $ORIGIN"
|
||||
# Start the application
|
||||
node build/index.js
|
||||
ORIGIN=$ORIGIN node build
|
||||
|
|
|
@ -14,6 +14,7 @@ const config = {
|
|||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
csrf: { checkOrigin: true, }
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@ export default {
|
|||
},
|
||||
plugins: [require("@tailwindcss/typography"), require("daisyui")],
|
||||
daisyui: {
|
||||
themes: ["night"],
|
||||
themes: ["sunset"],
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue