From bf6d7b391b8a8223e3082cd49da72311927a02bd Mon Sep 17 00:00:00 2001 From: Adi <66127119+adi-lux@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:22:58 -0700 Subject: [PATCH] Schema fixes and backend (#71) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: ♻️ included database setup instructions into README.md Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ added more steps to README.md Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ modified schema.ts for increased normalization Signed-off-by: Adithya Anandsaikrishnan * fix: 🐛 modified schema to remove primary key error Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ overhauled api directory structure to match specification Signed-off-by: Adithya Anandsaikrishnan * chore: 🔧 uninstalled prisma Signed-off-by: Adithya Anandsaikrishnan * Revert "chore: 🔧 uninstalled prisma" This reverts commit 83bb95c08d401f2b802c735e57d84a67b569f0b5. * feat: ✨ added boilerplate to all api routes Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ removed api routes, modified schema.ts for flatter hierarchy Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ temporarily removed migration, clarified schema changes Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ modified scripts Signed-off-by: Adithya Anandsaikrishnan * fix: 🐛 remove redundant import and script order Signed-off-by: Adithya Anandsaikrishnan * chore: 🔧 remove redundant import after merge Signed-off-by: Adithya Anandsaikrishnan * chore: 🔧 remove unused page Signed-off-by: Adithya Anandsaikrishnan * refactor: ♻️ removed migration from postinstall Signed-off-by: Adithya Anandsaikrishnan --------- Signed-off-by: Adithya Anandsaikrishnan --- .gitignore | 5 +- README.md | 11 +- drizzle.config.ts | 2 +- migrations/0000_worried_nightcrawler.sql | 134 ------- migrations/0001_vengeful_korg.sql | 1 - migrations/meta/0000_snapshot.json | 471 ---------------------- migrations/meta/0001_snapshot.json | 477 ----------------------- migrations/meta/_journal.json | 20 - package.json | 8 +- src/lib/db/migrate.ts | 16 + src/lib/db/schema.ts | 146 ++++--- 11 files changed, 133 insertions(+), 1158 deletions(-) delete mode 100644 migrations/0000_worried_nightcrawler.sql delete mode 100644 migrations/0001_vengeful_korg.sql delete mode 100644 migrations/meta/0000_snapshot.json delete mode 100644 migrations/meta/0001_snapshot.json delete mode 100644 migrations/meta/_journal.json create mode 100644 src/lib/db/migrate.ts diff --git a/.gitignore b/.gitignore index 6786ecdb..04019b97 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,5 @@ vite.config.ts.timestamp-* .sst cdk.context.json -# db -src/lib/db/meta -src/lib/db/*.sql +# db, temporary until first release +/src/lib/db/migrations/ diff --git a/README.md b/README.md index c8c6bb54..3d6b9cfc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Simple, clean, and efficient meeting scheduling app. ## Tech Stack - [**S**ST](https://sst.dev) -- [**P**risma](https://prisma.io) +- [**D**rizzle](https://orm.drizzle.team/) - [Svelte**K**it](https://kit.svelte.dev) - [**L**ucia](https://lucia-auth.com) @@ -30,6 +30,15 @@ Simple, clean, and efficient meeting scheduling app. 1. `pnpm start` (run `pnpm start --host` if you want to access the server from other devices on your network) 4. The app should be viewable at `localhost:5173` by default. Changes to the code will automatically update the page. If you ran `pnpm start --host`, you can access the app from other devices on your network at `host-ip:5173`. +### Local Database Setup + +1. Go to the [Postgres official website](https://www.postgresql.org/download/) and download the database for your specific OS. \([Here](https://www.postgresql.org/docs/16/tutorial-start.html) is more information, if you get stuck) +2. While running the setup, ensure that pgAdmin is downloaded alongside Postgres itself +3. Once connected to the Postgres Server, Right click on databases -> create -> database, and name it `zotmeet`. +4. In the ZotMeet project root directory,`pnpm db:update` will generate a migrations folder which contain scripts you can run to update your database schema. +5. Create a .env file, and set `DATABASE_URL=postgres://yourusername:yourpassword@localhost:5432/zotmeet` +6. Using pgAdmin, run all the migration files in order. This can be done using the query tool. + ### Committing Changes - Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification when writing commit messages. diff --git a/drizzle.config.ts b/drizzle.config.ts index db24b1ea..f069844b 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -13,7 +13,7 @@ if (!DATABASE_URL) { export default { schema: "./src/lib/db/schema.ts", - out: "./src/lib/db", + out: "./src/lib/db/migrations", driver: "pg", dbCredentials: { connectionString: DATABASE_URL, diff --git a/migrations/0000_worried_nightcrawler.sql b/migrations/0000_worried_nightcrawler.sql deleted file mode 100644 index 68d8c7fc..00000000 --- a/migrations/0000_worried_nightcrawler.sql +++ /dev/null @@ -1,134 +0,0 @@ -CREATE SCHEMA "zotmeet"; ---> statement-breakpoint -DO $$ BEGIN - CREATE TYPE "attendance" AS ENUM('accepted', 'maybe', 'declined'); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."availabilities" ( - "day" date NOT NULL, - "user_id" uuid NOT NULL, - "block_length" smallint DEFAULT 15 NOT NULL, - "meeting_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000' NOT NULL, - "earliest_time" numeric, - "latest_time" numeric, - "availability_string" text NOT NULL, - CONSTRAINT "availabilities_user_id_day_meeting_id_pk" PRIMARY KEY("user_id","day","meeting_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."groups" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "name" text NOT NULL, - "description" text, - "created_at" timestamp -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."keys" ( - "id" text PRIMARY KEY NOT NULL, - "hashed_password" text, - "user_id" uuid NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."meetings" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "title" text NOT NULL, - "date" timestamp NOT NULL, - "description" text, - "location" text, - "from_time" timestamp NOT NULL, - "to_time" timestamp NOT NULL, - "group_id" uuid, - "host_id" uuid -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."sessions" ( - "id" text PRIMARY KEY NOT NULL, - "active_expires" bigint NOT NULL, - "idle_expires" bigint NOT NULL, - "user_id" uuid NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."users_in_group" ( - "user_id" uuid NOT NULL, - "group_id" uuid NOT NULL, - CONSTRAINT "users_in_group_group_id_user_id_pk" PRIMARY KEY("group_id","user_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."users_in_meeting" ( - "user_id" uuid NOT NULL, - "meeting_id" uuid NOT NULL, - "attendance" "attendance", - CONSTRAINT "users_in_meeting_user_id_meeting_id_pk" PRIMARY KEY("user_id","meeting_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "zotmeet"."user" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "username" text NOT NULL, - "email" text NOT NULL, - "created_at" timestamp, - CONSTRAINT "user_username_unique" UNIQUE("username"), - CONSTRAINT "user_email_unique" UNIQUE("email") -); ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS "user_idx_keys" ON "zotmeet"."keys" ("user_id");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "user_idx_sessions" ON "zotmeet"."sessions" ("user_id");--> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."availabilities" ADD CONSTRAINT "availabilities_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "zotmeet"."user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."availabilities" ADD CONSTRAINT "availabilities_meeting_id_meetings_id_fk" FOREIGN KEY ("meeting_id") REFERENCES "zotmeet"."meetings"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."keys" ADD CONSTRAINT "keys_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "zotmeet"."user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."meetings" ADD CONSTRAINT "meetings_group_id_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "zotmeet"."groups"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."meetings" ADD CONSTRAINT "meetings_host_id_user_id_fk" FOREIGN KEY ("host_id") REFERENCES "zotmeet"."user"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."sessions" ADD CONSTRAINT "sessions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "zotmeet"."user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."users_in_group" ADD CONSTRAINT "users_in_group_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "zotmeet"."user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."users_in_group" ADD CONSTRAINT "users_in_group_group_id_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "zotmeet"."groups"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."users_in_meeting" ADD CONSTRAINT "users_in_meeting_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "zotmeet"."user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "zotmeet"."users_in_meeting" ADD CONSTRAINT "users_in_meeting_meeting_id_meetings_id_fk" FOREIGN KEY ("meeting_id") REFERENCES "zotmeet"."meetings"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/migrations/0001_vengeful_korg.sql b/migrations/0001_vengeful_korg.sql deleted file mode 100644 index 31f750cf..00000000 --- a/migrations/0001_vengeful_korg.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "zotmeet"."meetings" ADD COLUMN "scheduled" boolean; \ No newline at end of file diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json deleted file mode 100644 index 696a9048..00000000 --- a/migrations/meta/0000_snapshot.json +++ /dev/null @@ -1,471 +0,0 @@ -{ - "id": "7886a02b-fc60-4c07-9b59-98e5990eb77a", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "5", - "dialect": "pg", - "tables": { - "availabilities": { - "name": "availabilities", - "schema": "zotmeet", - "columns": { - "day": { - "name": "day", - "type": "date", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "block_length": { - "name": "block_length", - "type": "smallint", - "primaryKey": false, - "notNull": true, - "default": 15 - }, - "meeting_id": { - "name": "meeting_id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "'00000000-0000-0000-0000-000000000000'" - }, - "earliest_time": { - "name": "earliest_time", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "latest_time": { - "name": "latest_time", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "availability_string": { - "name": "availability_string", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "availabilities_user_id_user_id_fk": { - "name": "availabilities_user_id_user_id_fk", - "tableFrom": "availabilities", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "availabilities_meeting_id_meetings_id_fk": { - "name": "availabilities_meeting_id_meetings_id_fk", - "tableFrom": "availabilities", - "tableTo": "meetings", - "schemaTo": "zotmeet", - "columnsFrom": ["meeting_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "availabilities_user_id_day_meeting_id_pk": { - "name": "availabilities_user_id_day_meeting_id_pk", - "columns": ["user_id", "day", "meeting_id"] - } - }, - "uniqueConstraints": {} - }, - "groups": { - "name": "groups", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "keys": { - "name": "keys", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "hashed_password": { - "name": "hashed_password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "user_idx_keys": { - "name": "user_idx_keys", - "columns": ["user_id"], - "isUnique": false - } - }, - "foreignKeys": { - "keys_user_id_user_id_fk": { - "name": "keys_user_id_user_id_fk", - "tableFrom": "keys", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "meetings": { - "name": "meetings", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "date": { - "name": "date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location": { - "name": "location", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "from_time": { - "name": "from_time", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "to_time": { - "name": "to_time", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "group_id": { - "name": "group_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "meetings_group_id_groups_id_fk": { - "name": "meetings_group_id_groups_id_fk", - "tableFrom": "meetings", - "tableTo": "groups", - "schemaTo": "zotmeet", - "columnsFrom": ["group_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "meetings_host_id_user_id_fk": { - "name": "meetings_host_id_user_id_fk", - "tableFrom": "meetings", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["host_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "sessions": { - "name": "sessions", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "active_expires": { - "name": "active_expires", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "idle_expires": { - "name": "idle_expires", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "user_idx_sessions": { - "name": "user_idx_sessions", - "columns": ["user_id"], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_user_id_user_id_fk": { - "name": "sessions_user_id_user_id_fk", - "tableFrom": "sessions", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "users_in_group": { - "name": "users_in_group", - "schema": "zotmeet", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "group_id": { - "name": "group_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "users_in_group_user_id_user_id_fk": { - "name": "users_in_group_user_id_user_id_fk", - "tableFrom": "users_in_group", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_in_group_group_id_groups_id_fk": { - "name": "users_in_group_group_id_groups_id_fk", - "tableFrom": "users_in_group", - "tableTo": "groups", - "schemaTo": "zotmeet", - "columnsFrom": ["group_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_in_group_group_id_user_id_pk": { - "name": "users_in_group_group_id_user_id_pk", - "columns": ["group_id", "user_id"] - } - }, - "uniqueConstraints": {} - }, - "users_in_meeting": { - "name": "users_in_meeting", - "schema": "zotmeet", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "meeting_id": { - "name": "meeting_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "attendance": { - "name": "attendance", - "type": "attendance", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "users_in_meeting_user_id_user_id_fk": { - "name": "users_in_meeting_user_id_user_id_fk", - "tableFrom": "users_in_meeting", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_in_meeting_meeting_id_meetings_id_fk": { - "name": "users_in_meeting_meeting_id_meetings_id_fk", - "tableFrom": "users_in_meeting", - "tableTo": "meetings", - "schemaTo": "zotmeet", - "columnsFrom": ["meeting_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_in_meeting_user_id_meeting_id_pk": { - "name": "users_in_meeting_user_id_meeting_id_pk", - "columns": ["user_id", "meeting_id"] - } - }, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_username_unique": { - "name": "user_username_unique", - "nullsNotDistinct": false, - "columns": ["username"] - }, - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - } - } - }, - "enums": { - "attendance": { - "name": "attendance", - "values": { - "accepted": "accepted", - "maybe": "maybe", - "declined": "declined" - } - } - }, - "schemas": { - "zotmeet": "zotmeet" - }, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json deleted file mode 100644 index 6420a8f6..00000000 --- a/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,477 +0,0 @@ -{ - "id": "b498ad6e-dc2c-49f9-83ae-4f5786a4cc48", - "prevId": "7886a02b-fc60-4c07-9b59-98e5990eb77a", - "version": "5", - "dialect": "pg", - "tables": { - "availabilities": { - "name": "availabilities", - "schema": "zotmeet", - "columns": { - "day": { - "name": "day", - "type": "date", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "block_length": { - "name": "block_length", - "type": "smallint", - "primaryKey": false, - "notNull": true, - "default": 15 - }, - "meeting_id": { - "name": "meeting_id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "'00000000-0000-0000-0000-000000000000'" - }, - "earliest_time": { - "name": "earliest_time", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "latest_time": { - "name": "latest_time", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "availability_string": { - "name": "availability_string", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "availabilities_user_id_user_id_fk": { - "name": "availabilities_user_id_user_id_fk", - "tableFrom": "availabilities", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "availabilities_meeting_id_meetings_id_fk": { - "name": "availabilities_meeting_id_meetings_id_fk", - "tableFrom": "availabilities", - "tableTo": "meetings", - "schemaTo": "zotmeet", - "columnsFrom": ["meeting_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "availabilities_user_id_day_meeting_id_pk": { - "name": "availabilities_user_id_day_meeting_id_pk", - "columns": ["user_id", "day", "meeting_id"] - } - }, - "uniqueConstraints": {} - }, - "groups": { - "name": "groups", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "keys": { - "name": "keys", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "hashed_password": { - "name": "hashed_password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "user_idx_keys": { - "name": "user_idx_keys", - "columns": ["user_id"], - "isUnique": false - } - }, - "foreignKeys": { - "keys_user_id_user_id_fk": { - "name": "keys_user_id_user_id_fk", - "tableFrom": "keys", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "meetings": { - "name": "meetings", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "date": { - "name": "date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location": { - "name": "location", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scheduled": { - "name": "scheduled", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "from_time": { - "name": "from_time", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "to_time": { - "name": "to_time", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "group_id": { - "name": "group_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "meetings_group_id_groups_id_fk": { - "name": "meetings_group_id_groups_id_fk", - "tableFrom": "meetings", - "tableTo": "groups", - "schemaTo": "zotmeet", - "columnsFrom": ["group_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "meetings_host_id_user_id_fk": { - "name": "meetings_host_id_user_id_fk", - "tableFrom": "meetings", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["host_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "sessions": { - "name": "sessions", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "active_expires": { - "name": "active_expires", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "idle_expires": { - "name": "idle_expires", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "user_idx_sessions": { - "name": "user_idx_sessions", - "columns": ["user_id"], - "isUnique": false - } - }, - "foreignKeys": { - "sessions_user_id_user_id_fk": { - "name": "sessions_user_id_user_id_fk", - "tableFrom": "sessions", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "users_in_group": { - "name": "users_in_group", - "schema": "zotmeet", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "group_id": { - "name": "group_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "users_in_group_user_id_user_id_fk": { - "name": "users_in_group_user_id_user_id_fk", - "tableFrom": "users_in_group", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_in_group_group_id_groups_id_fk": { - "name": "users_in_group_group_id_groups_id_fk", - "tableFrom": "users_in_group", - "tableTo": "groups", - "schemaTo": "zotmeet", - "columnsFrom": ["group_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_in_group_group_id_user_id_pk": { - "name": "users_in_group_group_id_user_id_pk", - "columns": ["group_id", "user_id"] - } - }, - "uniqueConstraints": {} - }, - "users_in_meeting": { - "name": "users_in_meeting", - "schema": "zotmeet", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "meeting_id": { - "name": "meeting_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "attendance": { - "name": "attendance", - "type": "attendance", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "users_in_meeting_user_id_user_id_fk": { - "name": "users_in_meeting_user_id_user_id_fk", - "tableFrom": "users_in_meeting", - "tableTo": "user", - "schemaTo": "zotmeet", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "users_in_meeting_meeting_id_meetings_id_fk": { - "name": "users_in_meeting_meeting_id_meetings_id_fk", - "tableFrom": "users_in_meeting", - "tableTo": "meetings", - "schemaTo": "zotmeet", - "columnsFrom": ["meeting_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "users_in_meeting_user_id_meeting_id_pk": { - "name": "users_in_meeting_user_id_meeting_id_pk", - "columns": ["user_id", "meeting_id"] - } - }, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "schema": "zotmeet", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_username_unique": { - "name": "user_username_unique", - "nullsNotDistinct": false, - "columns": ["username"] - }, - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - } - } - }, - "enums": { - "attendance": { - "name": "attendance", - "values": { - "accepted": "accepted", - "maybe": "maybe", - "declined": "declined" - } - } - }, - "schemas": { - "zotmeet": "zotmeet" - }, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json deleted file mode 100644 index e88858a7..00000000 --- a/migrations/meta/_journal.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "5", - "dialect": "pg", - "entries": [ - { - "idx": 0, - "version": "5", - "when": 1709806964509, - "tag": "0000_worried_nightcrawler", - "breakpoints": true - }, - { - "idx": 1, - "version": "5", - "when": 1710398557780, - "tag": "0001_vengeful_korg", - "breakpoints": true - } - ] -} diff --git a/package.json b/package.json index d1dc621a..530ca325 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,15 @@ "build": "vite build", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "db:pull": "drizzle-kit db:pull", + "db:push": "drizzle-kit db:push", + "db:update": "pnpm generate && pnpm migrate", "dev": "sst bind vite dev", "format": "prettier --write .", - "postinstall": "husky install && drizzle-kit generate:pg && svelte-kit sync", + "generate": "drizzle-kit generate:pg --config=drizzle.config.ts", + "postinstall": "husky install && pnpm generate && svelte-kit sync", "lint": "eslint --quiet --fix .", - "migrate": "drizzle-kit push:pg", + "migrate": "pnpm dlx tsx src/lib/db/migrate.ts", "preview": "vite preview", "sst:deploy": "sst deploy", "sst:dev": "sst dev", diff --git a/src/lib/db/migrate.ts b/src/lib/db/migrate.ts new file mode 100644 index 00000000..7077417e --- /dev/null +++ b/src/lib/db/migrate.ts @@ -0,0 +1,16 @@ +import "dotenv/config"; +import { drizzle } from "drizzle-orm/postgres-js"; +import { migrate } from "drizzle-orm/postgres-js/migrator"; +import postgres from "postgres"; + +const DATABASE_URL = process.env["DATABASE_URL"]; +if (!DATABASE_URL) { + throw new Error( + "DATABASE_URL not found. Please ensure you have the DATABASE_URL variable defined inside of your environment configuration.", + ); +} +const migrationClient = postgres(DATABASE_URL, { max: 1, ssl: "prefer" }); +const db = drizzle(migrationClient); + +await migrate(db, { migrationsFolder: "src/lib/db/migrations" }); +await migrationClient.end(); diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index 5c318fba..163e4016 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -7,80 +7,112 @@ import { index, smallint, date, - numeric, primaryKey, - // json, pgEnum, boolean, + json, } from "drizzle-orm/pg-core"; export const zotMeet = pgSchema("zotmeet"); export const attendanceEnum = pgEnum("attendance", ["accepted", "maybe", "declined"]); +export const memberEnum = pgEnum("member_type", ["guest", "user"]); -export const users = zotMeet.table("user", { +// Members encompasses anyone who uses ZotMeet, regardless of guest or user status. +export const members = zotMeet.table("members", { id: text("id").primaryKey(), + type: memberEnum("type").notNull().default("guest"), +}); + +// Users encompasses Members who have created an account. +export const users = zotMeet.table("users", { + id: text("id") + .primaryKey() + .references(() => members.id, { onDelete: "cascade" }), displayName: text("displayName").notNull(), email: text("email").unique().notNull(), password: text("password"), created_at: timestamp("created_at"), + authMethods: json("auth_methods").$type().notNull(), }); -export const oauthAccountsTable = zotMeet.table( - "oauth_accounts", +// Guests are Members who do not have an account and are bound to one specific meeting. +export const guests = zotMeet.table( + "guests", { - userId: text("user_id") - .notNull() - .references(() => users.id, { - onDelete: "cascade", - }), - - providerId: text("provider_id").notNull(), - providerUserId: text("provider_user_id").notNull(), + id: text("id").unique().notNull(), + username: text("username").notNull(), + meeting_id: uuid("meeting_id").references(() => meetings.id, { onDelete: "cascade" }), }, (table) => ({ - pk: primaryKey({ columns: [table.providerId, table.providerUserId] }), + pk: primaryKey({ columns: [table.username, table.meeting_id] }), }), ); export const meetings = zotMeet.table("meetings", { id: uuid("id").defaultRandom().primaryKey(), title: text("title").notNull(), - date: timestamp("date").notNull(), description: text("description"), location: text("location"), scheduled: boolean("scheduled"), from_time: timestamp("from_time").notNull(), to_time: timestamp("to_time").notNull(), group_id: uuid("group_id").references(() => groups.id, { onDelete: "cascade" }), - host_id: text("host_id").references(() => users.id), + host_id: text("host_id").references(() => members.id), }); +export const meetingDates = zotMeet.table( + "meeting_dates", + { + id: uuid("id").unique().defaultRandom(), + meeting_id: uuid("meeting_id").references(() => meetings.id, { onDelete: "cascade" }), + date: timestamp("date").notNull(), + }, + (table) => ({ + pk: primaryKey({ columns: [table.id, table.date] }), + }), +); + export const groups = zotMeet.table("groups", { id: uuid("id").defaultRandom().primaryKey(), name: text("name").notNull(), description: text("description"), created_at: timestamp("created_at"), + created_by: text("user_id").references(() => users.id), }); export const availabilities = zotMeet.table( "availabilities", { day: date("day").notNull(), - user_id: text("user_id") + member_id: text("member_id") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => members.id, { onDelete: "cascade" }), + meeting_day: uuid("meeting_day") + .references(() => meetingDates.id, { onDelete: "cascade" }) + .notNull(), block_length: smallint("block_length").notNull().default(15), - meeting_id: uuid("meeting_id") - .references(() => meetings.id, { onDelete: "cascade" }) - .notNull() - .default("00000000-0000-0000-0000-000000000000"), - earliest_time: numeric("earliest_time"), - latest_time: numeric("latest_time"), availability_string: text("availability_string").notNull(), + }, // user and neeting + (table) => ({ + pk: primaryKey({ columns: [table.member_id, table.meeting_day] }), + }), +); +// meeting_day +export const oauthAccountsTable = zotMeet.table( + "oauth_accounts", + { + userId: text("user_id") + .notNull() + .references(() => users.id, { + onDelete: "cascade", + }), + + providerId: text("provider_id").notNull(), + providerUserId: text("provider_user_id").notNull(), }, (table) => ({ - pk: primaryKey({ columns: [table.user_id, table.day, table.meeting_id] }), + pk: primaryKey({ columns: [table.providerId, table.providerUserId] }), }), ); @@ -102,7 +134,6 @@ export const sessions = zotMeet.table( }; }, ); - export const usersInGroup = zotMeet.table( "users_in_group", { @@ -118,10 +149,10 @@ export const usersInGroup = zotMeet.table( }), ); -export const usersInMeeting = zotMeet.table( - "users_in_meeting", +export const membersInMeeting = zotMeet.table( + "members_in_meeting", { - userId: text("user_id") + memberId: text("member_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), meetingId: uuid("meeting_id") @@ -130,15 +161,19 @@ export const usersInMeeting = zotMeet.table( attending: attendanceEnum("attendance"), }, (table) => ({ - pk: primaryKey({ columns: [table.userId, table.meetingId] }), + pk: primaryKey({ columns: [table.memberId, table.meetingId] }), }), ); -export const userRelations = relations(users, ({ many }) => ({ +export const memberRelations = relations(members, ({ many }) => ({ + availabilities: many(availabilities), + membersInMeeting: many(membersInMeeting), +})); + +export const userRelations = relations(users, ({ one, many }) => ({ + oauthAccountsTable: one(oauthAccountsTable), usersInGroups: many(usersInGroup), sessions: many(sessions), - availabilities: many(availabilities), - usersInMeeting: many(usersInMeeting), })); export const groupsRelations = relations(groups, ({ many }) => ({ @@ -146,7 +181,7 @@ export const groupsRelations = relations(groups, ({ many }) => ({ meetings: many(meetings), })); -export const userGroupMemberRelations = relations(usersInGroup, ({ one }) => ({ +export const usersInGroupRelations = relations(usersInGroup, ({ one }) => ({ groups: one(groups, { fields: [usersInGroup.groupId], references: [groups.id], @@ -157,14 +192,14 @@ export const userGroupMemberRelations = relations(usersInGroup, ({ one }) => ({ }), })); -export const userMeetingMemberRelations = relations(usersInMeeting, ({ one }) => ({ +export const membersInMeetingRelations = relations(membersInMeeting, ({ one }) => ({ groups: one(meetings, { - fields: [usersInMeeting.meetingId], + fields: [membersInMeeting.meetingId], references: [meetings.id], }), - users: one(users, { - fields: [usersInMeeting.userId], - references: [users.id], + members: one(members, { + fields: [membersInMeeting.memberId], + references: [members.id], }), })); @@ -173,12 +208,20 @@ export const meetingsRelations = relations(meetings, ({ one, many }) => ({ fields: [meetings.group_id], references: [groups.id], }), - users: one(users, { + members: one(members, { fields: [meetings.host_id], - references: [users.id], + references: [members.id], }), availabilities: many(availabilities), - usersInMeeting: many(usersInMeeting), + membersInMeeting: many(membersInMeeting), + meetingDates: many(meetingDates), +})); + +export const meetingDateRelations = relations(meetingDates, ({ one }) => ({ + meetings: one(meetings, { + fields: [meetingDates.meeting_id], + references: [meetings.id], + }), })); export const sessionsRelations = relations(sessions, ({ one }) => ({ @@ -188,15 +231,22 @@ export const sessionsRelations = relations(sessions, ({ one }) => ({ }), })); -export const availabilitiesRelations = relations(availabilities, ({ one }) => ({ - meetings: one(meetings, { - fields: [availabilities.meeting_id], - references: [meetings.id], - }), +export const oauthRelations = relations(oauthAccountsTable, ({ one }) => ({ users: one(users, { - fields: [availabilities.user_id], + fields: [oauthAccountsTable.userId], references: [users.id], }), })); +export const availabilitiesRelations = relations(availabilities, ({ one }) => ({ + meetingDates: one(meetingDates, { + fields: [availabilities.meeting_day], + references: [meetingDates.id], + }), + members: one(members, { + fields: [availabilities.member_id], + references: [members.id], + }), +})); + export type UserInsertSchema = typeof users.$inferInsert;