1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 05:05:17 +02:00

Merge pull request #23 from seanmorley15/adventureServiceMigration

Adventure service migration
This commit is contained in:
Sean Morley 2024-04-10 19:42:47 -04:00 committed by GitHub
commit ed54f7a97c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 3552 additions and 141 deletions

View file

@ -1,26 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // 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 // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{ {
"name": "Ubuntu", "name": "AdventureLog",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:jammy", "image": "mcr.microsoft.com/devcontainers/base:jammy",
"features": { "features": {
"ghcr.io/devcontainers-contrib/features/node-asdf:0": {}, "ghcr.io/devcontainers-contrib/features/node-asdf:0": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {} "ghcr.io/devcontainers/features/docker-in-docker:2": {}
} }
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
// "features": {}, // "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [], // "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a", // "postCreateCommand": "uname -a",
// Configure tool-specific properties. // Configure tool-specific properties.
// "customizations": {}, // "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root" // "remoteUser": "root"
} }

1
.env.example Normal file
View file

@ -0,0 +1 @@
DATABASE_URL=

91
CONTRIBUTING.md Normal file
View 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/

View file

@ -1,6 +1,6 @@
# AdventureLog: Embark, Explore, Remember. 🌍 # 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!"* ### *"Never forget an adventure with AdventureLog - Your ultimate travel companion!"*
----- -----
## Installation ## Installation

View file

@ -5,6 +5,8 @@ services:
- "3000:3000" - "3000:3000"
environment: environment:
- DATABASE_URL=postgres://adventurelog:PO24VjITwGgk@db:5432/adventurelog - 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: depends_on:
- db - db
db: db:

View 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 $$;

View 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;

View file

@ -0,0 +1 @@
ALTER TABLE "user" ALTER COLUMN "hashed_password" SET DATA TYPE varchar;

View 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;

View 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 $$;

View 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;

View 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": {}
}
}

View 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": {}
}
}

View 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": {}
}
}

View 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": {}
}
}

View 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": {}
}
}

View 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": {}
}
}

View file

@ -29,6 +29,48 @@
"when": 1712083977580, "when": 1712083977580,
"tag": "0003_clammy_goblin_queen", "tag": "0003_clammy_goblin_queen",
"breakpoints": true "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

File diff suppressed because it is too large Load diff

View file

@ -21,11 +21,13 @@
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tailwindcss/typography": "^0.5.12", "@tailwindcss/typography": "^0.5.12",
"@types/pg": "^8.11.4",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"daisyui": "^4.9.0", "daisyui": "^4.9.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"drizzle-kit": "^0.20.14", "drizzle-kit": "^0.20.14",
"pg": "^8.11.4", "lucia": "^3.1.1",
"pg": "^8.11.5",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"svelte": "^4.2.7", "svelte": "^4.2.7",
"svelte-check": "^3.6.0", "svelte-check": "^3.6.0",
@ -36,7 +38,9 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@lucia-auth/adapter-drizzle": "^1.0.7",
"drizzle-orm": "^0.30.6", "drizzle-orm": "^0.30.6",
"oslo": "^1.2.0",
"postgres": "^3.4.4" "postgres": "^3.4.4"
} }
} }

11
src/app.d.ts vendored
View file

@ -1,12 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global { declare global {
namespace App { namespace App {
// interface Error {} interface Locals {
// interface Locals {} user: import("lucia").User | null;
// interface PageData {} session: import("lucia").Session | null;
// interface PageState {} }
// interface Platform {}
} }
} }

32
src/hooks.server.ts Normal file
View 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
View 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

View 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>

View file

@ -1,7 +1,13 @@
<script lang="ts"> <script lang="ts">
import { enhance } from "$app/forms";
import { visitCount } from "$lib/utils/stores/visitCountStore"; import { visitCount } from "$lib/utils/stores/visitCountStore";
import { goto } from "$app/navigation"; 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() { async function goHome() {
goto("/"); goto("/");
} }
@ -11,8 +17,35 @@
async function goToFeatured() { async function goToFeatured() {
goto("/featured"); goto("/featured");
} }
async function toToLogin() {
goto("/login");
}
async function toToSignup() {
goto("/signup");
}
let count = 0; 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) => { visitCount.subscribe((value) => {
count = value; count = value;
}); });
@ -21,10 +54,6 @@
const isBrowser = typeof window !== "undefined"; const isBrowser = typeof window !== "undefined";
if (isBrowser) { if (isBrowser) {
const storedAdventures = localStorage.getItem("adventures"); const storedAdventures = localStorage.getItem("adventures");
if (storedAdventures) {
let parsed = JSON.parse(storedAdventures);
visitCount.set(parsed.length);
}
} }
</script> </script>
@ -34,10 +63,11 @@
class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2" class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2"
on:click={goHome}>Home</button on:click={goHome}>Home</button
> >
<button {#if user}
class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2" <button class="btn btn-primary my-2 md:my-0 md:mr-4" on:click={goToLog}
on:click={goToLog}>My Log</button >My Log</button
> >
{/if}
<button class="btn btn-primary my-2 md:my-0" on:click={goToFeatured} <button class="btn btn-primary my-2 md:my-0" on:click={goToFeatured}
>Featured</button >Featured</button
> >
@ -45,7 +75,20 @@
<div class="navbar-center flex justify-center md:justify-center"> <div class="navbar-center flex justify-center md:justify-center">
<a class="btn btn-ghost text-xl" href="/">AdventureLog 🗺️</a> <a class="btn btn-ghost text-xl" href="/">AdventureLog 🗺️</a>
</div> </div>
{#if infoModalOpen}
<InfoModal on:close={closeModal} />
{/if}
<div class="navbar-end flex justify-around md:justify-end mr-4"> <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>
</div> </div>

View 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
View file

@ -0,0 +1,3 @@
export let appVersion = "Web 0.0.1";
export let appTitle = "AdventureLog";
export let copyrightYear = "2024"

View file

@ -1,8 +1,9 @@
import { drizzle } from "drizzle-orm/postgres-js"; import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres"; import postgres from "postgres";
import dotenv from "dotenv"; import dotenv from "dotenv";
import * as schema from "$lib/db/schema";
dotenv.config(); dotenv.config();
const { DATABASE_URL } = process.env; const { DATABASE_URL } = process.env;
const client = postgres(DATABASE_URL); const client = postgres(DATABASE_URL || ""); // Pass DATABASE_URL as a string argument
export const db = drizzle(client, {}); export const db = drizzle(client, { schema });

View file

@ -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", { export const featuredAdventures = pgTable("featuredAdventures", {
id: serial("id").primaryKey(), id: serial("id").primaryKey(),
@ -10,3 +17,34 @@ export const sharedAdventures = pgTable("sharedAdventures", {
id: text("id").primaryKey(), id: text("id").primaryKey(),
data: json("data").notNull(), 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"),
});

View file

@ -10,3 +10,77 @@ export function generateRandomString() {
} }
return randomString; 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
View 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;
}

View file

@ -1,3 +1,5 @@
import { writable } from "svelte/store"; 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
View 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>

View 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,
};
};

View file

@ -1,12 +1,13 @@
<script> <script lang="ts">
export let data;
import Footer from "$lib/components/Footer.svelte"; import Footer from "$lib/components/Footer.svelte";
import Navbar from "$lib/components/Navbar.svelte"; import Navbar from "$lib/components/Navbar.svelte";
import "../app.css"; import "../app.css";
// only show footer if scrolled to the bottom // only show footer if scrolled to the bottom
</script> </script>
<Navbar /> <!-- passes the user object to the navbar component -->
<Navbar user={data.user} />
<section> <section>
<slot /> <slot />
</section> </section>

View 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");
},
};

View file

@ -1,4 +1,8 @@
<script lang="ts"> <script lang="ts">
import { enhance } from "$app/forms";
import type { PageData } from "./$types";
export let data: PageData;
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import campingDrawing from "$lib/assets/camping.svg"; import campingDrawing from "$lib/assets/camping.svg";
import { visitCount } from "$lib/utils/stores/visitCountStore"; import { visitCount } from "$lib/utils/stores/visitCountStore";
@ -9,9 +13,14 @@
</script> </script>
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<article class="prose"> {#if data.user && data.user.username != ""}
<h1 class="mb-4">Welcome. Let's get Exploring!</h1> <h1 class="mb-6 text-4xl font-extrabold">
</article> 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" /> <img src={campingDrawing} class="w-1/4 mb-4" alt="Logo" />
<button on:click={navToLog} class="btn btn-primary">Open Log</button> <button on:click={navToLog} class="btn btn-primary">Open Log</button>

View 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",
},
});
}

View 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",
},
});
}
}

View 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",
},
}
);
}

View 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",
},
}
);
}

View file

@ -1,19 +1,34 @@
<script lang="ts"> <script lang="ts">
export let data; export let data;
console.log(data.result); console.log(data.result);
import { goto } from "$app/navigation";
import AdventureCard from "$lib/components/AdventureCard.svelte"; import AdventureCard from "$lib/components/AdventureCard.svelte";
import { visitCount } from "$lib/utils/stores/visitCountStore.js";
import type { Adventure } from "$lib/utils/types.js"; import type { Adventure } from "$lib/utils/types.js";
import { addAdventure, getNextId } from "../../services/adventureService.js";
function add(event: CustomEvent<{ name: string; location: string }>) { let count = 0;
console.log(event.detail); visitCount.subscribe((value) => {
let newAdventure: Adventure = { count = value;
id: getNextId(), });
name: event.detail.name,
location: event.detail.location, async function add(event: CustomEvent<{ name: string; location: string }>) {
created: "", const response = await fetch("/api/visits", {
}; method: "POST",
addAdventure(newAdventure); 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> </script>

View 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,
};
};

View file

@ -1,4 +1,7 @@
<script lang="ts"> <script lang="ts">
export let data;
let adventures: Adventure[] = [];
import AdventureCard from "$lib/components/AdventureCard.svelte"; import AdventureCard from "$lib/components/AdventureCard.svelte";
import type { Adventure } from "$lib/utils/types"; import type { Adventure } from "$lib/utils/types";
import { import {
@ -6,8 +9,6 @@
clearAdventures, clearAdventures,
getAdventures, getAdventures,
getNextId, getNextId,
removeAdventure,
saveEdit,
} from "../../services/adventureService"; } from "../../services/adventureService";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { exportData } from "../../services/export"; import { exportData } from "../../services/export";
@ -18,6 +19,7 @@
import mapDrawing from "$lib/assets/adventure_map.svg"; import mapDrawing from "$lib/assets/adventure_map.svg";
import EditModal from "$lib/components/EditModal.svelte"; import EditModal from "$lib/components/EditModal.svelte";
import { generateRandomString } from "$lib"; import { generateRandomString } from "$lib";
import { visitCount } from "$lib/utils/stores/visitCountStore";
let newName = ""; let newName = "";
let newLocation = ""; let newLocation = "";
@ -27,11 +29,20 @@
let editLocation: string = ""; let editLocation: string = "";
let editCreated: string = ""; let editCreated: string = "";
let adventures: Adventure[] = [];
let isShowingToast: boolean = false; let isShowingToast: boolean = false;
let toastAction: string = ""; 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) { function showToast(action: string) {
toastAction = action; toastAction = action;
isShowingToast = true; isShowingToast = true;
@ -47,43 +58,77 @@
const createNewAdventure = () => { const createNewAdventure = () => {
let currentDate = new Date(); let currentDate = new Date();
let dateString = currentDate.toISOString().slice(0, 10); // Get date in "yyyy-mm-dd" format let dateString = currentDate.toISOString().slice(0, 10); // Get date in "yyyy-mm-dd" format
const newAdventure: Adventure = { // post to /api/visits
id: getNextId(), fetch("/api/visits", {
name: newName, method: "POST",
location: newLocation, headers: {
created: dateString, "Content-Type": "application/json",
}; },
addAdventure(newAdventure); body: JSON.stringify({
newName = ""; // Reset newName and newLocation after adding adventure name: newName,
newLocation = ""; location: newLocation,
adventures = getAdventures(); // add to local array created: dateString,
showToast("added"); }),
})
.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 }) { function saveAdventure(event: { detail: Adventure }) {
console.log("Event" + event.detail); console.log("Event" + event.detail);
saveEdit(event.detail); // put request to /api/visits with id and advneture data
editId = NaN; fetch("/api/visits", {
editName = ""; method: "PUT",
editLocation = ""; headers: {
editCreated = ""; "Content-Type": "application/json",
adventures = getAdventures(); },
showToast("edited"); 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 }) { function editAdventure(event: { detail: number }) {
const adventure = adventures.find( const adventure = adventures.find(
(adventure) => adventure.id === event.detail (adventure) => adventure.id === event.detail,
); );
if (adventure) { if (adventure) {
editId = adventure.id; editId = adventure.id;
@ -122,9 +167,48 @@
} }
function deleteData() { function deleteData() {
clearAdventures(); fetch("/api/clearvisits", {
adventures = getAdventures(); method: "DELETE",
showToast("deleted"); 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> </script>
@ -184,8 +268,8 @@
name={adventure.name} name={adventure.name}
location={adventure.location} location={adventure.location}
created={adventure.created} created={adventure.created}
on:remove={triggerRemoveAdventure}
on:edit={editAdventure} on:edit={editAdventure}
on:remove={removeAdventure}
/> />
{/each} {/each}
</div> </div>

View 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, "/");
},
};

View 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>

View 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"
}
};
}
};

View 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>

View 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, "/");
},
};

View 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>

View file

@ -7,13 +7,7 @@ import { visitCount } from "$lib/utils/stores/visitCountStore";
// Check if localStorage is available (browser environment) // Check if localStorage is available (browser environment)
const isBrowser = typeof window !== "undefined"; 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() { export function getNextId() {
let nextId = Math.max(0, ...adventures.map((adventure) => adventure.id)) + 1; let nextId = Math.max(0, ...adventures.map((adventure) => adventure.id)) + 1;
@ -37,36 +31,6 @@ export function getAdventures(): Adventure[] {
return adventures; 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() { export function clearAdventures() {
adventures = []; adventures = [];
if (isBrowser) { if (isBrowser) {

View file

@ -17,8 +17,12 @@ echo "Starting AdventureLog"
# Wait for the database to start up # Wait for the database to start up
wait_for_db wait_for_db
# generate the schema
npm run generate
# Run database migration # Run database migration
npm run migrate npm run migrate
echo "The orgin to be set is: $ORIGIN"
# Start the application # Start the application
node build/index.js ORIGIN=$ORIGIN node build

View file

@ -14,6 +14,7 @@ const config = {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
kit: { kit: {
adapter: adapter(), adapter: adapter(),
csrf: { checkOrigin: true, }
}, },
}; };

View file

@ -6,6 +6,6 @@ export default {
}, },
plugins: [require("@tailwindcss/typography"), require("daisyui")], plugins: [require("@tailwindcss/typography"), require("daisyui")],
daisyui: { daisyui: {
themes: ["night"], themes: ["sunset"],
}, },
}; };