From c2eb0df2da3741886cad8d9c74bb6e400db66960 Mon Sep 17 00:00:00 2001 From: rykuno Date: Wed, 26 Jun 2024 16:57:52 -0500 Subject: [PATCH] added deployment config --- .node-version | 1 + docker-compose.yaml | 28 +-- package.json | 1 + render.yaml | 27 +++ ..._crystal.sql => 0000_sudden_human_fly.sql} | 28 ++- .../migrations/meta/0000_snapshot.json | 210 +++++++++++------- .../database/migrations/meta/_journal.json | 4 +- .../email-verifications.repository.ts | 3 +- .../services/email-verifications.service.ts | 2 +- 9 files changed, 189 insertions(+), 115 deletions(-) create mode 100644 .node-version create mode 100644 render.yaml rename src/lib/server/api/infrastructure/database/migrations/{0000_first_crystal.sql => 0000_sudden_human_fly.sql} (57%) diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..7d1aef0 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.1.0 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 68cbdc6..b75a03c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,4 @@ -version: "3.8" +version: '3.8' services: postgres: image: postgres:latest @@ -7,37 +7,15 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - - "5432:5432" + - '5432:5432' volumes: - postgres_data:/var/lib/postgresql/data - redis: image: redis:latest ports: - - "6379:6379" + - '6379:6379' volumes: - redis_data:/data - - minio: - image: docker.io/bitnami/minio - ports: - - '9000:9000' - - '9001:9001' - networks: - - minionetwork - volumes: - - 'minio_data:/data' - environment: - - MINIO_ROOT_USER=user - - MINIO_ROOT_PASSWORD=password - - MINIO_DEFAULT_BUCKETS=dev - volumes: postgres_data: redis_data: - minio_data: - driver: local - -networks: - minionetwork: - driver: bridge diff --git a/package.json b/package.json index 2a7fccc..e96e5c0 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "test": "npm run test:integration && npm run test:unit", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "initialize": "pnpm install && docker-compose up --no-recreate -d && pnpm db:migrate", "lint": "prettier --check . && eslint .", "format": "prettier --write .", "test:integration": "playwright test", diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..68891e7 --- /dev/null +++ b/render.yaml @@ -0,0 +1,27 @@ +services: + - type: web + name: web + runtime: node + region: oregon # optional (defaults to oregon) + plan: starter # optional (defaults to starter instance type) + branch: main # optional (defaults to master) + buildCommand: npm install --force && npm run build + startCommand: npm run db:migrate && node build/index.js + healthCheckPath: / + envVars: + - key: DATABASE_URL + fromDatabase: + name: db + property: connectionString + - key: PUBLIC_ORIGIN + fromDatabase: + name: web + property: host + - type: redis + name: private redis + ipAllowList: [] # Only allow internal connections + +databases: + - name: db + databaseName: postgres + ipAllowList: [] diff --git a/src/lib/server/api/infrastructure/database/migrations/0000_first_crystal.sql b/src/lib/server/api/infrastructure/database/migrations/0000_sudden_human_fly.sql similarity index 57% rename from src/lib/server/api/infrastructure/database/migrations/0000_first_crystal.sql rename to src/lib/server/api/infrastructure/database/migrations/0000_sudden_human_fly.sql index e6794c0..a59f2b4 100644 --- a/src/lib/server/api/infrastructure/database/migrations/0000_first_crystal.sql +++ b/src/lib/server/api/infrastructure/database/migrations/0000_sudden_human_fly.sql @@ -1,18 +1,30 @@ -CREATE TABLE IF NOT EXISTS "sessions" ( +CREATE EXTENSION IF NOT EXISTS "citext"; + +CREATE TABLE IF NOT EXISTS "email_verifications" ( "id" text PRIMARY KEY NOT NULL, + "hashed_token" text NOT NULL, "user_id" text NOT NULL, - "expires_at" timestamp with time zone NOT NULL + "requested_email" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "email_verifications_user_id_unique" UNIQUE("user_id") ); -CREATE TABLE IF NOT EXISTS "tokens" ( +CREATE TABLE IF NOT EXISTS "login_requests" ( "id" text PRIMARY KEY NOT NULL, - "token" text NOT NULL, - "user_id" text NOT NULL, + "hashed_token" text NOT NULL, "email" text NOT NULL, "expires_at" timestamp with time zone NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "tokens_token_unique" UNIQUE("token") + CONSTRAINT "login_requests_email_unique" UNIQUE("email") +); + +CREATE TABLE IF NOT EXISTS "sessions" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL ); CREATE TABLE IF NOT EXISTS "users" ( @@ -26,13 +38,13 @@ CREATE TABLE IF NOT EXISTS "users" ( ); DO $$ BEGIN - ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "email_verifications" ADD CONSTRAINT "email_verifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN - ALTER TABLE "tokens" ADD CONSTRAINT "tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json index 27effb7..eafc6a1 100644 --- a/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json @@ -1,9 +1,141 @@ { - "id": "8bb6c6c1-e68f-4b94-a390-b51666d00dbb", + "id": "2e0c1e11-ed33-45bf-8084-c3200d8f65a8", "prevId": "00000000-0000-0000-0000-000000000000", "version": "6", "dialect": "postgresql", "tables": { + "public.email_verifications": { + "name": "email_verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "hashed_token": { + "name": "hashed_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_email": { + "name": "requested_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "email_verifications_user_id_users_id_fk": { + "name": "email_verifications_user_id_users_id_fk", + "tableFrom": "email_verifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_verifications_user_id_unique": { + "name": "email_verifications_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.login_requests": { + "name": "login_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "hashed_token": { + "name": "hashed_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "login_requests_email_unique": { + "name": "login_requests_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, "public.sessions": { "name": "sessions", "schema": "", @@ -46,82 +178,6 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "public.tokens": { - "name": "tokens", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "tokens_user_id_users_id_fk": { - "name": "tokens_user_id_users_id_fk", - "tableFrom": "tokens", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "tokens_token_unique": { - "name": "tokens_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - } - }, "public.users": { "name": "users", "schema": "", diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json b/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json index 0dbdf28..d4e9fe2 100644 --- a/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json +++ b/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1716599372513, - "tag": "0000_first_crystal", + "when": 1719436322147, + "tag": "0000_sudden_human_fly", "breakpoints": false } ] diff --git a/src/lib/server/api/repositories/email-verifications.repository.ts b/src/lib/server/api/repositories/email-verifications.repository.ts index 38b2b4c..5161be1 100644 --- a/src/lib/server/api/repositories/email-verifications.repository.ts +++ b/src/lib/server/api/repositories/email-verifications.repository.ts @@ -20,11 +20,10 @@ export class EmailVerificationsRepository implements Repository { } // finds a valid record by token and userId - async findValidRecord(userId: string, hashedToken: string) { + async findValidRecord(userId: string) { return this.db.select().from(emailVerificationsTable).where( and( eq(emailVerificationsTable.userId, userId), - eq(emailVerificationsTable.hashedToken, hashedToken), lte(emailVerificationsTable.expiresAt, new Date()) )).then(takeFirst) } diff --git a/src/lib/server/api/services/email-verifications.service.ts b/src/lib/server/api/services/email-verifications.service.ts index 1fe3c72..31d00af 100644 --- a/src/lib/server/api/services/email-verifications.service.ts +++ b/src/lib/server/api/services/email-verifications.service.ts @@ -63,7 +63,7 @@ export class EmailVerificationsService { private async findAndBurnEmailVerificationToken(userId: string, token: string) { return this.db.transaction(async (trx) => { // find a valid record - const emailVerificationRecord = await this.emailVerificationsRepository.trxHost(trx).findValidRecord(userId, token); + const emailVerificationRecord = await this.emailVerificationsRepository.trxHost(trx).findValidRecord(userId); if (!emailVerificationRecord) return null; // check if the token is valid