From ac8721d2643effcef7c59a2fbff38228bc082f43 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 16 Jul 2024 14:54:32 -0700 Subject: [PATCH 01/34] Adding docker-compose for future use, using Card on all auth pages, adding start of reset password. --- .node-version | 2 +- .prettierrc | 2 +- components.json | 3 +- docker-compose.yaml | 20 + package.json | 18 +- pnpm-lock.yaml | 106 +++--- src/lib/constants/status-codes.ts | 357 ++++++++++++++++++ src/lib/validations/auth.ts | 8 + src/routes/(auth)/+layout.svelte | 50 ++- src/routes/(auth)/login/+page.svelte | 40 +- .../(auth)/password/reset/+page.server.ts | 40 +- src/routes/(auth)/password/reset/+page.svelte | 83 +++- src/routes/(auth)/sign-up/+page.svelte | 13 +- src/routes/(auth)/totp/+page.svelte | 30 +- 14 files changed, 661 insertions(+), 111 deletions(-) create mode 100644 docker-compose.yaml create mode 100644 src/lib/constants/status-codes.ts diff --git a/.node-version b/.node-version index aaccf12..119f15a 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.6.1 \ No newline at end of file +20.15.1 \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index b6cceea..b362ec7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,7 +5,7 @@ "trailingComma": "all", "bracketSpacing": true, "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "pluginSearchDirs": ["."], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] } diff --git a/components.json b/components.json index 16aece3..767bdc4 100644 --- a/components.json +++ b/components.json @@ -9,5 +9,6 @@ "aliases": { "components": "$lib/components", "utils": "$lib/utils" - } + }, + "typescript": true } \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..1d1d5ac --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,20 @@ +services: + postgres: + image: postgres:latest + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - '5432:5432' + volumes: + - postgres_data:/var/lib/postgresql/data + redis: + image: redis:latest + ports: + - '6379:6379' + volumes: + - redis_data:/data +volumes: + postgres_data: + redis_data: diff --git a/package.json b/package.json index e1dd14b..c239ed3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,11 @@ "version": "0.0.2", "private": "true", "scripts": { + "db:push": "drizzle-kit push", + "db:generate": "drizzle-kit generate", + "db:migrate": "tsx src/db/migrate.ts", + "db:seed": "tsx src/db/seed.ts", + "db:studio": "drizzle-kit studio --verbose", "dev": "NODE_OPTIONS=\"--inspect\" vite dev --host", "build": "vite build", "package": "svelte-kit package", @@ -11,14 +16,11 @@ "test:ui": "svelte-kit sync && playwright test --ui", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test:unit": "vitest", + "initialize": "pnpm install && docker compose up --no-recreate -d && pnpm db:migrate", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write .", "site:update": "pnpm update -i -L", - "generate": "drizzle-kit generate", - "migrate": "tsx src/db/migrate.ts", - "seed": "tsx src/db/seed.ts", - "push": "drizzle-kit push" + "test:unit": "vitest" }, "devDependencies": { "@melt-ui/pp": "^0.3.2", @@ -57,14 +59,14 @@ "svelte-preprocess": "^6.0.2", "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", - "sveltekit-rate-limiter": "^0.5.1", + "sveltekit-rate-limiter": "^0.5.2", "sveltekit-superforms": "^2.16.0", - "tailwindcss": "^3.4.4", + "tailwindcss": "^3.4.6", "ts-node": "^10.9.2", "tslib": "^2.6.3", "tsx": "^4.16.2", "typescript": "^5.5.3", - "vite": "^5.3.3", + "vite": "^5.3.4", "vitest": "^1.6.0", "zod": "^3.23.8" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 926718a..05c0a5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 2.2.2 '@sveltejs/adapter-vercel': specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))) + version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -67,7 +67,7 @@ importers: version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -118,10 +118,10 @@ importers: version: 2.4.0 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) + version: 0.2.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) + version: 1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) zod-to-json-schema: specifier: ^3.23.1 version: 3.23.1(zod@3.23.8) @@ -140,16 +140,16 @@ importers: version: 2.6.2 '@sveltejs/adapter-auto': specifier: ^3.2.2 - version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))) + version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))) '@sveltejs/enhanced-img': specifier: ^0.3.0 version: 0.3.0(rollup@4.18.1)(svelte@5.0.0-next.175) '@sveltejs/kit': specifier: ^2.5.18 - version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 @@ -233,16 +233,16 @@ importers: version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: - specifier: ^0.5.1 - version: 0.5.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))) + specifier: ^0.5.2 + version: 0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.16.0 - version: 2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: - specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + specifier: ^3.4.6 + version: 3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.14.10)(typescript@5.5.3) @@ -256,8 +256,8 @@ importers: specifier: ^5.5.3 version: 5.5.3 vite: - specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.77.8) + specifier: ^5.3.4 + version: 5.3.4(@types/node@20.14.10)(sass@1.77.8) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.14.10)(sass@1.77.8) @@ -4065,8 +4065,8 @@ packages: '@sveltejs/kit': 1.x || 2.x svelte: 3.x || 4.x || >=5.0.0-next.51 - sveltekit-rate-limiter@0.5.1: - resolution: {integrity: sha512-Q2C7mT9PdoL6v3VXgxngyXiEg2i3Dp0iVjVvKi722lroTM7oHxAJsmj66607BiSw8mdQk1Me6nhE6uRXrkDVIg==} + sveltekit-rate-limiter@0.5.2: + resolution: {integrity: sha512-7CELKmTffNjj0i/RUxT9SKYFA9IO/tQabjgT39clOlkKvlcGozNy8nqoIx+24amWfqEqC/WXYMEIek04PiFdyA==} peerDependencies: '@sveltejs/kit': 1.x || 2.x @@ -4093,8 +4093,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.4: - resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} + tailwindcss@3.4.6: + resolution: {integrity: sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==} engines: {node: '>=14.0.0'} hasBin: true @@ -4263,8 +4263,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.3.3: - resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} + vite@5.3.4: + resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5604,14 +5604,14 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: @@ -5627,9 +5627,9 @@ snapshots: - rollup - svelte - '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))': + '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -5643,28 +5643,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8) + vite: 5.3.4(@types/node@20.14.10)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8) + vite: 5.3.4(@types/node@20.14.10)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8) - vitefu: 0.2.5(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + vite: 5.3.4(@types/node@20.14.10)(sass@1.77.8) + vitefu: 0.2.5(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6566,11 +6566,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175) fraction.js@4.3.7: {} @@ -7928,19 +7928,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) - sveltekit-superforms@2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.16.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -7965,16 +7965,16 @@ snapshots: tailwind-merge@2.4.0: {} - tailwind-variants@0.2.1(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): + tailwind-variants@0.2.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): dependencies: tailwind-merge: 2.4.0 - tailwindcss: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): dependencies: - tailwindcss: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) - tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)): + tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8156,7 +8156,7 @@ snapshots: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8) + vite: 5.3.4(@types/node@20.14.10)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -8167,7 +8167,7 @@ snapshots: - supports-color - terser - vite@5.3.3(@types/node@20.14.10)(sass@1.77.8): + vite@5.3.4(@types/node@20.14.10)(sass@1.77.8): dependencies: esbuild: 0.21.5 postcss: 8.4.39 @@ -8177,9 +8177,9 @@ snapshots: fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)): + vitefu@0.2.5(vite@5.3.4(@types/node@20.14.10)(sass@1.77.8)): optionalDependencies: - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8) + vite: 5.3.4(@types/node@20.14.10)(sass@1.77.8) vitest@1.6.0(@types/node@20.14.10)(sass@1.77.8): dependencies: @@ -8200,7 +8200,7 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8) + vite: 5.3.4(@types/node@20.14.10)(sass@1.77.8) vite-node: 1.6.0(@types/node@20.14.10)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: diff --git a/src/lib/constants/status-codes.ts b/src/lib/constants/status-codes.ts new file mode 100644 index 0000000..1bcf271 --- /dev/null +++ b/src/lib/constants/status-codes.ts @@ -0,0 +1,357 @@ +// Taken from https://github.com/prettymuchbryce/http-status-codes/blob/master/src/status-codes.ts +export enum StatusCodes { + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1 + * + * This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished. + */ + CONTINUE = 100, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.2 + * + * This code is sent in response to an Upgrade request header by the client, and indicates the protocol the server is switching too. + */ + SWITCHING_PROTOCOLS = 101, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.1 + * + * This code indicates that the server has received and is processing the request, but no response is available yet. + */ + PROCESSING = 102, + /** + * Official Documentation @ https://www.rfc-editor.org/rfc/rfc8297#page-3 + * + * This code indicates to the client that the server is likely to send a final response with the header fields included in the informational response. + */ + EARLY_HINTS = 103, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.1 + * + * The request has succeeded. The meaning of a success varies depending on the HTTP method: + * GET: The resource has been fetched and is transmitted in the message body. + * HEAD: The entity headers are in the message body. + * POST: The resource describing the result of the action is transmitted in the message body. + * TRACE: The message body contains the request message as received by the server + */ + OK = 200, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.2 + * + * The request has succeeded and a new resource has been created as a result of it. This is typically the response sent after a PUT request. + */ + CREATED = 201, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.3 + * + * The request has been received but not yet acted upon. It is non-committal, meaning that there is no way in HTTP to later send an asynchronous response indicating the outcome of processing the request. It is intended for cases where another process or server handles the request, or for batch processing. + */ + ACCEPTED = 202, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.4 + * + * This response code means returned meta-information set is not exact set as available from the origin server, but collected from a local or a third party copy. Except this condition, 200 OK response should be preferred instead of this response. + */ + NON_AUTHORITATIVE_INFORMATION = 203, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5 + * + * There is no content to send for this request, but the headers may be useful. The user-agent may update its cached headers for this resource with the new ones. + */ + NO_CONTENT = 204, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.6 + * + * This response code is sent after accomplishing request to tell user agent reset document view which sent this request. + */ + RESET_CONTENT = 205, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.1 + * + * This response code is used because of range header sent by the client to separate download into multiple streams. + */ + PARTIAL_CONTENT = 206, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.2 + * + * A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate. + */ + MULTI_STATUS = 207, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.1 + * + * The request has more than one possible responses. User-agent or user should choose one of them. There is no standardized way to choose one of the responses. + */ + MULTIPLE_CHOICES = 300, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.2 + * + * This response code means that URI of requested resource has been changed. Probably, new URI would be given in the response. + */ + MOVED_PERMANENTLY = 301, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.3 + * + * This response code means that URI of requested resource has been changed temporarily. New changes in the URI might be made in the future. Therefore, this same URI should be used by the client in future requests. + */ + MOVED_TEMPORARILY = 302, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.4 + * + * Server sent this response to directing client to get requested resource to another URI with an GET request. + */ + SEE_OTHER = 303, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.1 + * + * This is used for caching purposes. It is telling to client that response has not been modified. So, client can continue to use same cached version of response. + */ + NOT_MODIFIED = 304, + /** + * @deprecated + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.6 + * + * Was defined in a previous version of the HTTP specification to indicate that a requested response must be accessed by a proxy. It has been deprecated due to security concerns regarding in-band configuration of a proxy. + */ + USE_PROXY = 305, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.7 + * + * Server sent this response to directing client to get requested resource to another URI with same method that used prior request. This has the same semantic than the 302 Found HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request. + */ + TEMPORARY_REDIRECT = 307, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7538#section-3 + * + * This means that the resource is now permanently located at another URI, specified by the Location: HTTP Response header. This has the same semantics as the 301 Moved Permanently HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request. + */ + PERMANENT_REDIRECT = 308, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.1 + * + * This response means that server could not understand the request due to invalid syntax. + */ + BAD_REQUEST = 400, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.1 + * + * Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. + */ + UNAUTHORIZED = 401, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.2 + * + * This response code is reserved for future use. Initial aim for creating this code was using it for digital payment systems however this is not used currently. + */ + PAYMENT_REQUIRED = 402, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.3 + * + * The client does not have access rights to the content, i.e. they are unauthorized, so server is rejecting to give proper response. Unlike 401, the client's identity is known to the server. + */ + FORBIDDEN = 403, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.4 + * + * The server can not find requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurence on the web. + */ + NOT_FOUND = 404, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.5 + * + * The request method is known by the server but has been disabled and cannot be used. For example, an API may forbid DELETE-ing a resource. The two mandatory methods, GET and HEAD, must never be disabled and should not return this error code. + */ + METHOD_NOT_ALLOWED = 405, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.6 + * + * This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent. + */ + NOT_ACCEPTABLE = 406, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.2 + * + * This is similar to 401 but authentication is needed to be done by a proxy. + */ + PROXY_AUTHENTICATION_REQUIRED = 407, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7 + * + * This response is sent on an idle connection by some servers, even without any previous request by the client. It means that the server would like to shut down this unused connection. This response is used much more since some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing. Also note that some servers merely shut down the connection without sending this message. + */ + REQUEST_TIMEOUT = 408, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.8 + * + * This response is sent when a request conflicts with the current state of the server. + */ + CONFLICT = 409, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.9 + * + * This response would be sent when the requested content has been permenantly deleted from server, with no forwarding address. Clients are expected to remove their caches and links to the resource. The HTTP specification intends this status code to be used for "limited-time, promotional services". APIs should not feel compelled to indicate resources that have been deleted with this status code. + */ + GONE = 410, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.10 + * + * The server rejected the request because the Content-Length header field is not defined and the server requires it. + */ + LENGTH_REQUIRED = 411, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.2 + * + * The client has indicated preconditions in its headers which the server does not meet. + */ + PRECONDITION_FAILED = 412, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.11 + * + * Request entity is larger than limits defined by server; the server might close the connection or return an Retry-After header field. + */ + REQUEST_TOO_LONG = 413, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.12 + * + * The URI requested by the client is longer than the server is willing to interpret. + */ + REQUEST_URI_TOO_LONG = 414, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.13 + * + * The media format of the requested data is not supported by the server, so the server is rejecting the request. + */ + UNSUPPORTED_MEDIA_TYPE = 415, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.4 + * + * The range specified by the Range header field in the request can't be fulfilled; it's possible that the range is outside the size of the target URI's data. + */ + REQUESTED_RANGE_NOT_SATISFIABLE = 416, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.14 + * + * This response code means the expectation indicated by the Expect request header field can't be met by the server. + */ + EXPECTATION_FAILED = 417, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2324#section-2.3.2 + * + * Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout. + */ + IM_A_TEAPOT = 418, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6 + * + * The 507 (Insufficient Storage) status code means the method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request. This condition is considered to be temporary. If the request which received this status code was the result of a user action, the request MUST NOT be repeated until it is requested by a separate user action. + */ + INSUFFICIENT_SPACE_ON_RESOURCE = 419, + /** + * @deprecated + * Official Documentation @ https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt + * + * A deprecated response used by the Spring Framework when a method has failed. + */ + METHOD_FAILURE = 420, + /** + * Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.2 + * + * Defined in the specification of HTTP/2 to indicate that a server is not able to produce a response for the combination of scheme and authority that are included in the request URI. + */ + MISDIRECTED_REQUEST = 421, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.3 + * + * The request was well-formed but was unable to be followed due to semantic errors. + */ + UNPROCESSABLE_ENTITY = 422, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.4 + * + * The resource that is being accessed is locked. + */ + LOCKED = 423, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.5 + * + * The request failed due to failure of a previous request. + */ + FAILED_DEPENDENCY = 424, + /** + * Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.15 + * + * The server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol. + */ + UPGRADE_REQUIRED = 426, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-3 + * + * The origin server requires the request to be conditional. Intended to prevent the 'lost update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict. + */ + PRECONDITION_REQUIRED = 428, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-4 + * + * The user has sent too many requests in a given amount of time ("rate limiting"). + */ + TOO_MANY_REQUESTS = 429, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-5 + * + * The server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields. + */ + REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7725 + * + * The user-agent requested a resource that cannot legally be provided, such as a web page censored by a government. + */ + UNAVAILABLE_FOR_LEGAL_REASONS = 451, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.1 + * + * The server encountered an unexpected condition that prevented it from fulfilling the request. + */ + INTERNAL_SERVER_ERROR = 500, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.2 + * + * The request method is not supported by the server and cannot be handled. The only methods that servers are required to support (and therefore that must not return this code) are GET and HEAD. + */ + NOT_IMPLEMENTED = 501, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.3 + * + * This error response means that the server, while working as a gateway to get a response needed to handle the request, got an invalid response. + */ + BAD_GATEWAY = 502, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.4 + * + * The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded. Note that together with this response, a user-friendly page explaining the problem should be sent. This responses should be used for temporary conditions and the Retry-After: HTTP header should, if possible, contain the estimated time before the recovery of the service. The webmaster must also take care about the caching-related headers that are sent along with this response, as these temporary condition responses should usually not be cached. + */ + SERVICE_UNAVAILABLE = 503, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.5 + * + * This error response is given when the server is acting as a gateway and cannot get a response in time. + */ + GATEWAY_TIMEOUT = 504, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.6 + * + * The HTTP version used in the request is not supported by the server. + */ + HTTP_VERSION_NOT_SUPPORTED = 505, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6 + * + * The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process. + */ + INSUFFICIENT_STORAGE = 507, + /** + * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-6 + * + * The 511 status code indicates that the client needs to authenticate to gain network access. + */ + NETWORK_AUTHENTICATION_REQUIRED = 511 +} \ No newline at end of file diff --git a/src/lib/validations/auth.ts b/src/lib/validations/auth.ts index ba17051..a8f6028 100644 --- a/src/lib/validations/auth.ts +++ b/src/lib/validations/auth.ts @@ -31,3 +31,11 @@ export const totpSchema = z.object({ export const recoveryCodeSchema = z.object({ recoveryCode: z.string().trim().min(10).max(10), }); + +export const resetPasswordEmailSchema = z.object({ + email: z.string().trim().max(64), +}); + +export const resetPasswordTokenSchema = z.object({ + resetToken: z.string().trim().min(6).max(6), +}); \ No newline at end of file diff --git a/src/routes/(auth)/+layout.svelte b/src/routes/(auth)/+layout.svelte index 6eda99d..171e892 100644 --- a/src/routes/(auth)/+layout.svelte +++ b/src/routes/(auth)/+layout.svelte @@ -14,17 +14,38 @@ Bored Game - + {:else if $page.url.pathname === "/login"} + + + {:else} +
+ + +
+ {/if}
= 768px) { + top: 2rem; + right: 2rem; + } + } + .auth-logo { display: flex; position: relative; diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 92c6f36..86b54a4 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -4,6 +4,7 @@ import * as flashModule from 'sveltekit-flash-message/client'; import { AlertCircle } from "lucide-svelte"; import { signInSchema } from '$lib/validations/auth'; + import * as Card from '$lib/components/ui/card'; import * as Form from '$lib/components/ui/form'; import { Label } from '$components/ui/label'; import { Input } from '$components/ui/input'; @@ -40,24 +41,24 @@ Bored Game | Login - + + + {@render usernamePasswordForm()} +

+ By clicking continue, you agree to our + + Terms of Use + + and + + Privacy Policy + . +

+
+ {#snippet usernamePasswordForm()}
@@ -75,7 +76,10 @@ - Login +
+ Login + +
{/snippet} diff --git a/src/routes/(auth)/password/reset/+page.server.ts b/src/routes/(auth)/password/reset/+page.server.ts index 3af3e22..ab1794c 100644 --- a/src/routes/(auth)/password/reset/+page.server.ts +++ b/src/routes/(auth)/password/reset/+page.server.ts @@ -1,3 +1,41 @@ +import { fail, error, type Actions } from '@sveltejs/kit'; +import { zod } from 'sveltekit-superforms/adapters'; +import { setError, superValidate } from 'sveltekit-superforms/server'; +import { redirect } from 'sveltekit-flash-message/server'; +import type { PageServerLoad } from './$types'; +import {resetPasswordEmailSchema, resetPasswordTokenSchema} from "$lib/validations/auth"; +import {StatusCodes} from "$lib/constants/status-codes"; + export const load: PageServerLoad = async (event) => { - return {}; + return { + emailForm: await superValidate(zod(resetPasswordEmailSchema)), + tokenForm: await superValidate(zod(resetPasswordTokenSchema)), + }; +}; + +export const actions = { + passwordReset: async ({ locals, request }) => { + const emailForm = await superValidate(request, zod(resetPasswordEmailSchema)); + if (!emailForm.valid) { + return fail(StatusCodes.BAD_REQUEST, { emailForm }); + } + const error = {}; + // const { error } = await locals.api.iam.login.request.$post({ json: emailRegisterForm.data }).then(locals.parseApiResponse); + if (error) { + return setError(emailForm, 'email', error); + } + return { emailForm }; + }, + verifyToken: async ({ locals, request }) => { + const tokenForm = await superValidate(request, zod(resetPasswordTokenSchema)); + if (!tokenForm.valid) { + return fail(StatusCodes.BAD_REQUEST, { tokenForm }); + } + const error = {}; + // const { error } = await locals.api.iam.login.verify.$post({ json: emailSignInForm.data }).then(locals.parseApiResponse) + if (error) { + return setError(tokenForm, 'token', error); + } + redirect(301, '/'); + } }; diff --git a/src/routes/(auth)/password/reset/+page.svelte b/src/routes/(auth)/password/reset/+page.svelte index 6fb94d1..5a836d0 100644 --- a/src/routes/(auth)/password/reset/+page.svelte +++ b/src/routes/(auth)/password/reset/+page.svelte @@ -1,4 +1,85 @@ -

Reset Password

+ + + Reset Password + Enter your email to reset your password + + +
+ {#if showTokenVerification} + {@render tokenForm()} + {:else} + {@render emailForm()} + {/if} +
+
+
+ +{#snippet emailForm()} +
+ + + Email + + + + + + +
+{/snippet} + +{#snippet tokenForm()} +
+ + + + + + + + + + +
+{/snippet} \ No newline at end of file diff --git a/src/routes/(auth)/sign-up/+page.svelte b/src/routes/(auth)/sign-up/+page.svelte index c7296d0..b1aa3a0 100644 --- a/src/routes/(auth)/sign-up/+page.svelte +++ b/src/routes/(auth)/sign-up/+page.svelte @@ -9,8 +9,9 @@ import { Label } from '$components/ui/label'; import { Input } from '$components/ui/input'; import { signUpSchema } from '$lib/validations/auth'; - import * as Collapsible from '$lib/components/ui/collapsible'; import * as Alert from '$lib/components/ui/alert'; + import * as Card from '$lib/components/ui/card'; + import * as Collapsible from '$lib/components/ui/collapsible'; import { boredState } from '$lib/stores/boredState.js'; export let data; @@ -37,9 +38,12 @@ Bored Game | Sign Up - + + + \ No newline at end of file diff --git a/src/lib/server/api/providers/database.provider.ts b/src/lib/server/api/providers/database.provider.ts new file mode 100644 index 0000000..f4221d5 --- /dev/null +++ b/src/lib/server/api/providers/database.provider.ts @@ -0,0 +1,10 @@ +import { db } from '../infrastructure/database'; + +// Symbol +export const DatabaseProvider = Symbol('DATABASE_TOKEN'); + +// Type +export type DatabaseProvider = typeof db; + +// Register +container.register(DatabaseProvider, { useValue: db }); diff --git a/src/lib/server/api/providers/index.ts b/src/lib/server/api/providers/index.ts new file mode 100644 index 0000000..0ac960b --- /dev/null +++ b/src/lib/server/api/providers/index.ts @@ -0,0 +1,3 @@ +export * from './database.provider'; +export * from './lucia.provider'; +export * from './redis.provider'; diff --git a/src/lib/server/api/providers/lucia.provider.ts b/src/lib/server/api/providers/lucia.provider.ts new file mode 100644 index 0000000..6546d43 --- /dev/null +++ b/src/lib/server/api/providers/lucia.provider.ts @@ -0,0 +1,11 @@ +import { container } from 'tsyringe'; +import { lucia } from '../infrastructure/auth/lucia'; + +// Symbol +export const LuciaProvider = Symbol('LUCIA_PROVIDER'); + +// Type +export type LuciaProvider = typeof lucia; + +// Register +container.register(LuciaProvider, { useValue: lucia }); diff --git a/src/lib/server/api/providers/redis.provider.ts b/src/lib/server/api/providers/redis.provider.ts new file mode 100644 index 0000000..26496de --- /dev/null +++ b/src/lib/server/api/providers/redis.provider.ts @@ -0,0 +1,14 @@ +import { container } from 'tsyringe'; +import RedisClient from 'ioredis' +import { config } from '../common/config'; + +// Symbol +export const RedisProvider = Symbol('REDIS_TOKEN'); + +// Type +export type RedisProvider = RedisClient; + +// Register +container.register(RedisProvider, { + useValue: new RedisClient(config.REDIS_URL) +}); diff --git a/src/lib/server/api/services/login-requests.service.ts b/src/lib/server/api/services/login-requests.service.ts new file mode 100644 index 0000000..e831e93 --- /dev/null +++ b/src/lib/server/api/services/login-requests.service.ts @@ -0,0 +1,62 @@ +import { BadRequest } from '../common/errors'; +import { DatabaseProvider } from '../providers'; +import { MailerService } from './mailer.service'; +import { TokensService } from './tokens.service'; +import { LuciaProvider } from '../providers/lucia.provider'; +import { UsersRepository } from '../repositories/users.repository'; +import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; +import type { RegisterEmailDto } from '../../../dtos/register-email.dto'; +import { LoginRequestsRepository } from '../repositories/login-requests.repository'; + +export class LoginRequestsService { + async create(data: RegisterEmailDto) { + // generate a token, expiry date, and hash + const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); + // save the login request to the database - ensuring we save the hashedToken + await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); + // send the login request email + await this.mailerService.sendLoginRequest({ + to: data.email, + props: { token: token } + }); + } + + async verify(data: SignInEmailDto) { + const validLoginRequest = await this.fetchValidRequest(data.email, data.token); + if (!validLoginRequest) throw BadRequest('Invalid token'); + + let existingUser = await this.usersRepository.findOneByEmail(data.email); + + if (!existingUser) { + const newUser = await this.handleNewUserRegistration(data.email); + return this.lucia.createSession(newUser.id, {}); + } + + return this.lucia.createSession(existingUser.id, {}); + } + + // Create a new user and send a welcome email - or other onboarding process + private async handleNewUserRegistration(email: string) { + const newUser = await this.usersRepository.create({ email, verified: true, avatar: null }) + this.mailerService.sendWelcome({ to: email, props: null }); + // TODO: add whatever onboarding process or extra data you need here + return newUser + } + + // Fetch a valid request from the database, verify the token and burn the request if it is valid + private async fetchValidRequest(email: string, token: string) { + return await this.db.transaction(async (trx) => { + // fetch the login request + const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) + if (!loginRequest) return null; + + // check if the token is valid + const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); + if (!isValidRequest) return null + + // if the token is valid, burn the request + await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); + return loginRequest + }) + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7b98d09..388ed92 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,10 @@ "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, - "strict": true + "strict": true, + "moduleResolution": "bundler", + "experimentalDecorators": true, + "emitDecoratorMetadata": true } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // From 3190e9601e346b4019810fe5952ed0f641fe1336 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sun, 28 Jul 2024 18:39:42 -0700 Subject: [PATCH 08/34] Creating controllers, updating deps, and adding more repositories. --- package.json | 26 +- pnpm-lock.yaml | 900 +++++++++--------- .../server/api/controllers/iam.controller.ts | 5 +- .../api/controllers/login.controller.ts | 13 + .../api/controllers/signup.controller.ts | 0 .../server/api/controllers/user.controller.ts | 22 + src/lib/server/api/index.ts | 23 +- .../database/tables/users.table.ts | 4 +- .../server/api/providers/lucia.provider.ts | 15 +- .../api/repositories/users.repository.ts | 56 ++ .../server/api/services/hashing.service.ts | 32 + src/lib/server/api/services/iam.service.ts | 24 + .../api/services/login-requests.service.ts | 62 -- src/lib/server/api/services/mailer.service.ts | 104 ++ src/lib/server/api/services/queues.service.ts | 19 + src/lib/server/api/services/tokens.service.ts | 33 + 16 files changed, 799 insertions(+), 539 deletions(-) create mode 100644 src/lib/server/api/controllers/login.controller.ts create mode 100644 src/lib/server/api/controllers/signup.controller.ts create mode 100644 src/lib/server/api/controllers/user.controller.ts create mode 100644 src/lib/server/api/repositories/users.repository.ts create mode 100644 src/lib/server/api/services/hashing.service.ts create mode 100644 src/lib/server/api/services/iam.service.ts delete mode 100644 src/lib/server/api/services/login-requests.service.ts create mode 100644 src/lib/server/api/services/mailer.service.ts create mode 100644 src/lib/server/api/services/queues.service.ts create mode 100644 src/lib/server/api/services/tokens.service.ts diff --git a/package.json b/package.json index 63fa3e3..325c70e 100644 --- a/package.json +++ b/package.json @@ -25,17 +25,17 @@ "devDependencies": { "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", - "@playwright/test": "^1.45.2", + "@playwright/test": "^1.45.3", "@resvg/resvg-js": "^2.6.2", "@sveltejs/adapter-auto": "^3.2.2", - "@sveltejs/enhanced-img": "^0.3.0", + "@sveltejs/enhanced-img": "^0.3.1", "@sveltejs/kit": "^2.5.18", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@types/cookie": "^0.6.0", - "@types/node": "^20.14.11", + "@types/node": "^20.14.13", "@types/pg": "^8.11.6", - "@typescript-eslint/eslint-plugin": "^7.16.1", - "@typescript-eslint/parser": "^7.16.1", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", "autoprefixer": "^10.4.19", "drizzle-kit": "^0.23.0", "eslint": "^8.57.0", @@ -43,7 +43,7 @@ "eslint-plugin-svelte": "^2.43.0", "just-clone": "^6.2.0", "just-debounce-it": "^3.2.0", - "postcss": "^8.4.39", + "postcss": "^8.4.40", "postcss-import": "^16.1.0", "postcss-load-config": "^5.1.0", "postcss-preset-env": "^9.6.0", @@ -61,12 +61,12 @@ "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", "sveltekit-superforms": "^2.16.1", - "tailwindcss": "^3.4.6", + "tailwindcss": "^3.4.7", "ts-node": "^10.9.2", "tslib": "^2.6.3", "tsx": "^4.16.2", - "typescript": "^5.5.3", - "vite": "^5.3.4", + "typescript": "^5.5.4", + "vite": "^5.3.5", "vitest": "^1.6.0", "zod": "^3.23.8" }, @@ -80,7 +80,7 @@ "@hono/zod-validator": "^0.2.2", "@iconify-icons/line-md": "^1.2.30", "@iconify-icons/mdi": "^1.2.48", - "@internationalized/date": "^3.5.4", + "@internationalized/date": "^3.5.5", "@lucia-auth/adapter-drizzle": "^1.0.7", "@lukeed/uuid": "^2.0.1", "@neondatabase/serverless": "^0.9.4", @@ -96,10 +96,10 @@ "cookie": "^0.6.0", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", - "drizzle-orm": "^0.32.0", + "drizzle-orm": "^0.32.1", "feather-icons": "^4.29.2", "formsnap": "^1.0.1", - "hono": "^4.5.0", + "hono": "^4.5.2", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", @@ -122,6 +122,6 @@ "tailwind-merge": "^2.4.0", "tailwind-variants": "^0.2.1", "tailwindcss-animate": "^1.0.7", - "zod-to-json-schema": "^3.23.1" + "zod-to-json-schema": "^3.23.2" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d950581..f36d828 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 5.0.13 '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.0)(zod@3.23.8) + version: 0.2.2(hono@4.5.2)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -21,8 +21,8 @@ importers: specifier: ^1.2.48 version: 1.2.48 '@internationalized/date': - specifier: ^3.5.4 - version: 3.5.4 + specifier: ^3.5.5 + version: 3.5.5 '@lucia-auth/adapter-drizzle': specifier: ^1.0.7 version: 1.0.7(lucia@3.2.0) @@ -37,7 +37,7 @@ importers: version: 2.2.2 '@sveltejs/adapter-vercel': specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))) + version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -69,20 +69,20 @@ importers: specifier: ^11.0.6 version: 11.0.6 drizzle-orm: - specifier: ^0.32.0 - version: 0.32.0(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) + specifier: ^0.32.1 + version: 0.32.1(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)) hono: - specifier: ^4.5.0 - version: 4.5.0 + specifier: ^4.5.2 + version: 4.5.2 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.0) + version: 0.4.0(hono@4.5.2) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -142,13 +142,13 @@ importers: version: 2.4.0 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3))) + version: 0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3))) + version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))) zod-to-json-schema: - specifier: ^3.23.1 - version: 3.23.1(zod@3.23.8) + specifier: ^3.23.2 + version: 3.23.2(zod@3.23.8) devDependencies: '@melt-ui/pp': specifier: ^0.3.2 @@ -157,41 +157,41 @@ importers: specifier: ^0.83.0 version: 0.83.0(svelte@5.0.0-next.175) '@playwright/test': - specifier: ^1.45.2 - version: 1.45.2 + specifier: ^1.45.3 + version: 1.45.3 '@resvg/resvg-js': specifier: ^2.6.2 version: 2.6.2 '@sveltejs/adapter-auto': specifier: ^3.2.2 - version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))) + version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) '@sveltejs/enhanced-img': - specifier: ^0.3.0 - version: 0.3.0(rollup@4.18.1)(svelte@5.0.0-next.175) + specifier: ^0.3.1 + version: 0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@sveltejs/kit': specifier: ^2.5.18 - version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.14.11 - version: 20.14.11 + specifier: ^20.14.13 + version: 20.14.13 '@types/pg': specifier: ^8.11.6 version: 8.11.6 '@typescript-eslint/eslint-plugin': - specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + specifier: ^7.17.0 + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': - specifier: ^7.16.1 - version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + specifier: ^7.17.0 + version: 7.17.0(eslint@8.57.0)(typescript@5.5.4) autoprefixer: specifier: ^10.4.19 - version: 10.4.19(postcss@8.4.39) + version: 10.4.19(postcss@8.4.40) drizzle-kit: specifier: ^0.23.0 version: 0.23.0 @@ -203,7 +203,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: ^2.43.0 - version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)) + version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -211,17 +211,17 @@ importers: specifier: ^3.2.0 version: 3.2.0 postcss: - specifier: ^8.4.39 - version: 8.4.39 + specifier: ^8.4.40 + version: 8.4.40 postcss-import: specifier: ^16.1.0 - version: 16.1.0(postcss@8.4.39) + version: 16.1.0(postcss@8.4.40) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2) postcss-preset-env: specifier: ^9.6.0 - version: 9.6.0(postcss@8.4.39) + version: 9.6.0(postcss@8.4.40) prettier: specifier: ^3.3.3 version: 3.3.3 @@ -242,34 +242,34 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.4 - version: 3.8.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2))(postcss@8.4.39)(sass@1.77.8)(svelte@5.0.0-next.175) + version: 3.8.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) svelte-meta-tags: specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(typescript@5.5.3) + version: 3.1.2(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2))(postcss@8.4.39)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.3) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))) + version: 0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.16.1 - version: 2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: - specifier: ^3.4.6 - version: 3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)) + specifier: ^3.4.7 + version: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.11)(typescript@5.5.3) + version: 10.9.2(@types/node@20.14.13)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 @@ -277,14 +277,14 @@ importers: specifier: ^4.16.2 version: 4.16.2 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 vite: - specifier: ^5.3.4 - version: 5.3.4(@types/node@20.14.11)(sass@1.77.8) + specifier: ^5.3.5 + version: 5.3.5(@types/node@20.14.13)(sass@1.77.8) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.11)(sass@1.77.8) + version: 1.6.0(@types/node@20.14.13)(sass@1.77.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -1314,8 +1314,8 @@ packages: cpu: [x64] os: [win32] - '@internationalized/date@3.5.4': - resolution: {integrity: sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==} + '@internationalized/date@3.5.5': + resolution: {integrity: sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ==} '@ioredis/commands@1.2.0': resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} @@ -1586,8 +1586,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.45.2': - resolution: {integrity: sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==} + '@playwright/test@1.45.3': + resolution: {integrity: sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==} engines: {node: '>=18'} hasBin: true @@ -1884,8 +1884,11 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/enhanced-img@0.3.0': - resolution: {integrity: sha512-o8FdEUyJR/+LjUUl4sgB9QeM9rSGpOzTO6/CH0AmO/FgwWkcJdj/MwVNtr2F/AtaPgNfzvRpnExjklmuuDOtPA==} + '@sveltejs/enhanced-img@0.3.1': + resolution: {integrity: sha512-75A4YiXQp+GRc54EyiNOlhHnHt9O8e0CdCHLm3RWESLRaazd5OIciSa4SbKIo9DM84yGwSVShU0buyUmNJvgWg==} + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: '>= 5.0.0' '@sveltejs/kit@2.5.18': resolution: {integrity: sha512-+g06hvpVAnH7b4CDjhnTDgFWBKBiQJpuSmQeGYOuzbO3SC3tdYjRNlDCrafvDtKbGiT2uxY5Dn9qdEUGVZdWOQ==} @@ -1941,8 +1944,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.14.11': - resolution: {integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==} + '@types/node@20.14.13': + resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} @@ -1953,8 +1956,8 @@ packages: '@types/validator@13.12.0': resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==} - '@typescript-eslint/eslint-plugin@7.16.1': - resolution: {integrity: sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==} + '@typescript-eslint/eslint-plugin@7.17.0': + resolution: {integrity: sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -1964,8 +1967,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.16.1': - resolution: {integrity: sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==} + '@typescript-eslint/parser@7.17.0': + resolution: {integrity: sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1974,12 +1977,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@7.16.1': - resolution: {integrity: sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==} + '@typescript-eslint/scope-manager@7.17.0': + resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/type-utils@7.16.1': - resolution: {integrity: sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==} + '@typescript-eslint/type-utils@7.17.0': + resolution: {integrity: sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1988,12 +1991,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@7.16.1': - resolution: {integrity: sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==} + '@typescript-eslint/types@7.17.0': + resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/typescript-estree@7.16.1': - resolution: {integrity: sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==} + '@typescript-eslint/typescript-estree@7.17.0': + resolution: {integrity: sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -2001,14 +2004,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@7.16.1': - resolution: {integrity: sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==} + '@typescript-eslint/utils@7.17.0': + resolution: {integrity: sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/visitor-keys@7.16.1': - resolution: {integrity: sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==} + '@typescript-eslint/visitor-keys@7.17.0': + resolution: {integrity: sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==} engines: {node: ^18.18.0 || >=20.0.0} '@ungap/structured-clone@1.2.0': @@ -2516,8 +2519,8 @@ packages: resolution: {integrity: sha512-w9jE97z193dd4jzAyj4Uv2SOh8Ydue70Ki6W0awy4bGM1aPXan6zD6Yv+nNTA6oGgNTDl2MJFxutjHG4fden5g==} hasBin: true - drizzle-orm@0.32.0: - resolution: {integrity: sha512-99IlfVGPNHzOFEXo9Phyu5At5TALLsY2t6WxFFy68rYd9Ej4cHX/7WjdPOn7JNRW69MNeNtP8XrDQg43SppuAA==} + drizzle-orm@0.32.1: + resolution: {integrity: sha512-Wq1J+lL8PzwR5K3a1FfoWsbs8powjr3pGA4+5+2ueN1VTLDNFYEolUyUWFtqy8DVRvYbL2n7sXZkgVmK9dQkng==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' @@ -2971,8 +2974,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.0: - resolution: {integrity: sha512-ZbezypZfn4odyApjCCv+Fw5OgweBqRLA/EsMyc4FUknFvBJcBIKhHy4sqmD1rWpBc/3wUlaQ6tqOPjk36R1ckg==} + hono@4.5.2: + resolution: {integrity: sha512-93P8XEALrHAUGRZoqXs8MDL3w9mDgRpbW9Sy5x4LS7srg78bKUw7EGynxze+Ft1e/rLGmDAbxeSTMu6dHUSRDw==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -3575,13 +3578,13 @@ packages: pkg-types@1.1.0: resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} - playwright-core@1.45.2: - resolution: {integrity: sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==} + playwright-core@1.45.3: + resolution: {integrity: sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==} engines: {node: '>=18'} hasBin: true - playwright@1.45.2: - resolution: {integrity: sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==} + playwright@1.45.3: + resolution: {integrity: sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==} engines: {node: '>=18'} hasBin: true @@ -3828,8 +3831,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.39: - resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + postcss@8.4.40: + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -4390,8 +4393,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.6: - resolution: {integrity: sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==} + tailwindcss@3.4.7: + resolution: {integrity: sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==} engines: {node: '>=14.0.0'} hasBin: true @@ -4508,8 +4511,8 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typescript@5.5.3: - resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true @@ -4580,8 +4583,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.3.4: - resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} + vite@5.3.5: + resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4735,8 +4738,8 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod-to-json-schema@3.23.1: - resolution: {integrity: sha512-oT9INvydob1XV0v1d2IadrR74rLtDInLvDFfAa1CG0Pmg/vxATk7I2gSelfj271mbzeM4Da0uuDQE/Nkj3DWNw==} + zod-to-json-schema@3.23.2: + resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==} peerDependencies: zod: ^3.23.3 @@ -4799,201 +4802,201 @@ snapshots: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.39)': + '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.40)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.1.0 - '@csstools/postcss-color-function@3.0.19(postcss@8.4.39)': + '@csstools/postcss-color-function@3.0.19(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.39)': + '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.39)': + '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.40)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.39)': + '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.40)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.39)': + '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.40)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.39)': + '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.39)': + '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.39)': + '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.39)': + '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.40)': dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@1.0.1(postcss@8.4.39)': + '@csstools/postcss-initial@1.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.39)': + '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.40)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.1.0 - '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.39)': + '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.40)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.39)': + '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.39)': + '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.39)': + '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.39)': + '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.39)': + '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.40)': dependencies: '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.39)': + '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.40)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.39)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.40)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.39)': + '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.40)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.39)': + '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.39)': + '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.39)': + '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.39)': + '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.40)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.39)': + '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 - '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.39)': + '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.40)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.39)': + '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.40)': dependencies: '@csstools/color-helpers': 4.2.1 - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.39)': + '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.40)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.39 + postcss: 8.4.40 - '@csstools/postcss-unset-value@3.0.1(postcss@8.4.39)': + '@csstools/postcss-unset-value@3.0.1(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 '@csstools/selector-resolve-nested@1.1.0(postcss-selector-parser@6.1.0)': dependencies: @@ -5003,9 +5006,9 @@ snapshots: dependencies: postcss-selector-parser: 6.1.0 - '@csstools/utilities@1.0.0(postcss@8.4.39)': + '@csstools/utilities@1.0.0(postcss@8.4.40)': dependencies: - postcss: 8.4.39 + postcss: 8.4.40 '@emnapi/core@0.45.0': dependencies: @@ -5379,9 +5382,9 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/zod-validator@0.2.2(hono@4.5.0)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.5.2)(zod@3.23.8)': dependencies: - hono: 4.5.0 + hono: 4.5.2 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -5481,7 +5484,7 @@ snapshots: '@img/sharp-win32-x64@0.33.4': optional: true - '@internationalized/date@3.5.4': + '@internationalized/date@3.5.5': dependencies: '@swc/helpers': 0.5.11 @@ -5560,7 +5563,7 @@ snapshots: dependencies: '@floating-ui/core': 1.6.2 '@floating-ui/dom': 1.6.5 - '@internationalized/date': 3.5.4 + '@internationalized/date': 3.5.5 dequal: 2.0.3 focus-trap: 7.5.4 nanoid: 5.0.7 @@ -5570,7 +5573,7 @@ snapshots: dependencies: '@floating-ui/core': 1.6.4 '@floating-ui/dom': 1.6.7 - '@internationalized/date': 3.5.4 + '@internationalized/date': 3.5.5 dequal: 2.0.3 focus-trap: 7.5.4 nanoid: 5.0.7 @@ -5729,9 +5732,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.45.2': + '@playwright/test@1.45.3': dependencies: - playwright: 1.45.2 + playwright: 1.45.3 '@polka/url@1.0.0-next.25': {} @@ -5924,32 +5927,33 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.0(rollup@4.18.1)(svelte@5.0.0-next.175)': + '@sveltejs/enhanced-img@0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': dependencies: magic-string: 0.30.10 + svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) vite-imagetools: 7.0.2(rollup@4.18.1) transitivePeerDependencies: - rollup - - svelte - '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))': + '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -5963,28 +5967,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8) - vitefu: 0.2.5(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vitefu: 0.2.5(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6014,13 +6018,13 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.14.11': + '@types/node@20.14.13': dependencies: undici-types: 5.26.5 '@types/pg@8.11.6': dependencies: - '@types/node': 20.14.11 + '@types/node': 20.14.13 pg-protocol: 1.6.1 pg-types: 4.0.2 @@ -6029,85 +6033,85 @@ snapshots: '@types/validator@13.12.0': optional: true - '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.16.1(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/type-utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/parser': 7.17.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/type-utils': 7.17.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.17.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.17.0 debug: 4.3.4 eslint: 8.57.0 optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.16.1': + '@typescript-eslint/scope-manager@7.17.0': dependencies: - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/visitor-keys': 7.17.0 - '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/type-utils@7.17.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) + '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.4) debug: 4.3.5 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.5.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.16.1': {} + '@typescript-eslint/types@7.17.0': {} - '@typescript-eslint/typescript-estree@7.16.1(typescript@5.5.3)': + '@typescript-eslint/typescript-estree@7.17.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/visitor-keys': 7.17.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/utils@7.17.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@7.16.1': + '@typescript-eslint/visitor-keys@7.17.0': dependencies: - '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/types': 7.17.0 eslint-visitor-keys: 3.4.3 '@ungap/structured-clone@1.2.0': {} @@ -6272,14 +6276,14 @@ snapshots: async-sema@3.1.1: {} - autoprefixer@10.4.19(postcss@8.4.39): + autoprefixer@10.4.19(postcss@8.4.40): dependencies: browserslist: 4.23.0 caniuse-lite: 1.0.30001616 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 axobject-query@4.0.0: @@ -6298,7 +6302,7 @@ snapshots: bits-ui@0.21.12(svelte@5.0.0-next.175): dependencies: - '@internationalized/date': 3.5.4 + '@internationalized/date': 3.5.5 '@melt-ui/svelte': 0.76.2(svelte@5.0.0-next.175) nanoid: 5.0.7 svelte: 5.0.0-next.175 @@ -6497,25 +6501,25 @@ snapshots: css-background-parser@0.1.0: {} - css-blank-pseudo@6.0.2(postcss@8.4.39): + css-blank-pseudo@6.0.2(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 css-box-shadow@1.0.0-3: {} css-color-keywords@1.0.0: {} - css-has-pseudo@6.0.5(postcss@8.4.39): + css-has-pseudo@6.0.5(postcss@8.4.40): dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.1.0 postcss-value-parser: 4.2.0 - css-prefers-color-scheme@9.0.1(postcss@8.4.39): + css-prefers-color-scheme@9.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 css-to-react-native@3.2.0: dependencies: @@ -6611,7 +6615,7 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.0(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.1(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4): optionalDependencies: '@neondatabase/serverless': 0.9.4 '@types/pg': 8.11.6 @@ -6778,7 +6782,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)): + eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -6786,9 +6790,9 @@ snapshots: eslint-compat-utils: 0.5.1(eslint@8.57.0) esutils: 2.0.3 known-css-properties: 0.34.0 - postcss: 8.4.39 - postcss-load-config: 3.1.4(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)) - postcss-safe-parser: 6.0.0(postcss@8.4.39) + postcss: 8.4.40 + postcss-load-config: 3.1.4(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) + postcss-safe-parser: 6.0.0(postcss@8.4.40) postcss-selector-parser: 6.1.0 semver: 7.6.2 svelte-eslint-parser: 0.41.0(svelte@5.0.0-next.175) @@ -7014,11 +7018,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7141,11 +7145,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.0): + hono-rate-limiter@0.4.0(hono@4.5.2): dependencies: - hono: 4.5.0 + hono: 4.5.2 - hono@4.5.0: {} + hono@4.5.2: {} html-entities@2.5.2: {} @@ -7689,281 +7693,281 @@ snapshots: mlly: 1.7.0 pathe: 1.1.2 - playwright-core@1.45.2: {} + playwright-core@1.45.3: {} - playwright@1.45.2: + playwright@1.45.3: dependencies: - playwright-core: 1.45.2 + playwright-core: 1.45.3 optionalDependencies: fsevents: 2.3.2 pngjs@5.0.0: {} - postcss-attribute-case-insensitive@6.0.3(postcss@8.4.39): + postcss-attribute-case-insensitive@6.0.3(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 - postcss-clamp@4.1.0(postcss@8.4.39): + postcss-clamp@4.1.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@6.0.14(postcss@8.4.39): + postcss-color-functional-notation@6.0.14(postcss@8.4.40): dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - postcss-color-hex-alpha@9.0.4(postcss@8.4.39): + postcss-color-hex-alpha@9.0.4(postcss@8.4.40): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@9.0.3(postcss@8.4.39): + postcss-color-rebeccapurple@9.0.3(postcss@8.4.40): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-custom-media@10.0.8(postcss@8.4.39): + postcss-custom-media@10.0.8(postcss@8.4.40): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.39 + postcss: 8.4.40 - postcss-custom-properties@13.3.12(postcss@8.4.39): + postcss-custom-properties@13.3.12(postcss@8.4.40): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-custom-selectors@7.1.12(postcss@8.4.39): + postcss-custom-selectors@7.1.12(postcss@8.4.40): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.1.0 - postcss-dir-pseudo-class@8.0.1(postcss@8.4.39): + postcss-dir-pseudo-class@8.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 - postcss-double-position-gradients@5.0.7(postcss@8.4.39): + postcss-double-position-gradients@5.0.7(postcss@8.4.40): dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-focus-visible@9.0.1(postcss@8.4.39): + postcss-focus-visible@9.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 - postcss-focus-within@8.0.1(postcss@8.4.39): + postcss-focus-within@8.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 - postcss-font-variant@5.0.0(postcss@8.4.39): + postcss-font-variant@5.0.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-gap-properties@5.0.1(postcss@8.4.39): + postcss-gap-properties@5.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-image-set-function@6.0.3(postcss@8.4.39): + postcss-image-set-function@6.0.3(postcss@8.4.40): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.4.39): + postcss-import@15.1.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-import@16.1.0(postcss@8.4.39): + postcss-import@16.1.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.39): + postcss-js@4.0.1(postcss@8.4.40): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.39 + postcss: 8.4.40 - postcss-lab-function@6.0.19(postcss@8.4.39): + postcss-lab-function@6.0.19(postcss@8.4.40): dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/utilities': 1.0.0(postcss@8.4.39) - postcss: 8.4.39 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/utilities': 1.0.0(postcss@8.4.40) + postcss: 8.4.40 - postcss-load-config@3.1.4(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)): + postcss-load-config@3.1.4(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.39 - ts-node: 10.9.2(@types/node@20.14.11)(typescript@5.5.3) + postcss: 8.4.40 + ts-node: 10.9.2(@types/node@20.14.13)(typescript@5.5.4) - postcss-load-config@4.0.2(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)): + postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: - postcss: 8.4.39 - ts-node: 10.9.2(@types/node@20.14.11)(typescript@5.5.3) + postcss: 8.4.40 + ts-node: 10.9.2(@types/node@20.14.13)(typescript@5.5.4) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: jiti: 1.21.6 - postcss: 8.4.39 + postcss: 8.4.40 tsx: 4.16.2 - postcss-logical@7.0.1(postcss@8.4.39): + postcss-logical@7.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-nested@6.0.1(postcss@8.4.39): + postcss-nested@6.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.1.0 - postcss-nesting@12.1.5(postcss@8.4.39): + postcss-nesting@12.1.5(postcss@8.4.40): dependencies: '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.0) '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.1.0 - postcss-opacity-percentage@2.0.0(postcss@8.4.39): + postcss-opacity-percentage@2.0.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-overflow-shorthand@5.0.1(postcss@8.4.39): + postcss-overflow-shorthand@5.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.4.39): + postcss-page-break@3.0.4(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-place@9.0.1(postcss@8.4.39): + postcss-place@9.0.1(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-value-parser: 4.2.0 - postcss-preset-env@9.6.0(postcss@8.4.39): + postcss-preset-env@9.6.0(postcss@8.4.40): dependencies: - '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.39) - '@csstools/postcss-color-function': 3.0.19(postcss@8.4.39) - '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.39) - '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.39) - '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.39) - '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.39) - '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.39) - '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.39) - '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.39) - '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.39) - '@csstools/postcss-initial': 1.0.1(postcss@8.4.39) - '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.39) - '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.39) - '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.39) - '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.39) - '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.39) - '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.39) - '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.39) - '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.39) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.39) - '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.39) - '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.39) - '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.39) - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.39) - '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.39) - '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.39) - '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.39) - '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.39) - '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.39) - '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.39) - autoprefixer: 10.4.19(postcss@8.4.39) + '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.40) + '@csstools/postcss-color-function': 3.0.19(postcss@8.4.40) + '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.40) + '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.40) + '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.40) + '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.40) + '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.40) + '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.40) + '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.40) + '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.40) + '@csstools/postcss-initial': 1.0.1(postcss@8.4.40) + '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.40) + '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.40) + '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.40) + '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.40) + '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.40) + '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.40) + '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.40) + '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.40) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.40) + '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.40) + '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.40) + '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.40) + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) + '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.40) + '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.40) + '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.40) + '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.40) + '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.40) + '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.40) + autoprefixer: 10.4.19(postcss@8.4.40) browserslist: 4.23.1 - css-blank-pseudo: 6.0.2(postcss@8.4.39) - css-has-pseudo: 6.0.5(postcss@8.4.39) - css-prefers-color-scheme: 9.0.1(postcss@8.4.39) + css-blank-pseudo: 6.0.2(postcss@8.4.40) + css-has-pseudo: 6.0.5(postcss@8.4.40) + css-prefers-color-scheme: 9.0.1(postcss@8.4.40) cssdb: 8.1.0 - postcss: 8.4.39 - postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.39) - postcss-clamp: 4.1.0(postcss@8.4.39) - postcss-color-functional-notation: 6.0.14(postcss@8.4.39) - postcss-color-hex-alpha: 9.0.4(postcss@8.4.39) - postcss-color-rebeccapurple: 9.0.3(postcss@8.4.39) - postcss-custom-media: 10.0.8(postcss@8.4.39) - postcss-custom-properties: 13.3.12(postcss@8.4.39) - postcss-custom-selectors: 7.1.12(postcss@8.4.39) - postcss-dir-pseudo-class: 8.0.1(postcss@8.4.39) - postcss-double-position-gradients: 5.0.7(postcss@8.4.39) - postcss-focus-visible: 9.0.1(postcss@8.4.39) - postcss-focus-within: 8.0.1(postcss@8.4.39) - postcss-font-variant: 5.0.0(postcss@8.4.39) - postcss-gap-properties: 5.0.1(postcss@8.4.39) - postcss-image-set-function: 6.0.3(postcss@8.4.39) - postcss-lab-function: 6.0.19(postcss@8.4.39) - postcss-logical: 7.0.1(postcss@8.4.39) - postcss-nesting: 12.1.5(postcss@8.4.39) - postcss-opacity-percentage: 2.0.0(postcss@8.4.39) - postcss-overflow-shorthand: 5.0.1(postcss@8.4.39) - postcss-page-break: 3.0.4(postcss@8.4.39) - postcss-place: 9.0.1(postcss@8.4.39) - postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.39) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.39) - postcss-selector-not: 7.0.2(postcss@8.4.39) + postcss: 8.4.40 + postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.40) + postcss-clamp: 4.1.0(postcss@8.4.40) + postcss-color-functional-notation: 6.0.14(postcss@8.4.40) + postcss-color-hex-alpha: 9.0.4(postcss@8.4.40) + postcss-color-rebeccapurple: 9.0.3(postcss@8.4.40) + postcss-custom-media: 10.0.8(postcss@8.4.40) + postcss-custom-properties: 13.3.12(postcss@8.4.40) + postcss-custom-selectors: 7.1.12(postcss@8.4.40) + postcss-dir-pseudo-class: 8.0.1(postcss@8.4.40) + postcss-double-position-gradients: 5.0.7(postcss@8.4.40) + postcss-focus-visible: 9.0.1(postcss@8.4.40) + postcss-focus-within: 8.0.1(postcss@8.4.40) + postcss-font-variant: 5.0.0(postcss@8.4.40) + postcss-gap-properties: 5.0.1(postcss@8.4.40) + postcss-image-set-function: 6.0.3(postcss@8.4.40) + postcss-lab-function: 6.0.19(postcss@8.4.40) + postcss-logical: 7.0.1(postcss@8.4.40) + postcss-nesting: 12.1.5(postcss@8.4.40) + postcss-opacity-percentage: 2.0.0(postcss@8.4.40) + postcss-overflow-shorthand: 5.0.1(postcss@8.4.40) + postcss-page-break: 3.0.4(postcss@8.4.40) + postcss-place: 9.0.1(postcss@8.4.40) + postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.40) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.40) + postcss-selector-not: 7.0.2(postcss@8.4.40) - postcss-pseudo-class-any-link@9.0.2(postcss@8.4.39): + postcss-pseudo-class-any-link@9.0.2(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 - postcss-replace-overflow-wrap@4.0.0(postcss@8.4.39): + postcss-replace-overflow-wrap@4.0.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-safe-parser@6.0.0(postcss@8.4.39): + postcss-safe-parser@6.0.0(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-scss@4.0.9(postcss@8.4.39): + postcss-scss@4.0.9(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 - postcss-selector-not@7.0.2(postcss@8.4.39): + postcss-selector-not@7.0.2(postcss@8.4.40): dependencies: - postcss: 8.4.39 + postcss: 8.4.40 postcss-selector-parser: 6.0.16 postcss-selector-parser@6.0.16: @@ -7978,7 +7982,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.39: + postcss@8.4.40: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 @@ -8220,9 +8224,9 @@ snapshots: postcss-value-parser: 4.2.0 yoga-wasm-web: 0.3.3 - schema-dts@1.1.2(typescript@5.5.3): + schema-dts@1.1.2(typescript@5.5.4): dependencies: - typescript: 5.5.3 + typescript: 5.5.4 semver@6.3.1: {} @@ -8414,15 +8418,15 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2))(postcss@8.4.39)(sass@1.77.8)(svelte@5.0.0-next.175): + svelte-check@3.8.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.0.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2))(postcss@8.4.39)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.3) - typescript: 5.5.3 + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + typescript: 5.5.4 transitivePeerDependencies: - '@babel/core' - coffeescript @@ -8439,8 +8443,8 @@ snapshots: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.39 - postcss-scss: 4.0.9(postcss@8.4.39) + postcss: 8.4.40 + postcss-scss: 4.0.9(postcss@8.4.40) optionalDependencies: svelte: 5.0.0-next.175 @@ -8466,9 +8470,9 @@ snapshots: svelte-lazy-loader@1.0.0: {} - svelte-meta-tags@3.1.2(svelte@5.0.0-next.175)(typescript@5.5.3): + svelte-meta-tags@3.1.2(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: - schema-dts: 1.1.2(typescript@5.5.3) + schema-dts: 1.1.2(typescript@5.5.4) svelte: 5.0.0-next.175 transitivePeerDependencies: - typescript @@ -8477,7 +8481,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2))(postcss@8.4.39)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.3): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8486,19 +8490,19 @@ snapshots: strip-indent: 3.0.0 svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.39 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2) + postcss: 8.4.40 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2) sass: 1.77.8 - typescript: 5.5.3 + typescript: 5.5.4 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2))(postcss@8.4.39)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.3): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.39 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.39)(tsx@4.16.2) + postcss: 8.4.40 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2) sass: 1.77.8 - typescript: 5.5.3 + typescript: 5.5.4 svelte-render@2.0.1(svelte@5.0.0-next.175): dependencies: @@ -8551,19 +8555,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) - sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8582,22 +8586,22 @@ snapshots: valibot: 0.35.0 yup: 1.4.0 zod: 3.23.8 - zod-to-json-schema: 3.23.1(zod@3.23.8) + zod-to-json-schema: 3.23.2(zod@3.23.8) tabbable@6.2.0: {} tailwind-merge@2.4.0: {} - tailwind-variants@0.2.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3))): + tailwind-variants@0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))): dependencies: tailwind-merge: 2.4.0 - tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)) + tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)) + tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) - tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)): + tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8613,11 +8617,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 - postcss: 8.4.39 - postcss-import: 15.1.0(postcss@8.4.39) - postcss-js: 4.0.1(postcss@8.4.39) - postcss-load-config: 4.0.2(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)) - postcss-nested: 6.0.1(postcss@8.4.39) + postcss: 8.4.40 + postcss-import: 15.1.0(postcss@8.4.40) + postcss-js: 4.0.1(postcss@8.4.40) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) + postcss-nested: 6.0.1(postcss@8.4.40) postcss-selector-parser: 6.1.0 resolve: 1.22.8 sucrase: 3.35.0 @@ -8675,29 +8679,29 @@ snapshots: ts-algebra@2.0.0: optional: true - ts-api-utils@1.3.0(typescript@5.5.3): + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: - typescript: 5.5.3 + typescript: 5.5.4 ts-deepmerge@7.0.0: {} ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3): + ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.11 + '@types/node': 20.14.13 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.3 + typescript: 5.5.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -8729,7 +8733,7 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typescript@5.5.3: {} + typescript@5.5.4: {} ufo@1.5.3: {} @@ -8786,13 +8790,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.14.11)(sass@1.77.8): + vite-node@1.6.0(@types/node@20.14.13)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -8803,21 +8807,21 @@ snapshots: - supports-color - terser - vite@5.3.4(@types/node@20.14.11)(sass@1.77.8): + vite@5.3.5(@types/node@20.14.13)(sass@1.77.8): dependencies: esbuild: 0.21.5 - postcss: 8.4.39 + postcss: 8.4.40 rollup: 4.17.2 optionalDependencies: - '@types/node': 20.14.11 + '@types/node': 20.14.13 fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)): + vitefu@0.2.5(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)): optionalDependencies: - vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) - vitest@1.6.0(@types/node@20.14.11)(sass@1.77.8): + vitest@1.6.0(@types/node@20.14.13)(sass@1.77.8): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -8836,11 +8840,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.14.11)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.14.13)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.14.11 + '@types/node': 20.14.13 transitivePeerDependencies: - less - lightningcss @@ -8943,7 +8947,7 @@ snapshots: zimmerframe@1.1.2: {} - zod-to-json-schema@3.23.1(zod@3.23.8): + zod-to-json-schema@3.23.2(zod@3.23.8): dependencies: zod: 3.23.8 diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 5be066e..372e240 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -4,7 +4,7 @@ import { requireAuth } from "../middleware/auth.middleware"; import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; import { limiter } from '../middleware/rate-limiter.middleware'; -const users = new Hono() +const app = new Hono() .get('/me', requireAuth, async (c) => { const user = c.var.user; return c.json({ user }); @@ -19,5 +19,4 @@ const users = new Hono() return c.json({ message: 'Verification email sent' }); }); -export default users; -export type UsersType = typeof users \ No newline at end of file +export default app; diff --git a/src/lib/server/api/controllers/login.controller.ts b/src/lib/server/api/controllers/login.controller.ts new file mode 100644 index 0000000..aeecf6e --- /dev/null +++ b/src/lib/server/api/controllers/login.controller.ts @@ -0,0 +1,13 @@ +import { Hono } from 'hono'; +import { zValidator } from '@hono/zod-validator'; +import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; +import { limiter } from '../middleware/rate-limiter.middleware'; + +const app = new Hono() + .post('/', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { email } = c.req.valid('json'); + await loginRequestsService.create({ email }); + return c.json({ message: 'Verification email sent' }); + }); + +export default app; diff --git a/src/lib/server/api/controllers/signup.controller.ts b/src/lib/server/api/controllers/signup.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts new file mode 100644 index 0000000..372e240 --- /dev/null +++ b/src/lib/server/api/controllers/user.controller.ts @@ -0,0 +1,22 @@ +import { Hono } from 'hono'; +import { zValidator } from '@hono/zod-validator'; +import { requireAuth } from "../middleware/auth.middleware"; +import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; +import { limiter } from '../middleware/rate-limiter.middleware'; + +const app = new Hono() + .get('/me', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }) + .get('/user', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }) + .post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { email } = c.req.valid('json'); + await this.loginRequestsService.create({ email }); + return c.json({ message: 'Verification email sent' }); + }); + +export default app; diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index a5476dd..3e53e76 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,7 +1,9 @@ import { Hono } from 'hono'; import { hc } from 'hono/client'; +import { cors } from 'hono/cors'; +import { logger } from 'hono/logger'; import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware'; -import users from './controllers/iam.controller'; +import users from './controllers/user.controller'; import { config } from './common/config'; /* ----------------------------------- Api ---------------------------------- */ @@ -9,10 +11,27 @@ const app = new Hono().basePath('/api'); /* --------------------------- Global Middlewares --------------------------- */ app.use(verifyOrigin).use(validateAuthSession); +app.use(logger()); + +app.use( + '/*', + cors({ + origin: [ + 'http://localhost:5173', + 'http://localhost:80', + 'http://host.docker.internal:80', + 'http://host.docker.internal:5173' + ], // Replace with your allowed domains + + allowMethods: ['POST'], + allowHeaders: ['Content-Type'] + // credentials: true, // If you need to send cookies or HTTP authentication + }) +); /* --------------------------------- Routes --------------------------------- */ const routes = app - .route('/iam', users) + .route('/user', users) .get('/', (c) => c.json({ message: 'Server is healthy' })); /* -------------------------------------------------------------------------- */ diff --git a/src/lib/server/api/infrastructure/database/tables/users.table.ts b/src/lib/server/api/infrastructure/database/tables/users.table.ts index e3e40cb..e520f2e 100644 --- a/src/lib/server/api/infrastructure/database/tables/users.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/users.table.ts @@ -4,7 +4,7 @@ import { type InferSelectModel, relations } from 'drizzle-orm'; import { timestamps } from '../utils'; import user_roles from './userRoles'; -const usersTable = pgTable('users', { +export const usersTable = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -25,5 +25,3 @@ export const userRelations = relations(usersTable, ({ many }) => ({ })); export type Users = InferSelectModel; - -export default usersTable; diff --git a/src/lib/server/api/providers/lucia.provider.ts b/src/lib/server/api/providers/lucia.provider.ts index 6546d43..e5af95a 100644 --- a/src/lib/server/api/providers/lucia.provider.ts +++ b/src/lib/server/api/providers/lucia.provider.ts @@ -1,11 +1,10 @@ -import { container } from 'tsyringe'; -import { lucia } from '../infrastructure/auth/lucia'; +// import { lucia } from '../infrastructure/auth/lucia'; -// Symbol -export const LuciaProvider = Symbol('LUCIA_PROVIDER'); +// // Symbol +// export const LuciaProvider = Symbol('LUCIA_PROVIDER'); -// Type -export type LuciaProvider = typeof lucia; +// // Type +// export type LuciaProvider = typeof lucia; -// Register -container.register(LuciaProvider, { useValue: lucia }); +// // Register +// container.register(LuciaProvider, { useValue: lucia }); diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts new file mode 100644 index 0000000..2621363 --- /dev/null +++ b/src/lib/server/api/repositories/users.repository.ts @@ -0,0 +1,56 @@ +import { eq, type InferInsertModel } from 'drizzle-orm'; +import { usersTable } from '../infrastructure/database/tables/users.table'; +import { takeFirstOrThrow } from '../infrastructure/database/utils'; +import { db } from '../infrastructure/database'; + +/* -------------------------------------------------------------------------- */ +/* Repository */ +/* -------------------------------------------------------------------------- */ +/* ---------------------------------- About --------------------------------- */ +/* +Repositories are the layer that interacts with the database. They are responsible for retrieving and +storing data. They should not contain any business logic, only database queries. +*/ +/* ---------------------------------- Notes --------------------------------- */ +/* + Repositories should only contain methods for CRUD operations and any other database interactions. + Any complex logic should be delegated to a service. If a repository method requires a transaction, + it should be passed in as an argument or the class should have a method to set the transaction. + In our case the method 'trxHost' is used to set the transaction context. +*/ + +export type CreateUser = InferInsertModel; +export type UpdateUser = Partial; + +export class UsersRepository { + async findOneById(id: string) { + return db.query.usersTable.findFirst({ + where: eq(usersTable.id, id) + }); + } + + async findOneByIdOrThrow(id: string) { + const user = await this.findOneById(id); + if (!user) throw Error('User not found'); + return user; + } + + async findOneByEmail(email: string) { + return db.query.usersTable.findFirst({ + where: eq(usersTable.email, email) + }); + } + + async create(data: CreateUser) { + return db.insert(usersTable).values(data).returning().then(takeFirstOrThrow); + } + + async update(id: string, data: UpdateUser) { + return db + .update(usersTable) + .set(data) + .where(eq(usersTable.id, id)) + .returning() + .then(takeFirstOrThrow); + } +} diff --git a/src/lib/server/api/services/hashing.service.ts b/src/lib/server/api/services/hashing.service.ts new file mode 100644 index 0000000..e1b7cd6 --- /dev/null +++ b/src/lib/server/api/services/hashing.service.ts @@ -0,0 +1,32 @@ +import { Argon2id } from "oslo/password"; + +/* ---------------------------------- Note ---------------------------------- */ +/* +Reference: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id + +I use Scrpt as the hashing algorithm due to its higher compatability +with vite's build system and it uses less memory than Argon2id. + +You can use Argon2id or any other hashing algorithm you prefer. +*/ +/* -------------------------------------------------------------------------- */ +/* +With Argon2id, you get the following error at times when vite optimizes its dependencies at times, + +Error: Build failed with 2 errors: +node_modules/.pnpm/@node-rs+argon2@1.7.0/node_modules/@node-rs/argon2/index.js:159:36: ERROR: No loader is configured for ".node" files: node_module +*/ +/* -------------------------------------------------------------------------- */ +// If you don't use a hasher from oslo, which are preconfigured with recommended parameters from OWASP, +// ensure that you configure them properly. +export class HashingService { + private readonly hasher = new Argon2id(); + + async hash(data: string) { + return this.hasher.hash(data); + } + + async verify(hash: string, data: string) { + return this.hasher.verify(hash, data) + } +} \ No newline at end of file diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts new file mode 100644 index 0000000..395c748 --- /dev/null +++ b/src/lib/server/api/services/iam.service.ts @@ -0,0 +1,24 @@ +import { lucia } from '../infrastructure/auth/lucia'; + +/* -------------------------------------------------------------------------- */ +/* Service */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* ---------------------------------- About --------------------------------- */ +/* +Services are responsible for handling business logic and data manipulation. +They genreally call on repositories or other services to complete a use-case. +*/ +/* ---------------------------------- Notes --------------------------------- */ +/* +Services should be kept as clean and simple as possible. + +Create private functions to handle complex logic and keep the public methods as +simple as possible. This makes the service easier to read, test and understand. +*/ +/* -------------------------------------------------------------------------- */ +export class IamService { + async logout(sessionId: string) { + return lucia.invalidateSession(sessionId); + } +} diff --git a/src/lib/server/api/services/login-requests.service.ts b/src/lib/server/api/services/login-requests.service.ts deleted file mode 100644 index e831e93..0000000 --- a/src/lib/server/api/services/login-requests.service.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { BadRequest } from '../common/errors'; -import { DatabaseProvider } from '../providers'; -import { MailerService } from './mailer.service'; -import { TokensService } from './tokens.service'; -import { LuciaProvider } from '../providers/lucia.provider'; -import { UsersRepository } from '../repositories/users.repository'; -import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; -import type { RegisterEmailDto } from '../../../dtos/register-email.dto'; -import { LoginRequestsRepository } from '../repositories/login-requests.repository'; - -export class LoginRequestsService { - async create(data: RegisterEmailDto) { - // generate a token, expiry date, and hash - const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); - // save the login request to the database - ensuring we save the hashedToken - await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); - // send the login request email - await this.mailerService.sendLoginRequest({ - to: data.email, - props: { token: token } - }); - } - - async verify(data: SignInEmailDto) { - const validLoginRequest = await this.fetchValidRequest(data.email, data.token); - if (!validLoginRequest) throw BadRequest('Invalid token'); - - let existingUser = await this.usersRepository.findOneByEmail(data.email); - - if (!existingUser) { - const newUser = await this.handleNewUserRegistration(data.email); - return this.lucia.createSession(newUser.id, {}); - } - - return this.lucia.createSession(existingUser.id, {}); - } - - // Create a new user and send a welcome email - or other onboarding process - private async handleNewUserRegistration(email: string) { - const newUser = await this.usersRepository.create({ email, verified: true, avatar: null }) - this.mailerService.sendWelcome({ to: email, props: null }); - // TODO: add whatever onboarding process or extra data you need here - return newUser - } - - // Fetch a valid request from the database, verify the token and burn the request if it is valid - private async fetchValidRequest(email: string, token: string) { - return await this.db.transaction(async (trx) => { - // fetch the login request - const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) - if (!loginRequest) return null; - - // check if the token is valid - const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); - if (!isValidRequest) return null - - // if the token is valid, burn the request - await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); - return loginRequest - }) - } -} \ No newline at end of file diff --git a/src/lib/server/api/services/mailer.service.ts b/src/lib/server/api/services/mailer.service.ts new file mode 100644 index 0000000..368fdb9 --- /dev/null +++ b/src/lib/server/api/services/mailer.service.ts @@ -0,0 +1,104 @@ +import fs from 'fs'; +import path from 'path'; +import nodemailer from 'nodemailer'; +import handlebars from 'handlebars'; +import { fileURLToPath } from 'url'; +import { injectable } from 'tsyringe'; + +/* -------------------------------------------------------------------------- */ +/* Service */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* ---------------------------------- About --------------------------------- */ +/* +Services are responsible for handling business logic and data manipulation. +They genreally call on repositories or other services to complete a use-case. +*/ +/* ---------------------------------- Notes --------------------------------- */ +/* +Services should be kept as clean and simple as possible. + +Create private functions to handle complex logic and keep the public methods as +simple as possible. This makes the service easier to read, test and understand. +*/ +/* -------------------------------------------------------------------------- */ + +type SendMail = { + to: string | string[]; + subject: string; + html: string; +}; + +type SendTemplate = { + to: string | string[]; + props: T; +}; + +@injectable() +export class MailerService { + private nodemailer = nodemailer.createTransport({ + host: 'smtp.ethereal.email', + port: 587, + secure: false, // Use `true` for port 465, `false` for all other ports + auth: { + user: 'adella.hoppe@ethereal.email', + pass: 'dshNQZYhATsdJ3ENke' + } + }); + + sendEmailVerificationToken(data: SendTemplate<{ token: string }>) { + const template = handlebars.compile(this.getTemplate('email-verification-token')); + return this.send({ + to: data.to, + subject: 'Email Verification', + html: template({ token: data.props.token }) + }); + } + + sendEmailChangeNotification(data: SendTemplate) { + const template = handlebars.compile(this.getTemplate('email-change-notice')); + return this.send({ + to: data.to, + subject: 'Email Change Notice', + html: template(null) + }); + } + + sendLoginRequest(data: SendTemplate<{ token: string }>) { + const template = handlebars.compile(this.getTemplate('email-verification-token')); + return this.send({ + to: data.to, + subject: 'Login Request', + html: template({ token: data.props.token }) + }); + } + + sendWelcome(data: SendTemplate) { + const template = handlebars.compile(this.getTemplate('welcome')); + return this.send({ + to: data.to, + subject: 'Welcome!', + html: template(null) + }); + } + + private async send({ to, subject, html }: SendMail) { + const message = await this.nodemailer.sendMail({ + from: '"Example" ', // sender address + bcc: to, + subject, // Subject line + text: html, + html + }); + console.log(nodemailer.getTestMessageUrl(message)); + } + + private getTemplate(template: string) { + const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file + const __dirname = path.dirname(__filename); // get the name of the directory + return fs.readFileSync( + path.join(__dirname, `../infrastructure/email-templates/${template}.hbs`), + 'utf-8' + ); + } +} diff --git a/src/lib/server/api/services/queues.service.ts b/src/lib/server/api/services/queues.service.ts new file mode 100644 index 0000000..97e7ca3 --- /dev/null +++ b/src/lib/server/api/services/queues.service.ts @@ -0,0 +1,19 @@ +import { injectable } from "tsyringe"; +import RedisClient from 'ioredis' +import { config } from "../common/config"; +import { Queue, Worker, type Processor } from 'bullmq'; + +@injectable() +export class QueuesServices { + connection = new RedisClient(config.REDIS_URL); + + constructor() { } + + createQueue(name: string) { + return new Queue(name, { connection: this.connection }) + } + + createWorker(name: string, prcoessor: Processor) { + return new Worker(name, prcoessor, { connection: this.connection }) + } +} \ No newline at end of file diff --git a/src/lib/server/api/services/tokens.service.ts b/src/lib/server/api/services/tokens.service.ts new file mode 100644 index 0000000..c8ac626 --- /dev/null +++ b/src/lib/server/api/services/tokens.service.ts @@ -0,0 +1,33 @@ +import { generateRandomString } from "oslo/crypto"; +import { TimeSpan, createDate, type TimeSpanUnit } from 'oslo'; +import { HashingService } from "./hashing.service"; + +export class TokensService { + private readonly hashingService = new HashingService(); + + generateToken() { + const alphabet = '23456789ACDEFGHJKLMNPQRSTUVWXYZ'; // alphabet with removed look-alike characters (0, 1, O, I) + return generateRandomString(6, alphabet); + } + + generateTokenWithExpiry(number: number, lifespan: TimeSpanUnit) { + return { + token: this.generateToken(), + expiry: createDate(new TimeSpan(number, lifespan)) + } + } + + async generateTokenWithExpiryAndHash(number: number, lifespan: TimeSpanUnit) { + const token = this.generateToken() + const hashedToken = await this.hashingService.hash(token) + return { + token, + hashedToken, + expiry: createDate(new TimeSpan(number, lifespan)) + } + } + + async verifyHashedToken(hashedToken: string, token: string) { + return this.hashingService.verify(hashedToken, token) + } +} From bf55b04de66ae6b57dd080d2b72286e5d34d3a2b Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 30 Jul 2024 18:50:46 -0700 Subject: [PATCH 09/34] Converting everything to the tsyringe IoC pattern. --- .node-version | 2 +- package.json | 13 +- pnpm-lock.yaml | 185 +++++++++++++++++- .../server/api/controllers/iam.controller.ts | 35 ++-- .../api/controllers/login.controller.ts | 29 ++- .../database/tables/credentials.table.ts | 20 ++ .../database/tables/expansions.ts | 2 +- .../tables/federatedIdentity.table.ts | 14 ++ .../api/interfaces/controller.interface.ts | 8 + src/lib/server/api/mockTest.ts | 10 + .../server/api/providers/database.provider.ts | 1 + .../server/api/providers/lucia.provider.ts | 15 +- .../repositories/credentials.repository.ts | 35 ++++ .../api/repositories/users.repository.ts | 6 + .../server/api/services/hashing.service.ts | 2 + src/lib/server/api/services/iam.service.ts | 10 +- .../api/services/loginrequest.service.ts | 75 +++++++ src/lib/server/api/services/mailer.service.ts | 6 +- src/lib/server/api/services/tokens.service.ts | 4 +- 19 files changed, 415 insertions(+), 57 deletions(-) create mode 100644 src/lib/server/api/infrastructure/database/tables/credentials.table.ts create mode 100644 src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts create mode 100644 src/lib/server/api/interfaces/controller.interface.ts create mode 100644 src/lib/server/api/mockTest.ts create mode 100644 src/lib/server/api/repositories/credentials.repository.ts create mode 100644 src/lib/server/api/services/loginrequest.service.ts diff --git a/.node-version b/.node-version index 119f15a..7d1aef0 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.15.1 \ No newline at end of file +22.1.0 \ No newline at end of file diff --git a/package.json b/package.json index 325c70e..3b14949 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,10 @@ "eslint-plugin-svelte": "^2.43.0", "just-clone": "^6.2.0", "just-debounce-it": "^3.2.0", + "lucia": "3.2.0", + "lucide-svelte": "^0.408.0", + "nodemailer": "^6.9.14", + "oslo": "^1.2.1", "postcss": "^8.4.40", "postcss-import": "^16.1.0", "postcss-load-config": "^5.1.0", @@ -71,10 +75,6 @@ "zod": "^3.23.8" }, "type": "module", - "engines": { - "node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0", - "pnpm": ">=8" - }, "dependencies": { "@fontsource/fira-mono": "^5.0.13", "@hono/zod-validator": "^0.2.2", @@ -91,6 +91,7 @@ "arctic": "^1.9.2", "bits-ui": "^0.21.12", "boardgamegeekclient": "^1.9.1", + "bullmq": "^5.11.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -99,6 +100,7 @@ "drizzle-orm": "^0.32.1", "feather-icons": "^4.29.2", "formsnap": "^1.0.1", + "handlebars": "^4.7.8", "hono": "^4.5.2", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", @@ -107,10 +109,7 @@ "just-capitalize": "^3.2.0", "just-kebab-case": "^4.2.0", "loader": "^2.1.1", - "lucia": "3.2.0", - "lucide-svelte": "^0.408.0", "open-props": "^1.7.5", - "oslo": "^1.2.1", "pg": "^8.12.0", "postgres": "^3.4.4", "qrcode": "^1.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f36d828..632134c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: boardgamegeekclient: specifier: ^1.9.1 version: 1.9.1 + bullmq: + specifier: ^5.11.0 + version: 5.11.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -77,6 +80,9 @@ importers: formsnap: specifier: ^1.0.1 version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)) + handlebars: + specifier: ^4.7.8 + version: 4.7.8 hono: specifier: ^4.5.2 version: 4.5.2 @@ -101,18 +107,9 @@ importers: loader: specifier: ^2.1.1 version: 2.1.1 - lucia: - specifier: 3.2.0 - version: 3.2.0 - lucide-svelte: - specifier: ^0.408.0 - version: 0.408.0(svelte@5.0.0-next.175) open-props: specifier: ^1.7.5 version: 1.7.5 - oslo: - specifier: ^1.2.1 - version: 1.2.1 pg: specifier: ^8.12.0 version: 8.12.0 @@ -210,6 +207,18 @@ importers: just-debounce-it: specifier: ^3.2.0 version: 3.2.0 + lucia: + specifier: 3.2.0 + version: 3.2.0 + lucide-svelte: + specifier: ^0.408.0 + version: 0.408.0(svelte@5.0.0-next.175) + nodemailer: + specifier: ^6.9.14 + version: 6.9.14 + oslo: + specifier: ^1.2.1 + version: 1.2.1 postcss: specifier: ^8.4.40 version: 8.4.40 @@ -1386,6 +1395,36 @@ packages: peerDependencies: svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + '@neondatabase/serverless@0.9.4': resolution: {integrity: sha512-D0AXgJh6xkf+XTlsO7iwE2Q1w8981E1cLCPAALMU2YKtkF/1SF6BiAzYARZFYo175ON+b1RNIy9TdSFHm5nteg==} @@ -2225,6 +2264,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bullmq@5.11.0: + resolution: {integrity: sha512-qVzyWGZqie3VHaYEgRXhId/j8ebfmj6MExEJyUByMsUJA5pVciVle3hKLer5fyMwtQ8lTMP7GwhXV/NZ+HzlRA==} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -2357,6 +2399,10 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2943,6 +2989,11 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3205,6 +3256,10 @@ packages: peerDependencies: svelte: ^3 || ^4 || ^5.0.0-next.42 + luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} @@ -3338,6 +3393,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.0: + resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -3358,6 +3420,12 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -3367,6 +3435,10 @@ packages: encoding: optional: true + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-gyp-build@4.8.1: resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} hasBin: true @@ -3374,6 +3446,10 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + nodemailer@6.9.14: + resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==} + engines: {node: '>=6.0.0'} + nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -4519,6 +4595,11 @@ packages: ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + uglify-js@3.19.1: + resolution: {integrity: sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==} + engines: {node: '>=0.8.0'} + hasBin: true + ultrahtml@1.5.3: resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==} @@ -4557,6 +4638,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -4670,6 +4755,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -5579,6 +5667,24 @@ snapshots: nanoid: 5.0.7 svelte: 5.0.0-next.175 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + '@neondatabase/serverless@0.9.4': dependencies: '@types/pg': 8.11.6 @@ -6367,6 +6473,18 @@ snapshots: buffer-from@1.1.2: {} + bullmq@5.11.0: + dependencies: + cron-parser: 4.9.0 + ioredis: 5.4.1 + msgpackr: 1.11.0 + node-abort-controller: 3.1.1 + semver: 7.6.3 + tslib: 2.6.3 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + bytes@3.1.2: {} cac@6.7.14: {} @@ -6493,6 +6611,10 @@ snapshots: create-require@1.1.1: {} + cron-parser@4.9.0: + dependencies: + luxon: 3.4.4 + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -7127,6 +7249,15 @@ snapshots: graphemer@1.4.0: {} + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.1 + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -7373,6 +7504,8 @@ snapshots: dependencies: svelte: 5.0.0-next.175 + luxon@3.4.4: {} + magic-string@0.30.10: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -7481,6 +7614,22 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.0: + optionalDependencies: + msgpackr-extract: 3.0.3 + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -7495,14 +7644,25 @@ snapshots: negotiator@0.6.3: {} + neo-async@2.6.2: {} + + node-abort-controller@3.1.1: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.3 + optional: true + node-gyp-build@4.8.1: {} node-releases@2.0.14: {} + nodemailer@6.9.14: {} + nopt@5.0.0: dependencies: abbrev: 1.1.1 @@ -8737,6 +8897,9 @@ snapshots: ufo@1.5.3: {} + uglify-js@3.19.1: + optional: true + ultrahtml@1.5.3: {} undici-types@5.26.5: {} @@ -8770,6 +8933,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} valibot@0.31.1: @@ -8878,6 +9043,8 @@ snapshots: word-wrap@1.2.5: {} + wordwrap@1.0.0: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 372e240..35e6fc6 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,22 +1,21 @@ import { Hono } from 'hono'; -import { zValidator } from '@hono/zod-validator'; +import { injectable } from 'tsyringe'; +import type { HonoTypes } from '../types'; import { requireAuth } from "../middleware/auth.middleware"; -import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; -import { limiter } from '../middleware/rate-limiter.middleware'; +import type { Controller } from '../interfaces/controller.interface'; -const app = new Hono() - .get('/me', requireAuth, async (c) => { - const user = c.var.user; - return c.json({ user }); - }) - .get('/user', requireAuth, async (c) => { - const user = c.var.user; - return c.json({ user }); - }) - .post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { email } = c.req.valid('json'); - await this.loginRequestsService.create({ email }); - return c.json({ message: 'Verification email sent' }); - }); +@injectable() +export class IamController implements Controller { + controller = new Hono(); -export default app; + constructor( + ) { } + + routes() { + return this.controller + .get('/me', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }); + } +} diff --git a/src/lib/server/api/controllers/login.controller.ts b/src/lib/server/api/controllers/login.controller.ts index aeecf6e..fab874c 100644 --- a/src/lib/server/api/controllers/login.controller.ts +++ b/src/lib/server/api/controllers/login.controller.ts @@ -1,13 +1,26 @@ import { Hono } from 'hono'; import { zValidator } from '@hono/zod-validator'; -import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; +import { inject, injectable } from 'tsyringe'; +import type { HonoTypes } from '../types'; import { limiter } from '../middleware/rate-limiter.middleware'; +import type { Controller } from '../interfaces/controller.interface'; +import { signInEmailDto } from '$lib/dtos/signin-email.dto'; +import type { LoginRequestsService } from '../services/loginrequest.service'; -const app = new Hono() - .post('/', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { email } = c.req.valid('json'); - await loginRequestsService.create({ email }); - return c.json({ message: 'Verification email sent' }); - }); +@injectable() +export class LoginController implements Controller { + controller = new Hono(); -export default app; + constructor( + @inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService + ) { } + + routes() { + return this.controller + .post('/', zValidator('json', signInEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { username, password } = c.req.valid('json'); + await this.loginRequestsService.verify({ username, password }); + return c.json({ message: 'Verification email sent' }); + }) + } +} diff --git a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts b/src/lib/server/api/infrastructure/database/tables/credentials.table.ts new file mode 100644 index 0000000..1683b6a --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/credentials.table.ts @@ -0,0 +1,20 @@ +import { pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { timestamps } from '../utils'; +import { usersTable } from "./users.table"; + +enum CredentialsType { + SECRET = 'secret', + PASSWORD = 'password', + TOTP = 'totp', + HOTP = 'hotp' +} + +export const credentialsTable = pgTable('credentials', { + id: uuid('id').primaryKey().defaultRandom(), + user_id: uuid('user_id') + .notNull() + .references(() => usersTable.id, { onDelete: 'cascade' }), + type: text('type').notNull().default(CredentialsType.PASSWORD), + secret_data: text('secret_data').notNull(), + ...timestamps +}); \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/tables/expansions.ts b/src/lib/server/api/infrastructure/database/tables/expansions.ts index bdbf2e3..ff56e4d 100644 --- a/src/lib/server/api/infrastructure/database/tables/expansions.ts +++ b/src/lib/server/api/infrastructure/database/tables/expansions.ts @@ -1,4 +1,4 @@ -import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import games from './games'; diff --git a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts new file mode 100644 index 0000000..c47f817 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts @@ -0,0 +1,14 @@ +import { pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { usersTable } from "./users.table"; +import { timestamps } from '../utils'; + +export const federatedIdentityTable = pgTable('federated_identity', { + id: uuid('id').primaryKey().defaultRandom(), + user_id: uuid('user_id') + .notNull() + .references(() => usersTable.id, { onDelete: 'cascade' }), + idenitity_provider: text('idenitity_provider').notNull(), + federated_user_id: text('federated_user_id').notNull(), + federated_username: text('federated_username').notNull(), + ...timestamps +}); \ No newline at end of file diff --git a/src/lib/server/api/interfaces/controller.interface.ts b/src/lib/server/api/interfaces/controller.interface.ts new file mode 100644 index 0000000..852e695 --- /dev/null +++ b/src/lib/server/api/interfaces/controller.interface.ts @@ -0,0 +1,8 @@ +import { Hono } from 'hono'; +import type { HonoTypes } from '../types'; +import type { BlankSchema } from 'hono/types'; + +export interface Controller { + controller: Hono; + routes(): any; +} diff --git a/src/lib/server/api/mockTest.ts b/src/lib/server/api/mockTest.ts new file mode 100644 index 0000000..a475662 --- /dev/null +++ b/src/lib/server/api/mockTest.ts @@ -0,0 +1,10 @@ +import { Argon2id } from "oslo/password"; + +export async function hash(value: string) { + const argon2 = new Argon2id() + return argon2.hash(value); +} + +export function verify(hashedValue: string, value: string) { + return new Argon2id().verify(hashedValue, value); +} diff --git a/src/lib/server/api/providers/database.provider.ts b/src/lib/server/api/providers/database.provider.ts index f4221d5..c7da118 100644 --- a/src/lib/server/api/providers/database.provider.ts +++ b/src/lib/server/api/providers/database.provider.ts @@ -1,3 +1,4 @@ +import { container } from 'tsyringe'; import { db } from '../infrastructure/database'; // Symbol diff --git a/src/lib/server/api/providers/lucia.provider.ts b/src/lib/server/api/providers/lucia.provider.ts index e5af95a..6546d43 100644 --- a/src/lib/server/api/providers/lucia.provider.ts +++ b/src/lib/server/api/providers/lucia.provider.ts @@ -1,10 +1,11 @@ -// import { lucia } from '../infrastructure/auth/lucia'; +import { container } from 'tsyringe'; +import { lucia } from '../infrastructure/auth/lucia'; -// // Symbol -// export const LuciaProvider = Symbol('LUCIA_PROVIDER'); +// Symbol +export const LuciaProvider = Symbol('LUCIA_PROVIDER'); -// // Type -// export type LuciaProvider = typeof lucia; +// Type +export type LuciaProvider = typeof lucia; -// // Register -// container.register(LuciaProvider, { useValue: lucia }); +// Register +container.register(LuciaProvider, { useValue: lucia }); diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts new file mode 100644 index 0000000..7bc32d3 --- /dev/null +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -0,0 +1,35 @@ +import { eq, type InferInsertModel } from "drizzle-orm"; +import { credentialsTable } from "../infrastructure/database/tables/credentials.table"; +import { db } from "../infrastructure/database"; +import { takeFirstOrThrow } from "../infrastructure/database/utils"; + +export type CreateCredentials = InferInsertModel; +export type UpdateCredentials = Partial; + +export class CredentialsRepository { + + async findOneById(id: string) { + return db.query.credentialsTable.findFirst({ + where: eq(credentialsTable.id, id) + }); + } + + async findOneByIdOrThrow(id: string) { + const credentials = await this.findOneById(id); + if (!credentials) throw Error('Credentials not found'); + return credentials; + } + + async create(data: CreateCredentials) { + return db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow); + } + + async update(id: string, data: UpdateCredentials) { + return db + .update(credentialsTable) + .set(data) + .where(eq(credentialsTable.id, id)) + .returning() + .then(takeFirstOrThrow); + } +} \ No newline at end of file diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts index 2621363..addeaf8 100644 --- a/src/lib/server/api/repositories/users.repository.ts +++ b/src/lib/server/api/repositories/users.repository.ts @@ -35,6 +35,12 @@ export class UsersRepository { return user; } + async findOneByUsername(username: string) { + return db.query.usersTable.findFirst({ + where: eq(usersTable.username, username) + }); + } + async findOneByEmail(email: string) { return db.query.usersTable.findFirst({ where: eq(usersTable.email, email) diff --git a/src/lib/server/api/services/hashing.service.ts b/src/lib/server/api/services/hashing.service.ts index e1b7cd6..5a03f4e 100644 --- a/src/lib/server/api/services/hashing.service.ts +++ b/src/lib/server/api/services/hashing.service.ts @@ -1,3 +1,4 @@ +import { injectable } from "tsyringe"; import { Argon2id } from "oslo/password"; /* ---------------------------------- Note ---------------------------------- */ @@ -19,6 +20,7 @@ node_modules/.pnpm/@node-rs+argon2@1.7.0/node_modules/@node-rs/argon2/index.js:1 /* -------------------------------------------------------------------------- */ // If you don't use a hasher from oslo, which are preconfigured with recommended parameters from OWASP, // ensure that you configure them properly. +@injectable() export class HashingService { private readonly hasher = new Argon2id(); diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts index 395c748..84b44da 100644 --- a/src/lib/server/api/services/iam.service.ts +++ b/src/lib/server/api/services/iam.service.ts @@ -1,4 +1,5 @@ -import { lucia } from '../infrastructure/auth/lucia'; +import { inject, injectable } from 'tsyringe'; +import { LuciaProvider } from '../providers/lucia.provider'; /* -------------------------------------------------------------------------- */ /* Service */ @@ -17,8 +18,13 @@ Create private functions to handle complex logic and keep the public methods as simple as possible. This makes the service easier to read, test and understand. */ /* -------------------------------------------------------------------------- */ +@injectable() export class IamService { + constructor( + @inject(LuciaProvider) private readonly lucia: LuciaProvider, + ) { } + async logout(sessionId: string) { - return lucia.invalidateSession(sessionId); + return this.lucia.invalidateSession(sessionId); } } diff --git a/src/lib/server/api/services/loginrequest.service.ts b/src/lib/server/api/services/loginrequest.service.ts new file mode 100644 index 0000000..5b9c1eb --- /dev/null +++ b/src/lib/server/api/services/loginrequest.service.ts @@ -0,0 +1,75 @@ +import { inject, injectable } from 'tsyringe'; +import { BadRequest } from '../common/errors'; +import { DatabaseProvider } from '../providers'; +import { MailerService } from './mailer.service'; +import { TokensService } from './tokens.service'; +import { LuciaProvider } from '../providers/lucia.provider'; +import { UsersRepository } from '../repositories/users.repository'; +import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; +import type { RegisterEmailDto } from '../../../dtos/register-email.dto'; +import { LoginRequestsRepository } from '../repositories/login-requests.repository'; + +@injectable() +export class LoginRequestsService { + constructor( + @inject(LuciaProvider) private readonly lucia: LuciaProvider, + @inject(DatabaseProvider) private readonly db: DatabaseProvider, + @inject(TokensService) private readonly tokensService: TokensService, + @inject(MailerService) private readonly mailerService: MailerService, + @inject(UsersRepository) private readonly usersRepository: UsersRepository, + @inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository, + ) { } + + async validate(data: SignInEmailDto) { + + } + + async create(data: RegisterEmailDto) { + // generate a token, expiry date, and hash + const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); + // save the login request to the database - ensuring we save the hashedToken + await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); + // send the login request email + await this.mailerService.sendLoginRequest({ + to: data.email, + props: { token: token } + }); + } + + async verify(data: SignInEmailDto) { + let existingUser = await this.usersRepository.findOneByUsername(data.username); + + if (!existingUser) { + throw BadRequest('User not found'); + } + + + + return this.lucia.createSession(existingUser.id, {}); + } + + // Create a new user and send a welcome email - or other onboarding process + private async handleNewUserRegistration(email: string) { + const newUser = await this.usersRepository.create({ email, verified: true, avatar: null }) + this.mailerService.sendWelcome({ to: email, props: null }); + // TODO: add whatever onboarding process or extra data you need here + return newUser + } + + // Fetch a valid request from the database, verify the token and burn the request if it is valid + private async fetchValidRequest(email: string, token: string) { + return await this.db.transaction(async (trx) => { + // fetch the login request + const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) + if (!loginRequest) return null; + + // check if the token is valid + const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); + if (!isValidRequest) return null + + // if the token is valid, burn the request + await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); + return loginRequest + }) + } +} \ No newline at end of file diff --git a/src/lib/server/api/services/mailer.service.ts b/src/lib/server/api/services/mailer.service.ts index 368fdb9..f644f11 100644 --- a/src/lib/server/api/services/mailer.service.ts +++ b/src/lib/server/api/services/mailer.service.ts @@ -11,14 +11,14 @@ import { injectable } from 'tsyringe'; /* -------------------------------------------------------------------------- */ /* ---------------------------------- About --------------------------------- */ /* -Services are responsible for handling business logic and data manipulation. +Services are responsible for handling business logic and data manipulation. They genreally call on repositories or other services to complete a use-case. */ /* ---------------------------------- Notes --------------------------------- */ /* -Services should be kept as clean and simple as possible. +Services should be kept as clean and simple as possible. -Create private functions to handle complex logic and keep the public methods as +Create private functions to handle complex logic and keep the public methods as simple as possible. This makes the service easier to read, test and understand. */ /* -------------------------------------------------------------------------- */ diff --git a/src/lib/server/api/services/tokens.service.ts b/src/lib/server/api/services/tokens.service.ts index c8ac626..a3f712e 100644 --- a/src/lib/server/api/services/tokens.service.ts +++ b/src/lib/server/api/services/tokens.service.ts @@ -1,9 +1,11 @@ +import { inject, injectable } from "tsyringe"; import { generateRandomString } from "oslo/crypto"; import { TimeSpan, createDate, type TimeSpanUnit } from 'oslo'; import { HashingService } from "./hashing.service"; +@injectable() export class TokensService { - private readonly hashingService = new HashingService(); + constructor(@inject(HashingService) private readonly hashingService: HashingService) { } generateToken() { const alphabet = '23456789ACDEFGHJKLMNPQRSTUVWXYZ'; // alphabet with removed look-alike characters (0, 1, O, I) From dbdac430ef8fe13942ba243f098abede36f7805b Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 1 Aug 2024 09:26:42 -0700 Subject: [PATCH 10/34] Adding verify login with TOTP credentials coming from the credentials table. --- .../api/controllers/login.controller.ts | 2 +- .../server/api/controllers/user.controller.ts | 42 ++++++++++++------- .../database/tables/credentials.table.ts | 2 +- .../repositories/credentials.repository.ts | 28 ++++++++++++- .../api/services/loginrequest.service.ts | 37 +++++++++++----- src/lib/server/api/services/users.service.ts | 17 ++++++++ 6 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 src/lib/server/api/services/users.service.ts diff --git a/src/lib/server/api/controllers/login.controller.ts b/src/lib/server/api/controllers/login.controller.ts index fab874c..15e4b37 100644 --- a/src/lib/server/api/controllers/login.controller.ts +++ b/src/lib/server/api/controllers/login.controller.ts @@ -19,7 +19,7 @@ export class LoginController implements Controller { return this.controller .post('/', zValidator('json', signInEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { const { username, password } = c.req.valid('json'); - await this.loginRequestsService.verify({ username, password }); + await this.loginRequestsService.verify({ username, password }, c.req); return c.json({ message: 'Verification email sent' }); }) } diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index 372e240..dc23cde 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -1,22 +1,34 @@ import { Hono } from 'hono'; import { zValidator } from '@hono/zod-validator'; +import { inject, injectable } from 'tsyringe'; import { requireAuth } from "../middleware/auth.middleware"; import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; import { limiter } from '../middleware/rate-limiter.middleware'; +import type { HonoTypes } from '../types'; +import type { Controller } from '../interfaces/controller.interface'; -const app = new Hono() - .get('/me', requireAuth, async (c) => { - const user = c.var.user; - return c.json({ user }); - }) - .get('/user', requireAuth, async (c) => { - const user = c.var.user; - return c.json({ user }); - }) - .post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { email } = c.req.valid('json'); - await this.loginRequestsService.create({ email }); - return c.json({ message: 'Verification email sent' }); - }); +@injectable() +export class UserController implements Controller { + controller = new Hono(); -export default app; + constructor( + @inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService + ) { } + + routes() { + return this.controller + .get('/me', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }) + .get('/user', requireAuth, async (c) => { + const user = c.var.user; + return c.json({ user }); + }) + .post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { email } = c.req.valid('json'); + await this.loginRequestsService.create({ email }); + return c.json({ message: 'Verification email sent' }); + }); + } +} diff --git a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts b/src/lib/server/api/infrastructure/database/tables/credentials.table.ts index 1683b6a..2025619 100644 --- a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/credentials.table.ts @@ -2,7 +2,7 @@ import { pgTable, text, uuid } from "drizzle-orm/pg-core"; import { timestamps } from '../utils'; import { usersTable } from "./users.table"; -enum CredentialsType { +export enum CredentialsType { SECRET = 'secret', PASSWORD = 'password', TOTP = 'totp', diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 7bc32d3..3c9ee07 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -1,5 +1,5 @@ -import { eq, type InferInsertModel } from "drizzle-orm"; -import { credentialsTable } from "../infrastructure/database/tables/credentials.table"; +import { and, eq, type InferInsertModel } from "drizzle-orm"; +import { credentialsTable, CredentialsType } from "../infrastructure/database/tables/credentials.table"; import { db } from "../infrastructure/database"; import { takeFirstOrThrow } from "../infrastructure/database/utils"; @@ -8,6 +8,30 @@ export type UpdateCredentials = Partial; export class CredentialsRepository { + async findOneByUserId(userId: string) { + return db.query.credentialsTable.findFirst({ + where: eq(credentialsTable.user_id, userId) + }); + } + + async findPasswordCredentialsByUserId(userId: string) { + return db.query.credentialsTable.findFirst({ + where: and( + eq(credentialsTable.user_id, userId), + eq(credentialsTable.type, CredentialsType.PASSWORD) + ) + }); + } + + async findTOTPCredentialsByUserId(userId: string) { + return db.query.credentialsTable.findFirst({ + where: and( + eq(credentialsTable.user_id, userId), + eq(credentialsTable.type, CredentialsType.TOTP) + ) + }); + } + async findOneById(id: string) { return db.query.credentialsTable.findFirst({ where: eq(credentialsTable.id, id) diff --git a/src/lib/server/api/services/loginrequest.service.ts b/src/lib/server/api/services/loginrequest.service.ts index 5b9c1eb..852b422 100644 --- a/src/lib/server/api/services/loginrequest.service.ts +++ b/src/lib/server/api/services/loginrequest.service.ts @@ -7,7 +7,8 @@ import { LuciaProvider } from '../providers/lucia.provider'; import { UsersRepository } from '../repositories/users.repository'; import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; import type { RegisterEmailDto } from '../../../dtos/register-email.dto'; -import { LoginRequestsRepository } from '../repositories/login-requests.repository'; +import { CredentialsRepository } from '../repositories/credentials.repository'; +import type { HonoRequest } from 'hono'; @injectable() export class LoginRequestsService { @@ -17,13 +18,9 @@ export class LoginRequestsService { @inject(TokensService) private readonly tokensService: TokensService, @inject(MailerService) private readonly mailerService: MailerService, @inject(UsersRepository) private readonly usersRepository: UsersRepository, - @inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository, + @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, ) { } - async validate(data: SignInEmailDto) { - - } - async create(data: RegisterEmailDto) { // generate a token, expiry date, and hash const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); @@ -36,16 +33,36 @@ export class LoginRequestsService { }); } - async verify(data: SignInEmailDto) { - let existingUser = await this.usersRepository.findOneByUsername(data.username); + async verify(data: SignInEmailDto, req: HonoRequest) { + const requestIpAddress = req.header('x-real-ip'); + const requestIpCountry = req.header('x-vercel-ip-country'); + const existingUser = await this.usersRepository.findOneByUsername(data.username); if (!existingUser) { throw BadRequest('User not found'); } - + const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id); - return this.lucia.createSession(existingUser.id, {}); + if (!credential) { + throw BadRequest('Invalid credentials'); + } + + if (!await this.tokensService.verifyHashedToken(credential.hashedPassword, data.password)) { + throw BadRequest('Invalid credentials'); + } + + const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id); + + return this.lucia.createSession(existingUser.id, { + ip_country: requestIpCountry || 'unknown', + ip_address: requestIpAddress || 'unknown', + twoFactorAuthEnabled: + !!totpCredentials && + totpCredentials?.secret !== null && + totpCredentials?.secret !== '', + isTwoFactorAuthenticated: false, + }); } // Create a new user and send a welcome email - or other onboarding process diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts new file mode 100644 index 0000000..8040442 --- /dev/null +++ b/src/lib/server/api/services/users.service.ts @@ -0,0 +1,17 @@ +import { inject, injectable } from 'tsyringe'; +import type { UsersRepository } from '../repositories/users.repository'; + +@injectable() +export class UsersService { + constructor( + @inject('UsersRepository') private readonly usersRepository: UsersRepository + ) { } + + async findOneByUsername(username: string) { + return this.usersRepository.findOneByUsername(username); + } + + async findOneById(id: string) { + return this.usersRepository.findOneById(id); + } +} \ No newline at end of file From 4dbc93f819ff3cfd265e6c3422c91d1da80599bb Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 1 Aug 2024 16:46:29 -0700 Subject: [PATCH 11/34] Fixing build but node version 22 is needed and pre-render fails. --- Caddyfile | 3 + docker-compose.yaml | 14 ++ package.json | 8 +- pnpm-lock.yaml | 120 +++++++++++++++++- src/hooks.server.ts | 1 + .../api/controllers/login.controller.ts | 5 +- .../server/api/controllers/user.controller.ts | 12 +- src/lib/server/api/index.ts | 8 +- .../database/tables/collections.ts | 2 +- .../infrastructure/database/tables/index.ts | 2 +- .../database/tables/passwordResetTokens.ts | 2 +- .../database/tables/recoveryCodes.ts | 2 +- .../database/tables/sessions.table.ts | 2 +- .../database/tables/two-factor.table.ts | 2 +- .../database/tables/userRoles.ts | 2 +- .../database/tables/users.table.ts | 1 - .../database/tables/wishlists.ts | 2 +- .../api/services/loginrequest.service.ts | 49 ++++--- .../api/tests/login-requests.service.test.ts | 72 +++++++++++ .../admin/users/[id]/+page.server.ts | 22 ++-- .../(app)/(protected)/profile/+page.server.ts | 24 ++-- .../security/password/change/+page.server.ts | 10 +- .../security/two-factor/+page.server.ts | 14 +- .../two-factor/recovery-codes/+page.server.ts | 6 +- src/routes/(app)/+page.server.ts | 6 +- src/routes/(app)/about/+page.ts | 14 +- src/routes/(app)/privacy/+page.server.ts | 2 +- src/routes/(app)/terms/+page.server.ts | 2 +- src/routes/(auth)/login/+page.server.ts | 6 +- src/routes/(auth)/sign-up/+page.server.ts | 8 +- src/routes/(auth)/totp/+page.server.ts | 10 +- src/routes/api/auth/reset-password/+server.ts | 6 +- .../auth/reset-password/[token]/+server.ts | 6 +- .../api/collection/[id]/search/+server.ts | 4 +- src/server/users.ts | 12 +- svelte.config.js | 1 + ....timestamp-1722555436409-2175218f70ffd.mjs | 41 ++++++ 37 files changed, 370 insertions(+), 133 deletions(-) create mode 100644 Caddyfile create mode 100644 src/lib/server/api/tests/login-requests.service.test.ts create mode 100644 vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..e9a1ab9 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,3 @@ +boredgame.localhost { + reverse_proxy / localhost:4173 +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 1d1d5ac..7cc5327 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,20 @@ services: - '6379:6379' volumes: - redis_data:/data + # caddy: + # image: caddy:latest + # restart: unless-stopped + # ports: + # - "80:80" + # - "443:443" + # - "443:443/udp" + # volumes: + # - ./Caddyfile:/etc/caddy/Caddyfile + # - ./site:/srv + # - caddy_data:/data + # - caddy_config:/config volumes: postgres_data: redis_data: + # caddy_data: + # caddy_config: \ No newline at end of file diff --git a/package.json b/package.json index 3b14949..1d103b0 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,13 @@ "site:update": "pnpm update -i -L", "test:unit": "vitest" }, + "engines": { + "node": "22.x" + }, "devDependencies": { "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", "@playwright/test": "^1.45.3", - "@resvg/resvg-js": "^2.6.2", "@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/enhanced-img": "^0.3.1", "@sveltejs/kit": "^2.5.18", @@ -46,7 +48,6 @@ "lucia": "3.2.0", "lucide-svelte": "^0.408.0", "nodemailer": "^6.9.14", - "oslo": "^1.2.1", "postcss": "^8.4.40", "postcss-import": "^16.1.0", "postcss-load-config": "^5.1.0", @@ -85,6 +86,7 @@ "@lukeed/uuid": "^2.0.1", "@neondatabase/serverless": "^0.9.4", "@paralleldrive/cuid2": "^2.2.2", + "@sveltejs/adapter-node": "^5.2.0", "@sveltejs/adapter-vercel": "^5.4.1", "@types/feather-icons": "^4.29.4", "@vercel/og": "^0.5.20", @@ -110,12 +112,14 @@ "just-kebab-case": "^4.2.0", "loader": "^2.1.1", "open-props": "^1.7.5", + "oslo": "^1.2.1", "pg": "^8.12.0", "postgres": "^3.4.4", "qrcode": "^1.5.3", "radix-svelte": "^0.9.0", "rate-limit-redis": "^4.2.0", "reflect-metadata": "^0.2.2", + "@resvg/resvg-js": "^2.6.2", "svelte-french-toast": "^1.2.0", "svelte-lazy-loader": "^1.0.0", "tailwind-merge": "^2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 632134c..4b9fcca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ importers: '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 + '@resvg/resvg-js': + specifier: ^2.6.2 + version: 2.6.2 + '@sveltejs/adapter-node': + specifier: ^5.2.0 + version: 5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) '@sveltejs/adapter-vercel': specifier: ^5.4.1 version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) @@ -110,6 +116,9 @@ importers: open-props: specifier: ^1.7.5 version: 1.7.5 + oslo: + specifier: ^1.2.1 + version: 1.2.1 pg: specifier: ^8.12.0 version: 8.12.0 @@ -156,9 +165,6 @@ importers: '@playwright/test': specifier: ^1.45.3 version: 1.45.3 - '@resvg/resvg-js': - specifier: ^2.6.2 - version: 2.6.2 '@sveltejs/adapter-auto': specifier: ^3.2.2 version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) @@ -216,9 +222,6 @@ importers: nodemailer: specifier: ^6.9.14 version: 6.9.14 - oslo: - specifier: ^1.2.1 - version: 1.2.1 postcss: specifier: ^8.4.40 version: 8.4.40 @@ -1717,6 +1720,33 @@ packages: resolution: {integrity: sha512-iDkBM6Ivex8nULtBu8cX670/lfsGxq8U1cuqE+qS9xFpPQP1enPdVm/33Kq3+B+bAldA+AHNZnCgpmlHo/fZrQ==} engines: {node: '>= 10'} + '@rollup/plugin-commonjs@26.0.1': + resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@15.2.3': + resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/pluginutils@4.2.1': resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -1918,6 +1948,11 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 + '@sveltejs/adapter-node@5.2.0': + resolution: {integrity: sha512-HVZoei2078XSyPmvdTHE03VXDUD0ytTvMuMHMQP0j6zX4nPDpCcKrgvU7baEblMeCCMdM/shQvstFxOJPQKlUQ==} + peerDependencies: + '@sveltejs/kit': ^2.4.0 + '@sveltejs/adapter-vercel@5.4.1': resolution: {integrity: sha512-JLcD1OgMnu9lQ8EssxVGxv7w0waWuyVzItTT1eqIH98Krufd9qfr1uC9zgo82z3dJ9v1AfPEbvIX5tonceg7XQ==} peerDependencies: @@ -1992,6 +2027,9 @@ packages: '@types/pug@2.0.10': resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/validator@13.12.0': resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==} @@ -2264,6 +2302,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + bullmq@5.11.0: resolution: {integrity: sha512-qVzyWGZqie3VHaYEgRXhId/j8ebfmj6MExEJyUByMsUJA5pVciVle3hKLer5fyMwtQ8lTMP7GwhXV/NZ+HzlRA==} @@ -2369,6 +2411,9 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -3094,6 +3139,10 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} @@ -3109,6 +3158,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -3117,6 +3169,9 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} @@ -5900,6 +5955,34 @@ snapshots: '@resvg/resvg-wasm@2.6.0': {} + '@rollup/plugin-commonjs@26.0.1(rollup@4.18.1)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.1) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 10.4.1 + is-reference: 1.2.1 + magic-string: 0.30.10 + optionalDependencies: + rollup: 4.18.1 + + '@rollup/plugin-json@6.1.0(rollup@4.18.1)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.1) + optionalDependencies: + rollup: 4.18.1 + + '@rollup/plugin-node-resolve@15.2.3(rollup@4.18.1)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.1) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-builtin-module: 3.2.1 + is-module: 1.0.0 + resolve: 1.22.8 + optionalDependencies: + rollup: 4.18.1 + '@rollup/pluginutils@4.2.1': dependencies: estree-walker: 2.0.2 @@ -6038,6 +6121,14 @@ snapshots: '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) import-meta-resolve: 4.1.0 + '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': + dependencies: + '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1) + '@rollup/plugin-json': 6.1.0(rollup@4.18.1) + '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + rollup: 4.18.1 + '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': dependencies: '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) @@ -6136,6 +6227,8 @@ snapshots: '@types/pug@2.0.10': {} + '@types/resolve@1.20.2': {} + '@types/validator@13.12.0': optional: true @@ -6473,6 +6566,8 @@ snapshots: buffer-from@1.1.2: {} + builtin-modules@3.3.0: {} + bullmq@5.11.0: dependencies: cron-parser: 4.9.0 @@ -6591,6 +6686,8 @@ snapshots: commander@4.1.1: {} + commondir@1.0.1: {} + concat-map@0.0.1: {} confbox@0.1.7: {} @@ -7355,6 +7452,10 @@ snapshots: dependencies: binary-extensions: 2.3.0 + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + is-core-module@2.13.1: dependencies: hasown: 2.0.2 @@ -7367,10 +7468,16 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-module@1.0.0: {} + is-number@7.0.0: {} is-path-inside@3.0.3: {} + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.5 + is-reference@3.0.2: dependencies: '@types/estree': 1.0.5 @@ -8327,7 +8434,6 @@ snapshots: '@rollup/rollup-win32-ia32-msvc': 4.18.1 '@rollup/rollup-win32-x64-msvc': 4.18.1 fsevents: 2.3.3 - optional: true run-parallel@1.2.0: dependencies: diff --git a/src/hooks.server.ts b/src/hooks.server.ts index f2b081b..f1e2dd8 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,4 +1,5 @@ // import * as Sentry from '@sentry/sveltekit'; +import 'reflect-metadata' import { hc } from 'hono/client'; import { sequence } from '@sveltejs/kit/hooks'; import { redirect, type Handle } from '@sveltejs/kit'; diff --git a/src/lib/server/api/controllers/login.controller.ts b/src/lib/server/api/controllers/login.controller.ts index 15e4b37..996e98e 100644 --- a/src/lib/server/api/controllers/login.controller.ts +++ b/src/lib/server/api/controllers/login.controller.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import { Hono } from 'hono'; import { zValidator } from '@hono/zod-validator'; import { inject, injectable } from 'tsyringe'; @@ -5,14 +6,14 @@ import type { HonoTypes } from '../types'; import { limiter } from '../middleware/rate-limiter.middleware'; import type { Controller } from '../interfaces/controller.interface'; import { signInEmailDto } from '$lib/dtos/signin-email.dto'; -import type { LoginRequestsService } from '../services/loginrequest.service'; +import { LoginRequestsService } from '../services/loginrequest.service'; @injectable() export class LoginController implements Controller { controller = new Hono(); constructor( - @inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService + @inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService ) { } routes() { diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index dc23cde..4ac575b 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -1,9 +1,7 @@ +import 'reflect-metadata'; import { Hono } from 'hono'; -import { zValidator } from '@hono/zod-validator'; -import { inject, injectable } from 'tsyringe'; +import { injectable } from 'tsyringe'; import { requireAuth } from "../middleware/auth.middleware"; -import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; -import { limiter } from '../middleware/rate-limiter.middleware'; import type { HonoTypes } from '../types'; import type { Controller } from '../interfaces/controller.interface'; @@ -12,7 +10,6 @@ export class UserController implements Controller { controller = new Hono(); constructor( - @inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService ) { } routes() { @@ -24,11 +21,6 @@ export class UserController implements Controller { .get('/user', requireAuth, async (c) => { const user = c.var.user; return c.json({ user }); - }) - .post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { email } = c.req.valid('json'); - await this.loginRequestsService.create({ email }); - return c.json({ message: 'Verification email sent' }); }); } } diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index 3e53e76..2a9ed1b 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,10 +1,13 @@ +import 'reflect-metadata' import { Hono } from 'hono'; import { hc } from 'hono/client'; import { cors } from 'hono/cors'; import { logger } from 'hono/logger'; import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware'; -import users from './controllers/user.controller'; import { config } from './common/config'; +import { container } from 'tsyringe'; +import { IamController } from './controllers/iam.controller'; +import { LoginController } from './controllers/login.controller'; /* ----------------------------------- Api ---------------------------------- */ const app = new Hono().basePath('/api'); @@ -31,7 +34,8 @@ app.use( /* --------------------------------- Routes --------------------------------- */ const routes = app - .route('/user', users) + .route('/user', container.resolve(IamController).routes()) + .route('/login', container.resolve(LoginController).routes()) .get('/', (c) => c.json({ message: 'Server is healthy' })); /* -------------------------------------------------------------------------- */ diff --git a/src/lib/server/api/infrastructure/database/tables/collections.ts b/src/lib/server/api/infrastructure/database/tables/collections.ts index 1a56443..58b8297 100644 --- a/src/lib/server/api/infrastructure/database/tables/collections.ts +++ b/src/lib/server/api/infrastructure/database/tables/collections.ts @@ -1,7 +1,7 @@ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; import { timestamps } from '../utils'; const collections = pgTable('collections', { diff --git a/src/lib/server/api/infrastructure/database/tables/index.ts b/src/lib/server/api/infrastructure/database/tables/index.ts index e7bf9ff..9b59c1e 100644 --- a/src/lib/server/api/infrastructure/database/tables/index.ts +++ b/src/lib/server/api/infrastructure/database/tables/index.ts @@ -1,4 +1,4 @@ -export { default as usersTable, userRelations as user_relations, type Users } from './users.table'; +export { usersTable, userRelations as user_relations, type Users } from './users.table'; export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes'; export { default as password_reset_tokens, diff --git a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts b/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts index a849bc1..143ee28 100644 --- a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts +++ b/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts @@ -1,7 +1,7 @@ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; import { timestamps } from '../utils'; const password_reset_tokens = pgTable('password_reset_tokens', { diff --git a/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts b/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts index b63c167..9a7ee8e 100644 --- a/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts +++ b/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts @@ -1,6 +1,6 @@ import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import type { InferSelectModel } from 'drizzle-orm'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; import { timestamps } from '../utils'; const recovery_codes = pgTable('recovery_codes', { diff --git a/src/lib/server/api/infrastructure/database/tables/sessions.table.ts b/src/lib/server/api/infrastructure/database/tables/sessions.table.ts index 813b2b8..823eb19 100644 --- a/src/lib/server/api/infrastructure/database/tables/sessions.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/sessions.table.ts @@ -1,6 +1,6 @@ import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { relations, type InferSelectModel } from 'drizzle-orm'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; const sessionsTable = pgTable('sessions', { id: text('id').primaryKey(), diff --git a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts b/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts index d488c1d..b523a16 100644 --- a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts @@ -2,7 +2,7 @@ import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { timestamps } from '../utils'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; const twoFactorTable = pgTable('two_factor', { id: uuid('id').primaryKey().defaultRandom(), diff --git a/src/lib/server/api/infrastructure/database/tables/userRoles.ts b/src/lib/server/api/infrastructure/database/tables/userRoles.ts index d241788..46489a0 100644 --- a/src/lib/server/api/infrastructure/database/tables/userRoles.ts +++ b/src/lib/server/api/infrastructure/database/tables/userRoles.ts @@ -1,7 +1,7 @@ import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; import roles from './roles'; import { timestamps } from '../utils'; diff --git a/src/lib/server/api/infrastructure/database/tables/users.table.ts b/src/lib/server/api/infrastructure/database/tables/users.table.ts index e520f2e..0e90b66 100644 --- a/src/lib/server/api/infrastructure/database/tables/users.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/users.table.ts @@ -10,7 +10,6 @@ export const usersTable = pgTable('users', { .unique() .$defaultFn(() => cuid2()), username: text('username').unique(), - hashed_password: text('hashed_password'), email: text('email').unique(), first_name: text('first_name'), last_name: text('last_name'), diff --git a/src/lib/server/api/infrastructure/database/tables/wishlists.ts b/src/lib/server/api/infrastructure/database/tables/wishlists.ts index 673308a..83cd754 100644 --- a/src/lib/server/api/infrastructure/database/tables/wishlists.ts +++ b/src/lib/server/api/infrastructure/database/tables/wishlists.ts @@ -1,7 +1,7 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; +import { usersTable } from './users.table'; import { timestamps } from '../utils'; const wishlists = pgTable('wishlists', { diff --git a/src/lib/server/api/services/loginrequest.service.ts b/src/lib/server/api/services/loginrequest.service.ts index 852b422..f2138b3 100644 --- a/src/lib/server/api/services/loginrequest.service.ts +++ b/src/lib/server/api/services/loginrequest.service.ts @@ -6,7 +6,6 @@ import { TokensService } from './tokens.service'; import { LuciaProvider } from '../providers/lucia.provider'; import { UsersRepository } from '../repositories/users.repository'; import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; -import type { RegisterEmailDto } from '../../../dtos/register-email.dto'; import { CredentialsRepository } from '../repositories/credentials.repository'; import type { HonoRequest } from 'hono'; @@ -21,17 +20,17 @@ export class LoginRequestsService { @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, ) { } - async create(data: RegisterEmailDto) { - // generate a token, expiry date, and hash - const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); - // save the login request to the database - ensuring we save the hashedToken - await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); - // send the login request email - await this.mailerService.sendLoginRequest({ - to: data.email, - props: { token: token } - }); - } + // async create(data: RegisterEmailDto) { + // // generate a token, expiry date, and hash + // const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); + // // save the login request to the database - ensuring we save the hashedToken + // await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); + // // send the login request email + // await this.mailerService.sendLoginRequest({ + // to: data.email, + // props: { token: token } + // }); + // } async verify(data: SignInEmailDto, req: HonoRequest) { const requestIpAddress = req.header('x-real-ip'); @@ -74,19 +73,19 @@ export class LoginRequestsService { } // Fetch a valid request from the database, verify the token and burn the request if it is valid - private async fetchValidRequest(email: string, token: string) { - return await this.db.transaction(async (trx) => { - // fetch the login request - const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) - if (!loginRequest) return null; + // private async fetchValidRequest(email: string, token: string) { + // return await this.db.transaction(async (trx) => { + // // fetch the login request + // const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) + // if (!loginRequest) return null; - // check if the token is valid - const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); - if (!isValidRequest) return null + // // check if the token is valid + // const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); + // if (!isValidRequest) return null - // if the token is valid, burn the request - await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); - return loginRequest - }) - } + // // if the token is valid, burn the request + // await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); + // return loginRequest + // }) + // } } \ No newline at end of file diff --git a/src/lib/server/api/tests/login-requests.service.test.ts b/src/lib/server/api/tests/login-requests.service.test.ts new file mode 100644 index 0000000..33329e5 --- /dev/null +++ b/src/lib/server/api/tests/login-requests.service.test.ts @@ -0,0 +1,72 @@ +// import 'reflect-metadata'; +// import { LoginRequestsService } from '../services/login-requests.service'; +// import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +// import { TokensService } from '../services/tokens.service'; +// import { MailerService } from '../services/mailer.service'; +// import { UsersRepository } from '../repositories/users.repository'; +// import { DatabaseProvider, LuciaProvider } from '../providers'; +// import { LoginRequestsRepository } from '../repositories/login-requests.repository'; +// import { PgDatabase } from 'drizzle-orm/pg-core'; +// import { container } from 'tsyringe'; + +// describe('LoginRequestService', () => { +// let service: LoginRequestsService; +// let tokensService = vi.mocked(TokensService.prototype) +// let mailerService = vi.mocked(MailerService.prototype); +// let usersRepository = vi.mocked(UsersRepository.prototype); +// let loginRequestsRepository = vi.mocked(LoginRequestsRepository.prototype); +// let luciaProvider = vi.mocked(LuciaProvider); +// let databaseProvider = vi.mocked(PgDatabase); + +// beforeAll(() => { +// service = container +// .register(TokensService, { useValue: tokensService }) +// .register(MailerService, { useValue: mailerService }) +// .register(UsersRepository, { useValue: usersRepository }) +// .register(LoginRequestsRepository, { useValue: loginRequestsRepository }) +// .register(LuciaProvider, { useValue: luciaProvider }) +// .register(DatabaseProvider, { useValue: databaseProvider }) +// .resolve(LoginRequestsService); +// }); + + +// afterAll(() => { +// vi.resetAllMocks() +// }) + +// describe('Create', () => { +// tokensService.generateTokenWithExpiryAndHash = vi.fn().mockResolvedValue({ +// token: "1", +// expiry: new Date(), +// hashedToken: "xyz" +// } satisfies Awaited>) + +// loginRequestsRepository.create = vi.fn().mockResolvedValue({ +// createdAt: new Date(), +// email: 'me@test.com', +// expiresAt: new Date(), +// hashedToken: '111', +// id: '1', +// updatedAt: new Date() +// } satisfies Awaited>) + +// mailerService.sendLoginRequest = vi.fn().mockResolvedValue(null) + +// const spy_mailerService_sendLoginRequest = vi.spyOn(mailerService, 'sendLoginRequest') +// const spy_tokensService_generateTokenWithExpiryAndHash = vi.spyOn(tokensService, 'generateTokenWithExpiryAndHash') +// const spy_loginRequestsRepository_create = vi.spyOn(loginRequestsRepository, 'create') + +// it('should resolve', async () => { +// await expect(service.create({ email: "test" })).resolves.toBeUndefined() +// }) +// it('should generate a token with expiry and hash', async () => { +// expect(spy_tokensService_generateTokenWithExpiryAndHash).toBeCalledTimes(1) +// }) +// it('should send an email with token', async () => { +// expect(spy_mailerService_sendLoginRequest).toHaveBeenCalledTimes(1) +// }) +// it('should create a new login request record', async () => { +// expect(spy_loginRequestsRepository_create).toBeCalledTimes(1) +// }) +// }) +// }); diff --git a/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts b/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts index d641278..5d18bf0 100644 --- a/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts +++ b/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts @@ -3,7 +3,7 @@ import { redirect } from 'sveltekit-flash-message/server'; import type { PageServerLoad } from './$types'; import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'; import db from '../../../../../../db'; -import { roles, userRoles, users } from '$db/schema'; +import { roles, userRoles, usersTable } from '$db/schema'; export const load: PageServerLoad = async (event) => { const { params } = event; @@ -14,10 +14,10 @@ export const load: PageServerLoad = async (event) => { redirect(302, '/login', notSignedInMessage, event); } - const foundUser = await db.query.users.findFirst({ - where: eq(users.cuid, id), + const foundUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.cuid, id), with: { - userRoles: { + user_roles: { with: { role: { columns: { @@ -30,7 +30,7 @@ export const load: PageServerLoad = async (event) => { }, }); - const containsAdminRole = foundUser?.userRoles?.some( + const containsAdminRole = foundUser?.user_roles?.some( (user_role) => user_role?.role?.name === 'admin', ); if (!containsAdminRole) { @@ -38,7 +38,7 @@ export const load: PageServerLoad = async (event) => { redirect(302, '/login', notSignedInMessage, event); } - const currentRoleIds = foundUser?.userRoles?.map((user_role) => user_role?.role.cuid) || []; + const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || []; let availableRoles: { name: string; cuid: string }[] = []; if (currentRoleIds?.length > 0) { availableRoles = await db.query.roles.findMany({ @@ -65,7 +65,7 @@ export const actions = { redirect(302, '/login', notSignedInMessage, event); } - const userRoles = await db.query.userRoles.findMany({ + const userRolesList = await db.query.userRoles.findMany({ where: eq(userRoles.user_id, user.id), with: { role: { @@ -77,9 +77,9 @@ export const actions = { }, }); - console.log('userRoles', userRoles); + console.log('userRoles', userRolesList); - const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin'); + const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin'); console.log('containsAdminRole', containsAdminRole); if (!containsAdminRole) { redirect(302, '/', forbiddenMessage, event); @@ -108,7 +108,7 @@ export const actions = { redirect(302, '/login', notSignedInMessage, event); } - const userRoles = await db.query.userRoles.findMany({ + const userRolesList = await db.query.userRoles.findMany({ where: eq(userRoles.user_id, user.id), with: { role: { @@ -120,7 +120,7 @@ export const actions = { }, }); - const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin'); + const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin'); if (!containsAdminRole) { redirect(302, '/', forbiddenMessage, event); } diff --git a/src/routes/(app)/(protected)/profile/+page.server.ts b/src/routes/(app)/(protected)/profile/+page.server.ts index 18ea6ad..8157721 100644 --- a/src/routes/(app)/(protected)/profile/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/+page.server.ts @@ -8,7 +8,7 @@ import { changeEmailSchema, profileSchema } from '$lib/validations/account'; import { notSignedInMessage } from '$lib/flashMessages'; import db from '../../../../db'; import type { PageServerLoad } from './$types'; -import { users, twoFactor } from '$db/schema'; +import { usersTable, twoFactor } from '$db/schema'; import { userNotAuthenticated } from '$lib/server/auth-utils'; export const load: PageServerLoad = async (event) => { @@ -18,8 +18,8 @@ export const load: PageServerLoad = async (event) => { redirect(302, '/login', notSignedInMessage, event); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user!.id!), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user!.id!), }); const profileForm = await superValidate(zod(profileSchema), { @@ -72,8 +72,8 @@ export const actions: Actions = { const user = event.locals.user; const newUsername = form.data.username; - const existingUser = await db.query.users.findFirst({ - where: eq(users.username, newUsername), + const existingUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.username, newUsername), }); if (existingUser && existingUser.id !== user.id) { @@ -81,13 +81,13 @@ export const actions: Actions = { } await db - .update(users) + .update(usersTable) .set({ first_name: form.data.firstName, last_name: form.data.lastName, username: form.data.username, }) - .where(eq(users.id, user.id)); + .where(eq(usersTable.id, user.id)); } catch (e) { // @ts-expect-error if (e.message === `AUTH_INVALID_USER_ID`) { @@ -119,17 +119,17 @@ export const actions: Actions = { } const user = event.locals.user; - const existingUser = await db.query.users.findFirst({ - where: eq(users.email, newEmail), + const existingUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.email, newEmail), }); if (existingUser && existingUser.id !== user.id) { return setError(form, 'email', 'That email is already taken'); } - await db.update(users).set({ email: form.data.email }).where(eq(users.id, user.id)); + await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id)); - if (user.email !== form.data.email) { + // if (user.email !== form.data.email) { // Send email to confirm new email? // auth.update // await locals.prisma.key.update({ @@ -143,7 +143,7 @@ export const actions: Actions = { // auth.updateUserAttributes(user.user_id, { // receiveEmail: false // }); - } + // } return message(form, { type: 'success', message: 'Email updated successfully!' }); }, diff --git a/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts b/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts index 8d6ef62..baa9ee0 100644 --- a/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts @@ -8,7 +8,7 @@ import type { PageServerLoad } from '../../../$types'; import db from '../../../../../../../db'; import { changeUserPasswordSchema } from '$lib/validations/account'; import { lucia } from '$lib/server/auth.js'; -import { users } from '$db/schema'; +import { usersTable } from '$db/schema'; import { notSignedInMessage } from '$lib/flashMessages'; import type { Cookie } from 'lucia'; import { userNotAuthenticated } from '$lib/server/auth-utils'; @@ -56,8 +56,8 @@ export const actions: Actions = { return fail(401); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user!.id), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user!.id), }); if (!dbUser?.hashed_password) { @@ -87,9 +87,9 @@ export const actions: Actions = { const hashedPassword = await new Argon2id().hash(form.data.password); await lucia.invalidateUserSessions(user.id); await db - .update(users) + .update(usersTable) .set({ hashed_password: hashedPassword }) - .where(eq(users.id, user.id)); + .where(eq(usersTable.id, user.id)); await lucia.createSession(user.id, { country: event.locals.session?.ipCountry ?? 'unknown', }); diff --git a/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts b/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts index 38bca3d..d119b43 100644 --- a/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts @@ -13,7 +13,7 @@ import type { PageServerLoad } from '../../$types'; import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account'; import { notSignedInMessage } from '$lib/flashMessages'; import db from '../../../../../../db'; -import { recoveryCodes, twoFactor, users } from '$db/schema'; +import { recoveryCodes, twoFactor, usersTable } from '$db/schema'; import { userNotAuthenticated } from '$lib/server/auth-utils'; import env from '../../../../../../env'; @@ -27,8 +27,8 @@ export const load: PageServerLoad = async (event) => { redirect(302, '/login', notSignedInMessage, event); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user!.id!), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user!.id!), }); const twoFactorDetails = await db.query.twoFactor.findFirst({ @@ -111,8 +111,8 @@ export const actions: Actions = { return fail(401); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user!.id!), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user!.id!), }); if (!dbUser?.hashed_password) { @@ -190,8 +190,8 @@ export const actions: Actions = { }); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user.id), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user.id), }); if (!dbUser?.hashed_password) { diff --git a/src/routes/(app)/(protected)/profile/security/two-factor/recovery-codes/+page.server.ts b/src/routes/(app)/(protected)/profile/security/two-factor/recovery-codes/+page.server.ts index 74a2820..fb8b009 100644 --- a/src/routes/(app)/(protected)/profile/security/two-factor/recovery-codes/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/two-factor/recovery-codes/+page.server.ts @@ -5,7 +5,7 @@ import { alphabet, generateRandomString } from 'oslo/crypto'; import { redirect } from 'sveltekit-flash-message/server'; import { notSignedInMessage } from '$lib/flashMessages'; import type { PageServerLoad } from '../../../$types'; -import {recoveryCodes, twoFactor, users} from '$db/schema'; +import {recoveryCodes, twoFactor, usersTable} from '$db/schema'; import { userNotAuthenticated } from '$lib/server/auth-utils'; export const load: PageServerLoad = async (event) => { @@ -15,8 +15,8 @@ export const load: PageServerLoad = async (event) => { redirect(302, '/login', notSignedInMessage, event); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user!.id), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user!.id), }); const twoFactorDetails = await db.query.twoFactor.findFirst({ diff --git a/src/routes/(app)/+page.server.ts b/src/routes/(app)/+page.server.ts index be5f150..190bd71 100644 --- a/src/routes/(app)/+page.server.ts +++ b/src/routes/(app)/+page.server.ts @@ -2,7 +2,7 @@ import type { MetaTagsProps } from 'svelte-meta-tags'; import { eq } from 'drizzle-orm'; import type { PageServerLoad } from './$types'; import db from '../../db'; -import { collections, users, wishlists } from '$db/schema'; +import { collections, usersTable, wishlists } from '$db/schema'; import { userFullyAuthenticated } from '$lib/server/auth-utils'; export const load: PageServerLoad = async (event) => { @@ -42,8 +42,8 @@ export const load: PageServerLoad = async (event) => { }); if (userFullyAuthenticated(user, session)) { - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user!.id!), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user!.id!), }); console.log('Sending back user details'); diff --git a/src/routes/(app)/about/+page.ts b/src/routes/(app)/about/+page.ts index 3e13462..763e9db 100644 --- a/src/routes/(app)/about/+page.ts +++ b/src/routes/(app)/about/+page.ts @@ -1,9 +1,9 @@ -import { dev } from '$app/environment'; +// import { dev } from '$app/environment'; -// we don't need any JS on this page, though we'll load -// it in dev so that we get hot module replacement... -export const csr = dev; +// // we don't need any JS on this page, though we'll load +// // it in dev so that we get hot module replacement... +// export const csr = dev; -// since there's no dynamic data here, we can prerender -// it so that it gets served as a static asset in prod -export const prerender = true; +// // since there's no dynamic data here, we can prerender +// // it so that it gets served as a static asset in prod +// export const prerender = true; diff --git a/src/routes/(app)/privacy/+page.server.ts b/src/routes/(app)/privacy/+page.server.ts index c8cacf0..10cfeb3 100644 --- a/src/routes/(app)/privacy/+page.server.ts +++ b/src/routes/(app)/privacy/+page.server.ts @@ -1 +1 @@ -export const prerender = true; \ No newline at end of file +// export const prerender = true; \ No newline at end of file diff --git a/src/routes/(app)/terms/+page.server.ts b/src/routes/(app)/terms/+page.server.ts index 189f71e..4973a60 100644 --- a/src/routes/(app)/terms/+page.server.ts +++ b/src/routes/(app)/terms/+page.server.ts @@ -1 +1 @@ -export const prerender = true; +// export const prerender = true; diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index 6d91a2a..5da474b 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -8,7 +8,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server'; import db from '../../../db'; import { lucia } from '$lib/server/auth'; import { signInSchema } from '$lib/validations/auth'; -import { twoFactor, users, type Users } from '$db/schema'; +import { twoFactor, usersTable, type Users } from '$db/schema'; import type { PageServerLoad } from './$types'; import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; @@ -57,8 +57,8 @@ export const actions: Actions = { let session; let sessionCookie; - const user: Users | undefined = await db.query.users.findFirst({ - where: or(eq(users.username, form.data.username), eq(users.email, form.data.username)), + const user: Users | undefined = await db.query.usersTable.findFirst({ + where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)), }); if (!user) { diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/sign-up/+page.server.ts index 1e5d73a..73b05d0 100644 --- a/src/routes/(auth)/sign-up/+page.server.ts +++ b/src/routes/(auth)/sign-up/+page.server.ts @@ -10,7 +10,7 @@ import { lucia } from '$lib/server/auth'; import { signUpSchema } from '$lib/validations/auth'; import { add_user_to_role } from '$server/roles'; import db from '../../../db'; -import { collections, users, wishlists } from '$db/schema'; +import { collections, usersTable, wishlists } from '$db/schema'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; @@ -76,8 +76,8 @@ export const actions: Actions = { // Adding user to the db console.log('Check if user already exists'); - const existing_user = await db.query.users.findFirst({ - where: eq(users.username, form.data.username), + const existing_user = await db.query.usersTable.findFirst({ + where: eq(usersTable.username, form.data.username), }); if (existing_user) { @@ -89,7 +89,7 @@ export const actions: Actions = { const hashedPassword = await new Argon2id().hash(form.data.password); const user = await db - .insert(users) + .insert(usersTable) .values({ username: form.data.username, hashed_password: hashedPassword, diff --git a/src/routes/(auth)/totp/+page.server.ts b/src/routes/(auth)/totp/+page.server.ts index 5892599..4e6f719 100644 --- a/src/routes/(auth)/totp/+page.server.ts +++ b/src/routes/(auth)/totp/+page.server.ts @@ -10,7 +10,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server'; import db from '../../../db'; import { lucia } from '$lib/server/auth'; import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth'; -import { users, twoFactor, recoveryCodes } from '$db/schema'; +import { usersTable, twoFactor, recoveryCodes } from '$db/schema'; import type {PageServerLoad, RequestEvent} from './$types'; import { notSignedInMessage } from '$lib/flashMessages'; import env from '../../../env'; @@ -24,8 +24,8 @@ export const load: PageServerLoad = async (event) => { } if (user && session) { - const dbUser = await db.query.users.findFirst({ - where: eq(users.username, user.username), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.username, user.username), }); const twoFactorDetails = await db.query.twoFactor.findFirst({ @@ -262,8 +262,8 @@ async function validateUserData(event: RequestEvent, locals: App.Locals) { throw fail(401); } - const dbUser = await db.query.users.findFirst({ - where: eq(users.username, user.username), + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.username, user.username), }); if (!dbUser) { diff --git a/src/routes/api/auth/reset-password/+server.ts b/src/routes/api/auth/reset-password/+server.ts index a36eebf..d793b58 100644 --- a/src/routes/api/auth/reset-password/+server.ts +++ b/src/routes/api/auth/reset-password/+server.ts @@ -1,7 +1,7 @@ import db from '../../../../db'; import { error } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; -import { users } from '$db/schema'; +import { usersTable } from '$db/schema'; import { createPasswordResetToken } from '$lib/server/auth-utils.js'; import { PUBLIC_SITE_URL } from '$env/static/public'; @@ -12,8 +12,8 @@ export async function POST({ locals, request }) { error(401, { message: 'Unauthorized' }); } - const user = await db.query.users.findFirst({ - where: eq(users.email, email), + const user = await db.query.usersTable.findFirst({ + where: eq(usersTable.email, email), }); if (!user) { diff --git a/src/routes/api/auth/reset-password/[token]/+server.ts b/src/routes/api/auth/reset-password/[token]/+server.ts index 5bb7bfb..d1d4ecf 100644 --- a/src/routes/api/auth/reset-password/[token]/+server.ts +++ b/src/routes/api/auth/reset-password/[token]/+server.ts @@ -1,9 +1,9 @@ -import db from '../../../../../db'; import { eq } from 'drizzle-orm'; -import { password_reset_tokens, users } from '$db/schema'; +import { password_reset_tokens, usersTable } from '$db/schema'; import { isWithinExpirationDate } from 'oslo'; import { lucia } from '$lib/server/auth.js'; import { Argon2id } from 'oslo/password'; +import db from '$db'; export async function POST({ request, params }) { const { password } = await request.json(); @@ -34,7 +34,7 @@ export async function POST({ request, params }) { await lucia.invalidateUserSessions(token.user_id); const hashPassword = await new Argon2id().hash(password); - await db.update(users).set({ hashed_password: hashPassword }).where(eq(users.id, token.user_id)); + await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id)); const session = await lucia.createSession(token.user_id, {}); const sessionCookie = lucia.createSessionCookie(session.id); diff --git a/src/routes/api/collection/[id]/search/+server.ts b/src/routes/api/collection/[id]/search/+server.ts index 2a338d5..046f107 100644 --- a/src/routes/api/collection/[id]/search/+server.ts +++ b/src/routes/api/collection/[id]/search/+server.ts @@ -1,7 +1,7 @@ import { error, json } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; import db from '../../../../../db'; -import { collection_items, users } from '$db/schema'; +import { collection_items, usersTable } from '$db/schema'; // Search a user's collection export async function GET({ url, locals, params }) { @@ -20,7 +20,7 @@ export async function GET({ url, locals, params }) { } const collection = await db.query.collections.findFirst({ - where: eq(users.id, locals?.user?.id), + where: eq(usersTable.id, locals?.user?.id), }); console.log('collection', collection); diff --git a/src/server/users.ts b/src/server/users.ts index 9a61736..e29b14c 100644 --- a/src/server/users.ts +++ b/src/server/users.ts @@ -1,17 +1,17 @@ import db from '../db'; import { eq } from 'drizzle-orm'; -import { users, type Users } from '$db/schema'; +import { usersTable, type Users } from '$db/schema'; import { add_user_to_role } from './roles'; export function create_user(user: Users) { - return db.insert(users).values({ + return db.insert(usersTable).values({ username: user.username, }); } export async function find_or_create_user(user: Users) { - const existing_user = await db.query.users.findFirst({ - where: eq(users.username, user.username), + const existing_user = await db.query.usersTable.findFirst({ + where: eq(usersTable.username, user.username), }); if (existing_user) { return existing_user; @@ -23,8 +23,8 @@ export async function find_or_create_user(user: Users) { } export async function find_user_with_roles(user_id: string) { - const user_with_roles = await db.query.users.findFirst({ - where: eq(users.id, user_id), + const user_with_roles = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, user_id), with: { user_roles: { with: { diff --git a/svelte.config.js b/svelte.config.js index 9f0b43e..435efb8 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,3 +1,4 @@ +import 'reflect-metadata' import adapter from '@sveltejs/adapter-vercel'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { preprocessMeltUI } from '@melt-ui/pp'; diff --git a/vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs b/vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs new file mode 100644 index 0000000..4b25ada --- /dev/null +++ b/vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs @@ -0,0 +1,41 @@ +// vite.config.ts +import { sveltekit } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/@sveltejs+kit@2.5.18_@sveltejs+vite-plugin-svelte@3.1.1_svelte@5.0.0-next.175_vite@5.3.5_@typ_ps3ubydtq463bedtodypwfb5fu/node_modules/@sveltejs/kit/src/exports/vite/index.js"; +import { defineConfig } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/vite@5.3.5_@types+node@20.14.13_sass@1.77.8/node_modules/vite/dist/node/index.js"; +var vite_config_default = defineConfig({ + plugins: [ + // sentrySvelteKit({ + // sourceMapsUploadOptions: { + // org: process.env.SENTRY_ORG, + // project: process.env.SENTRY_PROJECT, + // authToken: process.env.SENTRY_AUTH_TOKEN, + // cleanArtifacts: true, + // } + // }), + sveltekit() + ], + test: { + include: ["src/**/*.{test,spec}.{js,ts}"] + }, + css: { + devSourcemap: true, + preprocessorOptions: { + postcss: { + additionalData: ` + @custom-media --below_small (width < 400px); + @custom-media --below_med (width < 700px); + @custom-media --below_large (width < 900px); + @custom-media --below_xlarge (width < 1200px); + + @custom-media --above_small (width > 400px); + @custom-media --above_med (width > 700px); + @custom-media --above_large (width > 900px); + @custom-media --above_xlarge (width > 1200px); + ` + } + } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL2JzaGVsbG51L3Byb2plY3RzL3dlYnNpdGVzL2JvcmVkZ2FtZS92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWUvdml0ZS5jb25maWcudHNcIjsvLyBpbXBvcnQgeyBzZW50cnlTdmVsdGVLaXQgfSBmcm9tIFwiQHNlbnRyeS9zdmVsdGVraXRcIjtcbmltcG9ydCB7IHN2ZWx0ZWtpdCB9IGZyb20gJ0BzdmVsdGVqcy9raXQvdml0ZSc7XG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcblxuLy8gVE9ETzogRml4IFNlbnRyeVxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcblx0cGx1Z2luczogW1xuXHRcdC8vIHNlbnRyeVN2ZWx0ZUtpdCh7XG5cdFx0Ly8gXHRzb3VyY2VNYXBzVXBsb2FkT3B0aW9uczoge1xuXHRcdC8vIFx0XHRvcmc6IHByb2Nlc3MuZW52LlNFTlRSWV9PUkcsXG5cdFx0Ly8gXHRcdHByb2plY3Q6IHByb2Nlc3MuZW52LlNFTlRSWV9QUk9KRUNULFxuXHRcdC8vIFx0XHRhdXRoVG9rZW46IHByb2Nlc3MuZW52LlNFTlRSWV9BVVRIX1RPS0VOLFxuXHRcdC8vIFx0XHRjbGVhbkFydGlmYWN0czogdHJ1ZSxcblx0XHQvLyBcdH1cblx0XHQvLyB9KSxcblx0XHRzdmVsdGVraXQoKVxuXHRdLFxuXHR0ZXN0OiB7XG5cdFx0aW5jbHVkZTogWydzcmMvKiovKi57dGVzdCxzcGVjfS57anMsdHN9J11cblx0fSxcblx0Y3NzOiB7XG5cdFx0ZGV2U291cmNlbWFwOiB0cnVlLFxuXHRcdHByZXByb2Nlc3Nvck9wdGlvbnM6IHtcblx0XHRcdHBvc3Rjc3M6IHtcblx0XHRcdFx0YWRkaXRpb25hbERhdGE6IGBcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X3NtYWxsICh3aWR0aCA8IDQwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X21lZCAod2lkdGggPCA3MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd19sYXJnZSAod2lkdGggPCA5MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd194bGFyZ2UgKHdpZHRoIDwgMTIwMHB4KTtcblxuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfc21hbGwgKHdpZHRoID4gNDAwcHgpO1xuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfbWVkICh3aWR0aCA+IDcwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX2xhcmdlICh3aWR0aCA+IDkwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX3hsYXJnZSAod2lkdGggPiAxMjAwcHgpO1xuXHRcdFx0XHRgXG5cdFx0XHR9XG5cdFx0fVxuXHR9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyxpQkFBaUI7QUFDMUIsU0FBUyxvQkFBb0I7QUFHN0IsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDM0IsU0FBUztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQVNSLFVBQVU7QUFBQSxFQUNYO0FBQUEsRUFDQSxNQUFNO0FBQUEsSUFDTCxTQUFTLENBQUMsOEJBQThCO0FBQUEsRUFDekM7QUFBQSxFQUNBLEtBQUs7QUFBQSxJQUNKLGNBQWM7QUFBQSxJQUNkLHFCQUFxQjtBQUFBLE1BQ3BCLFNBQVM7QUFBQSxRQUNSLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFXakI7QUFBQSxJQUNEO0FBQUEsRUFDRDtBQUNELENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== From 9b24a5c0950f4948696d59a9c0ee336dc7986c4a Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 2 Aug 2024 17:48:45 -0700 Subject: [PATCH 12/34] Removing get client address for now and using adapter node because vercel breaks. --- .node-version | 2 +- package.json | 3 --- src/hooks.server.ts | 5 ++--- src/routes/(app)/about/+page.ts | 14 +++++++------- svelte.config.js | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.node-version b/.node-version index 7d1aef0..2edeafb 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.1.0 \ No newline at end of file +20 \ No newline at end of file diff --git a/package.json b/package.json index 1d103b0..37b8e76 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,6 @@ "site:update": "pnpm update -i -L", "test:unit": "vitest" }, - "engines": { - "node": "22.x" - }, "devDependencies": { "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", diff --git a/src/hooks.server.ts b/src/hooks.server.ts index f1e2dd8..ce495b2 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -22,19 +22,18 @@ const apiClient: Handle = async ({ event, resolve }) => { const { api } = hc('/', { fetch: event.fetch, headers: { - 'x-forwarded-for': event.getClientAddress(), host: event.request.headers.get('host') || '' } }); /* ----------------------------- Auth functions ----------------------------- */ async function getAuthedUser() { - const { data } = await api.iam.user.$get().then(parseApiResponse) + const { data } = await api.user.me.$get().then(parseApiResponse) return data && data.user; } async function getAuthedUserOrThrow() { - const { data } = await api.iam.user.$get().then(parseApiResponse); + const { data } = await api.user.me.$get().then(parseApiResponse); if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/'); return data?.user; } diff --git a/src/routes/(app)/about/+page.ts b/src/routes/(app)/about/+page.ts index 763e9db..3e13462 100644 --- a/src/routes/(app)/about/+page.ts +++ b/src/routes/(app)/about/+page.ts @@ -1,9 +1,9 @@ -// import { dev } from '$app/environment'; +import { dev } from '$app/environment'; -// // we don't need any JS on this page, though we'll load -// // it in dev so that we get hot module replacement... -// export const csr = dev; +// we don't need any JS on this page, though we'll load +// it in dev so that we get hot module replacement... +export const csr = dev; -// // since there's no dynamic data here, we can prerender -// // it so that it gets served as a static asset in prod -// export const prerender = true; +// since there's no dynamic data here, we can prerender +// it so that it gets served as a static asset in prod +export const prerender = true; diff --git a/svelte.config.js b/svelte.config.js index 435efb8..e3978f7 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,5 +1,5 @@ import 'reflect-metadata' -import adapter from '@sveltejs/adapter-vercel'; +import adapter from '@sveltejs/adapter-node'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { preprocessMeltUI } from '@melt-ui/pp'; import sequence from 'svelte-sequential-preprocessor'; From 9fb6db45ff3e095cc3736409ddcf5c52e0a94713 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 2 Aug 2024 21:51:44 -0700 Subject: [PATCH 13/34] Adding back tsyringe dependency. --- package.json | 3 ++- pnpm-lock.yaml | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 37b8e76..8fc5644 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@lukeed/uuid": "^2.0.1", "@neondatabase/serverless": "^0.9.4", "@paralleldrive/cuid2": "^2.2.2", + "@resvg/resvg-js": "^2.6.2", "@sveltejs/adapter-node": "^5.2.0", "@sveltejs/adapter-vercel": "^5.4.1", "@types/feather-icons": "^4.29.4", @@ -116,12 +117,12 @@ "radix-svelte": "^0.9.0", "rate-limit-redis": "^4.2.0", "reflect-metadata": "^0.2.2", - "@resvg/resvg-js": "^2.6.2", "svelte-french-toast": "^1.2.0", "svelte-lazy-loader": "^1.0.0", "tailwind-merge": "^2.4.0", "tailwind-variants": "^0.2.1", "tailwindcss-animate": "^1.0.7", + "tsyringe": "^4.8.0", "zod-to-json-schema": "^3.23.2" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b9fcca..9c3acfa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,6 +152,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))) + tsyringe: + specifier: ^4.8.0 + version: 4.8.0 zod-to-json-schema: specifier: ^3.23.2 version: 3.23.2(zod@3.23.8) @@ -4611,6 +4614,9 @@ packages: '@swc/wasm': optional: true + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} @@ -4622,6 +4628,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.8.0: + resolution: {integrity: sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==} + engines: {node: '>= 6.0.0'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -8971,6 +8981,8 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tslib@1.14.1: {} + tslib@2.4.0: optional: true @@ -8983,6 +8995,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsyringe@4.8.0: + dependencies: + tslib: 1.14.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 From 9f1ebffee5651ef609701830ddece5952dd9f6ec Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Mon, 5 Aug 2024 09:53:04 -0700 Subject: [PATCH 14/34] Try using adapter vercel. --- src/hooks.server.ts | 1 + svelte.config.js | 2 +- ....timestamp-1722555436409-2175218f70ffd.mjs | 41 ------------------- 3 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs diff --git a/src/hooks.server.ts b/src/hooks.server.ts index ce495b2..c9e32e9 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -22,6 +22,7 @@ const apiClient: Handle = async ({ event, resolve }) => { const { api } = hc('/', { fetch: event.fetch, headers: { + 'x-forwarded-for': event.url.host.includes('sveltekit-prerender') ? '127.0.0.1' : event.getClientAddress(), host: event.request.headers.get('host') || '' } }); diff --git a/svelte.config.js b/svelte.config.js index e3978f7..435efb8 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,5 +1,5 @@ import 'reflect-metadata' -import adapter from '@sveltejs/adapter-node'; +import adapter from '@sveltejs/adapter-vercel'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { preprocessMeltUI } from '@melt-ui/pp'; import sequence from 'svelte-sequential-preprocessor'; diff --git a/vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs b/vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs deleted file mode 100644 index 4b25ada..0000000 --- a/vite.config.ts.timestamp-1722555436409-2175218f70ffd.mjs +++ /dev/null @@ -1,41 +0,0 @@ -// vite.config.ts -import { sveltekit } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/@sveltejs+kit@2.5.18_@sveltejs+vite-plugin-svelte@3.1.1_svelte@5.0.0-next.175_vite@5.3.5_@typ_ps3ubydtq463bedtodypwfb5fu/node_modules/@sveltejs/kit/src/exports/vite/index.js"; -import { defineConfig } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/vite@5.3.5_@types+node@20.14.13_sass@1.77.8/node_modules/vite/dist/node/index.js"; -var vite_config_default = defineConfig({ - plugins: [ - // sentrySvelteKit({ - // sourceMapsUploadOptions: { - // org: process.env.SENTRY_ORG, - // project: process.env.SENTRY_PROJECT, - // authToken: process.env.SENTRY_AUTH_TOKEN, - // cleanArtifacts: true, - // } - // }), - sveltekit() - ], - test: { - include: ["src/**/*.{test,spec}.{js,ts}"] - }, - css: { - devSourcemap: true, - preprocessorOptions: { - postcss: { - additionalData: ` - @custom-media --below_small (width < 400px); - @custom-media --below_med (width < 700px); - @custom-media --below_large (width < 900px); - @custom-media --below_xlarge (width < 1200px); - - @custom-media --above_small (width > 400px); - @custom-media --above_med (width > 700px); - @custom-media --above_large (width > 900px); - @custom-media --above_xlarge (width > 1200px); - ` - } - } - } -}); -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL2JzaGVsbG51L3Byb2plY3RzL3dlYnNpdGVzL2JvcmVkZ2FtZS92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWUvdml0ZS5jb25maWcudHNcIjsvLyBpbXBvcnQgeyBzZW50cnlTdmVsdGVLaXQgfSBmcm9tIFwiQHNlbnRyeS9zdmVsdGVraXRcIjtcbmltcG9ydCB7IHN2ZWx0ZWtpdCB9IGZyb20gJ0BzdmVsdGVqcy9raXQvdml0ZSc7XG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcblxuLy8gVE9ETzogRml4IFNlbnRyeVxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcblx0cGx1Z2luczogW1xuXHRcdC8vIHNlbnRyeVN2ZWx0ZUtpdCh7XG5cdFx0Ly8gXHRzb3VyY2VNYXBzVXBsb2FkT3B0aW9uczoge1xuXHRcdC8vIFx0XHRvcmc6IHByb2Nlc3MuZW52LlNFTlRSWV9PUkcsXG5cdFx0Ly8gXHRcdHByb2plY3Q6IHByb2Nlc3MuZW52LlNFTlRSWV9QUk9KRUNULFxuXHRcdC8vIFx0XHRhdXRoVG9rZW46IHByb2Nlc3MuZW52LlNFTlRSWV9BVVRIX1RPS0VOLFxuXHRcdC8vIFx0XHRjbGVhbkFydGlmYWN0czogdHJ1ZSxcblx0XHQvLyBcdH1cblx0XHQvLyB9KSxcblx0XHRzdmVsdGVraXQoKVxuXHRdLFxuXHR0ZXN0OiB7XG5cdFx0aW5jbHVkZTogWydzcmMvKiovKi57dGVzdCxzcGVjfS57anMsdHN9J11cblx0fSxcblx0Y3NzOiB7XG5cdFx0ZGV2U291cmNlbWFwOiB0cnVlLFxuXHRcdHByZXByb2Nlc3Nvck9wdGlvbnM6IHtcblx0XHRcdHBvc3Rjc3M6IHtcblx0XHRcdFx0YWRkaXRpb25hbERhdGE6IGBcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X3NtYWxsICh3aWR0aCA8IDQwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X21lZCAod2lkdGggPCA3MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd19sYXJnZSAod2lkdGggPCA5MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd194bGFyZ2UgKHdpZHRoIDwgMTIwMHB4KTtcblxuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfc21hbGwgKHdpZHRoID4gNDAwcHgpO1xuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfbWVkICh3aWR0aCA+IDcwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX2xhcmdlICh3aWR0aCA+IDkwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX3hsYXJnZSAod2lkdGggPiAxMjAwcHgpO1xuXHRcdFx0XHRgXG5cdFx0XHR9XG5cdFx0fVxuXHR9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyxpQkFBaUI7QUFDMUIsU0FBUyxvQkFBb0I7QUFHN0IsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDM0IsU0FBUztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQVNSLFVBQVU7QUFBQSxFQUNYO0FBQUEsRUFDQSxNQUFNO0FBQUEsSUFDTCxTQUFTLENBQUMsOEJBQThCO0FBQUEsRUFDekM7QUFBQSxFQUNBLEtBQUs7QUFBQSxJQUNKLGNBQWM7QUFBQSxJQUNkLHFCQUFxQjtBQUFBLE1BQ3BCLFNBQVM7QUFBQSxRQUNSLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFXakI7QUFBQSxJQUNEO0FBQUEsRUFDRDtBQUNELENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== From 02d28001210938e98f21876e09b0935fe4edc033 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 6 Aug 2024 10:54:33 -0700 Subject: [PATCH 15/34] Updating dependencies --- package.json | 32 +- pnpm-lock.yaml | 994 +++++++++++++++++++++++++------------------------ 2 files changed, 520 insertions(+), 506 deletions(-) diff --git a/package.json b/package.json index 8fc5644..76cc897 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,18 @@ "devDependencies": { "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", - "@playwright/test": "^1.45.3", + "@playwright/test": "^1.46.0", "@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/enhanced-img": "^0.3.1", - "@sveltejs/kit": "^2.5.18", + "@sveltejs/kit": "^2.5.20", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@types/cookie": "^0.6.0", - "@types/node": "^20.14.13", + "@types/node": "^20.14.14", "@types/pg": "^8.11.6", - "@typescript-eslint/eslint-plugin": "^7.17.0", - "@typescript-eslint/parser": "^7.17.0", - "autoprefixer": "^10.4.19", - "drizzle-kit": "^0.23.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "autoprefixer": "^10.4.20", + "drizzle-kit": "^0.23.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.43.0", @@ -45,7 +45,7 @@ "lucia": "3.2.0", "lucide-svelte": "^0.408.0", "nodemailer": "^6.9.14", - "postcss": "^8.4.40", + "postcss": "^8.4.41", "postcss-import": "^16.1.0", "postcss-load-config": "^5.1.0", "postcss-preset-env": "^9.6.0", @@ -55,7 +55,7 @@ "satori": "^0.10.14", "satori-html": "^0.3.2", "svelte": "5.0.0-next.175", - "svelte-check": "^3.8.4", + "svelte-check": "^3.8.5", "svelte-headless-table": "^0.18.2", "svelte-meta-tags": "^3.1.2", "svelte-preprocess": "^6.0.2", @@ -66,7 +66,7 @@ "tailwindcss": "^3.4.7", "ts-node": "^10.9.2", "tslib": "^2.6.3", - "tsx": "^4.16.2", + "tsx": "^4.16.5", "typescript": "^5.5.4", "vite": "^5.3.5", "vitest": "^1.6.0", @@ -79,7 +79,7 @@ "@iconify-icons/line-md": "^1.2.30", "@iconify-icons/mdi": "^1.2.48", "@internationalized/date": "^3.5.5", - "@lucia-auth/adapter-drizzle": "^1.0.7", + "@lucia-auth/adapter-drizzle": "^1.1.0", "@lukeed/uuid": "^2.0.1", "@neondatabase/serverless": "^0.9.4", "@paralleldrive/cuid2": "^2.2.2", @@ -89,19 +89,19 @@ "@types/feather-icons": "^4.29.4", "@vercel/og": "^0.5.20", "arctic": "^1.9.2", - "bits-ui": "^0.21.12", + "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.11.0", + "bullmq": "^5.12.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", - "drizzle-orm": "^0.32.1", + "drizzle-orm": "^0.32.2", "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.5.2", + "hono": "^4.5.3", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", @@ -113,7 +113,7 @@ "oslo": "^1.2.1", "pg": "^8.12.0", "postgres": "^3.4.4", - "qrcode": "^1.5.3", + "qrcode": "^1.5.4", "radix-svelte": "^0.9.0", "rate-limit-redis": "^4.2.0", "reflect-metadata": "^0.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c3acfa..6e072f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 5.0.13 '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.2)(zod@3.23.8) + version: 0.2.2(hono@4.5.3)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -24,8 +24,8 @@ importers: specifier: ^3.5.5 version: 3.5.5 '@lucia-auth/adapter-drizzle': - specifier: ^1.0.7 - version: 1.0.7(lucia@3.2.0) + specifier: ^1.1.0 + version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) '@lukeed/uuid': specifier: ^2.0.1 version: 2.0.1 @@ -40,10 +40,10 @@ importers: version: 2.6.2 '@sveltejs/adapter-node': specifier: ^5.2.0 - version: 5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) + version: 5.2.0(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) '@sveltejs/adapter-vercel': specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) + version: 5.4.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -54,14 +54,14 @@ importers: specifier: ^1.9.2 version: 1.9.2 bits-ui: - specifier: ^0.21.12 - version: 0.21.12(svelte@5.0.0-next.175) + specifier: ^0.21.13 + version: 0.21.13(svelte@5.0.0-next.175) boardgamegeekclient: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.11.0 - version: 5.11.0 + specifier: ^5.12.0 + version: 5.12.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -78,23 +78,23 @@ importers: specifier: ^11.0.6 version: 11.0.6 drizzle-orm: - specifier: ^0.32.1 - version: 0.32.1(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) + specifier: ^0.32.2 + version: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.5.2 - version: 4.5.2 + specifier: ^4.5.3 + version: 4.5.3 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.2) + version: 0.4.0(hono@4.5.3) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -126,8 +126,8 @@ importers: specifier: ^3.4.4 version: 3.4.4 qrcode: - specifier: ^1.5.3 - version: 1.5.3 + specifier: ^1.5.4 + version: 1.5.4 radix-svelte: specifier: ^0.9.0 version: 0.9.0(svelte@5.0.0-next.175) @@ -148,10 +148,10 @@ importers: version: 2.4.0 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -166,41 +166,41 @@ importers: specifier: ^0.83.0 version: 0.83.0(svelte@5.0.0-next.175) '@playwright/test': - specifier: ^1.45.3 - version: 1.45.3 + specifier: ^1.46.0 + version: 1.46.0 '@sveltejs/adapter-auto': specifier: ^3.2.2 - version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) + version: 3.2.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) '@sveltejs/enhanced-img': specifier: ^0.3.1 - version: 0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + version: 0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) '@sveltejs/kit': - specifier: ^2.5.18 - version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + specifier: ^2.5.20 + version: 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.14.13 - version: 20.14.13 + specifier: ^20.14.14 + version: 20.14.14 '@types/pg': specifier: ^8.11.6 version: 8.11.6 '@typescript-eslint/eslint-plugin': - specifier: ^7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) + specifier: ^7.18.0 + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': - specifier: ^7.17.0 - version: 7.17.0(eslint@8.57.0)(typescript@5.5.4) + specifier: ^7.18.0 + version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) autoprefixer: - specifier: ^10.4.19 - version: 10.4.19(postcss@8.4.40) + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.41) drizzle-kit: - specifier: ^0.23.0 - version: 0.23.0 + specifier: ^0.23.2 + version: 0.23.2 eslint: specifier: ^8.57.0 version: 8.57.0 @@ -209,7 +209,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: ^2.43.0 - version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) + version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -226,17 +226,17 @@ importers: specifier: ^6.9.14 version: 6.9.14 postcss: - specifier: ^8.4.40 - version: 8.4.40 + specifier: ^8.4.41 + version: 8.4.41 postcss-import: specifier: ^16.1.0 - version: 16.1.0(postcss@8.4.40) + version: 16.1.0(postcss@8.4.41) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5) postcss-preset-env: specifier: ^9.6.0 - version: 9.6.0(postcss@8.4.40) + version: 9.6.0(postcss@8.4.41) prettier: specifier: ^3.3.3 version: 3.3.3 @@ -256,8 +256,8 @@ importers: specifier: 5.0.0-next.175 version: 5.0.0-next.175 svelte-check: - specifier: ^3.8.4 - version: 3.8.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175) + specifier: ^3.8.5 + version: 3.8.5(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) @@ -266,40 +266,40 @@ importers: version: 3.1.2(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) + version: 0.5.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.16.1 - version: 2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.7 - version: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) + version: 3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.13)(typescript@5.5.4) + version: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 tsx: - specifier: ^4.16.2 - version: 4.16.2 + specifier: ^4.16.5 + version: 4.16.5 typescript: specifier: ^5.5.4 version: 5.5.4 vite: specifier: ^5.3.5 - version: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + version: 5.3.5(@types/node@20.14.14)(sass@1.77.8) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.13)(sass@1.77.8) + version: 1.6.0(@types/node@20.14.14)(sass@1.77.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -568,6 +568,9 @@ packages: peerDependencies: postcss: ^8.4 + '@drizzle-team/brocli@0.8.2': + resolution: {integrity: sha512-zTrFENsqGvOkBOuHDC1pXCkDXNd2UhP4lI3gYGhQ1R1SPeAAfqzPsV1dcpMy4uNU6kB5VpU5NGhvwxVNETR02A==} + '@emnapi/core@0.45.0': resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} @@ -1368,9 +1371,10 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@lucia-auth/adapter-drizzle@1.0.7': - resolution: {integrity: sha512-X/V7fLBca8EC/gPXCntwbQpb0+F9oEuRoHElvsi9rCrdnGhCMNxHgwAvgiQ6pes+rIYpyvx4n3hvjqo/fPo03A==} + '@lucia-auth/adapter-drizzle@1.1.0': + resolution: {integrity: sha512-iCTnZWvfI5lLZOdUHZYiXA1jaspIFEeo2extLxQ3DjP3uOVys7IPwBi7zezLIRu9dhro4H4Kji+7gSYyjcef2A==} peerDependencies: + drizzle-orm: '>= 0.29 <1' lucia: 3.x '@lukeed/csprng@1.1.0': @@ -1631,8 +1635,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.45.3': - resolution: {integrity: sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==} + '@playwright/test@1.46.0': + resolution: {integrity: sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==} engines: {node: '>=18'} hasBin: true @@ -1967,8 +1971,8 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: '>= 5.0.0' - '@sveltejs/kit@2.5.18': - resolution: {integrity: sha512-+g06hvpVAnH7b4CDjhnTDgFWBKBiQJpuSmQeGYOuzbO3SC3tdYjRNlDCrafvDtKbGiT2uxY5Dn9qdEUGVZdWOQ==} + '@sveltejs/kit@2.5.20': + resolution: {integrity: sha512-47rJ5BoYwURE/Rp7FNMLp3NzdbWC9DQ/PmKd0mebxT2D/PrPxZxcLImcD3zsWdX2iS6oJk8ITJbO/N2lWnnUqA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2021,8 +2025,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.14.13': - resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} + '@types/node@20.14.14': + resolution: {integrity: sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==} '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} @@ -2036,8 +2040,8 @@ packages: '@types/validator@13.12.0': resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==} - '@typescript-eslint/eslint-plugin@7.17.0': - resolution: {integrity: sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==} + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -2047,8 +2051,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.17.0': - resolution: {integrity: sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==} + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -2057,12 +2061,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@7.17.0': - resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==} + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/type-utils@7.17.0': - resolution: {integrity: sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==} + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -2071,12 +2075,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@7.17.0': - resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==} + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/typescript-estree@7.17.0': - resolution: {integrity: sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==} + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -2084,14 +2088,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@7.17.0': - resolution: {integrity: sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==} + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/visitor-keys@7.17.0': - resolution: {integrity: sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==} + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} '@ungap/structured-clone@1.2.0': @@ -2238,8 +2242,8 @@ packages: async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} - autoprefixer@10.4.19: - resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -2262,8 +2266,8 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bits-ui@0.21.12: - resolution: {integrity: sha512-Cf0iB+ZKwA0ZjkpixrhrZK9PC6pGPFleW/65Xc/z0lpGvWaFtdOhiYEntCHHxZ0VihP3aJaG0OBhUBIbmAePaA==} + bits-ui@0.21.13: + resolution: {integrity: sha512-7nmOh6Ig7ND4DXZHv1FhNsY9yUGrad0+mf3tc4YN//3MgnJT1LnHtk4HZAKgmxCOe7txSX7/39LtYHbkrXokAQ==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.118 @@ -2289,13 +2293,13 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.0: - resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + browserslist@4.23.1: + resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.23.1: - resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + browserslist@4.23.3: + resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2309,8 +2313,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.11.0: - resolution: {integrity: sha512-qVzyWGZqie3VHaYEgRXhId/j8ebfmj6MExEJyUByMsUJA5pVciVle3hKLer5fyMwtQ8lTMP7GwhXV/NZ+HzlRA==} + bullmq@5.12.0: + resolution: {integrity: sha512-kOtSQx9ymylslsLNFD0xOMJM9mHqnq3x6KD7+DYkHByWe0HFRdblpYKhZyL4uR3rwaKZwzOrJVl3RwRaDjZxSg==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -2343,12 +2347,12 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001616: - resolution: {integrity: sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==} - caniuse-lite@1.0.30001640: resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==} + caniuse-lite@1.0.30001649: + resolution: {integrity: sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==} + chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} engines: {node: '>=4'} @@ -2527,6 +2531,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -2609,12 +2622,12 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} - drizzle-kit@0.23.0: - resolution: {integrity: sha512-w9jE97z193dd4jzAyj4Uv2SOh8Ydue70Ki6W0awy4bGM1aPXan6zD6Yv+nNTA6oGgNTDl2MJFxutjHG4fden5g==} + drizzle-kit@0.23.2: + resolution: {integrity: sha512-NWkQ7GD2OTbQ7HzcjsaCOf3n0tlFPSEAF38fvDpwDj8jRbGWGFtN2cD8I8wp4lU+5Os/oyP2xycTKGLHdPipUw==} hasBin: true - drizzle-orm@0.32.1: - resolution: {integrity: sha512-Wq1J+lL8PzwR5K3a1FfoWsbs8powjr3pGA4+5+2ueN1VTLDNFYEolUyUWFtqy8DVRvYbL2n7sXZkgVmK9dQkng==} + drizzle-orm@0.32.2: + resolution: {integrity: sha512-3fXKzPzrgZIcnWCSLiERKN5Opf9Iagrag75snfFlKeKSYB1nlgPBshzW3Zn6dQymkyiib+xc4nIz0t8U+Xdpuw==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' @@ -2708,12 +2721,12 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.4.758: - resolution: {integrity: sha512-/o9x6TCdrYZBMdGeTifAP3wlF/gVT+TtWJe3BSmtNh92Mw81U9hrYwW9OAGUh+sEOX/yz5e34sksqRruZbjYrw==} - electron-to-chromium@1.4.818: resolution: {integrity: sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA==} + electron-to-chromium@1.5.4: + resolution: {integrity: sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==} + emoji-regex@10.3.0: resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} @@ -2723,9 +2736,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encode-utf8@1.0.3: - resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} - encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -3073,8 +3083,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.2: - resolution: {integrity: sha512-93P8XEALrHAUGRZoqXs8MDL3w9mDgRpbW9Sy5x4LS7srg78bKUw7EGynxze+Ft1e/rLGmDAbxeSTMu6dHUSRDw==} + hono@4.5.3: + resolution: {integrity: sha512-r26WwwbKD3BAYdfB294knNnegNda7VfV1tVn66D9Kvl9WQTdrR+5eKdoeaQNHQcC3Gr0KBikzAtjd6VsRGVSaw==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -3504,6 +3514,9 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + nodemailer@6.9.14: resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==} engines: {node: '>=6.0.0'} @@ -3712,13 +3725,13 @@ packages: pkg-types@1.1.0: resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} - playwright-core@1.45.3: - resolution: {integrity: sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==} + playwright-core@1.46.0: + resolution: {integrity: sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==} engines: {node: '>=18'} hasBin: true - playwright@1.45.3: - resolution: {integrity: sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==} + playwright@1.46.0: + resolution: {integrity: sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==} engines: {node: '>=18'} hasBin: true @@ -3965,8 +3978,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.40: - resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -4038,8 +4051,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qrcode@1.5.3: - resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} hasBin: true @@ -4343,8 +4356,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@3.8.4: - resolution: {integrity: sha512-61aHMkdinWyH8BkkTX9jPLYxYzaAAz/FK/VQqdr2FiCQQ/q04WCwDlpGbHff1GdrMYTmW8chlTFvRWL9k0A8vg==} + svelte-check@3.8.5: + resolution: {integrity: sha512-3OGGgr9+bJ/+1nbPgsvulkLC48xBsqsgtc8Wam281H4G9F5v3mYGa2bHRsPuwHC5brKl4AxJH95QF73kmfihGQ==} hasBin: true peerDependencies: svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 @@ -4623,8 +4636,8 @@ packages: tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tsx@4.16.2: - resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} + tsx@4.16.5: + resolution: {integrity: sha512-ArsiAQHEW2iGaqZ8fTA1nX0a+lN5mNTyuGRRO6OW3H/Yno1y9/t1f9YOI1Cfoqz63VAthn++ZYcbDP7jPflc+A==} engines: {node: '>=18.0.0'} hasBin: true @@ -4681,12 +4694,6 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.0.15: - resolution: {integrity: sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - update-browserslist-db@1.1.0: resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} hasBin: true @@ -4955,201 +4962,201 @@ snapshots: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.40)': + '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.41)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.1.0 - '@csstools/postcss-color-function@3.0.19(postcss@8.4.40)': + '@csstools/postcss-color-function@3.0.19(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.40)': + '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.40)': + '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.41)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.40)': + '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.41)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.40)': + '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.41)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.40)': + '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.40)': + '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.40)': + '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.40)': + '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.41)': dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@1.0.1(postcss@8.4.40)': + '@csstools/postcss-initial@1.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.40)': + '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.41)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.1.0 - '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.40)': + '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.41)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.40)': + '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.40)': + '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.40)': + '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.40)': + '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.40)': + '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.41)': dependencies: '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.40)': + '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.41)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.40)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.41)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.40)': + '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.41)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.40)': + '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.40)': + '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.40)': + '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.40)': + '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.41)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.40)': + '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.40)': + '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.41)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.40)': + '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.41)': dependencies: '@csstools/color-helpers': 4.2.1 - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.40)': + '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.41)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.40 + postcss: 8.4.41 - '@csstools/postcss-unset-value@3.0.1(postcss@8.4.40)': + '@csstools/postcss-unset-value@3.0.1(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 '@csstools/selector-resolve-nested@1.1.0(postcss-selector-parser@6.1.0)': dependencies: @@ -5159,9 +5166,11 @@ snapshots: dependencies: postcss-selector-parser: 6.1.0 - '@csstools/utilities@1.0.0(postcss@8.4.40)': + '@csstools/utilities@1.0.0(postcss@8.4.41)': dependencies: - postcss: 8.4.40 + postcss: 8.4.41 + + '@drizzle-team/brocli@0.8.2': {} '@emnapi/core@0.45.0': dependencies: @@ -5535,9 +5544,9 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/zod-validator@0.2.2(hono@4.5.2)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.5.3)(zod@3.23.8)': dependencies: - hono: 4.5.2 + hono: 4.5.3 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -5680,8 +5689,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@lucia-auth/adapter-drizzle@1.0.7(lucia@3.2.0)': + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': dependencies: + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) lucia: 3.2.0 '@lukeed/csprng@1.1.0': {} @@ -5903,9 +5913,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.45.3': + '@playwright/test@1.46.0': dependencies: - playwright: 1.45.3 + playwright: 1.46.0 '@polka/url@1.0.0-next.25': {} @@ -6126,41 +6136,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': + '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1) '@rollup/plugin-json': 6.1.0(rollup@4.18.1) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1) - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) rollup: 4.18.1 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': + '@sveltejs/enhanced-img@0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': dependencies: magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) vite-imagetools: 7.0.2(rollup@4.18.1) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': + '@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6174,28 +6184,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) - vitefu: 0.2.5(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vitefu: 0.2.5(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6225,13 +6235,13 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.14.13': + '@types/node@20.14.14': dependencies: undici-types: 5.26.5 '@types/pg@8.11.6': dependencies: - '@types/node': 20.14.13 + '@types/node': 20.14.14 pg-protocol: 1.6.1 pg-types: 4.0.2 @@ -6242,14 +6252,14 @@ snapshots: '@types/validator@13.12.0': optional: true - '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.17.0(eslint@8.57.0)(typescript@5.5.4) - '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/type-utils': 7.17.0(eslint@8.57.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 7.17.0 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -6260,12 +6270,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 7.17.0 + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.4 eslint: 8.57.0 optionalDependencies: @@ -6273,16 +6283,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.17.0': + '@typescript-eslint/scope-manager@7.18.0': dependencies: - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/visitor-keys': 7.17.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.17.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) - '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.4) - debug: 4.3.5 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + debug: 4.3.6 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: @@ -6290,12 +6300,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.17.0': {} + '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.17.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/visitor-keys': 7.17.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -6307,20 +6317,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.17.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@7.17.0': + '@typescript-eslint/visitor-keys@7.18.0': dependencies: - '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 '@ungap/structured-clone@1.2.0': {} @@ -6420,7 +6430,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -6485,14 +6495,14 @@ snapshots: async-sema@3.1.1: {} - autoprefixer@10.4.19(postcss@8.4.40): + autoprefixer@10.4.20(postcss@8.4.41): dependencies: - browserslist: 4.23.0 - caniuse-lite: 1.0.30001616 + browserslist: 4.23.3 + caniuse-lite: 1.0.30001649 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.40 + picocolors: 1.0.1 + postcss: 8.4.41 postcss-value-parser: 4.2.0 axobject-query@4.0.0: @@ -6509,7 +6519,7 @@ snapshots: dependencies: file-uri-to-path: 1.0.0 - bits-ui@0.21.12(svelte@5.0.0-next.175): + bits-ui@0.21.13(svelte@5.0.0-next.175): dependencies: '@internationalized/date': 3.5.5 '@melt-ui/svelte': 0.76.2(svelte@5.0.0-next.175) @@ -6558,13 +6568,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.0: - dependencies: - caniuse-lite: 1.0.30001616 - electron-to-chromium: 1.4.758 - node-releases: 2.0.14 - update-browserslist-db: 1.0.15(browserslist@4.23.0) - browserslist@4.23.1: dependencies: caniuse-lite: 1.0.30001640 @@ -6572,13 +6575,20 @@ snapshots: node-releases: 2.0.14 update-browserslist-db: 1.1.0(browserslist@4.23.1) + browserslist@4.23.3: + dependencies: + caniuse-lite: 1.0.30001649 + electron-to-chromium: 1.5.4 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.23.3) + buffer-crc32@0.2.13: {} buffer-from@1.1.2: {} builtin-modules@3.3.0: {} - bullmq@5.11.0: + bullmq@5.12.0: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -6613,10 +6623,10 @@ snapshots: camelize@1.0.1: {} - caniuse-lite@1.0.30001616: {} - caniuse-lite@1.0.30001640: {} + caniuse-lite@1.0.30001649: {} + chai@4.4.1: dependencies: assertion-error: 1.1.0 @@ -6730,25 +6740,25 @@ snapshots: css-background-parser@0.1.0: {} - css-blank-pseudo@6.0.2(postcss@8.4.40): + css-blank-pseudo@6.0.2(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 css-box-shadow@1.0.0-3: {} css-color-keywords@1.0.0: {} - css-has-pseudo@6.0.5(postcss@8.4.40): + css-has-pseudo@6.0.5(postcss@8.4.41): dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.1.0 postcss-value-parser: 4.2.0 - css-prefers-color-scheme@9.0.1(postcss@8.4.40): + css-prefers-color-scheme@9.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 css-to-react-native@3.2.0: dependencies: @@ -6780,6 +6790,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + decamelize@1.2.0: {} deep-eql@4.1.3: @@ -6836,15 +6850,16 @@ snapshots: dotenv@16.4.5: {} - drizzle-kit@0.23.0: + drizzle-kit@0.23.2: dependencies: + '@drizzle-team/brocli': 0.8.2 '@esbuild-kit/esm-loader': 2.6.5 esbuild: 0.19.12 esbuild-register: 3.5.0(esbuild@0.19.12) transitivePeerDependencies: - supports-color - drizzle-orm@0.32.1(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4): optionalDependencies: '@neondatabase/serverless': 0.9.4 '@types/pg': 8.11.6 @@ -6855,18 +6870,16 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.4.758: {} - electron-to-chromium@1.4.818: {} + electron-to-chromium@1.5.4: {} + emoji-regex@10.3.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - encode-utf8@1.0.3: {} - encodeurl@1.0.2: {} es-define-property@1.0.0: @@ -7011,7 +7024,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): + eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -7019,9 +7032,9 @@ snapshots: eslint-compat-utils: 0.5.1(eslint@8.57.0) esutils: 2.0.3 known-css-properties: 0.34.0 - postcss: 8.4.40 - postcss-load-config: 3.1.4(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) - postcss-safe-parser: 6.0.0(postcss@8.4.40) + postcss: 8.4.41 + postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + postcss-safe-parser: 6.0.0(postcss@8.4.41) postcss-selector-parser: 6.1.0 semver: 7.6.2 svelte-eslint-parser: 0.41.0(svelte@5.0.0-next.175) @@ -7247,11 +7260,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7383,11 +7396,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.2): + hono-rate-limiter@0.4.0(hono@4.5.3): dependencies: - hono: 4.5.2 + hono: 4.5.3 - hono@4.5.2: {} + hono@4.5.3: {} html-entities@2.5.2: {} @@ -7402,7 +7415,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -7778,6 +7791,8 @@ snapshots: node-releases@2.0.14: {} + node-releases@2.0.18: {} + nodemailer@6.9.14: {} nopt@5.0.0: @@ -7970,281 +7985,281 @@ snapshots: mlly: 1.7.0 pathe: 1.1.2 - playwright-core@1.45.3: {} + playwright-core@1.46.0: {} - playwright@1.45.3: + playwright@1.46.0: dependencies: - playwright-core: 1.45.3 + playwright-core: 1.46.0 optionalDependencies: fsevents: 2.3.2 pngjs@5.0.0: {} - postcss-attribute-case-insensitive@6.0.3(postcss@8.4.40): + postcss-attribute-case-insensitive@6.0.3(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - postcss-clamp@4.1.0(postcss@8.4.40): + postcss-clamp@4.1.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@6.0.14(postcss@8.4.40): + postcss-color-functional-notation@6.0.14(postcss@8.4.41): dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - postcss-color-hex-alpha@9.0.4(postcss@8.4.40): + postcss-color-hex-alpha@9.0.4(postcss@8.4.41): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@9.0.3(postcss@8.4.40): + postcss-color-rebeccapurple@9.0.3(postcss@8.4.41): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-custom-media@10.0.8(postcss@8.4.40): + postcss-custom-media@10.0.8(postcss@8.4.41): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.40 + postcss: 8.4.41 - postcss-custom-properties@13.3.12(postcss@8.4.40): + postcss-custom-properties@13.3.12(postcss@8.4.41): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-custom-selectors@7.1.12(postcss@8.4.40): + postcss-custom-selectors@7.1.12(postcss@8.4.41): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.1.0 - postcss-dir-pseudo-class@8.0.1(postcss@8.4.40): + postcss-dir-pseudo-class@8.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - postcss-double-position-gradients@5.0.7(postcss@8.4.40): + postcss-double-position-gradients@5.0.7(postcss@8.4.41): dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-focus-visible@9.0.1(postcss@8.4.40): + postcss-focus-visible@9.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - postcss-focus-within@8.0.1(postcss@8.4.40): + postcss-focus-within@8.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - postcss-font-variant@5.0.0(postcss@8.4.40): + postcss-font-variant@5.0.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-gap-properties@5.0.1(postcss@8.4.40): + postcss-gap-properties@5.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-image-set-function@6.0.3(postcss@8.4.40): + postcss-image-set-function@6.0.3(postcss@8.4.41): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.4.40): + postcss-import@15.1.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-import@16.1.0(postcss@8.4.40): + postcss-import@16.1.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.40): + postcss-js@4.0.1(postcss@8.4.41): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.40 + postcss: 8.4.41 - postcss-lab-function@6.0.19(postcss@8.4.40): + postcss-lab-function@6.0.19(postcss@8.4.41): dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/utilities': 1.0.0(postcss@8.4.40) - postcss: 8.4.40 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/utilities': 1.0.0(postcss@8.4.41) + postcss: 8.4.41 - postcss-load-config@3.1.4(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): + postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.40 - ts-node: 10.9.2(@types/node@20.14.13)(typescript@5.5.4) + postcss: 8.4.41 + ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) - postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: - postcss: 8.4.40 - ts-node: 10.9.2(@types/node@20.14.13)(typescript@5.5.4) + postcss: 8.4.41 + ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: jiti: 1.21.6 - postcss: 8.4.40 - tsx: 4.16.2 + postcss: 8.4.41 + tsx: 4.16.5 - postcss-logical@7.0.1(postcss@8.4.40): + postcss-logical@7.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-nested@6.0.1(postcss@8.4.40): + postcss-nested@6.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.1.0 - postcss-nesting@12.1.5(postcss@8.4.40): + postcss-nesting@12.1.5(postcss@8.4.41): dependencies: '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.0) '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.1.0 - postcss-opacity-percentage@2.0.0(postcss@8.4.40): + postcss-opacity-percentage@2.0.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-overflow-shorthand@5.0.1(postcss@8.4.40): + postcss-overflow-shorthand@5.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.4.40): + postcss-page-break@3.0.4(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-place@9.0.1(postcss@8.4.40): + postcss-place@9.0.1(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-value-parser: 4.2.0 - postcss-preset-env@9.6.0(postcss@8.4.40): + postcss-preset-env@9.6.0(postcss@8.4.41): dependencies: - '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.40) - '@csstools/postcss-color-function': 3.0.19(postcss@8.4.40) - '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.40) - '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.40) - '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.40) - '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.40) - '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.40) - '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.40) - '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.40) - '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.40) - '@csstools/postcss-initial': 1.0.1(postcss@8.4.40) - '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.40) - '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.40) - '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.40) - '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.40) - '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.40) - '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.40) - '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.40) - '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.40) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.40) - '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.40) - '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.40) - '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.40) - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.40) - '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.40) - '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.40) - '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.40) - '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.40) - '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.40) - '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.40) - autoprefixer: 10.4.19(postcss@8.4.40) + '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.41) + '@csstools/postcss-color-function': 3.0.19(postcss@8.4.41) + '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.41) + '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.41) + '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.41) + '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.41) + '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.41) + '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.41) + '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.41) + '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.41) + '@csstools/postcss-initial': 1.0.1(postcss@8.4.41) + '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.41) + '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.41) + '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.41) + '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.41) + '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.41) + '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.41) + '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.41) + '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.41) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.41) + '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.41) + '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.41) + '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.41) + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) + '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.41) + '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.41) + '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.41) + '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.41) + '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.41) + '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.41) + autoprefixer: 10.4.20(postcss@8.4.41) browserslist: 4.23.1 - css-blank-pseudo: 6.0.2(postcss@8.4.40) - css-has-pseudo: 6.0.5(postcss@8.4.40) - css-prefers-color-scheme: 9.0.1(postcss@8.4.40) + css-blank-pseudo: 6.0.2(postcss@8.4.41) + css-has-pseudo: 6.0.5(postcss@8.4.41) + css-prefers-color-scheme: 9.0.1(postcss@8.4.41) cssdb: 8.1.0 - postcss: 8.4.40 - postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.40) - postcss-clamp: 4.1.0(postcss@8.4.40) - postcss-color-functional-notation: 6.0.14(postcss@8.4.40) - postcss-color-hex-alpha: 9.0.4(postcss@8.4.40) - postcss-color-rebeccapurple: 9.0.3(postcss@8.4.40) - postcss-custom-media: 10.0.8(postcss@8.4.40) - postcss-custom-properties: 13.3.12(postcss@8.4.40) - postcss-custom-selectors: 7.1.12(postcss@8.4.40) - postcss-dir-pseudo-class: 8.0.1(postcss@8.4.40) - postcss-double-position-gradients: 5.0.7(postcss@8.4.40) - postcss-focus-visible: 9.0.1(postcss@8.4.40) - postcss-focus-within: 8.0.1(postcss@8.4.40) - postcss-font-variant: 5.0.0(postcss@8.4.40) - postcss-gap-properties: 5.0.1(postcss@8.4.40) - postcss-image-set-function: 6.0.3(postcss@8.4.40) - postcss-lab-function: 6.0.19(postcss@8.4.40) - postcss-logical: 7.0.1(postcss@8.4.40) - postcss-nesting: 12.1.5(postcss@8.4.40) - postcss-opacity-percentage: 2.0.0(postcss@8.4.40) - postcss-overflow-shorthand: 5.0.1(postcss@8.4.40) - postcss-page-break: 3.0.4(postcss@8.4.40) - postcss-place: 9.0.1(postcss@8.4.40) - postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.40) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.40) - postcss-selector-not: 7.0.2(postcss@8.4.40) + postcss: 8.4.41 + postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.41) + postcss-clamp: 4.1.0(postcss@8.4.41) + postcss-color-functional-notation: 6.0.14(postcss@8.4.41) + postcss-color-hex-alpha: 9.0.4(postcss@8.4.41) + postcss-color-rebeccapurple: 9.0.3(postcss@8.4.41) + postcss-custom-media: 10.0.8(postcss@8.4.41) + postcss-custom-properties: 13.3.12(postcss@8.4.41) + postcss-custom-selectors: 7.1.12(postcss@8.4.41) + postcss-dir-pseudo-class: 8.0.1(postcss@8.4.41) + postcss-double-position-gradients: 5.0.7(postcss@8.4.41) + postcss-focus-visible: 9.0.1(postcss@8.4.41) + postcss-focus-within: 8.0.1(postcss@8.4.41) + postcss-font-variant: 5.0.0(postcss@8.4.41) + postcss-gap-properties: 5.0.1(postcss@8.4.41) + postcss-image-set-function: 6.0.3(postcss@8.4.41) + postcss-lab-function: 6.0.19(postcss@8.4.41) + postcss-logical: 7.0.1(postcss@8.4.41) + postcss-nesting: 12.1.5(postcss@8.4.41) + postcss-opacity-percentage: 2.0.0(postcss@8.4.41) + postcss-overflow-shorthand: 5.0.1(postcss@8.4.41) + postcss-page-break: 3.0.4(postcss@8.4.41) + postcss-place: 9.0.1(postcss@8.4.41) + postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.41) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.41) + postcss-selector-not: 7.0.2(postcss@8.4.41) - postcss-pseudo-class-any-link@9.0.2(postcss@8.4.40): + postcss-pseudo-class-any-link@9.0.2(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 - postcss-replace-overflow-wrap@4.0.0(postcss@8.4.40): + postcss-replace-overflow-wrap@4.0.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-safe-parser@6.0.0(postcss@8.4.40): + postcss-safe-parser@6.0.0(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-scss@4.0.9(postcss@8.4.40): + postcss-scss@4.0.9(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 - postcss-selector-not@7.0.2(postcss@8.4.40): + postcss-selector-not@7.0.2(postcss@8.4.41): dependencies: - postcss: 8.4.40 + postcss: 8.4.41 postcss-selector-parser: 6.0.16 postcss-selector-parser@6.0.16: @@ -8259,7 +8274,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.40: + postcss@8.4.41: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 @@ -8314,10 +8329,9 @@ snapshots: punycode@2.3.1: {} - qrcode@1.5.3: + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 - encode-utf8: 1.0.3 pngjs: 5.0.0 yargs: 15.4.1 @@ -8694,14 +8708,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175): + svelte-check@3.8.5(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.0.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - '@babel/core' @@ -8719,8 +8733,8 @@ snapshots: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.40 - postcss-scss: 4.0.9(postcss@8.4.40) + postcss: 8.4.41 + postcss-scss: 4.0.9(postcss@8.4.41) optionalDependencies: svelte: 5.0.0-next.175 @@ -8757,7 +8771,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8766,17 +8780,17 @@ snapshots: strip-indent: 3.0.0 svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.40 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2) + postcss: 8.4.41 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5) sass: 1.77.8 typescript: 5.5.4 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2))(postcss@8.4.40)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.40 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.40)(tsx@4.16.2) + postcss: 8.4.41 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5) sass: 1.77.8 typescript: 5.5.4 @@ -8831,19 +8845,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) - sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) + '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8868,16 +8882,16 @@ snapshots: tailwind-merge@2.4.0: {} - tailwind-variants@0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))): dependencies: tailwind-merge: 2.4.0 - tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) + tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) + tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) - tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): + tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8893,11 +8907,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 - postcss: 8.4.40 - postcss-import: 15.1.0(postcss@8.4.40) - postcss-js: 4.0.1(postcss@8.4.40) - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) - postcss-nested: 6.0.1(postcss@8.4.40) + postcss: 8.4.41 + postcss-import: 15.1.0(postcss@8.4.41) + postcss-js: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + postcss-nested: 6.0.1(postcss@8.4.41) postcss-selector-parser: 6.1.0 resolve: 1.22.8 sucrase: 3.35.0 @@ -8963,14 +8977,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.13 + '@types/node': 20.14.14 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -8988,7 +9002,7 @@ snapshots: tslib@2.6.3: {} - tsx@4.16.2: + tsx@4.16.5: dependencies: esbuild: 0.21.5 get-tsconfig: 4.7.5 @@ -9035,18 +9049,18 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.0.15(browserslist@4.23.0): - dependencies: - browserslist: 4.23.0 - escalade: 3.1.2 - picocolors: 1.0.0 - update-browserslist-db@1.1.0(browserslist@4.23.1): dependencies: browserslist: 4.23.1 escalade: 3.1.2 picocolors: 1.0.1 + update-browserslist-db@1.1.0(browserslist@4.23.3): + dependencies: + browserslist: 4.23.3 + escalade: 3.1.2 + picocolors: 1.0.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -9077,13 +9091,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.14.13)(sass@1.77.8): + vite-node@1.6.0(@types/node@20.14.14)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -9094,21 +9108,21 @@ snapshots: - supports-color - terser - vite@5.3.5(@types/node@20.14.13)(sass@1.77.8): + vite@5.3.5(@types/node@20.14.14)(sass@1.77.8): dependencies: esbuild: 0.21.5 - postcss: 8.4.40 + postcss: 8.4.41 rollup: 4.17.2 optionalDependencies: - '@types/node': 20.14.13 + '@types/node': 20.14.14 fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)): + vitefu@0.2.5(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)): optionalDependencies: - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) - vitest@1.6.0(@types/node@20.14.13)(sass@1.77.8): + vitest@1.6.0(@types/node@20.14.14)(sass@1.77.8): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9127,11 +9141,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.3.5(@types/node@20.14.13)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.14.13)(sass@1.77.8) + vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.14.14)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.14.13 + '@types/node': 20.14.14 transitivePeerDependencies: - less - lightningcss From 488b638d162df973fe04ee152e960562e8deace6 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Wed, 7 Aug 2024 10:01:38 -0700 Subject: [PATCH 16/34] Refactor files to export all and no default export. --- .../api/infrastructure/database/seed.ts | 2 +- .../database/tables/categories.table.ts | 23 +++++++ .../database/tables/categories.ts | 25 -------- ...Ids.ts => categoriesToExternalIdsTable.ts} | 18 +++--- .../database/tables/categoriesToGames.ts | 17 +++-- .../database/tables/collectionItems.ts | 6 +- .../database/tables/collections.ts | 5 +- .../database/tables/expansions.ts | 2 - .../database/tables/externalIds.ts | 4 +- .../tables/federatedIdentity.table.ts | 5 +- .../infrastructure/database/tables/games.ts | 18 +++--- .../database/tables/gamesToExternalIds.ts | 8 +-- .../infrastructure/database/tables/index.ts | 62 ++++++++----------- .../database/tables/mechanics.ts | 4 +- .../database/tables/mechanicsToExternalIds.ts | 8 +-- .../database/tables/mechanicsToGames.ts | 6 +- .../database/tables/passwordResetTokens.ts | 4 +- .../database/tables/publishers.ts | 6 +- .../tables/publishersToExternalIds.ts | 8 +-- .../database/tables/publishersToGames.ts | 8 +-- .../database/tables/recoveryCodes.ts | 4 +- .../infrastructure/database/tables/roles.ts | 6 +- .../database/tables/sessions.table.ts | 4 +- .../database/tables/two-factor.table.ts | 4 +- .../database/tables/userRoles.ts | 6 +- .../database/tables/users.table.ts | 2 +- .../database/tables/wishlistItems.ts | 6 +- .../database/tables/wishlists.ts | 4 +- 28 files changed, 113 insertions(+), 162 deletions(-) create mode 100644 src/lib/server/api/infrastructure/database/tables/categories.table.ts delete mode 100644 src/lib/server/api/infrastructure/database/tables/categories.ts rename src/lib/server/api/infrastructure/database/tables/{categoriesToExternalIds.ts => categoriesToExternalIdsTable.ts} (62%) diff --git a/src/lib/server/api/infrastructure/database/seed.ts b/src/lib/server/api/infrastructure/database/seed.ts index 016f5b9..7a7495e 100644 --- a/src/lib/server/api/infrastructure/database/seed.ts +++ b/src/lib/server/api/infrastructure/database/seed.ts @@ -14,7 +14,7 @@ async function resetTable(db: db, table: Table) { for (const table of [ schema.categories, - schema.categoriesToExternalIds, + schema.categoriesToExternalIdsTable, schema.categories_to_games, schema.collection_items, schema.collections, diff --git a/src/lib/server/api/infrastructure/database/tables/categories.table.ts b/src/lib/server/api/infrastructure/database/tables/categories.table.ts new file mode 100644 index 0000000..b22cfc7 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/tables/categories.table.ts @@ -0,0 +1,23 @@ +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import {categoriesToExternalIdsTable} from './categoriesToExternalIdsTable'; +import { categories_to_games_table } from './categoriesToGames'; +import { timestamps } from '../utils'; + +export const categoriesTable = pgTable('categories', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name'), + slug: text('slug'), + ...timestamps, +}); + +export type Categories = InferSelectModel; + +export const categories_relations = relations(categoriesTable, ({ many }) => ({ + categories_to_games: many(categories_to_games_table), + categoriesToExternalIds: many(categoriesToExternalIdsTable), +})); diff --git a/src/lib/server/api/infrastructure/database/tables/categories.ts b/src/lib/server/api/infrastructure/database/tables/categories.ts deleted file mode 100644 index 5936858..0000000 --- a/src/lib/server/api/infrastructure/database/tables/categories.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import categoriesToExternalIds from './categoriesToExternalIds'; -import categories_to_games from './categoriesToGames'; -import { timestamps } from '../utils'; - -const categories = pgTable('categories', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name'), - slug: text('slug'), - ...timestamps, -}); - -export type Categories = InferSelectModel; - -export const categories_relations = relations(categories, ({ many }) => ({ - categories_to_games: many(categories_to_games), - categoriesToExternalIds: many(categoriesToExternalIds), -})); - -export default categories; diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts similarity index 62% rename from src/lib/server/api/infrastructure/database/tables/categoriesToExternalIds.ts rename to src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts index 352b732..b06a267 100644 --- a/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIds.ts +++ b/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts @@ -1,14 +1,14 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import categories from './categories'; +import { categoriesTable } from './categories.table'; import externalIds from './externalIds'; import { relations } from 'drizzle-orm'; -const categoriesToExternalIds = pgTable( +export const categoriesToExternalIdsTable = pgTable( 'categories_to_external_ids', { categoryId: uuid('category_id') .notNull() - .references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + .references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }), externalId: uuid('external_id') .notNull() .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), @@ -23,17 +23,15 @@ const categoriesToExternalIds = pgTable( ); export const categoriesToExternalIdsRelations = relations( - categoriesToExternalIds, + categoriesToExternalIdsTable, ({ one }) => ({ - category: one(categories, { - fields: [categoriesToExternalIds.categoryId], - references: [categories.id], + category: one(categoriesTable, { + fields: [categoriesToExternalIdsTable.categoryId], + references: [categoriesTable.id], }), externalId: one(externalIds, { - fields: [categoriesToExternalIds.externalId], + fields: [categoriesToExternalIdsTable.externalId], references: [externalIds.id], }), }), ); - -export default categoriesToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts b/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts index 7afb8e2..4343a7c 100644 --- a/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts +++ b/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts @@ -1,14 +1,14 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; -import categories from './categories'; +import { categoriesTable } from './categories.table'; import games from './games'; -const categories_to_games = pgTable( +export const categories_to_games_table = pgTable( 'categories_to_games', { category_id: uuid('category_id') .notNull() - .references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }), + .references(() => categoriesTable.id, { onDelete: 'restrict', onUpdate: 'cascade' }), game_id: uuid('game_id') .notNull() .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), @@ -22,15 +22,14 @@ const categories_to_games = pgTable( }, ); -export const categories_to_games_relations = relations(categories_to_games, ({ one }) => ({ - category: one(categories, { - fields: [categories_to_games.category_id], - references: [categories.id], +export const categories_to_games_relations = relations(categories_to_games_table, ({ one }) => ({ + category: one(categoriesTable, { + fields: [categories_to_games_table.category_id], + references: [categoriesTable.id], }), game: one(games, { - fields: [categories_to_games.game_id], + fields: [categories_to_games_table.game_id], references: [games.id], }), })); -export default categories_to_games; diff --git a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts b/src/lib/server/api/infrastructure/database/tables/collectionItems.ts index cf1805c..131ac2f 100644 --- a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts +++ b/src/lib/server/api/infrastructure/database/tables/collectionItems.ts @@ -1,11 +1,11 @@ import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import collections from './collections'; +import { collections } from './collections'; import games from './games'; import { timestamps } from '../utils'; -const collection_items = pgTable('collection_items', { +export const collection_items = pgTable('collection_items', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -32,5 +32,3 @@ export const collection_item_relations = relations(collection_items, ({ one }) = references: [games.id], }), })); - -export default collection_items; diff --git a/src/lib/server/api/infrastructure/database/tables/collections.ts b/src/lib/server/api/infrastructure/database/tables/collections.ts index 58b8297..9f881ba 100644 --- a/src/lib/server/api/infrastructure/database/tables/collections.ts +++ b/src/lib/server/api/infrastructure/database/tables/collections.ts @@ -1,10 +1,10 @@ -import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; +import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import { usersTable } from './users.table'; import { timestamps } from '../utils'; -const collections = pgTable('collections', { +export const collections = pgTable('collections', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -25,4 +25,3 @@ export const collection_relations = relations(collections, ({ one }) => ({ export type Collections = InferSelectModel; -export default collections; diff --git a/src/lib/server/api/infrastructure/database/tables/expansions.ts b/src/lib/server/api/infrastructure/database/tables/expansions.ts index ff56e4d..200bfff 100644 --- a/src/lib/server/api/infrastructure/database/tables/expansions.ts +++ b/src/lib/server/api/infrastructure/database/tables/expansions.ts @@ -30,5 +30,3 @@ export const expansion_relations = relations(expansions, ({ one }) => ({ references: [games.id], }), })); - -export default expansions; diff --git a/src/lib/server/api/infrastructure/database/tables/externalIds.ts b/src/lib/server/api/infrastructure/database/tables/externalIds.ts index 5d0a481..80c1033 100644 --- a/src/lib/server/api/infrastructure/database/tables/externalIds.ts +++ b/src/lib/server/api/infrastructure/database/tables/externalIds.ts @@ -11,7 +11,7 @@ export const externalIdType = pgEnum('external_id_type', [ 'artist', ]); -const externalIds = pgTable('external_ids', { +export const externalIds = pgTable('external_ids', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -21,5 +21,3 @@ const externalIds = pgTable('external_ids', { }); export type ExternalIds = InferSelectModel; - -export default externalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts index c47f817..63d9313 100644 --- a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts @@ -1,4 +1,5 @@ import { pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { type InferSelectModel } from 'drizzle-orm'; import { usersTable } from "./users.table"; import { timestamps } from '../utils'; @@ -11,4 +12,6 @@ export const federatedIdentityTable = pgTable('federated_identity', { federated_user_id: text('federated_user_id').notNull(), federated_username: text('federated_username').notNull(), ...timestamps -}); \ No newline at end of file +}); + +export type FederatedIdentity = InferSelectModel; diff --git a/src/lib/server/api/infrastructure/database/tables/games.ts b/src/lib/server/api/infrastructure/database/tables/games.ts index 91d5cfa..99ec01a 100644 --- a/src/lib/server/api/infrastructure/database/tables/games.ts +++ b/src/lib/server/api/infrastructure/database/tables/games.ts @@ -1,13 +1,13 @@ import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations, sql } from 'drizzle-orm'; -import categoriesToGames from './categoriesToGames'; -import gamesToExternalIds from './gamesToExternalIds'; -import mechanicsToGames from './mechanicsToGames'; -import publishersToGames from './publishersToGames'; +import {categories_to_games_table} from './categoriesToGames'; +import {gamesToExternalIds} from './gamesToExternalIds'; +import {mechanics_to_games} from './mechanicsToGames'; +import {publishers_to_games} from './publishersToGames'; import { timestamps } from '../utils'; -const games = pgTable( +export const games = pgTable( 'games', { id: uuid('id').primaryKey().defaultRandom(), @@ -42,12 +42,10 @@ const games = pgTable( ); export const gameRelations = relations(games, ({ many }) => ({ - categories_to_games: many(categoriesToGames), - mechanics_to_games: many(mechanicsToGames), - publishers_to_games: many(publishersToGames), + categories_to_games: many(categories_to_games_table), + mechanics_to_games: many(mechanics_to_games), + publishers_to_games: many(publishers_to_games), gamesToExternalIds: many(gamesToExternalIds), })); export type Games = InferSelectModel; - -export default games; diff --git a/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts index 66edec9..b625924 100644 --- a/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts +++ b/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts @@ -1,8 +1,8 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import games from './games'; -import externalIds from './externalIds'; +import {games} from './games'; +import {externalIds} from './externalIds'; -const gamesToExternalIds = pgTable( +export const gamesToExternalIds = pgTable( 'games_to_external_ids', { gameId: uuid('game_id') @@ -20,5 +20,3 @@ const gamesToExternalIds = pgTable( }; }, ); - -export default gamesToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/index.ts b/src/lib/server/api/infrastructure/database/tables/index.ts index 9b59c1e..e706804 100644 --- a/src/lib/server/api/infrastructure/database/tables/index.ts +++ b/src/lib/server/api/infrastructure/database/tables/index.ts @@ -1,36 +1,26 @@ -export { usersTable, userRelations as user_relations, type Users } from './users.table'; -export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes'; -export { - default as password_reset_tokens, - password_reset_token_relations, - type PasswordResetTokens, -} from './passwordResetTokens'; -export { default as sessionsTable, type Sessions } from './sessions.table'; -export { default as roles, role_relations, type Roles } from './roles'; -export { default as userRoles, user_role_relations, type UserRoles } from './userRoles'; -export { default as collections, collection_relations, type Collections } from './collections'; -export { - default as collection_items, - collection_item_relations, - type CollectionItems, -} from './collectionItems'; -export { default as wishlists, wishlists_relations, type Wishlists } from './wishlists'; -export { - default as wishlist_items, - wishlist_item_relations, - type WishlistItems, -} from './wishlistItems'; -export { default as externalIds, type ExternalIds, externalIdType } from './externalIds'; -export { default as games, gameRelations, type Games } from './games'; -export { default as gamesToExternalIds } from './gamesToExternalIds'; -export { default as expansions, expansion_relations, type Expansions } from './expansions'; -export { default as publishers, publishers_relations, type Publishers } from './publishers'; -export { default as publishers_to_games, publishers_to_games_relations } from './publishersToGames'; -export { default as publishersToExternalIds } from './publishersToExternalIds'; -export { default as categories, categories_relations, type Categories } from './categories'; -export { default as categoriesToExternalIds } from './categoriesToExternalIds'; -export { default as categories_to_games, categories_to_games_relations } from './categoriesToGames'; -export { default as mechanics, mechanics_relations, type Mechanics } from './mechanics'; -export { default as mechanicsToExternalIds } from './mechanicsToExternalIds'; -export { default as mechanics_to_games, mechanics_to_games_relations } from './mechanicsToGames'; -export { default as twoFactor } from './two-factor.table'; +export * from './categories.table'; +export * from './categoriesToExternalIdsTable'; +export * from './categoriesToGames'; +export * from './collectionItems'; +export * from './collections'; +export * from './credentials.table'; +export * from './expansions'; +export * from './externalIds'; +export * from './federatedIdentity.table'; +export * from './games'; +export * from './gamesToExternalIds'; +export * from './mechanics'; +export * from './mechanicsToExternalIds'; +export * from './mechanicsToGames' +export * from './passwordResetTokens'; +export * from './publishers'; +export * from './publishersToExternalIds'; +export * from './publishersToGames'; +export * from './recoveryCodes'; +export * from './roles'; +export * from './sessions.table'; +export * from './two-factor.table'; +export * from './userRoles'; +export * from './users.table'; +export * from './wishlistItems'; +export * from './wishlists'; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/tables/mechanics.ts b/src/lib/server/api/infrastructure/database/tables/mechanics.ts index 3a80fb0..72f5902 100644 --- a/src/lib/server/api/infrastructure/database/tables/mechanics.ts +++ b/src/lib/server/api/infrastructure/database/tables/mechanics.ts @@ -5,7 +5,7 @@ import mechanicsToGames from './mechanicsToGames'; import mechanicsToExternalIds from './mechanicsToExternalIds'; import { timestamps } from '../utils'; -const mechanics = pgTable('mechanics', { +export const mechanics = pgTable('mechanics', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -21,5 +21,3 @@ export const mechanics_relations = relations(mechanics, ({ many }) => ({ mechanics_to_games: many(mechanicsToGames), mechanicsToExternalIds: many(mechanicsToExternalIds), })); - -export default mechanics; diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts index 8fab637..b755eab 100644 --- a/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts +++ b/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts @@ -1,8 +1,8 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import mechanics from './mechanics'; -import externalIds from './externalIds'; +import {mechanics} from './mechanics'; +import {externalIds} from './externalIds'; -const mechanicsToExternalIds = pgTable( +export const mechanicsToExternalIds = pgTable( 'mechanics_to_external_ids', { mechanicId: uuid('mechanic_id') @@ -20,5 +20,3 @@ const mechanicsToExternalIds = pgTable( }; }, ); - -export default mechanicsToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts b/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts index aa549c4..6faf721 100644 --- a/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts +++ b/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts @@ -1,9 +1,9 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; -import mechanics from './mechanics'; -import games from './games'; +import {mechanics} from './mechanics'; +import {games} from './games'; -const mechanics_to_games = pgTable( +export const mechanics_to_games = pgTable( 'mechanics_to_games', { mechanic_id: uuid('mechanic_id') diff --git a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts b/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts index 143ee28..3fa94bf 100644 --- a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts +++ b/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts @@ -4,7 +4,7 @@ import { type InferSelectModel, relations } from 'drizzle-orm'; import { usersTable } from './users.table'; import { timestamps } from '../utils'; -const password_reset_tokens = pgTable('password_reset_tokens', { +export const password_reset_tokens = pgTable('password_reset_tokens', { id: text('id') .primaryKey() .$defaultFn(() => cuid2()), @@ -23,5 +23,3 @@ export const password_reset_token_relations = relations(password_reset_tokens, ( references: [usersTable.id], }), })); - -export default password_reset_tokens; diff --git a/src/lib/server/api/infrastructure/database/tables/publishers.ts b/src/lib/server/api/infrastructure/database/tables/publishers.ts index b83a3ef..65a0c2d 100644 --- a/src/lib/server/api/infrastructure/database/tables/publishers.ts +++ b/src/lib/server/api/infrastructure/database/tables/publishers.ts @@ -1,11 +1,11 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import publishers_to_games from './publishersToGames'; +import {publishers_to_games} from './publishersToGames'; import publishersToExternalIds from './publishersToExternalIds'; import { timestamps } from '../utils'; -const publishers = pgTable('publishers', { +export const publishers = pgTable('publishers', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -21,5 +21,3 @@ export const publishers_relations = relations(publishers, ({ many }) => ({ publishers_to_games: many(publishers_to_games), publishersToExternalIds: many(publishersToExternalIds), })); - -export default publishers; diff --git a/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts b/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts index 898e6da..1f630d8 100644 --- a/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts +++ b/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts @@ -1,8 +1,8 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import publishers from './publishers'; -import externalIds from './externalIds'; +import {publishers} from './publishers'; +import {externalIds} from './externalIds'; -const publishersToExternalIds = pgTable( +export const publishersToExternalIds = pgTable( 'publishers_to_external_ids', { publisherId: uuid('publisher_id') @@ -20,5 +20,3 @@ const publishersToExternalIds = pgTable( }; }, ); - -export default publishersToExternalIds; diff --git a/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts b/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts index 86de77d..f2c2d24 100644 --- a/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts +++ b/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts @@ -1,9 +1,9 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; -import publishers from './publishers'; -import games from './games'; +import {publishers} from './publishers'; +import {games} from './games'; -const publishers_to_games = pgTable( +export const publishers_to_games = pgTable( 'publishers_to_games', { publisher_id: uuid('publisher_id') @@ -32,5 +32,3 @@ export const publishers_to_games_relations = relations(publishers_to_games, ({ o references: [games.id], }), })); - -export default publishers_to_games; diff --git a/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts b/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts index 9a7ee8e..936ae79 100644 --- a/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts +++ b/src/lib/server/api/infrastructure/database/tables/recoveryCodes.ts @@ -3,7 +3,7 @@ import type { InferSelectModel } from 'drizzle-orm'; import { usersTable } from './users.table'; import { timestamps } from '../utils'; -const recovery_codes = pgTable('recovery_codes', { +export const recovery_codes = pgTable('recovery_codes', { id: uuid('id').primaryKey().defaultRandom(), userId: uuid('user_id') .notNull() @@ -14,5 +14,3 @@ const recovery_codes = pgTable('recovery_codes', { }); export type RecoveryCodes = InferSelectModel; - -export default recovery_codes; diff --git a/src/lib/server/api/infrastructure/database/tables/roles.ts b/src/lib/server/api/infrastructure/database/tables/roles.ts index 43768b0..4e701ac 100644 --- a/src/lib/server/api/infrastructure/database/tables/roles.ts +++ b/src/lib/server/api/infrastructure/database/tables/roles.ts @@ -1,10 +1,10 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import user_roles from './userRoles'; +import {user_roles} from './userRoles'; import { timestamps } from '../utils'; -const roles = pgTable('roles', { +export const roles = pgTable('roles', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -19,5 +19,3 @@ export type Roles = InferSelectModel; export const role_relations = relations(roles, ({ many }) => ({ user_roles: many(user_roles), })); - -export default roles; diff --git a/src/lib/server/api/infrastructure/database/tables/sessions.table.ts b/src/lib/server/api/infrastructure/database/tables/sessions.table.ts index 823eb19..2985393 100644 --- a/src/lib/server/api/infrastructure/database/tables/sessions.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/sessions.table.ts @@ -2,7 +2,7 @@ import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { relations, type InferSelectModel } from 'drizzle-orm'; import { usersTable } from './users.table'; -const sessionsTable = pgTable('sessions', { +export const sessionsTable = pgTable('sessions', { id: text('id').primaryKey(), userId: uuid('user_id') .notNull() @@ -25,5 +25,3 @@ export const sessionsRelations = relations(sessionsTable, ({ one }) => ({ })); export type Sessions = InferSelectModel; - -export default sessionsTable; diff --git a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts b/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts index b523a16..f4cd9a2 100644 --- a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts @@ -4,7 +4,7 @@ import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { timestamps } from '../utils'; import { usersTable } from './users.table'; -const twoFactorTable = pgTable('two_factor', { +export const twoFactorTable = pgTable('two_factor', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -30,5 +30,3 @@ export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) = })); export type TwoFactor = InferSelectModel; - -export default twoFactorTable; diff --git a/src/lib/server/api/infrastructure/database/tables/userRoles.ts b/src/lib/server/api/infrastructure/database/tables/userRoles.ts index 46489a0..6d3d833 100644 --- a/src/lib/server/api/infrastructure/database/tables/userRoles.ts +++ b/src/lib/server/api/infrastructure/database/tables/userRoles.ts @@ -2,10 +2,10 @@ import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import { usersTable } from './users.table'; -import roles from './roles'; +import {roles} from './roles'; import { timestamps } from '../utils'; -const user_roles = pgTable('user_roles', { +export const user_roles = pgTable('user_roles', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -32,5 +32,3 @@ export const user_role_relations = relations(user_roles, ({ one }) => ({ })); export type UserRoles = InferSelectModel; - -export default user_roles; diff --git a/src/lib/server/api/infrastructure/database/tables/users.table.ts b/src/lib/server/api/infrastructure/database/tables/users.table.ts index 0e90b66..77224f4 100644 --- a/src/lib/server/api/infrastructure/database/tables/users.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/users.table.ts @@ -2,7 +2,7 @@ import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import { timestamps } from '../utils'; -import user_roles from './userRoles'; +import {user_roles} from './userRoles'; export const usersTable = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), diff --git a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts b/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts index ac4bc61..1aaecac 100644 --- a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts +++ b/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts @@ -2,10 +2,10 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import wishlists from './wishlists'; -import games from './games'; +import {games} from './games'; import { timestamps } from '../utils'; -const wishlist_items = pgTable('wishlist_items', { +export const wishlist_items = pgTable('wishlist_items', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -31,5 +31,3 @@ export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({ references: [games.id], }), })); - -export default wishlist_items; diff --git a/src/lib/server/api/infrastructure/database/tables/wishlists.ts b/src/lib/server/api/infrastructure/database/tables/wishlists.ts index 83cd754..8e0ab5e 100644 --- a/src/lib/server/api/infrastructure/database/tables/wishlists.ts +++ b/src/lib/server/api/infrastructure/database/tables/wishlists.ts @@ -4,7 +4,7 @@ import { type InferSelectModel, relations } from 'drizzle-orm'; import { usersTable } from './users.table'; import { timestamps } from '../utils'; -const wishlists = pgTable('wishlists', { +export const wishlists = pgTable('wishlists', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') .unique() @@ -24,5 +24,3 @@ export const wishlists_relations = relations(wishlists, ({ one }) => ({ references: [usersTable.id], }), })); - -export default wishlists; From 049ab4ef0b60df4b191ac0bf0edbba814f9a297d Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Wed, 7 Aug 2024 20:59:30 -0700 Subject: [PATCH 17/34] Fixing migrate and seed. --- .../api/infrastructure/database/migrate.ts | 4 ++-- .../server/api/infrastructure/database/seed.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/server/api/infrastructure/database/migrate.ts b/src/lib/server/api/infrastructure/database/migrate.ts index a1198be..156ac34 100644 --- a/src/lib/server/api/infrastructure/database/migrate.ts +++ b/src/lib/server/api/infrastructure/database/migrate.ts @@ -2,8 +2,8 @@ import 'dotenv/config'; import postgres from 'postgres'; import { drizzle } from 'drizzle-orm/postgres-js'; import { migrate } from 'drizzle-orm/postgres-js/migrator'; -import env from '../env'; -import config from '../../drizzle.config'; +import env from '../../../../../env'; +import config from '../../../../../../drizzle.config'; const connection = postgres({ host: env.DATABASE_HOST || 'localhost', diff --git a/src/lib/server/api/infrastructure/database/seed.ts b/src/lib/server/api/infrastructure/database/seed.ts index 7a7495e..9eca187 100644 --- a/src/lib/server/api/infrastructure/database/seed.ts +++ b/src/lib/server/api/infrastructure/database/seed.ts @@ -1,5 +1,5 @@ import { Table, getTableName, sql } from 'drizzle-orm'; -import env from '../env'; +import env from '../../../../../env'; import { db, pool } from '$db'; import * as schema from './tables'; import * as seeds from './seeds'; @@ -13,13 +13,15 @@ async function resetTable(db: db, table: Table) { } for (const table of [ - schema.categories, + schema.categoriesTable, schema.categoriesToExternalIdsTable, - schema.categories_to_games, + schema.categories_to_games_table, schema.collection_items, schema.collections, + schema.credentialsTable, schema.expansions, schema.externalIds, + schema.federatedIdentityTable, schema.games, schema.gamesToExternalIds, schema.mechanics, @@ -29,14 +31,14 @@ for (const table of [ schema.publishers, schema.publishersToExternalIds, schema.publishers_to_games, - schema.recoveryCodes, + schema.recovery_codes, schema.roles, schema.sessionsTable, - schema.userRoles, + schema.twoFactorTable, + schema.user_roles, schema.usersTable, - schema.twoFactor, - schema.wishlists, schema.wishlist_items, + schema.wishlists, ]) { // await db.delete(table); // clear tables without truncating / resetting ids await resetTable(db, table); From 2652d4fef603625ddf3af476877a7407fbbf73cb Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 8 Aug 2024 12:38:17 -0700 Subject: [PATCH 18/34] Adding controllers slowly. --- src/hooks.server.ts | 4 +- src/lib/dtos/signup-username-email.dto.ts | 18 ++++ .../api/controllers/signup.controller.ts | 97 +++++++++++++++++++ .../server/api/controllers/user.controller.ts | 10 +- src/lib/server/api/index.ts | 10 +- .../tables/federatedIdentity.table.ts | 2 +- .../server/api/middleware/auth.middleware.ts | 1 + src/routes/(auth)/sign-up/+page.server.ts | 31 +++--- 8 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 src/lib/dtos/signup-username-email.dto.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index c9e32e9..a8d1a2c 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -29,12 +29,12 @@ const apiClient: Handle = async ({ event, resolve }) => { /* ----------------------------- Auth functions ----------------------------- */ async function getAuthedUser() { - const { data } = await api.user.me.$get().then(parseApiResponse) + const { data } = await api.user.$get().then(parseApiResponse) return data && data.user; } async function getAuthedUserOrThrow() { - const { data } = await api.user.me.$get().then(parseApiResponse); + const { data } = await api.user.$get().then(parseApiResponse); if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/'); return data?.user; } diff --git a/src/lib/dtos/signup-username-email.dto.ts b/src/lib/dtos/signup-username-email.dto.ts new file mode 100644 index 0000000..1cd0b87 --- /dev/null +++ b/src/lib/dtos/signup-username-email.dto.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; +import { refinePasswords } from "$lib/validations/account"; + +export const signupUsernameEmailDto = z.object({ + firstName: z.string().trim().optional(), + lastName: z.string().trim().optional(), + email: z.string().trim().max(64, {message: 'Email must be less than 64 characters'}).optional(), + username: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}), + password: z.string({required_error: 'Password is required'}).trim(), + confirm_password: z.string({required_error: 'Confirm Password is required'}).trim() + }) + .superRefine(({ confirm_password, password }, ctx) => { + refinePasswords(confirm_password, password, ctx); + }); \ No newline at end of file diff --git a/src/lib/server/api/controllers/signup.controller.ts b/src/lib/server/api/controllers/signup.controller.ts index e69de29..ca795af 100644 --- a/src/lib/server/api/controllers/signup.controller.ts +++ b/src/lib/server/api/controllers/signup.controller.ts @@ -0,0 +1,97 @@ +import 'reflect-metadata'; +import { Hono } from 'hono'; +import { injectable } from 'tsyringe'; +import { zValidator } from '@hono/zod-validator'; +import type { HonoTypes } from '../types'; +import type { Controller } from '../interfaces/controller.interface'; +import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto"; +import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; + +@injectable() +export class SignupController implements Controller { + controller = new Hono(); + + constructor( + ) { } + + routes() { + return this.controller + .post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { firstName, lastName, email, username, password } = await c.req.valid('json'); + +// const existing_user = await db.query.usersTable.findFirst({ +// where: eq(usersTable.username, form.data.username), +// }); +// +// if (existing_user) { +// return setError(form, 'username', 'You cannot create an account with that username'); +// } +// +// console.log('Creating user'); +// +// const hashedPassword = await new Argon2id().hash(form.data.password); +// +// const user = await db +// .insert(usersTable) +// .values({ +// username: form.data.username, +// hashed_password: hashedPassword, +// email: form.data.email, +// first_name: form.data.firstName ?? '', +// last_name: form.data.lastName ?? '', +// verified: false, +// receive_email: false, +// theme: 'system', +// }) +// .returning(); +// console.log('signup user', user); +// +// if (!user || user.length === 0) { +// return fail(400, { +// form, +// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, +// }); +// } +// +// await add_user_to_role(user[0].id, 'user', true); +// await db.insert(collections).values({ +// user_id: user[0].id, +// }); +// await db.insert(wishlists).values({ +// user_id: user[0].id, +// }); +// +// try { +// session = await lucia.createSession(user[0].id, { +// ip_country: event.locals.ip, +// ip_address: event.locals.country, +// twoFactorAuthEnabled: false, +// isTwoFactorAuthenticated: false, +// }); +// sessionCookie = lucia.createSessionCookie(session.id); +// } catch (e: any) { +// if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) { +// // key already exists +// console.error('Lucia Error: ', e); +// } +// console.log(e); +// const message = { +// type: 'error', +// message: 'Unable to create your account. Please try again.', +// }; +// form.data.password = ''; +// form.data.confirm_password = ''; +// error(500, message); +// } +// +// event.cookies.set(sessionCookie.name, sessionCookie.value, { +// path: '.', +// ...sessionCookie.attributes, +// }); +// +// redirect(302, '/'); + + return c.json({ message: 'Hello, world!' }); + }); + } +} \ No newline at end of file diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index 4ac575b..45de16c 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -14,11 +14,17 @@ export class UserController implements Controller { routes() { return this.controller - .get('/me', requireAuth, async (c) => { + .get('/', async (c) => { const user = c.var.user; return c.json({ user }); }) - .get('/user', requireAuth, async (c) => { + .get('/:id', requireAuth, async (c) => { + const id = c.req.param('id'); + const user = c.var.user; + return c.json({ user }); + }) + .get('/username/:userName', requireAuth, async (c) => { + const userName = c.req.param('userName'); const user = c.var.user; return c.json({ user }); }); diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index 2a9ed1b..24fd321 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -8,6 +8,8 @@ import { config } from './common/config'; import { container } from 'tsyringe'; import { IamController } from './controllers/iam.controller'; import { LoginController } from './controllers/login.controller'; +import {UserController} from "$lib/server/api/controllers/user.controller"; +import {SignupController} from "$lib/server/api/controllers/signup.controller"; /* ----------------------------------- Api ---------------------------------- */ const app = new Hono().basePath('/api'); @@ -34,9 +36,11 @@ app.use( /* --------------------------------- Routes --------------------------------- */ const routes = app - .route('/user', container.resolve(IamController).routes()) - .route('/login', container.resolve(LoginController).routes()) - .get('/', (c) => c.json({ message: 'Server is healthy' })); + .route('/me', container.resolve(IamController).routes()) + .route('/user', container.resolve(UserController).routes()) + .route('/login', container.resolve(LoginController).routes()) + .route('/signup', container.resolve(SignupController).routes()) + .get('/', (c) => c.json({ message: 'Server is healthy' })); /* -------------------------------------------------------------------------- */ /* Exports */ diff --git a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts index 63d9313..ea23de2 100644 --- a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts @@ -8,7 +8,7 @@ export const federatedIdentityTable = pgTable('federated_identity', { user_id: uuid('user_id') .notNull() .references(() => usersTable.id, { onDelete: 'cascade' }), - idenitity_provider: text('idenitity_provider').notNull(), + identity_provider: text('identity_provider').notNull(), federated_user_id: text('federated_user_id').notNull(), federated_username: text('federated_username').notNull(), ...timestamps diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index 9c21ea8..b1eb3d6 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -45,6 +45,7 @@ export const requireAuth: MiddlewareHandler<{ }; }> = createMiddleware(async (c, next) => { const user = c.var.user; + const session = c.var.session; if (!user) throw Unauthorized('You must be logged in to access this resource'); return next(); }); diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/sign-up/+page.server.ts index 73b05d0..b9db366 100644 --- a/src/routes/(auth)/sign-up/+page.server.ts +++ b/src/routes/(auth)/sign-up/+page.server.ts @@ -33,22 +33,29 @@ export const load: PageServerLoad = async (event) => { const { locals, cookies } = event; const { user, session } = event.locals; - if (userFullyAuthenticated(user, session)) { + const authedUser = await locals.getAuthedUser(); + + if (authedUser) { const message = { type: 'success', message: 'You are already signed in' } as const; throw redirect('/', message, event); - } else if (userNotFullyAuthenticated(user, session)) { - try { - await lucia.invalidateSession(locals.session!.id!); - } catch (error) { - console.log('Session already invalidated'); - } - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); } + // if (userFullyAuthenticated(user, session)) { + // const message = { type: 'success', message: 'You are already signed in' } as const; + // throw redirect('/', message, event); + // } else if (userNotFullyAuthenticated(user, session)) { + // try { + // await lucia.invalidateSession(locals.session!.id!); + // } catch (error) { + // console.log('Session already invalidated'); + // } + // const sessionCookie = lucia.createBlankSessionCookie(); + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }); + // } + return { form: await superValidate(zod(signUpSchema), { defaults: signUpDefaults, From 80b956b35c47e385c38ae1ec63a76d66f20d8842 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sat, 10 Aug 2024 10:03:30 -0700 Subject: [PATCH 19/34] Signup flow servers and repositories for user create. --- src/lib/dtos/create-user-role.dto.ts | 1 + src/lib/dtos/signup-username-email.dto.ts | 4 +- .../api/controllers/signup.controller.ts | 11 ++- .../repositories/credentials.repository.ts | 2 + .../api/repositories/roles.repository.ts | 76 +++++++++++++++++++ .../api/repositories/user_roles.repository.ts | 58 ++++++++++++++ .../api/repositories/users.repository.ts | 10 +++ src/lib/server/api/services/roles.service.ts | 14 ++++ src/lib/server/api/services/tokens.service.ts | 4 + .../server/api/services/user_roles.service.ts | 42 ++++++++++ src/lib/server/api/services/users.service.ts | 73 +++++++++++++++++- 11 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 src/lib/dtos/create-user-role.dto.ts create mode 100644 src/lib/server/api/repositories/roles.repository.ts create mode 100644 src/lib/server/api/repositories/user_roles.repository.ts create mode 100644 src/lib/server/api/services/roles.service.ts create mode 100644 src/lib/server/api/services/user_roles.service.ts diff --git a/src/lib/dtos/create-user-role.dto.ts b/src/lib/dtos/create-user-role.dto.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/lib/dtos/create-user-role.dto.ts @@ -0,0 +1 @@ + diff --git a/src/lib/dtos/signup-username-email.dto.ts b/src/lib/dtos/signup-username-email.dto.ts index 1cd0b87..e0acae7 100644 --- a/src/lib/dtos/signup-username-email.dto.ts +++ b/src/lib/dtos/signup-username-email.dto.ts @@ -15,4 +15,6 @@ export const signupUsernameEmailDto = z.object({ }) .superRefine(({ confirm_password, password }, ctx) => { refinePasswords(confirm_password, password, ctx); - }); \ No newline at end of file + }); + +export type SignupUsernameEmailDto = z.infer diff --git a/src/lib/server/api/controllers/signup.controller.ts b/src/lib/server/api/controllers/signup.controller.ts index ca795af..d7ae203 100644 --- a/src/lib/server/api/controllers/signup.controller.ts +++ b/src/lib/server/api/controllers/signup.controller.ts @@ -1,23 +1,32 @@ import 'reflect-metadata'; import { Hono } from 'hono'; -import { injectable } from 'tsyringe'; +import {inject, injectable} from 'tsyringe'; import { zValidator } from '@hono/zod-validator'; import type { HonoTypes } from '../types'; import type { Controller } from '../interfaces/controller.interface'; import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto"; import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; +import {UsersService} from "$lib/server/api/services/users.service"; @injectable() export class SignupController implements Controller { controller = new Hono(); constructor( + @inject(UsersService) private readonly usersService: UsersService ) { } routes() { return this.controller .post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { const { firstName, lastName, email, username, password } = await c.req.valid('json'); + const existingUser = await this.usersService.findOneByUsername(username); + + if (existingUser) { + return c.body("User already exists", 400); + } + + const user = await this.usersService.create(signupUsernameEmailDto); // const existing_user = await db.query.usersTable.findFirst({ // where: eq(usersTable.username, form.data.username), diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 3c9ee07..3cc4e6c 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -2,10 +2,12 @@ import { and, eq, type InferInsertModel } from "drizzle-orm"; import { credentialsTable, CredentialsType } from "../infrastructure/database/tables/credentials.table"; import { db } from "../infrastructure/database"; import { takeFirstOrThrow } from "../infrastructure/database/utils"; +import {injectable} from "tsyringe"; export type CreateCredentials = InferInsertModel; export type UpdateCredentials = Partial; +@injectable() export class CredentialsRepository { async findOneByUserId(userId: string) { diff --git a/src/lib/server/api/repositories/roles.repository.ts b/src/lib/server/api/repositories/roles.repository.ts new file mode 100644 index 0000000..7ced97f --- /dev/null +++ b/src/lib/server/api/repositories/roles.repository.ts @@ -0,0 +1,76 @@ +import { eq, type InferInsertModel } from 'drizzle-orm'; +import { takeFirstOrThrow } from '../infrastructure/database/utils'; +import { db } from '../infrastructure/database'; +import {roles} from "$lib/server/api/infrastructure/database/tables"; +import {injectable} from "tsyringe"; + +/* -------------------------------------------------------------------------- */ +/* Repository */ +/* -------------------------------------------------------------------------- */ +/* ---------------------------------- About --------------------------------- */ +/* +Repositories are the layer that interacts with the database. They are responsible for retrieving and +storing data. They should not contain any business logic, only database queries. +*/ +/* ---------------------------------- Notes --------------------------------- */ +/* + Repositories should only contain methods for CRUD operations and any other database interactions. + Any complex logic should be delegated to a service. If a repository method requires a transaction, + it should be passed in as an argument or the class should have a method to set the transaction. + In our case the method 'trxHost' is used to set the transaction context. +*/ + +export type CreateRole = InferInsertModel; +export type UpdateRole = Partial; + +@injectable() +export class RolesRepository { + async findOneById(id: string) { + return db.query.roles.findFirst({ + where: eq(roles.id, id) + }); + } + + async findOneByIdOrThrow(id: string) { + const role = await this.findOneById(id); + if (!role) throw Error('Role not found'); + return role; + } + + async findAll() { + return db.query.roles.findMany(); + } + + async findOneByName(name: string) { + return db.query.roles.findFirst({ + where: eq(roles.name, name) + }); + } + + async findOneByNameOrThrow(name: string) { + const role = await this.findOneByName(name); + if (!role) throw Error('Role not found'); + return role; + } + + async create(data: CreateRole) { + return db.insert(roles).values(data).returning().then(takeFirstOrThrow); + } + + async update(id: string, data: UpdateRole) { + return db + .update(roles) + .set(data) + .where(eq(roles.id, id)) + .returning() + .then(takeFirstOrThrow); + } + + async delete(id: string) { + return db + .delete(roles) + .where(eq(roles.id, id)) + .returning() + .then(takeFirstOrThrow); + } +} diff --git a/src/lib/server/api/repositories/user_roles.repository.ts b/src/lib/server/api/repositories/user_roles.repository.ts new file mode 100644 index 0000000..d553a69 --- /dev/null +++ b/src/lib/server/api/repositories/user_roles.repository.ts @@ -0,0 +1,58 @@ +import { eq, type InferInsertModel } from 'drizzle-orm'; +import { usersTable } from '../infrastructure/database/tables/users.table'; +import { takeFirstOrThrow } from '../infrastructure/database/utils'; +import { db } from '../infrastructure/database'; +import {user_roles} from "$lib/server/api/infrastructure/database/tables"; +import {injectable} from "tsyringe"; + +/* -------------------------------------------------------------------------- */ +/* Repository */ +/* -------------------------------------------------------------------------- */ +/* ---------------------------------- About --------------------------------- */ +/* +Repositories are the layer that interacts with the database. They are responsible for retrieving and +storing data. They should not contain any business logic, only database queries. +*/ +/* ---------------------------------- Notes --------------------------------- */ +/* + Repositories should only contain methods for CRUD operations and any other database interactions. + Any complex logic should be delegated to a service. If a repository method requires a transaction, + it should be passed in as an argument or the class should have a method to set the transaction. + In our case the method 'trxHost' is used to set the transaction context. +*/ + +export type CreateUserRole = InferInsertModel; +export type UpdateUserRole = Partial; + +@injectable() +export class UserRolesRepository { + async findOneById(id: string) { + return db.query.user_roles.findFirst({ + where: eq(user_roles.id, id) + }); + } + + async findOneByIdOrThrow(id: string) { + const userRole = await this.findOneById(id); + if (!userRole) throw Error('User not found'); + return userRole; + } + + async findAllByUserId(userId: string) { + return db.query.user_roles.findMany({ + where: eq(user_roles.user_id, userId) + }); + } + + async create(data: CreateUserRole) { + return db.insert(user_roles).values(data).returning().then(takeFirstOrThrow); + } + + async delete(id: string) { + return db + .delete(user_roles) + .where(eq(user_roles.id, id)) + .returning() + .then(takeFirstOrThrow); + } +} diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts index addeaf8..5c60ba6 100644 --- a/src/lib/server/api/repositories/users.repository.ts +++ b/src/lib/server/api/repositories/users.repository.ts @@ -2,6 +2,7 @@ import { eq, type InferInsertModel } from 'drizzle-orm'; import { usersTable } from '../infrastructure/database/tables/users.table'; import { takeFirstOrThrow } from '../infrastructure/database/utils'; import { db } from '../infrastructure/database'; +import {injectable} from "tsyringe"; /* -------------------------------------------------------------------------- */ /* Repository */ @@ -22,6 +23,7 @@ storing data. They should not contain any business logic, only database queries. export type CreateUser = InferInsertModel; export type UpdateUser = Partial; +@injectable() export class UsersRepository { async findOneById(id: string) { return db.query.usersTable.findFirst({ @@ -59,4 +61,12 @@ export class UsersRepository { .returning() .then(takeFirstOrThrow); } + + async delete(id: string) { + return db + .delete(usersTable) + .where(eq(usersTable.id, id)) + .returning() + .then(takeFirstOrThrow); + } } diff --git a/src/lib/server/api/services/roles.service.ts b/src/lib/server/api/services/roles.service.ts new file mode 100644 index 0000000..fad30e5 --- /dev/null +++ b/src/lib/server/api/services/roles.service.ts @@ -0,0 +1,14 @@ +import {inject, injectable} from "tsyringe"; +import { RolesRepository } from "$lib/server/api/repositories/roles.repository"; + +@injectable() +export class RolesService { + constructor( + @inject(RolesRepository) private readonly rolesRepository: RolesRepository + ) { } + + + async findOneByNameOrThrow(name: string) { + return this.rolesRepository.findOneByNameOrThrow(name); + } +} \ No newline at end of file diff --git a/src/lib/server/api/services/tokens.service.ts b/src/lib/server/api/services/tokens.service.ts index a3f712e..c204fb0 100644 --- a/src/lib/server/api/services/tokens.service.ts +++ b/src/lib/server/api/services/tokens.service.ts @@ -29,6 +29,10 @@ export class TokensService { } } + async createHashedToken(token: string) { + return this.hashingService.hash(token) + } + async verifyHashedToken(hashedToken: string, token: string) { return this.hashingService.verify(hashedToken, token) } diff --git a/src/lib/server/api/services/user_roles.service.ts b/src/lib/server/api/services/user_roles.service.ts new file mode 100644 index 0000000..25044bd --- /dev/null +++ b/src/lib/server/api/services/user_roles.service.ts @@ -0,0 +1,42 @@ +import {inject, injectable} from "tsyringe"; +import {type CreateUserRole, UserRolesRepository} from "$lib/server/api/repositories/user_roles.repository"; +import db from "$db"; +import {eq} from "drizzle-orm"; +import {roles, userRoles} from "$db/schema"; +import {RolesService} from "$lib/server/api/services/roles.service"; + +@injectable() +export class UserRolesService { + constructor( + @inject(UserRolesRepository) private readonly userRolesRepository: UserRolesRepository, + @inject(RolesService) private readonly rolesService: RolesService + ) { } + + async findOneById(id: string) { + return this.userRolesRepository.findOneById(id); + } + + async findAllByUserId(userId: string) { + return this.userRolesRepository.findAllByUserId(userId); + } + + async create(data: CreateUserRole) { + return this.userRolesRepository.create(data); + } + + async addRoleToUser(userId: string, roleName: string, primary = false) { + // Find the role by its name + const role = await this.rolesService.findOneByNameOrThrow(roleName); + + if (!role || !role.id) { + throw new Error(`Role with name ${roleName} not found`); + } + + // Create a UserRole entry linking the user and the role + return db.insert(userRoles).values({ + user_id: userId, + role_id: role.id, + primary, + }); + } +} \ No newline at end of file diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index 8040442..ee1411c 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -1,12 +1,81 @@ import { inject, injectable } from 'tsyringe'; -import type { UsersRepository } from '../repositories/users.repository'; +import { UsersRepository } from '../repositories/users.repository'; +import type {SignupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto"; +import {TokensService} from "$lib/server/api/services/tokens.service"; +import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository"; +import {CredentialsType} from "$lib/server/api/infrastructure/database/tables"; +import {UserRolesService} from "$lib/server/api/services/user_roles.service"; @injectable() export class UsersService { constructor( - @inject('UsersRepository') private readonly usersRepository: UsersRepository + @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, + @inject(TokensService) private readonly tokenService: TokensService, + @inject(UsersRepository) private readonly usersRepository: UsersRepository, + @inject(UserRolesService) private readonly userRolesService: UserRolesService, ) { } + async create(data: SignupUsernameEmailDto) { + const { firstName, lastName, email, username, password } = data; + + const hashedPassword = await this.tokenService.createHashedToken(password); + const user = await this.usersRepository.create({ + first_name: firstName, + last_name: lastName, + email, + username, + }); + + if (!user) { + return null; + } + + const credentials = await this.credentialsRepository.create({ + user_id: user.id, + type: CredentialsType.PASSWORD, + secret_data: hashedPassword, + }); + + if (!credentials) { + await this.usersRepository.delete(user.id); + return null; + } + + this.userRolesService.addRoleToUser(user.id, 'user', true); +// +// const user = await db +// .insert(usersTable) +// .values({ +// username: form.data.username, +// hashed_password: hashedPassword, +// email: form.data.email, +// first_name: form.data.firstName ?? '', +// last_name: form.data.lastName ?? '', +// verified: false, +// receive_email: false, +// theme: 'system', +// }) +// .returning(); +// console.log('signup user', user); +// +// if (!user || user.length === 0) { +// return fail(400, { +// form, +// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, +// }); +// } +// +// await add_user_to_role(user[0].id, 'user', true); +// await db.insert(collections).values({ +// user_id: user[0].id, +// }); +// await db.insert(wishlists).values({ +// user_id: user[0].id, +// }); + + return this.usersRepository.create(data); + } + async findOneByUsername(username: string) { return this.usersRepository.findOneByUsername(username); } From 426ced1373cc8bfde4a93d16f526cb35c28d7311 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sat, 10 Aug 2024 10:10:57 -0700 Subject: [PATCH 20/34] Fixing importing db provider on repositories. --- .../repositories/collections.repository.ts | 11 ++++++++ .../repositories/credentials.repository.ts | 5 ++-- .../api/repositories/roles.repository.ts | 6 +++-- .../api/repositories/user_roles.repository.ts | 7 +++--- .../api/repositories/users.repository.ts | 6 +++-- .../api/repositories/wishlists.repository.ts | 9 +++++++ .../api/services/collections.service.ts | 10 ++++++++ src/lib/server/api/services/users.service.ts | 25 +------------------ 8 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 src/lib/server/api/repositories/collections.repository.ts create mode 100644 src/lib/server/api/repositories/wishlists.repository.ts create mode 100644 src/lib/server/api/services/collections.service.ts diff --git a/src/lib/server/api/repositories/collections.repository.ts b/src/lib/server/api/repositories/collections.repository.ts new file mode 100644 index 0000000..80f4a31 --- /dev/null +++ b/src/lib/server/api/repositories/collections.repository.ts @@ -0,0 +1,11 @@ +import {inject, injectable} from "tsyringe"; +import {DatabaseProvider} from "$lib/server/api/providers"; + +@injectable() +export class CollectionsRepository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } + + async findAll() { + return db.query.collections.findMany(); + } +} \ No newline at end of file diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 3cc4e6c..4d7c468 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -1,14 +1,15 @@ import { and, eq, type InferInsertModel } from "drizzle-orm"; import { credentialsTable, CredentialsType } from "../infrastructure/database/tables/credentials.table"; -import { db } from "../infrastructure/database"; import { takeFirstOrThrow } from "../infrastructure/database/utils"; -import {injectable} from "tsyringe"; +import {inject, injectable} from "tsyringe"; +import {DatabaseProvider} from "$lib/server/api/providers"; export type CreateCredentials = InferInsertModel; export type UpdateCredentials = Partial; @injectable() export class CredentialsRepository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } async findOneByUserId(userId: string) { return db.query.credentialsTable.findFirst({ diff --git a/src/lib/server/api/repositories/roles.repository.ts b/src/lib/server/api/repositories/roles.repository.ts index 7ced97f..816fa84 100644 --- a/src/lib/server/api/repositories/roles.repository.ts +++ b/src/lib/server/api/repositories/roles.repository.ts @@ -1,8 +1,8 @@ import { eq, type InferInsertModel } from 'drizzle-orm'; import { takeFirstOrThrow } from '../infrastructure/database/utils'; -import { db } from '../infrastructure/database'; import {roles} from "$lib/server/api/infrastructure/database/tables"; -import {injectable} from "tsyringe"; +import {inject, injectable} from "tsyringe"; +import {DatabaseProvider} from "$lib/server/api/providers"; /* -------------------------------------------------------------------------- */ /* Repository */ @@ -25,6 +25,8 @@ export type UpdateRole = Partial; @injectable() export class RolesRepository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } + async findOneById(id: string) { return db.query.roles.findFirst({ where: eq(roles.id, id) diff --git a/src/lib/server/api/repositories/user_roles.repository.ts b/src/lib/server/api/repositories/user_roles.repository.ts index d553a69..2dfcd4d 100644 --- a/src/lib/server/api/repositories/user_roles.repository.ts +++ b/src/lib/server/api/repositories/user_roles.repository.ts @@ -1,9 +1,8 @@ import { eq, type InferInsertModel } from 'drizzle-orm'; -import { usersTable } from '../infrastructure/database/tables/users.table'; import { takeFirstOrThrow } from '../infrastructure/database/utils'; -import { db } from '../infrastructure/database'; import {user_roles} from "$lib/server/api/infrastructure/database/tables"; -import {injectable} from "tsyringe"; +import {inject, injectable} from "tsyringe"; +import {DatabaseProvider} from "$lib/server/api/providers"; /* -------------------------------------------------------------------------- */ /* Repository */ @@ -26,6 +25,8 @@ export type UpdateUserRole = Partial; @injectable() export class UserRolesRepository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } + async findOneById(id: string) { return db.query.user_roles.findFirst({ where: eq(user_roles.id, id) diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts index 5c60ba6..4106263 100644 --- a/src/lib/server/api/repositories/users.repository.ts +++ b/src/lib/server/api/repositories/users.repository.ts @@ -1,8 +1,8 @@ import { eq, type InferInsertModel } from 'drizzle-orm'; import { usersTable } from '../infrastructure/database/tables/users.table'; import { takeFirstOrThrow } from '../infrastructure/database/utils'; -import { db } from '../infrastructure/database'; -import {injectable} from "tsyringe"; +import {inject, injectable} from "tsyringe"; +import {DatabaseProvider} from "$lib/server/api/providers"; /* -------------------------------------------------------------------------- */ /* Repository */ @@ -25,6 +25,8 @@ export type UpdateUser = Partial; @injectable() export class UsersRepository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } + async findOneById(id: string) { return db.query.usersTable.findFirst({ where: eq(usersTable.id, id) diff --git a/src/lib/server/api/repositories/wishlists.repository.ts b/src/lib/server/api/repositories/wishlists.repository.ts new file mode 100644 index 0000000..9cdf394 --- /dev/null +++ b/src/lib/server/api/repositories/wishlists.repository.ts @@ -0,0 +1,9 @@ +import {inject, injectable} from "tsyringe"; +import {DatabaseProvider} from "$lib/server/api/providers"; + +@injectable() +export class CollectionsService { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } + + +} \ No newline at end of file diff --git a/src/lib/server/api/services/collections.service.ts b/src/lib/server/api/services/collections.service.ts new file mode 100644 index 0000000..61e4337 --- /dev/null +++ b/src/lib/server/api/services/collections.service.ts @@ -0,0 +1,10 @@ +import { inject, injectable } from "tsyringe"; + +@injectable() +export class CollectionsService { + constructor( + @inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository + ) { } + + +} \ No newline at end of file diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index ee1411c..be3dbce 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -42,30 +42,7 @@ export class UsersService { } this.userRolesService.addRoleToUser(user.id, 'user', true); -// -// const user = await db -// .insert(usersTable) -// .values({ -// username: form.data.username, -// hashed_password: hashedPassword, -// email: form.data.email, -// first_name: form.data.firstName ?? '', -// last_name: form.data.lastName ?? '', -// verified: false, -// receive_email: false, -// theme: 'system', -// }) -// .returning(); -// console.log('signup user', user); -// -// if (!user || user.length === 0) { -// return fail(400, { -// form, -// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, -// }); -// } -// -// await add_user_to_role(user[0].id, 'user', true); + // await db.insert(collections).values({ // user_id: user[0].id, // }); From 14fe604bf49fec733aae886acbde3ade67db314f Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 13 Aug 2024 15:19:57 -0700 Subject: [PATCH 21/34] Fix tests, write random name generator, install faker, and update dependencies. --- package.json | 25 +- pnpm-lock.yaml | 281 +++++++++--------- .../tables/categoriesToExternalIdsTable.ts | 2 +- .../database/tables/categoriesToGames.ts | 2 +- .../database/tables/collectionItems.ts | 2 +- .../database/tables/expansions.ts | 2 +- .../database/tables/mechanics.ts | 6 +- .../database/tables/mechanicsToGames.ts | 2 - .../database/tables/publishers.ts | 2 +- .../database/tables/wishlistItems.ts | 2 +- .../repositories/collections.repository.ts | 33 +- .../repositories/credentials.repository.ts | 12 +- .../api/repositories/roles.repository.ts | 12 +- .../api/repositories/user_roles.repository.ts | 8 +- .../api/repositories/users.repository.ts | 12 +- .../api/repositories/wishlists.repository.ts | 43 ++- .../api/services/collections.service.ts | 11 + .../server/api/services/user_roles.service.ts | 5 +- src/lib/server/api/services/users.service.ts | 16 +- .../server/api/services/wishlists.service.ts | 26 ++ .../api/tests/login-requests.service.test.ts | 72 ----- .../server/api/tests/users.service.test.ts | 111 +++++++ src/lib/tests/randomDataUtil.test.ts | 7 + src/lib/utils/randomDataUtil.ts | 40 +++ 24 files changed, 469 insertions(+), 265 deletions(-) create mode 100644 src/lib/server/api/services/wishlists.service.ts delete mode 100644 src/lib/server/api/tests/login-requests.service.test.ts create mode 100644 src/lib/server/api/tests/users.service.test.ts create mode 100644 src/lib/tests/randomDataUtil.test.ts create mode 100644 src/lib/utils/randomDataUtil.ts diff --git a/package.json b/package.json index 76cc897..e8f3cd7 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,16 @@ "test:unit": "vitest" }, "devDependencies": { + "@faker-js/faker": "^8.4.1", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", "@playwright/test": "^1.46.0", - "@sveltejs/adapter-auto": "^3.2.2", - "@sveltejs/enhanced-img": "^0.3.1", - "@sveltejs/kit": "^2.5.20", + "@sveltejs/adapter-auto": "^3.2.4", + "@sveltejs/enhanced-img": "^0.3.3", + "@sveltejs/kit": "^2.5.22", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@types/cookie": "^0.6.0", - "@types/node": "^20.14.14", + "@types/node": "^20.14.15", "@types/pg": "^8.11.6", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", @@ -63,12 +64,12 @@ "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", "sveltekit-superforms": "^2.16.1", - "tailwindcss": "^3.4.7", + "tailwindcss": "^3.4.9", "ts-node": "^10.9.2", "tslib": "^2.6.3", - "tsx": "^4.16.5", + "tsx": "^4.17.0", "typescript": "^5.5.4", - "vite": "^5.3.5", + "vite": "^5.4.0", "vitest": "^1.6.0", "zod": "^3.23.8" }, @@ -84,14 +85,14 @@ "@neondatabase/serverless": "^0.9.4", "@paralleldrive/cuid2": "^2.2.2", "@resvg/resvg-js": "^2.6.2", - "@sveltejs/adapter-node": "^5.2.0", - "@sveltejs/adapter-vercel": "^5.4.1", + "@sveltejs/adapter-node": "^5.2.2", + "@sveltejs/adapter-vercel": "^5.4.3", "@types/feather-icons": "^4.29.4", "@vercel/og": "^0.5.20", "arctic": "^1.9.2", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.12.0", + "bullmq": "^5.12.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -101,7 +102,7 @@ "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.5.3", + "hono": "^4.5.5", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", @@ -119,7 +120,7 @@ "reflect-metadata": "^0.2.2", "svelte-french-toast": "^1.2.0", "svelte-lazy-loader": "^1.0.0", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.2", "tailwind-variants": "^0.2.1", "tailwindcss-animate": "^1.0.7", "tsyringe": "^4.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e072f3..afa7e6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 5.0.13 '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.3)(zod@3.23.8) + version: 0.2.2(hono@4.5.5)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -39,11 +39,11 @@ importers: specifier: ^2.6.2 version: 2.6.2 '@sveltejs/adapter-node': - specifier: ^5.2.0 - version: 5.2.0(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) + specifier: ^5.2.2 + version: 5.2.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) '@sveltejs/adapter-vercel': - specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) + specifier: ^5.4.3 + version: 5.4.3(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -60,8 +60,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.12.0 - version: 5.12.0 + specifier: ^5.12.5 + version: 5.12.5 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -85,16 +85,16 @@ importers: version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.5.3 - version: 4.5.3 + specifier: ^4.5.5 + version: 4.5.5 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.3) + version: 0.4.0(hono@4.5.5) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -144,14 +144,14 @@ importers: specifier: ^1.0.0 version: 1.0.0 tailwind-merge: - specifier: ^2.4.0 - version: 2.4.0 + specifier: ^2.5.2 + version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -159,6 +159,9 @@ importers: specifier: ^3.23.2 version: 3.23.2(zod@3.23.8) devDependencies: + '@faker-js/faker': + specifier: ^8.4.1 + version: 8.4.1 '@melt-ui/pp': specifier: ^0.3.2 version: 0.3.2(@melt-ui/svelte@0.83.0(svelte@5.0.0-next.175))(svelte@5.0.0-next.175) @@ -169,23 +172,23 @@ importers: specifier: ^1.46.0 version: 1.46.0 '@sveltejs/adapter-auto': - specifier: ^3.2.2 - version: 3.2.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) + specifier: ^3.2.4 + version: 3.2.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) '@sveltejs/enhanced-img': - specifier: ^0.3.1 - version: 0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + specifier: ^0.3.3 + version: 0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) '@sveltejs/kit': - specifier: ^2.5.20 - version: 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + specifier: ^2.5.22 + version: 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + version: 3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.14.14 - version: 20.14.14 + specifier: ^20.14.15 + version: 20.14.15 '@types/pg': specifier: ^8.11.6 version: 8.11.6 @@ -209,7 +212,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: ^2.43.0 - version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -233,7 +236,7 @@ importers: version: 16.1.0(postcss@8.4.41) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0) postcss-preset-env: specifier: ^9.6.0 version: 9.6.0(postcss@8.4.41) @@ -257,7 +260,7 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.5 - version: 3.8.5(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) + version: 3.8.5(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) @@ -266,40 +269,40 @@ importers: version: 3.1.2(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))) + version: 0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.16.1 - version: 2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: - specifier: ^3.4.7 - version: 3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + specifier: ^3.4.9 + version: 3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) + version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 tsx: - specifier: ^4.16.5 - version: 4.16.5 + specifier: ^4.17.0 + version: 4.17.0 typescript: specifier: ^5.5.4 version: 5.5.4 vite: - specifier: ^5.3.5 - version: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + specifier: ^5.4.0 + version: 5.4.0(@types/node@20.14.15)(sass@1.77.8) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.14)(sass@1.77.8) + version: 1.6.0(@types/node@20.14.15)(sass@1.77.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -1159,6 +1162,10 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@faker-js/faker@8.4.1': + resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + '@floating-ui/core@1.6.1': resolution: {integrity: sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==} @@ -1950,29 +1957,29 @@ packages: '@sodaru/yup-to-json-schema@2.0.1': resolution: {integrity: sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==} - '@sveltejs/adapter-auto@3.2.2': - resolution: {integrity: sha512-Mso5xPCA8zgcKrv+QioVlqMZkyUQ5MjDJiEPuG/Z7cV/5tmwV7LmcVWk5tZ+H0NCOV1x12AsoSpt/CwFwuVXMA==} + '@sveltejs/adapter-auto@3.2.4': + resolution: {integrity: sha512-a64AKYbfTUrVwU0xslzv1Jf3M8bj0IwhptaXmhgIkjXspBXhD0od9JiItQHchijpLMGdEDcYBlvqySkEawv6mQ==} peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/adapter-node@5.2.0': - resolution: {integrity: sha512-HVZoei2078XSyPmvdTHE03VXDUD0ytTvMuMHMQP0j6zX4nPDpCcKrgvU7baEblMeCCMdM/shQvstFxOJPQKlUQ==} + '@sveltejs/adapter-node@5.2.2': + resolution: {integrity: sha512-BCX4zP0cf86TXpmvLQTnnT/tp7P12UMezf+5LwljP1MJC1fFzn9XOXpAHQCyP+pyHGy2K7p5gY0LyLcZFAL02w==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/adapter-vercel@5.4.1': - resolution: {integrity: sha512-JLcD1OgMnu9lQ8EssxVGxv7w0waWuyVzItTT1eqIH98Krufd9qfr1uC9zgo82z3dJ9v1AfPEbvIX5tonceg7XQ==} + '@sveltejs/adapter-vercel@5.4.3': + resolution: {integrity: sha512-srZBkMpeaa7lflO1ZGdKTW3jWHscE9rdAkyxgRlVMVyugjcPOZ3dcpEfpoM4wtVCbpEOdHniWqQR9yL+zs4ybA==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/enhanced-img@0.3.1': - resolution: {integrity: sha512-75A4YiXQp+GRc54EyiNOlhHnHt9O8e0CdCHLm3RWESLRaazd5OIciSa4SbKIo9DM84yGwSVShU0buyUmNJvgWg==} + '@sveltejs/enhanced-img@0.3.3': + resolution: {integrity: sha512-nsqJkVuYLUXARDLjMoGKAt4oLzwtY8X2E8rIl/TJl7ueLjpTISxrAhVRN3r8yMO+R+so4G6Taiix2mpiPpqZeg==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: '>= 5.0.0' - '@sveltejs/kit@2.5.20': - resolution: {integrity: sha512-47rJ5BoYwURE/Rp7FNMLp3NzdbWC9DQ/PmKd0mebxT2D/PrPxZxcLImcD3zsWdX2iS6oJk8ITJbO/N2lWnnUqA==} + '@sveltejs/kit@2.5.22': + resolution: {integrity: sha512-PQ98baF2WzvG5yiO4cZKJZJG60XjHTZD1jyho3u9Kmthx2ytdGYyVPPvKXgKXpKSq4wwctD9dl0d2blSbJMcOg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2025,8 +2032,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.14.14': - resolution: {integrity: sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==} + '@types/node@20.14.15': + resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} @@ -2313,8 +2320,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.12.0: - resolution: {integrity: sha512-kOtSQx9ymylslsLNFD0xOMJM9mHqnq3x6KD7+DYkHByWe0HFRdblpYKhZyL4uR3rwaKZwzOrJVl3RwRaDjZxSg==} + bullmq@5.12.5: + resolution: {integrity: sha512-lchCvFuPdaIbq01qnyS7MOt2piPeCDHzCqIxNAQEgDSzZ+Eb4RBboUUMgmW90UtMjV46mEqsWY9B1l/7/C13SA==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3083,8 +3090,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.3: - resolution: {integrity: sha512-r26WwwbKD3BAYdfB294knNnegNda7VfV1tVn66D9Kvl9WQTdrR+5eKdoeaQNHQcC3Gr0KBikzAtjd6VsRGVSaw==} + hono@4.5.5: + resolution: {integrity: sha512-fXBXHqaVfimWofbelLXci8pZyIwBMkDIwCa4OwZvK+xVbEyYLELVP4DfbGaj1aEM6ZY3hHgs4qLvCO2ChkhgQw==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -4526,8 +4533,8 @@ packages: tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwind-merge@2.4.0: - resolution: {integrity: sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==} + tailwind-merge@2.5.2: + resolution: {integrity: sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==} tailwind-variants@0.2.1: resolution: {integrity: sha512-2xmhAf4UIc3PijOUcJPA1LP4AbxhpcHuHM2C26xM0k81r0maAO6uoUSHl3APmvHZcY5cZCY/bYuJdfFa4eGoaw==} @@ -4540,8 +4547,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.7: - resolution: {integrity: sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==} + tailwindcss@3.4.9: + resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} engines: {node: '>=14.0.0'} hasBin: true @@ -4636,8 +4643,8 @@ packages: tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tsx@4.16.5: - resolution: {integrity: sha512-ArsiAQHEW2iGaqZ8fTA1nX0a+lN5mNTyuGRRO6OW3H/Yno1y9/t1f9YOI1Cfoqz63VAthn++ZYcbDP7jPflc+A==} + tsx@4.17.0: + resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} engines: {node: '>=18.0.0'} hasBin: true @@ -4740,8 +4747,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.3.5: - resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} + vite@5.4.0: + resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4749,6 +4756,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -4761,6 +4769,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -5499,6 +5509,8 @@ snapshots: '@exodus/schemasafe@1.3.0': optional: true + '@faker-js/faker@8.4.1': {} + '@floating-ui/core@1.6.1': dependencies: '@floating-ui/utils': 0.2.2 @@ -5544,9 +5556,9 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/zod-validator@0.2.2(hono@4.5.3)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.5.5)(zod@3.23.8)': dependencies: - hono: 4.5.3 + hono: 4.5.5 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -6136,41 +6148,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))': + '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1) '@rollup/plugin-json': 6.1.0(rollup@4.18.1) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1) - '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) rollup: 4.18.1 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.1(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': + '@sveltejs/enhanced-img@0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': dependencies: magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) vite-imagetools: 7.0.2(rollup@4.18.1) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': + '@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6184,28 +6196,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) - vitefu: 0.2.5(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vitefu: 0.2.5(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6235,13 +6247,13 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.14.14': + '@types/node@20.14.15': dependencies: undici-types: 5.26.5 '@types/pg@8.11.6': dependencies: - '@types/node': 20.14.14 + '@types/node': 20.14.15 pg-protocol: 1.6.1 pg-types: 4.0.2 @@ -6588,7 +6600,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.12.0: + bullmq@5.12.5: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -7007,7 +7019,6 @@ snapshots: '@esbuild/win32-arm64': 0.23.0 '@esbuild/win32-ia32': 0.23.0 '@esbuild/win32-x64': 0.23.0 - optional: true escalade@3.1.2: {} @@ -7024,7 +7035,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): + eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -7033,7 +7044,7 @@ snapshots: esutils: 2.0.3 known-css-properties: 0.34.0 postcss: 8.4.41 - postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) postcss-safe-parser: 6.0.0(postcss@8.4.41) postcss-selector-parser: 6.1.0 semver: 7.6.2 @@ -7260,11 +7271,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7396,11 +7407,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.3): + hono-rate-limiter@0.4.0(hono@4.5.5): dependencies: - hono: 4.5.3 + hono: 4.5.5 - hono@4.5.3: {} + hono@4.5.5: {} html-entities@2.5.2: {} @@ -8115,30 +8126,30 @@ snapshots: '@csstools/utilities': 1.0.0(postcss@8.4.41) postcss: 8.4.41 - postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): + postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) - postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: jiti: 1.21.6 postcss: 8.4.41 - tsx: 4.16.5 + tsx: 4.17.0 postcss-logical@7.0.1(postcss@8.4.41): dependencies: @@ -8708,14 +8719,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.5(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): + svelte-check@3.8.5(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.0.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - '@babel/core' @@ -8771,7 +8782,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8781,16 +8792,16 @@ snapshots: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0) sass: 1.77.8 typescript: 5.5.4 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.16.5) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0) sass: 1.77.8 typescript: 5.5.4 @@ -8845,19 +8856,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) - sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8880,18 +8891,18 @@ snapshots: tabbable@6.2.0: {} - tailwind-merge@2.4.0: {} + tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): dependencies: - tailwind-merge: 2.4.0 - tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + tailwind-merge: 2.5.2 + tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) - tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)): + tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8910,7 +8921,7 @@ snapshots: postcss: 8.4.41 postcss-import: 15.1.0(postcss@8.4.41) postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) postcss-nested: 6.0.1(postcss@8.4.41) postcss-selector-parser: 6.1.0 resolve: 1.22.8 @@ -8977,14 +8988,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.14 + '@types/node': 20.14.15 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -9002,9 +9013,9 @@ snapshots: tslib@2.6.3: {} - tsx@4.16.5: + tsx@4.17.0: dependencies: - esbuild: 0.21.5 + esbuild: 0.23.0 get-tsconfig: 4.7.5 optionalDependencies: fsevents: 2.3.3 @@ -9091,38 +9102,39 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.14.14)(sass@1.77.8): + vite-node@1.6.0(@types/node@20.14.15)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite@5.3.5(@types/node@20.14.14)(sass@1.77.8): + vite@5.4.0(@types/node@20.14.15)(sass@1.77.8): dependencies: esbuild: 0.21.5 postcss: 8.4.41 rollup: 4.17.2 optionalDependencies: - '@types/node': 20.14.14 + '@types/node': 20.14.15 fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.3.5(@types/node@20.14.14)(sass@1.77.8)): + vitefu@0.2.5(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)): optionalDependencies: - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) - vitest@1.6.0(@types/node@20.14.14)(sass@1.77.8): + vitest@1.6.0(@types/node@20.14.15)(sass@1.77.8): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9141,15 +9153,16 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.3.5(@types/node@20.14.14)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.14.14)(sass@1.77.8) + vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.14.15)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.14.14 + '@types/node': 20.14.15 transitivePeerDependencies: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts b/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts index b06a267..c8ab5f3 100644 --- a/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts +++ b/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts @@ -1,6 +1,6 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; import { categoriesTable } from './categories.table'; -import externalIds from './externalIds'; +import { externalIds } from './externalIds'; import { relations } from 'drizzle-orm'; export const categoriesToExternalIdsTable = pgTable( diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts b/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts index 4343a7c..a977fdd 100644 --- a/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts +++ b/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts @@ -1,7 +1,7 @@ import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; import { categoriesTable } from './categories.table'; -import games from './games'; +import { games } from './games'; export const categories_to_games_table = pgTable( 'categories_to_games', diff --git a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts b/src/lib/server/api/infrastructure/database/tables/collectionItems.ts index 131ac2f..14ea009 100644 --- a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts +++ b/src/lib/server/api/infrastructure/database/tables/collectionItems.ts @@ -2,7 +2,7 @@ import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import { collections } from './collections'; -import games from './games'; +import {games} from './games'; import { timestamps } from '../utils'; export const collection_items = pgTable('collection_items', { diff --git a/src/lib/server/api/infrastructure/database/tables/expansions.ts b/src/lib/server/api/infrastructure/database/tables/expansions.ts index 200bfff..3390b88 100644 --- a/src/lib/server/api/infrastructure/database/tables/expansions.ts +++ b/src/lib/server/api/infrastructure/database/tables/expansions.ts @@ -1,7 +1,7 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import games from './games'; +import {games} from './games'; import { timestamps } from '../utils'; export const expansions = pgTable('expansions', { diff --git a/src/lib/server/api/infrastructure/database/tables/mechanics.ts b/src/lib/server/api/infrastructure/database/tables/mechanics.ts index 72f5902..2f489bc 100644 --- a/src/lib/server/api/infrastructure/database/tables/mechanics.ts +++ b/src/lib/server/api/infrastructure/database/tables/mechanics.ts @@ -1,8 +1,8 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import mechanicsToGames from './mechanicsToGames'; -import mechanicsToExternalIds from './mechanicsToExternalIds'; +import {mechanics_to_games} from './mechanicsToGames'; +import {mechanicsToExternalIds} from './mechanicsToExternalIds'; import { timestamps } from '../utils'; export const mechanics = pgTable('mechanics', { @@ -18,6 +18,6 @@ export const mechanics = pgTable('mechanics', { export type Mechanics = InferSelectModel; export const mechanics_relations = relations(mechanics, ({ many }) => ({ - mechanics_to_games: many(mechanicsToGames), + mechanics_to_games: many(mechanics_to_games), mechanicsToExternalIds: many(mechanicsToExternalIds), })); diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts b/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts index 6faf721..c81cde4 100644 --- a/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts +++ b/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts @@ -32,5 +32,3 @@ export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one references: [games.id], }), })); - -export default mechanics_to_games; diff --git a/src/lib/server/api/infrastructure/database/tables/publishers.ts b/src/lib/server/api/infrastructure/database/tables/publishers.ts index 65a0c2d..48d0e66 100644 --- a/src/lib/server/api/infrastructure/database/tables/publishers.ts +++ b/src/lib/server/api/infrastructure/database/tables/publishers.ts @@ -2,7 +2,7 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; import {publishers_to_games} from './publishersToGames'; -import publishersToExternalIds from './publishersToExternalIds'; +import {publishersToExternalIds} from './publishersToExternalIds'; import { timestamps } from '../utils'; export const publishers = pgTable('publishers', { diff --git a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts b/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts index 1aaecac..b46ca72 100644 --- a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts +++ b/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts @@ -1,7 +1,7 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { createId as cuid2 } from '@paralleldrive/cuid2'; import { type InferSelectModel, relations } from 'drizzle-orm'; -import wishlists from './wishlists'; +import {wishlists} from './wishlists'; import {games} from './games'; import { timestamps } from '../utils'; diff --git a/src/lib/server/api/repositories/collections.repository.ts b/src/lib/server/api/repositories/collections.repository.ts index 80f4a31..e1fd4f1 100644 --- a/src/lib/server/api/repositories/collections.repository.ts +++ b/src/lib/server/api/repositories/collections.repository.ts @@ -1,11 +1,42 @@ import {inject, injectable} from "tsyringe"; +import { eq, type InferInsertModel } from "drizzle-orm"; import {DatabaseProvider} from "$lib/server/api/providers"; +import { collections } from "../infrastructure/database/tables"; +import { takeFirstOrThrow } from "../infrastructure/database/utils"; + +export type CreateCollection = InferInsertModel; +export type UpdateCollection = Partial; @injectable() export class CollectionsRepository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } async findAll() { - return db.query.collections.findMany(); + return this.db.query.collections.findMany(); + } + + async findOneById(id: string) { + return this.db.query.collections.findFirst({ + where: eq(collections.id, id) + }) + } + + async findOneByUserId(userId: string) { + return this.db.query.collections.findFirst({ + where: eq(collections.user_id, userId) + }) + } + + async create(data: CreateCollection) { + return this.db.insert(collections).values(data).returning().then(takeFirstOrThrow); + } + + async update(id: string, data: UpdateCollection) { + return this.db + .update(collections) + .set(data) + .where(eq(collections.id, id)) + .returning() + .then(takeFirstOrThrow); } } \ No newline at end of file diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 4d7c468..2084a6b 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -12,13 +12,13 @@ export class CredentialsRepository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } async findOneByUserId(userId: string) { - return db.query.credentialsTable.findFirst({ + return this.db.query.credentialsTable.findFirst({ where: eq(credentialsTable.user_id, userId) }); } async findPasswordCredentialsByUserId(userId: string) { - return db.query.credentialsTable.findFirst({ + return this.db.query.credentialsTable.findFirst({ where: and( eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.PASSWORD) @@ -27,7 +27,7 @@ export class CredentialsRepository { } async findTOTPCredentialsByUserId(userId: string) { - return db.query.credentialsTable.findFirst({ + return this.db.query.credentialsTable.findFirst({ where: and( eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.TOTP) @@ -36,7 +36,7 @@ export class CredentialsRepository { } async findOneById(id: string) { - return db.query.credentialsTable.findFirst({ + return this.db.query.credentialsTable.findFirst({ where: eq(credentialsTable.id, id) }); } @@ -48,11 +48,11 @@ export class CredentialsRepository { } async create(data: CreateCredentials) { - return db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow); } async update(id: string, data: UpdateCredentials) { - return db + return this.db .update(credentialsTable) .set(data) .where(eq(credentialsTable.id, id)) diff --git a/src/lib/server/api/repositories/roles.repository.ts b/src/lib/server/api/repositories/roles.repository.ts index 816fa84..b1ab846 100644 --- a/src/lib/server/api/repositories/roles.repository.ts +++ b/src/lib/server/api/repositories/roles.repository.ts @@ -28,7 +28,7 @@ export class RolesRepository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } async findOneById(id: string) { - return db.query.roles.findFirst({ + return this.db.query.roles.findFirst({ where: eq(roles.id, id) }); } @@ -40,11 +40,11 @@ export class RolesRepository { } async findAll() { - return db.query.roles.findMany(); + return this.db.query.roles.findMany(); } async findOneByName(name: string) { - return db.query.roles.findFirst({ + return this.db.query.roles.findFirst({ where: eq(roles.name, name) }); } @@ -56,11 +56,11 @@ export class RolesRepository { } async create(data: CreateRole) { - return db.insert(roles).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(roles).values(data).returning().then(takeFirstOrThrow); } async update(id: string, data: UpdateRole) { - return db + return this.db .update(roles) .set(data) .where(eq(roles.id, id)) @@ -69,7 +69,7 @@ export class RolesRepository { } async delete(id: string) { - return db + return this.db .delete(roles) .where(eq(roles.id, id)) .returning() diff --git a/src/lib/server/api/repositories/user_roles.repository.ts b/src/lib/server/api/repositories/user_roles.repository.ts index 2dfcd4d..cbc688f 100644 --- a/src/lib/server/api/repositories/user_roles.repository.ts +++ b/src/lib/server/api/repositories/user_roles.repository.ts @@ -28,7 +28,7 @@ export class UserRolesRepository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } async findOneById(id: string) { - return db.query.user_roles.findFirst({ + return this.db.query.user_roles.findFirst({ where: eq(user_roles.id, id) }); } @@ -40,17 +40,17 @@ export class UserRolesRepository { } async findAllByUserId(userId: string) { - return db.query.user_roles.findMany({ + return this.db.query.user_roles.findMany({ where: eq(user_roles.user_id, userId) }); } async create(data: CreateUserRole) { - return db.insert(user_roles).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(user_roles).values(data).returning().then(takeFirstOrThrow); } async delete(id: string) { - return db + return this.db .delete(user_roles) .where(eq(user_roles.id, id)) .returning() diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts index 4106263..e65ec7a 100644 --- a/src/lib/server/api/repositories/users.repository.ts +++ b/src/lib/server/api/repositories/users.repository.ts @@ -28,7 +28,7 @@ export class UsersRepository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } async findOneById(id: string) { - return db.query.usersTable.findFirst({ + return this.db.query.usersTable.findFirst({ where: eq(usersTable.id, id) }); } @@ -40,23 +40,23 @@ export class UsersRepository { } async findOneByUsername(username: string) { - return db.query.usersTable.findFirst({ + return this.db.query.usersTable.findFirst({ where: eq(usersTable.username, username) }); } async findOneByEmail(email: string) { - return db.query.usersTable.findFirst({ + return this.db.query.usersTable.findFirst({ where: eq(usersTable.email, email) }); } async create(data: CreateUser) { - return db.insert(usersTable).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(usersTable).values(data).returning().then(takeFirstOrThrow); } async update(id: string, data: UpdateUser) { - return db + return this.db .update(usersTable) .set(data) .where(eq(usersTable.id, id)) @@ -65,7 +65,7 @@ export class UsersRepository { } async delete(id: string) { - return db + return this.db .delete(usersTable) .where(eq(usersTable.id, id)) .returning() diff --git a/src/lib/server/api/repositories/wishlists.repository.ts b/src/lib/server/api/repositories/wishlists.repository.ts index 9cdf394..a566d06 100644 --- a/src/lib/server/api/repositories/wishlists.repository.ts +++ b/src/lib/server/api/repositories/wishlists.repository.ts @@ -1,9 +1,48 @@ import {inject, injectable} from "tsyringe"; import {DatabaseProvider} from "$lib/server/api/providers"; +import { eq, type InferInsertModel } from "drizzle-orm"; +import { wishlists } from "../infrastructure/database/tables"; +import { takeFirstOrThrow } from "../infrastructure/database/utils"; + +export type CreateWishlist = InferInsertModel; +export type UpdateWishlist = Partial; @injectable() -export class CollectionsService { +export class WishlistsRepository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } - + async findAll() { + return this.db.query.wishlists.findMany(); + } + + async findOneById(id: string) { + return this.db.query.wishlists.findFirst({ + where: eq(wishlists.id, id) + }) + } + + async findOneByUserId(userId: string) { + return this.db.query.wishlists.findFirst({ + where: eq(wishlists.user_id, userId) + }) + } + + async findAllByUserId(userId: string) { + return this.db.query.wishlists.findMany({ + where: eq(wishlists.user_id, userId) + }) + } + + async create(data: CreateWishlist) { + return this.db.insert(wishlists).values(data).returning().then(takeFirstOrThrow); + } + + async update(id: string, data: UpdateWishlist) { + return this.db + .update(wishlists) + .set(data) + .where(eq(wishlists.id, id)) + .returning() + .then(takeFirstOrThrow); + } } \ No newline at end of file diff --git a/src/lib/server/api/services/collections.service.ts b/src/lib/server/api/services/collections.service.ts index 61e4337..fb39773 100644 --- a/src/lib/server/api/services/collections.service.ts +++ b/src/lib/server/api/services/collections.service.ts @@ -1,4 +1,6 @@ import { inject, injectable } from "tsyringe"; +import { generateRandomAnimalName } from "$lib/utils/randomDataUtil"; +import { CollectionsRepository } from "../repositories/collections.repository"; @injectable() export class CollectionsService { @@ -6,5 +8,14 @@ export class CollectionsService { @inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository ) { } + async createEmptyNoName(userId: string) { + return this.createEmpty(userId, null); + } + async createEmpty(userId: string, name: string | null) { + return this.collectionsRepository.create({ + user_id: userId, + name: name ?? generateRandomAnimalName(), + }); + } } \ No newline at end of file diff --git a/src/lib/server/api/services/user_roles.service.ts b/src/lib/server/api/services/user_roles.service.ts index 25044bd..63a56b2 100644 --- a/src/lib/server/api/services/user_roles.service.ts +++ b/src/lib/server/api/services/user_roles.service.ts @@ -1,9 +1,8 @@ import {inject, injectable} from "tsyringe"; import {type CreateUserRole, UserRolesRepository} from "$lib/server/api/repositories/user_roles.repository"; import db from "$db"; -import {eq} from "drizzle-orm"; -import {roles, userRoles} from "$db/schema"; import {RolesService} from "$lib/server/api/services/roles.service"; +import { user_roles } from "../infrastructure/database/tables"; @injectable() export class UserRolesService { @@ -33,7 +32,7 @@ export class UserRolesService { } // Create a UserRole entry linking the user and the role - return db.insert(userRoles).values({ + return db.insert(user_roles).values({ user_id: userId, role_id: role.id, primary, diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index be3dbce..02c2d78 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -5,14 +5,18 @@ import {TokensService} from "$lib/server/api/services/tokens.service"; import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository"; import {CredentialsType} from "$lib/server/api/infrastructure/database/tables"; import {UserRolesService} from "$lib/server/api/services/user_roles.service"; +import { CollectionsService } from './collections.service'; +import { WishlistsService } from './wishlists.service'; @injectable() export class UsersService { constructor( + @inject(CollectionsService) private readonly collectionsService: CollectionsService, @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, @inject(TokensService) private readonly tokenService: TokensService, @inject(UsersRepository) private readonly usersRepository: UsersRepository, @inject(UserRolesService) private readonly userRolesService: UserRolesService, + @inject(WishlistsService) private readonly wishlistsService: WishlistsService ) { } async create(data: SignupUsernameEmailDto) { @@ -41,16 +45,12 @@ export class UsersService { return null; } - this.userRolesService.addRoleToUser(user.id, 'user', true); + await this.userRolesService.addRoleToUser(user.id, 'user', true); -// await db.insert(collections).values({ -// user_id: user[0].id, -// }); -// await db.insert(wishlists).values({ -// user_id: user[0].id, -// }); + await this.wishlistsService.createEmptyNoName(user.id); + await this.collectionsService.createEmptyNoName(user.id); - return this.usersRepository.create(data); + return user; } async findOneByUsername(username: string) { diff --git a/src/lib/server/api/services/wishlists.service.ts b/src/lib/server/api/services/wishlists.service.ts new file mode 100644 index 0000000..47aa57a --- /dev/null +++ b/src/lib/server/api/services/wishlists.service.ts @@ -0,0 +1,26 @@ +import { inject, injectable } from "tsyringe"; +import { WishlistsRepository } from "../repositories/wishlists.repository"; +import { generateRandomAnimalName } from "$lib/utils/randomDataUtil"; + +@injectable() +export class WishlistsService { + + constructor( + @inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository + ) { } + + async findAllByUserId(userId: string) { + return this.wishlistsRepository.findAllByUserId(userId); + } + + async createEmptyNoName(userId: string) { + return this.createEmpty(userId, null); + } + + async createEmpty(userId: string, name: string | null) { + return this.wishlistsRepository.create({ + user_id: userId, + name: name ?? generateRandomAnimalName(), + }); + } +} \ No newline at end of file diff --git a/src/lib/server/api/tests/login-requests.service.test.ts b/src/lib/server/api/tests/login-requests.service.test.ts deleted file mode 100644 index 33329e5..0000000 --- a/src/lib/server/api/tests/login-requests.service.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -// import 'reflect-metadata'; -// import { LoginRequestsService } from '../services/login-requests.service'; -// import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -// import { TokensService } from '../services/tokens.service'; -// import { MailerService } from '../services/mailer.service'; -// import { UsersRepository } from '../repositories/users.repository'; -// import { DatabaseProvider, LuciaProvider } from '../providers'; -// import { LoginRequestsRepository } from '../repositories/login-requests.repository'; -// import { PgDatabase } from 'drizzle-orm/pg-core'; -// import { container } from 'tsyringe'; - -// describe('LoginRequestService', () => { -// let service: LoginRequestsService; -// let tokensService = vi.mocked(TokensService.prototype) -// let mailerService = vi.mocked(MailerService.prototype); -// let usersRepository = vi.mocked(UsersRepository.prototype); -// let loginRequestsRepository = vi.mocked(LoginRequestsRepository.prototype); -// let luciaProvider = vi.mocked(LuciaProvider); -// let databaseProvider = vi.mocked(PgDatabase); - -// beforeAll(() => { -// service = container -// .register(TokensService, { useValue: tokensService }) -// .register(MailerService, { useValue: mailerService }) -// .register(UsersRepository, { useValue: usersRepository }) -// .register(LoginRequestsRepository, { useValue: loginRequestsRepository }) -// .register(LuciaProvider, { useValue: luciaProvider }) -// .register(DatabaseProvider, { useValue: databaseProvider }) -// .resolve(LoginRequestsService); -// }); - - -// afterAll(() => { -// vi.resetAllMocks() -// }) - -// describe('Create', () => { -// tokensService.generateTokenWithExpiryAndHash = vi.fn().mockResolvedValue({ -// token: "1", -// expiry: new Date(), -// hashedToken: "xyz" -// } satisfies Awaited>) - -// loginRequestsRepository.create = vi.fn().mockResolvedValue({ -// createdAt: new Date(), -// email: 'me@test.com', -// expiresAt: new Date(), -// hashedToken: '111', -// id: '1', -// updatedAt: new Date() -// } satisfies Awaited>) - -// mailerService.sendLoginRequest = vi.fn().mockResolvedValue(null) - -// const spy_mailerService_sendLoginRequest = vi.spyOn(mailerService, 'sendLoginRequest') -// const spy_tokensService_generateTokenWithExpiryAndHash = vi.spyOn(tokensService, 'generateTokenWithExpiryAndHash') -// const spy_loginRequestsRepository_create = vi.spyOn(loginRequestsRepository, 'create') - -// it('should resolve', async () => { -// await expect(service.create({ email: "test" })).resolves.toBeUndefined() -// }) -// it('should generate a token with expiry and hash', async () => { -// expect(spy_tokensService_generateTokenWithExpiryAndHash).toBeCalledTimes(1) -// }) -// it('should send an email with token', async () => { -// expect(spy_mailerService_sendLoginRequest).toHaveBeenCalledTimes(1) -// }) -// it('should create a new login request record', async () => { -// expect(spy_loginRequestsRepository_create).toBeCalledTimes(1) -// }) -// }) -// }); diff --git a/src/lib/server/api/tests/users.service.test.ts b/src/lib/server/api/tests/users.service.test.ts new file mode 100644 index 0000000..5fe24d3 --- /dev/null +++ b/src/lib/server/api/tests/users.service.test.ts @@ -0,0 +1,111 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { UsersService } from '../services/users.service'; +import { CredentialsRepository } from '../repositories/credentials.repository'; +import { TokensService } from '../services/tokens.service'; +import { UserRolesService } from '../services/user_roles.service'; +import { UsersRepository } from '../repositories/users.repository'; +import { Argon2id } from 'oslo/password'; +import { WishlistsService } from '../services/wishlists.service'; +import { CollectionsService } from '../services/collections.service'; +// import { LoginRequestsService } from '../services/login-requests.service'; +// import { TokensService } from '../services/tokens.service'; +// import { MailerService } from '../services/mailer.service'; +// import { UsersRepository } from '../repositories/users.repository'; +// import { DatabaseProvider, LuciaProvider } from '../providers'; +// import { LoginRequestsRepository } from '../repositories/login-requests.repository'; +// import { PgDatabase } from 'drizzle-orm/pg-core'; + +describe('UsersService', () => { + let service: UsersService; + const credentialsRepository = vi.mocked(CredentialsRepository.prototype); + const tokensService = vi.mocked(TokensService.prototype); + const usersRepository = vi.mocked(UsersRepository.prototype); + const userRolesService = vi.mocked(UserRolesService.prototype); + const wishlistsService = vi.mocked(WishlistsService.prototype); + const collectionsService = vi.mocked(CollectionsService.prototype); + + beforeAll(() => { + service = container + .register(CredentialsRepository, { useValue: credentialsRepository }) + .register(TokensService, { useValue: tokensService }) + .register(UsersRepository, { useValue: usersRepository }) + .register(UserRolesService, { useValue: userRolesService }) + .register(WishlistsService, { useValue: wishlistsService }) + .register(CollectionsService, { useValue: collectionsService }) + .resolve(UsersService); + }); + + afterAll(() => { + vi.resetAllMocks() + }) + + describe('Create User', () => { + const hashedPassword = new Argon2id().hash('111'); + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) + + usersRepository.create = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: 'test', + last_name: 'test', + email: 'test@example.com', + username: 'test', + verified: false, + receive_email: false, + theme: 'system', + createdAt: new Date(), + updatedAt: new Date() + } satisfies Awaited>) + + credentialsRepository.create = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + type: 'PASSWORD', + secret_data: hashedPassword + }) + + userRolesService.addRoleToUser = vi.fn().mockResolvedValue(undefined) + + wishlistsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) + + collectionsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) + + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken'); + const spy_usersRepository_create = vi.spyOn(usersRepository, 'create'); + const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create'); + const spy_userRolesService_addRoleToUser = vi.spyOn(userRolesService, 'addRoleToUser'); + const spy_wishlistsService_createEmptyNoName = vi.spyOn(wishlistsService, 'createEmptyNoName'); + const spy_collectionsService_createEmptyNoName = vi.spyOn(collectionsService, 'createEmptyNoName'); + + it('should resolve', async () => { + await expect(service.create({ + firstName: 'test', + lastName: 'test', + email: 'test@example.com', + username: 'test', + password: '111', + confirm_password: '111' + })).resolves.not.toThrow() + }) + it('should generate a hashed token', async () => { + expect(spy_tokensService_createHashToken).toBeCalledTimes(1) + }) + it('should create a new user', async () => { + expect(spy_usersRepository_create).toHaveBeenCalledTimes(1) + }) + it('should create a new credential', async () => { + expect(spy_credentialsRepository_create).toBeCalledTimes(1) + }) + it('should add role to user', async () => { + expect(spy_userRolesService_addRoleToUser).toBeCalledTimes(1) + }) + it('should create a new wishlist', async () => { + expect(spy_wishlistsService_createEmptyNoName).toBeCalledTimes(1) + }) + it('should create a new collection', async () => { + expect(spy_collectionsService_createEmptyNoName).toBeCalledTimes(1) + }) + }) +}); diff --git a/src/lib/tests/randomDataUtil.test.ts b/src/lib/tests/randomDataUtil.test.ts new file mode 100644 index 0000000..1fda57f --- /dev/null +++ b/src/lib/tests/randomDataUtil.test.ts @@ -0,0 +1,7 @@ +import { generateRandomAnimalName } from '$lib/utils/randomDataUtil'; +import { expect, test } from 'vitest'; + +test('generateRandomAnimalName', () => { + expect(generateRandomAnimalName()).not.toBeUndefined(); + expect(generateRandomAnimalName()).not.toEqual(generateRandomAnimalName()); +}); \ No newline at end of file diff --git a/src/lib/utils/randomDataUtil.ts b/src/lib/utils/randomDataUtil.ts new file mode 100644 index 0000000..6d7af68 --- /dev/null +++ b/src/lib/utils/randomDataUtil.ts @@ -0,0 +1,40 @@ +import { faker } from '@faker-js/faker'; + +export function generateRandomAnimalName() { + return fromRandomAnimalTypeGetAnimalName(faker.animal.type()); +} + +function fromRandomAnimalTypeGetAnimalName(animalType: string) { + switch (animalType) { + case 'bear': + return faker.animal.bear(); + case 'bird': + return faker.animal.bird(); + case 'cat': + return faker.animal.cat(); + case 'cetacean': + return faker.animal.cetacean(); + case 'cow': + return faker.animal.cow(); + case 'crocodilia': + return faker.animal.crocodilia(); + case 'dog': + return faker.animal.dog(); + case 'fish': + return faker.animal.fish(); + case 'horse': + return faker.animal.horse(); + case 'insect': + return faker.animal.insect(); + case 'lion': + return faker.animal.lion(); + case 'rabbit': + return faker.animal.rabbit(); + case 'rodent': + return faker.animal.rodent(); + case 'snake': + return faker.animal.snake(); + default: + return faker.animal.type(); + } +} \ No newline at end of file From 7c2105d43723288b7d7cca37bc11dbe11534cc99 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 13 Aug 2024 16:42:10 -0700 Subject: [PATCH 22/34] Adding more tests for services. --- .../server/api/services/user_roles.service.ts | 4 +- .../server/api/tests/hashing.service.test.ts | 32 ++++++++++ .../server/api/tests/tokens.service.test.ts | 49 +++++++++++++++ .../api/tests/user_roles.service.test.ts | 62 +++++++++++++++++++ .../server/api/tests/users.service.test.ts | 7 --- 5 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 src/lib/server/api/tests/hashing.service.test.ts create mode 100644 src/lib/server/api/tests/tokens.service.test.ts create mode 100644 src/lib/server/api/tests/user_roles.service.test.ts diff --git a/src/lib/server/api/services/user_roles.service.ts b/src/lib/server/api/services/user_roles.service.ts index 63a56b2..04ad1d0 100644 --- a/src/lib/server/api/services/user_roles.service.ts +++ b/src/lib/server/api/services/user_roles.service.ts @@ -1,8 +1,6 @@ import {inject, injectable} from "tsyringe"; import {type CreateUserRole, UserRolesRepository} from "$lib/server/api/repositories/user_roles.repository"; -import db from "$db"; import {RolesService} from "$lib/server/api/services/roles.service"; -import { user_roles } from "../infrastructure/database/tables"; @injectable() export class UserRolesService { @@ -32,7 +30,7 @@ export class UserRolesService { } // Create a UserRole entry linking the user and the role - return db.insert(user_roles).values({ + return this.userRolesRepository.create({ user_id: userId, role_id: role.id, primary, diff --git a/src/lib/server/api/tests/hashing.service.test.ts b/src/lib/server/api/tests/hashing.service.test.ts new file mode 100644 index 0000000..94c7f89 --- /dev/null +++ b/src/lib/server/api/tests/hashing.service.test.ts @@ -0,0 +1,32 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { HashingService } from '../services/hashing.service'; + +describe('HashingService', () => { + let service: HashingService; + + beforeAll(() => { + service = container.resolve(HashingService); + }); + + afterAll(() => { + vi.resetAllMocks() + }); + + describe('Create Hash', () => { + it('should create a hash', async () => { + const hash = await service.hash('111'); + expect(hash).not.toBeUndefined(); + expect(hash).not.toBeNull(); + }); + }) + + describe('Verify Hash', () => { + it('should verify a hash', async () => { + const hash = await service.hash('111'); + const verifiable = await service.verify(hash, '111'); + expect(verifiable).toBeTruthy(); + }); + }) +}) \ No newline at end of file diff --git a/src/lib/server/api/tests/tokens.service.test.ts b/src/lib/server/api/tests/tokens.service.test.ts new file mode 100644 index 0000000..5c0e34e --- /dev/null +++ b/src/lib/server/api/tests/tokens.service.test.ts @@ -0,0 +1,49 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { TokensService } from '../services/tokens.service'; +import { HashingService } from '../services/hashing.service'; +import { Argon2id } from 'oslo/password'; + +describe('TokensService', () => { + let service: TokensService; + const hashingService = vi.mocked(HashingService.prototype); + + beforeAll(() => { + service = container + .register(HashingService, { useValue: hashingService }) + .resolve(TokensService); + }); + + afterAll(() => { + vi.resetAllMocks() + }); + + describe('Generate Token', () => { + const hashedPassword = new Argon2id().hash('111'); + + hashingService.hash = vi.fn().mockResolvedValue(hashedPassword); + hashingService.verify = vi.fn().mockResolvedValue(true); + + const spy_hashingService_hash = vi.spyOn(hashingService, 'hash'); + const spy_hashingService_verify = vi.spyOn(hashingService, 'verify'); + + it('should resolve', async () => { + await expect(service.createHashedToken('111')).resolves.string + }) + it('should generate a token that is verifiable', async () => { + const token = await service.createHashedToken('111'); + expect(token).not.toBeUndefined(); + expect(token).not.toBeNull(); + const verifiable = await service.verifyHashedToken(token, '111'); + expect(verifiable).toBeTruthy(); + }); + + it('should generate a hashed token', async () => { + expect(spy_hashingService_hash).toHaveBeenCalledTimes(2); + }) + it('should verify a hashed token', async () => { + expect(spy_hashingService_verify).toHaveBeenCalledTimes(1); + }) + }); +}); \ No newline at end of file diff --git a/src/lib/server/api/tests/user_roles.service.test.ts b/src/lib/server/api/tests/user_roles.service.test.ts new file mode 100644 index 0000000..dfd47d2 --- /dev/null +++ b/src/lib/server/api/tests/user_roles.service.test.ts @@ -0,0 +1,62 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { UserRolesService } from '../services/user_roles.service'; +import { UserRolesRepository } from '../repositories/user_roles.repository'; +import { RolesService } from '../services/roles.service'; + +describe('UserRolesService', () => { + let service: UserRolesService; + const userRolesRepository = vi.mocked(UserRolesRepository.prototype); + const rolesService = vi.mocked(RolesService.prototype); + + beforeAll(() => { + service = container + .register(UserRolesRepository, { useValue: userRolesRepository }) + .register(RolesService, { useValue: rolesService }) + .resolve(UserRolesService); + }); + + afterAll(() => { + vi.resetAllMocks() + }); + + describe('Create User Role', () => { + rolesService.findOneByNameOrThrow = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + name: 'user', + createdAt: new Date(), + updatedAt: new Date() + } satisfies Awaited>); + + userRolesRepository.create = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8fff', + role_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + primary: true, + createdAt: new Date(), + updatedAt: new Date() + } satisfies Awaited>); + + const spy_rolesService_findOneByNameOrThrow = vi.spyOn(rolesService, 'findOneByNameOrThrow'); + const spy_userRolesRepository_create = vi.spyOn(userRolesRepository, 'create'); + + it('should resolve', async () => { + await expect(service.addRoleToUser('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8fff', 'user', true)).resolves.not.toThrowError(); + }) + it('should call rolesService.findOneByNameOrThrow', async () => { + expect(spy_rolesService_findOneByNameOrThrow).toBeCalledWith('user'); + expect(spy_rolesService_findOneByNameOrThrow).toBeCalledTimes(1); + }) + it('should call userRolesRepository.create', async () => { + expect(spy_userRolesRepository_create).toBeCalledWith({ + user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8fff', + role_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + primary: true + }); + expect(spy_userRolesRepository_create).toBeCalledTimes(1); + }) + }) +}); \ No newline at end of file diff --git a/src/lib/server/api/tests/users.service.test.ts b/src/lib/server/api/tests/users.service.test.ts index 5fe24d3..fb40e5f 100644 --- a/src/lib/server/api/tests/users.service.test.ts +++ b/src/lib/server/api/tests/users.service.test.ts @@ -9,13 +9,6 @@ import { UsersRepository } from '../repositories/users.repository'; import { Argon2id } from 'oslo/password'; import { WishlistsService } from '../services/wishlists.service'; import { CollectionsService } from '../services/collections.service'; -// import { LoginRequestsService } from '../services/login-requests.service'; -// import { TokensService } from '../services/tokens.service'; -// import { MailerService } from '../services/mailer.service'; -// import { UsersRepository } from '../repositories/users.repository'; -// import { DatabaseProvider, LuciaProvider } from '../providers'; -// import { LoginRequestsRepository } from '../repositories/login-requests.repository'; -// import { PgDatabase } from 'drizzle-orm/pg-core'; describe('UsersService', () => { let service: UsersService; From 8894fbf98bdd602e21e4946ec1446a8b0f0ee7d4 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 13 Aug 2024 17:07:21 -0700 Subject: [PATCH 23/34] Update migrations and fix seeding. --- package.json | 10 +- pnpm-lock.yaml | 54 +- src/lib/server/api/common/config.ts | 4 +- .../migrations/0003_worried_taskmaster.sql | 32 + .../migrations/meta/0003_snapshot.json | 1856 +++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + .../api/infrastructure/database/seed.ts | 2 +- .../infrastructure/database/seeds/users.ts | 28 +- 8 files changed, 1951 insertions(+), 42 deletions(-) create mode 100644 src/lib/server/api/infrastructure/database/migrations/0003_worried_taskmaster.sql create mode 100644 src/lib/server/api/infrastructure/database/migrations/meta/0003_snapshot.json diff --git a/package.json b/package.json index e8f3cd7..c8b2fa5 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "scripts": { "db:push": "drizzle-kit push", "db:generate": "drizzle-kit generate", - "db:migrate": "tsx src/db/migrate.ts", - "db:seed": "tsx src/db/seed.ts", + "db:migrate": "tsx src/lib/server/api/infrastructure/database/migrate.ts", + "db:seed": "tsx src/lib/server/api/infrastructure/database/seed.ts", "db:studio": "drizzle-kit studio --verbose", "dev": "NODE_OPTIONS=\"--inspect\" vite dev --host", "build": "vite build", @@ -16,7 +16,7 @@ "test:ui": "svelte-kit sync && playwright test --ui", "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", + "initialize": "pnpm install && docker compose up --no-recreate -d && pnpm db:migrate && pnpm db:seed", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write .", "site:update": "pnpm update -i -L", @@ -63,8 +63,8 @@ "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", - "sveltekit-superforms": "^2.16.1", - "tailwindcss": "^3.4.9", + "sveltekit-superforms": "^2.17.0", + "tailwindcss": "^3.4.10", "ts-node": "^10.9.2", "tslib": "^2.6.3", "tsx": "^4.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afa7e6e..ba40219 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -148,10 +148,10 @@ importers: version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -280,11 +280,11 @@ importers: specifier: ^0.5.2 version: 0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) sveltekit-superforms: - specifier: ^2.16.1 - version: 2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) + specifier: ^2.17.0 + version: 2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: - specifier: ^3.4.9 - version: 3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + specifier: ^3.4.10 + version: 3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) @@ -1951,8 +1951,8 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sinclair/typebox@0.32.34': - resolution: {integrity: sha512-a3Z3ytYl6R/+7ldxx04PO1semkwWlX/8pTqxsPw4quIcIXDFPZhOc1Wx8azWmkU26ccK3mHwcWenn0avNgAKQg==} + '@sinclair/typebox@0.32.35': + resolution: {integrity: sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA==} '@sodaru/yup-to-json-schema@2.0.1': resolution: {integrity: sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==} @@ -4524,8 +4524,8 @@ packages: peerDependencies: '@sveltejs/kit': 1.x || 2.x - sveltekit-superforms@2.16.1: - resolution: {integrity: sha512-RNBdN43xge/ADmc3s7+pfdnRGuZ9gZiqpX6VKAQCnCI+ICc5rrPv5idYbx4iuY1Ia0lRMAq1hP0x2oHaPjB+Kg==} + sveltekit-superforms@2.17.0: + resolution: {integrity: sha512-QrX8pkcmE0XoeVU42zMhsah4FoDrgtPc/4cZEr38rDlgU+DE0xNc5J0E7z1456sUJNbFjaB0+HZwwAkX0vYqaA==} peerDependencies: '@sveltejs/kit': 1.x || 2.x svelte: 3.x || 4.x || >=5.0.0-next.51 @@ -4547,8 +4547,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.9: - resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} + tailwindcss@3.4.10: + resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==} engines: {node: '>=14.0.0'} hasBin: true @@ -4613,8 +4613,8 @@ packages: peerDependencies: typescript: '>=4.2.0' - ts-deepmerge@7.0.0: - resolution: {integrity: sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==} + ts-deepmerge@7.0.1: + resolution: {integrity: sha512-JBFCmNenZdUCc+TRNCtXVM6N8y/nDQHAcpj5BlwXG/gnogjam1NunulB9ia68mnqYI446giMfpqeBFFkOleh+g==} engines: {node: '>=14.13.1'} ts-interface-checker@0.1.13: @@ -6142,7 +6142,7 @@ snapshots: '@sinclair/typebox@0.27.8': {} - '@sinclair/typebox@0.32.34': + '@sinclair/typebox@0.32.35': optional: true '@sodaru/yup-to-json-schema@2.0.1': @@ -7271,11 +7271,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -8866,18 +8866,18 @@ snapshots: '@isaacs/ttlcache': 1.4.1 '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) - sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 svelte: 5.0.0-next.175 - ts-deepmerge: 7.0.0 + ts-deepmerge: 7.0.1 optionalDependencies: '@exodus/schemasafe': 1.3.0 '@gcornut/valibot-json-schema': 0.31.0 - '@sinclair/typebox': 0.32.34 + '@sinclair/typebox': 0.32.35 '@sodaru/yup-to-json-schema': 2.0.1 '@vinejs/vine': 1.8.0 arktype: 2.0.0-beta.0 @@ -8893,16 +8893,16 @@ snapshots: tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): dependencies: tailwind-merge: 2.5.2 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) - tailwindcss@3.4.9(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): + tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8984,7 +8984,7 @@ snapshots: dependencies: typescript: 5.5.4 - ts-deepmerge@7.0.0: {} + ts-deepmerge@7.0.1: {} ts-interface-checker@0.1.13: {} diff --git a/src/lib/server/api/common/config.ts b/src/lib/server/api/common/config.ts index c0a9225..8f82d9d 100644 --- a/src/lib/server/api/common/config.ts +++ b/src/lib/server/api/common/config.ts @@ -1,4 +1,4 @@ -import * as envs from '$env/static/private'; +import env from '../../../../env'; const isPreview = process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development'; @@ -11,4 +11,4 @@ if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'product domain = 'localhost'; } -export const config = { ...envs, isProduction: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production', domain }; +export const config = { ...env, isProduction: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production', domain }; diff --git a/src/lib/server/api/infrastructure/database/migrations/0003_worried_taskmaster.sql b/src/lib/server/api/infrastructure/database/migrations/0003_worried_taskmaster.sql new file mode 100644 index 0000000..c395895 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/0003_worried_taskmaster.sql @@ -0,0 +1,32 @@ +CREATE TABLE IF NOT EXISTS "credentials" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "type" text DEFAULT 'password' NOT NULL, + "secret_data" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "federated_identity" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "identity_provider" text NOT NULL, + "federated_user_id" text NOT NULL, + "federated_username" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "credentials" ADD CONSTRAINT "credentials_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "federated_identity" ADD CONSTRAINT "federated_identity_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN IF EXISTS "hashed_password"; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0003_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0003_snapshot.json new file mode 100644 index 0000000..a5213e7 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0003_snapshot.json @@ -0,0 +1,1856 @@ +{ + "id": "5a4165ba-0d5a-4a58-aa14-e68e3e41a181", + "prevId": "79adee85-e57c-4a9f-87df-835457b68129", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "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": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "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": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "secret_data": { + "name": "secret_data", + "type": "text", + "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": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "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": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.federated_identity": { + "name": "federated_identity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_user_id": { + "name": "federated_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_username": { + "name": "federated_username", + "type": "text", + "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": { + "federated_identity_user_id_users_id_fk": { + "name": "federated_identity_user_id_users_id_fk", + "tableFrom": "federated_identity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "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": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "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": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "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": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "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": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "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": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file 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 18ded62..c0c8782 100644 --- a/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json +++ b/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1720626020902, "tag": "0002_fancy_valkyrie", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1723593488634, + "tag": "0003_worried_taskmaster", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/seed.ts b/src/lib/server/api/infrastructure/database/seed.ts index 9eca187..68152aa 100644 --- a/src/lib/server/api/infrastructure/database/seed.ts +++ b/src/lib/server/api/infrastructure/database/seed.ts @@ -1,6 +1,6 @@ import { Table, getTableName, sql } from 'drizzle-orm'; import env from '../../../../../env'; -import { db, pool } from '$db'; +import { db, pool } from './index'; import * as schema from './tables'; import * as seeds from './seeds'; diff --git a/src/lib/server/api/infrastructure/database/seeds/users.ts b/src/lib/server/api/infrastructure/database/seeds/users.ts index eba6ed2..15fab9a 100644 --- a/src/lib/server/api/infrastructure/database/seeds/users.ts +++ b/src/lib/server/api/infrastructure/database/seeds/users.ts @@ -1,7 +1,7 @@ import { eq } from 'drizzle-orm'; import { Argon2id } from 'oslo/password'; -import { type db } from '$db'; -import * as schema from '$db/schema'; +import { type db } from '$lib/server/api/infrastructure/database'; +import * as schema from '$lib/server/api/infrastructure/database/tables'; import users from './data/users.json'; import { config } from '../../../common/config'; @@ -31,7 +31,6 @@ export default async function seed(db: db) { .values({ username: `${config.ADMIN_USERNAME}`, email: '', - hashed_password: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`), first_name: 'Brad', last_name: 'S', verified: true, @@ -41,6 +40,14 @@ export default async function seed(db: db) { console.log('Admin user created.', adminUser); + await db + .insert(schema.credentialsTable) + .values({ + user_id: adminUser[0].id, + type: schema.CredentialsType.PASSWORD, + secret_data: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`), + }); + await db .insert(schema.collections) .values({ user_id: adminUser[0].id }) @@ -52,20 +59,22 @@ export default async function seed(db: db) { .onConflictDoNothing(); await db - .insert(schema.userRoles) + .insert(schema.user_roles) .values({ user_id: adminUser[0].id, role_id: adminRole[0].id, + primary: true, }) .onConflictDoNothing(); console.log('Admin user given admin role.'); await db - .insert(schema.userRoles) + .insert(schema.user_roles) .values({ user_id: adminUser[0].id, role_id: userRole[0].id, + primary: false, }) .onConflictDoNothing(); @@ -76,9 +85,13 @@ export default async function seed(db: db) { .insert(schema.usersTable) .values({ ...user, - hashed_password: await new Argon2id().hash(user.password), }) .returning(); + await db.insert(schema.credentialsTable).values({ + user_id: insertedUser?.id, + type: schema.CredentialsType.PASSWORD, + secret_data: await new Argon2id().hash(user.password), + }) await db.insert(schema.collections).values({ user_id: insertedUser?.id }); await db.insert(schema.wishlists).values({ user_id: insertedUser?.id }); await Promise.all( @@ -86,7 +99,8 @@ export default async function seed(db: db) { const foundRole = await db.query.roles.findFirst({ where: eq(schema.roles.name, role.name), }); - await db.insert(schema.userRoles).values({ + if (!foundRole) { throw new Error('Role not found'); }; + await db.insert(schema.user_roles).values({ user_id: insertedUser?.id, role_id: foundRole?.id, primary: role?.primary, From eeca4e41032df557f4fbf91b90c026fea2b1e2d5 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Wed, 14 Aug 2024 18:07:50 -0700 Subject: [PATCH 24/34] Fixing login cookie max-age. --- ...in-email.dto.ts => signin-username.dto.ts} | 4 +- .../api/controllers/login.controller.ts | 26 +++++++-- .../server/api/infrastructure/auth/lucia.ts | 2 +- .../api/services/loginrequest.service.ts | 12 ++-- src/routes/(auth)/login/+page.server.ts | 57 +++++++++++-------- src/routes/(auth)/login/+page.svelte | 2 +- src/routes/(auth)/sign-up/+page.server.ts | 4 +- 7 files changed, 66 insertions(+), 41 deletions(-) rename src/lib/dtos/{signin-email.dto.ts => signin-username.dto.ts} (69%) diff --git a/src/lib/dtos/signin-email.dto.ts b/src/lib/dtos/signin-username.dto.ts similarity index 69% rename from src/lib/dtos/signin-email.dto.ts rename to src/lib/dtos/signin-username.dto.ts index 1ade43a..0ab4a3a 100644 --- a/src/lib/dtos/signin-email.dto.ts +++ b/src/lib/dtos/signin-username.dto.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -export const signInEmailDto = z.object({ +export const signinUsernameDto = z.object({ username: z .string() .trim() @@ -9,4 +9,4 @@ export const signInEmailDto = z.object({ password: z.string({ required_error: 'Password is required' }).trim(), }); -export type SignInEmailDto = z.infer; +export type SigninUsernameDto = z.infer; \ No newline at end of file diff --git a/src/lib/server/api/controllers/login.controller.ts b/src/lib/server/api/controllers/login.controller.ts index 996e98e..c86cefc 100644 --- a/src/lib/server/api/controllers/login.controller.ts +++ b/src/lib/server/api/controllers/login.controller.ts @@ -1,27 +1,43 @@ import 'reflect-metadata'; import { Hono } from 'hono'; +import { setCookie } from 'hono/cookie'; import { zValidator } from '@hono/zod-validator'; import { inject, injectable } from 'tsyringe'; +import { TimeSpan } from 'oslo'; import type { HonoTypes } from '../types'; import { limiter } from '../middleware/rate-limiter.middleware'; import type { Controller } from '../interfaces/controller.interface'; -import { signInEmailDto } from '$lib/dtos/signin-email.dto'; import { LoginRequestsService } from '../services/loginrequest.service'; +import { signinUsernameDto } from "$lib/dtos/signin-username.dto"; +import {LuciaProvider} from "$lib/server/api/providers"; @injectable() export class LoginController implements Controller { controller = new Hono(); constructor( - @inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService + @inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService, + @inject(LuciaProvider) private lucia: LuciaProvider ) { } routes() { return this.controller - .post('/', zValidator('json', signInEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + .post('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => { const { username, password } = c.req.valid('json'); - await this.loginRequestsService.verify({ username, password }, c.req); - return c.json({ message: 'Verification email sent' }); + const session = await this.loginRequestsService.verify({ username, password }, c.req); + const sessionCookie = this.lucia.createSessionCookie(session.id); + console.log("set cookie", sessionCookie); + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires + }); + return c.json({ message: 'ok' }); }) } } diff --git a/src/lib/server/api/infrastructure/auth/lucia.ts b/src/lib/server/api/infrastructure/auth/lucia.ts index a9873c1..712338d 100644 --- a/src/lib/server/api/infrastructure/auth/lucia.ts +++ b/src/lib/server/api/infrastructure/auth/lucia.ts @@ -21,7 +21,7 @@ export const lucia = new Lucia(adapter, { ...attributes, }; }, - sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days + sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks sessionCookie: { name: 'session', expires: false, // session cookies have very long lifespan (2 years) diff --git a/src/lib/server/api/services/loginrequest.service.ts b/src/lib/server/api/services/loginrequest.service.ts index f2138b3..eafaf28 100644 --- a/src/lib/server/api/services/loginrequest.service.ts +++ b/src/lib/server/api/services/loginrequest.service.ts @@ -5,9 +5,9 @@ import { MailerService } from './mailer.service'; import { TokensService } from './tokens.service'; import { LuciaProvider } from '../providers/lucia.provider'; import { UsersRepository } from '../repositories/users.repository'; -import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; import { CredentialsRepository } from '../repositories/credentials.repository'; import type { HonoRequest } from 'hono'; +import type {SigninUsernameDto} from "$lib/dtos/signin-username.dto"; @injectable() export class LoginRequestsService { @@ -32,7 +32,7 @@ export class LoginRequestsService { // }); // } - async verify(data: SignInEmailDto, req: HonoRequest) { + async verify(data: SigninUsernameDto, req: HonoRequest) { const requestIpAddress = req.header('x-real-ip'); const requestIpCountry = req.header('x-vercel-ip-country'); const existingUser = await this.usersRepository.findOneByUsername(data.username); @@ -47,7 +47,7 @@ export class LoginRequestsService { throw BadRequest('Invalid credentials'); } - if (!await this.tokensService.verifyHashedToken(credential.hashedPassword, data.password)) { + if (!await this.tokensService.verifyHashedToken(credential.secret_data, data.password)) { throw BadRequest('Invalid credentials'); } @@ -58,15 +58,15 @@ export class LoginRequestsService { ip_address: requestIpAddress || 'unknown', twoFactorAuthEnabled: !!totpCredentials && - totpCredentials?.secret !== null && - totpCredentials?.secret !== '', + totpCredentials?.secret_data !== null && + totpCredentials?.secret_data !== '', isTwoFactorAuthenticated: false, }); } // Create a new user and send a welcome email - or other onboarding process private async handleNewUserRegistration(email: string) { - const newUser = await this.usersRepository.create({ email, verified: true, avatar: null }) + const newUser = await this.usersRepository.create({ email, verified: true }) this.mailerService.sendWelcome({ to: email, props: null }); // TODO: add whatever onboarding process or extra data you need here return newUser diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index 5da474b..9c254cc 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -1,4 +1,4 @@ -import { fail, error, type Actions } from '@sveltejs/kit'; +import { fail, type Actions } from '@sveltejs/kit'; import { eq, or } from 'drizzle-orm'; import { Argon2id } from 'oslo/password'; import { zod } from 'sveltekit-superforms/adapters'; @@ -7,46 +7,57 @@ import { redirect } from 'sveltekit-flash-message/server'; import { RateLimiter } from 'sveltekit-rate-limiter/server'; import db from '../../../db'; import { lucia } from '$lib/server/auth'; -import { signInSchema } from '$lib/validations/auth'; import { twoFactor, usersTable, type Users } from '$db/schema'; import type { PageServerLoad } from './$types'; -import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; +import {signinUsernameDto} from "$lib/dtos/signin-username.dto"; export const load: PageServerLoad = async (event) => { - const { locals, cookies } = event; - const { user, session } = event.locals; + const { locals } = event; - if (userFullyAuthenticated(user, session)) { + const authedUser = await locals.getAuthedUser(); + + if (authedUser) { const message = { type: 'success', message: 'You are already signed in' } as const; throw redirect('/', message, event); - } else if (userNotFullyAuthenticated(user, session)) { - await lucia.invalidateSession(locals.session!.id!); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); } - const form = await superValidate(event, zod(signInSchema)); + + // if (userFullyAuthenticated(user, session)) { + // const message = { type: 'success', message: 'You are already signed in' } as const; + // throw redirect('/', message, event); + // } else if (userNotFullyAuthenticated(user, session)) { + // await lucia.invalidateSession(locals.session!.id!); + // const sessionCookie = lucia.createBlankSessionCookie(); + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }); + // } + const form = await superValidate(event, zod(signinUsernameDto)); return { form, }; }; -const limiter = new RateLimiter({ - // A rate is defined by [number, unit] - IPUA: [5, 'm'], -}); - export const actions: Actions = { default: async (event) => { - if (await limiter.isLimited(event)) { - throw error(429); - } + // if (await limiter.isLimited(event)) { + // throw error(429); + // } const { locals } = event; - const form = await superValidate(event, zod(signInSchema)); + + const authedUser = await locals.getAuthedUser(); + + if (authedUser) { + const message = { type: 'success', message: 'You are already signed in' } as const; + throw redirect('/', message, event); + } + + const form = await superValidate(event, zod(signinUsernameDto)); + + const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse); + if (error) return setError(form, 'username', error); if (!form.valid) { form.data.password = ''; diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 188b4c4..0239c9f 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -67,7 +67,7 @@
- Username/Email + Username diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/sign-up/+page.server.ts index b9db366..aa64cdf 100644 --- a/src/routes/(auth)/sign-up/+page.server.ts +++ b/src/routes/(auth)/sign-up/+page.server.ts @@ -12,7 +12,6 @@ import { add_user_to_role } from '$server/roles'; import db from '../../../db'; import { collections, usersTable, wishlists } from '$db/schema'; import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; const limiter = new RateLimiter({ // A rate is defined by [number, unit] @@ -30,8 +29,7 @@ const signUpDefaults = { }; export const load: PageServerLoad = async (event) => { - const { locals, cookies } = event; - const { user, session } = event.locals; + const { locals } = event; const authedUser = await locals.getAuthedUser(); From 60d0706d58a44d8c20a8d664a6690cc414b7d04e Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 15 Aug 2024 16:25:41 -0700 Subject: [PATCH 25/34] Deleting all the old DB folder data and slowly migrating each page. --- src/db/index.ts | 24 - src/db/migrate.ts | 26 - src/db/migrations/0000_dazzling_stick.sql | 410 ---- src/db/migrations/0001_noisy_sally_floyd.sql | 2 - src/db/migrations/0002_fancy_valkyrie.sql | 1 - src/db/migrations/meta/0000_snapshot.json | 1728 ----------------- src/db/migrations/meta/0001_snapshot.json | 1728 ----------------- src/db/migrations/meta/0002_snapshot.json | 1728 ----------------- src/db/migrations/meta/_journal.json | 27 - src/db/schema/categories.ts | 25 - src/db/schema/categoriesToExternalIds.ts | 39 - src/db/schema/categoriesToGames.ts | 36 - src/db/schema/collectionItems.ts | 36 - src/db/schema/collections.ts | 28 - src/db/schema/expansions.ts | 34 - src/db/schema/externalIds.ts | 25 - src/db/schema/games.ts | 53 - src/db/schema/gamesToExternalIds.ts | 24 - src/db/schema/index.ts | 36 - src/db/schema/mechanics.ts | 25 - src/db/schema/mechanicsToExternalIds.ts | 24 - src/db/schema/mechanicsToGames.ts | 36 - src/db/schema/passwordResetTokens.ts | 27 - src/db/schema/publishers.ts | 25 - src/db/schema/publishersToExternalIds.ts | 24 - src/db/schema/publishersToGames.ts | 36 - src/db/schema/recoveryCodes.ts | 18 - src/db/schema/roles.ts | 23 - src/db/schema/sessions.table.ts | 29 - src/db/schema/two-factor.table.ts | 34 - src/db/schema/userRoles.ts | 36 - src/db/schema/users.table.ts | 29 - src/db/schema/wishlistItems.ts | 35 - src/db/schema/wishlists.ts | 28 - src/db/seed.ts | 49 - src/db/seeds/data/roles.json | 14 - src/db/seeds/data/users.json | 62 - src/db/seeds/index.ts | 2 - src/db/seeds/roles.ts | 11 - src/db/seeds/users.ts | 98 - src/db/utils.ts | 43 - src/hooks.server.ts | 4 +- src/lib/dtos/signup-username-email.dto.ts | 4 +- .../api/controllers/signup.controller.ts | 101 +- .../server/api/infrastructure/auth/lucia.ts | 11 +- .../database/tables/credentials.table.ts | 5 +- .../server/api/middleware/auth.middleware.ts | 1 - .../api/services/loginrequest.service.ts | 25 +- src/lib/server/auth-utils.ts | 4 +- src/lib/server/auth.ts | 68 - src/lib/validations/auth.ts | 4 +- .../security/password/change/+page.server.ts | 28 +- .../security/two-factor/+page.server.ts | 34 +- src/routes/(app)/+layout.server.ts | 25 +- src/routes/(app)/+page.server.ts | 19 +- src/routes/(auth)/login/+page.server.ts | 167 +- src/routes/(auth)/sign-up/+page.server.ts | 168 +- src/routes/+layout.server.ts | 6 +- .../auth/reset-password/[token]/+server.ts | 2 +- 59 files changed, 291 insertions(+), 7103 deletions(-) delete mode 100644 src/db/index.ts delete mode 100644 src/db/migrate.ts delete mode 100644 src/db/migrations/0000_dazzling_stick.sql delete mode 100644 src/db/migrations/0001_noisy_sally_floyd.sql delete mode 100644 src/db/migrations/0002_fancy_valkyrie.sql delete mode 100644 src/db/migrations/meta/0000_snapshot.json delete mode 100644 src/db/migrations/meta/0001_snapshot.json delete mode 100644 src/db/migrations/meta/0002_snapshot.json delete mode 100644 src/db/migrations/meta/_journal.json delete mode 100644 src/db/schema/categories.ts delete mode 100644 src/db/schema/categoriesToExternalIds.ts delete mode 100644 src/db/schema/categoriesToGames.ts delete mode 100644 src/db/schema/collectionItems.ts delete mode 100644 src/db/schema/collections.ts delete mode 100644 src/db/schema/expansions.ts delete mode 100644 src/db/schema/externalIds.ts delete mode 100644 src/db/schema/games.ts delete mode 100644 src/db/schema/gamesToExternalIds.ts delete mode 100644 src/db/schema/index.ts delete mode 100644 src/db/schema/mechanics.ts delete mode 100644 src/db/schema/mechanicsToExternalIds.ts delete mode 100644 src/db/schema/mechanicsToGames.ts delete mode 100644 src/db/schema/passwordResetTokens.ts delete mode 100644 src/db/schema/publishers.ts delete mode 100644 src/db/schema/publishersToExternalIds.ts delete mode 100644 src/db/schema/publishersToGames.ts delete mode 100644 src/db/schema/recoveryCodes.ts delete mode 100644 src/db/schema/roles.ts delete mode 100644 src/db/schema/sessions.table.ts delete mode 100644 src/db/schema/two-factor.table.ts delete mode 100644 src/db/schema/userRoles.ts delete mode 100644 src/db/schema/users.table.ts delete mode 100644 src/db/schema/wishlistItems.ts delete mode 100644 src/db/schema/wishlists.ts delete mode 100644 src/db/seed.ts delete mode 100644 src/db/seeds/data/roles.json delete mode 100644 src/db/seeds/data/users.json delete mode 100644 src/db/seeds/index.ts delete mode 100644 src/db/seeds/roles.ts delete mode 100644 src/db/seeds/users.ts delete mode 100644 src/db/utils.ts delete mode 100644 src/lib/server/auth.ts diff --git a/src/db/index.ts b/src/db/index.ts deleted file mode 100644 index 8715a69..0000000 --- a/src/db/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { drizzle } from 'drizzle-orm/node-postgres'; -import pg from 'pg'; -import env from '../env'; -import * as schema from './schema'; - -// create the connection -export const pool = new pg.Pool({ - user: env.DATABASE_USER, - password: env.DATABASE_PASSWORD, - host: env.DATABASE_HOST, - port: Number(env.DATABASE_PORT).valueOf(), - database: env.DATABASE_DB, - ssl: env.DATABASE_HOST !== 'localhost', - max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined, -}); - -export const db = drizzle(pool, { - schema, - logger: env.NODE_ENV === 'development', -}); - -export type db = typeof db; - -export default db; diff --git a/src/db/migrate.ts b/src/db/migrate.ts deleted file mode 100644 index a1198be..0000000 --- a/src/db/migrate.ts +++ /dev/null @@ -1,26 +0,0 @@ -import 'dotenv/config'; -import postgres from 'postgres'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { migrate } from 'drizzle-orm/postgres-js/migrator'; -import env from '../env'; -import config from '../../drizzle.config'; - -const connection = postgres({ - host: env.DATABASE_HOST || 'localhost', - port: env.DATABASE_PORT, - user: env.DATABASE_USER || 'root', - password: env.DATABASE_PASSWORD || '', - database: env.DATABASE_DB || 'boredgame', - ssl: env.NODE_ENV === 'development' ? false : 'require', - max: 1, -}); -const db = drizzle(connection); - -try { - await migrate(db, { migrationsFolder: config.out! }); - console.log('Migrations complete'); -} catch (e) { - console.error(e); -} - -process.exit(); diff --git a/src/db/migrations/0000_dazzling_stick.sql b/src/db/migrations/0000_dazzling_stick.sql deleted file mode 100644 index d02362e..0000000 --- a/src/db/migrations/0000_dazzling_stick.sql +++ /dev/null @@ -1,410 +0,0 @@ -DO $$ BEGIN - CREATE TYPE "public"."external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist'); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "categories" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "name" text, - "slug" text, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "categories_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "categories_to_external_ids" ( - "category_id" uuid NOT NULL, - "external_id" uuid NOT NULL, - CONSTRAINT "categories_to_external_ids_category_id_external_id_pk" PRIMARY KEY("category_id","external_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "categories_to_games" ( - "category_id" uuid NOT NULL, - "game_id" uuid NOT NULL, - CONSTRAINT "categories_to_games_category_id_game_id_pk" PRIMARY KEY("category_id","game_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "collection_items" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "collection_id" uuid NOT NULL, - "game_id" uuid NOT NULL, - "times_played" integer DEFAULT 0, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "collection_items_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "collections" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "user_id" uuid NOT NULL, - "name" text DEFAULT 'My Collection' NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "collections_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "expansions" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "base_game_id" uuid NOT NULL, - "game_id" uuid NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "expansions_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "external_ids" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "type" "external_id_type", - "external_id" text NOT NULL, - CONSTRAINT "external_ids_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "games" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "name" text NOT NULL, - "slug" text NOT NULL, - "description" text, - "year_published" integer, - "min_players" integer, - "max_players" integer, - "playtime" integer, - "min_playtime" integer, - "max_playtime" integer, - "min_age" integer, - "image_url" text, - "thumb_url" text, - "url" text, - "last_sync_at" timestamp, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "games_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "games_to_external_ids" ( - "game_id" uuid NOT NULL, - "external_id" uuid NOT NULL, - CONSTRAINT "games_to_external_ids_game_id_external_id_pk" PRIMARY KEY("game_id","external_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "mechanics" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "name" text, - "slug" text, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "mechanics_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "mechanics_to_external_ids" ( - "mechanic_id" uuid NOT NULL, - "external_id" uuid NOT NULL, - CONSTRAINT "mechanics_to_external_ids_mechanic_id_external_id_pk" PRIMARY KEY("mechanic_id","external_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "mechanics_to_games" ( - "mechanic_id" uuid NOT NULL, - "game_id" uuid NOT NULL, - CONSTRAINT "mechanics_to_games_mechanic_id_game_id_pk" PRIMARY KEY("mechanic_id","game_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "password_reset_tokens" ( - "id" text PRIMARY KEY NOT NULL, - "user_id" uuid NOT NULL, - "expires_at" timestamp, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "publishers" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "name" text, - "slug" text, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "publishers_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "publishers_to_external_ids" ( - "publisher_id" uuid NOT NULL, - "external_id" uuid NOT NULL, - CONSTRAINT "publishers_to_external_ids_publisher_id_external_id_pk" PRIMARY KEY("publisher_id","external_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "publishers_to_games" ( - "publisher_id" uuid NOT NULL, - "game_id" uuid NOT NULL, - CONSTRAINT "publishers_to_games_publisher_id_game_id_pk" PRIMARY KEY("publisher_id","game_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "recovery_codes" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" uuid NOT NULL, - "code" text NOT NULL, - "used" boolean DEFAULT false, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "roles" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text NOT NULL, - "name" text NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "roles_cuid_unique" UNIQUE("cuid"), - CONSTRAINT "roles_name_unique" UNIQUE("name") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "sessions" ( - "id" text PRIMARY KEY NOT NULL, - "user_id" uuid NOT NULL, - "expires_at" timestamp with time zone NOT NULL, - "ip_country" text, - "ip_address" text, - "two_factor_auth_enabled" boolean DEFAULT false, - "is_two_factor_authenticated" boolean DEFAULT false -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "two_factor" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "two_factor_secret" text NOT NULL, - "two_factor_enabled" boolean DEFAULT false NOT NULL, - "initiated_time" timestamp with time zone NOT NULL, - "user_id" uuid NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "two_factor_cuid_unique" UNIQUE("cuid"), - CONSTRAINT "two_factor_user_id_unique" UNIQUE("user_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "user_roles" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "user_id" uuid NOT NULL, - "role_id" uuid NOT NULL, - "primary" boolean DEFAULT false, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "user_roles_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "users" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "username" text, - "hashed_password" text, - "email" text, - "first_name" text, - "last_name" text, - "verified" boolean DEFAULT false, - "receive_email" boolean DEFAULT false, - "theme" text DEFAULT 'system', - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "users_cuid_unique" UNIQUE("cuid"), - CONSTRAINT "users_username_unique" UNIQUE("username"), - CONSTRAINT "users_email_unique" UNIQUE("email") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "wishlist_items" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "wishlist_id" uuid NOT NULL, - "game_id" uuid NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "wishlist_items_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "wishlists" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "cuid" text, - "user_id" uuid NOT NULL, - "name" text DEFAULT 'My Wishlist' NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "wishlists_cuid_unique" UNIQUE("cuid") -); ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_collection_id_collections_id_fk" FOREIGN KEY ("collection_id") REFERENCES "public"."collections"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "collections" ADD CONSTRAINT "collections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "expansions" ADD CONSTRAINT "expansions_base_game_id_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "expansions" ADD CONSTRAINT "expansions_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "public"."mechanics"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "public"."mechanics"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "public"."publishers"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "public"."external_ids"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "public"."publishers"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE restrict ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "recovery_codes" ADD CONSTRAINT "recovery_codes_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 $$; ---> statement-breakpoint -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; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_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 $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_wishlist_id_wishlists_id_fk" FOREIGN KEY ("wishlist_id") REFERENCES "public"."wishlists"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "wishlists" ADD CONSTRAINT "wishlists_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS "search_index" ON "games" USING gin (( - setweight(to_tsvector('english', "name"), 'A') || - setweight(to_tsvector('english', "slug"), 'B') - )); \ No newline at end of file diff --git a/src/db/migrations/0001_noisy_sally_floyd.sql b/src/db/migrations/0001_noisy_sally_floyd.sql deleted file mode 100644 index 5809aa6..0000000 --- a/src/db/migrations/0001_noisy_sally_floyd.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "two_factor" RENAME COLUMN "two_factor_secret" TO "secret";--> statement-breakpoint -ALTER TABLE "two_factor" RENAME COLUMN "two_factor_enabled" TO "enabled"; \ No newline at end of file diff --git a/src/db/migrations/0002_fancy_valkyrie.sql b/src/db/migrations/0002_fancy_valkyrie.sql deleted file mode 100644 index e05cd36..0000000 --- a/src/db/migrations/0002_fancy_valkyrie.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "two_factor" ALTER COLUMN "initiated_time" DROP NOT NULL; \ No newline at end of file diff --git a/src/db/migrations/meta/0000_snapshot.json b/src/db/migrations/meta/0000_snapshot.json deleted file mode 100644 index 91e2a6e..0000000 --- a/src/db/migrations/meta/0000_snapshot.json +++ /dev/null @@ -1,1728 +0,0 @@ -{ - "id": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.categories": { - "name": "categories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "categories_cuid_unique": { - "name": "categories_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.categories_to_external_ids": { - "name": "categories_to_external_ids", - "schema": "", - "columns": { - "category_id": { - "name": "category_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "categories_to_external_ids_category_id_categories_id_fk": { - "name": "categories_to_external_ids_category_id_categories_id_fk", - "tableFrom": "categories_to_external_ids", - "tableTo": "categories", - "columnsFrom": [ - "category_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "categories_to_external_ids_external_id_external_ids_id_fk": { - "name": "categories_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "categories_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "categories_to_external_ids_category_id_external_id_pk": { - "name": "categories_to_external_ids_category_id_external_id_pk", - "columns": [ - "category_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.categories_to_games": { - "name": "categories_to_games", - "schema": "", - "columns": { - "category_id": { - "name": "category_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "categories_to_games_category_id_categories_id_fk": { - "name": "categories_to_games_category_id_categories_id_fk", - "tableFrom": "categories_to_games", - "tableTo": "categories", - "columnsFrom": [ - "category_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "categories_to_games_game_id_games_id_fk": { - "name": "categories_to_games_game_id_games_id_fk", - "tableFrom": "categories_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "categories_to_games_category_id_game_id_pk": { - "name": "categories_to_games_category_id_game_id_pk", - "columns": [ - "category_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.collection_items": { - "name": "collection_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "collection_id": { - "name": "collection_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "times_played": { - "name": "times_played", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "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": { - "collection_items_collection_id_collections_id_fk": { - "name": "collection_items_collection_id_collections_id_fk", - "tableFrom": "collection_items", - "tableTo": "collections", - "columnsFrom": [ - "collection_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "collection_items_game_id_games_id_fk": { - "name": "collection_items_game_id_games_id_fk", - "tableFrom": "collection_items", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "collection_items_cuid_unique": { - "name": "collection_items_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.collections": { - "name": "collections", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'My Collection'" - }, - "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": { - "collections_user_id_users_id_fk": { - "name": "collections_user_id_users_id_fk", - "tableFrom": "collections", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "collections_cuid_unique": { - "name": "collections_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.expansions": { - "name": "expansions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "base_game_id": { - "name": "base_game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "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": { - "expansions_base_game_id_games_id_fk": { - "name": "expansions_base_game_id_games_id_fk", - "tableFrom": "expansions", - "tableTo": "games", - "columnsFrom": [ - "base_game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "expansions_game_id_games_id_fk": { - "name": "expansions_game_id_games_id_fk", - "tableFrom": "expansions", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "expansions_cuid_unique": { - "name": "expansions_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.external_ids": { - "name": "external_ids", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "external_id_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "external_ids_cuid_unique": { - "name": "external_ids_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.games": { - "name": "games", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "year_published": { - "name": "year_published", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_players": { - "name": "min_players", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "max_players": { - "name": "max_players", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime": { - "name": "playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_playtime": { - "name": "min_playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "max_playtime": { - "name": "max_playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_age": { - "name": "min_age", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "image_url": { - "name": "image_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "thumb_url": { - "name": "thumb_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_sync_at": { - "name": "last_sync_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "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": { - "search_index": { - "name": "search_index", - "columns": [ - { - "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "games_cuid_unique": { - "name": "games_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.games_to_external_ids": { - "name": "games_to_external_ids", - "schema": "", - "columns": { - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "games_to_external_ids_game_id_games_id_fk": { - "name": "games_to_external_ids_game_id_games_id_fk", - "tableFrom": "games_to_external_ids", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "games_to_external_ids_external_id_external_ids_id_fk": { - "name": "games_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "games_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "games_to_external_ids_game_id_external_id_pk": { - "name": "games_to_external_ids_game_id_external_id_pk", - "columns": [ - "game_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.mechanics": { - "name": "mechanics", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "mechanics_cuid_unique": { - "name": "mechanics_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.mechanics_to_external_ids": { - "name": "mechanics_to_external_ids", - "schema": "", - "columns": { - "mechanic_id": { - "name": "mechanic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { - "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", - "tableFrom": "mechanics_to_external_ids", - "tableTo": "mechanics", - "columnsFrom": [ - "mechanic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "mechanics_to_external_ids_external_id_external_ids_id_fk": { - "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "mechanics_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "mechanics_to_external_ids_mechanic_id_external_id_pk": { - "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", - "columns": [ - "mechanic_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.mechanics_to_games": { - "name": "mechanics_to_games", - "schema": "", - "columns": { - "mechanic_id": { - "name": "mechanic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "mechanics_to_games_mechanic_id_mechanics_id_fk": { - "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", - "tableFrom": "mechanics_to_games", - "tableTo": "mechanics", - "columnsFrom": [ - "mechanic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "mechanics_to_games_game_id_games_id_fk": { - "name": "mechanics_to_games_game_id_games_id_fk", - "tableFrom": "mechanics_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "mechanics_to_games_mechanic_id_game_id_pk": { - "name": "mechanics_to_games_mechanic_id_game_id_pk", - "columns": [ - "mechanic_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.password_reset_tokens": { - "name": "password_reset_tokens", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "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": { - "password_reset_tokens_user_id_users_id_fk": { - "name": "password_reset_tokens_user_id_users_id_fk", - "tableFrom": "password_reset_tokens", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.publishers": { - "name": "publishers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "publishers_cuid_unique": { - "name": "publishers_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.publishers_to_external_ids": { - "name": "publishers_to_external_ids", - "schema": "", - "columns": { - "publisher_id": { - "name": "publisher_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "publishers_to_external_ids_publisher_id_publishers_id_fk": { - "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", - "tableFrom": "publishers_to_external_ids", - "tableTo": "publishers", - "columnsFrom": [ - "publisher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "publishers_to_external_ids_external_id_external_ids_id_fk": { - "name": "publishers_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "publishers_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "publishers_to_external_ids_publisher_id_external_id_pk": { - "name": "publishers_to_external_ids_publisher_id_external_id_pk", - "columns": [ - "publisher_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.publishers_to_games": { - "name": "publishers_to_games", - "schema": "", - "columns": { - "publisher_id": { - "name": "publisher_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "publishers_to_games_publisher_id_publishers_id_fk": { - "name": "publishers_to_games_publisher_id_publishers_id_fk", - "tableFrom": "publishers_to_games", - "tableTo": "publishers", - "columnsFrom": [ - "publisher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "publishers_to_games_game_id_games_id_fk": { - "name": "publishers_to_games_game_id_games_id_fk", - "tableFrom": "publishers_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "publishers_to_games_publisher_id_game_id_pk": { - "name": "publishers_to_games_publisher_id_game_id_pk", - "columns": [ - "publisher_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.recovery_codes": { - "name": "recovery_codes", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "used": { - "name": "used", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "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": { - "recovery_codes_user_id_users_id_fk": { - "name": "recovery_codes_user_id_users_id_fk", - "tableFrom": "recovery_codes", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "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": { - "roles_cuid_unique": { - "name": "roles_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - } - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "ip_country": { - "name": "ip_country", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "two_factor_auth_enabled": { - "name": "two_factor_auth_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "is_two_factor_authenticated": { - "name": "is_two_factor_authenticated", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.two_factor": { - "name": "two_factor", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "two_factor_secret": { - "name": "two_factor_secret", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "two_factor_enabled": { - "name": "two_factor_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "initiated_time": { - "name": "initiated_time", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "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": { - "two_factor_user_id_users_id_fk": { - "name": "two_factor_user_id_users_id_fk", - "tableFrom": "two_factor", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "two_factor_cuid_unique": { - "name": "two_factor_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "two_factor_user_id_unique": { - "name": "two_factor_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - } - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "primary": { - "name": "primary", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "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": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_roles_cuid_unique": { - "name": "user_roles_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "hashed_password": { - "name": "hashed_password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "verified": { - "name": "verified", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "receive_email": { - "name": "receive_email", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "theme": { - "name": "theme", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'system'" - }, - "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": { - "users_cuid_unique": { - "name": "users_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "users_username_unique": { - "name": "users_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - }, - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - } - }, - "public.wishlist_items": { - "name": "wishlist_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "wishlist_id": { - "name": "wishlist_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "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": { - "wishlist_items_wishlist_id_wishlists_id_fk": { - "name": "wishlist_items_wishlist_id_wishlists_id_fk", - "tableFrom": "wishlist_items", - "tableTo": "wishlists", - "columnsFrom": [ - "wishlist_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "wishlist_items_game_id_games_id_fk": { - "name": "wishlist_items_game_id_games_id_fk", - "tableFrom": "wishlist_items", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wishlist_items_cuid_unique": { - "name": "wishlist_items_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.wishlists": { - "name": "wishlists", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'My Wishlist'" - }, - "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": { - "wishlists_user_id_users_id_fk": { - "name": "wishlists_user_id_users_id_fk", - "tableFrom": "wishlists", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wishlists_cuid_unique": { - "name": "wishlists_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - } - }, - "enums": { - "public.external_id_type": { - "name": "external_id_type", - "schema": "public", - "values": [ - "game", - "category", - "mechanic", - "publisher", - "designer", - "artist" - ] - } - }, - "schemas": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/0001_snapshot.json b/src/db/migrations/meta/0001_snapshot.json deleted file mode 100644 index 4de88b6..0000000 --- a/src/db/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,1728 +0,0 @@ -{ - "id": "52e7c416-89cb-4c6a-9118-68a03cfc2920", - "prevId": "e120d11a-bf28-4c96-9f2f-96e23e23c7e2", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.categories": { - "name": "categories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "categories_cuid_unique": { - "name": "categories_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.categories_to_external_ids": { - "name": "categories_to_external_ids", - "schema": "", - "columns": { - "category_id": { - "name": "category_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "categories_to_external_ids_category_id_categories_id_fk": { - "name": "categories_to_external_ids_category_id_categories_id_fk", - "tableFrom": "categories_to_external_ids", - "tableTo": "categories", - "columnsFrom": [ - "category_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "categories_to_external_ids_external_id_external_ids_id_fk": { - "name": "categories_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "categories_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "categories_to_external_ids_category_id_external_id_pk": { - "name": "categories_to_external_ids_category_id_external_id_pk", - "columns": [ - "category_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.categories_to_games": { - "name": "categories_to_games", - "schema": "", - "columns": { - "category_id": { - "name": "category_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "categories_to_games_category_id_categories_id_fk": { - "name": "categories_to_games_category_id_categories_id_fk", - "tableFrom": "categories_to_games", - "tableTo": "categories", - "columnsFrom": [ - "category_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "categories_to_games_game_id_games_id_fk": { - "name": "categories_to_games_game_id_games_id_fk", - "tableFrom": "categories_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "categories_to_games_category_id_game_id_pk": { - "name": "categories_to_games_category_id_game_id_pk", - "columns": [ - "category_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.collection_items": { - "name": "collection_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "collection_id": { - "name": "collection_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "times_played": { - "name": "times_played", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "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": { - "collection_items_collection_id_collections_id_fk": { - "name": "collection_items_collection_id_collections_id_fk", - "tableFrom": "collection_items", - "tableTo": "collections", - "columnsFrom": [ - "collection_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "collection_items_game_id_games_id_fk": { - "name": "collection_items_game_id_games_id_fk", - "tableFrom": "collection_items", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "collection_items_cuid_unique": { - "name": "collection_items_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.collections": { - "name": "collections", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'My Collection'" - }, - "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": { - "collections_user_id_users_id_fk": { - "name": "collections_user_id_users_id_fk", - "tableFrom": "collections", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "collections_cuid_unique": { - "name": "collections_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.expansions": { - "name": "expansions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "base_game_id": { - "name": "base_game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "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": { - "expansions_base_game_id_games_id_fk": { - "name": "expansions_base_game_id_games_id_fk", - "tableFrom": "expansions", - "tableTo": "games", - "columnsFrom": [ - "base_game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "expansions_game_id_games_id_fk": { - "name": "expansions_game_id_games_id_fk", - "tableFrom": "expansions", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "expansions_cuid_unique": { - "name": "expansions_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.external_ids": { - "name": "external_ids", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "external_id_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "external_ids_cuid_unique": { - "name": "external_ids_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.games": { - "name": "games", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "year_published": { - "name": "year_published", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_players": { - "name": "min_players", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "max_players": { - "name": "max_players", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime": { - "name": "playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_playtime": { - "name": "min_playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "max_playtime": { - "name": "max_playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_age": { - "name": "min_age", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "image_url": { - "name": "image_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "thumb_url": { - "name": "thumb_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_sync_at": { - "name": "last_sync_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "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": { - "search_index": { - "name": "search_index", - "columns": [ - { - "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "games_cuid_unique": { - "name": "games_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.games_to_external_ids": { - "name": "games_to_external_ids", - "schema": "", - "columns": { - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "games_to_external_ids_game_id_games_id_fk": { - "name": "games_to_external_ids_game_id_games_id_fk", - "tableFrom": "games_to_external_ids", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "games_to_external_ids_external_id_external_ids_id_fk": { - "name": "games_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "games_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "games_to_external_ids_game_id_external_id_pk": { - "name": "games_to_external_ids_game_id_external_id_pk", - "columns": [ - "game_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.mechanics": { - "name": "mechanics", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "mechanics_cuid_unique": { - "name": "mechanics_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.mechanics_to_external_ids": { - "name": "mechanics_to_external_ids", - "schema": "", - "columns": { - "mechanic_id": { - "name": "mechanic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { - "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", - "tableFrom": "mechanics_to_external_ids", - "tableTo": "mechanics", - "columnsFrom": [ - "mechanic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "mechanics_to_external_ids_external_id_external_ids_id_fk": { - "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "mechanics_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "mechanics_to_external_ids_mechanic_id_external_id_pk": { - "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", - "columns": [ - "mechanic_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.mechanics_to_games": { - "name": "mechanics_to_games", - "schema": "", - "columns": { - "mechanic_id": { - "name": "mechanic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "mechanics_to_games_mechanic_id_mechanics_id_fk": { - "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", - "tableFrom": "mechanics_to_games", - "tableTo": "mechanics", - "columnsFrom": [ - "mechanic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "mechanics_to_games_game_id_games_id_fk": { - "name": "mechanics_to_games_game_id_games_id_fk", - "tableFrom": "mechanics_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "mechanics_to_games_mechanic_id_game_id_pk": { - "name": "mechanics_to_games_mechanic_id_game_id_pk", - "columns": [ - "mechanic_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.password_reset_tokens": { - "name": "password_reset_tokens", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "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": { - "password_reset_tokens_user_id_users_id_fk": { - "name": "password_reset_tokens_user_id_users_id_fk", - "tableFrom": "password_reset_tokens", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.publishers": { - "name": "publishers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "publishers_cuid_unique": { - "name": "publishers_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.publishers_to_external_ids": { - "name": "publishers_to_external_ids", - "schema": "", - "columns": { - "publisher_id": { - "name": "publisher_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "publishers_to_external_ids_publisher_id_publishers_id_fk": { - "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", - "tableFrom": "publishers_to_external_ids", - "tableTo": "publishers", - "columnsFrom": [ - "publisher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "publishers_to_external_ids_external_id_external_ids_id_fk": { - "name": "publishers_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "publishers_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "publishers_to_external_ids_publisher_id_external_id_pk": { - "name": "publishers_to_external_ids_publisher_id_external_id_pk", - "columns": [ - "publisher_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.publishers_to_games": { - "name": "publishers_to_games", - "schema": "", - "columns": { - "publisher_id": { - "name": "publisher_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "publishers_to_games_publisher_id_publishers_id_fk": { - "name": "publishers_to_games_publisher_id_publishers_id_fk", - "tableFrom": "publishers_to_games", - "tableTo": "publishers", - "columnsFrom": [ - "publisher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "publishers_to_games_game_id_games_id_fk": { - "name": "publishers_to_games_game_id_games_id_fk", - "tableFrom": "publishers_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "publishers_to_games_publisher_id_game_id_pk": { - "name": "publishers_to_games_publisher_id_game_id_pk", - "columns": [ - "publisher_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.recovery_codes": { - "name": "recovery_codes", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "used": { - "name": "used", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "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": { - "recovery_codes_user_id_users_id_fk": { - "name": "recovery_codes_user_id_users_id_fk", - "tableFrom": "recovery_codes", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "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": { - "roles_cuid_unique": { - "name": "roles_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - } - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "ip_country": { - "name": "ip_country", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "two_factor_auth_enabled": { - "name": "two_factor_auth_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "is_two_factor_authenticated": { - "name": "is_two_factor_authenticated", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.two_factor": { - "name": "two_factor", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "secret": { - "name": "secret", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "initiated_time": { - "name": "initiated_time", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "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": { - "two_factor_user_id_users_id_fk": { - "name": "two_factor_user_id_users_id_fk", - "tableFrom": "two_factor", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "two_factor_cuid_unique": { - "name": "two_factor_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "two_factor_user_id_unique": { - "name": "two_factor_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - } - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "primary": { - "name": "primary", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "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": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_roles_cuid_unique": { - "name": "user_roles_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "hashed_password": { - "name": "hashed_password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "verified": { - "name": "verified", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "receive_email": { - "name": "receive_email", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "theme": { - "name": "theme", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'system'" - }, - "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": { - "users_cuid_unique": { - "name": "users_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "users_username_unique": { - "name": "users_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - }, - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - } - }, - "public.wishlist_items": { - "name": "wishlist_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "wishlist_id": { - "name": "wishlist_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "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": { - "wishlist_items_wishlist_id_wishlists_id_fk": { - "name": "wishlist_items_wishlist_id_wishlists_id_fk", - "tableFrom": "wishlist_items", - "tableTo": "wishlists", - "columnsFrom": [ - "wishlist_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "wishlist_items_game_id_games_id_fk": { - "name": "wishlist_items_game_id_games_id_fk", - "tableFrom": "wishlist_items", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wishlist_items_cuid_unique": { - "name": "wishlist_items_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.wishlists": { - "name": "wishlists", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'My Wishlist'" - }, - "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": { - "wishlists_user_id_users_id_fk": { - "name": "wishlists_user_id_users_id_fk", - "tableFrom": "wishlists", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wishlists_cuid_unique": { - "name": "wishlists_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - } - }, - "enums": { - "public.external_id_type": { - "name": "external_id_type", - "schema": "public", - "values": [ - "game", - "category", - "mechanic", - "publisher", - "designer", - "artist" - ] - } - }, - "schemas": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/0002_snapshot.json b/src/db/migrations/meta/0002_snapshot.json deleted file mode 100644 index 8f50271..0000000 --- a/src/db/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,1728 +0,0 @@ -{ - "id": "79adee85-e57c-4a9f-87df-835457b68129", - "prevId": "52e7c416-89cb-4c6a-9118-68a03cfc2920", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.categories": { - "name": "categories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "categories_cuid_unique": { - "name": "categories_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.categories_to_external_ids": { - "name": "categories_to_external_ids", - "schema": "", - "columns": { - "category_id": { - "name": "category_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "categories_to_external_ids_category_id_categories_id_fk": { - "name": "categories_to_external_ids_category_id_categories_id_fk", - "tableFrom": "categories_to_external_ids", - "tableTo": "categories", - "columnsFrom": [ - "category_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "categories_to_external_ids_external_id_external_ids_id_fk": { - "name": "categories_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "categories_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "categories_to_external_ids_category_id_external_id_pk": { - "name": "categories_to_external_ids_category_id_external_id_pk", - "columns": [ - "category_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.categories_to_games": { - "name": "categories_to_games", - "schema": "", - "columns": { - "category_id": { - "name": "category_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "categories_to_games_category_id_categories_id_fk": { - "name": "categories_to_games_category_id_categories_id_fk", - "tableFrom": "categories_to_games", - "tableTo": "categories", - "columnsFrom": [ - "category_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "categories_to_games_game_id_games_id_fk": { - "name": "categories_to_games_game_id_games_id_fk", - "tableFrom": "categories_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "categories_to_games_category_id_game_id_pk": { - "name": "categories_to_games_category_id_game_id_pk", - "columns": [ - "category_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.collection_items": { - "name": "collection_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "collection_id": { - "name": "collection_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "times_played": { - "name": "times_played", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "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": { - "collection_items_collection_id_collections_id_fk": { - "name": "collection_items_collection_id_collections_id_fk", - "tableFrom": "collection_items", - "tableTo": "collections", - "columnsFrom": [ - "collection_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "collection_items_game_id_games_id_fk": { - "name": "collection_items_game_id_games_id_fk", - "tableFrom": "collection_items", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "collection_items_cuid_unique": { - "name": "collection_items_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.collections": { - "name": "collections", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'My Collection'" - }, - "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": { - "collections_user_id_users_id_fk": { - "name": "collections_user_id_users_id_fk", - "tableFrom": "collections", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "collections_cuid_unique": { - "name": "collections_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.expansions": { - "name": "expansions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "base_game_id": { - "name": "base_game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "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": { - "expansions_base_game_id_games_id_fk": { - "name": "expansions_base_game_id_games_id_fk", - "tableFrom": "expansions", - "tableTo": "games", - "columnsFrom": [ - "base_game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "expansions_game_id_games_id_fk": { - "name": "expansions_game_id_games_id_fk", - "tableFrom": "expansions", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "expansions_cuid_unique": { - "name": "expansions_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.external_ids": { - "name": "external_ids", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "external_id_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "external_ids_cuid_unique": { - "name": "external_ids_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.games": { - "name": "games", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "year_published": { - "name": "year_published", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_players": { - "name": "min_players", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "max_players": { - "name": "max_players", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime": { - "name": "playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_playtime": { - "name": "min_playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "max_playtime": { - "name": "max_playtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "min_age": { - "name": "min_age", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "image_url": { - "name": "image_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "thumb_url": { - "name": "thumb_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_sync_at": { - "name": "last_sync_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "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": { - "search_index": { - "name": "search_index", - "columns": [ - { - "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "games_cuid_unique": { - "name": "games_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.games_to_external_ids": { - "name": "games_to_external_ids", - "schema": "", - "columns": { - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "games_to_external_ids_game_id_games_id_fk": { - "name": "games_to_external_ids_game_id_games_id_fk", - "tableFrom": "games_to_external_ids", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "games_to_external_ids_external_id_external_ids_id_fk": { - "name": "games_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "games_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "games_to_external_ids_game_id_external_id_pk": { - "name": "games_to_external_ids_game_id_external_id_pk", - "columns": [ - "game_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.mechanics": { - "name": "mechanics", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "mechanics_cuid_unique": { - "name": "mechanics_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.mechanics_to_external_ids": { - "name": "mechanics_to_external_ids", - "schema": "", - "columns": { - "mechanic_id": { - "name": "mechanic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { - "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", - "tableFrom": "mechanics_to_external_ids", - "tableTo": "mechanics", - "columnsFrom": [ - "mechanic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "mechanics_to_external_ids_external_id_external_ids_id_fk": { - "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "mechanics_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "mechanics_to_external_ids_mechanic_id_external_id_pk": { - "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", - "columns": [ - "mechanic_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.mechanics_to_games": { - "name": "mechanics_to_games", - "schema": "", - "columns": { - "mechanic_id": { - "name": "mechanic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "mechanics_to_games_mechanic_id_mechanics_id_fk": { - "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", - "tableFrom": "mechanics_to_games", - "tableTo": "mechanics", - "columnsFrom": [ - "mechanic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "mechanics_to_games_game_id_games_id_fk": { - "name": "mechanics_to_games_game_id_games_id_fk", - "tableFrom": "mechanics_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "mechanics_to_games_mechanic_id_game_id_pk": { - "name": "mechanics_to_games_mechanic_id_game_id_pk", - "columns": [ - "mechanic_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.password_reset_tokens": { - "name": "password_reset_tokens", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "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": { - "password_reset_tokens_user_id_users_id_fk": { - "name": "password_reset_tokens_user_id_users_id_fk", - "tableFrom": "password_reset_tokens", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.publishers": { - "name": "publishers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "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": { - "publishers_cuid_unique": { - "name": "publishers_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.publishers_to_external_ids": { - "name": "publishers_to_external_ids", - "schema": "", - "columns": { - "publisher_id": { - "name": "publisher_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "external_id": { - "name": "external_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "publishers_to_external_ids_publisher_id_publishers_id_fk": { - "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", - "tableFrom": "publishers_to_external_ids", - "tableTo": "publishers", - "columnsFrom": [ - "publisher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "publishers_to_external_ids_external_id_external_ids_id_fk": { - "name": "publishers_to_external_ids_external_id_external_ids_id_fk", - "tableFrom": "publishers_to_external_ids", - "tableTo": "external_ids", - "columnsFrom": [ - "external_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "publishers_to_external_ids_publisher_id_external_id_pk": { - "name": "publishers_to_external_ids_publisher_id_external_id_pk", - "columns": [ - "publisher_id", - "external_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.publishers_to_games": { - "name": "publishers_to_games", - "schema": "", - "columns": { - "publisher_id": { - "name": "publisher_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "publishers_to_games_publisher_id_publishers_id_fk": { - "name": "publishers_to_games_publisher_id_publishers_id_fk", - "tableFrom": "publishers_to_games", - "tableTo": "publishers", - "columnsFrom": [ - "publisher_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - }, - "publishers_to_games_game_id_games_id_fk": { - "name": "publishers_to_games_game_id_games_id_fk", - "tableFrom": "publishers_to_games", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "publishers_to_games_publisher_id_game_id_pk": { - "name": "publishers_to_games_publisher_id_game_id_pk", - "columns": [ - "publisher_id", - "game_id" - ] - } - }, - "uniqueConstraints": {} - }, - "public.recovery_codes": { - "name": "recovery_codes", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "used": { - "name": "used", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "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": { - "recovery_codes_user_id_users_id_fk": { - "name": "recovery_codes_user_id_users_id_fk", - "tableFrom": "recovery_codes", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "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": { - "roles_cuid_unique": { - "name": "roles_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - } - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "ip_country": { - "name": "ip_country", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "two_factor_auth_enabled": { - "name": "two_factor_auth_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "is_two_factor_authenticated": { - "name": "is_two_factor_authenticated", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.two_factor": { - "name": "two_factor", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "secret": { - "name": "secret", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "initiated_time": { - "name": "initiated_time", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "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": { - "two_factor_user_id_users_id_fk": { - "name": "two_factor_user_id_users_id_fk", - "tableFrom": "two_factor", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "two_factor_cuid_unique": { - "name": "two_factor_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "two_factor_user_id_unique": { - "name": "two_factor_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - } - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "primary": { - "name": "primary", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "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": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_roles_cuid_unique": { - "name": "user_roles_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "hashed_password": { - "name": "hashed_password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "verified": { - "name": "verified", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "receive_email": { - "name": "receive_email", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "theme": { - "name": "theme", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'system'" - }, - "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": { - "users_cuid_unique": { - "name": "users_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - }, - "users_username_unique": { - "name": "users_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - }, - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - } - }, - "public.wishlist_items": { - "name": "wishlist_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "wishlist_id": { - "name": "wishlist_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "uuid", - "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": { - "wishlist_items_wishlist_id_wishlists_id_fk": { - "name": "wishlist_items_wishlist_id_wishlists_id_fk", - "tableFrom": "wishlist_items", - "tableTo": "wishlists", - "columnsFrom": [ - "wishlist_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "wishlist_items_game_id_games_id_fk": { - "name": "wishlist_items_game_id_games_id_fk", - "tableFrom": "wishlist_items", - "tableTo": "games", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wishlist_items_cuid_unique": { - "name": "wishlist_items_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - }, - "public.wishlists": { - "name": "wishlists", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "cuid": { - "name": "cuid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'My Wishlist'" - }, - "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": { - "wishlists_user_id_users_id_fk": { - "name": "wishlists_user_id_users_id_fk", - "tableFrom": "wishlists", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wishlists_cuid_unique": { - "name": "wishlists_cuid_unique", - "nullsNotDistinct": false, - "columns": [ - "cuid" - ] - } - } - } - }, - "enums": { - "public.external_id_type": { - "name": "external_id_type", - "schema": "public", - "values": [ - "game", - "category", - "mechanic", - "publisher", - "designer", - "artist" - ] - } - }, - "schemas": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json deleted file mode 100644 index 18ded62..0000000 --- a/src/db/migrations/meta/_journal.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1720625651245, - "tag": "0000_dazzling_stick", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1720625948784, - "tag": "0001_noisy_sally_floyd", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1720626020902, - "tag": "0002_fancy_valkyrie", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/src/db/schema/categories.ts b/src/db/schema/categories.ts deleted file mode 100644 index 5936858..0000000 --- a/src/db/schema/categories.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import categoriesToExternalIds from './categoriesToExternalIds'; -import categories_to_games from './categoriesToGames'; -import { timestamps } from '../utils'; - -const categories = pgTable('categories', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name'), - slug: text('slug'), - ...timestamps, -}); - -export type Categories = InferSelectModel; - -export const categories_relations = relations(categories, ({ many }) => ({ - categories_to_games: many(categories_to_games), - categoriesToExternalIds: many(categoriesToExternalIds), -})); - -export default categories; diff --git a/src/db/schema/categoriesToExternalIds.ts b/src/db/schema/categoriesToExternalIds.ts deleted file mode 100644 index 352b732..0000000 --- a/src/db/schema/categoriesToExternalIds.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import categories from './categories'; -import externalIds from './externalIds'; -import { relations } from 'drizzle-orm'; - -const categoriesToExternalIds = pgTable( - 'categories_to_external_ids', - { - categoryId: uuid('category_id') - .notNull() - .references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - externalId: uuid('external_id') - .notNull() - .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - categoriesToExternalIdsPkey: primaryKey({ - columns: [table.categoryId, table.externalId], - }), - }; - }, -); - -export const categoriesToExternalIdsRelations = relations( - categoriesToExternalIds, - ({ one }) => ({ - category: one(categories, { - fields: [categoriesToExternalIds.categoryId], - references: [categories.id], - }), - externalId: one(externalIds, { - fields: [categoriesToExternalIds.externalId], - references: [externalIds.id], - }), - }), -); - -export default categoriesToExternalIds; diff --git a/src/db/schema/categoriesToGames.ts b/src/db/schema/categoriesToGames.ts deleted file mode 100644 index 7afb8e2..0000000 --- a/src/db/schema/categoriesToGames.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import { relations } from 'drizzle-orm'; -import categories from './categories'; -import games from './games'; - -const categories_to_games = pgTable( - 'categories_to_games', - { - category_id: uuid('category_id') - .notNull() - .references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - game_id: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - categoriesToGamesPkey: primaryKey({ - columns: [table.category_id, table.game_id], - }), - }; - }, -); - -export const categories_to_games_relations = relations(categories_to_games, ({ one }) => ({ - category: one(categories, { - fields: [categories_to_games.category_id], - references: [categories.id], - }), - game: one(games, { - fields: [categories_to_games.game_id], - references: [games.id], - }), -})); - -export default categories_to_games; diff --git a/src/db/schema/collectionItems.ts b/src/db/schema/collectionItems.ts deleted file mode 100644 index cf1805c..0000000 --- a/src/db/schema/collectionItems.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import collections from './collections'; -import games from './games'; -import { timestamps } from '../utils'; - -const collection_items = pgTable('collection_items', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - collection_id: uuid('collection_id') - .notNull() - .references(() => collections.id, { onDelete: 'cascade' }), - game_id: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'cascade' }), - times_played: integer('times_played').default(0), - ...timestamps, -}); - -export type CollectionItems = InferSelectModel; - -export const collection_item_relations = relations(collection_items, ({ one }) => ({ - collection: one(collections, { - fields: [collection_items.collection_id], - references: [collections.id], - }), - game: one(games, { - fields: [collection_items.game_id], - references: [games.id], - }), -})); - -export default collection_items; diff --git a/src/db/schema/collections.ts b/src/db/schema/collections.ts deleted file mode 100644 index 1a56443..0000000 --- a/src/db/schema/collections.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; -import { timestamps } from '../utils'; - -const collections = pgTable('collections', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - user_id: uuid('user_id') - .notNull() - .references(() => usersTable.id, { onDelete: 'cascade' }), - name: text('name').notNull().default('My Collection'), - ...timestamps, -}); - -export const collection_relations = relations(collections, ({ one }) => ({ - user: one(usersTable, { - fields: [collections.user_id], - references: [usersTable.id], - }), -})); - -export type Collections = InferSelectModel; - -export default collections; diff --git a/src/db/schema/expansions.ts b/src/db/schema/expansions.ts deleted file mode 100644 index bdbf2e3..0000000 --- a/src/db/schema/expansions.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import games from './games'; -import { timestamps } from '../utils'; - -export const expansions = pgTable('expansions', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - base_game_id: uuid('base_game_id') - .notNull() - .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - game_id: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - ...timestamps, -}); - -export type Expansions = InferSelectModel; - -export const expansion_relations = relations(expansions, ({ one }) => ({ - baseGame: one(games, { - fields: [expansions.base_game_id], - references: [games.id], - }), - game: one(games, { - fields: [expansions.game_id], - references: [games.id], - }), -})); - -export default expansions; diff --git a/src/db/schema/externalIds.ts b/src/db/schema/externalIds.ts deleted file mode 100644 index 5d0a481..0000000 --- a/src/db/schema/externalIds.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { pgEnum, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import type { InferSelectModel } from 'drizzle-orm'; - -export const externalIdType = pgEnum('external_id_type', [ - 'game', - 'category', - 'mechanic', - 'publisher', - 'designer', - 'artist', -]); - -const externalIds = pgTable('external_ids', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - type: externalIdType('type'), - externalId: text('external_id').notNull(), -}); - -export type ExternalIds = InferSelectModel; - -export default externalIds; diff --git a/src/db/schema/games.ts b/src/db/schema/games.ts deleted file mode 100644 index 91d5cfa..0000000 --- a/src/db/schema/games.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations, sql } from 'drizzle-orm'; -import categoriesToGames from './categoriesToGames'; -import gamesToExternalIds from './gamesToExternalIds'; -import mechanicsToGames from './mechanicsToGames'; -import publishersToGames from './publishersToGames'; -import { timestamps } from '../utils'; - -const games = pgTable( - 'games', - { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name').notNull(), - slug: text('slug').notNull(), - description: text('description'), - year_published: integer('year_published'), - min_players: integer('min_players'), - max_players: integer('max_players'), - playtime: integer('playtime'), - min_playtime: integer('min_playtime'), - max_playtime: integer('max_playtime'), - min_age: integer('min_age'), - image_url: text('image_url'), - thumb_url: text('thumb_url'), - url: text('url'), - last_sync_at: timestamp('last_sync_at'), - ...timestamps, - }, - (table) => ({ - searchIndex: index('search_index').using( - 'gin', - sql`( - setweight(to_tsvector('english', ${table.name}), 'A') || - setweight(to_tsvector('english', ${table.slug}), 'B') - )`, - ), - }), -); - -export const gameRelations = relations(games, ({ many }) => ({ - categories_to_games: many(categoriesToGames), - mechanics_to_games: many(mechanicsToGames), - publishers_to_games: many(publishersToGames), - gamesToExternalIds: many(gamesToExternalIds), -})); - -export type Games = InferSelectModel; - -export default games; diff --git a/src/db/schema/gamesToExternalIds.ts b/src/db/schema/gamesToExternalIds.ts deleted file mode 100644 index 66edec9..0000000 --- a/src/db/schema/gamesToExternalIds.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import games from './games'; -import externalIds from './externalIds'; - -const gamesToExternalIds = pgTable( - 'games_to_external_ids', - { - gameId: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - externalId: uuid('external_id') - .notNull() - .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - gamesToExternalIdsPkey: primaryKey({ - columns: [table.gameId, table.externalId], - }), - }; - }, -); - -export default gamesToExternalIds; diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts deleted file mode 100644 index e7bf9ff..0000000 --- a/src/db/schema/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export { default as usersTable, userRelations as user_relations, type Users } from './users.table'; -export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes'; -export { - default as password_reset_tokens, - password_reset_token_relations, - type PasswordResetTokens, -} from './passwordResetTokens'; -export { default as sessionsTable, type Sessions } from './sessions.table'; -export { default as roles, role_relations, type Roles } from './roles'; -export { default as userRoles, user_role_relations, type UserRoles } from './userRoles'; -export { default as collections, collection_relations, type Collections } from './collections'; -export { - default as collection_items, - collection_item_relations, - type CollectionItems, -} from './collectionItems'; -export { default as wishlists, wishlists_relations, type Wishlists } from './wishlists'; -export { - default as wishlist_items, - wishlist_item_relations, - type WishlistItems, -} from './wishlistItems'; -export { default as externalIds, type ExternalIds, externalIdType } from './externalIds'; -export { default as games, gameRelations, type Games } from './games'; -export { default as gamesToExternalIds } from './gamesToExternalIds'; -export { default as expansions, expansion_relations, type Expansions } from './expansions'; -export { default as publishers, publishers_relations, type Publishers } from './publishers'; -export { default as publishers_to_games, publishers_to_games_relations } from './publishersToGames'; -export { default as publishersToExternalIds } from './publishersToExternalIds'; -export { default as categories, categories_relations, type Categories } from './categories'; -export { default as categoriesToExternalIds } from './categoriesToExternalIds'; -export { default as categories_to_games, categories_to_games_relations } from './categoriesToGames'; -export { default as mechanics, mechanics_relations, type Mechanics } from './mechanics'; -export { default as mechanicsToExternalIds } from './mechanicsToExternalIds'; -export { default as mechanics_to_games, mechanics_to_games_relations } from './mechanicsToGames'; -export { default as twoFactor } from './two-factor.table'; diff --git a/src/db/schema/mechanics.ts b/src/db/schema/mechanics.ts deleted file mode 100644 index 3a80fb0..0000000 --- a/src/db/schema/mechanics.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import mechanicsToGames from './mechanicsToGames'; -import mechanicsToExternalIds from './mechanicsToExternalIds'; -import { timestamps } from '../utils'; - -const mechanics = pgTable('mechanics', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name'), - slug: text('slug'), - ...timestamps, -}); - -export type Mechanics = InferSelectModel; - -export const mechanics_relations = relations(mechanics, ({ many }) => ({ - mechanics_to_games: many(mechanicsToGames), - mechanicsToExternalIds: many(mechanicsToExternalIds), -})); - -export default mechanics; diff --git a/src/db/schema/mechanicsToExternalIds.ts b/src/db/schema/mechanicsToExternalIds.ts deleted file mode 100644 index 8fab637..0000000 --- a/src/db/schema/mechanicsToExternalIds.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import mechanics from './mechanics'; -import externalIds from './externalIds'; - -const mechanicsToExternalIds = pgTable( - 'mechanics_to_external_ids', - { - mechanicId: uuid('mechanic_id') - .notNull() - .references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - externalId: uuid('external_id') - .notNull() - .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - mechanicsToExternalIdsPkey: primaryKey({ - columns: [table.mechanicId, table.externalId], - }), - }; - }, -); - -export default mechanicsToExternalIds; diff --git a/src/db/schema/mechanicsToGames.ts b/src/db/schema/mechanicsToGames.ts deleted file mode 100644 index aa549c4..0000000 --- a/src/db/schema/mechanicsToGames.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import { relations } from 'drizzle-orm'; -import mechanics from './mechanics'; -import games from './games'; - -const mechanics_to_games = pgTable( - 'mechanics_to_games', - { - mechanic_id: uuid('mechanic_id') - .notNull() - .references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - game_id: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - mechanicsToGamesPkey: primaryKey({ - columns: [table.mechanic_id, table.game_id], - }), - }; - }, -); - -export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({ - mechanic: one(mechanics, { - fields: [mechanics_to_games.mechanic_id], - references: [mechanics.id], - }), - game: one(games, { - fields: [mechanics_to_games.game_id], - references: [games.id], - }), -})); - -export default mechanics_to_games; diff --git a/src/db/schema/passwordResetTokens.ts b/src/db/schema/passwordResetTokens.ts deleted file mode 100644 index a849bc1..0000000 --- a/src/db/schema/passwordResetTokens.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; -import { timestamps } from '../utils'; - -const password_reset_tokens = pgTable('password_reset_tokens', { - id: text('id') - .primaryKey() - .$defaultFn(() => cuid2()), - user_id: uuid('user_id') - .notNull() - .references(() => usersTable.id, { onDelete: 'cascade' }), - expires_at: timestamp('expires_at'), - ...timestamps, -}); - -export type PasswordResetTokens = InferSelectModel; - -export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({ - user: one(usersTable, { - fields: [password_reset_tokens.user_id], - references: [usersTable.id], - }), -})); - -export default password_reset_tokens; diff --git a/src/db/schema/publishers.ts b/src/db/schema/publishers.ts deleted file mode 100644 index b83a3ef..0000000 --- a/src/db/schema/publishers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import publishers_to_games from './publishersToGames'; -import publishersToExternalIds from './publishersToExternalIds'; -import { timestamps } from '../utils'; - -const publishers = pgTable('publishers', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name'), - slug: text('slug'), - ...timestamps, -}); - -export type Publishers = InferSelectModel; - -export const publishers_relations = relations(publishers, ({ many }) => ({ - publishers_to_games: many(publishers_to_games), - publishersToExternalIds: many(publishersToExternalIds), -})); - -export default publishers; diff --git a/src/db/schema/publishersToExternalIds.ts b/src/db/schema/publishersToExternalIds.ts deleted file mode 100644 index 898e6da..0000000 --- a/src/db/schema/publishersToExternalIds.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import publishers from './publishers'; -import externalIds from './externalIds'; - -const publishersToExternalIds = pgTable( - 'publishers_to_external_ids', - { - publisherId: uuid('publisher_id') - .notNull() - .references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - externalId: uuid('external_id') - .notNull() - .references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - publishersToExternalIdsPkey: primaryKey({ - columns: [table.publisherId, table.externalId], - }), - }; - }, -); - -export default publishersToExternalIds; diff --git a/src/db/schema/publishersToGames.ts b/src/db/schema/publishersToGames.ts deleted file mode 100644 index 86de77d..0000000 --- a/src/db/schema/publishersToGames.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core'; -import { relations } from 'drizzle-orm'; -import publishers from './publishers'; -import games from './games'; - -const publishers_to_games = pgTable( - 'publishers_to_games', - { - publisher_id: uuid('publisher_id') - .notNull() - .references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - game_id: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), - }, - (table) => { - return { - publishersToGamesPkey: primaryKey({ - columns: [table.publisher_id, table.game_id], - }), - }; - }, -); - -export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({ - publisher: one(publishers, { - fields: [publishers_to_games.publisher_id], - references: [publishers.id], - }), - game: one(games, { - fields: [publishers_to_games.game_id], - references: [games.id], - }), -})); - -export default publishers_to_games; diff --git a/src/db/schema/recoveryCodes.ts b/src/db/schema/recoveryCodes.ts deleted file mode 100644 index b63c167..0000000 --- a/src/db/schema/recoveryCodes.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import type { InferSelectModel } from 'drizzle-orm'; -import usersTable from './users.table'; -import { timestamps } from '../utils'; - -const recovery_codes = pgTable('recovery_codes', { - id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id') - .notNull() - .references(() => usersTable.id), - code: text('code').notNull(), - used: boolean('used').default(false), - ...timestamps, -}); - -export type RecoveryCodes = InferSelectModel; - -export default recovery_codes; diff --git a/src/db/schema/roles.ts b/src/db/schema/roles.ts deleted file mode 100644 index 43768b0..0000000 --- a/src/db/schema/roles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import user_roles from './userRoles'; -import { timestamps } from '../utils'; - -const roles = pgTable('roles', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()) - .notNull(), - name: text('name').unique().notNull(), - ...timestamps, -}); - -export type Roles = InferSelectModel; - -export const role_relations = relations(roles, ({ many }) => ({ - user_roles: many(user_roles), -})); - -export default roles; diff --git a/src/db/schema/sessions.table.ts b/src/db/schema/sessions.table.ts deleted file mode 100644 index 813b2b8..0000000 --- a/src/db/schema/sessions.table.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { relations, type InferSelectModel } from 'drizzle-orm'; -import usersTable from './users.table'; - -const sessionsTable = pgTable('sessions', { - id: text('id').primaryKey(), - userId: uuid('user_id') - .notNull() - .references(() => usersTable.id), - expiresAt: timestamp('expires_at', { - withTimezone: true, - mode: 'date', - }).notNull(), - ipCountry: text('ip_country'), - ipAddress: text('ip_address'), - twoFactorAuthEnabled: boolean('two_factor_auth_enabled').default(false), - isTwoFactorAuthenticated: boolean('is_two_factor_authenticated').default(false), -}); - -export const sessionsRelations = relations(sessionsTable, ({ one }) => ({ - user: one(usersTable, { - fields: [sessionsTable.userId], - references: [usersTable.id], - }) -})); - -export type Sessions = InferSelectModel; - -export default sessionsTable; diff --git a/src/db/schema/two-factor.table.ts b/src/db/schema/two-factor.table.ts deleted file mode 100644 index d488c1d..0000000 --- a/src/db/schema/two-factor.table.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { timestamps } from '../utils'; -import usersTable from './users.table'; - -const twoFactorTable = pgTable('two_factor', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - secret: text('secret').notNull(), - enabled: boolean('enabled').notNull().default(false), - initiatedTime: timestamp('initiated_time', { - mode: 'date', - withTimezone: true, - }), - userId: uuid('user_id') - .notNull() - .references(() => usersTable.id) - .unique(), - ...timestamps, -}); - -export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({ - user: one(usersTable, { - fields: [twoFactorTable.userId], - references: [usersTable.id], - }), -})); - -export type TwoFactor = InferSelectModel; - -export default twoFactorTable; diff --git a/src/db/schema/userRoles.ts b/src/db/schema/userRoles.ts deleted file mode 100644 index d241788..0000000 --- a/src/db/schema/userRoles.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; -import roles from './roles'; -import { timestamps } from '../utils'; - -const user_roles = pgTable('user_roles', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - user_id: uuid('user_id') - .notNull() - .references(() => usersTable.id, { onDelete: 'cascade' }), - role_id: uuid('role_id') - .notNull() - .references(() => roles.id, { onDelete: 'cascade' }), - primary: boolean('primary').default(false), - ...timestamps, -}); - -export const user_role_relations = relations(user_roles, ({ one }) => ({ - role: one(roles, { - fields: [user_roles.role_id], - references: [roles.id], - }), - user: one(usersTable, { - fields: [user_roles.user_id], - references: [usersTable.id], - }), -})); - -export type UserRoles = InferSelectModel; - -export default user_roles; diff --git a/src/db/schema/users.table.ts b/src/db/schema/users.table.ts deleted file mode 100644 index e3e40cb..0000000 --- a/src/db/schema/users.table.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { timestamps } from '../utils'; -import user_roles from './userRoles'; - -const usersTable = pgTable('users', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - username: text('username').unique(), - hashed_password: text('hashed_password'), - email: text('email').unique(), - first_name: text('first_name'), - last_name: text('last_name'), - verified: boolean('verified').default(false), - receive_email: boolean('receive_email').default(false), - theme: text('theme').default('system'), - ...timestamps, -}); - -export const userRelations = relations(usersTable, ({ many }) => ({ - user_roles: many(user_roles), -})); - -export type Users = InferSelectModel; - -export default usersTable; diff --git a/src/db/schema/wishlistItems.ts b/src/db/schema/wishlistItems.ts deleted file mode 100644 index ac4bc61..0000000 --- a/src/db/schema/wishlistItems.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import wishlists from './wishlists'; -import games from './games'; -import { timestamps } from '../utils'; - -const wishlist_items = pgTable('wishlist_items', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - wishlist_id: uuid('wishlist_id') - .notNull() - .references(() => wishlists.id, { onDelete: 'cascade' }), - game_id: uuid('game_id') - .notNull() - .references(() => games.id, { onDelete: 'cascade' }), - ...timestamps, -}); - -export type WishlistItems = InferSelectModel; - -export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({ - wishlist: one(wishlists, { - fields: [wishlist_items.wishlist_id], - references: [wishlists.id], - }), - game: one(games, { - fields: [wishlist_items.game_id], - references: [games.id], - }), -})); - -export default wishlist_items; diff --git a/src/db/schema/wishlists.ts b/src/db/schema/wishlists.ts deleted file mode 100644 index 673308a..0000000 --- a/src/db/schema/wishlists.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import usersTable from './users.table'; -import { timestamps } from '../utils'; - -const wishlists = pgTable('wishlists', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - user_id: uuid('user_id') - .notNull() - .references(() => usersTable.id, { onDelete: 'cascade' }), - name: text('name').notNull().default('My Wishlist'), - ...timestamps, -}); - -export type Wishlists = InferSelectModel; - -export const wishlists_relations = relations(wishlists, ({ one }) => ({ - user: one(usersTable, { - fields: [wishlists.user_id], - references: [usersTable.id], - }), -})); - -export default wishlists; diff --git a/src/db/seed.ts b/src/db/seed.ts deleted file mode 100644 index 7220575..0000000 --- a/src/db/seed.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Table, getTableName, sql } from 'drizzle-orm'; -import env from '../env'; -import { db, pool } from '$db'; -import * as schema from './schema'; -import * as seeds from './seeds'; - -if (!env.DB_SEEDING) { - throw new Error('You must set DB_SEEDING to "true" when running seeds'); -} - -async function resetTable(db: db, table: Table) { - return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`)); -} - -for (const table of [ - schema.categories, - schema.categoriesToExternalIds, - schema.categories_to_games, - schema.collection_items, - schema.collections, - schema.expansions, - schema.externalIds, - schema.games, - schema.gamesToExternalIds, - schema.mechanics, - schema.mechanicsToExternalIds, - schema.mechanics_to_games, - schema.password_reset_tokens, - schema.publishers, - schema.publishersToExternalIds, - schema.publishers_to_games, - schema.recoveryCodes, - schema.roles, - schema.sessionsTable, - schema.userRoles, - schema.usersTable, - schema.twoFactor, - schema.wishlists, - schema.wishlist_items, -]) { - // await db.delete(table); // clear tables without truncating / resetting ids - await resetTable(db, table); -} - -await seeds.roles(db); -await seeds.users(db); - -await pool.end(); -process.exit(); diff --git a/src/db/seeds/data/roles.json b/src/db/seeds/data/roles.json deleted file mode 100644 index 3209c58..0000000 --- a/src/db/seeds/data/roles.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "name": "admin" - }, - { - "name": "user" - }, - { - "name": "editor" - }, - { - "name": "moderator" - } -] \ No newline at end of file diff --git a/src/db/seeds/data/users.json b/src/db/seeds/data/users.json deleted file mode 100644 index 61fa25d..0000000 --- a/src/db/seeds/data/users.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "first_name": "John", - "last_name": "Smith", - "username": "john.smith", - "email": "john.smith@example.com", - "password": "password", - "roles": [ - { - "name": "user", - "primary": true - } - ] - }, - { - "first_name": "Jane", - "last_name": "Doe", - "username": "jane.doe", - "email": "jane.doe@example.com", - "password": "password", - "roles": [ - { - "name": "user", - "primary": true - } - ] - }, - { - "first_name": "Michael", - "last_name": "Editor", - "username": "michael.editor", - "email": "michael.editor@example.com", - "password": "password", - "roles": [ - { - "name": "editor", - "primary": true - }, - { - "name": "user", - "primary": false - } - ] - }, - { - "first_name": "Jane", - "last_name": "Moderator", - "username": "jane.moderator", - "email": "jane.moderator@example.com", - "password": "password", - "roles": [ - { - "name": "moderator", - "primary": true - }, - { - "name": "user", - "primary": false - } - ] - } -] \ No newline at end of file diff --git a/src/db/seeds/index.ts b/src/db/seeds/index.ts deleted file mode 100644 index 0a05205..0000000 --- a/src/db/seeds/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as users } from './users'; -export { default as roles } from './roles'; diff --git a/src/db/seeds/roles.ts b/src/db/seeds/roles.ts deleted file mode 100644 index 46b94e0..0000000 --- a/src/db/seeds/roles.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type db } from '$db'; -import * as schema from '$db/schema'; -import roles from './data/roles.json'; - -export default async function seed(db: db) { - console.log('Creating roles ...'); - for (const role of roles) { - await db.insert(schema.roles).values(role).onConflictDoNothing(); - } - console.log('Roles created.'); -} diff --git a/src/db/seeds/users.ts b/src/db/seeds/users.ts deleted file mode 100644 index c13741d..0000000 --- a/src/db/seeds/users.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { eq } from 'drizzle-orm'; -import { Argon2id } from 'oslo/password'; -import { type db } from '$db'; -import * as schema from '$db/schema'; -import users from './data/users.json'; -import env from '../../env'; - -type JsonUser = { - id: string; - username: string; - email: string; - password: string; - roles: { - name: string; - primary: boolean; - }[]; -}; - -type JsonRole = { - name: string; - primary: boolean; -}; - -export default async function seed(db: db) { - const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin')); - const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user')); - - console.log('Admin Role: ', adminRole); - const adminUser = await db - .insert(schema.usersTable) - .values({ - username: `${env.ADMIN_USERNAME}`, - email: '', - hashed_password: await new Argon2id().hash(`${env.ADMIN_PASSWORD}`), - first_name: 'Brad', - last_name: 'S', - verified: true, - }) - .returning() - .onConflictDoNothing(); - - console.log('Admin user created.', adminUser); - - await db - .insert(schema.collections) - .values({ user_id: adminUser[0].id }) - .onConflictDoNothing(); - - await db - .insert(schema.wishlists) - .values({ user_id: adminUser[0].id }) - .onConflictDoNothing(); - - await db - .insert(schema.userRoles) - .values({ - user_id: adminUser[0].id, - role_id: adminRole[0].id, - }) - .onConflictDoNothing(); - - console.log('Admin user given admin role.'); - - await db - .insert(schema.userRoles) - .values({ - user_id: adminUser[0].id, - role_id: userRole[0].id, - }) - .onConflictDoNothing(); - - console.log('Admin user given user role.'); - await Promise.all( - users.map(async (user) => { - const [insertedUser] = await db - .insert(schema.usersTable) - .values({ - ...user, - hashed_password: await new Argon2id().hash(user.password), - }) - .returning(); - await db.insert(schema.collections).values({ user_id: insertedUser?.id }); - await db.insert(schema.wishlists).values({ user_id: insertedUser?.id }); - await Promise.all( - user.roles.map(async (role: JsonRole) => { - const foundRole = await db.query.roles.findFirst({ - where: eq(schema.roles.name, role.name), - }); - await db.insert(schema.userRoles).values({ - user_id: insertedUser?.id, - role_id: foundRole?.id, - primary: role?.primary, - }); - }), - ); - }), - ); -} diff --git a/src/db/utils.ts b/src/db/utils.ts deleted file mode 100644 index 4d8a66a..0000000 --- a/src/db/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -// import { HTTPException } from 'hono/http-exception'; -import { timestamp } from 'drizzle-orm/pg-core'; -import { customType } from 'drizzle-orm/pg-core'; - -export const citext = customType<{ data: string }>({ - dataType() { - return 'citext'; - }, -}); - -export const cuid2 = customType<{ data: string }>({ - dataType() { - return 'text'; - }, -}); - -export const takeFirst = (values: T[]): T | null => { - if (values.length === 0) return null; - return values[0]!; -}; - -export const takeFirstOrThrow = (values: T[]): T => { - if (values.length === 0) - // throw new HTTPException(404, { - // message: 'Resource not found', - // }); - return values[0]!; -}; - -export const timestamps = { - createdAt: timestamp('created_at', { - mode: 'date', - withTimezone: true, - }) - .notNull() - .defaultNow(), - updatedAt: timestamp('updated_at', { - mode: 'date', - withTimezone: true, - }) - .notNull() - .defaultNow(), -}; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index a8d1a2c..fd1e06a 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -4,7 +4,7 @@ import { hc } from 'hono/client'; import { sequence } from '@sveltejs/kit/hooks'; import { redirect, type Handle } from '@sveltejs/kit'; import { dev } from '$app/environment'; -import { lucia } from '$lib/server/auth'; +// import { lucia } from '$lib/server/auth'; import type { ApiRoutes } from '$lib/server/api'; import { parseApiResponse } from '$lib/utils/api'; import { StatusCodes } from '$lib/constants/status-codes'; @@ -92,7 +92,7 @@ export const authentication: Handle = async function ({ event, resolve }) { export const handle: Handle = sequence( // Sentry.sentryHandle(), - authentication, + // authentication, apiClient ); // export const handleError = Sentry.handleErrorWithSentry(); diff --git a/src/lib/dtos/signup-username-email.dto.ts b/src/lib/dtos/signup-username-email.dto.ts index e0acae7..b697777 100644 --- a/src/lib/dtos/signup-username-email.dto.ts +++ b/src/lib/dtos/signup-username-email.dto.ts @@ -13,8 +13,8 @@ export const signupUsernameEmailDto = z.object({ password: z.string({required_error: 'Password is required'}).trim(), confirm_password: z.string({required_error: 'Confirm Password is required'}).trim() }) - .superRefine(({ confirm_password, password }, ctx) => { - refinePasswords(confirm_password, password, ctx); + .superRefine(async ({ confirm_password, password }, ctx) => { + return await refinePasswords(confirm_password, password, ctx); }); export type SignupUsernameEmailDto = z.infer diff --git a/src/lib/server/api/controllers/signup.controller.ts b/src/lib/server/api/controllers/signup.controller.ts index d7ae203..f8070e9 100644 --- a/src/lib/server/api/controllers/signup.controller.ts +++ b/src/lib/server/api/controllers/signup.controller.ts @@ -1,106 +1,57 @@ import 'reflect-metadata'; import { Hono } from 'hono'; +import { setCookie } from 'hono/cookie'; import {inject, injectable} from 'tsyringe'; import { zValidator } from '@hono/zod-validator'; +import { TimeSpan } from 'oslo'; import type { HonoTypes } from '../types'; import type { Controller } from '../interfaces/controller.interface'; import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto"; import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; import {UsersService} from "$lib/server/api/services/users.service"; +import {LoginRequestsService} from "$lib/server/api/services/loginrequest.service"; +import {LuciaProvider} from "$lib/server/api/providers"; @injectable() export class SignupController implements Controller { controller = new Hono(); constructor( - @inject(UsersService) private readonly usersService: UsersService + @inject(UsersService) private readonly usersService: UsersService, + @inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService, + @inject(LuciaProvider) private lucia: LuciaProvider ) { } routes() { return this.controller .post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { firstName, lastName, email, username, password } = await c.req.valid('json'); + const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json'); const existingUser = await this.usersService.findOneByUsername(username); if (existingUser) { return c.body("User already exists", 400); } - const user = await this.usersService.create(signupUsernameEmailDto); + const user = await this.usersService.create({ firstName, lastName, email, username, password, confirm_password }); -// const existing_user = await db.query.usersTable.findFirst({ -// where: eq(usersTable.username, form.data.username), -// }); -// -// if (existing_user) { -// return setError(form, 'username', 'You cannot create an account with that username'); -// } -// -// console.log('Creating user'); -// -// const hashedPassword = await new Argon2id().hash(form.data.password); -// -// const user = await db -// .insert(usersTable) -// .values({ -// username: form.data.username, -// hashed_password: hashedPassword, -// email: form.data.email, -// first_name: form.data.firstName ?? '', -// last_name: form.data.lastName ?? '', -// verified: false, -// receive_email: false, -// theme: 'system', -// }) -// .returning(); -// console.log('signup user', user); -// -// if (!user || user.length === 0) { -// return fail(400, { -// form, -// message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, -// }); -// } -// -// await add_user_to_role(user[0].id, 'user', true); -// await db.insert(collections).values({ -// user_id: user[0].id, -// }); -// await db.insert(wishlists).values({ -// user_id: user[0].id, -// }); -// -// try { -// session = await lucia.createSession(user[0].id, { -// ip_country: event.locals.ip, -// ip_address: event.locals.country, -// twoFactorAuthEnabled: false, -// isTwoFactorAuthenticated: false, -// }); -// sessionCookie = lucia.createSessionCookie(session.id); -// } catch (e: any) { -// if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) { -// // key already exists -// console.error('Lucia Error: ', e); -// } -// console.log(e); -// const message = { -// type: 'error', -// message: 'Unable to create your account. Please try again.', -// }; -// form.data.password = ''; -// form.data.confirm_password = ''; -// error(500, message); -// } -// -// event.cookies.set(sessionCookie.name, sessionCookie.value, { -// path: '.', -// ...sessionCookie.attributes, -// }); -// -// redirect(302, '/'); + if (!user) { + return c.body("Failed to create user", 500); + } - return c.json({ message: 'Hello, world!' }); + const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined); + const sessionCookie = this.lucia.createSessionCookie(session.id); + console.log("set cookie", sessionCookie); + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires + }); + return c.json({ message: 'ok' }); }); } } \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/auth/lucia.ts b/src/lib/server/api/infrastructure/auth/lucia.ts index 712338d..63a758c 100644 --- a/src/lib/server/api/infrastructure/auth/lucia.ts +++ b/src/lib/server/api/infrastructure/auth/lucia.ts @@ -1,8 +1,8 @@ // lib/server/lucia.ts import { Lucia, TimeSpan } from 'lucia'; import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; -import db from '$db/index'; -import { sessionsTable, usersTable } from '$db/schema'; +import { db } from '../database'; +import { sessionsTable, usersTable } from '../database/tables'; import { config } from '../../common/config'; const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable); @@ -18,7 +18,12 @@ export const lucia = new Lucia(adapter, { }, getUserAttributes: (attributes) => { return { - ...attributes, + // ...attributes, + username: attributes.username, + email: attributes.email, + firstName: attributes.firstName, + lastName: attributes.lastName, + theme: attributes.theme, }; }, sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks diff --git a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts b/src/lib/server/api/infrastructure/database/tables/credentials.table.ts index 2025619..179071d 100644 --- a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/credentials.table.ts @@ -1,4 +1,5 @@ import { pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { type InferSelectModel } from 'drizzle-orm'; import { timestamps } from '../utils'; import { usersTable } from "./users.table"; @@ -17,4 +18,6 @@ export const credentialsTable = pgTable('credentials', { type: text('type').notNull().default(CredentialsType.PASSWORD), secret_data: text('secret_data').notNull(), ...timestamps -}); \ No newline at end of file +}); + +export type Credentials = InferSelectModel; diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index b1eb3d6..9c21ea8 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -45,7 +45,6 @@ export const requireAuth: MiddlewareHandler<{ }; }> = createMiddleware(async (c, next) => { const user = c.var.user; - const session = c.var.session; if (!user) throw Unauthorized('You must be logged in to access this resource'); return next(); }); diff --git a/src/lib/server/api/services/loginrequest.service.ts b/src/lib/server/api/services/loginrequest.service.ts index eafaf28..d3f9c8d 100644 --- a/src/lib/server/api/services/loginrequest.service.ts +++ b/src/lib/server/api/services/loginrequest.service.ts @@ -8,6 +8,7 @@ import { UsersRepository } from '../repositories/users.repository'; import { CredentialsRepository } from '../repositories/credentials.repository'; import type { HonoRequest } from 'hono'; import type {SigninUsernameDto} from "$lib/dtos/signin-username.dto"; +import type {Credentials} from "$lib/server/api/infrastructure/database/tables"; @injectable() export class LoginRequestsService { @@ -53,17 +54,23 @@ export class LoginRequestsService { const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id); - return this.lucia.createSession(existingUser.id, { - ip_country: requestIpCountry || 'unknown', - ip_address: requestIpAddress || 'unknown', - twoFactorAuthEnabled: - !!totpCredentials && - totpCredentials?.secret_data !== null && - totpCredentials?.secret_data !== '', - isTwoFactorAuthenticated: false, - }); + return await this.createUserSession(existingUser.id, req, totpCredentials); } + async createUserSession(existingUserId: string, req: HonoRequest, totpCredentials: Credentials | undefined) { + const requestIpAddress = req.header('x-real-ip'); + const requestIpCountry = req.header('x-vercel-ip-country'); + return this.lucia.createSession(existingUserId, { + ip_country: requestIpCountry || 'unknown', + ip_address: requestIpAddress || 'unknown', + twoFactorAuthEnabled: + !!totpCredentials && + totpCredentials?.secret_data !== null && + totpCredentials?.secret_data !== '', + isTwoFactorAuthenticated: false, + }); + } + // Create a new user and send a welcome email - or other onboarding process private async handleNewUserRegistration(email: string) { const newUser = await this.usersRepository.create({ email, verified: true }) diff --git a/src/lib/server/auth-utils.ts b/src/lib/server/auth-utils.ts index 9973484..949b0d2 100644 --- a/src/lib/server/auth-utils.ts +++ b/src/lib/server/auth-utils.ts @@ -1,8 +1,8 @@ import { generateIdFromEntropySize, type Session, type User } from 'lucia'; import { TimeSpan, createDate } from 'oslo'; import { eq } from 'drizzle-orm'; -import db from '../../db'; -import { password_reset_tokens } from '$db/schema'; +import { db } from './api/infrastructure/database/index'; +import { password_reset_tokens } from './api/infrastructure/database/tables'; export async function createPasswordResetToken(userId: string): Promise { // optionally invalidate all existing tokens diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts deleted file mode 100644 index f6b8b69..0000000 --- a/src/lib/server/auth.ts +++ /dev/null @@ -1,68 +0,0 @@ -// lib/server/lucia.ts -import { Lucia, TimeSpan } from 'lucia'; -import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; -import db from '../../db'; -import { sessionsTable, usersTable } from '$db/schema'; - -const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable); - -let domain; -if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production') { - domain = 'boredgame.vercel.app'; -} else if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') { - domain = process.env.VERCEL_BRANCH_URL; -} else { - domain = 'localhost'; -} - -export const lucia = new Lucia(adapter, { - getSessionAttributes: (attributes) => { - return { - ipCountry: attributes.ip_country, - ipAddress: attributes.ip_address, - isTwoFactorAuthEnabled: attributes.twoFactorAuthEnabled, - isTwoFactorAuthenticated: attributes.isTwoFactorAuthenticated, - }; - }, - getUserAttributes: (attributes) => { - return { - username: attributes.username, - email: attributes.email, - firstName: attributes.firstName, - lastName: attributes.lastName, - theme: attributes.theme, - }; - }, - sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days - sessionCookie: { - name: 'session', - expires: false, // session cookies have very long lifespan (2 years) - attributes: { - // set to `true` when using HTTPS - secure: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production', - sameSite: 'strict', - domain, - }, - }, -}); - -declare module 'lucia' { - interface Register { - Lucia: typeof lucia; - DatabaseUserAttributes: DatabaseUserAttributes; - DatabaseSessionAttributes: DatabaseSessionAttributes; - } - interface DatabaseSessionAttributes { - ip_country: string; - ip_address: string; - twoFactorAuthEnabled: boolean; - isTwoFactorAuthenticated: boolean; - } - interface DatabaseUserAttributes { - username: string; - email: string; - firstName: string; - lastName: string; - theme: string; - } -} diff --git a/src/lib/validations/auth.ts b/src/lib/validations/auth.ts index 6be095c..af8d1b2 100644 --- a/src/lib/validations/auth.ts +++ b/src/lib/validations/auth.ts @@ -11,8 +11,8 @@ export const signUpSchema = userSchema password: true, confirm_password: true, }) - .superRefine(({ confirm_password, password }, ctx) => { - refinePasswords(confirm_password, password, ctx); + .superRefine(async ({ confirm_password, password }, ctx) => { + return await refinePasswords(confirm_password, password, ctx); }); export const signInSchema = z.object({ diff --git a/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts b/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts index baa9ee0..ae0168f 100644 --- a/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts @@ -60,18 +60,18 @@ export const actions: Actions = { where: eq(usersTable.id, user!.id), }); - if (!dbUser?.hashed_password) { - form.data.password = ''; - form.data.confirm_password = ''; - form.data.current_password = ''; - return setError( - form, - 'Error occurred. Please try again or contact support if you need further help.', - ); - } + // if (!dbUser?.hashed_password) { + // form.data.password = ''; + // form.data.confirm_password = ''; + // form.data.current_password = ''; + // return setError( + // form, + // 'Error occurred. Please try again or contact support if you need further help.', + // ); + // } const currentPasswordVerified = await new Argon2id().verify( - dbUser.hashed_password, + // dbUser.hashed_password, form.data.current_password, ); @@ -86,10 +86,10 @@ export const actions: Actions = { } const hashedPassword = await new Argon2id().hash(form.data.password); await lucia.invalidateUserSessions(user.id); - await db - .update(usersTable) - .set({ hashed_password: hashedPassword }) - .where(eq(usersTable.id, user.id)); + // await db + // .update(usersTable) + // .set({ hashed_password: hashedPassword }) + // .where(eq(usersTable.id, user.id)); await lucia.createSession(user.id, { country: event.locals.session?.ipCountry ?? 'unknown', }); diff --git a/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts b/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts index d119b43..9b92587 100644 --- a/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts @@ -115,14 +115,14 @@ export const actions: Actions = { where: eq(usersTable.id, user!.id!), }); - if (!dbUser?.hashed_password) { - addTwoFactorForm.data.current_password = ''; - addTwoFactorForm.data.two_factor_code = ''; - return setError( - addTwoFactorForm, - 'Error occurred. Please try again or contact support if you need further help.', - ); - } + // if (!dbUser?.hashed_password) { + // addTwoFactorForm.data.current_password = ''; + // addTwoFactorForm.data.two_factor_code = ''; + // return setError( + // addTwoFactorForm, + // 'Error occurred. Please try again or contact support if you need further help.', + // ); + // } const twoFactorDetails = await db.query.twoFactor.findFirst({ where: eq(twoFactor.userId, dbUser?.id), @@ -147,7 +147,7 @@ export const actions: Actions = { } const currentPasswordVerified = await new Argon2id().verify( - dbUser.hashed_password, + // dbUser.hashed_password, addTwoFactorForm.data.current_password, ); @@ -194,16 +194,16 @@ export const actions: Actions = { where: eq(usersTable.id, user.id), }); - if (!dbUser?.hashed_password) { - removeTwoFactorForm.data.current_password = ''; - return setError( - removeTwoFactorForm, - 'Error occurred. Please try again or contact support if you need further help.', - ); - } + // if (!dbUser?.hashed_password) { + // removeTwoFactorForm.data.current_password = ''; + // return setError( + // removeTwoFactorForm, + // 'Error occurred. Please try again or contact support if you need further help.', + // ); + // } const currentPasswordVerified = await new Argon2id().verify( - dbUser.hashed_password, + // dbUser.hashed_password, removeTwoFactorForm.data.current_password, ); diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts index 5aa2068..58a037f 100644 --- a/src/routes/(app)/+layout.server.ts +++ b/src/routes/(app)/+layout.server.ts @@ -1,23 +1,24 @@ import { loadFlash } from 'sveltekit-flash-message/server'; import type { LayoutServerLoad } from '../$types'; -import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; -import { lucia } from '$lib/server/auth'; +// import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; +// import { lucia } from '$lib/server/auth'; export const load: LayoutServerLoad = loadFlash(async (event) => { const { url, locals, cookies } = event; - const { user, session } = locals; + const authedUser = await locals.getAuthedUserOrThrow(); - if (userNotFullyAuthenticated(user, session)) { - await lucia.invalidateSession(locals.session!.id!); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - } + // if (userNotFullyAuthenticated(user, session)) { + // await lucia.invalidateSession(locals.session!.id!); + // const sessionCookie = lucia.createBlankSessionCookie(); + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }); + // } return { url: url.pathname, - user: userFullyAuthenticated(user, session) ? locals.user : null, + // user: userFullyAuthenticated(user, session) ? locals.user : null, + user: authedUser, }; }); diff --git a/src/routes/(app)/+page.server.ts b/src/routes/(app)/+page.server.ts index 190bd71..2985735 100644 --- a/src/routes/(app)/+page.server.ts +++ b/src/routes/(app)/+page.server.ts @@ -1,13 +1,14 @@ import type { MetaTagsProps } from 'svelte-meta-tags'; import { eq } from 'drizzle-orm'; import type { PageServerLoad } from './$types'; -import db from '../../db'; -import { collections, usersTable, wishlists } from '$db/schema'; -import { userFullyAuthenticated } from '$lib/server/auth-utils'; +import {db} from '$lib/server/api/infrastructure/database/index'; +import { collections, usersTable, wishlists } from '$lib/server/api/infrastructure/database/tables'; +// import { userFullyAuthenticated } from '$lib/server/auth-utils'; export const load: PageServerLoad = async (event) => { const { locals, url } = event; - const { user, session } = locals; + + const authedUser = await locals.getAuthedUser(); const image = { url: `${ @@ -41,9 +42,9 @@ export const load: PageServerLoad = async (event) => { }, }); - if (userFullyAuthenticated(user, session)) { + // if (userFullyAuthenticated(user, session)) { const dbUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.id, user!.id!), + where: eq(usersTable.id, authedUser!.id!), }); console.log('Sending back user details'); @@ -52,14 +53,14 @@ export const load: PageServerLoad = async (event) => { cuid: true, name: true, }, - where: eq(wishlists.user_id, user!.id!), + where: eq(wishlists.user_id, authedUser!.id!), }); const userCollection = await db.query.collections.findMany({ columns: { cuid: true, name: true, }, - where: eq(collections.user_id, user!.id!), + where: eq(collections.user_id, authedUser!.id!), }); console.log('Wishlists', userWishlists); @@ -74,7 +75,7 @@ export const load: PageServerLoad = async (event) => { wishlists: userWishlists, collections: userCollection, }; - } + // } return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] }; }; diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index 9c254cc..81ece50 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -4,10 +4,9 @@ import { Argon2id } from 'oslo/password'; import { zod } from 'sveltekit-superforms/adapters'; import { setError, superValidate } from 'sveltekit-superforms/server'; import { redirect } from 'sveltekit-flash-message/server'; -import { RateLimiter } from 'sveltekit-rate-limiter/server'; -import db from '../../../db'; -import { lucia } from '$lib/server/auth'; -import { twoFactor, usersTable, type Users } from '$db/schema'; +import { db } from '../../../lib/server/api/infrastructure/database/index'; +import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia'; +import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables'; import type { PageServerLoad } from './$types'; import {signinUsernameDto} from "$lib/dtos/signin-username.dto"; @@ -41,10 +40,6 @@ export const load: PageServerLoad = async (event) => { export const actions: Actions = { default: async (event) => { - // if (await limiter.isLimited(event)) { - // throw error(429); - // } - const { locals } = event; const authedUser = await locals.getAuthedUser(); @@ -66,68 +61,68 @@ export const actions: Actions = { }); } - let session; - let sessionCookie; - const user: Users | undefined = await db.query.usersTable.findFirst({ - where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)), - }); - - if (!user) { - form.data.password = ''; - return setError(form, 'username', 'Your username or password is incorrect.'); - } - - let twoFactorDetails; - + // let session; + // let sessionCookie; + // const user: Users | undefined = await db.query.usersTable.findFirst({ + // where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)), + // }); + // + // if (!user) { + // form.data.password = ''; + // return setError(form, 'username', 'Your username or password is incorrect.'); + // } + // + // let twoFactorDetails; + // try { - const password = form.data.password; - console.log('user', JSON.stringify(user, null, 2)); - - if (!user?.hashed_password) { - console.log('invalid username/password'); - form.data.password = ''; - return setError(form, 'password', 'Your username or password is incorrect.'); - } - - const validPassword = await new Argon2id().verify(user.hashed_password, password); - if (!validPassword) { - console.log('invalid password'); - form.data.password = ''; - return setError(form, 'password', 'Your username or password is incorrect.'); - } - - console.log('ip', locals.ip); - console.log('country', locals.country); - - twoFactorDetails = await db.query.twoFactor.findFirst({ - where: eq(twoFactor.userId, user?.id), - }); - - if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { - await db.update(twoFactor).set({ - initiatedTime: new Date(), - }); - - session = await lucia.createSession(user.id, { - ip_country: locals.country, - ip_address: locals.ip, - twoFactorAuthEnabled: - twoFactorDetails?.enabled && - twoFactorDetails?.secret !== null && - twoFactorDetails?.secret !== '', - isTwoFactorAuthenticated: false, - }); - } else { - session = await lucia.createSession(user.id, { - ip_country: locals.country, - ip_address: locals.ip, - twoFactorAuthEnabled: false, - isTwoFactorAuthenticated: false, - }); - } - console.log('logging in session', session); - sessionCookie = lucia.createSessionCookie(session.id); - console.log('logging in session cookie', sessionCookie); + // const password = form.data.password; + // console.log('user', JSON.stringify(user, null, 2)); + // + // if (!user?.hashed_password) { + // console.log('invalid username/password'); + // form.data.password = ''; + // return setError(form, 'password', 'Your username or password is incorrect.'); + // } + // + // const validPassword = await new Argon2id().verify(user.hashed_password, password); + // if (!validPassword) { + // console.log('invalid password'); + // form.data.password = ''; + // return setError(form, 'password', 'Your username or password is incorrect.'); + // } + // + // console.log('ip', locals.ip); + // console.log('country', locals.country); + // + // twoFactorDetails = await db.query.twoFactor.findFirst({ + // where: eq(twoFactor.userId, user?.id), + // }); + // + // if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { + // await db.update(twoFactor).set({ + // initiatedTime: new Date(), + // }); + // + // session = await lucia.createSession(user.id, { + // ip_country: locals.country, + // ip_address: locals.ip, + // twoFactorAuthEnabled: + // twoFactorDetails?.enabled && + // twoFactorDetails?.secret !== null && + // twoFactorDetails?.secret !== '', + // isTwoFactorAuthenticated: false, + // }); + // } else { + // session = await lucia.createSession(user.id, { + // ip_country: locals.country, + // ip_address: locals.ip, + // twoFactorAuthEnabled: false, + // isTwoFactorAuthenticated: false, + // }); + // } + // console.log('logging in session', session); + // sessionCookie = lucia.createSessionCookie(session.id); + // console.log('logging in session cookie', sessionCookie); } catch (e) { // TODO: need to return error message to the client console.error(e); @@ -135,26 +130,26 @@ export const actions: Actions = { return setError(form, '', 'Your username or password is incorrect.'); } - console.log('setting session cookie', sessionCookie); - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); + // console.log('setting session cookie', sessionCookie); + // event.cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }); form.data.username = ''; form.data.password = ''; - if ( - twoFactorDetails?.enabled && - twoFactorDetails?.secret !== null && - twoFactorDetails?.secret !== '' - ) { - console.log('redirecting to TOTP page'); - const message = { type: 'success', message: 'Please enter your TOTP code.' } as const; - redirect(302, '/totp', message, event); - } else { - const message = { type: 'success', message: 'Signed In!' } as const; - redirect(302, '/', message, event); - } + // if ( + // twoFactorDetails?.enabled && + // twoFactorDetails?.secret !== null && + // twoFactorDetails?.secret !== '' + // ) { + // console.log('redirecting to TOTP page'); + // const message = { type: 'success', message: 'Please enter your TOTP code.' } as const; + // redirect(302, '/totp', message, event); + // } else { + // const message = { type: 'success', message: 'Signed In!' } as const; + // redirect(302, '/', message, event); + // } }, }; diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/sign-up/+page.server.ts index aa64cdf..ffd8016 100644 --- a/src/routes/(auth)/sign-up/+page.server.ts +++ b/src/routes/(auth)/sign-up/+page.server.ts @@ -12,6 +12,7 @@ import { add_user_to_role } from '$server/roles'; import db from '../../../db'; import { collections, usersTable, wishlists } from '$db/schema'; import { createId as cuid2 } from '@paralleldrive/cuid2'; +import {signupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto"; const limiter = new RateLimiter({ // A rate is defined by [number, unit] @@ -55,7 +56,7 @@ export const load: PageServerLoad = async (event) => { // } return { - form: await superValidate(zod(signUpSchema), { + form: await superValidate(zod(signupUsernameEmailDto), { defaults: signUpDefaults, }), }; @@ -63,11 +64,20 @@ export const load: PageServerLoad = async (event) => { export const actions: Actions = { default: async (event) => { - if (await limiter.isLimited(event)) { - throw error(429); + const { locals } = event; + + const authedUser = await locals.getAuthedUser(); + + if (authedUser) { + const message = { type: 'success', message: 'You are already signed in' } as const; + throw redirect('/', message, event); } - // fail(401, { message: 'Sign-up not yet available. Please add your email to the waitlist!' }); - const form = await superValidate(event, zod(signUpSchema)); + + const form = await superValidate(event, zod(signupUsernameEmailDto)); + + const { error } = await locals.api.signup.$post({ json: form.data }).then(locals.parseApiResponse); + if (error) return setError(form, 'username', error); + if (!form.valid) { form.data.password = ''; form.data.confirm_password = ''; @@ -76,80 +86,80 @@ export const actions: Actions = { }); } - let session; - let sessionCookie; - // Adding user to the db - console.log('Check if user already exists'); - - const existing_user = await db.query.usersTable.findFirst({ - where: eq(usersTable.username, form.data.username), - }); - - if (existing_user) { - return setError(form, 'username', 'You cannot create an account with that username'); - } - - console.log('Creating user'); - - const hashedPassword = await new Argon2id().hash(form.data.password); - - const user = await db - .insert(usersTable) - .values({ - username: form.data.username, - hashed_password: hashedPassword, - email: form.data.email, - first_name: form.data.firstName ?? '', - last_name: form.data.lastName ?? '', - verified: false, - receive_email: false, - theme: 'system', - }) - .returning(); - console.log('signup user', user); - - if (!user || user.length === 0) { - return fail(400, { - form, - message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, - }); - } - - await add_user_to_role(user[0].id, 'user', true); - await db.insert(collections).values({ - user_id: user[0].id, - }); - await db.insert(wishlists).values({ - user_id: user[0].id, - }); - - try { - session = await lucia.createSession(user[0].id, { - ip_country: event.locals.ip, - ip_address: event.locals.country, - twoFactorAuthEnabled: false, - isTwoFactorAuthenticated: false, - }); - sessionCookie = lucia.createSessionCookie(session.id); - } catch (e: any) { - if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) { - // key already exists - console.error('Lucia Error: ', e); - } - console.log(e); - const message = { - type: 'error', - message: 'Unable to create your account. Please try again.', - }; - form.data.password = ''; - form.data.confirm_password = ''; - error(500, message); - } - - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); + // let session; + // let sessionCookie; + // // Adding user to the db + // console.log('Check if user already exists'); + // + // const existing_user = await db.query.usersTable.findFirst({ + // where: eq(usersTable.username, form.data.username), + // }); + // + // if (existing_user) { + // return setError(form, 'username', 'You cannot create an account with that username'); + // } + // + // console.log('Creating user'); + // + // const hashedPassword = await new Argon2id().hash(form.data.password); + // + // const user = await db + // .insert(usersTable) + // .values({ + // username: form.data.username, + // hashed_password: hashedPassword, + // email: form.data.email, + // first_name: form.data.firstName ?? '', + // last_name: form.data.lastName ?? '', + // verified: false, + // receive_email: false, + // theme: 'system', + // }) + // .returning(); + // console.log('signup user', user); + // + // if (!user || user.length === 0) { + // return fail(400, { + // form, + // message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`, + // }); + // } + // + // await add_user_to_role(user[0].id, 'user', true); + // await db.insert(collections).values({ + // user_id: user[0].id, + // }); + // await db.insert(wishlists).values({ + // user_id: user[0].id, + // }); + // + // try { + // session = await lucia.createSession(user[0].id, { + // ip_country: event.locals.ip, + // ip_address: event.locals.country, + // twoFactorAuthEnabled: false, + // isTwoFactorAuthenticated: false, + // }); + // sessionCookie = lucia.createSessionCookie(session.id); + // } catch (e: any) { + // if (e.message.toUpperCase() === `DUPLICATE_KEY_ID`) { + // // key already exists + // console.error('Lucia Error: ', e); + // } + // console.log(e); + // const message = { + // type: 'error', + // message: 'Unable to create your account. Please try again.', + // }; + // form.data.password = ''; + // form.data.confirm_password = ''; + // error(500, message); + // } + // + // event.cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }); redirect(302, '/'); // const message = { type: 'success', message: 'Signed Up!' } as const; diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 74051e3..c9983c9 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,9 +1,11 @@ import { loadFlash } from 'sveltekit-flash-message/server'; import type { LayoutServerLoad } from './$types'; -export const load: LayoutServerLoad = loadFlash(async ({ url, locals }) => { +export const load: LayoutServerLoad = loadFlash(async (event) => { + const { locals, url } = event; + const user = await locals.getAuthedUser(); return { url: url.pathname, - user: locals.user + user, }; }); diff --git a/src/routes/api/auth/reset-password/[token]/+server.ts b/src/routes/api/auth/reset-password/[token]/+server.ts index d1d4ecf..938137d 100644 --- a/src/routes/api/auth/reset-password/[token]/+server.ts +++ b/src/routes/api/auth/reset-password/[token]/+server.ts @@ -34,7 +34,7 @@ export async function POST({ request, params }) { await lucia.invalidateUserSessions(token.user_id); const hashPassword = await new Argon2id().hash(password); - await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id)); + // await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id)); const session = await lucia.createSession(token.user_id, {}); const sessionCookie = lucia.createSessionCookie(session.id); From abe8ca90ee0b40ab6a5177f38fdfadf4194ffb05 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 15 Aug 2024 16:46:58 -0700 Subject: [PATCH 26/34] Fixing infinite user get loop. --- src/hooks.server.ts | 80 +++++++++---------- .../server/api/controllers/iam.controller.ts | 22 ++++- src/routes/(app)/+layout.server.ts | 2 +- src/routes/(app)/+page.server.ts | 4 +- src/routes/(auth)/logout/+page.server.ts | 18 +---- 5 files changed, 67 insertions(+), 59 deletions(-) diff --git a/src/hooks.server.ts b/src/hooks.server.ts index fd1e06a..295a8f4 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -3,8 +3,6 @@ import 'reflect-metadata' import { hc } from 'hono/client'; import { sequence } from '@sveltejs/kit/hooks'; import { redirect, type Handle } from '@sveltejs/kit'; -import { dev } from '$app/environment'; -// import { lucia } from '$lib/server/auth'; import type { ApiRoutes } from '$lib/server/api'; import { parseApiResponse } from '$lib/utils/api'; import { StatusCodes } from '$lib/constants/status-codes'; @@ -50,45 +48,45 @@ const apiClient: Handle = async ({ event, resolve }) => { return response; }; -export const authentication: Handle = async function ({ event, resolve }) { - event.locals.startTimer = Date.now(); - - const ip = event.request.headers.get('x-forwarded-for') as string; - const country = event.request.headers.get('x-vercel-ip-country') as string; - event.locals.ip = dev ? '127.0.0.1' : ip; // || event.getClientAddress(); - event.locals.country = dev ? 'us' : country; - - 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); - console.log('sessionCookie', JSON.stringify(sessionCookie, null, 2)); - // sveltekit types deviates from the de-facto standard, you can use 'as any' too - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - } - console.log('session from hooks', JSON.stringify(session, null, 2)); - if (!session) { - const sessionCookie = lucia.createBlankSessionCookie(); - console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2)); - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - } - event.locals.user = user; - event.locals.session = session; - - return resolve(event); -}; +// export const authentication: Handle = async function ({ event, resolve }) { +// event.locals.startTimer = Date.now(); +// +// const ip = event.request.headers.get('x-forwarded-for') as string; +// const country = event.request.headers.get('x-vercel-ip-country') as string; +// event.locals.ip = dev ? '127.0.0.1' : ip; // || event.getClientAddress(); +// event.locals.country = dev ? 'us' : country; +// +// 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); +// console.log('sessionCookie', JSON.stringify(sessionCookie, null, 2)); +// // sveltekit types deviates from the de-facto standard, you can use 'as any' too +// event.cookies.set(sessionCookie.name, sessionCookie.value, { +// path: '.', +// ...sessionCookie.attributes, +// }); +// } +// console.log('session from hooks', JSON.stringify(session, null, 2)); +// if (!session) { +// const sessionCookie = lucia.createBlankSessionCookie(); +// console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2)); +// event.cookies.set(sessionCookie.name, sessionCookie.value, { +// path: '.', +// ...sessionCookie.attributes, +// }); +// } +// event.locals.user = user; +// event.locals.session = session; +// +// return resolve(event); +// }; export const handle: Handle = sequence( // Sentry.sentryHandle(), diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 35e6fc6..fdcc6d3 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,14 +1,19 @@ import { Hono } from 'hono'; -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; +import { setCookie } from 'hono/cookie'; import type { HonoTypes } from '../types'; import { requireAuth } from "../middleware/auth.middleware"; import type { Controller } from '../interfaces/controller.interface'; +import {IamService} from "$lib/server/api/services/iam.service"; +import {LuciaProvider} from "$lib/server/api/providers"; @injectable() export class IamController implements Controller { controller = new Hono(); constructor( + @inject(IamService) private readonly iamService: IamService, + @inject(LuciaProvider) private lucia: LuciaProvider ) { } routes() { @@ -16,6 +21,21 @@ export class IamController implements Controller { .get('/me', requireAuth, async (c) => { const user = c.var.user; return c.json({ user }); + }) + .post('/logout', requireAuth, async (c) => { + const sessionId = c.var.session.id; + await this.iamService.logout(sessionId); + const sessionCookie = this.lucia.createBlankSessionCookie(); + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: sessionCookie.attributes.maxAge, + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires + }); + return c.json({ status: 'success' }); }); } } diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts index 58a037f..aa43781 100644 --- a/src/routes/(app)/+layout.server.ts +++ b/src/routes/(app)/+layout.server.ts @@ -5,7 +5,7 @@ import type { LayoutServerLoad } from '../$types'; export const load: LayoutServerLoad = loadFlash(async (event) => { const { url, locals, cookies } = event; - const authedUser = await locals.getAuthedUserOrThrow(); + const authedUser = await locals.getAuthedUser(); // if (userNotFullyAuthenticated(user, session)) { // await lucia.invalidateSession(locals.session!.id!); diff --git a/src/routes/(app)/+page.server.ts b/src/routes/(app)/+page.server.ts index 2985735..d554b67 100644 --- a/src/routes/(app)/+page.server.ts +++ b/src/routes/(app)/+page.server.ts @@ -42,7 +42,7 @@ export const load: PageServerLoad = async (event) => { }, }); - // if (userFullyAuthenticated(user, session)) { + if (authedUser) { const dbUser = await db.query.usersTable.findFirst({ where: eq(usersTable.id, authedUser!.id!), }); @@ -75,7 +75,7 @@ export const load: PageServerLoad = async (event) => { wishlists: userWishlists, collections: userCollection, }; - // } + } return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] }; }; diff --git a/src/routes/(auth)/logout/+page.server.ts b/src/routes/(auth)/logout/+page.server.ts index 78f9d98..b8fb2c5 100644 --- a/src/routes/(auth)/logout/+page.server.ts +++ b/src/routes/(auth)/logout/+page.server.ts @@ -1,22 +1,12 @@ -import { fail } from '@sveltejs/kit'; import { redirect } from 'sveltekit-flash-message/server'; -import { lucia } from '$lib/server/auth'; -import { signedOutMessage } from '$lib/flashMessages'; import type { Actions } from "./$types"; +import {StatusCodes} from "$lib/constants/status-codes"; export const actions: Actions = { default: async (event) => { - const { locals, cookies } = event; + const { locals } = event; console.log('Signing out user'); - if (!locals.session) { - return fail(401); - } - await lucia.invalidateSession(locals.session.id); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes - }); - return redirect(302, '/login', signedOutMessage, event); + await locals.api.me.logout.$post() + redirect(StatusCodes.SEE_OTHER, '/login') } }; From 3ac7de641f3618fa8ed9c2b0715ac8c605950498 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sun, 18 Aug 2024 20:28:15 -0700 Subject: [PATCH 27/34] Update shadcn, add biomejs, and implementing profile pages. --- biome.json | 52 +++ docker-compose.yaml | 33 +- package.json | 15 +- pnpm-lock.yaml | 329 +++++++++++------- policies/.cerbos.yaml | 8 + policies/ticket.yaml | 26 ++ policies/ticket_test.yaml | 43 +++ src/lib/components/ui/button/index.ts | 4 +- .../ui/form/form-description.svelte | 2 +- .../ui/form/form-field-errors.svelte | 2 +- src/lib/components/ui/form/form-legend.svelte | 2 +- src/lib/components/ui/input/index.ts | 6 +- src/lib/components/ui/input/input.svelte | 13 +- src/lib/dtos/signup-username-email.dto.ts | 10 +- src/lib/dtos/update-email.dto.ts | 11 + src/lib/dtos/update-profile.dto.ts | 23 ++ .../server/api/controllers/iam.controller.ts | 16 + .../infrastructure/database/seeds/roles.ts | 4 +- src/lib/server/api/services/iam.service.ts | 20 +- src/lib/server/api/services/users.service.ts | 6 +- .../(app)/(protected)/profile/+page.server.ts | 58 +-- .../(app)/(protected)/profile/+page.svelte | 6 +- src/routes/(auth)/sign-up/+page.server.ts | 14 - src/routes/(auth)/sign-up/+page.svelte | 3 +- 24 files changed, 512 insertions(+), 194 deletions(-) create mode 100644 biome.json create mode 100644 policies/.cerbos.yaml create mode 100644 policies/ticket.yaml create mode 100644 policies/ticket_test.yaml create mode 100644 src/lib/dtos/update-email.dto.ts create mode 100644 src/lib/dtos/update-profile.dto.ts diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..7eaa0b2 --- /dev/null +++ b/biome.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "tab", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 100, + "attributePosition": "auto", + "ignore": [ + "**/.DS_Store", + "**/node_modules", + "./build", + "./.svelte-kit", + "./package", + "**/.env", + "**/.env.*", + "**/pnpm-lock.yaml", + "**/package-lock.json", + "**/yarn.lock" + ] + }, + "organizeImports": { "enabled": true }, + "linter": { "enabled": true, "rules": { "recommended": true } }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "asNeeded", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + }, + "overrides": [ + { + "include": ["*.svelte"], + "linter": { + "rules": { + "style": { + "useConst": "off", + "useImportType": "off" + } + } + } + } + ] +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 7cc5327..82b9871 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,20 +15,29 @@ services: - '6379:6379' volumes: - redis_data:/data - # caddy: - # image: caddy:latest - # restart: unless-stopped - # ports: - # - "80:80" - # - "443:443" - # - "443:443/udp" - # volumes: - # - ./Caddyfile:/etc/caddy/Caddyfile - # - ./site:/srv - # - caddy_data:/data - # - caddy_config:/config +# cerbos: +# image: ghcr.io/cerbos/cerbos:0.38.1 +# environment: +# CERBOS_NO_TELEMETRY: 1 +# ports: +# - '3592:3592' +# volumes: +# - ./policies:/policies +# caddy: +# image: caddy:latest +# restart: unless-stopped +# ports: +# - "80:80" +# - "443:443" +# - "443:443/udp" +# volumes: +# - ./Caddyfile:/etc/caddy/Caddyfile +# - ./site:/srv +# - caddy_data:/data +# - caddy_config:/config volumes: postgres_data: redis_data: +# policies_data: # caddy_data: # caddy_config: \ No newline at end of file diff --git a/package.json b/package.json index c8b2fa5..3829d3f 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,17 @@ "test:unit": "vitest" }, "devDependencies": { + "@biomejs/biome": "1.8.3", "@faker-js/faker": "^8.4.1", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", - "@playwright/test": "^1.46.0", + "@playwright/test": "^1.46.1", "@sveltejs/adapter-auto": "^3.2.4", "@sveltejs/enhanced-img": "^0.3.3", "@sveltejs/kit": "^2.5.22", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@types/cookie": "^0.6.0", - "@types/node": "^20.14.15", + "@types/node": "^20.16.0", "@types/pg": "^8.11.6", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", @@ -58,7 +59,7 @@ "svelte": "5.0.0-next.175", "svelte-check": "^3.8.5", "svelte-headless-table": "^0.18.2", - "svelte-meta-tags": "^3.1.2", + "svelte-meta-tags": "^3.1.3", "svelte-preprocess": "^6.0.2", "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", @@ -69,13 +70,13 @@ "tslib": "^2.6.3", "tsx": "^4.17.0", "typescript": "^5.5.4", - "vite": "^5.4.0", + "vite": "^5.4.1", "vitest": "^1.6.0", "zod": "^3.23.8" }, "type": "module", "dependencies": { - "@fontsource/fira-mono": "^5.0.13", + "@fontsource/fira-mono": "^5.0.14", "@hono/zod-validator": "^0.2.2", "@iconify-icons/line-md": "^1.2.30", "@iconify-icons/mdi": "^1.2.48", @@ -92,7 +93,7 @@ "arctic": "^1.9.2", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.12.5", + "bullmq": "^5.12.9", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -102,7 +103,7 @@ "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.5.5", + "hono": "^4.5.6", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba40219..f66fdcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@fontsource/fira-mono': - specifier: ^5.0.13 - version: 5.0.13 + specifier: ^5.0.14 + version: 5.0.14 '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.5)(zod@3.23.8) + version: 0.2.2(hono@4.5.6)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -40,10 +40,10 @@ importers: version: 2.6.2 '@sveltejs/adapter-node': specifier: ^5.2.2 - version: 5.2.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) + version: 5.2.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))) '@sveltejs/adapter-vercel': specifier: ^5.4.3 - version: 5.4.3(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) + version: 5.4.3(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -60,8 +60,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.12.5 - version: 5.12.5 + specifier: ^5.12.9 + version: 5.12.9 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -85,16 +85,16 @@ importers: version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.5.5 - version: 4.5.5 + specifier: ^4.5.6 + version: 4.5.6 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.5) + version: 0.4.0(hono@4.5.6) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -148,10 +148,10 @@ importers: version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -159,6 +159,9 @@ importers: specifier: ^3.23.2 version: 3.23.2(zod@3.23.8) devDependencies: + '@biomejs/biome': + specifier: 1.8.3 + version: 1.8.3 '@faker-js/faker': specifier: ^8.4.1 version: 8.4.1 @@ -169,26 +172,26 @@ importers: specifier: ^0.83.0 version: 0.83.0(svelte@5.0.0-next.175) '@playwright/test': - specifier: ^1.46.0 - version: 1.46.0 + specifier: ^1.46.1 + version: 1.46.1 '@sveltejs/adapter-auto': specifier: ^3.2.4 - version: 3.2.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) + version: 3.2.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))) '@sveltejs/enhanced-img': specifier: ^0.3.3 - version: 0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + version: 0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) '@sveltejs/kit': specifier: ^2.5.22 - version: 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + version: 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + version: 3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.14.15 - version: 20.14.15 + specifier: ^20.16.0 + version: 20.16.0 '@types/pg': specifier: ^8.11.6 version: 8.11.6 @@ -212,7 +215,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: ^2.43.0 - version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -265,8 +268,8 @@ importers: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) svelte-meta-tags: - specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(typescript@5.5.4) + specifier: ^3.1.3 + version: 3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) @@ -275,19 +278,19 @@ importers: version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))) + version: 0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.17.0 - version: 2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + version: 3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) + version: 10.9.2(@types/node@20.16.0)(typescript@5.5.4) tslib: specifier: ^2.6.3 version: 2.6.3 @@ -298,11 +301,11 @@ importers: specifier: ^5.5.4 version: 5.5.4 vite: - specifier: ^5.4.0 - version: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + specifier: ^5.4.1 + version: 5.4.1(@types/node@20.16.0)(sass@1.77.8) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.15)(sass@1.77.8) + version: 1.6.0(@types/node@20.16.0)(sass@1.77.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -327,6 +330,59 @@ packages: resolution: {integrity: sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==} engines: {node: '>=6.9.0'} + '@biomejs/biome@1.8.3': + resolution: {integrity: sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.8.3': + resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.8.3': + resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.8.3': + resolution: {integrity: sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.8.3': + resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.8.3': + resolution: {integrity: sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.8.3': + resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.8.3': + resolution: {integrity: sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.8.3': + resolution: {integrity: sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -1187,8 +1243,8 @@ packages: '@floating-ui/utils@0.2.4': resolution: {integrity: sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==} - '@fontsource/fira-mono@5.0.13': - resolution: {integrity: sha512-fZDjR2BdAqmauEbTjcIT62zYzbOgDa5+IQH34D2k8Pxmy1T815mAqQkZciWZVQ9dc/BgdTtTUV9HJ2ulBNwchg==} + '@fontsource/fira-mono@5.0.14': + resolution: {integrity: sha512-4IKa+cuHipk/vr2frgZh4pyR2XcoQk/j3zmMlo8uuAGUB3IPLpQlgN6Qm5d3RfRZ7dXGlTn/PWiAJeU8bkmD4w==} '@gcornut/valibot-json-schema@0.31.0': resolution: {integrity: sha512-3xGptCurm23e7nuPQkdrE5rEs1FeTPHhAUsBuwwqG4/YeZLwJOoYZv+fmsppUEfo5y9lzUwNQrNqLS/q7HMc7g==} @@ -1642,8 +1698,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.46.0': - resolution: {integrity: sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==} + '@playwright/test@1.46.1': + resolution: {integrity: sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==} engines: {node: '>=18'} hasBin: true @@ -2032,8 +2088,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.14.15': - resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} + '@types/node@20.16.0': + resolution: {integrity: sha512-vDxceJcoZhIVh67S568bm1UGZO0DX0hpplJZxzeXMKwIPLn190ec5RRxQ69BKhX44SUGIxxgMdDY557lGLKprQ==} '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} @@ -2320,8 +2376,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.12.5: - resolution: {integrity: sha512-lchCvFuPdaIbq01qnyS7MOt2piPeCDHzCqIxNAQEgDSzZ+Eb4RBboUUMgmW90UtMjV46mEqsWY9B1l/7/C13SA==} + bullmq@5.12.9: + resolution: {integrity: sha512-34YW4NaC6IfuAtwdllNkBSEP9/sq9bjpvlvbdSv1JXQUzqbey1+DZTvecM8SdewsNKa0RxuwYx/KATe7iXvH7A==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3090,8 +3146,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.5: - resolution: {integrity: sha512-fXBXHqaVfimWofbelLXci8pZyIwBMkDIwCa4OwZvK+xVbEyYLELVP4DfbGaj1aEM6ZY3hHgs4qLvCO2ChkhgQw==} + hono@4.5.6: + resolution: {integrity: sha512-9SuUC/zLQv8YAcnIxJko0KCeLI0Q6menPsDWuJ9jaH+r8ZkVXeLqeLs1QJXCPKKbURAWj9x0SJBSFh803EnAUw==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -3732,13 +3788,13 @@ packages: pkg-types@1.1.0: resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} - playwright-core@1.46.0: - resolution: {integrity: sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==} + playwright-core@1.46.1: + resolution: {integrity: sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==} engines: {node: '>=18'} hasBin: true - playwright@1.46.0: - resolution: {integrity: sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==} + playwright@1.46.1: + resolution: {integrity: sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==} engines: {node: '>=18'} hasBin: true @@ -4402,8 +4458,8 @@ packages: svelte-lazy-loader@1.0.0: resolution: {integrity: sha512-AZD6R60vksyojn21FgXLglmBiBB9K5Dkdu0hdGrLbCaRCYT68IsWkZfRUqKhMx1IfzqWcZQ8X9y/f+Ih0oNQkQ==} - svelte-meta-tags@3.1.2: - resolution: {integrity: sha512-zw8xSA10ce7atFO1o0N1x41+qU+HBnpGx8KcVRAWPy5iiRdO6fvUFMg6VwJVgMhLSBEUTZXKAvMALLUssbCoCw==} + svelte-meta-tags@3.1.3: + resolution: {integrity: sha512-iIdJgxKdMUqFGR4m88jBE9KTSO2jdKE5CRjyRtAjdevW51jL4TtDZwL7GOtr5Fd2dw/+jyQIPD7APATP191qIA==} peerDependencies: svelte: ^3.55.0 || ^4.0.0 @@ -4688,8 +4744,8 @@ packages: ultrahtml@1.5.3: resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.6: + resolution: {integrity: sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==} unfetch@4.2.0: resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} @@ -4747,8 +4803,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.0: - resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} + vite@5.4.1: + resolution: {integrity: sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4938,6 +4994,41 @@ snapshots: regenerator-runtime: 0.14.1 optional: true + '@biomejs/biome@1.8.3': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.8.3 + '@biomejs/cli-darwin-x64': 1.8.3 + '@biomejs/cli-linux-arm64': 1.8.3 + '@biomejs/cli-linux-arm64-musl': 1.8.3 + '@biomejs/cli-linux-x64': 1.8.3 + '@biomejs/cli-linux-x64-musl': 1.8.3 + '@biomejs/cli-win32-arm64': 1.8.3 + '@biomejs/cli-win32-x64': 1.8.3 + + '@biomejs/cli-darwin-arm64@1.8.3': + optional: true + + '@biomejs/cli-darwin-x64@1.8.3': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.8.3': + optional: true + + '@biomejs/cli-linux-arm64@1.8.3': + optional: true + + '@biomejs/cli-linux-x64-musl@1.8.3': + optional: true + + '@biomejs/cli-linux-x64@1.8.3': + optional: true + + '@biomejs/cli-win32-arm64@1.8.3': + optional: true + + '@biomejs/cli-win32-x64@1.8.3': + optional: true + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -5537,7 +5628,7 @@ snapshots: '@floating-ui/utils@0.2.4': {} - '@fontsource/fira-mono@5.0.13': {} + '@fontsource/fira-mono@5.0.14': {} '@gcornut/valibot-json-schema@0.31.0': dependencies: @@ -5556,9 +5647,9 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/zod-validator@0.2.2(hono@4.5.5)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.5.6)(zod@3.23.8)': dependencies: - hono: 4.5.5 + hono: 4.5.6 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -5925,9 +6016,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.46.0': + '@playwright/test@1.46.1': dependencies: - playwright: 1.46.0 + playwright: 1.46.1 '@polka/url@1.0.0-next.25': {} @@ -6148,41 +6239,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))': + '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1) '@rollup/plugin-json': 6.1.0(rollup@4.18.1) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1) - '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) rollup: 4.18.1 - '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': + '@sveltejs/enhanced-img@0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))': dependencies: magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) vite-imagetools: 7.0.2(rollup@4.18.1) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': + '@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6196,28 +6287,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) - vitefu: 0.2.5(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) + vitefu: 0.2.5(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6247,13 +6338,13 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.14.15': + '@types/node@20.16.0': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.6 '@types/pg@8.11.6': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.0 pg-protocol: 1.6.1 pg-types: 4.0.2 @@ -6600,7 +6691,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.12.5: + bullmq@5.12.9: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -7035,7 +7126,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): + eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -7044,7 +7135,7 @@ snapshots: esutils: 2.0.3 known-css-properties: 0.34.0 postcss: 8.4.41 - postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)) postcss-safe-parser: 6.0.0(postcss@8.4.41) postcss-selector-parser: 6.1.0 semver: 7.6.2 @@ -7271,11 +7362,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7407,11 +7498,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.5): + hono-rate-limiter@0.4.0(hono@4.5.6): dependencies: - hono: 4.5.5 + hono: 4.5.6 - hono@4.5.5: {} + hono@4.5.6: {} html-entities@2.5.2: {} @@ -7996,11 +8087,11 @@ snapshots: mlly: 1.7.0 pathe: 1.1.2 - playwright-core@1.46.0: {} + playwright-core@1.46.1: {} - playwright@1.46.0: + playwright@1.46.1: dependencies: - playwright-core: 1.46.0 + playwright-core: 1.46.1 optionalDependencies: fsevents: 2.3.2 @@ -8126,21 +8217,21 @@ snapshots: '@csstools/utilities': 1.0.0(postcss@8.4.41) postcss: 8.4.41 - postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): + postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.16.0)(typescript@5.5.4) - postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.16.0)(typescript@5.5.4) postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0): dependencies: @@ -8771,7 +8862,7 @@ snapshots: svelte-lazy-loader@1.0.0: {} - svelte-meta-tags@3.1.2(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-meta-tags@3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: schema-dts: 1.1.2(typescript@5.5.4) svelte: 5.0.0-next.175 @@ -8856,19 +8947,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) - sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)) + '@sveltejs/kit': 2.5.22(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8893,16 +8984,16 @@ snapshots: tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4))): dependencies: tailwind-merge: 2.5.2 - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)) - tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)): + tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8921,7 +9012,7 @@ snapshots: postcss: 8.4.41 postcss-import: 15.1.0(postcss@8.4.41) postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4)) postcss-nested: 6.0.1(postcss@8.4.41) postcss-selector-parser: 6.1.0 resolve: 1.22.8 @@ -8988,14 +9079,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.16.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.15 + '@types/node': 20.16.0 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -9049,7 +9140,7 @@ snapshots: ultrahtml@1.5.3: {} - undici-types@5.26.5: {} + undici-types@6.19.6: {} unfetch@4.2.0: {} @@ -9102,13 +9193,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.14.15)(sass@1.77.8): + vite-node@1.6.0(@types/node@20.16.0)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -9120,21 +9211,21 @@ snapshots: - supports-color - terser - vite@5.4.0(@types/node@20.14.15)(sass@1.77.8): + vite@5.4.1(@types/node@20.16.0)(sass@1.77.8): dependencies: esbuild: 0.21.5 postcss: 8.4.41 rollup: 4.17.2 optionalDependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.0 fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.4.0(@types/node@20.14.15)(sass@1.77.8)): + vitefu@0.2.5(vite@5.4.1(@types/node@20.16.0)(sass@1.77.8)): optionalDependencies: - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) - vitest@1.6.0(@types/node@20.14.15)(sass@1.77.8): + vitest@1.6.0(@types/node@20.16.0)(sass@1.77.8): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9153,11 +9244,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.4.0(@types/node@20.14.15)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.14.15)(sass@1.77.8) + vite: 5.4.1(@types/node@20.16.0)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.16.0)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.0 transitivePeerDependencies: - less - lightningcss diff --git a/policies/.cerbos.yaml b/policies/.cerbos.yaml new file mode 100644 index 0000000..5c29127 --- /dev/null +++ b/policies/.cerbos.yaml @@ -0,0 +1,8 @@ +server: + httpListenAddr: ":3592" + +storage: + driver: "disk" + disk: + directory: /policies + watchForChanges: true diff --git a/policies/ticket.yaml b/policies/ticket.yaml new file mode 100644 index 0000000..9d9c583 --- /dev/null +++ b/policies/ticket.yaml @@ -0,0 +1,26 @@ +apiVersion: api.cerbos.dev/v1 +resourcePolicy: + version: default + resource: ticket + + rules: + - actions: + - "*" + effect: EFFECT_ALLOW + roles: + - admin + - actions: + - read + - update + effect: EFFECT_ALLOW + roles: + - customer + condition: + match: + expr: request.resource.attr.cust_id == request.principal.id + - actions: + - create + - delete + effect: EFFECT_DENY + roles: + - customer diff --git a/policies/ticket_test.yaml b/policies/ticket_test.yaml new file mode 100644 index 0000000..20ac4e5 --- /dev/null +++ b/policies/ticket_test.yaml @@ -0,0 +1,43 @@ +name: test ticket + +principals: + adminOne: + id: admin_1 + roles: + - admin + customerOne: + id: cust_1 + roles: + - customer + +resources: + ticketOne: + kind: ticket + id: ticket_1 + attr: + cust_id: cust_1 + +tests: + - name: test ticket + input: + principals: + - adminOne + - customerOne + resources: + - ticketOne + actions: + - create + - delete + + expected: + - principal: adminOne + resource: ticketOne + actions: + create: EFFECT_ALLOW + delete: EFFECT_ALLOW + + - principal: customerOne + resource: ticketOne + actions: + create: EFFECT_DENY + delete: EFFECT_DENY \ No newline at end of file diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts index a927293..af1e188 100644 --- a/src/lib/components/ui/button/index.ts +++ b/src/lib/components/ui/button/index.ts @@ -3,13 +3,13 @@ import type { Button as ButtonPrimitive } from "bits-ui"; import Root from "./button.svelte"; const buttonVariants = tv({ - base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + "border-input bg-background hover:bg-accent hover:text-accent-foreground border", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", diff --git a/src/lib/components/ui/form/form-description.svelte b/src/lib/components/ui/form/form-description.svelte index 7d36254..8877821 100644 --- a/src/lib/components/ui/form/form-description.svelte +++ b/src/lib/components/ui/form/form-description.svelte @@ -9,7 +9,7 @@ diff --git a/src/lib/components/ui/form/form-field-errors.svelte b/src/lib/components/ui/form/form-field-errors.svelte index 9395326..513fb90 100644 --- a/src/lib/components/ui/form/form-field-errors.svelte +++ b/src/lib/components/ui/form/form-field-errors.svelte @@ -12,7 +12,7 @@ diff --git a/src/lib/components/ui/input/index.ts b/src/lib/components/ui/input/index.ts index 17f4e22..75e3bc2 100644 --- a/src/lib/components/ui/input/index.ts +++ b/src/lib/components/ui/input/index.ts @@ -1,6 +1,6 @@ import Root from "./input.svelte"; -type FormInputEvent = T & { +export type FormInputEvent = T & { currentTarget: EventTarget & HTMLInputElement; }; export type InputEvents = { @@ -16,12 +16,14 @@ export type InputEvents = { mouseover: FormInputEvent; mouseenter: FormInputEvent; mouseleave: FormInputEvent; + mousemove: FormInputEvent; paste: FormInputEvent; input: FormInputEvent; + wheel: FormInputEvent; }; export { Root, // - Root as Input + Root as Input, }; diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte index 54dff4a..cab1457 100644 --- a/src/lib/components/ui/input/input.svelte +++ b/src/lib/components/ui/input/input.svelte @@ -1,7 +1,7 @@ diff --git a/src/lib/dtos/signup-username-email.dto.ts b/src/lib/dtos/signup-username-email.dto.ts index b697777..81e259e 100644 --- a/src/lib/dtos/signup-username-email.dto.ts +++ b/src/lib/dtos/signup-username-email.dto.ts @@ -4,7 +4,11 @@ import { refinePasswords } from "$lib/validations/account"; export const signupUsernameEmailDto = z.object({ firstName: z.string().trim().optional(), lastName: z.string().trim().optional(), - email: z.string().trim().max(64, {message: 'Email must be less than 64 characters'}).optional(), + email: z.string() + .trim() + .max(64, {message: 'Email must be less than 64 characters'}) + .email({message: 'Please enter a valid email'}) + .optional(), username: z .string() .trim() @@ -13,8 +17,8 @@ export const signupUsernameEmailDto = z.object({ password: z.string({required_error: 'Password is required'}).trim(), confirm_password: z.string({required_error: 'Confirm Password is required'}).trim() }) - .superRefine(async ({ confirm_password, password }, ctx) => { - return await refinePasswords(confirm_password, password, ctx); + .superRefine(({ confirm_password, password }, ctx) => { + return refinePasswords(confirm_password, password, ctx); }); export type SignupUsernameEmailDto = z.infer diff --git a/src/lib/dtos/update-email.dto.ts b/src/lib/dtos/update-email.dto.ts new file mode 100644 index 0000000..45af50c --- /dev/null +++ b/src/lib/dtos/update-email.dto.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const updateEmailDto = z.object({ + email: z + .string() + .trim() + .max(64, {message: 'Email must be less than 64 characters'}) + .email({message: 'Please enter a valid email'}) +}); + +export type UpdateEmailDto = z.infer; diff --git a/src/lib/dtos/update-profile.dto.ts b/src/lib/dtos/update-profile.dto.ts new file mode 100644 index 0000000..9ea0c6f --- /dev/null +++ b/src/lib/dtos/update-profile.dto.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +export const updateProfileDto = z.object({ + firstName: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}) + .optional(), + lastName: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}) + .optional(), + username: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}) +}); + +export type UpdateProfileDto = z.infer; diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index fdcc6d3..bf60910 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,11 +1,15 @@ import { Hono } from 'hono'; import { inject, injectable } from 'tsyringe'; import { setCookie } from 'hono/cookie'; +import { zValidator } from '@hono/zod-validator'; import type { HonoTypes } from '../types'; import { requireAuth } from "../middleware/auth.middleware"; import type { Controller } from '../interfaces/controller.interface'; import {IamService} from "$lib/server/api/services/iam.service"; import {LuciaProvider} from "$lib/server/api/providers"; +import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; +import {updateProfileDto} from "$lib/dtos/update-profile.dto"; +import {updateEmailDto} from "$lib/dtos/update-email.dto"; @injectable() export class IamController implements Controller { @@ -22,6 +26,18 @@ export class IamController implements Controller { const user = c.var.user; return c.json({ user }); }) + .post('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const user = c.var.user; + const { firstName, lastName, username } = c.req.valid('json'); + await this.iamService.updateProfile(user.id, { first_name: firstName, last_name: lastName, username }); + return c.json({ status: 'success' }); + }) + .post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const user = c.var.user; + const { email } = c.req.valid('json'); + await this.iamService.updateEmail(user.id, email); + return c.json({ status: 'success' }); + }) .post('/logout', requireAuth, async (c) => { const sessionId = c.var.session.id; await this.iamService.logout(sessionId); diff --git a/src/lib/server/api/infrastructure/database/seeds/roles.ts b/src/lib/server/api/infrastructure/database/seeds/roles.ts index 46b94e0..20741ea 100644 --- a/src/lib/server/api/infrastructure/database/seeds/roles.ts +++ b/src/lib/server/api/infrastructure/database/seeds/roles.ts @@ -1,5 +1,5 @@ -import { type db } from '$db'; -import * as schema from '$db/schema'; +import { type db } from '$lib/server/api/infrastructure/database'; +import * as schema from '$lib/server/api/infrastructure/database/tables'; import roles from './data/roles.json'; export default async function seed(db: db) { diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts index 84b44da..e3a33c2 100644 --- a/src/lib/server/api/services/iam.service.ts +++ b/src/lib/server/api/services/iam.service.ts @@ -1,5 +1,8 @@ import { inject, injectable } from 'tsyringe'; import { LuciaProvider } from '../providers/lucia.provider'; +import {UsersService} from "$lib/server/api/services/users.service"; +import type {UpdateProfileDto} from "$lib/dtos/update-profile.dto"; +import type {UpdateEmailDto} from "$lib/dtos/update-email.dto"; /* -------------------------------------------------------------------------- */ /* Service */ @@ -8,7 +11,7 @@ import { LuciaProvider } from '../providers/lucia.provider'; /* ---------------------------------- About --------------------------------- */ /* Services are responsible for handling business logic and data manipulation. -They genreally call on repositories or other services to complete a use-case. +They generally call on repositories or other services to complete a use-case. */ /* ---------------------------------- Notes --------------------------------- */ /* @@ -22,9 +25,24 @@ simple as possible. This makes the service easier to read, test and understand. export class IamService { constructor( @inject(LuciaProvider) private readonly lucia: LuciaProvider, + @inject(UsersService) private readonly usersService: UsersService ) { } async logout(sessionId: string) { return this.lucia.invalidateSession(sessionId); } + + async updateProfile(userId: string, data: UpdateProfileDto) { + return this.usersService.updateUser(userId, { + first_name: data.firstName, + last_name: data.lastName, + username: data.username + }); + } + + async updateEmail(userId: string, data: UpdateEmailDto) { + return this.usersService.updateUser(userId, { + email: data.email + }); + } } diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index 02c2d78..e22bad9 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -1,5 +1,5 @@ import { inject, injectable } from 'tsyringe'; -import { UsersRepository } from '../repositories/users.repository'; +import {type UpdateUser, UsersRepository} from '../repositories/users.repository'; import type {SignupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto"; import {TokensService} from "$lib/server/api/services/tokens.service"; import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository"; @@ -53,6 +53,10 @@ export class UsersService { return user; } + async updateUser(userId: string, data: UpdateUser) { + return this.usersRepository.update(userId, data); + } + async findOneByUsername(username: string) { return this.usersRepository.findOneByUsername(username); } diff --git a/src/routes/(app)/(protected)/profile/+page.server.ts b/src/routes/(app)/(protected)/profile/+page.server.ts index 8157721..6c692fc 100644 --- a/src/routes/(app)/(protected)/profile/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/+page.server.ts @@ -6,43 +6,49 @@ import { message, setError, superValidate } from 'sveltekit-superforms/server'; import { redirect } from 'sveltekit-flash-message/server'; import { changeEmailSchema, profileSchema } from '$lib/validations/account'; import { notSignedInMessage } from '$lib/flashMessages'; -import db from '../../../../db'; +import { db } from '$lib/server/api/infrastructure/database'; import type { PageServerLoad } from './$types'; -import { usersTable, twoFactor } from '$db/schema'; +import { usersTable, credentialsTable } from '$lib/server/api/infrastructure/database/tables'; import { userNotAuthenticated } from '$lib/server/auth-utils'; +import {updateProfileDto} from "$lib/dtos/update-profile.dto"; +import {updateEmailDto} from "$lib/dtos/update-email.dto"; export const load: PageServerLoad = async (event) => { const { locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); - } - const dbUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.id, user!.id!), - }); + const authedUser = await locals.getAuthedUser(); + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event); + } + // if (userNotAuthenticated(user, session)) { + // redirect(302, '/login', notSignedInMessage, event); + // } + // + // const dbUser = await db.query.usersTable.findFirst({ + // where: eq(usersTable.id, user!.id!), + // }); const profileForm = await superValidate(zod(profileSchema), { defaults: { - firstName: dbUser?.first_name ?? '', - lastName: dbUser?.last_name ?? '', - username: dbUser?.username ?? '', + firstName: authedUser?.first_name ?? '', + lastName: authedUser?.last_name ?? '', + username: authedUser?.username ?? '', }, }); const emailForm = await superValidate(zod(changeEmailSchema), { defaults: { - email: dbUser?.email ?? '', + email: authedUser?.email ?? '', }, }); - const twoFactorDetails = await db.query.twoFactor.findFirst({ - where: eq(twoFactor.userId, dbUser!.id!), - }); + // const twoFactorDetails = await db.query.twoFactor.findFirst({ + // where: eq(twoFactor.userId, authedUser!.id!), + // }); return { profileForm, emailForm, - hasSetupTwoFactor: !!twoFactorDetails?.enabled, + hasSetupTwoFactor: false //!!twoFactorDetails?.enabled, }; }; @@ -56,16 +62,24 @@ const changeEmailIfNotEmpty = z.object({ export const actions: Actions = { profileUpdate: async (event) => { - const form = await superValidate(event, zod(profileSchema)); + const { locals } = event; + + const authedUser = await locals.getAuthedUser(); + + if (!authedUser) { + redirect(302, '/login', notSignedInMessage, event); + } + + const form = await superValidate(event, zod(updateProfileDto)); + + const { error } = await locals.api.user.$post({ json: form.data }).then(locals.parseApiResponse); + if (error) return setError(form, 'username', error); if (!form.valid) { return fail(400, { form, }); } - if (!event.locals.user) { - redirect(302, '/login', notSignedInMessage, event); - } try { console.log('updating profile'); @@ -101,7 +115,7 @@ export const actions: Actions = { return message(form, { type: 'success', message: 'Profile updated successfully!' }); }, changeEmail: async (event) => { - const form = await superValidate(event, zod(changeEmailSchema)); + const form = await superValidate(event, zod(updateEmailDto)); const newEmail = form.data?.email; if ( diff --git a/src/routes/(app)/(protected)/profile/+page.svelte b/src/routes/(app)/(protected)/profile/+page.svelte index 9854af1..18dfd18 100644 --- a/src/routes/(app)/(protected)/profile/+page.svelte +++ b/src/routes/(app)/(protected)/profile/+page.svelte @@ -9,6 +9,8 @@ import { Label } from '$lib/components/ui/label'; import { Input } from '$components/ui/input'; import { Button } from '$components/ui/button'; + import { updateProfileDto } from '$lib/dtos/update-profile.dto'; + import { updateEmailDto } from '$lib/dtos/update-email.dto'; const { data } = $props(); @@ -16,7 +18,7 @@ const { form: profileForm, errors: profileErrors, enhance: profileEnhance } = superForm(data.profileForm, { taintedMessage: null, - validators: zodClient(profileSchema), + validators: zodClient(updateProfileDto), delayMs: 500, multipleSubmits: 'prevent', syncFlashMessage: true, @@ -27,7 +29,7 @@ const { form: emailForm, errors: emailErrors, enhance: emailEnhance } = superForm(data.emailForm, { taintedMessage: null, - validators: zodClient(changeEmailSchema), + validators: zodClient(updateEmailDto), delayMs: 500, multipleSubmits: 'prevent', syncFlashMessage: true, diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/sign-up/+page.server.ts index ffd8016..abc18b3 100644 --- a/src/routes/(auth)/sign-up/+page.server.ts +++ b/src/routes/(auth)/sign-up/+page.server.ts @@ -1,24 +1,10 @@ import { fail, error, type Actions } from '@sveltejs/kit'; -import { Argon2id } from 'oslo/password'; -import { eq } from 'drizzle-orm'; import { zod } from 'sveltekit-superforms/adapters'; import { setError, superValidate } from 'sveltekit-superforms/server'; import { redirect } from 'sveltekit-flash-message/server'; -import { RateLimiter } from 'sveltekit-rate-limiter/server'; import type { PageServerLoad } from './$types'; -import { lucia } from '$lib/server/auth'; -import { signUpSchema } from '$lib/validations/auth'; -import { add_user_to_role } from '$server/roles'; -import db from '../../../db'; -import { collections, usersTable, wishlists } from '$db/schema'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; import {signupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto"; -const limiter = new RateLimiter({ - // A rate is defined by [number, unit] - IPUA: [5, 'm'], -}); - const signUpDefaults = { firstName: '', lastName: '', diff --git a/src/routes/(auth)/sign-up/+page.svelte b/src/routes/(auth)/sign-up/+page.svelte index 7c9551a..a126a5b 100644 --- a/src/routes/(auth)/sign-up/+page.svelte +++ b/src/routes/(auth)/sign-up/+page.svelte @@ -14,6 +14,7 @@ import * as Collapsible from '$lib/components/ui/collapsible'; import { send, receive } from '$lib/utils/pageCrossfade'; import { boredState } from '$lib/stores/boredState.js'; + import { signupUsernameEmailDto } from '$lib/dtos/signup-username-email.dto'; export let data; @@ -28,7 +29,7 @@ } }, taintedMessage: null, - validators: zodClient(signUpSchema), + validators: zodClient(signupUsernameEmailDto), delayMs: 0 }); From 940b485273ea6c426a83c080f8f7a532d781ee2f Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sun, 18 Aug 2024 23:00:59 -0700 Subject: [PATCH 28/34] Starting update profile with checks on services. --- src/lib/page_loading_indicator.svelte | 2 +- src/lib/server/api/controllers/iam.controller.ts | 5 +++-- .../api/repositories/wishlists.repository.ts | 2 +- src/lib/server/api/services/iam.service.ts | 16 +++++++++++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/lib/page_loading_indicator.svelte b/src/lib/page_loading_indicator.svelte index 7b1f203..592f7a2 100644 --- a/src/lib/page_loading_indicator.svelte +++ b/src/lib/page_loading_indicator.svelte @@ -34,7 +34,7 @@
-
+
\ No newline at end of file + diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index 81ece50..b5f7757 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -1,23 +1,23 @@ -import { fail, type Actions } from '@sveltejs/kit'; -import { eq, or } from 'drizzle-orm'; -import { Argon2id } from 'oslo/password'; -import { zod } from 'sveltekit-superforms/adapters'; -import { setError, superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { db } from '../../../lib/server/api/infrastructure/database/index'; -import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia'; -import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables'; -import type { PageServerLoad } from './$types'; -import {signinUsernameDto} from "$lib/dtos/signin-username.dto"; +import { fail, type Actions } from '@sveltejs/kit' +import { eq, or } from 'drizzle-orm' +import { Argon2id } from 'oslo/password' +import { zod } from 'sveltekit-superforms/adapters' +import { setError, superValidate } from 'sveltekit-superforms/server' +import { redirect } from 'sveltekit-flash-message/server' +import { db } from '../../../lib/server/api/infrastructure/database/index' +import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia' +import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables' +import type { PageServerLoad } from './$types' +import { signinUsernameDto } from '$lib/dtos/signin-username.dto' export const load: PageServerLoad = async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (authedUser) { - const message = { type: 'success', message: 'You are already signed in' } as const; - throw redirect('/', message, event); + const message = { type: 'success', message: 'You are already signed in' } as const + throw redirect('/', message, event) } // if (userFullyAuthenticated(user, session)) { @@ -31,34 +31,34 @@ export const load: PageServerLoad = async (event) => { // ...sessionCookie.attributes, // }); // } - const form = await superValidate(event, zod(signinUsernameDto)); + const form = await superValidate(event, zod(signinUsernameDto)) return { form, - }; -}; + } +} export const actions: Actions = { default: async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (authedUser) { - const message = { type: 'success', message: 'You are already signed in' } as const; - throw redirect('/', message, event); + const message = { type: 'success', message: 'You are already signed in' } as const + throw redirect('/', message, event) } - const form = await superValidate(event, zod(signinUsernameDto)); + const form = await superValidate(event, zod(signinUsernameDto)) - const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse); - if (error) return setError(form, 'username', error); + const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse) + if (error) return setError(form, 'username', error) if (!form.valid) { - form.data.password = ''; + form.data.password = '' return fail(400, { form, - }); + }) } // let session; @@ -75,59 +75,59 @@ export const actions: Actions = { // let twoFactorDetails; // try { - // const password = form.data.password; - // console.log('user', JSON.stringify(user, null, 2)); - // - // if (!user?.hashed_password) { - // console.log('invalid username/password'); - // form.data.password = ''; - // return setError(form, 'password', 'Your username or password is incorrect.'); - // } - // - // const validPassword = await new Argon2id().verify(user.hashed_password, password); - // if (!validPassword) { - // console.log('invalid password'); - // form.data.password = ''; - // return setError(form, 'password', 'Your username or password is incorrect.'); - // } - // - // console.log('ip', locals.ip); - // console.log('country', locals.country); - // - // twoFactorDetails = await db.query.twoFactor.findFirst({ - // where: eq(twoFactor.userId, user?.id), - // }); - // - // if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { - // await db.update(twoFactor).set({ - // initiatedTime: new Date(), - // }); - // - // session = await lucia.createSession(user.id, { - // ip_country: locals.country, - // ip_address: locals.ip, - // twoFactorAuthEnabled: - // twoFactorDetails?.enabled && - // twoFactorDetails?.secret !== null && - // twoFactorDetails?.secret !== '', - // isTwoFactorAuthenticated: false, - // }); - // } else { - // session = await lucia.createSession(user.id, { - // ip_country: locals.country, - // ip_address: locals.ip, - // twoFactorAuthEnabled: false, - // isTwoFactorAuthenticated: false, - // }); - // } - // console.log('logging in session', session); - // sessionCookie = lucia.createSessionCookie(session.id); - // console.log('logging in session cookie', sessionCookie); + // const password = form.data.password; + // console.log('user', JSON.stringify(user, null, 2)); + // + // if (!user?.hashed_password) { + // console.log('invalid username/password'); + // form.data.password = ''; + // return setError(form, 'password', 'Your username or password is incorrect.'); + // } + // + // const validPassword = await new Argon2id().verify(user.hashed_password, password); + // if (!validPassword) { + // console.log('invalid password'); + // form.data.password = ''; + // return setError(form, 'password', 'Your username or password is incorrect.'); + // } + // + // console.log('ip', locals.ip); + // console.log('country', locals.country); + // + // twoFactorDetails = await db.query.twoFactor.findFirst({ + // where: eq(twoFactor.userId, user?.id), + // }); + // + // if (twoFactorDetails?.secret && twoFactorDetails?.enabled) { + // await db.update(twoFactor).set({ + // initiatedTime: new Date(), + // }); + // + // session = await lucia.createSession(user.id, { + // ip_country: locals.country, + // ip_address: locals.ip, + // twoFactorAuthEnabled: + // twoFactorDetails?.enabled && + // twoFactorDetails?.secret !== null && + // twoFactorDetails?.secret !== '', + // isTwoFactorAuthenticated: false, + // }); + // } else { + // session = await lucia.createSession(user.id, { + // ip_country: locals.country, + // ip_address: locals.ip, + // twoFactorAuthEnabled: false, + // isTwoFactorAuthenticated: false, + // }); + // } + // console.log('logging in session', session); + // sessionCookie = lucia.createSessionCookie(session.id); + // console.log('logging in session cookie', sessionCookie); } catch (e) { // TODO: need to return error message to the client - console.error(e); - form.data.password = ''; - return setError(form, '', 'Your username or password is incorrect.'); + console.error(e) + form.data.password = '' + return setError(form, '', 'Your username or password is incorrect.') } // console.log('setting session cookie', sessionCookie); @@ -136,8 +136,8 @@ export const actions: Actions = { // ...sessionCookie.attributes, // }); - form.data.username = ''; - form.data.password = ''; + form.data.username = '' + form.data.password = '' // if ( // twoFactorDetails?.enabled && @@ -152,4 +152,4 @@ export const actions: Actions = { // redirect(302, '/', message, event); // } }, -}; +} diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 0239c9f..195f233 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -87,25 +87,4 @@ {/snippet} \ No newline at end of file From ead20829e4b6ea5600d7c4ed83447b4f30fdc368 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sun, 25 Aug 2024 17:53:32 -0700 Subject: [PATCH 30/34] Fixing collection by CUID2 and fixing the controllers for these. --- package.json | 6 +- pnpm-lock.yaml | 63 +++--- src/env.ts | 17 +- src/lib/server/api/common/config.ts | 7 +- .../api/controllers/collection.controller.ts | 4 +- .../api/controllers/wishlist.controller.ts | 1 - .../(protected)/collections/+page.server.ts | 4 +- .../{[id] => [cuid]}/+error.svelte | 0 .../{[id] => [cuid]}/+page.server.ts | 192 ++++++++++-------- .../collections/{[id] => [cuid]}/+page.svelte | 0 .../security/two-factor/+page.server.ts | 4 +- .../wishlists/[cuid]/+page.server.ts | 2 - 12 files changed, 165 insertions(+), 135 deletions(-) rename src/routes/(app)/(protected)/collections/{[id] => [cuid]}/+error.svelte (100%) rename src/routes/(app)/(protected)/collections/{[id] => [cuid]}/+page.server.ts (51%) rename src/routes/(app)/(protected)/collections/{[id] => [cuid]}/+page.svelte (100%) diff --git a/package.json b/package.json index 9cebec6..5d4c785 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "sveltekit-superforms": "^2.17.0", "tailwindcss": "^3.4.10", "ts-node": "^10.9.2", - "tslib": "^2.6.3", - "tsx": "^4.17.0", + "tslib": "^2.7.0", + "tsx": "^4.18.0", "typescript": "^5.5.4", "vite": "^5.4.2", "vitest": "^1.6.0", @@ -77,7 +77,7 @@ "type": "module", "dependencies": { "@fontsource/fira-mono": "^5.0.14", - "@hono/swagger-ui": "^0.4.0", + "@hono/swagger-ui": "^0.4.1", "@hono/zod-openapi": "^0.15.3", "@hono/zod-validator": "^0.2.2", "@iconify-icons/line-md": "^1.2.30", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ffc797..739aaed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^5.0.14 version: 5.0.14 '@hono/swagger-ui': - specifier: ^0.4.0 - version: 0.4.0(hono@4.5.8) + specifier: ^0.4.1 + version: 0.4.1(hono@4.5.8) '@hono/zod-openapi': specifier: ^0.15.3 version: 0.15.3(hono@4.5.8)(zod@3.23.8) @@ -248,7 +248,7 @@ importers: version: 16.1.0(postcss@8.4.41) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0) postcss-preset-env: specifier: ^9.6.0 version: 9.6.0(postcss@8.4.41) @@ -272,7 +272,7 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) + version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) @@ -281,7 +281,7 @@ importers: version: 3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 @@ -301,11 +301,11 @@ importers: specifier: ^10.9.2 version: 10.9.2(@types/node@20.16.1)(typescript@5.5.4) tslib: - specifier: ^2.6.3 - version: 2.6.3 + specifier: ^2.7.0 + version: 2.7.0 tsx: - specifier: ^4.17.0 - version: 4.17.0 + specifier: ^4.18.0 + version: 4.18.0 typescript: specifier: ^5.5.4 version: 5.5.4 @@ -1270,8 +1270,8 @@ packages: '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - '@hono/swagger-ui@0.4.0': - resolution: {integrity: sha512-8lF+dexzgV2HRM1R/gf49E5djroq4jVMYMSwLkSF9pT0I6sYhuqirFDCRFrBtbbLCBsKzw6f2MF5rS+WY3d7Nw==} + '@hono/swagger-ui@0.4.1': + resolution: {integrity: sha512-kPaJatHffeYQ3yVkHo878hCqwfapqx54FczJVJ+eRWt8J4biyVVMIdCAJb6MyA8bcnHUoTmUpPc7OJAV1VTg2g==} peerDependencies: hono: '*' @@ -4734,8 +4734,11 @@ packages: tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tsx@4.17.0: - resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tsx@4.18.0: + resolution: {integrity: sha512-a1jaKBSVQkd6yEc1/NI7G6yHFfefIcuf3QJST7ZEyn4oQnxLYrZR5uZAM8UrwUa3Ge8suiZHcNS1gNrEvmobqg==} engines: {node: '>=18.0.0'} hasBin: true @@ -5320,17 +5323,17 @@ snapshots: '@emnapi/core@0.45.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 optional: true '@emnapi/runtime@0.45.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 optional: true '@emnapi/runtime@1.2.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 optional: true '@esbuild-kit/core-utils@3.3.2': @@ -5692,7 +5695,7 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/swagger-ui@0.4.0(hono@4.5.8)': + '@hono/swagger-ui@0.4.1(hono@4.5.8)': dependencies: hono: 4.5.8 @@ -6370,7 +6373,7 @@ snapshots: '@swc/helpers@0.5.11': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@tsconfig/node10@1.0.11': {} @@ -6382,7 +6385,7 @@ snapshots: '@tybys/wasm-util@0.8.3': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 optional: true '@types/cookie@0.6.0': {} @@ -6754,7 +6757,7 @@ snapshots: msgpackr: 1.11.0 node-abort-controller: 3.1.1 semver: 7.6.3 - tslib: 2.6.3 + tslib: 2.7.0 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -8298,14 +8301,14 @@ snapshots: postcss: 8.4.41 ts-node: 10.9.2(@types/node@20.16.1)(typescript@5.5.4) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: jiti: 1.21.6 postcss: 8.4.41 - tsx: 4.17.0 + tsx: 4.18.0 postcss-logical@7.0.1(postcss@8.4.41): dependencies: @@ -8875,14 +8878,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): + svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.0.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - '@babel/core' @@ -8938,7 +8941,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8948,16 +8951,16 @@ snapshots: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0) sass: 1.77.8 typescript: 5.5.4 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.17.0) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0) sass: 1.77.8 typescript: 5.5.4 @@ -9169,7 +9172,9 @@ snapshots: tslib@2.6.3: {} - tsx@4.17.0: + tslib@2.7.0: {} + + tsx@4.18.0: dependencies: esbuild: 0.23.0 get-tsconfig: 4.7.5 diff --git a/src/env.ts b/src/env.ts index 469b805..33366fa 100644 --- a/src/env.ts +++ b/src/env.ts @@ -10,21 +10,22 @@ const stringBoolean = z.coerce .default('false'); const EnvSchema = z.object({ - NODE_ENV: z.string().default('development'), + ADMIN_USERNAME: z.string(), + ADMIN_PASSWORD: z.string(), DATABASE_USER: z.string(), DATABASE_PASSWORD: z.string(), DATABASE_HOST: z.string(), DATABASE_PORT: z.coerce.number(), DATABASE_DB: z.string(), + DB_MIGRATING: stringBoolean, + DB_SEEDING: stringBoolean, + NODE_ENV: z.string().default('development'), PUBLIC_SITE_NAME: z.string(), PUBLIC_SITE_URL: z.string(), PUBLIC_UMAMI_DO_NOT_TRACK: z.string(), PUBLIC_UMAMI_ID: z.string(), PUBLIC_UMAMI_URL: z.string(), - DB_MIGRATING: stringBoolean, - DB_SEEDING: stringBoolean, - ADMIN_USERNAME: z.string(), - ADMIN_PASSWORD: z.string(), + REDIS_URL: z.string(), TWO_FACTOR_TIMEOUT: z.coerce.number().default(300000), }); @@ -37,9 +38,9 @@ try { } catch (error) { if (error instanceof ZodError) { let message = 'Missing required values in .env:\n'; - error.issues.forEach((issue) => { - message += issue.path[0] + '\n'; - }); + for (const issue of error.issues) { + message += `${issue.path[0]}\n`; + } const e = new Error(message); e.stack = ''; throw e; diff --git a/src/lib/server/api/common/config.ts b/src/lib/server/api/common/config.ts index 8f82d9d..35d0ae9 100644 --- a/src/lib/server/api/common/config.ts +++ b/src/lib/server/api/common/config.ts @@ -2,13 +2,14 @@ import env from '../../../../env'; const isPreview = process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development'; -let domain; +let domain: string; if (process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production') { domain = 'boredgame.vercel.app'; -} else if (isPreview) { +} else if (isPreview && process.env.VERCEL_BRANCH_URL !== undefined) { domain = process.env.VERCEL_BRANCH_URL; } else { domain = 'localhost'; } -export const config = { ...env, isProduction: process.env.NODE_ENV === 'production' || process.env.VERCEL_ENV === 'production', domain }; +export const config = { ...env, isProduction: process.env.NODE_ENV === 'production' + || process.env.VERCEL_ENV === 'production', domain }; diff --git a/src/lib/server/api/controllers/collection.controller.ts b/src/lib/server/api/controllers/collection.controller.ts index 7bc8379..8f61bd1 100644 --- a/src/lib/server/api/controllers/collection.controller.ts +++ b/src/lib/server/api/controllers/collection.controller.ts @@ -24,8 +24,8 @@ export class CollectionController implements Controller { }) .get('/:cuid', requireAuth, async (c) => { const cuid = c.req.param('cuid'); - const user = await this.collectionsService.findOneByCuid(cuid); - return c.json({ user }); + const collection = await this.collectionsService.findOneByCuid(cuid); + return c.json({ collection }); }); } } diff --git a/src/lib/server/api/controllers/wishlist.controller.ts b/src/lib/server/api/controllers/wishlist.controller.ts index 3a8dbb7..90fdc15 100644 --- a/src/lib/server/api/controllers/wishlist.controller.ts +++ b/src/lib/server/api/controllers/wishlist.controller.ts @@ -23,7 +23,6 @@ export class WishlistController implements Controller { }) .get('/:cuid', requireAuth, async (c) => { const cuid = c.req.param('cuid') - console.log(cuid) const wishlist = await this.wishlistsService.findOneByCuid(cuid) return c.json({ wishlist }); }); diff --git a/src/routes/(app)/(protected)/collections/+page.server.ts b/src/routes/(app)/(protected)/collections/+page.server.ts index bd734aa..07e63b5 100644 --- a/src/routes/(app)/(protected)/collections/+page.server.ts +++ b/src/routes/(app)/(protected)/collections/+page.server.ts @@ -4,8 +4,8 @@ import { superValidate } from 'sveltekit-superforms/server'; import { zod } from 'sveltekit-superforms/adapters'; import { redirect } from 'sveltekit-flash-message/server'; import { modifyListGameSchema } from '$lib/validations/zod-schemas'; -import db from '../../../../db'; -import { collection_items, collections, games } from '$db/schema'; +import { db } from '$lib/server/api/infrastructure/database'; +import { collection_items, collections, games } from '$lib/server/api/infrastructure/database/tables'; import { notSignedInMessage } from '$lib/flashMessages'; import { userNotAuthenticated } from '$lib/server/auth-utils'; diff --git a/src/routes/(app)/(protected)/collections/[id]/+error.svelte b/src/routes/(app)/(protected)/collections/[cuid]/+error.svelte similarity index 100% rename from src/routes/(app)/(protected)/collections/[id]/+error.svelte rename to src/routes/(app)/(protected)/collections/[cuid]/+error.svelte diff --git a/src/routes/(app)/(protected)/collections/[id]/+page.server.ts b/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts similarity index 51% rename from src/routes/(app)/(protected)/collections/[id]/+page.server.ts rename to src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts index b99d52c..c8e3e45 100644 --- a/src/routes/(app)/(protected)/collections/[id]/+page.server.ts +++ b/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts @@ -3,100 +3,126 @@ import { and, eq } from 'drizzle-orm'; import { zod } from 'sveltekit-superforms/adapters'; import { superValidate } from 'sveltekit-superforms/server'; import { redirect } from 'sveltekit-flash-message/server'; -import { type ListGame, modifyListGameSchema } from '$lib/validations/zod-schemas'; -import db from '../../../../../db'; +import { modifyListGameSchema } from '$lib/validations/zod-schemas'; +import { db } from '$lib/server/api/infrastructure/database'; import { notSignedInMessage } from '$lib/flashMessages.js'; -import { collections, games, collection_items } from '$db/schema'; -import { search_schema } from '$lib/zodValidation'; +import { collections, games, collection_items } from '$lib/server/api/infrastructure/database/tables'; import { userNotAuthenticated } from '$lib/server/auth-utils'; export async function load(event) { - const { locals, params, url } = event; - const { user, session } = locals; - const { id } = params; + const { params, locals } = event; + const { cuid } = params; - if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); - } - const searchParams = Object.fromEntries(url?.searchParams); - console.log('searchParams', searchParams); - const q = searchParams?.q; - const limit = parseInt(searchParams?.limit) || 10; - const skip = parseInt(searchParams?.skip) || 0; - - const searchData = { - q, - limit, - skip, - }; - - const searchForm = await superValidate(searchData, zod(search_schema)); - const listManageForm = await superValidate(zod(modifyListGameSchema)); - - const collection = await db.query.collections.findFirst({ - columns: { - id: true, - cuid: true, - name: true, - }, - where: and(eq(collections.user_id, user!.id!), eq(collections.cuid, id)), - }); - console.log('collection', collection); - - if (!collection) { - console.log('Collection was not found'); - error(404, 'Collection was not found'); + const authedUser = await locals.getAuthedUser(); + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event); } - const collectionItems = await db.query.collection_items.findMany({ - columns: { - collection_id: true, - times_played: true, - }, - where: eq(collection_items.collection_id, collection.id), - with: { - game: { - columns: { - id: true, - name: true, - thumb_url: true, - }, - }, - }, - offset: skip, - limit, - }); + try { + const { data, errors } = await locals.api.collections[':cuid'].$get({ + param: { cuid } + }).then(locals.parseApiResponse); - console.log('collection_items', collectionItems); - - const items: ListGame[] = []; - for (const item of collectionItems) { - console.log('item', item); - const game = item.game; - if (game) { - items.push({ - game_id: '', - in_wishlist: false, - wishlist_id: '', - id: game.id, - collection_id: item.collection_id, - game_name: game.name ?? "Game doesn't have a name", - thumb_url: game.thumb_url, - times_played: item.times_played ?? 0, - in_collection: true, - }); + if (errors) { + return error(500, 'Failed to fetch collection'); } + + const { collection } = data; + + if (!collection) { + redirect(302, '/404'); + } + + console.log('collection', collection); + + return { + collection, + }; + } catch (e) { + console.error(e); } - return { - searchForm, - listManageForm, - collection: { - name: collection.name, - cuid: collection.cuid ?? '', - }, - items, - }; + redirect(302, '/404'); + + // const searchParams = Object.fromEntries(url?.searchParams); + // console.log('searchParams', searchParams); + // const q = searchParams?.q; + // const limit = parseInt(searchParams?.limit) || 10; + // const skip = parseInt(searchParams?.skip) || 0; + // + // const searchData = { + // q, + // limit, + // skip, + // }; + // + // const searchForm = await superValidate(searchData, zod(search_schema)); + // const listManageForm = await superValidate(zod(modifyListGameSchema)); + // + // const collection = await db.query.collections.findFirst({ + // columns: { + // id: true, + // cuid: true, + // name: true, + // }, + // where: and(eq(collections.user_id, user!.id!), eq(collections.cuid, id)), + // }); + // console.log('collection', collection); + + // if (!collection) { + // console.log('Collection was not found'); + // error(404, 'Collection was not found'); + // } + // + // const collectionItems = await db.query.collection_items.findMany({ + // columns: { + // collection_id: true, + // times_played: true, + // }, + // where: eq(collection_items.collection_id, collection.id), + // with: { + // game: { + // columns: { + // id: true, + // name: true, + // thumb_url: true, + // }, + // }, + // }, + // offset: skip, + // limit, + // }); + // + // console.log('collection_items', collectionItems); + // + // const items: ListGame[] = []; + // for (const item of collectionItems) { + // console.log('item', item); + // const game = item.game; + // if (game) { + // items.push({ + // game_id: '', + // in_wishlist: false, + // wishlist_id: '', + // id: game.id, + // collection_id: item.collection_id, + // game_name: game.name ?? "Game doesn't have a name", + // thumb_url: game.thumb_url, + // times_played: item.times_played ?? 0, + // in_collection: true, + // }); + // } + // } + // + // return { + // searchForm, + // listManageForm, + // collection: { + // name: collection.name, + // cuid: collection.cuid ?? '', + // }, + // items, + // }; } export const actions: Actions = { diff --git a/src/routes/(app)/(protected)/collections/[id]/+page.svelte b/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte similarity index 100% rename from src/routes/(app)/(protected)/collections/[id]/+page.svelte rename to src/routes/(app)/(protected)/collections/[cuid]/+page.svelte diff --git a/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts b/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts index 9b92587..a074ba2 100644 --- a/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/two-factor/+page.server.ts @@ -12,8 +12,8 @@ import { redirect, setFlash } from 'sveltekit-flash-message/server'; import type { PageServerLoad } from '../../$types'; import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account'; import { notSignedInMessage } from '$lib/flashMessages'; -import db from '../../../../../../db'; -import { recoveryCodes, twoFactor, usersTable } from '$db/schema'; +import db from '$lib/server/api/infrastructure/database'; +import { recoveryCodes, twoFactor, usersTable } from '$lib/server/api/infrastructure/database/tables'; import { userNotAuthenticated } from '$lib/server/auth-utils'; import env from '../../../../../../env'; diff --git a/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts b/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts index abaf1d3..1e60741 100644 --- a/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts +++ b/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts @@ -28,9 +28,7 @@ export async function load(event) { if (errors) { return error(500, 'Failed to fetch wishlist'); } - console.log('data', data); const { wishlist } = data; - console.log('wishlist', wishlist); if (!wishlist) { redirect(302, '/404'); From df582f15340116e4ab8706b36c265a0d9e00f54b Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 29 Aug 2024 16:12:40 -0700 Subject: [PATCH 31/34] Adding mfa page instead of 2FA, starting controller based password verification and totp generation. --- .../cleanupSessions/+server.ts | 0 .../api => oldApis}/collection/+server.ts | 0 .../collection}/[id]/items/+server.ts | 12 +- .../collection/[id]/search/+server.ts | 6 +- .../external/game/[id]/+server.ts | 0 .../external/search/+server.ts | 0 .../api => oldApis}/games/[id]/+server.ts | 0 .../api => oldApis}/games/random/+server.ts | 0 .../api => oldApis}/games/search/+server.ts | 0 .../api => oldApis}/publisher/+server.ts | 0 .../api => oldApis}/publisher/[id]/+server.ts | 0 .../reset-password/+server.ts | 4 +- .../reset-password/[token]/+server.ts | 19 +- .../api => oldApis}/wishlist/+server.ts | 0 .../wishlist}/[id]/items/+server.ts | 12 +- .../wishlist/[id]/search/+server.ts | 0 package.json | 6 +- pnpm-lock.yaml | 78 +++---- src/lib/components/Header.svelte | 2 +- src/lib/components/signup.svelte | 2 +- src/lib/dtos/verify-password.dto.ts | 7 + .../server/api/controllers/iam.controller.ts | 10 + .../server/api/controllers/mfa.controller.ts | 39 ++++ src/lib/server/api/index.ts | 2 + .../api/infrastructure/database/seed.ts | 2 +- .../infrastructure/database/tables/index.ts | 2 +- ...coveryCodes.ts => recovery-codes.table.ts} | 4 +- .../repositories/credentials.repository.ts | 21 ++ src/lib/server/api/services/iam.service.ts | 9 + src/lib/server/api/services/totp.service.ts | 36 ++++ src/lib/server/api/services/users.service.ts | 13 ++ .../(app)/(protected)/list/+layout.server.ts | 14 +- .../(app)/(protected)/profile/+page.server.ts | 198 +++++++++--------- .../(app)/(protected)/profile/+page.svelte | 12 +- .../{two-factor => mfa}/+page.server.ts | 198 ++++++++---------- .../security/{two-factor => mfa}/+page.svelte | 4 +- .../recovery-codes/+page.server.ts | 23 +- .../recovery-codes/+page.svelte | 0 .../security/password/change/+page.server.ts | 29 +-- src/routes/(app)/game/[id]/+page.svelte | 2 +- src/routes/(auth)/+layout.svelte | 4 +- .../{sign-up => signup}/+page.server.ts | 0 .../(auth)/{sign-up => signup}/+page.svelte | 2 +- svelte.config.js | 3 +- 44 files changed, 443 insertions(+), 332 deletions(-) rename {src/routes/api/crons => oldApis}/cleanupSessions/+server.ts (100%) rename {src/routes/api => oldApis}/collection/+server.ts (100%) rename {src/routes/api/wishlist => oldApis/collection}/[id]/items/+server.ts (96%) rename {src/routes/api => oldApis}/collection/[id]/search/+server.ts (89%) rename {src/routes/api => oldApis}/external/game/[id]/+server.ts (100%) rename {src/routes/api => oldApis}/external/search/+server.ts (100%) rename {src/routes/api => oldApis}/games/[id]/+server.ts (100%) rename {src/routes/api => oldApis}/games/random/+server.ts (100%) rename {src/routes/api => oldApis}/games/search/+server.ts (100%) rename {src/routes/api => oldApis}/publisher/+server.ts (100%) rename {src/routes/api => oldApis}/publisher/[id]/+server.ts (100%) rename {src/routes/api/auth => oldApis}/reset-password/+server.ts (86%) rename {src/routes/api/auth => oldApis}/reset-password/[token]/+server.ts (61%) rename {src/routes/api => oldApis}/wishlist/+server.ts (100%) rename {src/routes/api/collection => oldApis/wishlist}/[id]/items/+server.ts (96%) rename {src/routes/api => oldApis}/wishlist/[id]/search/+server.ts (100%) create mode 100644 src/lib/dtos/verify-password.dto.ts create mode 100644 src/lib/server/api/controllers/mfa.controller.ts rename src/lib/server/api/infrastructure/database/tables/{recoveryCodes.ts => recovery-codes.table.ts} (74%) create mode 100644 src/lib/server/api/services/totp.service.ts rename src/routes/(app)/(protected)/profile/security/{two-factor => mfa}/+page.server.ts (53%) rename src/routes/(app)/(protected)/profile/security/{two-factor => mfa}/+page.svelte (93%) rename src/routes/(app)/(protected)/profile/security/{two-factor => mfa}/recovery-codes/+page.server.ts (72%) rename src/routes/(app)/(protected)/profile/security/{two-factor => mfa}/recovery-codes/+page.svelte (100%) rename src/routes/(auth)/{sign-up => signup}/+page.server.ts (100%) rename src/routes/(auth)/{sign-up => signup}/+page.svelte (98%) diff --git a/src/routes/api/crons/cleanupSessions/+server.ts b/oldApis/cleanupSessions/+server.ts similarity index 100% rename from src/routes/api/crons/cleanupSessions/+server.ts rename to oldApis/cleanupSessions/+server.ts diff --git a/src/routes/api/collection/+server.ts b/oldApis/collection/+server.ts similarity index 100% rename from src/routes/api/collection/+server.ts rename to oldApis/collection/+server.ts diff --git a/src/routes/api/wishlist/[id]/items/+server.ts b/oldApis/collection/[id]/items/+server.ts similarity index 96% rename from src/routes/api/wishlist/[id]/items/+server.ts rename to oldApis/collection/[id]/items/+server.ts index fc7727d..4d9ebe1 100644 --- a/src/routes/api/wishlist/[id]/items/+server.ts +++ b/oldApis/collection/[id]/items/+server.ts @@ -1,6 +1,6 @@ -import { error, json } from '@sveltejs/kit'; - -export async function GET({ url, locals, params }) { - const searchParams = Object.fromEntries(url.searchParams); - return json({}); -} +import { error, json } from '@sveltejs/kit'; + +export async function GET({ url, locals, params }) { + const searchParams = Object.fromEntries(url.searchParams); + return json({}); +} diff --git a/src/routes/api/collection/[id]/search/+server.ts b/oldApis/collection/[id]/search/+server.ts similarity index 89% rename from src/routes/api/collection/[id]/search/+server.ts rename to oldApis/collection/[id]/search/+server.ts index 046f107..528c6f6 100644 --- a/src/routes/api/collection/[id]/search/+server.ts +++ b/oldApis/collection/[id]/search/+server.ts @@ -1,14 +1,14 @@ import { error, json } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; -import db from '../../../../../db'; +import {db} from '$lib/server/api/infrastructure/database'; import { collection_items, usersTable } from '$db/schema'; // Search a user's collection export async function GET({ url, locals, params }) { const searchParams = Object.fromEntries(url.searchParams); const q = searchParams?.q || ''; - const limit = parseInt(searchParams?.limit) || 10; - const skip = parseInt(searchParams?.skip) || 0; + const limit = Number.parseInt(searchParams?.limit) || 10; + const skip = Number.parseInt(searchParams?.skip) || 0; const order = searchParams?.order || 'asc'; const sort = searchParams?.sort || 'name'; const collection_id = params.id; diff --git a/src/routes/api/external/game/[id]/+server.ts b/oldApis/external/game/[id]/+server.ts similarity index 100% rename from src/routes/api/external/game/[id]/+server.ts rename to oldApis/external/game/[id]/+server.ts diff --git a/src/routes/api/external/search/+server.ts b/oldApis/external/search/+server.ts similarity index 100% rename from src/routes/api/external/search/+server.ts rename to oldApis/external/search/+server.ts diff --git a/src/routes/api/games/[id]/+server.ts b/oldApis/games/[id]/+server.ts similarity index 100% rename from src/routes/api/games/[id]/+server.ts rename to oldApis/games/[id]/+server.ts diff --git a/src/routes/api/games/random/+server.ts b/oldApis/games/random/+server.ts similarity index 100% rename from src/routes/api/games/random/+server.ts rename to oldApis/games/random/+server.ts diff --git a/src/routes/api/games/search/+server.ts b/oldApis/games/search/+server.ts similarity index 100% rename from src/routes/api/games/search/+server.ts rename to oldApis/games/search/+server.ts diff --git a/src/routes/api/publisher/+server.ts b/oldApis/publisher/+server.ts similarity index 100% rename from src/routes/api/publisher/+server.ts rename to oldApis/publisher/+server.ts diff --git a/src/routes/api/publisher/[id]/+server.ts b/oldApis/publisher/[id]/+server.ts similarity index 100% rename from src/routes/api/publisher/[id]/+server.ts rename to oldApis/publisher/[id]/+server.ts diff --git a/src/routes/api/auth/reset-password/+server.ts b/oldApis/reset-password/+server.ts similarity index 86% rename from src/routes/api/auth/reset-password/+server.ts rename to oldApis/reset-password/+server.ts index d793b58..c661f15 100644 --- a/src/routes/api/auth/reset-password/+server.ts +++ b/oldApis/reset-password/+server.ts @@ -1,7 +1,7 @@ -import db from '../../../../db'; +import { db } from '$lib/server/api/infrastructure/database'; import { error } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; -import { usersTable } from '$db/schema'; +import { usersTable } from '$lib/server/api/infrastructure/database/tables'; import { createPasswordResetToken } from '$lib/server/auth-utils.js'; import { PUBLIC_SITE_URL } from '$env/static/public'; diff --git a/src/routes/api/auth/reset-password/[token]/+server.ts b/oldApis/reset-password/[token]/+server.ts similarity index 61% rename from src/routes/api/auth/reset-password/[token]/+server.ts rename to oldApis/reset-password/[token]/+server.ts index 938137d..2b50676 100644 --- a/src/routes/api/auth/reset-password/[token]/+server.ts +++ b/oldApis/reset-password/[token]/+server.ts @@ -1,9 +1,8 @@ import { eq } from 'drizzle-orm'; -import { password_reset_tokens, usersTable } from '$db/schema'; +import { password_reset_tokens } from '$lib/server/api/infrastructure/database/tables'; import { isWithinExpirationDate } from 'oslo'; -import { lucia } from '$lib/server/auth.js'; -import { Argon2id } from 'oslo/password'; -import db from '$db'; +// import { lucia } from '$lib/server/lucia'; +import {db} from '$lib/server/api/infrastructure/database'; export async function POST({ request, params }) { const { password } = await request.json(); @@ -32,12 +31,12 @@ export async function POST({ request, params }) { }); } - await lucia.invalidateUserSessions(token.user_id); - const hashPassword = await new Argon2id().hash(password); - // await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id)); - - const session = await lucia.createSession(token.user_id, {}); - const sessionCookie = lucia.createSessionCookie(session.id); + // await lucia.invalidateUserSessions(token.user_id); + // const hashPassword = await new Argon2id().hash(password); + // // await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id)); + // + // const session = await lucia.createSession(token.user_id, {}); + // const sessionCookie = lucia.createSessionCookie(session.id); return new Response(null, { status: 302, diff --git a/src/routes/api/wishlist/+server.ts b/oldApis/wishlist/+server.ts similarity index 100% rename from src/routes/api/wishlist/+server.ts rename to oldApis/wishlist/+server.ts diff --git a/src/routes/api/collection/[id]/items/+server.ts b/oldApis/wishlist/[id]/items/+server.ts similarity index 96% rename from src/routes/api/collection/[id]/items/+server.ts rename to oldApis/wishlist/[id]/items/+server.ts index fc7727d..4d9ebe1 100644 --- a/src/routes/api/collection/[id]/items/+server.ts +++ b/oldApis/wishlist/[id]/items/+server.ts @@ -1,6 +1,6 @@ -import { error, json } from '@sveltejs/kit'; - -export async function GET({ url, locals, params }) { - const searchParams = Object.fromEntries(url.searchParams); - return json({}); -} +import { error, json } from '@sveltejs/kit'; + +export async function GET({ url, locals, params }) { + const searchParams = Object.fromEntries(url.searchParams); + return json({}); +} diff --git a/src/routes/api/wishlist/[id]/search/+server.ts b/oldApis/wishlist/[id]/search/+server.ts similarity index 100% rename from src/routes/api/wishlist/[id]/search/+server.ts rename to oldApis/wishlist/[id]/search/+server.ts diff --git a/package.json b/package.json index 5d4c785..c00a9a0 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "svelte": "5.0.0-next.175", "svelte-check": "^3.8.6", "svelte-headless-table": "^0.18.2", - "svelte-meta-tags": "^3.1.3", + "svelte-meta-tags": "^3.1.4", "svelte-preprocess": "^6.0.2", "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", @@ -68,7 +68,7 @@ "tailwindcss": "^3.4.10", "ts-node": "^10.9.2", "tslib": "^2.7.0", - "tsx": "^4.18.0", + "tsx": "^4.19.0", "typescript": "^5.5.4", "vite": "^5.4.2", "vitest": "^1.6.0", @@ -106,7 +106,7 @@ "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.5.8", + "hono": "^4.5.9", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 739aaed..88491f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: version: 5.0.14 '@hono/swagger-ui': specifier: ^0.4.1 - version: 0.4.1(hono@4.5.8) + version: 0.4.1(hono@4.5.9) '@hono/zod-openapi': specifier: ^0.15.3 - version: 0.15.3(hono@4.5.8)(zod@3.23.8) + version: 0.15.3(hono@4.5.9)(zod@3.23.8) '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.8)(zod@3.23.8) + version: 0.2.2(hono@4.5.9)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -99,11 +99,11 @@ importers: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.5.8 - version: 4.5.8 + specifier: ^4.5.9 + version: 4.5.9 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.8) + version: 0.4.0(hono@4.5.9) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -248,7 +248,7 @@ importers: version: 16.1.0(postcss@8.4.41) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0) postcss-preset-env: specifier: ^9.6.0 version: 9.6.0(postcss@8.4.41) @@ -272,16 +272,16 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) + version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) svelte-meta-tags: - specifier: ^3.1.3 - version: 3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4) + specifier: ^3.1.4 + version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 @@ -304,8 +304,8 @@ importers: specifier: ^2.7.0 version: 2.7.0 tsx: - specifier: ^4.18.0 - version: 4.18.0 + specifier: ^4.19.0 + version: 4.19.0 typescript: specifier: ^5.5.4 version: 5.5.4 @@ -3178,8 +3178,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.8: - resolution: {integrity: sha512-pqpSlcdqGkpTTRpLYU1PnCz52gVr0zVR9H5GzMyJWuKQLLEBQxh96q45QizJ2PPX8NATtz2mu31/PKW/Jt+90Q==} + hono@4.5.9: + resolution: {integrity: sha512-zz8ktqMDRrZETjxBrv8C5PQRFbrTRCLNVAjD1SNQyOzv4VjmX68Uxw83xQ6oxdAB60HiWnGEatiKA8V3SZLDkQ==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -4493,8 +4493,8 @@ packages: svelte-lazy-loader@1.0.0: resolution: {integrity: sha512-AZD6R60vksyojn21FgXLglmBiBB9K5Dkdu0hdGrLbCaRCYT68IsWkZfRUqKhMx1IfzqWcZQ8X9y/f+Ih0oNQkQ==} - svelte-meta-tags@3.1.3: - resolution: {integrity: sha512-iIdJgxKdMUqFGR4m88jBE9KTSO2jdKE5CRjyRtAjdevW51jL4TtDZwL7GOtr5Fd2dw/+jyQIPD7APATP191qIA==} + svelte-meta-tags@3.1.4: + resolution: {integrity: sha512-TUIfhut0iVeTm7f5v/ZuU/tZ9XsNig9bNN8yK0t2x2WL9qw6AxAVRe9i5XddYJE0SuVwkoDCzjoSg5hXv7oWbQ==} peerDependencies: svelte: ^3.55.0 || ^4.0.0 @@ -4737,8 +4737,8 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tsx@4.18.0: - resolution: {integrity: sha512-a1jaKBSVQkd6yEc1/NI7G6yHFfefIcuf3QJST7ZEyn4oQnxLYrZR5uZAM8UrwUa3Ge8suiZHcNS1gNrEvmobqg==} + tsx@4.19.0: + resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} engines: {node: '>=18.0.0'} hasBin: true @@ -5695,20 +5695,20 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/swagger-ui@0.4.1(hono@4.5.8)': + '@hono/swagger-ui@0.4.1(hono@4.5.9)': dependencies: - hono: 4.5.8 + hono: 4.5.9 - '@hono/zod-openapi@0.15.3(hono@4.5.8)(zod@3.23.8)': + '@hono/zod-openapi@0.15.3(hono@4.5.9)(zod@3.23.8)': dependencies: '@asteasolutions/zod-to-openapi': 7.1.1(zod@3.23.8) - '@hono/zod-validator': 0.2.2(hono@4.5.8)(zod@3.23.8) - hono: 4.5.8 + '@hono/zod-validator': 0.2.2(hono@4.5.9)(zod@3.23.8) + hono: 4.5.9 zod: 3.23.8 - '@hono/zod-validator@0.2.2(hono@4.5.8)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.5.9)(zod@3.23.8)': dependencies: - hono: 4.5.8 + hono: 4.5.9 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -7562,11 +7562,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.8): + hono-rate-limiter@0.4.0(hono@4.5.9): dependencies: - hono: 4.5.8 + hono: 4.5.9 - hono@4.5.8: {} + hono@4.5.9: {} html-entities@2.5.2: {} @@ -8301,14 +8301,14 @@ snapshots: postcss: 8.4.41 ts-node: 10.9.2(@types/node@20.16.1)(typescript@5.5.4) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: jiti: 1.21.6 postcss: 8.4.41 - tsx: 4.18.0 + tsx: 4.19.0 postcss-logical@7.0.1(postcss@8.4.41): dependencies: @@ -8878,14 +8878,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): + svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.0.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - '@babel/core' @@ -8930,7 +8930,7 @@ snapshots: svelte-lazy-loader@1.0.0: {} - svelte-meta-tags@3.1.3(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-meta-tags@3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: schema-dts: 1.1.2(typescript@5.5.4) svelte: 5.0.0-next.175 @@ -8941,7 +8941,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8951,16 +8951,16 @@ snapshots: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0) sass: 1.77.8 typescript: 5.5.4 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.18.0) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0) sass: 1.77.8 typescript: 5.5.4 @@ -9174,7 +9174,7 @@ snapshots: tslib@2.7.0: {} - tsx@4.18.0: + tsx@4.19.0: dependencies: esbuild: 0.23.0 get-tsconfig: 4.7.5 diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index d632423..5768ad5 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -92,7 +92,7 @@ {:else} Login - Sign Up + Sign Up {/if} diff --git a/src/lib/components/signup.svelte b/src/lib/components/signup.svelte index d3ee0d9..ca5db91 100644 --- a/src/lib/components/signup.svelte +++ b/src/lib/components/signup.svelte @@ -13,7 +13,7 @@ // $: termsValue = $form.terms as Writable; - +

Signup user

diff --git a/src/routes/(auth)/+layout.svelte b/src/routes/(auth)/+layout.svelte index 5524879..0c042f8 100644 --- a/src/routes/(auth)/+layout.svelte +++ b/src/routes/(auth)/+layout.svelte @@ -18,8 +18,8 @@ {#if $page.url.pathname !== '/login'} {/if} - {#if $page.url.pathname !== '/sign-up'} - + {#if $page.url.pathname !== '/signup'} + {/if}
diff --git a/src/routes/(auth)/sign-up/+page.server.ts b/src/routes/(auth)/signup/+page.server.ts similarity index 100% rename from src/routes/(auth)/sign-up/+page.server.ts rename to src/routes/(auth)/signup/+page.server.ts diff --git a/src/routes/(auth)/sign-up/+page.svelte b/src/routes/(auth)/signup/+page.svelte similarity index 98% rename from src/routes/(auth)/sign-up/+page.svelte rename to src/routes/(auth)/signup/+page.svelte index a126a5b..54c563b 100644 --- a/src/routes/(auth)/sign-up/+page.svelte +++ b/src/routes/(auth)/signup/+page.svelte @@ -46,7 +46,7 @@ Signup for an account - + diff --git a/svelte.config.js b/svelte.config.js index 435efb8..c4d0477 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -22,9 +22,10 @@ const config = { $assets: './src/assets', $components: './src/components', '$components/*': 'src/lib/components/*', - $db: './src/db', + $db: './src/lib/server/api/infrastructure/database', $server: './src/server', $lib: './src/lib', + $src: './src', $state: './src/state', $styles: './src/styles', $themes: './src/themes', From 16f00607b1ea41f563733e9b7f942fc48b92baf8 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 30 Aug 2024 17:36:22 -0700 Subject: [PATCH 32/34] Updating mfa totp. --- package.json | 12 +- pnpm-lock.yaml | 209 +- src/lib/dtos/verify-totp.dto.ts | 11 + .../server/api/controllers/iam.controller.ts | 7 +- .../server/api/controllers/mfa.controller.ts | 71 +- .../server/api/infrastructure/auth/lucia.ts | 2 + .../database/migrations/0004_heavy_sphinx.sql | 1 + .../migrations/0005_true_mathemanic.sql | 1 + .../migrations/meta/0004_snapshot.json | 1863 +++++++++++++++++ .../migrations/meta/0005_snapshot.json | 1863 +++++++++++++++++ .../database/migrations/meta/_journal.json | 14 + .../database/tables/users.table.ts | 17 +- .../server/api/middleware/auth.middleware.ts | 60 +- .../repositories/credentials.repository.ts | 74 +- src/lib/server/api/services/totp.service.ts | 24 +- src/lib/utils/api.ts | 2 +- .../profile/security/mfa/+page.server.ts | 111 +- .../mfa/recovery-codes/+page.server.ts | 49 +- 18 files changed, 4077 insertions(+), 314 deletions(-) create mode 100644 src/lib/dtos/verify-totp.dto.ts create mode 100644 src/lib/server/api/infrastructure/database/migrations/0004_heavy_sphinx.sql create mode 100644 src/lib/server/api/infrastructure/database/migrations/0005_true_mathemanic.sql create mode 100644 src/lib/server/api/infrastructure/database/migrations/meta/0004_snapshot.json create mode 100644 src/lib/server/api/infrastructure/database/migrations/meta/0005_snapshot.json diff --git a/package.json b/package.json index c00a9a0..dd9738d 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,12 @@ "@melt-ui/svelte": "^0.83.0", "@playwright/test": "^1.46.1", "@sveltejs/adapter-auto": "^3.2.4", - "@sveltejs/enhanced-img": "^0.3.3", - "@sveltejs/kit": "^2.5.24", + "@sveltejs/enhanced-img": "^0.3.4", + "@sveltejs/kit": "^2.5.25", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/cookie": "^0.6.0", - "@types/node": "^20.16.1", - "@types/pg": "^8.11.6", + "@types/node": "^20.16.2", + "@types/pg": "^8.11.8", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "autoprefixer": "^10.4.20", @@ -95,7 +95,7 @@ "arctic": "^1.9.2", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.12.10", + "bullmq": "^5.12.12", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -114,7 +114,7 @@ "just-capitalize": "^3.2.0", "just-kebab-case": "^4.2.0", "loader": "^2.1.1", - "open-props": "^1.7.5", + "open-props": "^1.7.6", "oslo": "^1.2.1", "pg": "^8.12.0", "postgres": "^3.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88491f8..fd902d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 3.5.5 '@lucia-auth/adapter-drizzle': specifier: ^1.1.0 - version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) + version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) '@lukeed/uuid': specifier: ^2.0.1 version: 2.0.1 @@ -46,10 +46,10 @@ importers: version: 2.6.2 '@sveltejs/adapter-node': specifier: ^5.2.2 - version: 5.2.2(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))) + version: 5.2.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) '@sveltejs/adapter-vercel': specifier: ^5.4.3 - version: 5.4.3(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))) + version: 5.4.3(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -66,8 +66,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.12.10 - version: 5.12.10 + specifier: ^5.12.12 + version: 5.12.12 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -85,16 +85,16 @@ importers: version: 11.0.6 drizzle-orm: specifier: ^0.32.2 - version: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) + version: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -123,8 +123,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 open-props: - specifier: ^1.7.5 - version: 1.7.5 + specifier: ^1.7.6 + version: 1.7.6 oslo: specifier: ^1.2.1 version: 1.2.1 @@ -157,10 +157,10 @@ importers: version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -185,25 +185,25 @@ importers: version: 1.46.1 '@sveltejs/adapter-auto': specifier: ^3.2.4 - version: 3.2.4(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))) + version: 3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) '@sveltejs/enhanced-img': - specifier: ^0.3.3 - version: 0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + specifier: ^0.3.4 + version: 0.3.4(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) '@sveltejs/kit': - specifier: ^2.5.24 - version: 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + specifier: ^2.5.25 + version: 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.16.1 - version: 20.16.1 + specifier: ^20.16.2 + version: 20.16.2 '@types/pg': - specifier: ^8.11.6 - version: 8.11.6 + specifier: ^8.11.8 + version: 8.11.8 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) @@ -224,7 +224,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: ^2.43.0 - version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)) + version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -287,19 +287,19 @@ importers: version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))) + version: 0.5.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.17.0 - version: 2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)) + version: 3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.16.1)(typescript@5.5.4) + version: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -311,10 +311,10 @@ importers: version: 5.5.4 vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.16.1)(sass@1.77.8) + version: 5.4.2(@types/node@20.16.2)(sass@1.77.8) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.16.1)(sass@1.77.8) + version: 1.6.0(@types/node@20.16.2)(sass@1.77.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -2054,14 +2054,14 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/enhanced-img@0.3.3': - resolution: {integrity: sha512-nsqJkVuYLUXARDLjMoGKAt4oLzwtY8X2E8rIl/TJl7ueLjpTISxrAhVRN3r8yMO+R+so4G6Taiix2mpiPpqZeg==} + '@sveltejs/enhanced-img@0.3.4': + resolution: {integrity: sha512-eX+ob5uWr0bTLMKeG9nhhM84aR88hqiLiyEfWZPX7ijhk/wlmYSUX9nOiaVHh2ct1U+Ju9Hhb90Copw+ZNOB8w==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: '>= 5.0.0' - '@sveltejs/kit@2.5.24': - resolution: {integrity: sha512-Nr2oxsCsDfEkdS/zzQQQbsPYTbu692Qs3/iE3L7VHzCVjG2+WujF9oMUozWI7GuX98KxYSoPMlAsfmDLSg44hQ==} + '@sveltejs/kit@2.5.25': + resolution: {integrity: sha512-5hBSEN8XEjDZ5+2bHkFh8Z0QyOk0C187cyb12aANe1c8aeKbfu5ZD5XaC2vEH4h0alJFDXPdUkXQBmeeXeMr1A==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2114,12 +2114,15 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.16.1': - resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==} + '@types/node@20.16.2': + resolution: {integrity: sha512-91s/n4qUPV/wg8eE9KHYW1kouTfDk2FPGjXbBMfRWP/2vg1rCXNQL1OCabwGs0XSdukuK+MwCDXE30QpSeMUhQ==} '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} + '@types/pg@8.11.8': + resolution: {integrity: sha512-IqpCf8/569txXN/HoP5i1LjXfKZWL76Yr2R77xgeIICUbAYHeoaEZFhYHo2uDftecLWrTJUq63JvQu8q3lnDyA==} + '@types/pug@2.0.10': resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} @@ -2402,8 +2405,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.12.10: - resolution: {integrity: sha512-lXH8Caj+FvYHiBS0QBEpQOq57RcVuEPziBC5cBlWguCVNfn1UMSri22bRrynDKuof8o9XB43ctmYASUpoa0DeQ==} + bullmq@5.12.12: + resolution: {integrity: sha512-xrWKDj1ZwnGKmrlmFqF6Vmub3WqDFfdBcIRLCooIs5+jeVzbHK7/1usgYSFg2pZiwK6h6eMivTb9WvcKkNW/+w==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3667,8 +3670,8 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - open-props@1.7.5: - resolution: {integrity: sha512-DajjLQDJgIa0i+QdB2q5M8lNLo2ICk+DbDh4TsqNsT1tAO8Zm8F7dndSkLMQkobT98lbvDMMpJWO8NT0ibjrjA==} + open-props@1.7.6: + resolution: {integrity: sha512-fE3E22x8lCf6gJrPO3L54NKrPaqQGxeKoXLz4JIXrhALD3Ua1kkE2kMw8HmlTKYF9BSLLMfoVfsT3UC1PJcaww==} openapi3-ts@4.3.3: resolution: {integrity: sha512-LKkzBGJcZ6wdvkKGMoSvpK+0cbN5Xc3XuYkJskO+vjEQWJgs1kgtyUk0pjf8KwPuysv323Er62F5P17XQl96Qg==} @@ -5851,9 +5854,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) lucia: 3.2.0 '@lukeed/csprng@1.1.0': {} @@ -6298,41 +6301,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))': + '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1) '@rollup/plugin-json': 6.1.0(rollup@4.18.1) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1) - '@sveltejs/kit': 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) rollup: 4.18.1 - '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.3(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))': + '@sveltejs/enhanced-img@0.3.4(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': dependencies: magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) vite-imagetools: 7.0.2(rollup@4.18.1) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))': + '@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6346,28 +6349,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) - vitefu: 0.2.5(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vitefu: 0.2.5(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6397,13 +6400,19 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.16.1': + '@types/node@20.16.2': dependencies: undici-types: 6.19.6 '@types/pg@8.11.6': dependencies: - '@types/node': 20.16.1 + '@types/node': 20.16.2 + pg-protocol: 1.6.1 + pg-types: 4.0.2 + + '@types/pg@8.11.8': + dependencies: + '@types/node': 20.16.2 pg-protocol: 1.6.1 pg-types: 4.0.2 @@ -6750,7 +6759,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.12.10: + bullmq@5.12.12: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -7021,16 +7030,16 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4): optionalDependencies: '@neondatabase/serverless': 0.9.4 - '@types/pg': 8.11.6 + '@types/pg': 8.11.8 pg: 8.12.0 postgres: 3.4.4 - drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) zod: 3.23.8 eastasianwidth@0.2.0: {} @@ -7190,7 +7199,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)): + eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -7199,7 +7208,7 @@ snapshots: esutils: 2.0.3 known-css-properties: 0.34.0 postcss: 8.4.41 - postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)) + postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) postcss-safe-parser: 6.0.0(postcss@8.4.41) postcss-selector-parser: 6.1.0 semver: 7.6.2 @@ -7426,11 +7435,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -8003,7 +8012,7 @@ snapshots: dependencies: mimic-fn: 4.0.0 - open-props@1.7.5: {} + open-props@1.7.6: {} openapi3-ts@4.3.3: dependencies: @@ -8285,21 +8294,21 @@ snapshots: '@csstools/utilities': 1.0.0(postcss@8.4.41) postcss: 8.4.41 - postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)): + postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.16.1)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) - postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.16.1)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0): dependencies: @@ -9015,19 +9024,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) - sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.24(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -9052,16 +9061,16 @@ snapshots: tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))): dependencies: tailwind-merge: 2.5.2 - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) - tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)): + tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9080,7 +9089,7 @@ snapshots: postcss: 8.4.41 postcss-import: 15.1.0(postcss@8.4.41) postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) postcss-nested: 6.0.1(postcss@8.4.41) postcss-selector-parser: 6.1.0 resolve: 1.22.8 @@ -9147,14 +9156,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.1 + '@types/node': 20.16.2 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -9263,13 +9272,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.16.1)(sass@1.77.8): + vite-node@1.6.0(@types/node@20.16.2)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -9281,21 +9290,21 @@ snapshots: - supports-color - terser - vite@5.4.2(@types/node@20.16.1)(sass@1.77.8): + vite@5.4.2(@types/node@20.16.2)(sass@1.77.8): dependencies: esbuild: 0.21.5 postcss: 8.4.41 rollup: 4.21.0 optionalDependencies: - '@types/node': 20.16.1 + '@types/node': 20.16.2 fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.4.2(@types/node@20.16.1)(sass@1.77.8)): + vitefu@0.2.5(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)): optionalDependencies: - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) - vitest@1.6.0(@types/node@20.16.1)(sass@1.77.8): + vitest@1.6.0(@types/node@20.16.2)(sass@1.77.8): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9314,11 +9323,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.4.2(@types/node@20.16.1)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.16.1)(sass@1.77.8) + vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.16.2)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.16.1 + '@types/node': 20.16.2 transitivePeerDependencies: - less - lightningcss diff --git a/src/lib/dtos/verify-totp.dto.ts b/src/lib/dtos/verify-totp.dto.ts new file mode 100644 index 0000000..cd5f886 --- /dev/null +++ b/src/lib/dtos/verify-totp.dto.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const verifyTotpDto = z.object({ + code: z + .string() + .trim() + .min(6, { message: 'Must be at least 6 characters' }) + .max(6, { message: 'Must be less than 6 characters' }), +}); + +export type VerifyTotpDto = z.infer; \ No newline at end of file diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index d872758..0f34305 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -33,7 +33,7 @@ export class IamController implements Controller { const { firstName, lastName, username } = c.req.valid('json') const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username }) if (!updatedUser) { - return c.json("Username already in use", StatusCodes.BAD_REQUEST); + return c.json('Username already in use', StatusCodes.BAD_REQUEST) } return c.json({ user: updatedUser }, StatusCodes.OK) }) @@ -42,16 +42,17 @@ export class IamController implements Controller { const { password } = c.req.valid('json') const passwordVerified = await this.iamService.verifyPassword(user.id, { password }) if (!passwordVerified) { + console.log('Incorrect password') return c.json('Incorrect password', StatusCodes.BAD_REQUEST) } - return c.json({ }, StatusCodes.OK) + return c.json({}, StatusCodes.OK) }) .post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { const user = c.var.user const { email } = c.req.valid('json') const updatedUser = await this.iamService.updateEmail(user.id, { email }) if (!updatedUser) { - return c.json("Email already in use", StatusCodes.BAD_REQUEST); + return c.json('Email already in use', StatusCodes.BAD_REQUEST) } return c.json({ user: updatedUser }, StatusCodes.OK) }) diff --git a/src/lib/server/api/controllers/mfa.controller.ts b/src/lib/server/api/controllers/mfa.controller.ts index 7c72abf..dbbc13c 100644 --- a/src/lib/server/api/controllers/mfa.controller.ts +++ b/src/lib/server/api/controllers/mfa.controller.ts @@ -1,39 +1,62 @@ -import 'reflect-metadata'; -import { Hono } from 'hono'; -import { inject, injectable } from 'tsyringe'; -import { requireAuth } from "../middleware/auth.middleware"; -import type { HonoTypes } from '../types'; -import type { Controller } from '$lib/server/api/interfaces/controller.interface'; -import { TotpService } from '$lib/server/api/services/totp.service'; -import {StatusCodes} from "$lib/constants/status-codes"; +import 'reflect-metadata' +import { inject, injectable } from 'tsyringe' +import { Hono } from 'hono' +import { zValidator } from '@hono/zod-validator' +import { requireAuth } from '../middleware/auth.middleware' +import type { HonoTypes } from '../types' +import type { Controller } from '$lib/server/api/interfaces/controller.interface' +import { TotpService } from '$lib/server/api/services/totp.service' +import { StatusCodes } from '$lib/constants/status-codes' +import { verifyTotpDto } from '$lib/dtos/verify-totp.dto' +import { UsersService } from '../services/users.service' +import { CredentialsType } from '$db/tables' @injectable() export class MfaController implements Controller { - controller = new Hono(); + controller = new Hono() constructor( - @inject(TotpService) private readonly totpService: TotpService - ) { - } - + @inject(TotpService) private readonly totpService: TotpService, + @inject(UsersService) private readonly usersService: UsersService, + ) {} routes() { return this.controller .get('/totp', requireAuth, async (c) => { - const user = c.var.user; - const totpCredential = await this.totpService.findOneByUserId(user.id); - return c.json({ totpCredential }); + const user = c.var.user + const totpCredential = await this.totpService.findOneByUserId(user.id) + return c.json({ totpCredential }) }) .post('/totp', requireAuth, async (c) => { - const user = c.var.user; - const totpCredential = await this.totpService.create(user.id); + const user = c.var.user + const totpCredential = await this.totpService.create(user.id) return c.json({ totpCredential }) }) .delete('/totp', requireAuth, async (c) => { - const user = c.var.user; - await this.totpService.deleteOneByUserId(user.id); - return c.status(StatusCodes.NO_CONTENT); - }); + const user = c.var.user + try { + await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP) + console.log('TOTP deleted') + return c.body(null, StatusCodes.NO_CONTENT) + } catch (e) { + console.error(e) + return c.status(StatusCodes.INTERNAL_SERVER_ERROR) + } + }) + .post('/totp/verify', requireAuth, zValidator('json', verifyTotpDto), async (c) => { + try { + const user = c.var.user + const { code } = c.req.valid('json') + const verified = await this.totpService.verify(user.id, code) + if (verified) { + this.usersService.updateUser(user.id, { mfa_enabled: true }) + return c.json({}, StatusCodes.OK) + } + return c.json({}, StatusCodes.BAD_REQUEST) + } catch (e) { + console.error(e) + return c.status(StatusCodes.INTERNAL_SERVER_ERROR) + } + }) } - -} \ No newline at end of file +} diff --git a/src/lib/server/api/infrastructure/auth/lucia.ts b/src/lib/server/api/infrastructure/auth/lucia.ts index 9fb1eb9..8141cb6 100644 --- a/src/lib/server/api/infrastructure/auth/lucia.ts +++ b/src/lib/server/api/infrastructure/auth/lucia.ts @@ -23,6 +23,7 @@ export const lucia = new Lucia(adapter, { email: attributes.email, firstName: attributes.first_name, lastName: attributes.last_name, + mfa_enabled: attributes.mfa_enabled, theme: attributes.theme, }; }, @@ -56,6 +57,7 @@ declare module 'lucia' { email: string; first_name: string; last_name: string; + mfa_enabled: boolean; theme: string; } } diff --git a/src/lib/server/api/infrastructure/database/migrations/0004_heavy_sphinx.sql b/src/lib/server/api/infrastructure/database/migrations/0004_heavy_sphinx.sql new file mode 100644 index 0000000..70a7029 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/0004_heavy_sphinx.sql @@ -0,0 +1 @@ +ALTER TABLE "users" ADD COLUMN "enabled" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/0005_true_mathemanic.sql b/src/lib/server/api/infrastructure/database/migrations/0005_true_mathemanic.sql new file mode 100644 index 0000000..a37a946 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/0005_true_mathemanic.sql @@ -0,0 +1 @@ +ALTER TABLE "users" RENAME COLUMN "enabled" TO "mfa_enabled"; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0004_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0004_snapshot.json new file mode 100644 index 0000000..11e04b2 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0004_snapshot.json @@ -0,0 +1,1863 @@ +{ + "id": "ee44907c-0c2a-4aa0-8e3a-59cf74321c7a", + "prevId": "5a4165ba-0d5a-4a58-aa14-e68e3e41a181", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "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": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "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": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "secret_data": { + "name": "secret_data", + "type": "text", + "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": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "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": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.federated_identity": { + "name": "federated_identity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_user_id": { + "name": "federated_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_username": { + "name": "federated_username", + "type": "text", + "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": { + "federated_identity_user_id_users_id_fk": { + "name": "federated_identity_user_id_users_id_fk", + "tableFrom": "federated_identity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "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": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "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": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "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": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "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": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "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": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0005_snapshot.json b/src/lib/server/api/infrastructure/database/migrations/meta/0005_snapshot.json new file mode 100644 index 0000000..c82db66 --- /dev/null +++ b/src/lib/server/api/infrastructure/database/migrations/meta/0005_snapshot.json @@ -0,0 +1,1863 @@ +{ + "id": "b7874689-9dc9-4171-9115-5b6333a0f995", + "prevId": "ee44907c-0c2a-4aa0-8e3a-59cf74321c7a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "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": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "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": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "secret_data": { + "name": "secret_data", + "type": "text", + "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": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "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": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.federated_identity": { + "name": "federated_identity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_user_id": { + "name": "federated_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_username": { + "name": "federated_username", + "type": "text", + "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": { + "federated_identity_user_id_users_id_fk": { + "name": "federated_identity_user_id_users_id_fk", + "tableFrom": "federated_identity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "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": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "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": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "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": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "mfa_enabled": { + "name": "mfa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "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": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "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": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "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": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file 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 c0c8782..3fa0832 100644 --- a/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json +++ b/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json @@ -29,6 +29,20 @@ "when": 1723593488634, "tag": "0003_worried_taskmaster", "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1725055403926, + "tag": "0004_heavy_sphinx", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1725055643756, + "tag": "0005_true_mathemanic", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/database/tables/users.table.ts b/src/lib/server/api/infrastructure/database/tables/users.table.ts index 83f6fef..a61ca90 100644 --- a/src/lib/server/api/infrastructure/database/tables/users.table.ts +++ b/src/lib/server/api/infrastructure/database/tables/users.table.ts @@ -1,8 +1,8 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { timestamps } from '../utils'; -import {user_roles} from './userRoles'; +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { timestamps } from '../utils' +import { user_roles } from './userRoles' export const usersTable = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), @@ -15,12 +15,13 @@ export const usersTable = pgTable('users', { last_name: text('last_name'), verified: boolean('verified').default(false), receive_email: boolean('receive_email').default(false), + mfa_enabled: boolean('mfa_enabled').notNull().default(false), theme: text('theme').default('system'), ...timestamps, -}); +}) export const userRelations = relations(usersTable, ({ many }) => ({ user_roles: many(user_roles), -})); +})) -export type Users = InferSelectModel; +export type Users = InferSelectModel diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index 9c21ea8..bd2f4c9 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -1,50 +1,50 @@ -import type { MiddlewareHandler } from 'hono'; -import { createMiddleware } from 'hono/factory'; -import type { HonoTypes } from '../types'; -import { lucia } from '../infrastructure/auth/lucia'; -import { verifyRequestOrigin } from 'lucia'; -import type { Session, User } from 'lucia'; -import { Unauthorized } from '../common/errors'; +import type { MiddlewareHandler } from 'hono' +import { createMiddleware } from 'hono/factory' +import type { HonoTypes } from '../types' +import { lucia } from '../infrastructure/auth/lucia' +import { verifyRequestOrigin } from 'oslo/request' +import type { Session, User } from 'lucia' +import { Unauthorized } from '../common/errors' export const verifyOrigin: MiddlewareHandler = createMiddleware(async (c, next) => { - if (c.req.method === "GET") { - return next(); + if (c.req.method === 'GET') { + return next() } - const originHeader = c.req.header("Origin") ?? null; - const hostHeader = c.req.header("Host") ?? null; + const originHeader = c.req.header('Origin') ?? null + const hostHeader = c.req.header('Host') ?? null if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { - return c.body(null, 403); + return c.body(null, 403) } - return next(); + return next() }) export const validateAuthSession: MiddlewareHandler = createMiddleware(async (c, next) => { - const sessionId = lucia.readSessionCookie(c.req.header("Cookie") ?? ""); + const sessionId = lucia.readSessionCookie(c.req.header('Cookie') ?? '') if (!sessionId) { - c.set("user", null); - c.set("session", null); - return next(); + c.set('user', null) + c.set('session', null) + return next() } - const { session, user } = await lucia.validateSession(sessionId); + const { session, user } = await lucia.validateSession(sessionId) if (session && session.fresh) { - c.header("Set-Cookie", lucia.createSessionCookie(session.id).serialize(), { append: true }); + c.header('Set-Cookie', lucia.createSessionCookie(session.id).serialize(), { append: true }) } if (!session) { - c.header("Set-Cookie", lucia.createBlankSessionCookie().serialize(), { append: true }); + c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true }) } - c.set("session", session); - c.set("user", user); - return next(); + c.set('session', session) + c.set('user', user) + return next() }) export const requireAuth: MiddlewareHandler<{ Variables: { - session: Session; - user: User; - }; + session: Session + user: User + } }> = createMiddleware(async (c, next) => { - const user = c.var.user; - if (!user) throw Unauthorized('You must be logged in to access this resource'); - return next(); -}); + const user = c.var.user + if (!user) throw Unauthorized('You must be logged in to access this resource') + return next() +}) diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 84d3334..2594af5 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -1,83 +1,69 @@ -import { and, eq, type InferInsertModel } from "drizzle-orm"; -import { credentialsTable, CredentialsType } from "../infrastructure/database/tables/credentials.table"; -import { takeFirstOrThrow } from "../infrastructure/database/utils"; -import {inject, injectable} from "tsyringe"; -import {DatabaseProvider} from "$lib/server/api/providers"; +import { and, eq, type InferInsertModel } from 'drizzle-orm' +import { credentialsTable, CredentialsType } from '../infrastructure/database/tables/credentials.table' +import { takeFirstOrThrow } from '../infrastructure/database/utils' +import { inject, injectable } from 'tsyringe' +import { DatabaseProvider } from '$lib/server/api/providers' -export type CreateCredentials = InferInsertModel; -export type UpdateCredentials = Partial; +export type CreateCredentials = InferInsertModel +export type UpdateCredentials = Partial @injectable() export class CredentialsRepository { - constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findOneByUserId(userId: string) { return this.db.query.credentialsTable.findFirst({ - where: eq(credentialsTable.user_id, userId) - }); + where: eq(credentialsTable.user_id, userId), + }) } async findOneByUserIdAndType(userId: string, type: CredentialsType) { return this.db.query.credentialsTable.findFirst({ - where: and( - eq(credentialsTable.user_id, userId), - eq(credentialsTable.type, type) - ) - }); + where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type)), + }) } async findPasswordCredentialsByUserId(userId: string) { return this.db.query.credentialsTable.findFirst({ - where: and( - eq(credentialsTable.user_id, userId), - eq(credentialsTable.type, CredentialsType.PASSWORD) - ) - }); + where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.PASSWORD)), + }) } async findTOTPCredentialsByUserId(userId: string) { return this.db.query.credentialsTable.findFirst({ - where: and( - eq(credentialsTable.user_id, userId), - eq(credentialsTable.type, CredentialsType.TOTP) - ) - }); + where: and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, CredentialsType.TOTP)), + }) } async findOneById(id: string) { return this.db.query.credentialsTable.findFirst({ - where: eq(credentialsTable.id, id) - }); + where: eq(credentialsTable.id, id), + }) } async findOneByIdOrThrow(id: string) { - const credentials = await this.findOneById(id); - if (!credentials) throw Error('Credentials not found'); - return credentials; + const credentials = await this.findOneById(id) + if (!credentials) throw Error('Credentials not found') + return credentials } async create(data: CreateCredentials) { - return this.db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow) } async update(id: string, data: UpdateCredentials) { - return this.db - .update(credentialsTable) - .set(data) - .where(eq(credentialsTable.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.update(credentialsTable).set(data).where(eq(credentialsTable.id, id)).returning().then(takeFirstOrThrow) } async delete(id: string) { - return this.db - .delete(credentialsTable) - .where(eq(credentialsTable.id, id)); + return this.db.delete(credentialsTable).where(eq(credentialsTable.id, id)) } async deleteByUserId(userId: string) { - return this.db - .delete(credentialsTable) - .where(eq(credentialsTable.user_id, userId)); + return this.db.delete(credentialsTable).where(eq(credentialsTable.user_id, userId)) } -} \ No newline at end of file + + async deleteByUserIdAndType(userId: string, type: CredentialsType) { + return this.db.delete(credentialsTable).where(and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type))) + } +} diff --git a/src/lib/server/api/services/totp.service.ts b/src/lib/server/api/services/totp.service.ts index 77bb004..f75674e 100644 --- a/src/lib/server/api/services/totp.service.ts +++ b/src/lib/server/api/services/totp.service.ts @@ -1,7 +1,9 @@ import { inject, injectable } from "tsyringe"; import { HMAC } from 'oslo/crypto'; -import { encodeHex } from 'oslo/encoding'; +import { decodeHex, encodeHex } from 'oslo/encoding'; import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository"; +import { TOTPController } from "oslo/otp"; +import type { CredentialsType } from "$db/tables"; @injectable() export class TotpService { @@ -15,6 +17,14 @@ export class TotpService { return this.credentialsRepository.findTOTPCredentialsByUserId(userId); } + async findOneByUserIdOrThrow(userId: string) { + const credential = await this.findOneByUserId(userId); + if (!credential) { + throw new Error('TOTP credential not found'); + } + return credential; + } + async create(userId: string) { const twoFactorSecret = await new HMAC('SHA-1').generateKey(); @@ -33,4 +43,16 @@ export class TotpService { async deleteOneByUserId(userId: string) { return this.credentialsRepository.deleteByUserId(userId); } + + async deleteOneByUserIdAndType(userId: string, type: CredentialsType) { + return this.credentialsRepository.deleteByUserIdAndType(userId, type) + } + + async verify(userId: string, code: string) { + const credential = await this.credentialsRepository.findTOTPCredentialsByUserId(userId); + if (!credential) { + throw new Error('TOTP credential not found'); + } + return await new TOTPController().verify(code, decodeHex(credential.secret_data)) + } } \ No newline at end of file diff --git a/src/lib/utils/api.ts b/src/lib/utils/api.ts index 12fb9c5..38dfdc4 100644 --- a/src/lib/utils/api.ts +++ b/src/lib/utils/api.ts @@ -10,7 +10,7 @@ export async function parseApiResponse(response: ClientResponse) { if (response.ok) { const data = await response.json() as T; - return { data, error: null, status: response.status }; + return { data, error: null, response } } // handle errors diff --git a/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts b/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts index 64a2394..cbb2dc9 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts @@ -1,43 +1,43 @@ -import { type Actions, fail, error } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import { encodeHex, decodeHex } from 'oslo/encoding'; -import { Argon2id } from 'oslo/password'; -import { createTOTPKeyURI, TOTPController } from 'oslo/otp'; -import { HMAC } from 'oslo/crypto'; -import kebabCase from 'just-kebab-case'; -import QRCode from 'qrcode'; -import { zod } from 'sveltekit-superforms/adapters'; -import { setError, superValidate } from 'sveltekit-superforms/server'; -import { redirect, setFlash } from 'sveltekit-flash-message/server'; -import type { PageServerLoad } from '../../$types'; -import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account'; -import { notSignedInMessage } from '$lib/flashMessages'; -import { db } from '$lib/server/api/infrastructure/database'; -import { recoveryCodesTable, credentialsTable, usersTable, type Credentials } from '$lib/server/api/infrastructure/database/tables'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; -import env from '$src/env'; +import { type Actions, fail, error } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import { encodeHex, decodeHex } from 'oslo/encoding' +import { Argon2id } from 'oslo/password' +import { createTOTPKeyURI, TOTPController } from 'oslo/otp' +import { HMAC } from 'oslo/crypto' +import kebabCase from 'just-kebab-case' +import QRCode from 'qrcode' +import { zod } from 'sveltekit-superforms/adapters' +import { setError, superValidate } from 'sveltekit-superforms/server' +import { redirect, setFlash } from 'sveltekit-flash-message/server' +import type { PageServerLoad } from '../../$types' +import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account' +import { notSignedInMessage } from '$lib/flashMessages' +import { db } from '$lib/server/api/infrastructure/database' +import { recoveryCodesTable, credentialsTable, usersTable, type Credentials } from '$lib/server/api/infrastructure/database/tables' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import env from '$src/env' +import { StatusCodes } from '$lib/constants/status-codes' export const load: PageServerLoad = async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } - const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema)); - const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema)); + const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema)) + const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema)) // const addAuthNFactorForm = await superValidate(event, zod(addAuthNFactorSchema)); - const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse); + const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse) if (error || !data) { return fail(500, { addTwoFactorForm, - }); + }) } const { totpCredential } = data - - if (totpCredential) { + if (totpCredential && authedUser.mfa_enabled) { return { addTwoFactorForm, removeTwoFactorForm, @@ -48,9 +48,13 @@ export const load: PageServerLoad = async (event) => { } } - const issuer = kebabCase(env.PUBLIC_SITE_NAME); - const accountName = authedUser.email || authedUser.username; - const { data: createdTotpData, error: createdTotpError } = await locals.api.mfa.totp.$post().then(locals.parseApiResponse); + if (totpCredential && !authedUser.mfa_enabled) { + await locals.api.mfa.totp.$delete().then(locals.parseApiResponse) + } + + const issuer = kebabCase(env.PUBLIC_SITE_NAME) + const accountName = authedUser.email || authedUser.username + const { data: createdTotpData, error: createdTotpError } = await locals.api.mfa.totp.$post().then(locals.parseApiResponse) if (createdTotpError || !createdTotpData) { return fail(500, { @@ -58,19 +62,19 @@ export const load: PageServerLoad = async (event) => { }) } - const { totpCredential: createdTotpCredentials } = createdTotpData; + const { totpCredential: createdTotpCredentials } = createdTotpData // pass the website's name and the user identifier (e.g. email, username) if (!createdTotpCredentials?.secret_data) { return fail(500, { addTwoFactorForm, }) } - const totpUri = createTOTPKeyURI(issuer, accountName, createdTotpCredentials.secret_data); + const totpUri = createTOTPKeyURI(issuer, accountName, decodeHex(createdTotpCredentials.secret_data)) addTwoFactorForm.data = { current_password: '', two_factor_code: '', - }; + } return { addTwoFactorForm, removeTwoFactorForm, @@ -78,8 +82,8 @@ export const load: PageServerLoad = async (event) => { recoveryCodes: [], totpUri, qrCode: await QRCode.toDataURL(totpUri), - }; -}; + } +} export const actions: Actions = { enableTotp: async (event) => { @@ -98,31 +102,12 @@ export const actions: Actions = { }) } - const { data, error } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse) - if (error || !data) { - return fail(500, { - addTwoFactorForm, - }) - } - const { totpCredential } = data - - if (!totpCredential) { - addTwoFactorForm.data.current_password = '' - addTwoFactorForm.data.two_factor_code = '' - return setError(addTwoFactorForm, 'Error occurred. Please try again or contact support if you need further help.') - } - - if (totpCredential.secret_data === '' || totpCredential.secret_data === null) { - addTwoFactorForm.data.current_password = '' - addTwoFactorForm.data.two_factor_code = '' - return setError(addTwoFactorForm, 'Error occurred. Please try again or contact support if you need further help.') - } - - const currentPasswordVerified = await locals.api.me.verify.password.$post({ + const { error: verifyPasswordError } = await locals.api.me.verify.password.$post({ json: { password: addTwoFactorForm.data.current_password }, - }); + }).then(locals.parseApiResponse) - if (!currentPasswordVerified) { + if (verifyPasswordError) { + console.log(verifyPasswordError) return setError(addTwoFactorForm, 'current_password', 'Your password is incorrect') } @@ -131,14 +116,16 @@ export const actions: Actions = { } const twoFactorCode = addTwoFactorForm.data.two_factor_code - const validOTP = await new TOTPController().verify(twoFactorCode, decodeHex(twoFactorDetails.secret)) + const { error: verifyTotpError } = locals.api.mfa.totp.verify + .$post({ + json: { code: twoFactorCode }, + }) + .then(locals.parseApiResponse) - if (!validOTP) { + if (verifyTotpError) { return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code') } - await db.update(twoFactor).set({ enabled: true }).where(eq(twoFactor.userId, user!.id!)) - redirect(302, '/profile/security/two-factor/recovery-codes') }, disableTotp: async (event) => { diff --git a/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts b/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts index b14c28e..84ceece 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts @@ -5,8 +5,7 @@ import { redirect } from 'sveltekit-flash-message/server'; import { db } from '$lib/server/api/infrastructure/database'; import { notSignedInMessage } from '$lib/flashMessages'; import type { PageServerLoad } from '../../../$types'; -import { recoveryCodesTable, twoFactorTable, usersTable} from '$lib/server/api/infrastructure/database/tables'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; +import { recoveryCodesTable } from '$lib/server/api/infrastructure/database/tables'; export const load: PageServerLoad = async (event) => { const { locals } = event; @@ -16,51 +15,31 @@ export const load: PageServerLoad = async (event) => { throw redirect(302, '/login', notSignedInMessage, event); } - const dbUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.id, authedUser.id), - }); - - if (!dbUser) { - throw redirect(302, '/login', notSignedInMessage, event); - } - - const twoFactorDetails = await db.query.twoFactor.findFirst({ - where: eq(twoFactor.userId, dbUser.id), - }); - - if (twoFactorDetails?.enabled) { - const dbRecoveryCodes = await db.query.recoveryCodes.findMany({ - where: eq(recoveryCodes.userId, authedUser.id), - }); + if (authedUser.mfa_enabled) { + const dbRecoveryCodes = await db.query.recoveryCodesTable.findMany({ + where: eq(recoveryCodesTable.userId, authedUser.id), + }) if (dbRecoveryCodes.length === 0) { - const createdRecoveryCodes = Array.from({ length: 5 }, () => - generateRandomString(10, alphabet('A-Z', '0-9')), - ); + const createdRecoveryCodes = Array.from({ length: 5 }, () => generateRandomString(10, alphabet('A-Z', '0-9'))) if (createdRecoveryCodes) { for (const code of createdRecoveryCodes) { - const hashedCode = await new Argon2id().hash(code); - console.log('Inserting recovery code', code, hashedCode); - await db.insert(recoveryCodes).values({ + const hashedCode = await new Argon2id().hash(code) + console.log('Inserting recovery code', code, hashedCode) + await db.insert(recoveryCodesTable).values({ userId: authedUser.id, code: hashedCode, - }); + }) } } return { recoveryCodes: createdRecoveryCodes, - }; + } } return { recoveryCodes: [], - }; - } else { - console.error('2FA not enabled'); - redirect( - 302, - '/profile', - { message: 'Two-Factor Authentication is not enabled', type: 'error' }, - event, - ); + } } + console.error('2FA not enabled') + redirect(302, '/profile', { message: 'Two-Factor Authentication is not enabled', type: 'error' }, event) }; From 3aa537f389a2598e608d04267dd7d7e9d458b012 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sun, 1 Sep 2024 12:22:00 -0700 Subject: [PATCH 33/34] Refactoring to match simplifying done on the origin TaroStack. --- .gitignore | 2 + drizzle.config.ts | 16 +- oldApis/collection/[id]/search/+server.ts | 51 +- oldApis/reset-password/+server.ts | 28 +- oldApis/reset-password/[token]/+server.ts | 24 +- package.json | 4 +- src/env.ts | 34 +- src/hooks.server.ts | 96 +--- .../api/common/{errors.ts => exceptions.ts} | 0 .../common/interfaces/controller.interface.ts | 8 + .../api/common/interfaces/email.interface.ts | 4 + .../common/interfaces/repository.interface.ts | 5 + .../api/common/utils/repository.utils.ts | 14 + .../server/api/common/utils/table.utils.ts | 29 ++ .../server/api/{common => configs}/config.ts | 0 .../api/controllers/collection.controller.ts | 34 +- .../server/api/controllers/iam.controller.ts | 26 +- .../api/controllers/login.controller.ts | 65 +-- .../server/api/controllers/mfa.controller.ts | 18 +- .../api/controllers/signup.controller.ts | 91 ++-- .../server/api/controllers/user.controller.ts | 38 +- .../api/controllers/wishlist.controller.ts | 30 +- src/lib/server/api/databases/migrate.ts | 26 + .../migrations/0000_dazzling_stick.sql | 0 .../migrations/0001_noisy_sally_floyd.sql | 0 .../migrations/0002_fancy_valkyrie.sql | 0 .../migrations/0003_worried_taskmaster.sql | 0 .../migrations/0004_heavy_sphinx.sql | 0 .../migrations/0005_true_mathemanic.sql | 0 .../migrations/meta/0000_snapshot.json | 0 .../migrations/meta/0001_snapshot.json | 0 .../migrations/meta/0002_snapshot.json | 0 .../migrations/meta/0003_snapshot.json | 0 .../migrations/meta/0004_snapshot.json | 0 .../migrations/meta/0005_snapshot.json | 0 .../migrations/meta/_journal.json | 0 .../schemas/collections.schema.ts | 7 +- .../schemas/users.schemas.ts | 2 +- .../database => databases}/seed.ts | 24 +- .../seeds/data/roles.json | 0 .../seeds/data/users.json | 0 .../database => databases}/seeds/index.ts | 0 src/lib/server/api/databases/seeds/roles.ts | 11 + .../database => databases}/seeds/users.ts | 92 ++-- .../tables/categories.table.ts | 18 +- .../tables/categoriesToExternalIdsTable.ts | 0 .../tables/categoriesToGames.ts | 0 .../tables/collectionItems.ts | 18 +- .../tables/collections.ts | 17 +- .../tables/credentials.table.ts | 16 +- .../tables/expansions.ts | 16 +- .../tables/externalIds.ts | 0 .../tables/federatedIdentity.table.ts | 14 +- .../database => databases}/tables/games.ts | 22 +- .../tables/gamesToExternalIds.ts | 0 .../database => databases}/tables/index.ts | 0 .../server/api/databases/tables/mechanics.ts | 23 + .../tables/mechanicsToExternalIds.ts | 0 .../tables/mechanicsToGames.ts | 0 .../tables/passwordResetTokens.ts | 16 +- .../server/api/databases/tables/publishers.ts | 23 + .../tables/publishersToExternalIds.ts | 0 .../tables/publishersToGames.ts | 0 .../tables/recovery-codes.table.ts | 12 +- src/lib/server/api/databases/tables/roles.ts | 21 + .../tables/sessions.table.ts | 0 .../tables/two-factor.table.ts | 16 +- .../tables/userRoles.ts | 18 +- .../tables/users.table.ts | 6 +- .../tables/wishlistItems.ts | 18 +- .../tables/wishlists.ts | 16 +- .../server/api/dtos/create-user-role.dto.ts | 1 + src/lib/server/api/dtos/id-params.dto.ts | 5 + .../api/dtos/register-emailpassword.dto.ts | 20 + .../server/api/dtos/signin-username.dto.ts | 12 + .../api/dtos/signup-username-email.dto.ts | 24 + src/lib/server/api/dtos/update-email.dto.ts | 11 + src/lib/server/api/dtos/update-profile.dto.ts | 23 + .../server/api/dtos/verify-password.dto.ts | 7 + src/lib/server/api/dtos/verify-totp.dto.ts | 11 + .../email-change-notice.hbs | 0 src/lib/server/api/index.ts | 91 ++-- .../api/infrastructure/database/migrate.ts | 26 - .../infrastructure/database/seeds/roles.ts | 11 - .../database/tables/mechanics.ts | 23 - .../database/tables/publishers.ts | 23 - .../infrastructure/database/tables/roles.ts | 21 - .../api/infrastructure/database/utils.ts | 42 -- .../api/interfaces/controller.interface.ts | 8 - src/lib/server/api/jobs/auth-cleanup.job.ts | 42 ++ .../server/api/middleware/auth.middleware.ts | 8 +- .../api/middleware/rate-limiter.middleware.ts | 54 +- .../database/index.ts => packages/drizzle.ts} | 14 +- .../auth => packages}/lucia.ts | 44 +- .../server/api/providers/database.provider.ts | 10 +- src/lib/server/api/providers/index.ts | 3 - .../server/api/providers/lucia.provider.ts | 10 +- .../server/api/providers/redis.provider.ts | 19 +- .../repositories/collections.repository.ts | 50 +- .../repositories/credentials.repository.ts | 15 +- .../api/repositories/roles.repository.ts | 60 ++- .../api/repositories/user_roles.repository.ts | 45 +- .../api/repositories/users.repository.ts | 56 +-- .../api/repositories/wishlists.repository.ts | 52 +- src/lib/server/api/services/iam.service.ts | 49 +- src/lib/server/api/services/jobs.service.ts | 16 + .../api/services/loginrequest.service.ts | 147 +++--- src/lib/server/api/services/mailer.service.ts | 126 ++--- src/lib/server/api/services/queues.service.ts | 19 - src/lib/server/api/services/totp.service.ts | 44 +- src/lib/server/api/services/users.service.ts | 66 +-- src/lib/server/auth-utils.ts | 24 +- src/lib/utils/db/categoryUtils.ts | 64 ++- src/lib/utils/db/expansionUtils.ts | 39 +- src/lib/utils/db/gameUtils.ts | 134 +++-- src/lib/utils/db/mechanicUtils.ts | 46 +- src/lib/utils/db/publisherUtils.ts | 68 ++- .../(app)/(protected)/admin/+layout.server.ts | 36 +- .../(protected)/admin/users/+page.server.ts | 31 +- .../admin/users/[id]/+page.server.ts | 112 ++--- .../(protected)/collections/+page.server.ts | 107 ++-- .../collections/[cuid]/+page.server.ts | 131 +++-- .../(app)/(protected)/list/+layout.server.ts | 24 +- .../(protected)/list/[id]/+page.server.ts | 85 ++-- .../(app)/(protected)/profile/+page.server.ts | 219 ++++---- .../(app)/(protected)/profile/+page.svelte | 75 +-- .../profile/security/mfa/+page.server.ts | 34 +- .../mfa/recovery-codes/+page.server.ts | 24 +- .../security/password/change/+page.server.ts | 95 ++-- .../(protected)/wishlists/+page.server.ts | 131 +++-- .../wishlists/[cuid]/+page.server.ts | 128 +++-- src/routes/(app)/+page.server.ts | 44 +- src/routes/(app)/game/[id]/+page.server.ts | 128 ++--- src/routes/(auth)/login/+page.server.ts | 12 +- src/routes/(auth)/signup/+page.server.ts | 50 +- src/routes/(auth)/signup/+page.svelte | 62 +-- src/routes/(auth)/totp/+page.server.ts | 469 ++++++++---------- 137 files changed, 2344 insertions(+), 2405 deletions(-) rename src/lib/server/api/common/{errors.ts => exceptions.ts} (100%) create mode 100644 src/lib/server/api/common/interfaces/controller.interface.ts create mode 100644 src/lib/server/api/common/interfaces/email.interface.ts create mode 100644 src/lib/server/api/common/interfaces/repository.interface.ts create mode 100644 src/lib/server/api/common/utils/repository.utils.ts create mode 100644 src/lib/server/api/common/utils/table.utils.ts rename src/lib/server/api/{common => configs}/config.ts (100%) create mode 100644 src/lib/server/api/databases/migrate.ts rename src/lib/server/api/{infrastructure/database => databases}/migrations/0000_dazzling_stick.sql (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/0001_noisy_sally_floyd.sql (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/0002_fancy_valkyrie.sql (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/0003_worried_taskmaster.sql (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/0004_heavy_sphinx.sql (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/0005_true_mathemanic.sql (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/0000_snapshot.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/0001_snapshot.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/0002_snapshot.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/0003_snapshot.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/0004_snapshot.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/0005_snapshot.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/migrations/meta/_journal.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/schemas/collections.schema.ts (68%) rename src/lib/server/api/{infrastructure/database => databases}/schemas/users.schemas.ts (92%) rename src/lib/server/api/{infrastructure/database => databases}/seed.ts (73%) rename src/lib/server/api/{infrastructure/database => databases}/seeds/data/roles.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/seeds/data/users.json (100%) rename src/lib/server/api/{infrastructure/database => databases}/seeds/index.ts (100%) create mode 100644 src/lib/server/api/databases/seeds/roles.ts rename src/lib/server/api/{infrastructure/database => databases}/seeds/users.ts (53%) rename src/lib/server/api/{infrastructure/database => databases}/tables/categories.table.ts (54%) rename src/lib/server/api/{infrastructure/database => databases}/tables/categoriesToExternalIdsTable.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/categoriesToGames.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/collectionItems.ts (75%) rename src/lib/server/api/{infrastructure/database => databases}/tables/collections.ts (58%) rename src/lib/server/api/{infrastructure/database => databases}/tables/credentials.table.ts (64%) rename src/lib/server/api/{infrastructure/database => databases}/tables/expansions.ts (65%) rename src/lib/server/api/{infrastructure/database => databases}/tables/externalIds.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/federatedIdentity.table.ts (63%) rename src/lib/server/api/{infrastructure/database => databases}/tables/games.ts (74%) rename src/lib/server/api/{infrastructure/database => databases}/tables/gamesToExternalIds.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/index.ts (100%) create mode 100644 src/lib/server/api/databases/tables/mechanics.ts rename src/lib/server/api/{infrastructure/database => databases}/tables/mechanicsToExternalIds.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/mechanicsToGames.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/passwordResetTokens.ts (70%) create mode 100644 src/lib/server/api/databases/tables/publishers.ts rename src/lib/server/api/{infrastructure/database => databases}/tables/publishersToExternalIds.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/publishersToGames.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/recovery-codes.table.ts (68%) create mode 100644 src/lib/server/api/databases/tables/roles.ts rename src/lib/server/api/{infrastructure/database => databases}/tables/sessions.table.ts (100%) rename src/lib/server/api/{infrastructure/database => databases}/tables/two-factor.table.ts (68%) rename src/lib/server/api/{infrastructure/database => databases}/tables/userRoles.ts (68%) rename src/lib/server/api/{infrastructure/database => databases}/tables/users.table.ts (92%) rename src/lib/server/api/{infrastructure/database => databases}/tables/wishlistItems.ts (68%) rename src/lib/server/api/{infrastructure/database => databases}/tables/wishlists.ts (58%) create mode 100644 src/lib/server/api/dtos/create-user-role.dto.ts create mode 100644 src/lib/server/api/dtos/id-params.dto.ts create mode 100644 src/lib/server/api/dtos/register-emailpassword.dto.ts create mode 100644 src/lib/server/api/dtos/signin-username.dto.ts create mode 100644 src/lib/server/api/dtos/signup-username-email.dto.ts create mode 100644 src/lib/server/api/dtos/update-email.dto.ts create mode 100644 src/lib/server/api/dtos/update-profile.dto.ts create mode 100644 src/lib/server/api/dtos/verify-password.dto.ts create mode 100644 src/lib/server/api/dtos/verify-totp.dto.ts rename src/lib/server/api/{infrastructure/email-templates => emails}/email-change-notice.hbs (100%) delete mode 100644 src/lib/server/api/infrastructure/database/migrate.ts delete mode 100644 src/lib/server/api/infrastructure/database/seeds/roles.ts delete mode 100644 src/lib/server/api/infrastructure/database/tables/mechanics.ts delete mode 100644 src/lib/server/api/infrastructure/database/tables/publishers.ts delete mode 100644 src/lib/server/api/infrastructure/database/tables/roles.ts delete mode 100644 src/lib/server/api/infrastructure/database/utils.ts delete mode 100644 src/lib/server/api/interfaces/controller.interface.ts create mode 100644 src/lib/server/api/jobs/auth-cleanup.job.ts rename src/lib/server/api/{infrastructure/database/index.ts => packages/drizzle.ts} (68%) rename src/lib/server/api/{infrastructure/auth => packages}/lucia.ts (66%) delete mode 100644 src/lib/server/api/providers/index.ts create mode 100644 src/lib/server/api/services/jobs.service.ts delete mode 100644 src/lib/server/api/services/queues.service.ts diff --git a/.gitignore b/.gitignore index 88ad69d..15629aa 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ node_modules .env.* *.xdp* !.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* .vercel .output .idea diff --git a/drizzle.config.ts b/drizzle.config.ts index e981991..f4618ee 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,11 +1,11 @@ -import 'dotenv/config'; -import { defineConfig } from 'drizzle-kit'; -import env from './src/env'; +import 'dotenv/config' +import { defineConfig } from 'drizzle-kit' +import env from './src/env' export default defineConfig({ dialect: 'postgresql', - out: './src/lib/server/api/infrastructure/database/migrations', - schema: './src/lib/server/api/infrastructure/database/tables/index.ts', + out: './src/lib/server/api/databases/migrations', + schema: './src/lib/server/api/databases/tables/drizzle.ts', dbCredentials: { host: env.DATABASE_HOST || 'localhost', port: Number(env.DATABASE_PORT) || 5432, @@ -20,6 +20,6 @@ export default defineConfig({ strict: true, migrations: { table: 'migrations', - schema: 'public' - } -}); + schema: 'public', + }, +}) diff --git a/oldApis/collection/[id]/search/+server.ts b/oldApis/collection/[id]/search/+server.ts index 528c6f6..37e442e 100644 --- a/oldApis/collection/[id]/search/+server.ts +++ b/oldApis/collection/[id]/search/+server.ts @@ -1,32 +1,32 @@ -import { error, json } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import {db} from '$lib/server/api/infrastructure/database'; -import { collection_items, usersTable } from '$db/schema'; +import { collection_items, usersTable } from '$db/schema' +import { error, json } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import { db } from '../../../../src/lib/server/api/packages/drizzle' // Search a user's collection export async function GET({ url, locals, params }) { - const searchParams = Object.fromEntries(url.searchParams); - const q = searchParams?.q || ''; - const limit = Number.parseInt(searchParams?.limit) || 10; - const skip = Number.parseInt(searchParams?.skip) || 0; - const order = searchParams?.order || 'asc'; - const sort = searchParams?.sort || 'name'; - const collection_id = params.id; - console.log('url', url); - console.log('username', locals?.user?.id); + const searchParams = Object.fromEntries(url.searchParams) + const q = searchParams?.q || '' + const limit = Number.parseInt(searchParams?.limit) || 10 + const skip = Number.parseInt(searchParams?.skip) || 0 + const order = searchParams?.order || 'asc' + const sort = searchParams?.sort || 'name' + const collection_id = params.id + console.log('url', url) + console.log('username', locals?.user?.id) if (!locals.user) { - error(401, { message: 'Unauthorized' }); + error(401, { message: 'Unauthorized' }) } const collection = await db.query.collections.findFirst({ where: eq(usersTable.id, locals?.user?.id), - }); - console.log('collection', collection); + }) + console.log('collection', collection) if (!collection) { - console.log('Collection was not found'); - error(404, { message: 'Collection was not found' }); + console.log('Collection was not found') + error(404, { message: 'Collection was not found' }) } try { @@ -42,21 +42,20 @@ export async function GET({ url, locals, params }) { }, }, orderBy: (collection_items, { asc, desc }) => { - const dbSort = - sort === 'dateAdded' ? collection_items.created_at : collection_items.times_played; + const dbSort = sort === 'dateAdded' ? collection_items.created_at : collection_items.times_played if (order === 'asc') { - return asc(dbSort); + return asc(dbSort) } else { - return desc(dbSort); + return desc(dbSort) } }, offset: skip, limit, - }); + }) - return json(userCollectionItems); + return json(userCollectionItems) } catch (e) { - console.error(e); - error(500, { message: 'Something went wrong' }); + console.error(e) + error(500, { message: 'Something went wrong' }) } } diff --git a/oldApis/reset-password/+server.ts b/oldApis/reset-password/+server.ts index c661f15..3187ce2 100644 --- a/oldApis/reset-password/+server.ts +++ b/oldApis/reset-password/+server.ts @@ -1,34 +1,34 @@ -import { db } from '$lib/server/api/infrastructure/database'; -import { error } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import { usersTable } from '$lib/server/api/infrastructure/database/tables'; -import { createPasswordResetToken } from '$lib/server/auth-utils.js'; -import { PUBLIC_SITE_URL } from '$env/static/public'; +import { PUBLIC_SITE_URL } from '$env/static/public' +import { createPasswordResetToken } from '$lib/server/auth-utils.js' +import { error } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import { usersTable } from '../../src/lib/server/api/databases/tables' +import { db } from '../../src/lib/server/api/packages/drizzle' export async function POST({ locals, request }) { - const { email }: { email: string } = await request.json(); + const { email }: { email: string } = await request.json() if (!locals.user) { - error(401, { message: 'Unauthorized' }); + error(401, { message: 'Unauthorized' }) } const user = await db.query.usersTable.findFirst({ where: eq(usersTable.email, email), - }); + }) if (!user) { error(200, { message: 'Email sent! Please check your email for a link to reset your password.', - }); + }) } - const verificationToken = await createPasswordResetToken(user.id); - const verificationLink = PUBLIC_SITE_URL + verificationToken; + const verificationToken = await createPasswordResetToken(user.id) + const verificationLink = PUBLIC_SITE_URL + verificationToken // TODO: send email - console.log('Verification link: ' + verificationLink); + console.log('Verification link: ' + verificationLink) return new Response(null, { status: 200, - }); + }) } diff --git a/oldApis/reset-password/[token]/+server.ts b/oldApis/reset-password/[token]/+server.ts index 2b50676..8d6360b 100644 --- a/oldApis/reset-password/[token]/+server.ts +++ b/oldApis/reset-password/[token]/+server.ts @@ -1,34 +1,34 @@ -import { eq } from 'drizzle-orm'; -import { password_reset_tokens } from '$lib/server/api/infrastructure/database/tables'; -import { isWithinExpirationDate } from 'oslo'; +import { eq } from 'drizzle-orm' +import { isWithinExpirationDate } from 'oslo' +import { password_reset_tokens } from '../../../src/lib/server/api/databases/tables' // import { lucia } from '$lib/server/lucia'; -import {db} from '$lib/server/api/infrastructure/database'; +import { db } from '../../../src/lib/server/api/packages/drizzle' export async function POST({ request, params }) { - const { password } = await request.json(); + const { password } = await request.json() if (typeof password !== 'string' || password.length < 8) { return new Response(null, { status: 400, - }); + }) } - const verificationToken = params.token; + const verificationToken = params.token const token = await db.query.password_reset_tokens.findFirst({ where: eq(password_reset_tokens.id, verificationToken), - }); + }) if (!token) { - await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken)); + await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken)) return new Response(null, { status: 400, - }); + }) } if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) { return new Response(null, { status: 400, - }); + }) } // await lucia.invalidateUserSessions(token.user_id); @@ -44,5 +44,5 @@ export async function POST({ request, params }) { Location: '/', 'Set-Cookie': sessionCookie.serialize(), }, - }); + }) } diff --git a/package.json b/package.json index dd9738d..7fea7c8 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "scripts": { "db:push": "drizzle-kit push", "db:generate": "drizzle-kit generate", - "db:migrate": "tsx src/lib/server/api/infrastructure/database/migrate.ts", - "db:seed": "tsx src/lib/server/api/infrastructure/database/seed.ts", + "db:migrate": "tsx src/lib/server/api/databases/migrate.ts", + "db:seed": "tsx src/lib/server/api/databases/seed.ts", "db:studio": "drizzle-kit studio --verbose", "dev": "NODE_OPTIONS=\"--inspect\" vite dev --host", "build": "vite build", diff --git a/src/env.ts b/src/env.ts index 33366fa..fd60759 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,13 +1,13 @@ -import { config } from 'dotenv'; -import { expand } from 'dotenv-expand'; -import { ZodError, z } from 'zod'; +import { config } from 'dotenv' +import { expand } from 'dotenv-expand' +import { ZodError, z } from 'zod' const stringBoolean = z.coerce .string() .transform((val) => { - return val === 'true'; + return val === 'true' }) - .default('false'); + .default('false') const EnvSchema = z.object({ ADMIN_USERNAME: z.string(), @@ -20,6 +20,7 @@ const EnvSchema = z.object({ DB_MIGRATING: stringBoolean, DB_SEEDING: stringBoolean, NODE_ENV: z.string().default('development'), + ORIGIN: z.string(), PUBLIC_SITE_NAME: z.string(), PUBLIC_SITE_URL: z.string(), PUBLIC_UMAMI_DO_NOT_TRACK: z.string(), @@ -27,26 +28,25 @@ const EnvSchema = z.object({ PUBLIC_UMAMI_URL: z.string(), REDIS_URL: z.string(), TWO_FACTOR_TIMEOUT: z.coerce.number().default(300000), -}); +}) -export type EnvSchema = z.infer; +export type EnvSchema = z.infer -expand(config()); +expand(config()) try { - EnvSchema.parse(process.env); + EnvSchema.parse(process.env) } catch (error) { if (error instanceof ZodError) { - let message = 'Missing required values in .env:\n'; + let message = 'Missing required values in .env:\n' for (const issue of error.issues) { - message += `${issue.path[0]}\n`; + message += `${issue.path[0]}\n` } - const e = new Error(message); - e.stack = ''; - throw e; - } else { - console.error(error); + const e = new Error(message) + e.stack = '' + throw e } + console.error(error) } -export default EnvSchema.parse(process.env); +export default EnvSchema.parse(process.env) diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6b91f8b..6b9521d 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,19 +1,10 @@ -// import * as Sentry from '@sentry/sveltekit'; import 'reflect-metadata' -import { hc } from 'hono/client'; -import { sequence } from '@sveltejs/kit/hooks'; -import { redirect, type Handle } from '@sveltejs/kit'; -import type { ApiRoutes } from '$lib/server/api'; -import { parseApiResponse } from '$lib/utils/api'; -import { StatusCodes } from '$lib/constants/status-codes'; - -// TODO: Fix Sentry as it is not working on SvelteKit v2 -// Sentry.init({ -// dsn: 'https://742e43279df93a3c4a4a78c12eb1f879@o4506057768632320.ingest.sentry.io/4506057770401792', -// tracesSampleRate: 1, -// environment: dev ? 'development' : 'production', -// enabled: !dev -// }); +import { StatusCodes } from '$lib/constants/status-codes' +import type { ApiRoutes } from '$lib/server/api' +import { parseApiResponse } from '$lib/utils/api' +import { type Handle, redirect } from '@sveltejs/kit' +import { sequence } from '@sveltejs/kit/hooks' +import { hc } from 'hono/client' const apiClient: Handle = async ({ event, resolve }) => { /* ------------------------------ Register api ------------------------------ */ @@ -21,76 +12,31 @@ const apiClient: Handle = async ({ event, resolve }) => { fetch: event.fetch, headers: { 'x-forwarded-for': event.url.host.includes('sveltekit-prerender') ? '127.0.0.1' : event.getClientAddress(), - host: event.request.headers.get('host') || '' - } - }); + host: event.request.headers.get('host') || '', + }, + }) /* ----------------------------- Auth functions ----------------------------- */ async function getAuthedUser() { const { data } = await api.user.$get().then(parseApiResponse) - return data?.user; + return data?.user } async function getAuthedUserOrThrow() { - const { data } = await api.user.$get().then(parseApiResponse); - if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/'); - return data?.user; + const { data } = await api.user.$get().then(parseApiResponse) + if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, '/') + return data?.user } /* ------------------------------ Set contexts ------------------------------ */ - event.locals.api = api; - event.locals.parseApiResponse = parseApiResponse; - event.locals.getAuthedUser = getAuthedUser; - event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow; + event.locals.api = api + event.locals.parseApiResponse = parseApiResponse + event.locals.getAuthedUser = getAuthedUser + event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow /* ----------------------------- Return response ---------------------------- */ - const response = await resolve(event); - return response; -}; + const response = await resolve(event) + return response +} -// export const authentication: Handle = async function ({ event, resolve }) { -// event.locals.startTimer = Date.now(); -// -// const ip = event.request.headers.get('x-forwarded-for') as string; -// const country = event.request.headers.get('x-vercel-ip-country') as string; -// event.locals.ip = dev ? '127.0.0.1' : ip; // || event.getClientAddress(); -// event.locals.country = dev ? 'us' : country; -// -// 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); -// console.log('sessionCookie', JSON.stringify(sessionCookie, null, 2)); -// // sveltekit types deviates from the de-facto standard, you can use 'as any' too -// event.cookies.set(sessionCookie.name, sessionCookie.value, { -// path: '.', -// ...sessionCookie.attributes, -// }); -// } -// console.log('session from hooks', JSON.stringify(session, null, 2)); -// if (!session) { -// const sessionCookie = lucia.createBlankSessionCookie(); -// console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2)); -// event.cookies.set(sessionCookie.name, sessionCookie.value, { -// path: '.', -// ...sessionCookie.attributes, -// }); -// } -// event.locals.user = user; -// event.locals.session = session; -// -// return resolve(event); -// }; - -export const handle: Handle = sequence( - // Sentry.sentryHandle(), - // authentication, - apiClient -); -// export const handleError = Sentry.handleErrorWithSentry(); +export const handle: Handle = sequence(apiClient) diff --git a/src/lib/server/api/common/errors.ts b/src/lib/server/api/common/exceptions.ts similarity index 100% rename from src/lib/server/api/common/errors.ts rename to src/lib/server/api/common/exceptions.ts diff --git a/src/lib/server/api/common/interfaces/controller.interface.ts b/src/lib/server/api/common/interfaces/controller.interface.ts new file mode 100644 index 0000000..7bb91a2 --- /dev/null +++ b/src/lib/server/api/common/interfaces/controller.interface.ts @@ -0,0 +1,8 @@ +import { Hono } from 'hono' +import type { BlankSchema } from 'hono/types' +import type { HonoTypes } from '../../types' + +export interface Controller { + controller: Hono + routes(): any +} diff --git a/src/lib/server/api/common/interfaces/email.interface.ts b/src/lib/server/api/common/interfaces/email.interface.ts new file mode 100644 index 0000000..ae19964 --- /dev/null +++ b/src/lib/server/api/common/interfaces/email.interface.ts @@ -0,0 +1,4 @@ +export interface Email { + subject(): string + html(): string +} diff --git a/src/lib/server/api/common/interfaces/repository.interface.ts b/src/lib/server/api/common/interfaces/repository.interface.ts new file mode 100644 index 0000000..fc96e24 --- /dev/null +++ b/src/lib/server/api/common/interfaces/repository.interface.ts @@ -0,0 +1,5 @@ +import type { DatabaseProvider } from '$lib/server/api/providers/database.provider' + +export interface Repository { + trxHost(trx: DatabaseProvider): any +} diff --git a/src/lib/server/api/common/utils/repository.utils.ts b/src/lib/server/api/common/utils/repository.utils.ts new file mode 100644 index 0000000..1310269 --- /dev/null +++ b/src/lib/server/api/common/utils/repository.utils.ts @@ -0,0 +1,14 @@ +import { HTTPException } from 'hono/http-exception' + +export const takeFirst = (values: T[]): T | null => { + if (values.length === 0) return null + return values[0] as T +} + +export const takeFirstOrThrow = (values: T[]): T => { + if (values.length === 0) + throw new HTTPException(404, { + message: 'Resource not found', + }) + return values[0] as T +} diff --git a/src/lib/server/api/common/utils/table.utils.ts b/src/lib/server/api/common/utils/table.utils.ts new file mode 100644 index 0000000..a9fe09d --- /dev/null +++ b/src/lib/server/api/common/utils/table.utils.ts @@ -0,0 +1,29 @@ +import { timestamp } from 'drizzle-orm/pg-core' +import { customType } from 'drizzle-orm/pg-core' + +export const citext = customType<{ data: string }>({ + dataType() { + return 'citext' + }, +}) + +export const cuid2 = customType<{ data: string }>({ + dataType() { + return 'text' + }, +}) + +export const timestamps = { + createdAt: timestamp('created_at', { + mode: 'date', + withTimezone: true, + }) + .notNull() + .defaultNow(), + updatedAt: timestamp('updated_at', { + mode: 'date', + withTimezone: true, + }) + .notNull() + .defaultNow(), +} diff --git a/src/lib/server/api/common/config.ts b/src/lib/server/api/configs/config.ts similarity index 100% rename from src/lib/server/api/common/config.ts rename to src/lib/server/api/configs/config.ts diff --git a/src/lib/server/api/controllers/collection.controller.ts b/src/lib/server/api/controllers/collection.controller.ts index 8f61bd1..169e6b5 100644 --- a/src/lib/server/api/controllers/collection.controller.ts +++ b/src/lib/server/api/controllers/collection.controller.ts @@ -1,31 +1,29 @@ -import 'reflect-metadata'; -import { Hono } from 'hono'; -import { inject, injectable } from 'tsyringe'; -import { requireAuth } from "../middleware/auth.middleware"; -import type { HonoTypes } from '../types'; -import type { Controller } from '../interfaces/controller.interface'; -import {CollectionsService} from "$lib/server/api/services/collections.service"; +import 'reflect-metadata' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { CollectionsService } from '$lib/server/api/services/collections.service' +import { Hono } from 'hono' +import { inject, injectable } from 'tsyringe' +import { requireAuth } from '../middleware/auth.middleware' +import type { HonoTypes } from '../types' @injectable() export class CollectionController implements Controller { - controller = new Hono(); + controller = new Hono() - constructor( - @inject(CollectionsService) private readonly collectionsService: CollectionsService, - ) { } + constructor(@inject(CollectionsService) private readonly collectionsService: CollectionsService) {} routes() { return this.controller .get('/', requireAuth, async (c) => { - const user = c.var.user; - const collections = await this.collectionsService.findAllByUserId(user.id); + const user = c.var.user + const collections = await this.collectionsService.findAllByUserId(user.id) console.log('collections service', collections) - return c.json({ collections }); + return c.json({ collections }) }) .get('/:cuid', requireAuth, async (c) => { - const cuid = c.req.param('cuid'); - const collection = await this.collectionsService.findOneByCuid(cuid); - return c.json({ collection }); - }); + const cuid = c.req.param('cuid') + const collection = await this.collectionsService.findOneByCuid(cuid) + return c.json({ collection }) + }) } } diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 0f34305..df28fb0 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,17 +1,17 @@ -import { Hono } from 'hono' -import { inject, injectable } from 'tsyringe' -import { setCookie } from 'hono/cookie' -import { zValidator } from '@hono/zod-validator' -import type { HonoTypes } from '../types' -import { requireAuth } from '../middleware/auth.middleware' -import type { Controller } from '$lib/server/api/interfaces/controller.interface' -import { IamService } from '$lib/server/api/services/iam.service' -import { LuciaProvider } from '$lib/server/api/providers' -import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware' -import { updateProfileDto } from '$lib/dtos/update-profile.dto' -import { updateEmailDto } from '$lib/dtos/update-email.dto' import { StatusCodes } from '$lib/constants/status-codes' -import { verifyPasswordDto } from '$lib/dtos/verify-password.dto' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto' +import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto' +import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto' +import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware' +import { LuciaProvider } from '$lib/server/api/providers/lucia.provider' +import { IamService } from '$lib/server/api/services/iam.service' +import { zValidator } from '@hono/zod-validator' +import { Hono } from 'hono' +import { setCookie } from 'hono/cookie' +import { inject, injectable } from 'tsyringe' +import { requireAuth } from '../middleware/auth.middleware' +import type { HonoTypes } from '../types' @injectable() export class IamController implements Controller { diff --git a/src/lib/server/api/controllers/login.controller.ts b/src/lib/server/api/controllers/login.controller.ts index c86cefc..df9ed50 100644 --- a/src/lib/server/api/controllers/login.controller.ts +++ b/src/lib/server/api/controllers/login.controller.ts @@ -1,43 +1,44 @@ -import 'reflect-metadata'; -import { Hono } from 'hono'; -import { setCookie } from 'hono/cookie'; -import { zValidator } from '@hono/zod-validator'; -import { inject, injectable } from 'tsyringe'; -import { TimeSpan } from 'oslo'; -import type { HonoTypes } from '../types'; -import { limiter } from '../middleware/rate-limiter.middleware'; -import type { Controller } from '../interfaces/controller.interface'; -import { LoginRequestsService } from '../services/loginrequest.service'; -import { signinUsernameDto } from "$lib/dtos/signin-username.dto"; -import {LuciaProvider} from "$lib/server/api/providers"; +import 'reflect-metadata' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto' +import { LuciaProvider } from '$lib/server/api/providers/lucia.provider' +import { zValidator } from '@hono/zod-validator' +import { Hono } from 'hono' +import { setCookie } from 'hono/cookie' +import { TimeSpan } from 'oslo' +import { inject, injectable } from 'tsyringe' +import { limiter } from '../middleware/rate-limiter.middleware' +import { LoginRequestsService } from '../services/loginrequest.service' +import type { HonoTypes } from '../types' @injectable() export class LoginController implements Controller { - controller = new Hono(); + controller = new Hono() constructor( @inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService, - @inject(LuciaProvider) private lucia: LuciaProvider - ) { } + @inject(LuciaProvider) private lucia: LuciaProvider, + ) {} routes() { - return this.controller - .post('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { username, password } = c.req.valid('json'); - const session = await this.loginRequestsService.verify({ username, password }, c.req); - const sessionCookie = this.lucia.createSessionCookie(session.id); - console.log("set cookie", sessionCookie); - setCookie(c, sessionCookie.name, sessionCookie.value, { - path: sessionCookie.attributes.path, - maxAge: sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() - ? sessionCookie.attributes.maxAge : new TimeSpan(2, 'w').seconds(), - domain: sessionCookie.attributes.domain, - sameSite: sessionCookie.attributes.sameSite as any, - secure: sessionCookie.attributes.secure, - httpOnly: sessionCookie.attributes.httpOnly, - expires: sessionCookie.attributes.expires - }); - return c.json({ message: 'ok' }); + return this.controller.post('/', zValidator('json', signinUsernameDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { username, password } = c.req.valid('json') + const session = await this.loginRequestsService.verify({ username, password }, c.req) + const sessionCookie = this.lucia.createSessionCookie(session.id) + console.log('set cookie', sessionCookie) + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: + sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge + : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, }) + return c.json({ message: 'ok' }) + }) } } diff --git a/src/lib/server/api/controllers/mfa.controller.ts b/src/lib/server/api/controllers/mfa.controller.ts index dbbc13c..148b390 100644 --- a/src/lib/server/api/controllers/mfa.controller.ts +++ b/src/lib/server/api/controllers/mfa.controller.ts @@ -1,15 +1,15 @@ import 'reflect-metadata' -import { inject, injectable } from 'tsyringe' -import { Hono } from 'hono' -import { zValidator } from '@hono/zod-validator' -import { requireAuth } from '../middleware/auth.middleware' -import type { HonoTypes } from '../types' -import type { Controller } from '$lib/server/api/interfaces/controller.interface' -import { TotpService } from '$lib/server/api/services/totp.service' import { StatusCodes } from '$lib/constants/status-codes' -import { verifyTotpDto } from '$lib/dtos/verify-totp.dto' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto' +import { TotpService } from '$lib/server/api/services/totp.service' +import { zValidator } from '@hono/zod-validator' +import { Hono } from 'hono' +import { inject, injectable } from 'tsyringe' +import { CredentialsType } from '../databases/tables' +import { requireAuth } from '../middleware/auth.middleware' import { UsersService } from '../services/users.service' -import { CredentialsType } from '$db/tables' +import type { HonoTypes } from '../types' @injectable() export class MfaController implements Controller { diff --git a/src/lib/server/api/controllers/signup.controller.ts b/src/lib/server/api/controllers/signup.controller.ts index f8070e9..67a7b24 100644 --- a/src/lib/server/api/controllers/signup.controller.ts +++ b/src/lib/server/api/controllers/signup.controller.ts @@ -1,57 +1,58 @@ -import 'reflect-metadata'; -import { Hono } from 'hono'; -import { setCookie } from 'hono/cookie'; -import {inject, injectable} from 'tsyringe'; -import { zValidator } from '@hono/zod-validator'; -import { TimeSpan } from 'oslo'; -import type { HonoTypes } from '../types'; -import type { Controller } from '../interfaces/controller.interface'; -import { signupUsernameEmailDto } from "$lib/dtos/signup-username-email.dto"; -import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware"; -import {UsersService} from "$lib/server/api/services/users.service"; -import {LoginRequestsService} from "$lib/server/api/services/loginrequest.service"; -import {LuciaProvider} from "$lib/server/api/providers"; +import 'reflect-metadata' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { signupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto' +import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware' +import { LuciaProvider } from '$lib/server/api/providers/lucia.provider' +import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service' +import { UsersService } from '$lib/server/api/services/users.service' +import { zValidator } from '@hono/zod-validator' +import { Hono } from 'hono' +import { setCookie } from 'hono/cookie' +import { TimeSpan } from 'oslo' +import { inject, injectable } from 'tsyringe' +import type { HonoTypes } from '../types' @injectable() export class SignupController implements Controller { - controller = new Hono(); + controller = new Hono() constructor( - @inject(UsersService) private readonly usersService: UsersService, - @inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService, - @inject(LuciaProvider) private lucia: LuciaProvider - ) { } + @inject(UsersService) private readonly usersService: UsersService, + @inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService, + @inject(LuciaProvider) private lucia: LuciaProvider, + ) {} routes() { - return this.controller - .post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json'); - const existingUser = await this.usersService.findOneByUsername(username); + return this.controller.post('/', zValidator('json', signupUsernameEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const { firstName, lastName, email, username, password, confirm_password } = await c.req.valid('json') + const existingUser = await this.usersService.findOneByUsername(username) - if (existingUser) { - return c.body("User already exists", 400); - } + if (existingUser) { + return c.body('User already exists', 400) + } - const user = await this.usersService.create({ firstName, lastName, email, username, password, confirm_password }); + const user = await this.usersService.create({ firstName, lastName, email, username, password, confirm_password }) - if (!user) { - return c.body("Failed to create user", 500); - } + if (!user) { + return c.body('Failed to create user', 500) + } - const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined); - const sessionCookie = this.lucia.createSessionCookie(session.id); - console.log("set cookie", sessionCookie); - setCookie(c, sessionCookie.name, sessionCookie.value, { - path: sessionCookie.attributes.path, - maxAge: sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() - ? sessionCookie.attributes.maxAge : new TimeSpan(2, 'w').seconds(), - domain: sessionCookie.attributes.domain, - sameSite: sessionCookie.attributes.sameSite as any, - secure: sessionCookie.attributes.secure, - httpOnly: sessionCookie.attributes.httpOnly, - expires: sessionCookie.attributes.expires - }); - return c.json({ message: 'ok' }); - }); + const session = await this.loginRequestService.createUserSession(user.id, c.req, undefined) + const sessionCookie = this.lucia.createSessionCookie(session.id) + console.log('set cookie', sessionCookie) + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: + sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge + : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, + }) + return c.json({ message: 'ok' }) + }) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index 90a0cc8..ec8d7a1 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -1,34 +1,32 @@ -import 'reflect-metadata'; -import { Hono } from 'hono'; -import { inject, injectable } from 'tsyringe'; -import { requireAuth } from "../middleware/auth.middleware"; -import type { HonoTypes } from '../types'; -import type { Controller } from '../interfaces/controller.interface'; -import {UsersService} from "$lib/server/api/services/users.service"; +import 'reflect-metadata' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { UsersService } from '$lib/server/api/services/users.service' +import { Hono } from 'hono' +import { inject, injectable } from 'tsyringe' +import { requireAuth } from '../middleware/auth.middleware' +import type { HonoTypes } from '../types' @injectable() export class UserController implements Controller { - controller = new Hono(); + controller = new Hono() - constructor( - @inject(UsersService) private readonly usersService: UsersService - ) { } + constructor(@inject(UsersService) private readonly usersService: UsersService) {} routes() { return this.controller .get('/', async (c) => { - const user = c.var.user; - return c.json({ user }); + const user = c.var.user + return c.json({ user }) }) .get('/:id', requireAuth, async (c) => { - const id = c.req.param('id'); - const user = await this.usersService.findOneById(id); - return c.json({ user }); + const id = c.req.param('id') + const user = await this.usersService.findOneById(id) + return c.json({ user }) }) .get('/username/:userName', requireAuth, async (c) => { - const userName = c.req.param('userName'); - const user = await this.usersService.findOneByUsername(userName); - return c.json({ user }); - }); + const userName = c.req.param('userName') + const user = await this.usersService.findOneByUsername(userName) + return c.json({ user }) + }) } } diff --git a/src/lib/server/api/controllers/wishlist.controller.ts b/src/lib/server/api/controllers/wishlist.controller.ts index 90fdc15..9c19d37 100644 --- a/src/lib/server/api/controllers/wishlist.controller.ts +++ b/src/lib/server/api/controllers/wishlist.controller.ts @@ -1,30 +1,28 @@ -import 'reflect-metadata'; -import { Hono } from 'hono'; -import { inject, injectable } from 'tsyringe'; -import { requireAuth } from "../middleware/auth.middleware"; -import type { HonoTypes } from '../types'; -import type { Controller } from '../interfaces/controller.interface'; -import {WishlistsService} from "$lib/server/api/services/wishlists.service"; +import 'reflect-metadata' +import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' +import { WishlistsService } from '$lib/server/api/services/wishlists.service' +import { Hono } from 'hono' +import { inject, injectable } from 'tsyringe' +import { requireAuth } from '../middleware/auth.middleware' +import type { HonoTypes } from '../types' @injectable() export class WishlistController implements Controller { - controller = new Hono(); + controller = new Hono() - constructor( - @inject(WishlistsService) private readonly wishlistsService: WishlistsService - ) { } + constructor(@inject(WishlistsService) private readonly wishlistsService: WishlistsService) {} routes() { return this.controller .get('/', requireAuth, async (c) => { - const user = c.var.user; - const wishlists = await this.wishlistsService.findAllByUserId(user.id); - return c.json({ wishlists }); + const user = c.var.user + const wishlists = await this.wishlistsService.findAllByUserId(user.id) + return c.json({ wishlists }) }) .get('/:cuid', requireAuth, async (c) => { const cuid = c.req.param('cuid') const wishlist = await this.wishlistsService.findOneByCuid(cuid) - return c.json({ wishlist }); - }); + return c.json({ wishlist }) + }) } } diff --git a/src/lib/server/api/databases/migrate.ts b/src/lib/server/api/databases/migrate.ts new file mode 100644 index 0000000..d476b12 --- /dev/null +++ b/src/lib/server/api/databases/migrate.ts @@ -0,0 +1,26 @@ +import 'dotenv/config' +import { drizzle } from 'drizzle-orm/postgres-js' +import { migrate } from 'drizzle-orm/postgres-js/migrator' +import postgres from 'postgres' +import config from '../../../../../drizzle.config' +import env from '../../../../env' + +const connection = postgres({ + host: env.DATABASE_HOST || 'localhost', + port: env.DATABASE_PORT, + user: env.DATABASE_USER || 'root', + password: env.DATABASE_PASSWORD || '', + database: env.DATABASE_DB || 'boredgame', + ssl: env.NODE_ENV === 'development' ? false : 'require', + max: 1, +}) +const db = drizzle(connection) + +try { + await migrate(db, { migrationsFolder: config.out! }) + console.log('Migrations complete') +} catch (e) { + console.error(e) +} + +process.exit() diff --git a/src/lib/server/api/infrastructure/database/migrations/0000_dazzling_stick.sql b/src/lib/server/api/databases/migrations/0000_dazzling_stick.sql similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/0000_dazzling_stick.sql rename to src/lib/server/api/databases/migrations/0000_dazzling_stick.sql diff --git a/src/lib/server/api/infrastructure/database/migrations/0001_noisy_sally_floyd.sql b/src/lib/server/api/databases/migrations/0001_noisy_sally_floyd.sql similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/0001_noisy_sally_floyd.sql rename to src/lib/server/api/databases/migrations/0001_noisy_sally_floyd.sql diff --git a/src/lib/server/api/infrastructure/database/migrations/0002_fancy_valkyrie.sql b/src/lib/server/api/databases/migrations/0002_fancy_valkyrie.sql similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/0002_fancy_valkyrie.sql rename to src/lib/server/api/databases/migrations/0002_fancy_valkyrie.sql diff --git a/src/lib/server/api/infrastructure/database/migrations/0003_worried_taskmaster.sql b/src/lib/server/api/databases/migrations/0003_worried_taskmaster.sql similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/0003_worried_taskmaster.sql rename to src/lib/server/api/databases/migrations/0003_worried_taskmaster.sql diff --git a/src/lib/server/api/infrastructure/database/migrations/0004_heavy_sphinx.sql b/src/lib/server/api/databases/migrations/0004_heavy_sphinx.sql similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/0004_heavy_sphinx.sql rename to src/lib/server/api/databases/migrations/0004_heavy_sphinx.sql diff --git a/src/lib/server/api/infrastructure/database/migrations/0005_true_mathemanic.sql b/src/lib/server/api/databases/migrations/0005_true_mathemanic.sql similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/0005_true_mathemanic.sql rename to src/lib/server/api/databases/migrations/0005_true_mathemanic.sql diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json b/src/lib/server/api/databases/migrations/meta/0000_snapshot.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/0000_snapshot.json rename to src/lib/server/api/databases/migrations/meta/0000_snapshot.json diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0001_snapshot.json b/src/lib/server/api/databases/migrations/meta/0001_snapshot.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/0001_snapshot.json rename to src/lib/server/api/databases/migrations/meta/0001_snapshot.json diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0002_snapshot.json b/src/lib/server/api/databases/migrations/meta/0002_snapshot.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/0002_snapshot.json rename to src/lib/server/api/databases/migrations/meta/0002_snapshot.json diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0003_snapshot.json b/src/lib/server/api/databases/migrations/meta/0003_snapshot.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/0003_snapshot.json rename to src/lib/server/api/databases/migrations/meta/0003_snapshot.json diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0004_snapshot.json b/src/lib/server/api/databases/migrations/meta/0004_snapshot.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/0004_snapshot.json rename to src/lib/server/api/databases/migrations/meta/0004_snapshot.json diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/0005_snapshot.json b/src/lib/server/api/databases/migrations/meta/0005_snapshot.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/0005_snapshot.json rename to src/lib/server/api/databases/migrations/meta/0005_snapshot.json diff --git a/src/lib/server/api/infrastructure/database/migrations/meta/_journal.json b/src/lib/server/api/databases/migrations/meta/_journal.json similarity index 100% rename from src/lib/server/api/infrastructure/database/migrations/meta/_journal.json rename to src/lib/server/api/databases/migrations/meta/_journal.json diff --git a/src/lib/server/api/infrastructure/database/schemas/collections.schema.ts b/src/lib/server/api/databases/schemas/collections.schema.ts similarity index 68% rename from src/lib/server/api/infrastructure/database/schemas/collections.schema.ts rename to src/lib/server/api/databases/schemas/collections.schema.ts index 84d7ecc..ecb84c8 100644 --- a/src/lib/server/api/infrastructure/database/schemas/collections.schema.ts +++ b/src/lib/server/api/databases/schemas/collections.schema.ts @@ -1,11 +1,10 @@ +import { collections } from '$lib/server/api/databases/tables' import { createInsertSchema, createSelectSchema } from 'drizzle-zod' import type { z } from 'zod' -import { collections } from '$lib/server/api/infrastructure/database/tables' export const InsertCollectionSchema = createInsertSchema(collections, { - name: (schema) => schema.name.trim() - .min(3, { message: 'Must be at least 3 characters' }) - .max(64, { message: 'Must be less than 64 characters' }).optional(), + name: (schema) => + schema.name.trim().min(3, { message: 'Must be at least 3 characters' }).max(64, { message: 'Must be less than 64 characters' }).optional(), }).omit({ id: true, cuid: true, diff --git a/src/lib/server/api/infrastructure/database/schemas/users.schemas.ts b/src/lib/server/api/databases/schemas/users.schemas.ts similarity index 92% rename from src/lib/server/api/infrastructure/database/schemas/users.schemas.ts rename to src/lib/server/api/databases/schemas/users.schemas.ts index 96564a5..7d01d3e 100644 --- a/src/lib/server/api/infrastructure/database/schemas/users.schemas.ts +++ b/src/lib/server/api/databases/schemas/users.schemas.ts @@ -1,6 +1,6 @@ +import { usersTable } from '$lib/server/api/databases/tables' import { createInsertSchema, createSelectSchema } from 'drizzle-zod' import type { z } from 'zod' -import { usersTable } from '$lib/server/api/infrastructure/database/tables' export const InsertUserSchema = createInsertSchema(usersTable, { email: (schema) => schema.email.max(64).email().optional(), diff --git a/src/lib/server/api/infrastructure/database/seed.ts b/src/lib/server/api/databases/seed.ts similarity index 73% rename from src/lib/server/api/infrastructure/database/seed.ts rename to src/lib/server/api/databases/seed.ts index 1223370..de83be1 100644 --- a/src/lib/server/api/infrastructure/database/seed.ts +++ b/src/lib/server/api/databases/seed.ts @@ -1,15 +1,15 @@ -import { Table, getTableName, sql } from 'drizzle-orm'; -import env from '../../../../../env'; -import { db, pool } from './index'; -import * as schema from './tables'; -import * as seeds from './seeds'; +import { Table, getTableName, sql } from 'drizzle-orm' +import env from '../../../../env' +import { db, pool } from '../packages/drizzle' +import * as seeds from './seeds' +import * as schema from './tables' if (!env.DB_SEEDING) { - throw new Error('You must set DB_SEEDING to "true" when running seeds'); + throw new Error('You must set DB_SEEDING to "true" when running seeds') } async function resetTable(db: db, table: Table) { - return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`)); + return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`)) } for (const table of [ @@ -41,11 +41,11 @@ for (const table of [ schema.wishlists, ]) { // await db.delete(table); // clear tables without truncating / resetting ids - await resetTable(db, table); + await resetTable(db, table) } -await seeds.roles(db); -await seeds.users(db); +await seeds.roles(db) +await seeds.users(db) -await pool.end(); -process.exit(); +await pool.end() +process.exit() diff --git a/src/lib/server/api/infrastructure/database/seeds/data/roles.json b/src/lib/server/api/databases/seeds/data/roles.json similarity index 100% rename from src/lib/server/api/infrastructure/database/seeds/data/roles.json rename to src/lib/server/api/databases/seeds/data/roles.json diff --git a/src/lib/server/api/infrastructure/database/seeds/data/users.json b/src/lib/server/api/databases/seeds/data/users.json similarity index 100% rename from src/lib/server/api/infrastructure/database/seeds/data/users.json rename to src/lib/server/api/databases/seeds/data/users.json diff --git a/src/lib/server/api/infrastructure/database/seeds/index.ts b/src/lib/server/api/databases/seeds/index.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/seeds/index.ts rename to src/lib/server/api/databases/seeds/index.ts diff --git a/src/lib/server/api/databases/seeds/roles.ts b/src/lib/server/api/databases/seeds/roles.ts new file mode 100644 index 0000000..518899c --- /dev/null +++ b/src/lib/server/api/databases/seeds/roles.ts @@ -0,0 +1,11 @@ +import * as schema from '$lib/server/api/databases/tables' +import { type db } from '$lib/server/api/packages/drizzle' +import roles from './data/roles.json' + +export default async function seed(db: db) { + console.log('Creating roles ...') + for (const role of roles) { + await db.insert(schema.roles).values(role).onConflictDoNothing() + } + console.log('Roles created.') +} diff --git a/src/lib/server/api/infrastructure/database/seeds/users.ts b/src/lib/server/api/databases/seeds/users.ts similarity index 53% rename from src/lib/server/api/infrastructure/database/seeds/users.ts rename to src/lib/server/api/databases/seeds/users.ts index 15fab9a..23ae9f6 100644 --- a/src/lib/server/api/infrastructure/database/seeds/users.ts +++ b/src/lib/server/api/databases/seeds/users.ts @@ -1,31 +1,31 @@ -import { eq } from 'drizzle-orm'; -import { Argon2id } from 'oslo/password'; -import { type db } from '$lib/server/api/infrastructure/database'; -import * as schema from '$lib/server/api/infrastructure/database/tables'; -import users from './data/users.json'; -import { config } from '../../../common/config'; +import * as schema from '$lib/server/api/databases/tables' +import { type db } from '$lib/server/api/packages/drizzle' +import { eq } from 'drizzle-orm' +import { Argon2id } from 'oslo/password' +import { config } from '../../configs/config' +import users from './data/users.json' type JsonUser = { - id: string; - username: string; - email: string; - password: string; + id: string + username: string + email: string + password: string roles: { - name: string; - primary: boolean; - }[]; -}; + name: string + primary: boolean + }[] +} type JsonRole = { - name: string; - primary: boolean; -}; + name: string + primary: boolean +} export default async function seed(db: db) { - const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin')); - const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user')); + const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin')) + const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user')) - console.log('Admin Role: ', adminRole); + console.log('Admin Role: ', adminRole) const adminUser = await db .insert(schema.usersTable) .values({ @@ -36,27 +36,19 @@ export default async function seed(db: db) { verified: true, }) .returning() - .onConflictDoNothing(); + .onConflictDoNothing() - console.log('Admin user created.', adminUser); + console.log('Admin user created.', adminUser) - await db - .insert(schema.credentialsTable) - .values({ - user_id: adminUser[0].id, - type: schema.CredentialsType.PASSWORD, - secret_data: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`), - }); + await db.insert(schema.credentialsTable).values({ + user_id: adminUser[0].id, + type: schema.CredentialsType.PASSWORD, + secret_data: await new Argon2id().hash(`${config.ADMIN_PASSWORD}`), + }) - await db - .insert(schema.collections) - .values({ user_id: adminUser[0].id }) - .onConflictDoNothing(); + await db.insert(schema.collections).values({ user_id: adminUser[0].id }).onConflictDoNothing() - await db - .insert(schema.wishlists) - .values({ user_id: adminUser[0].id }) - .onConflictDoNothing(); + await db.insert(schema.wishlists).values({ user_id: adminUser[0].id }).onConflictDoNothing() await db .insert(schema.user_roles) @@ -65,9 +57,9 @@ export default async function seed(db: db) { role_id: adminRole[0].id, primary: true, }) - .onConflictDoNothing(); + .onConflictDoNothing() - console.log('Admin user given admin role.'); + console.log('Admin user given admin role.') await db .insert(schema.user_roles) @@ -76,9 +68,9 @@ export default async function seed(db: db) { role_id: userRole[0].id, primary: false, }) - .onConflictDoNothing(); + .onConflictDoNothing() - console.log('Admin user given user role.'); + console.log('Admin user given user role.') await Promise.all( users.map(async (user) => { const [insertedUser] = await db @@ -86,27 +78,29 @@ export default async function seed(db: db) { .values({ ...user, }) - .returning(); + .returning() await db.insert(schema.credentialsTable).values({ user_id: insertedUser?.id, type: schema.CredentialsType.PASSWORD, secret_data: await new Argon2id().hash(user.password), }) - await db.insert(schema.collections).values({ user_id: insertedUser?.id }); - await db.insert(schema.wishlists).values({ user_id: insertedUser?.id }); + await db.insert(schema.collections).values({ user_id: insertedUser?.id }) + await db.insert(schema.wishlists).values({ user_id: insertedUser?.id }) await Promise.all( user.roles.map(async (role: JsonRole) => { const foundRole = await db.query.roles.findFirst({ where: eq(schema.roles.name, role.name), - }); - if (!foundRole) { throw new Error('Role not found'); }; + }) + if (!foundRole) { + throw new Error('Role not found') + } await db.insert(schema.user_roles).values({ user_id: insertedUser?.id, role_id: foundRole?.id, primary: role?.primary, - }); + }) }), - ); + ) }), - ); + ) } diff --git a/src/lib/server/api/infrastructure/database/tables/categories.table.ts b/src/lib/server/api/databases/tables/categories.table.ts similarity index 54% rename from src/lib/server/api/infrastructure/database/tables/categories.table.ts rename to src/lib/server/api/databases/tables/categories.table.ts index b22cfc7..eff7cc5 100644 --- a/src/lib/server/api/infrastructure/database/tables/categories.table.ts +++ b/src/lib/server/api/databases/tables/categories.table.ts @@ -1,9 +1,9 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import {categoriesToExternalIdsTable} from './categoriesToExternalIdsTable'; -import { categories_to_games_table } from './categoriesToGames'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { categoriesToExternalIdsTable } from './categoriesToExternalIdsTable' +import { categories_to_games_table } from './categoriesToGames' export const categoriesTable = pgTable('categories', { id: uuid('id').primaryKey().defaultRandom(), @@ -13,11 +13,11 @@ export const categoriesTable = pgTable('categories', { name: text('name'), slug: text('slug'), ...timestamps, -}); +}) -export type Categories = InferSelectModel; +export type Categories = InferSelectModel export const categories_relations = relations(categoriesTable, ({ many }) => ({ categories_to_games: many(categories_to_games_table), categoriesToExternalIds: many(categoriesToExternalIdsTable), -})); +})) diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts b/src/lib/server/api/databases/tables/categoriesToExternalIdsTable.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/categoriesToExternalIdsTable.ts rename to src/lib/server/api/databases/tables/categoriesToExternalIdsTable.ts diff --git a/src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts b/src/lib/server/api/databases/tables/categoriesToGames.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/categoriesToGames.ts rename to src/lib/server/api/databases/tables/categoriesToGames.ts diff --git a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts b/src/lib/server/api/databases/tables/collectionItems.ts similarity index 75% rename from src/lib/server/api/infrastructure/database/tables/collectionItems.ts rename to src/lib/server/api/databases/tables/collectionItems.ts index 14ea009..8466605 100644 --- a/src/lib/server/api/infrastructure/database/tables/collectionItems.ts +++ b/src/lib/server/api/databases/tables/collectionItems.ts @@ -1,9 +1,9 @@ -import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { collections } from './collections'; -import {games} from './games'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { integer, pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { collections } from './collections' +import { games } from './games' export const collection_items = pgTable('collection_items', { id: uuid('id').primaryKey().defaultRandom(), @@ -18,9 +18,9 @@ export const collection_items = pgTable('collection_items', { .references(() => games.id, { onDelete: 'cascade' }), times_played: integer('times_played').default(0), ...timestamps, -}); +}) -export type CollectionItems = InferSelectModel; +export type CollectionItems = InferSelectModel export const collection_item_relations = relations(collection_items, ({ one }) => ({ collection: one(collections, { @@ -31,4 +31,4 @@ export const collection_item_relations = relations(collection_items, ({ one }) = fields: [collection_items.game_id], references: [games.id], }), -})); +})) diff --git a/src/lib/server/api/infrastructure/database/tables/collections.ts b/src/lib/server/api/databases/tables/collections.ts similarity index 58% rename from src/lib/server/api/infrastructure/database/tables/collections.ts rename to src/lib/server/api/databases/tables/collections.ts index 9f881ba..ed1499e 100644 --- a/src/lib/server/api/infrastructure/database/tables/collections.ts +++ b/src/lib/server/api/databases/tables/collections.ts @@ -1,8 +1,8 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { usersTable } from './users.table'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export const collections = pgTable('collections', { id: uuid('id').primaryKey().defaultRandom(), @@ -14,14 +14,13 @@ export const collections = pgTable('collections', { .references(() => usersTable.id, { onDelete: 'cascade' }), name: text('name').notNull().default('My Collection'), ...timestamps, -}); +}) export const collection_relations = relations(collections, ({ one }) => ({ user: one(usersTable, { fields: [collections.user_id], references: [usersTable.id], }), -})); - -export type Collections = InferSelectModel; +})) +export type Collections = InferSelectModel diff --git a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts b/src/lib/server/api/databases/tables/credentials.table.ts similarity index 64% rename from src/lib/server/api/infrastructure/database/tables/credentials.table.ts rename to src/lib/server/api/databases/tables/credentials.table.ts index 179071d..f0204b5 100644 --- a/src/lib/server/api/infrastructure/database/tables/credentials.table.ts +++ b/src/lib/server/api/databases/tables/credentials.table.ts @@ -1,13 +1,13 @@ -import { pgTable, text, uuid } from "drizzle-orm/pg-core"; -import { type InferSelectModel } from 'drizzle-orm'; -import { timestamps } from '../utils'; -import { usersTable } from "./users.table"; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { type InferSelectModel } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export enum CredentialsType { SECRET = 'secret', PASSWORD = 'password', TOTP = 'totp', - HOTP = 'hotp' + HOTP = 'hotp', } export const credentialsTable = pgTable('credentials', { @@ -17,7 +17,7 @@ export const credentialsTable = pgTable('credentials', { .references(() => usersTable.id, { onDelete: 'cascade' }), type: text('type').notNull().default(CredentialsType.PASSWORD), secret_data: text('secret_data').notNull(), - ...timestamps -}); + ...timestamps, +}) -export type Credentials = InferSelectModel; +export type Credentials = InferSelectModel diff --git a/src/lib/server/api/infrastructure/database/tables/expansions.ts b/src/lib/server/api/databases/tables/expansions.ts similarity index 65% rename from src/lib/server/api/infrastructure/database/tables/expansions.ts rename to src/lib/server/api/databases/tables/expansions.ts index 3390b88..fa6dd26 100644 --- a/src/lib/server/api/infrastructure/database/tables/expansions.ts +++ b/src/lib/server/api/databases/tables/expansions.ts @@ -1,8 +1,8 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import {games} from './games'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { games } from './games' export const expansions = pgTable('expansions', { id: uuid('id').primaryKey().defaultRandom(), @@ -16,9 +16,9 @@ export const expansions = pgTable('expansions', { .notNull() .references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }), ...timestamps, -}); +}) -export type Expansions = InferSelectModel; +export type Expansions = InferSelectModel export const expansion_relations = relations(expansions, ({ one }) => ({ baseGame: one(games, { @@ -29,4 +29,4 @@ export const expansion_relations = relations(expansions, ({ one }) => ({ fields: [expansions.game_id], references: [games.id], }), -})); +})) diff --git a/src/lib/server/api/infrastructure/database/tables/externalIds.ts b/src/lib/server/api/databases/tables/externalIds.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/externalIds.ts rename to src/lib/server/api/databases/tables/externalIds.ts diff --git a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts b/src/lib/server/api/databases/tables/federatedIdentity.table.ts similarity index 63% rename from src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts rename to src/lib/server/api/databases/tables/federatedIdentity.table.ts index ea23de2..311683c 100644 --- a/src/lib/server/api/infrastructure/database/tables/federatedIdentity.table.ts +++ b/src/lib/server/api/databases/tables/federatedIdentity.table.ts @@ -1,7 +1,7 @@ -import { pgTable, text, uuid } from "drizzle-orm/pg-core"; -import { type InferSelectModel } from 'drizzle-orm'; -import { usersTable } from "./users.table"; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { type InferSelectModel } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export const federatedIdentityTable = pgTable('federated_identity', { id: uuid('id').primaryKey().defaultRandom(), @@ -11,7 +11,7 @@ export const federatedIdentityTable = pgTable('federated_identity', { identity_provider: text('identity_provider').notNull(), federated_user_id: text('federated_user_id').notNull(), federated_username: text('federated_username').notNull(), - ...timestamps -}); + ...timestamps, +}) -export type FederatedIdentity = InferSelectModel; +export type FederatedIdentity = InferSelectModel diff --git a/src/lib/server/api/infrastructure/database/tables/games.ts b/src/lib/server/api/databases/tables/games.ts similarity index 74% rename from src/lib/server/api/infrastructure/database/tables/games.ts rename to src/lib/server/api/databases/tables/games.ts index 99ec01a..3767bc2 100644 --- a/src/lib/server/api/infrastructure/database/tables/games.ts +++ b/src/lib/server/api/databases/tables/games.ts @@ -1,11 +1,11 @@ -import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations, sql } from 'drizzle-orm'; -import {categories_to_games_table} from './categoriesToGames'; -import {gamesToExternalIds} from './gamesToExternalIds'; -import {mechanics_to_games} from './mechanicsToGames'; -import {publishers_to_games} from './publishersToGames'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations, sql } from 'drizzle-orm' +import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core' +import { categories_to_games_table } from './categoriesToGames' +import { gamesToExternalIds } from './gamesToExternalIds' +import { mechanics_to_games } from './mechanicsToGames' +import { publishers_to_games } from './publishersToGames' export const games = pgTable( 'games', @@ -39,13 +39,13 @@ export const games = pgTable( )`, ), }), -); +) export const gameRelations = relations(games, ({ many }) => ({ categories_to_games: many(categories_to_games_table), mechanics_to_games: many(mechanics_to_games), publishers_to_games: many(publishers_to_games), gamesToExternalIds: many(gamesToExternalIds), -})); +})) -export type Games = InferSelectModel; +export type Games = InferSelectModel diff --git a/src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts b/src/lib/server/api/databases/tables/gamesToExternalIds.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/gamesToExternalIds.ts rename to src/lib/server/api/databases/tables/gamesToExternalIds.ts diff --git a/src/lib/server/api/infrastructure/database/tables/index.ts b/src/lib/server/api/databases/tables/index.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/index.ts rename to src/lib/server/api/databases/tables/index.ts diff --git a/src/lib/server/api/databases/tables/mechanics.ts b/src/lib/server/api/databases/tables/mechanics.ts new file mode 100644 index 0000000..b017a7b --- /dev/null +++ b/src/lib/server/api/databases/tables/mechanics.ts @@ -0,0 +1,23 @@ +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { mechanicsToExternalIds } from './mechanicsToExternalIds' +import { mechanics_to_games } from './mechanicsToGames' + +export const mechanics = pgTable('mechanics', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name'), + slug: text('slug'), + ...timestamps, +}) + +export type Mechanics = InferSelectModel + +export const mechanics_relations = relations(mechanics, ({ many }) => ({ + mechanics_to_games: many(mechanics_to_games), + mechanicsToExternalIds: many(mechanicsToExternalIds), +})) diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts b/src/lib/server/api/databases/tables/mechanicsToExternalIds.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/mechanicsToExternalIds.ts rename to src/lib/server/api/databases/tables/mechanicsToExternalIds.ts diff --git a/src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts b/src/lib/server/api/databases/tables/mechanicsToGames.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/mechanicsToGames.ts rename to src/lib/server/api/databases/tables/mechanicsToGames.ts diff --git a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts b/src/lib/server/api/databases/tables/passwordResetTokens.ts similarity index 70% rename from src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts rename to src/lib/server/api/databases/tables/passwordResetTokens.ts index 3fa94bf..9531e0e 100644 --- a/src/lib/server/api/infrastructure/database/tables/passwordResetTokens.ts +++ b/src/lib/server/api/databases/tables/passwordResetTokens.ts @@ -1,8 +1,8 @@ -import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { usersTable } from './users.table'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export const password_reset_tokens = pgTable('password_reset_tokens', { id: text('id') @@ -13,13 +13,13 @@ export const password_reset_tokens = pgTable('password_reset_tokens', { .references(() => usersTable.id, { onDelete: 'cascade' }), expires_at: timestamp('expires_at'), ...timestamps, -}); +}) -export type PasswordResetTokens = InferSelectModel; +export type PasswordResetTokens = InferSelectModel export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({ user: one(usersTable, { fields: [password_reset_tokens.user_id], references: [usersTable.id], }), -})); +})) diff --git a/src/lib/server/api/databases/tables/publishers.ts b/src/lib/server/api/databases/tables/publishers.ts new file mode 100644 index 0000000..e501f4c --- /dev/null +++ b/src/lib/server/api/databases/tables/publishers.ts @@ -0,0 +1,23 @@ +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { publishersToExternalIds } from './publishersToExternalIds' +import { publishers_to_games } from './publishersToGames' + +export const publishers = pgTable('publishers', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()), + name: text('name'), + slug: text('slug'), + ...timestamps, +}) + +export type Publishers = InferSelectModel + +export const publishers_relations = relations(publishers, ({ many }) => ({ + publishers_to_games: many(publishers_to_games), + publishersToExternalIds: many(publishersToExternalIds), +})) diff --git a/src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts b/src/lib/server/api/databases/tables/publishersToExternalIds.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/publishersToExternalIds.ts rename to src/lib/server/api/databases/tables/publishersToExternalIds.ts diff --git a/src/lib/server/api/infrastructure/database/tables/publishersToGames.ts b/src/lib/server/api/databases/tables/publishersToGames.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/publishersToGames.ts rename to src/lib/server/api/databases/tables/publishersToGames.ts diff --git a/src/lib/server/api/infrastructure/database/tables/recovery-codes.table.ts b/src/lib/server/api/databases/tables/recovery-codes.table.ts similarity index 68% rename from src/lib/server/api/infrastructure/database/tables/recovery-codes.table.ts rename to src/lib/server/api/databases/tables/recovery-codes.table.ts index 80495b9..1429da0 100644 --- a/src/lib/server/api/infrastructure/database/tables/recovery-codes.table.ts +++ b/src/lib/server/api/databases/tables/recovery-codes.table.ts @@ -1,7 +1,7 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import type { InferSelectModel } from 'drizzle-orm'; -import { usersTable } from './users.table'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import type { InferSelectModel } from 'drizzle-orm' +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export const recoveryCodesTable = pgTable('recovery_codes', { id: uuid('id').primaryKey().defaultRandom(), @@ -11,6 +11,6 @@ export const recoveryCodesTable = pgTable('recovery_codes', { code: text('code').notNull(), used: boolean('used').default(false), ...timestamps, -}); +}) -export type RecoveryCodesTable = InferSelectModel; +export type RecoveryCodesTable = InferSelectModel diff --git a/src/lib/server/api/databases/tables/roles.ts b/src/lib/server/api/databases/tables/roles.ts new file mode 100644 index 0000000..0630396 --- /dev/null +++ b/src/lib/server/api/databases/tables/roles.ts @@ -0,0 +1,21 @@ +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { user_roles } from './userRoles' + +export const roles = pgTable('roles', { + id: uuid('id').primaryKey().defaultRandom(), + cuid: text('cuid') + .unique() + .$defaultFn(() => cuid2()) + .notNull(), + name: text('name').unique().notNull(), + ...timestamps, +}) + +export type Roles = InferSelectModel + +export const role_relations = relations(roles, ({ many }) => ({ + user_roles: many(user_roles), +})) diff --git a/src/lib/server/api/infrastructure/database/tables/sessions.table.ts b/src/lib/server/api/databases/tables/sessions.table.ts similarity index 100% rename from src/lib/server/api/infrastructure/database/tables/sessions.table.ts rename to src/lib/server/api/databases/tables/sessions.table.ts diff --git a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts b/src/lib/server/api/databases/tables/two-factor.table.ts similarity index 68% rename from src/lib/server/api/infrastructure/database/tables/two-factor.table.ts rename to src/lib/server/api/databases/tables/two-factor.table.ts index f4cd9a2..a8ce4d7 100644 --- a/src/lib/server/api/infrastructure/database/tables/two-factor.table.ts +++ b/src/lib/server/api/databases/tables/two-factor.table.ts @@ -1,8 +1,8 @@ -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -import { timestamps } from '../utils'; -import { usersTable } from './users.table'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export const twoFactorTable = pgTable('two_factor', { id: uuid('id').primaryKey().defaultRandom(), @@ -20,13 +20,13 @@ export const twoFactorTable = pgTable('two_factor', { .references(() => usersTable.id) .unique(), ...timestamps, -}); +}) export const emailVerificationsRelations = relations(twoFactorTable, ({ one }) => ({ user: one(usersTable, { fields: [twoFactorTable.userId], references: [usersTable.id], }), -})); +})) -export type TwoFactor = InferSelectModel; +export type TwoFactor = InferSelectModel diff --git a/src/lib/server/api/infrastructure/database/tables/userRoles.ts b/src/lib/server/api/databases/tables/userRoles.ts similarity index 68% rename from src/lib/server/api/infrastructure/database/tables/userRoles.ts rename to src/lib/server/api/databases/tables/userRoles.ts index 6d3d833..265bf88 100644 --- a/src/lib/server/api/infrastructure/database/tables/userRoles.ts +++ b/src/lib/server/api/databases/tables/userRoles.ts @@ -1,9 +1,9 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { usersTable } from './users.table'; -import {roles} from './roles'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { roles } from './roles' +import { usersTable } from './users.table' export const user_roles = pgTable('user_roles', { id: uuid('id').primaryKey().defaultRandom(), @@ -18,7 +18,7 @@ export const user_roles = pgTable('user_roles', { .references(() => roles.id, { onDelete: 'cascade' }), primary: boolean('primary').default(false), ...timestamps, -}); +}) export const user_role_relations = relations(user_roles, ({ one }) => ({ role: one(roles, { @@ -29,6 +29,6 @@ export const user_role_relations = relations(user_roles, ({ one }) => ({ fields: [user_roles.user_id], references: [usersTable.id], }), -})); +})) -export type UserRoles = InferSelectModel; +export type UserRoles = InferSelectModel diff --git a/src/lib/server/api/infrastructure/database/tables/users.table.ts b/src/lib/server/api/databases/tables/users.table.ts similarity index 92% rename from src/lib/server/api/infrastructure/database/tables/users.table.ts rename to src/lib/server/api/databases/tables/users.table.ts index a61ca90..93850c0 100644 --- a/src/lib/server/api/infrastructure/database/tables/users.table.ts +++ b/src/lib/server/api/databases/tables/users.table.ts @@ -1,7 +1,7 @@ -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core' -import { type InferSelectModel, relations } from 'drizzle-orm' +import { timestamps } from '$lib/server/api/common/utils/table.utils' import { createId as cuid2 } from '@paralleldrive/cuid2' -import { timestamps } from '../utils' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core' import { user_roles } from './userRoles' export const usersTable = pgTable('users', { diff --git a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts b/src/lib/server/api/databases/tables/wishlistItems.ts similarity index 68% rename from src/lib/server/api/infrastructure/database/tables/wishlistItems.ts rename to src/lib/server/api/databases/tables/wishlistItems.ts index b46ca72..e9d57c8 100644 --- a/src/lib/server/api/infrastructure/database/tables/wishlistItems.ts +++ b/src/lib/server/api/databases/tables/wishlistItems.ts @@ -1,9 +1,9 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import {wishlists} from './wishlists'; -import {games} from './games'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { games } from './games' +import { wishlists } from './wishlists' export const wishlist_items = pgTable('wishlist_items', { id: uuid('id').primaryKey().defaultRandom(), @@ -17,9 +17,9 @@ export const wishlist_items = pgTable('wishlist_items', { .notNull() .references(() => games.id, { onDelete: 'cascade' }), ...timestamps, -}); +}) -export type WishlistItems = InferSelectModel; +export type WishlistItems = InferSelectModel export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({ wishlist: one(wishlists, { @@ -30,4 +30,4 @@ export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({ fields: [wishlist_items.game_id], references: [games.id], }), -})); +})) diff --git a/src/lib/server/api/infrastructure/database/tables/wishlists.ts b/src/lib/server/api/databases/tables/wishlists.ts similarity index 58% rename from src/lib/server/api/infrastructure/database/tables/wishlists.ts rename to src/lib/server/api/databases/tables/wishlists.ts index 8e0ab5e..10dbec9 100644 --- a/src/lib/server/api/infrastructure/database/tables/wishlists.ts +++ b/src/lib/server/api/databases/tables/wishlists.ts @@ -1,8 +1,8 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import { usersTable } from './users.table'; -import { timestamps } from '../utils'; +import { timestamps } from '$lib/server/api/common/utils/table.utils' +import { createId as cuid2 } from '@paralleldrive/cuid2' +import { type InferSelectModel, relations } from 'drizzle-orm' +import { pgTable, text, uuid } from 'drizzle-orm/pg-core' +import { usersTable } from './users.table' export const wishlists = pgTable('wishlists', { id: uuid('id').primaryKey().defaultRandom(), @@ -14,13 +14,13 @@ export const wishlists = pgTable('wishlists', { .references(() => usersTable.id, { onDelete: 'cascade' }), name: text('name').notNull().default('My Wishlist'), ...timestamps, -}); +}) -export type Wishlists = InferSelectModel; +export type Wishlists = InferSelectModel export const wishlists_relations = relations(wishlists, ({ one }) => ({ user: one(usersTable, { fields: [wishlists.user_id], references: [usersTable.id], }), -})); +})) diff --git a/src/lib/server/api/dtos/create-user-role.dto.ts b/src/lib/server/api/dtos/create-user-role.dto.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/lib/server/api/dtos/create-user-role.dto.ts @@ -0,0 +1 @@ + diff --git a/src/lib/server/api/dtos/id-params.dto.ts b/src/lib/server/api/dtos/id-params.dto.ts new file mode 100644 index 0000000..8a4aa50 --- /dev/null +++ b/src/lib/server/api/dtos/id-params.dto.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; + +export const IdParamsDto = z.object({ + id: z.trim().number(), +}); \ No newline at end of file diff --git a/src/lib/server/api/dtos/register-emailpassword.dto.ts b/src/lib/server/api/dtos/register-emailpassword.dto.ts new file mode 100644 index 0000000..ce32b51 --- /dev/null +++ b/src/lib/server/api/dtos/register-emailpassword.dto.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; +import { refinePasswords } from "$lib/validations/account"; + +export const registerEmailPasswordDto = z.object({ + firstName: z.string().trim().optional(), + lastName: z.string().trim().optional(), + email: z.string().trim().max(64, { message: 'Email must be less than 64 characters' }).optional(), + username: z + .string() + .trim() + .min(3, { message: 'Must be at least 3 characters' }) + .max(50, { message: 'Must be less than 50 characters' }), + password: z.string({ required_error: 'Password is required' }).trim(), + confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim(), +}) + .superRefine(({ confirm_password, password }, ctx) => { + refinePasswords(confirm_password, password, ctx); + }); + +export type RegisterEmailPasswordDto = z.infer; \ No newline at end of file diff --git a/src/lib/server/api/dtos/signin-username.dto.ts b/src/lib/server/api/dtos/signin-username.dto.ts new file mode 100644 index 0000000..0ab4a3a --- /dev/null +++ b/src/lib/server/api/dtos/signin-username.dto.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export const signinUsernameDto = z.object({ + username: z + .string() + .trim() + .min(3, { message: 'Must be at least 3 characters' }) + .max(50, { message: 'Must be less than 50 characters' }), + password: z.string({ required_error: 'Password is required' }).trim(), +}); + +export type SigninUsernameDto = z.infer; \ No newline at end of file diff --git a/src/lib/server/api/dtos/signup-username-email.dto.ts b/src/lib/server/api/dtos/signup-username-email.dto.ts new file mode 100644 index 0000000..81e259e --- /dev/null +++ b/src/lib/server/api/dtos/signup-username-email.dto.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import { refinePasswords } from "$lib/validations/account"; + +export const signupUsernameEmailDto = z.object({ + firstName: z.string().trim().optional(), + lastName: z.string().trim().optional(), + email: z.string() + .trim() + .max(64, {message: 'Email must be less than 64 characters'}) + .email({message: 'Please enter a valid email'}) + .optional(), + username: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}), + password: z.string({required_error: 'Password is required'}).trim(), + confirm_password: z.string({required_error: 'Confirm Password is required'}).trim() + }) + .superRefine(({ confirm_password, password }, ctx) => { + return refinePasswords(confirm_password, password, ctx); + }); + +export type SignupUsernameEmailDto = z.infer diff --git a/src/lib/server/api/dtos/update-email.dto.ts b/src/lib/server/api/dtos/update-email.dto.ts new file mode 100644 index 0000000..45af50c --- /dev/null +++ b/src/lib/server/api/dtos/update-email.dto.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const updateEmailDto = z.object({ + email: z + .string() + .trim() + .max(64, {message: 'Email must be less than 64 characters'}) + .email({message: 'Please enter a valid email'}) +}); + +export type UpdateEmailDto = z.infer; diff --git a/src/lib/server/api/dtos/update-profile.dto.ts b/src/lib/server/api/dtos/update-profile.dto.ts new file mode 100644 index 0000000..9ea0c6f --- /dev/null +++ b/src/lib/server/api/dtos/update-profile.dto.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +export const updateProfileDto = z.object({ + firstName: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}) + .optional(), + lastName: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}) + .optional(), + username: z + .string() + .trim() + .min(3, {message: 'Must be at least 3 characters'}) + .max(50, {message: 'Must be less than 50 characters'}) +}); + +export type UpdateProfileDto = z.infer; diff --git a/src/lib/server/api/dtos/verify-password.dto.ts b/src/lib/server/api/dtos/verify-password.dto.ts new file mode 100644 index 0000000..a883135 --- /dev/null +++ b/src/lib/server/api/dtos/verify-password.dto.ts @@ -0,0 +1,7 @@ +import { z } from 'zod' + +export const verifyPasswordDto = z.object({ + password: z.string({ required_error: 'Password is required' }).trim(), +}) + +export type VerifyPasswordDto = z.infer diff --git a/src/lib/server/api/dtos/verify-totp.dto.ts b/src/lib/server/api/dtos/verify-totp.dto.ts new file mode 100644 index 0000000..cd5f886 --- /dev/null +++ b/src/lib/server/api/dtos/verify-totp.dto.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const verifyTotpDto = z.object({ + code: z + .string() + .trim() + .min(6, { message: 'Must be at least 6 characters' }) + .max(6, { message: 'Must be less than 6 characters' }), +}); + +export type VerifyTotpDto = z.infer; \ No newline at end of file diff --git a/src/lib/server/api/infrastructure/email-templates/email-change-notice.hbs b/src/lib/server/api/emails/email-change-notice.hbs similarity index 100% rename from src/lib/server/api/infrastructure/email-templates/email-change-notice.hbs rename to src/lib/server/api/emails/email-change-notice.hbs diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index d6a6b16..18ba883 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,59 +1,64 @@ import 'reflect-metadata' -import { Hono } from 'hono'; -import { hc } from 'hono/client'; -import { cors } from 'hono/cors'; -import { logger } from 'hono/logger'; -import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware'; -import { config } from './common/config'; -import { container } from 'tsyringe'; -import { IamController } from './controllers/iam.controller'; -import { LoginController } from './controllers/login.controller'; -import { MfaController} from "$lib/server/api/controllers/mfa.controller"; -import {UserController} from "$lib/server/api/controllers/user.controller"; -import {SignupController} from "$lib/server/api/controllers/signup.controller"; -import {WishlistController} from "$lib/server/api/controllers/wishlist.controller"; -import {CollectionController} from "$lib/server/api/controllers/collection.controller"; +import { CollectionController } from '$lib/server/api/controllers/collection.controller' +import { MfaController } from '$lib/server/api/controllers/mfa.controller' +import { SignupController } from '$lib/server/api/controllers/signup.controller' +import { UserController } from '$lib/server/api/controllers/user.controller' +import { WishlistController } from '$lib/server/api/controllers/wishlist.controller' +import { AuthCleanupJobs } from '$lib/server/api/jobs/auth-cleanup.job' +import { Hono } from 'hono' +import { hc } from 'hono/client' +import { cors } from 'hono/cors' +import { logger } from 'hono/logger' +import { container } from 'tsyringe' +import { config } from './configs/config' +import { IamController } from './controllers/iam.controller' +import { LoginController } from './controllers/login.controller' +import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware' -/* ----------------------------------- Api ---------------------------------- */ -const app = new Hono().basePath('/api'); +/* -------------------------------------------------------------------------- */ +/* App */ +/* -------------------------------------------------------------------------- */ +export const app = new Hono().basePath('/api') -/* --------------------------- Global Middlewares --------------------------- */ -app.use(verifyOrigin).use(validateAuthSession); -app.use(logger()); +/* -------------------------------------------------------------------------- */ +/* Global Middlewares */ +/* -------------------------------------------------------------------------- */ +app.use(verifyOrigin).use(validateAuthSession) +app.use(logger()) app.use( '/*', cors({ - origin: [ - 'http://localhost:5173', - 'http://localhost:80', - 'http://host.docker.internal:80', - 'http://host.docker.internal:5173' - ], // Replace with your allowed domains + origin: ['http://localhost:5173', 'http://localhost:80', 'http://host.docker.internal:80', 'http://host.docker.internal:5173'], // Replace with your allowed domains allowMethods: ['POST'], - allowHeaders: ['Content-Type'] + allowHeaders: ['Content-Type'], // credentials: true, // If you need to send cookies or HTTP authentication - }) -); + }), +) -/* --------------------------------- Routes --------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* Routes */ +/* -------------------------------------------------------------------------- */ const routes = app - .route('/me', container.resolve(IamController).routes()) - .route('/user', container.resolve(UserController).routes()) - .route('/login', container.resolve(LoginController).routes()) - .route('/signup', container.resolve(SignupController).routes()) - .route('/wishlists', container.resolve(WishlistController).routes()) - .route('/collections', container.resolve(CollectionController).routes()) - .route('/mfa', container.resolve(MfaController).routes()) - .get('/', (c) => c.json({ message: 'Server is healthy' })); + .route('/me', container.resolve(IamController).routes()) + .route('/user', container.resolve(UserController).routes()) + .route('/login', container.resolve(LoginController).routes()) + .route('/signup', container.resolve(SignupController).routes()) + .route('/wishlists', container.resolve(WishlistController).routes()) + .route('/collections', container.resolve(CollectionController).routes()) + .route('/mfa', container.resolve(MfaController).routes()) + .get('/', (c) => c.json({ message: 'Server is healthy' })) + +/* -------------------------------------------------------------------------- */ +/* Cron Jobs */ +/* -------------------------------------------------------------------------- */ +container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests() +container.resolve(AuthCleanupJobs).deleteStaleLoginRequests() /* -------------------------------------------------------------------------- */ /* Exports */ /* -------------------------------------------------------------------------- */ -export type AppType = typeof routes; - -export const rpc = hc(config.ORIGIN); -export type ApiClient = typeof rpc; -export type ApiRoutes = typeof routes; -export { app }; \ No newline at end of file +export const rpc = hc(config.ORIGIN) +export type ApiClient = typeof rpc +export type ApiRoutes = typeof routes diff --git a/src/lib/server/api/infrastructure/database/migrate.ts b/src/lib/server/api/infrastructure/database/migrate.ts deleted file mode 100644 index 156ac34..0000000 --- a/src/lib/server/api/infrastructure/database/migrate.ts +++ /dev/null @@ -1,26 +0,0 @@ -import 'dotenv/config'; -import postgres from 'postgres'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { migrate } from 'drizzle-orm/postgres-js/migrator'; -import env from '../../../../../env'; -import config from '../../../../../../drizzle.config'; - -const connection = postgres({ - host: env.DATABASE_HOST || 'localhost', - port: env.DATABASE_PORT, - user: env.DATABASE_USER || 'root', - password: env.DATABASE_PASSWORD || '', - database: env.DATABASE_DB || 'boredgame', - ssl: env.NODE_ENV === 'development' ? false : 'require', - max: 1, -}); -const db = drizzle(connection); - -try { - await migrate(db, { migrationsFolder: config.out! }); - console.log('Migrations complete'); -} catch (e) { - console.error(e); -} - -process.exit(); diff --git a/src/lib/server/api/infrastructure/database/seeds/roles.ts b/src/lib/server/api/infrastructure/database/seeds/roles.ts deleted file mode 100644 index 20741ea..0000000 --- a/src/lib/server/api/infrastructure/database/seeds/roles.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type db } from '$lib/server/api/infrastructure/database'; -import * as schema from '$lib/server/api/infrastructure/database/tables'; -import roles from './data/roles.json'; - -export default async function seed(db: db) { - console.log('Creating roles ...'); - for (const role of roles) { - await db.insert(schema.roles).values(role).onConflictDoNothing(); - } - console.log('Roles created.'); -} diff --git a/src/lib/server/api/infrastructure/database/tables/mechanics.ts b/src/lib/server/api/infrastructure/database/tables/mechanics.ts deleted file mode 100644 index 2f489bc..0000000 --- a/src/lib/server/api/infrastructure/database/tables/mechanics.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import {mechanics_to_games} from './mechanicsToGames'; -import {mechanicsToExternalIds} from './mechanicsToExternalIds'; -import { timestamps } from '../utils'; - -export const mechanics = pgTable('mechanics', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name'), - slug: text('slug'), - ...timestamps, -}); - -export type Mechanics = InferSelectModel; - -export const mechanics_relations = relations(mechanics, ({ many }) => ({ - mechanics_to_games: many(mechanics_to_games), - mechanicsToExternalIds: many(mechanicsToExternalIds), -})); diff --git a/src/lib/server/api/infrastructure/database/tables/publishers.ts b/src/lib/server/api/infrastructure/database/tables/publishers.ts deleted file mode 100644 index 48d0e66..0000000 --- a/src/lib/server/api/infrastructure/database/tables/publishers.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import {publishers_to_games} from './publishersToGames'; -import {publishersToExternalIds} from './publishersToExternalIds'; -import { timestamps } from '../utils'; - -export const publishers = pgTable('publishers', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()), - name: text('name'), - slug: text('slug'), - ...timestamps, -}); - -export type Publishers = InferSelectModel; - -export const publishers_relations = relations(publishers, ({ many }) => ({ - publishers_to_games: many(publishers_to_games), - publishersToExternalIds: many(publishersToExternalIds), -})); diff --git a/src/lib/server/api/infrastructure/database/tables/roles.ts b/src/lib/server/api/infrastructure/database/tables/roles.ts deleted file mode 100644 index 4e701ac..0000000 --- a/src/lib/server/api/infrastructure/database/tables/roles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; -import { createId as cuid2 } from '@paralleldrive/cuid2'; -import { type InferSelectModel, relations } from 'drizzle-orm'; -import {user_roles} from './userRoles'; -import { timestamps } from '../utils'; - -export const roles = pgTable('roles', { - id: uuid('id').primaryKey().defaultRandom(), - cuid: text('cuid') - .unique() - .$defaultFn(() => cuid2()) - .notNull(), - name: text('name').unique().notNull(), - ...timestamps, -}); - -export type Roles = InferSelectModel; - -export const role_relations = relations(roles, ({ many }) => ({ - user_roles: many(user_roles), -})); diff --git a/src/lib/server/api/infrastructure/database/utils.ts b/src/lib/server/api/infrastructure/database/utils.ts deleted file mode 100644 index 5b926ef..0000000 --- a/src/lib/server/api/infrastructure/database/utils.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { HTTPException } from 'hono/http-exception'; -import { timestamp, customType } from 'drizzle-orm/pg-core'; - -export const citext = customType<{ data: string }>({ - dataType() { - return 'citext'; - } -}); - -export const cuid2 = customType<{ data: string }>({ - dataType() { - return 'text'; - } -}); - -export const takeFirst = (values: T[]): T | null => { - if (values.length === 0) return null; - return values[0]!; -}; - -export const takeFirstOrThrow = (values: T[]): T => { - if (values.length === 0) - throw new HTTPException(404, { - message: 'Resource not found' - }); - return values[0]!; -}; - -export const timestamps = { - createdAt: timestamp('created_at', { - mode: 'date', - withTimezone: true - }) - .notNull() - .defaultNow(), - updatedAt: timestamp('updated_at', { - mode: 'date', - withTimezone: true - }) - .notNull() - .defaultNow() -}; diff --git a/src/lib/server/api/interfaces/controller.interface.ts b/src/lib/server/api/interfaces/controller.interface.ts deleted file mode 100644 index 852e695..0000000 --- a/src/lib/server/api/interfaces/controller.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Hono } from 'hono'; -import type { HonoTypes } from '../types'; -import type { BlankSchema } from 'hono/types'; - -export interface Controller { - controller: Hono; - routes(): any; -} diff --git a/src/lib/server/api/jobs/auth-cleanup.job.ts b/src/lib/server/api/jobs/auth-cleanup.job.ts new file mode 100644 index 0000000..fb2ec6c --- /dev/null +++ b/src/lib/server/api/jobs/auth-cleanup.job.ts @@ -0,0 +1,42 @@ +import { inject, injectable } from 'tsyringe' +import { JobsService } from '../services/jobs.service' + +@injectable() +export class AuthCleanupJobs { + private queue + + constructor(@inject(JobsService) private jobsService: JobsService) { + /* ------------------------------ Create Queue ------------------------------ */ + this.queue = this.jobsService.createQueue('test') + + /* ---------------------------- Register Workers ---------------------------- */ + this.worker().then((r) => console.log('auth-cleanup job worker started')) + } + + async deleteStaleEmailVerificationRequests() { + await this.queue.add('delete_stale_email_verifiactions', null, { + repeat: { + pattern: '0 0 * * 0', // Runs once a week at midnight on Sunday + }, + }) + } + + async deleteStaleLoginRequests() { + await this.queue.add('delete_stale_login_requests', null, { + repeat: { + pattern: '0 0 * * 0', // Runs once a week at midnight on Sunday + }, + }) + } + + private async worker() { + return this.jobsService.createWorker(this.queue.name, async (job) => { + if (job.name === 'delete_stale_email_verifiactions') { + // delete stale email verifications + } + if (job.name === 'delete_stale_login_requests') { + // delete stale email verifications + } + }) + } +} diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index bd2f4c9..5d2eab7 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -1,10 +1,10 @@ import type { MiddlewareHandler } from 'hono' import { createMiddleware } from 'hono/factory' -import type { HonoTypes } from '../types' -import { lucia } from '../infrastructure/auth/lucia' -import { verifyRequestOrigin } from 'oslo/request' import type { Session, User } from 'lucia' -import { Unauthorized } from '../common/errors' +import { verifyRequestOrigin } from 'oslo/request' +import { Unauthorized } from '../common/exceptions' +import { lucia } from '../packages/lucia' +import type { HonoTypes } from '../types' export const verifyOrigin: MiddlewareHandler = createMiddleware(async (c, next) => { if (c.req.method === 'GET') { diff --git a/src/lib/server/api/middleware/rate-limiter.middleware.ts b/src/lib/server/api/middleware/rate-limiter.middleware.ts index d704571..9b49d10 100644 --- a/src/lib/server/api/middleware/rate-limiter.middleware.ts +++ b/src/lib/server/api/middleware/rate-limiter.middleware.ts @@ -1,32 +1,34 @@ -import { rateLimiter } from "hono-rate-limiter"; -import { RedisStore } from 'rate-limit-redis' +import { rateLimiter } from 'hono-rate-limiter' import RedisClient from 'ioredis' -import type { HonoTypes } from "../types"; -import { config } from "../common/config"; +import { RedisStore } from 'rate-limit-redis' +import { config } from '../configs/config' +import type { HonoTypes } from '../types' const client = new RedisClient(config.REDIS_URL) -export function limiter({ limit, minutes, key = "" }: { - limit: number; - minutes: number; - key?: string; +export function limiter({ + limit, + minutes, + key = '', +}: { + limit: number + minutes: number + key?: string }) { - return rateLimiter({ - windowMs: minutes * 60 * 1000, // every x minutes - limit, // Limit each IP to 100 requests per `window` (here, per 15 minutes). - standardHeaders: "draft-6", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header - keyGenerator: (c) => { - const vars = c.var as HonoTypes['Variables']; - const clientKey = vars.user?.id || c.req.header("x-forwarded-for"); - const pathKey = key || c.req.routePath; - return `${clientKey}_${pathKey}` - }, // Method to generate custom identifiers for clients. - // Redis store configuration - store: new RedisStore({ - // @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis - sendCommand: (...args: string[]) => client.call(...args), - }) as any, - }) + return rateLimiter({ + windowMs: minutes * 60 * 1000, // every x minutes + limit, // Limit each IP to 100 requests per `window` (here, per 15 minutes). + standardHeaders: 'draft-6', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header + keyGenerator: (c) => { + const vars = c.var as HonoTypes['Variables'] + const clientKey = vars.user?.id || c.req.header('x-forwarded-for') + const pathKey = key || c.req.routePath + return `${clientKey}_${pathKey}` + }, // Method to generate custom identifiers for clients. + // Redis store configuration + store: new RedisStore({ + // @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis + sendCommand: (...args: string[]) => client.call(...args), + }) as any, + }) } - - diff --git a/src/lib/server/api/infrastructure/database/index.ts b/src/lib/server/api/packages/drizzle.ts similarity index 68% rename from src/lib/server/api/infrastructure/database/index.ts rename to src/lib/server/api/packages/drizzle.ts index 556e598..474ce89 100644 --- a/src/lib/server/api/infrastructure/database/index.ts +++ b/src/lib/server/api/packages/drizzle.ts @@ -1,7 +1,7 @@ -import { drizzle } from 'drizzle-orm/node-postgres'; -import pg from 'pg'; -import { config } from '../../common/config'; -import * as schema from './tables'; +import { drizzle } from 'drizzle-orm/node-postgres' +import pg from 'pg' +import { config } from '../configs/config' +import * as schema from '../databases/tables' // create the connection export const pool = new pg.Pool({ @@ -12,11 +12,11 @@ export const pool = new pg.Pool({ database: config.DATABASE_DB, ssl: config.DATABASE_HOST !== 'localhost', max: config.DB_MIGRATING || config.DB_SEEDING ? 1 : undefined, -}); +}) export const db = drizzle(pool, { schema, logger: config.NODE_ENV === 'development', -}); +}) -export type db = typeof db; +export type db = typeof db diff --git a/src/lib/server/api/infrastructure/auth/lucia.ts b/src/lib/server/api/packages/lucia.ts similarity index 66% rename from src/lib/server/api/infrastructure/auth/lucia.ts rename to src/lib/server/api/packages/lucia.ts index 8141cb6..825e478 100644 --- a/src/lib/server/api/infrastructure/auth/lucia.ts +++ b/src/lib/server/api/packages/lucia.ts @@ -1,11 +1,11 @@ +import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle' // lib/server/lucia.ts -import { Lucia, TimeSpan } from 'lucia'; -import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; -import { db } from '../database'; -import { sessionsTable, usersTable } from '../database/tables'; -import { config } from '../../common/config'; +import { Lucia, TimeSpan } from 'lucia' +import { config } from '../configs/config' +import { sessionsTable, usersTable } from '../databases/tables' +import { db } from './drizzle' -const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable); +const adapter = new DrizzlePostgreSQLAdapter(db, sessionsTable, usersTable) export const lucia = new Lucia(adapter, { getSessionAttributes: (attributes) => { @@ -14,7 +14,7 @@ export const lucia = new Lucia(adapter, { ipAddress: attributes.ip_address, isTwoFactorAuthEnabled: attributes.twoFactorAuthEnabled, isTwoFactorAuthenticated: attributes.isTwoFactorAuthenticated, - }; + } }, getUserAttributes: (attributes) => { return { @@ -25,7 +25,7 @@ export const lucia = new Lucia(adapter, { lastName: attributes.last_name, mfa_enabled: attributes.mfa_enabled, theme: attributes.theme, - }; + } }, sessionExpiresIn: new TimeSpan(2, 'w'), // 2 weeks sessionCookie: { @@ -38,26 +38,26 @@ export const lucia = new Lucia(adapter, { domain: config.domain, }, }, -}); +}) declare module 'lucia' { interface Register { - Lucia: typeof lucia; - DatabaseUserAttributes: DatabaseUserAttributes; - DatabaseSessionAttributes: DatabaseSessionAttributes; + Lucia: typeof lucia + DatabaseUserAttributes: DatabaseUserAttributes + DatabaseSessionAttributes: DatabaseSessionAttributes } interface DatabaseSessionAttributes { - ip_country: string; - ip_address: string; - twoFactorAuthEnabled: boolean; - isTwoFactorAuthenticated: boolean; + ip_country: string + ip_address: string + twoFactorAuthEnabled: boolean + isTwoFactorAuthenticated: boolean } interface DatabaseUserAttributes { - username: string; - email: string; - first_name: string; - last_name: string; - mfa_enabled: boolean; - theme: string; + username: string + email: string + first_name: string + last_name: string + mfa_enabled: boolean + theme: string } } diff --git a/src/lib/server/api/providers/database.provider.ts b/src/lib/server/api/providers/database.provider.ts index c7da118..df2ed64 100644 --- a/src/lib/server/api/providers/database.provider.ts +++ b/src/lib/server/api/providers/database.provider.ts @@ -1,11 +1,11 @@ -import { container } from 'tsyringe'; -import { db } from '../infrastructure/database'; +import { container } from 'tsyringe' +import { db } from '../packages/drizzle' // Symbol -export const DatabaseProvider = Symbol('DATABASE_TOKEN'); +export const DatabaseProvider = Symbol('DATABASE_TOKEN') // Type -export type DatabaseProvider = typeof db; +export type DatabaseProvider = typeof db // Register -container.register(DatabaseProvider, { useValue: db }); +container.register(DatabaseProvider, { useValue: db }) diff --git a/src/lib/server/api/providers/index.ts b/src/lib/server/api/providers/index.ts deleted file mode 100644 index 0ac960b..0000000 --- a/src/lib/server/api/providers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './database.provider'; -export * from './lucia.provider'; -export * from './redis.provider'; diff --git a/src/lib/server/api/providers/lucia.provider.ts b/src/lib/server/api/providers/lucia.provider.ts index 6546d43..4291977 100644 --- a/src/lib/server/api/providers/lucia.provider.ts +++ b/src/lib/server/api/providers/lucia.provider.ts @@ -1,11 +1,11 @@ -import { container } from 'tsyringe'; -import { lucia } from '../infrastructure/auth/lucia'; +import { container } from 'tsyringe' +import { lucia } from '../packages/lucia' // Symbol -export const LuciaProvider = Symbol('LUCIA_PROVIDER'); +export const LuciaProvider = Symbol('LUCIA_PROVIDER') // Type -export type LuciaProvider = typeof lucia; +export type LuciaProvider = typeof lucia // Register -container.register(LuciaProvider, { useValue: lucia }); +container.register(LuciaProvider, { useValue: lucia }) diff --git a/src/lib/server/api/providers/redis.provider.ts b/src/lib/server/api/providers/redis.provider.ts index 26496de..cf7ec0d 100644 --- a/src/lib/server/api/providers/redis.provider.ts +++ b/src/lib/server/api/providers/redis.provider.ts @@ -1,14 +1,11 @@ -import { container } from 'tsyringe'; import RedisClient from 'ioredis' -import { config } from '../common/config'; +import { container } from 'tsyringe' +import { config } from '../configs/config' -// Symbol -export const RedisProvider = Symbol('REDIS_TOKEN'); - -// Type -export type RedisProvider = RedisClient; - -// Register +export const RedisProvider = Symbol('REDIS_TOKEN') +export type RedisProvider = RedisClient container.register(RedisProvider, { - useValue: new RedisClient(config.REDIS_URL) -}); + useValue: new RedisClient(config.REDIS_URL, { + maxRetriesPerRequest: null, + }), +}) diff --git a/src/lib/server/api/repositories/collections.repository.ts b/src/lib/server/api/repositories/collections.repository.ts index 72db342..fa5669d 100644 --- a/src/lib/server/api/repositories/collections.repository.ts +++ b/src/lib/server/api/repositories/collections.repository.ts @@ -1,18 +1,19 @@ -import {inject, injectable} from "tsyringe"; -import { eq, type InferInsertModel } from "drizzle-orm"; -import {DatabaseProvider} from "$lib/server/api/providers"; -import { collections } from "../infrastructure/database/tables"; -import { takeFirstOrThrow } from "../infrastructure/database/utils"; +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository.utils' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { collections } from '../databases/tables' -export type CreateCollection = InferInsertModel; -export type UpdateCollection = Partial; +export type CreateCollection = InferInsertModel +export type UpdateCollection = Partial @injectable() -export class CollectionsRepository { - constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } +export class CollectionsRepository implements Repository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findAll() { - return this.db.query.collections.findMany(); + return this.db.query.collections.findMany() } async findOneById(id: string) { @@ -20,8 +21,8 @@ export class CollectionsRepository { where: eq(collections.id, id), columns: { cuid: true, - name: true - } + name: true, + }, }) } @@ -30,8 +31,8 @@ export class CollectionsRepository { where: eq(collections.cuid, cuid), columns: { cuid: true, - name: true - } + name: true, + }, }) } @@ -40,27 +41,26 @@ export class CollectionsRepository { where: eq(collections.user_id, userId), columns: { cuid: true, - name: true - } + name: true, + }, }) } async findAllByUserId(userId: string) { return this.db.query.collections.findMany({ - where: eq(collections.user_id, userId) + where: eq(collections.user_id, userId), }) } async create(data: CreateCollection) { - return this.db.insert(collections).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(collections).values(data).returning().then(takeFirstOrThrow) } async update(id: string, data: UpdateCollection) { - return this.db - .update(collections) - .set(data) - .where(eq(collections.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.update(collections).set(data).where(eq(collections.id, id)).returning().then(takeFirstOrThrow) } -} \ No newline at end of file + + trxHost(trx: DatabaseProvider) { + return new CollectionsRepository(trx) + } +} diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 2594af5..7042384 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -1,14 +1,15 @@ -import { and, eq, type InferInsertModel } from 'drizzle-orm' -import { credentialsTable, CredentialsType } from '../infrastructure/database/tables/credentials.table' -import { takeFirstOrThrow } from '../infrastructure/database/utils' +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { CredentialsType, credentialsTable } from '$lib/server/api/databases/tables/credentials.table' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, and, eq } from 'drizzle-orm' import { inject, injectable } from 'tsyringe' -import { DatabaseProvider } from '$lib/server/api/providers' +import { takeFirstOrThrow } from '../common/utils/repository.utils' export type CreateCredentials = InferInsertModel export type UpdateCredentials = Partial @injectable() -export class CredentialsRepository { +export class CredentialsRepository implements Repository { constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findOneByUserId(userId: string) { @@ -66,4 +67,8 @@ export class CredentialsRepository { async deleteByUserIdAndType(userId: string, type: CredentialsType) { return this.db.delete(credentialsTable).where(and(eq(credentialsTable.user_id, userId), eq(credentialsTable.type, type))) } + + trxHost(trx: DatabaseProvider) { + return new CredentialsRepository(trx) + } } diff --git a/src/lib/server/api/repositories/roles.repository.ts b/src/lib/server/api/repositories/roles.repository.ts index b1ab846..e940f4b 100644 --- a/src/lib/server/api/repositories/roles.repository.ts +++ b/src/lib/server/api/repositories/roles.repository.ts @@ -1,8 +1,9 @@ -import { eq, type InferInsertModel } from 'drizzle-orm'; -import { takeFirstOrThrow } from '../infrastructure/database/utils'; -import {roles} from "$lib/server/api/infrastructure/database/tables"; -import {inject, injectable} from "tsyringe"; -import {DatabaseProvider} from "$lib/server/api/providers"; +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { takeFirstOrThrow } from '../common/utils/repository.utils' +import { roles } from '../databases/tables' /* -------------------------------------------------------------------------- */ /* Repository */ @@ -20,59 +21,54 @@ storing data. They should not contain any business logic, only database queries. In our case the method 'trxHost' is used to set the transaction context. */ -export type CreateRole = InferInsertModel; -export type UpdateRole = Partial; +export type CreateRole = InferInsertModel +export type UpdateRole = Partial @injectable() -export class RolesRepository { - constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } +export class RolesRepository implements Repository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findOneById(id: string) { return this.db.query.roles.findFirst({ - where: eq(roles.id, id) - }); + where: eq(roles.id, id), + }) } async findOneByIdOrThrow(id: string) { - const role = await this.findOneById(id); - if (!role) throw Error('Role not found'); - return role; + const role = await this.findOneById(id) + if (!role) throw Error('Role not found') + return role } async findAll() { - return this.db.query.roles.findMany(); + return this.db.query.roles.findMany() } async findOneByName(name: string) { return this.db.query.roles.findFirst({ - where: eq(roles.name, name) - }); + where: eq(roles.name, name), + }) } async findOneByNameOrThrow(name: string) { - const role = await this.findOneByName(name); - if (!role) throw Error('Role not found'); - return role; + const role = await this.findOneByName(name) + if (!role) throw Error('Role not found') + return role } async create(data: CreateRole) { - return this.db.insert(roles).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(roles).values(data).returning().then(takeFirstOrThrow) } async update(id: string, data: UpdateRole) { - return this.db - .update(roles) - .set(data) - .where(eq(roles.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.update(roles).set(data).where(eq(roles.id, id)).returning().then(takeFirstOrThrow) } async delete(id: string) { - return this.db - .delete(roles) - .where(eq(roles.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.delete(roles).where(eq(roles.id, id)).returning().then(takeFirstOrThrow) + } + + trxHost(trx: DatabaseProvider) { + return new RolesRepository(trx) } } diff --git a/src/lib/server/api/repositories/user_roles.repository.ts b/src/lib/server/api/repositories/user_roles.repository.ts index cbc688f..90e2823 100644 --- a/src/lib/server/api/repositories/user_roles.repository.ts +++ b/src/lib/server/api/repositories/user_roles.repository.ts @@ -1,8 +1,9 @@ -import { eq, type InferInsertModel } from 'drizzle-orm'; -import { takeFirstOrThrow } from '../infrastructure/database/utils'; -import {user_roles} from "$lib/server/api/infrastructure/database/tables"; -import {inject, injectable} from "tsyringe"; -import {DatabaseProvider} from "$lib/server/api/providers"; +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { takeFirstOrThrow } from '../common/utils/repository.utils' +import { user_roles } from '../databases/tables' /* -------------------------------------------------------------------------- */ /* Repository */ @@ -20,40 +21,40 @@ storing data. They should not contain any business logic, only database queries. In our case the method 'trxHost' is used to set the transaction context. */ -export type CreateUserRole = InferInsertModel; -export type UpdateUserRole = Partial; +export type CreateUserRole = InferInsertModel +export type UpdateUserRole = Partial @injectable() -export class UserRolesRepository { - constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } +export class UserRolesRepository implements Repository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findOneById(id: string) { return this.db.query.user_roles.findFirst({ - where: eq(user_roles.id, id) - }); + where: eq(user_roles.id, id), + }) } async findOneByIdOrThrow(id: string) { - const userRole = await this.findOneById(id); - if (!userRole) throw Error('User not found'); - return userRole; + const userRole = await this.findOneById(id) + if (!userRole) throw Error('User not found') + return userRole } async findAllByUserId(userId: string) { return this.db.query.user_roles.findMany({ - where: eq(user_roles.user_id, userId) - }); + where: eq(user_roles.user_id, userId), + }) } async create(data: CreateUserRole) { - return this.db.insert(user_roles).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(user_roles).values(data).returning().then(takeFirstOrThrow) } async delete(id: string) { - return this.db - .delete(user_roles) - .where(eq(user_roles.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.delete(user_roles).where(eq(user_roles.id, id)).returning().then(takeFirstOrThrow) + } + + trxHost(trx: DatabaseProvider) { + return new UserRolesRepository(trx) } } diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts index e65ec7a..b44b1da 100644 --- a/src/lib/server/api/repositories/users.repository.ts +++ b/src/lib/server/api/repositories/users.repository.ts @@ -1,8 +1,9 @@ -import { eq, type InferInsertModel } from 'drizzle-orm'; -import { usersTable } from '../infrastructure/database/tables/users.table'; -import { takeFirstOrThrow } from '../infrastructure/database/utils'; -import {inject, injectable} from "tsyringe"; -import {DatabaseProvider} from "$lib/server/api/providers"; +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { usersTable } from '$lib/server/api/databases/tables/users.table' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { takeFirstOrThrow } from '../common/utils/repository.utils' /* -------------------------------------------------------------------------- */ /* Repository */ @@ -20,55 +21,50 @@ storing data. They should not contain any business logic, only database queries. In our case the method 'trxHost' is used to set the transaction context. */ -export type CreateUser = InferInsertModel; -export type UpdateUser = Partial; +export type CreateUser = InferInsertModel +export type UpdateUser = Partial @injectable() -export class UsersRepository { - constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) { } +export class UsersRepository implements Repository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findOneById(id: string) { return this.db.query.usersTable.findFirst({ - where: eq(usersTable.id, id) - }); + where: eq(usersTable.id, id), + }) } async findOneByIdOrThrow(id: string) { - const user = await this.findOneById(id); - if (!user) throw Error('User not found'); - return user; + const user = await this.findOneById(id) + if (!user) throw Error('User not found') + return user } async findOneByUsername(username: string) { return this.db.query.usersTable.findFirst({ - where: eq(usersTable.username, username) - }); + where: eq(usersTable.username, username), + }) } async findOneByEmail(email: string) { return this.db.query.usersTable.findFirst({ - where: eq(usersTable.email, email) - }); + where: eq(usersTable.email, email), + }) } async create(data: CreateUser) { - return this.db.insert(usersTable).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(usersTable).values(data).returning().then(takeFirstOrThrow) } async update(id: string, data: UpdateUser) { - return this.db - .update(usersTable) - .set(data) - .where(eq(usersTable.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.update(usersTable).set(data).where(eq(usersTable.id, id)).returning().then(takeFirstOrThrow) } async delete(id: string) { - return this.db - .delete(usersTable) - .where(eq(usersTable.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.delete(usersTable).where(eq(usersTable.id, id)).returning().then(takeFirstOrThrow) + } + + trxHost(trx: DatabaseProvider) { + return new UsersRepository(trx) } } diff --git a/src/lib/server/api/repositories/wishlists.repository.ts b/src/lib/server/api/repositories/wishlists.repository.ts index 4ea4076..00c074b 100644 --- a/src/lib/server/api/repositories/wishlists.repository.ts +++ b/src/lib/server/api/repositories/wishlists.repository.ts @@ -1,18 +1,19 @@ -import {inject, injectable} from "tsyringe"; -import {DatabaseProvider} from "$lib/server/api/providers"; -import { eq, type InferInsertModel } from "drizzle-orm"; -import { wishlists } from "../infrastructure/database/tables"; -import { takeFirstOrThrow } from "../infrastructure/database/utils"; +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { takeFirstOrThrow } from '../common/utils/repository.utils' +import { wishlists } from '../databases/tables' -export type CreateWishlist = InferInsertModel; -export type UpdateWishlist = Partial; +export type CreateWishlist = InferInsertModel +export type UpdateWishlist = Partial @injectable() -export class WishlistsRepository { - constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider){ } +export class WishlistsRepository implements Repository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} async findAll() { - return this.db.query.wishlists.findMany(); + return this.db.query.wishlists.findMany() } async findOneById(id: string) { @@ -20,8 +21,8 @@ export class WishlistsRepository { where: eq(wishlists.id, id), columns: { cuid: true, - name: true - } + name: true, + }, }) } @@ -30,8 +31,8 @@ export class WishlistsRepository { where: eq(wishlists.cuid, cuid), columns: { cuid: true, - name: true - } + name: true, + }, }) } @@ -40,8 +41,8 @@ export class WishlistsRepository { where: eq(wishlists.user_id, userId), columns: { cuid: true, - name: true - } + name: true, + }, }) } @@ -50,21 +51,20 @@ export class WishlistsRepository { where: eq(wishlists.user_id, userId), columns: { cuid: true, - name: true - } + name: true, + }, }) } async create(data: CreateWishlist) { - return this.db.insert(wishlists).values(data).returning().then(takeFirstOrThrow); + return this.db.insert(wishlists).values(data).returning().then(takeFirstOrThrow) } async update(id: string, data: UpdateWishlist) { - return this.db - .update(wishlists) - .set(data) - .where(eq(wishlists.id, id)) - .returning() - .then(takeFirstOrThrow); + return this.db.update(wishlists).set(data).where(eq(wishlists.id, id)).returning().then(takeFirstOrThrow) } -} \ No newline at end of file + + trxHost(trx: DatabaseProvider) { + return new WishlistsRepository(trx) + } +} diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts index 16a8cba..f7b3599 100644 --- a/src/lib/server/api/services/iam.service.ts +++ b/src/lib/server/api/services/iam.service.ts @@ -1,8 +1,9 @@ -import type { UpdateEmailDto } from "$lib/dtos/update-email.dto"; -import type { UpdateProfileDto } from "$lib/dtos/update-profile.dto"; -import { UsersService } from "$lib/server/api/services/users.service"; -import { inject, injectable } from 'tsyringe'; -import { LuciaProvider } from '$lib/server/api/providers'; +import type { UpdateEmailDto } from '$lib/server/api/dtos/update-email.dto' +import type { UpdateProfileDto } from '$lib/server/api/dtos/update-profile.dto' +import type { VerifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto' +import { LuciaProvider } from '$lib/server/api/providers/lucia.provider' +import { UsersService } from '$lib/server/api/services/users.service' +import { inject, injectable } from 'tsyringe' /* -------------------------------------------------------------------------- */ /* Service */ @@ -25,54 +26,54 @@ simple as possible. This makes the service easier to read, test and understand. export class IamService { constructor( @inject(LuciaProvider) private readonly lucia: LuciaProvider, - @inject(UsersService) private readonly usersService: UsersService - ) { } + @inject(UsersService) private readonly usersService: UsersService, + ) {} async logout(sessionId: string) { - return this.lucia.invalidateSession(sessionId); + return this.lucia.invalidateSession(sessionId) } async updateProfile(userId: string, data: UpdateProfileDto) { - const user = await this.usersService.findOneById(userId); + const user = await this.usersService.findOneById(userId) if (!user) { return { - error: 'User not found' - }; + error: 'User not found', + } } - const existingUserForNewUsername = await this.usersService.findOneByUsername(data.username); + const existingUserForNewUsername = await this.usersService.findOneByUsername(data.username) if (existingUserForNewUsername && existingUserForNewUsername.id !== userId) { return { - error: 'Username already in use' - }; + error: 'Username already in use', + } } return this.usersService.updateUser(userId, { first_name: data.firstName, last_name: data.lastName, - username: data.username !== user.username ? data.username : user.username - }); + username: data.username !== user.username ? data.username : user.username, + }) } async updateEmail(userId: string, data: UpdateEmailDto) { - const { email } = data; + const { email } = data - const existingUserEmail = await this.usersService.findOneByEmail(email); + const existingUserEmail = await this.usersService.findOneByEmail(email) if (existingUserEmail && existingUserEmail.id !== userId) { - return null; + return null } return this.usersService.updateUser(userId, { email, - }); + }) } async verifyPassword(userId: string, data: VerifyPasswordDto) { - const user = await this.usersService.findOneById(userId); + const user = await this.usersService.findOneById(userId) if (!user) { - return null; + return null } - const { password } = data; - return this.usersService.verifyPassword(userId, { password }); + const { password } = data + return this.usersService.verifyPassword(userId, { password }) } } diff --git a/src/lib/server/api/services/jobs.service.ts b/src/lib/server/api/services/jobs.service.ts new file mode 100644 index 0000000..4f5f0d1 --- /dev/null +++ b/src/lib/server/api/services/jobs.service.ts @@ -0,0 +1,16 @@ +import { RedisProvider } from '$lib/server/api/providers/redis.provider' +import { type Processor, Queue, Worker } from 'bullmq' +import { inject, injectable } from 'tsyringe' + +@injectable() +export class JobsService { + constructor(@inject(RedisProvider) private readonly redis: RedisProvider) {} + + createQueue(name: string) { + return new Queue(name, { connection: this.redis }) + } + + createWorker(name: string, processor: Processor) { + return new Worker(name, processor, { connection: this.redis }) + } +} diff --git a/src/lib/server/api/services/loginrequest.service.ts b/src/lib/server/api/services/loginrequest.service.ts index d3f9c8d..6748eb8 100644 --- a/src/lib/server/api/services/loginrequest.service.ts +++ b/src/lib/server/api/services/loginrequest.service.ts @@ -1,98 +1,95 @@ -import { inject, injectable } from 'tsyringe'; -import { BadRequest } from '../common/errors'; -import { DatabaseProvider } from '../providers'; -import { MailerService } from './mailer.service'; -import { TokensService } from './tokens.service'; -import { LuciaProvider } from '../providers/lucia.provider'; -import { UsersRepository } from '../repositories/users.repository'; -import { CredentialsRepository } from '../repositories/credentials.repository'; -import type { HonoRequest } from 'hono'; -import type {SigninUsernameDto} from "$lib/dtos/signin-username.dto"; -import type {Credentials} from "$lib/server/api/infrastructure/database/tables"; +import type { SigninUsernameDto } from '$lib/server/api/dtos/signin-username.dto' +import type { HonoRequest } from 'hono' +import { inject, injectable } from 'tsyringe' +import { BadRequest } from '../common/exceptions' +import type { Credentials } from '../databases/tables' +import { DatabaseProvider } from '../providers/database.provider' +import { LuciaProvider } from '../providers/lucia.provider' +import { CredentialsRepository } from '../repositories/credentials.repository' +import { UsersRepository } from '../repositories/users.repository' +import { MailerService } from './mailer.service' +import { TokensService } from './tokens.service' @injectable() export class LoginRequestsService { - constructor( - @inject(LuciaProvider) private readonly lucia: LuciaProvider, - @inject(DatabaseProvider) private readonly db: DatabaseProvider, - @inject(TokensService) private readonly tokensService: TokensService, - @inject(MailerService) private readonly mailerService: MailerService, - @inject(UsersRepository) private readonly usersRepository: UsersRepository, - @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, - ) { } + constructor( + @inject(LuciaProvider) private readonly lucia: LuciaProvider, + @inject(DatabaseProvider) private readonly db: DatabaseProvider, + @inject(TokensService) private readonly tokensService: TokensService, + @inject(MailerService) private readonly mailerService: MailerService, + @inject(UsersRepository) private readonly usersRepository: UsersRepository, + @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, + ) {} - // async create(data: RegisterEmailDto) { - // // generate a token, expiry date, and hash - // const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); - // // save the login request to the database - ensuring we save the hashedToken - // await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); - // // send the login request email - // await this.mailerService.sendLoginRequest({ - // to: data.email, - // props: { token: token } - // }); - // } + // async create(data: RegisterEmailDto) { + // // generate a token, expiry date, and hash + // const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); + // // save the login request to the database - ensuring we save the hashedToken + // await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); + // // send the login request email + // await this.mailerService.sendLoginRequest({ + // to: data.email, + // props: { token: token } + // }); + // } - async verify(data: SigninUsernameDto, req: HonoRequest) { - const requestIpAddress = req.header('x-real-ip'); - const requestIpCountry = req.header('x-vercel-ip-country'); - const existingUser = await this.usersRepository.findOneByUsername(data.username); + async verify(data: SigninUsernameDto, req: HonoRequest) { + const requestIpAddress = req.header('x-real-ip') + const requestIpCountry = req.header('x-vercel-ip-country') + const existingUser = await this.usersRepository.findOneByUsername(data.username) - if (!existingUser) { - throw BadRequest('User not found'); + if (!existingUser) { + throw BadRequest('User not found') } - const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id); + const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id) - if (!credential) { - throw BadRequest('Invalid credentials'); - } + if (!credential) { + throw BadRequest('Invalid credentials') + } - if (!await this.tokensService.verifyHashedToken(credential.secret_data, data.password)) { - throw BadRequest('Invalid credentials'); - } + if (!(await this.tokensService.verifyHashedToken(credential.secret_data, data.password))) { + throw BadRequest('Invalid credentials') + } - const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id); + const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id) - return await this.createUserSession(existingUser.id, req, totpCredentials); - } + return await this.createUserSession(existingUser.id, req, totpCredentials) + } async createUserSession(existingUserId: string, req: HonoRequest, totpCredentials: Credentials | undefined) { - const requestIpAddress = req.header('x-real-ip'); - const requestIpCountry = req.header('x-vercel-ip-country'); + const requestIpAddress = req.header('x-real-ip') + const requestIpCountry = req.header('x-vercel-ip-country') return this.lucia.createSession(existingUserId, { ip_country: requestIpCountry || 'unknown', ip_address: requestIpAddress || 'unknown', - twoFactorAuthEnabled: - !!totpCredentials && - totpCredentials?.secret_data !== null && - totpCredentials?.secret_data !== '', + twoFactorAuthEnabled: !!totpCredentials && totpCredentials?.secret_data !== null && totpCredentials?.secret_data !== '', isTwoFactorAuthenticated: false, - }); + }) } - // Create a new user and send a welcome email - or other onboarding process - private async handleNewUserRegistration(email: string) { - const newUser = await this.usersRepository.create({ email, verified: true }) - this.mailerService.sendWelcome({ to: email, props: null }); - // TODO: add whatever onboarding process or extra data you need here - return newUser - } + // Create a new user and send a welcome email - or other onboarding process + private async handleNewUserRegistration(email: string) { + const newUser = await this.usersRepository.create({ email, verified: true }) + this.mailerService.sendWelcome({ to: email, props: null }) + // TODO: add whatever onboarding process or extra data you need here + return newUser + } - // Fetch a valid request from the database, verify the token and burn the request if it is valid - // private async fetchValidRequest(email: string, token: string) { - // return await this.db.transaction(async (trx) => { - // // fetch the login request - // const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) - // if (!loginRequest) return null; + // Fetch a valid request from the database, verify the token and burn the request if it is valid + // private async fetchValidRequest(email: string, token: string) { + // return await this.db.transaction(async (trx) => { + // // fetch the login request + // const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) + // if (!loginRequest) return null; - // // check if the token is valid - // const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); - // if (!isValidRequest) return null + // // check if the token is valid + // const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); + // if (!isValidRequest) return null - // // if the token is valid, burn the request - // await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); - // return loginRequest - // }) - // } -} \ No newline at end of file + // // if the token is valid, burn the request + // await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); + // return loginRequest + // }) + // } +} diff --git a/src/lib/server/api/services/mailer.service.ts b/src/lib/server/api/services/mailer.service.ts index f644f11..c780275 100644 --- a/src/lib/server/api/services/mailer.service.ts +++ b/src/lib/server/api/services/mailer.service.ts @@ -1,104 +1,42 @@ -import fs from 'fs'; -import path from 'path'; -import nodemailer from 'nodemailer'; -import handlebars from 'handlebars'; -import { fileURLToPath } from 'url'; -import { injectable } from 'tsyringe'; +import { injectable } from 'tsyringe' +import type { Email } from '../common/inferfaces/email.interface' +import { config } from '../configs/config' -/* -------------------------------------------------------------------------- */ -/* Service */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* ---------------------------------- About --------------------------------- */ -/* -Services are responsible for handling business logic and data manipulation. -They genreally call on repositories or other services to complete a use-case. -*/ -/* ---------------------------------- Notes --------------------------------- */ -/* -Services should be kept as clean and simple as possible. - -Create private functions to handle complex logic and keep the public methods as -simple as possible. This makes the service easier to read, test and understand. -*/ -/* -------------------------------------------------------------------------- */ - -type SendMail = { - to: string | string[]; - subject: string; - html: string; -}; - -type SendTemplate = { - to: string | string[]; - props: T; -}; +type SendProps = { + to: string | string[] + email: Email +} @injectable() export class MailerService { - private nodemailer = nodemailer.createTransport({ - host: 'smtp.ethereal.email', - port: 587, - secure: false, // Use `true` for port 465, `false` for all other ports - auth: { - user: 'adella.hoppe@ethereal.email', - pass: 'dshNQZYhATsdJ3ENke' + async send(data: SendProps) { + const mailer = config.isProduction ? this.sendProd : this.sendDev + await mailer(data) + } + + private async sendDev({ to, email }: SendProps) { + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Attachments: [], + From: { Email: 'noreply@tofustack.com', Name: 'TofuStack' }, + HTML: email.html(), + Subject: email.subject(), + Text: email.html(), + To: Array.isArray(to) ? to.map((to) => ({ Email: to, Name: to })) : [{ Email: to, Name: to }], + }), } - }); - sendEmailVerificationToken(data: SendTemplate<{ token: string }>) { - const template = handlebars.compile(this.getTemplate('email-verification-token')); - return this.send({ - to: data.to, - subject: 'Email Verification', - html: template({ token: data.props.token }) - }); + const response = await fetch('http://localhost:8025/api/v1/send', options) + const data = await response.json() + console.log(`http://localhost:8025/view/${data.ID}`) } - sendEmailChangeNotification(data: SendTemplate) { - const template = handlebars.compile(this.getTemplate('email-change-notice')); - return this.send({ - to: data.to, - subject: 'Email Change Notice', - html: template(null) - }); - } - - sendLoginRequest(data: SendTemplate<{ token: string }>) { - const template = handlebars.compile(this.getTemplate('email-verification-token')); - return this.send({ - to: data.to, - subject: 'Login Request', - html: template({ token: data.props.token }) - }); - } - - sendWelcome(data: SendTemplate) { - const template = handlebars.compile(this.getTemplate('welcome')); - return this.send({ - to: data.to, - subject: 'Welcome!', - html: template(null) - }); - } - - private async send({ to, subject, html }: SendMail) { - const message = await this.nodemailer.sendMail({ - from: '"Example" ', // sender address - bcc: to, - subject, // Subject line - text: html, - html - }); - console.log(nodemailer.getTestMessageUrl(message)); - } - - private getTemplate(template: string) { - const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file - const __dirname = path.dirname(__filename); // get the name of the directory - return fs.readFileSync( - path.join(__dirname, `../infrastructure/email-templates/${template}.hbs`), - 'utf-8' - ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private async sendProd({ to, email }: SendProps) { + // CONFIGURE MAILER } } diff --git a/src/lib/server/api/services/queues.service.ts b/src/lib/server/api/services/queues.service.ts deleted file mode 100644 index 97e7ca3..0000000 --- a/src/lib/server/api/services/queues.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { injectable } from "tsyringe"; -import RedisClient from 'ioredis' -import { config } from "../common/config"; -import { Queue, Worker, type Processor } from 'bullmq'; - -@injectable() -export class QueuesServices { - connection = new RedisClient(config.REDIS_URL); - - constructor() { } - - createQueue(name: string) { - return new Queue(name, { connection: this.connection }) - } - - createWorker(name: string, prcoessor: Processor) { - return new Worker(name, prcoessor, { connection: this.connection }) - } -} \ No newline at end of file diff --git a/src/lib/server/api/services/totp.service.ts b/src/lib/server/api/services/totp.service.ts index f75674e..b8c5fbf 100644 --- a/src/lib/server/api/services/totp.service.ts +++ b/src/lib/server/api/services/totp.service.ts @@ -1,47 +1,43 @@ -import { inject, injectable } from "tsyringe"; -import { HMAC } from 'oslo/crypto'; -import { decodeHex, encodeHex } from 'oslo/encoding'; -import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository"; -import { TOTPController } from "oslo/otp"; -import type { CredentialsType } from "$db/tables"; +import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' +import { HMAC } from 'oslo/crypto' +import { decodeHex, encodeHex } from 'oslo/encoding' +import { TOTPController } from 'oslo/otp' +import { inject, injectable } from 'tsyringe' +import type { CredentialsType } from '../databases/tables' @injectable() export class TotpService { - - constructor( - @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository - ) { - } + constructor(@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository) {} async findOneByUserId(userId: string) { - return this.credentialsRepository.findTOTPCredentialsByUserId(userId); + return this.credentialsRepository.findTOTPCredentialsByUserId(userId) } async findOneByUserIdOrThrow(userId: string) { - const credential = await this.findOneByUserId(userId); + const credential = await this.findOneByUserId(userId) if (!credential) { - throw new Error('TOTP credential not found'); + throw new Error('TOTP credential not found') } - return credential; + return credential } async create(userId: string) { - const twoFactorSecret = await new HMAC('SHA-1').generateKey(); + const twoFactorSecret = await new HMAC('SHA-1').generateKey() try { return await this.credentialsRepository.create({ user_id: userId, secret_data: encodeHex(twoFactorSecret), - type: 'totp' - }); + type: 'totp', + }) } catch (e) { - console.error(e); - return null; + console.error(e) + return null } } async deleteOneByUserId(userId: string) { - return this.credentialsRepository.deleteByUserId(userId); + return this.credentialsRepository.deleteByUserId(userId) } async deleteOneByUserIdAndType(userId: string, type: CredentialsType) { @@ -49,10 +45,10 @@ export class TotpService { } async verify(userId: string, code: string) { - const credential = await this.credentialsRepository.findTOTPCredentialsByUserId(userId); + const credential = await this.credentialsRepository.findTOTPCredentialsByUserId(userId) if (!credential) { - throw new Error('TOTP credential not found'); + throw new Error('TOTP credential not found') } return await new TOTPController().verify(code, decodeHex(credential.secret_data)) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index 75ca372..89e6eea 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -1,12 +1,12 @@ -import { inject, injectable } from 'tsyringe'; -import {type UpdateUser, UsersRepository} from '../repositories/users.repository'; -import type {SignupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto"; -import {TokensService} from "$lib/server/api/services/tokens.service"; -import {CredentialsRepository} from "$lib/server/api/repositories/credentials.repository"; -import {CredentialsType} from "$lib/server/api/infrastructure/database/tables"; -import {UserRolesService} from "$lib/server/api/services/user_roles.service"; -import { CollectionsService } from './collections.service'; -import { WishlistsService } from './wishlists.service'; +import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto' +import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' +import { TokensService } from '$lib/server/api/services/tokens.service' +import { UserRolesService } from '$lib/server/api/services/user_roles.service' +import { inject, injectable } from 'tsyringe' +import { CredentialsType } from '../databases/tables' +import { type UpdateUser, UsersRepository } from '../repositories/users.repository' +import { CollectionsService } from './collections.service' +import { WishlistsService } from './wishlists.service' @injectable() export class UsersService { @@ -16,69 +16,69 @@ export class UsersService { @inject(TokensService) private readonly tokenService: TokensService, @inject(UsersRepository) private readonly usersRepository: UsersRepository, @inject(UserRolesService) private readonly userRolesService: UserRolesService, - @inject(WishlistsService) private readonly wishlistsService: WishlistsService - ) { } + @inject(WishlistsService) private readonly wishlistsService: WishlistsService, + ) {} async create(data: SignupUsernameEmailDto) { - const { firstName, lastName, email, username, password } = data; + const { firstName, lastName, email, username, password } = data - const hashedPassword = await this.tokenService.createHashedToken(password); + const hashedPassword = await this.tokenService.createHashedToken(password) const user = await this.usersRepository.create({ first_name: firstName, last_name: lastName, email, username, - }); + }) if (!user) { - return null; + return null } const credentials = await this.credentialsRepository.create({ user_id: user.id, type: CredentialsType.PASSWORD, secret_data: hashedPassword, - }); + }) if (!credentials) { - await this.usersRepository.delete(user.id); - return null; + await this.usersRepository.delete(user.id) + return null } - await this.userRolesService.addRoleToUser(user.id, 'user', true); + await this.userRolesService.addRoleToUser(user.id, 'user', true) - await this.wishlistsService.createEmptyNoName(user.id); - await this.collectionsService.createEmptyNoName(user.id); + await this.wishlistsService.createEmptyNoName(user.id) + await this.collectionsService.createEmptyNoName(user.id) - return user; + return user } async updateUser(userId: string, data: UpdateUser) { - return this.usersRepository.update(userId, data); + return this.usersRepository.update(userId, data) } async findOneByUsername(username: string) { - return this.usersRepository.findOneByUsername(username); + return this.usersRepository.findOneByUsername(username) } async findOneByEmail(email: string) { - return this.usersRepository.findOneByEmail(email); + return this.usersRepository.findOneByEmail(email) } async findOneById(id: string) { - return this.usersRepository.findOneById(id); + return this.usersRepository.findOneById(id) } async verifyPassword(userId: string, data: { password: string }) { - const user = await this.usersRepository.findOneById(userId); + const user = await this.usersRepository.findOneById(userId) if (!user) { - throw new Error('User not found'); + throw new Error('User not found') } - const credential = await this.credentialsRepository.findOneByUserIdAndType(userId, CredentialsType.PASSWORD); + const credential = await this.credentialsRepository.findOneByUserIdAndType(userId, CredentialsType.PASSWORD) if (!credential) { - throw new Error('Password credentials not found'); + throw new Error('Password credentials not found') } - const { password } = data; - return this.tokenService.verifyHashedToken(credential.secret_data, password); + const { password } = data + return this.tokenService.verifyHashedToken(credential.secret_data, password) } -} \ No newline at end of file +} diff --git a/src/lib/server/auth-utils.ts b/src/lib/server/auth-utils.ts index 949b0d2..d0d6693 100644 --- a/src/lib/server/auth-utils.ts +++ b/src/lib/server/auth-utils.ts @@ -1,19 +1,19 @@ -import { generateIdFromEntropySize, type Session, type User } from 'lucia'; -import { TimeSpan, createDate } from 'oslo'; -import { eq } from 'drizzle-orm'; -import { db } from './api/infrastructure/database/index'; -import { password_reset_tokens } from './api/infrastructure/database/tables'; +import { eq } from 'drizzle-orm' +import { type Session, type User, generateIdFromEntropySize } from 'lucia' +import { TimeSpan, createDate } from 'oslo' +import { password_reset_tokens } from './api/databases/tables' +import { db } from './api/packages/drizzle' export async function createPasswordResetToken(userId: string): Promise { // optionally invalidate all existing tokens - await db.delete(password_reset_tokens).where(eq(password_reset_tokens.user_id, userId)); - const tokenId = generateIdFromEntropySize(40); + await db.delete(password_reset_tokens).where(eq(password_reset_tokens.user_id, userId)) + const tokenId = generateIdFromEntropySize(40) await db.insert(password_reset_tokens).values({ id: tokenId, user_id: userId, expires_at: createDate(new TimeSpan(2, 'h')), - }); - return tokenId; + }) + return tokenId } /** @@ -24,7 +24,7 @@ export async function createPasswordResetToken(userId: string): Promise * @returns True if the user is not fully authenticated, otherwise false. */ export function userNotFullyAuthenticated(user: User | null, session: Session | null) { - return user && session && session.isTwoFactorAuthEnabled && !session.isTwoFactorAuthenticated; + return user && session && session.isTwoFactorAuthEnabled && !session.isTwoFactorAuthenticated } /** @@ -35,7 +35,7 @@ export function userNotFullyAuthenticated(user: User | null, session: Session | * @returns {boolean} True if the user is not fully authenticated, otherwise false. */ export function userNotAuthenticated(user: User | null, session: Session | null) { - return !user || !session || userNotFullyAuthenticated(user, session); + return !user || !session || userNotFullyAuthenticated(user, session) } /** @@ -46,5 +46,5 @@ export function userNotAuthenticated(user: User | null, session: Session | null) * @returns {boolean} True if the user is fully authenticated, otherwise false. */ export function userFullyAuthenticated(user: User | null, session: Session | null) { - return !userNotAuthenticated(user, session); + return !userNotAuthenticated(user, session) } diff --git a/src/lib/utils/db/categoryUtils.ts b/src/lib/utils/db/categoryUtils.ts index c2317de..780685d 100644 --- a/src/lib/utils/db/categoryUtils.ts +++ b/src/lib/utils/db/categoryUtils.ts @@ -1,83 +1,77 @@ -import { error } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import kebabCase from 'just-kebab-case'; -import { PUBLIC_SITE_URL } from '$env/static/public'; -import db from '../../../db'; -import { - externalIds, - type Mechanics, - type Categories, - categories, - categoriesToExternalIds, -} from '$db/schema'; +import { PUBLIC_SITE_URL } from '$env/static/public' +import { type Categories, type Mechanics, categoriesTable, categoriesToExternalIdsTable, externalIds } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { error } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import kebabCase from 'just-kebab-case' export async function createCategory(locals: App.Locals, category: Categories, externalId: string) { if (!category || !externalId || externalId === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { const dbExternalId = await db.query.externalIds.findFirst({ where: eq(externalIds.externalId, externalId), - }); + }) if (dbExternalId) { const foundCategory = await db .select({ - id: categories.id, - name: categories.name, - slug: categories.slug, + id: categoriesTable.id, + name: categoriesTable.name, + slug: categoriesTable.slug, }) - .from(categories) - .leftJoin(categoriesToExternalIds, eq(categoriesToExternalIds.externalId, externalId)); - console.log('Mechanic already exists', foundCategory); + .from(categoriesTable) + .leftJoin(categoriesToExternalIdsTable, eq(categoriesToExternalIdsTable.externalId, externalId)) + console.log('Mechanic already exists', foundCategory) if (foundCategory.length > 0) { - console.log('Mechanic name', foundCategory[0].name); + console.log('Mechanic name', foundCategory[0].name) return new Response('Mechanic already exists', { headers: { 'Content-Type': 'application/json', Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundCategory[0].id}`, }, status: 409, - }); + }) } } - let dbCategory: Mechanics[] = []; - console.log('Creating category', JSON.stringify(category, null, 2)); + let dbCategory: Mechanics[] = [] + console.log('Creating category', JSON.stringify(category, null, 2)) await db.transaction(async (transaction) => { dbCategory = await transaction - .insert(categories) + .insert(categoriesTable) .values({ name: category.name, slug: kebabCase(category.name ?? category.slug ?? ''), }) - .returning(); + .returning() const dbExternalIds = await transaction .insert(externalIds) .values({ externalId, type: 'category', }) - .returning({ id: externalIds.id }); - await transaction.insert(categoriesToExternalIds).values({ + .returning({ id: externalIds.id }) + await transaction.insert(categoriesToExternalIdsTable).values({ categoryId: dbCategory[0].id, externalId: dbExternalIds[0].id, - }); - }); + }) + }) if (dbCategory.length === 0) { return new Response('Could not create category', { status: 500, - }); + }) } - console.log('Created category', JSON.stringify(dbCategory[0], null, 2)); + console.log('Created category', JSON.stringify(dbCategory[0], null, 2)) return new Response(JSON.stringify(dbCategory[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Category'); + console.error(e) + throw new Error('Something went wrong creating Category') } } diff --git a/src/lib/utils/db/expansionUtils.ts b/src/lib/utils/db/expansionUtils.ts index a4c9cd1..d6ff8db 100644 --- a/src/lib/utils/db/expansionUtils.ts +++ b/src/lib/utils/db/expansionUtils.ts @@ -1,60 +1,57 @@ -import { error } from '@sveltejs/kit'; -import { and, eq } from 'drizzle-orm'; -import db from '../../../db'; -import { type Expansions, expansions } from '$db/schema'; -import { PUBLIC_SITE_URL } from '$env/static/public'; +import { PUBLIC_SITE_URL } from '$env/static/public' +import { type Expansions, expansions } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { error } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' export async function createExpansion(locals: App.Locals, expansion: Expansions) { if (!expansion || expansion?.base_game_id === '' || expansion?.game_id === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { const foundExpansion = await db.query.expansions.findFirst({ - where: and( - eq(expansions.base_game_id, expansion.base_game_id), - eq(expansions.game_id, expansion.game_id), - ), + where: and(eq(expansions.base_game_id, expansion.base_game_id), eq(expansions.game_id, expansion.game_id)), columns: { id: true, game_id: true, base_game_id: true, }, - }); - console.log('Expansion already exists', foundExpansion); + }) + console.log('Expansion already exists', foundExpansion) if (foundExpansion) { - console.log('Expansion Game ID', foundExpansion.game_id); + console.log('Expansion Game ID', foundExpansion.game_id) return new Response('Expansion already exists', { headers: { 'Content-Type': 'application/json', Location: `${PUBLIC_SITE_URL}/api/game/${foundExpansion.game_id}`, }, status: 409, - }); + }) } - console.log('Creating expansion', JSON.stringify(expansion, null, 2)); + console.log('Creating expansion', JSON.stringify(expansion, null, 2)) const dbExpansion = await db .insert(expansions) .values({ base_game_id: expansion.base_game_id, game_id: expansion.game_id, }) - .returning(); + .returning() if (dbExpansion.length === 0) { return new Response('Could not create expansion', { status: 500, - }); + }) } - console.log('Created expansion', JSON.stringify(dbExpansion[0], null, 2)); + console.log('Created expansion', JSON.stringify(dbExpansion[0], null, 2)) return new Response(JSON.stringify(dbExpansion[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Expansion'); + console.error(e) + throw new Error('Something went wrong creating Expansion') } } diff --git a/src/lib/utils/db/gameUtils.ts b/src/lib/utils/db/gameUtils.ts index e96e4ed..b8a375b 100644 --- a/src/lib/utils/db/gameUtils.ts +++ b/src/lib/utils/db/gameUtils.ts @@ -1,36 +1,36 @@ -import kebabCase from 'just-kebab-case'; -import db from '../../../db'; -import { externalIds, gamesToExternalIds, type Games, games } from '$db/schema'; -import { eq } from 'drizzle-orm'; -import { error } from '@sveltejs/kit'; -import { PUBLIC_SITE_URL } from '$env/static/public'; +import { PUBLIC_SITE_URL } from '$env/static/public' +import { type Games, externalIds, games, gamesToExternalIds } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { error } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import kebabCase from 'just-kebab-case' export async function getGame(locals: App.Locals, id: string) { if (!id || id === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { return await db.query.games.findFirst({ where: eq(games.id, id), - }); + }) } catch (e) { - console.error(e); + console.error(e) return new Response('Could not get games', { status: 500, - }); + }) } } export async function createGame(locals: App.Locals, game: Games, externalId: string) { if (!game || !externalId || externalId === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { const dbExternalId = await db.query.externalIds.findFirst({ where: eq(externalIds.externalId, externalId), - }); + }) if (dbExternalId) { const foundGame = await db @@ -40,22 +40,22 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st slug: games.slug, }) .from(games) - .leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId)); - console.log('Game already exists', foundGame); + .leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId)) + console.log('Game already exists', foundGame) if (foundGame.length > 0) { - console.log('Game name', foundGame[0].name); + console.log('Game name', foundGame[0].name) return new Response('Game already exists', { headers: { 'Content-Type': 'application/json', Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}`, }, status: 409, - }); + }) } } - let dbGames: Games[] = []; - console.log('Creating game', JSON.stringify(game, null, 2)); + let dbGames: Games[] = [] + console.log('Creating game', JSON.stringify(game, null, 2)) await db.transaction(async (transaction) => { dbGames = await transaction .insert(games) @@ -73,50 +73,46 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st min_playtime: game.min_playtime, max_playtime: game.max_playtime, }) - .returning(); + .returning() const dbExternalIds = await transaction .insert(externalIds) .values({ externalId, type: 'game', }) - .returning({ id: externalIds.id }); + .returning({ id: externalIds.id }) await transaction.insert(gamesToExternalIds).values({ gameId: dbGames[0].id, externalId: dbExternalIds[0].id, - }); - }); + }) + }) if (dbGames.length === 0) { return new Response('Could not create game', { status: 500, - }); + }) } - console.log('Created game', JSON.stringify(dbGames[0], null, 2)); + console.log('Created game', JSON.stringify(dbGames[0], null, 2)) return new Response(JSON.stringify(dbGames[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Game'); + console.error(e) + throw new Error('Something went wrong creating Game') } } -export async function createOrUpdateGameMinimal( - locals: App.Locals, - game: Games, - externalId: string, -) { +export async function createOrUpdateGameMinimal(locals: App.Locals, game: Games, externalId: string) { if (!game || !externalId || externalId === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } - console.log('Creating or updating minimal game data', JSON.stringify(game, null, 2)); - const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}`; + console.log('Creating or updating minimal game data', JSON.stringify(game, null, 2)) + const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}` try { - let dbGames: Games[] = []; - console.log('Creating game', JSON.stringify(game, null, 2)); + let dbGames: Games[] = [] + console.log('Creating game', JSON.stringify(game, null, 2)) await db.transaction(async (transaction) => { dbGames = await transaction .insert(games) @@ -151,7 +147,7 @@ export async function createOrUpdateGameMinimal( max_playtime: game.max_playtime, }, }) - .returning(); + .returning() const dbExternalIds = await transaction .insert(externalIds) .values({ @@ -159,42 +155,42 @@ export async function createOrUpdateGameMinimal( type: 'game', }) .onConflictDoNothing() - .returning({ id: externalIds.id }); + .returning({ id: externalIds.id }) await transaction .insert(gamesToExternalIds) .values({ gameId: dbGames[0].id, externalId: dbExternalIds[0].id, }) - .onConflictDoNothing(); - }); + .onConflictDoNothing() + }) if (dbGames.length === 0) { return new Response('Could not create game', { status: 500, - }); + }) } - console.log('Created game', JSON.stringify(dbGames[0], null, 2)); + console.log('Created game', JSON.stringify(dbGames[0], null, 2)) return new Response(JSON.stringify(dbGames[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Game'); + console.error(e) + throw new Error('Something went wrong creating Game') } } export async function createOrUpdateGame(locals: App.Locals, game: Games, externalId: string) { if (!game || !externalId || externalId === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { - const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}`; + const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}` const dbExternalId = await db.query.externalIds.findFirst({ where: eq(externalIds.externalId, externalId), - }); + }) if (dbExternalId) { const foundGame = await db @@ -204,22 +200,22 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern slug: games.slug, }) .from(games) - .leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId)); - console.log('Game already exists', foundGame); + .leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId)) + console.log('Game already exists', foundGame) if (foundGame.length > 0) { - console.log('Game name', foundGame[0].name); + console.log('Game name', foundGame[0].name) return new Response('Game already exists', { headers: { 'Content-Type': 'application/json', Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}`, }, status: 409, - }); + }) } } - let dbGames: Games[] = []; - console.log('Creating game', JSON.stringify(game, null, 2)); + let dbGames: Games[] = [] + console.log('Creating game', JSON.stringify(game, null, 2)) await db.transaction(async (transaction) => { dbGames = await transaction .insert(games) @@ -254,7 +250,7 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern max_playtime: game.max_playtime, }, }) - .returning(); + .returning() const dbExternalIds = await transaction .insert(externalIds) .values({ @@ -262,35 +258,35 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern type: 'game', }) .onConflictDoNothing() - .returning({ id: externalIds.id }); + .returning({ id: externalIds.id }) await transaction .insert(gamesToExternalIds) .values({ gameId: dbGames[0].id, externalId: dbExternalIds[0].id, }) - .onConflictDoNothing(); - }); + .onConflictDoNothing() + }) if (dbGames.length === 0) { return new Response('Could not create game', { status: 500, - }); + }) } - console.log('Created game', JSON.stringify(dbGames[0], null, 2)); + console.log('Created game', JSON.stringify(dbGames[0], null, 2)) return new Response(JSON.stringify(dbGames[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Game'); + console.error(e) + throw new Error('Something went wrong creating Game') } } export async function updateGame(locals: App.Locals, game: Games, id: string) { if (!game || !id || id === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { @@ -311,17 +307,17 @@ export async function updateGame(locals: App.Locals, game: Games, id: string) { max_playtime: game.max_playtime, }) .where(eq(games.id, id)) - .returning(); + .returning() return new Response(JSON.stringify(dbGame[0]), { headers: { 'Content-Type': 'application/json', }, - }); + }) } catch (e) { - console.error(e); + console.error(e) return new Response('Could not get publishers', { status: 500, - }); + }) } } diff --git a/src/lib/utils/db/mechanicUtils.ts b/src/lib/utils/db/mechanicUtils.ts index 393db1a..4d963c0 100644 --- a/src/lib/utils/db/mechanicUtils.ts +++ b/src/lib/utils/db/mechanicUtils.ts @@ -1,19 +1,19 @@ -import kebabCase from 'just-kebab-case'; -import db from '../../../db'; -import { externalIds, mechanics, mechanicsToExternalIds, type Mechanics } from '$db/schema'; -import { eq } from 'drizzle-orm'; -import { error } from '@sveltejs/kit'; -import { PUBLIC_SITE_URL } from '$env/static/public'; +import { PUBLIC_SITE_URL } from '$env/static/public' +import { type Mechanics, externalIds, mechanics, mechanicsToExternalIds } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { error } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import kebabCase from 'just-kebab-case' export async function createMechanic(locals: App.Locals, mechanic: Mechanics, externalId: string) { if (!mechanic || !externalId || externalId === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { const dbExternalId = await db.query.externalIds.findFirst({ where: eq(externalIds.externalId, externalId), - }); + }) if (dbExternalId) { const foundMechanic = await db @@ -23,22 +23,22 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex slug: mechanics.slug, }) .from(mechanics) - .leftJoin(mechanicsToExternalIds, eq(mechanicsToExternalIds.externalId, externalId)); - console.log('Mechanic already exists', foundMechanic); + .leftJoin(mechanicsToExternalIds, eq(mechanicsToExternalIds.externalId, externalId)) + console.log('Mechanic already exists', foundMechanic) if (foundMechanic.length > 0) { - console.log('Mechanic name', foundMechanic[0].name); + console.log('Mechanic name', foundMechanic[0].name) return new Response('Mechanic already exists', { headers: { 'Content-Type': 'application/json', Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundMechanic[0].id}`, }, status: 409, - }); + }) } } - let dbMechanics: Mechanics[] = []; - console.log('Creating mechanic', JSON.stringify(mechanic, null, 2)); + let dbMechanics: Mechanics[] = [] + console.log('Creating mechanic', JSON.stringify(mechanic, null, 2)) await db.transaction(async (transaction) => { dbMechanics = await transaction .insert(mechanics) @@ -46,32 +46,32 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex name: mechanic.name, slug: kebabCase(mechanic.name || mechanic.slug || ''), }) - .returning(); + .returning() const dbExternalIds = await transaction .insert(externalIds) .values({ externalId, type: 'mechanic', }) - .returning({ id: externalIds.id }); + .returning({ id: externalIds.id }) await transaction.insert(mechanicsToExternalIds).values({ mechanicId: dbMechanics[0].id, externalId: dbExternalIds[0].id, - }); - }); + }) + }) if (dbMechanics.length === 0) { return new Response('Could not create mechanic', { status: 500, - }); + }) } - console.log('Created mechanic', JSON.stringify(dbMechanics[0], null, 2)); + console.log('Created mechanic', JSON.stringify(dbMechanics[0], null, 2)) return new Response(JSON.stringify(dbMechanics[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Mechanic'); + console.error(e) + throw new Error('Something went wrong creating Mechanic') } } diff --git a/src/lib/utils/db/publisherUtils.ts b/src/lib/utils/db/publisherUtils.ts index edef0ef..a66fe4e 100644 --- a/src/lib/utils/db/publisherUtils.ts +++ b/src/lib/utils/db/publisherUtils.ts @@ -1,25 +1,25 @@ -import { error } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import kebabCase from 'just-kebab-case'; -import db from '../../../db'; -import { externalIds, publishersToExternalIds, type Publishers, publishers } from '$db/schema'; -import { PUBLIC_SITE_URL } from '$env/static/public'; +import { PUBLIC_SITE_URL } from '$env/static/public' +import { type Publishers, externalIds, publishers, publishersToExternalIds } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { error } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import kebabCase from 'just-kebab-case' export async function getPublisher(locals: App.Locals, id: string) { - const publisher = await db.select().from(publishers).where(eq(publishers.id, id)); + const publisher = await db.select().from(publishers).where(eq(publishers.id, id)) if (publisher.length === 0) { - error(404, 'not found'); + error(404, 'not found') } return new Response(JSON.stringify(publisher[0]), { headers: { 'Content-Type': 'application/json', }, - }); + }) } export async function updatePublisher(locals: App.Locals, publisher: Publishers, id: string) { if (!publisher || publisher.name === '' || !id || id === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { @@ -30,33 +30,29 @@ export async function updatePublisher(locals: App.Locals, publisher: Publishers, slug: kebabCase(publisher.name || ''), }) .where(eq(publishers.id, id)) - .returning(); + .returning() return new Response(JSON.stringify(dbPublisher[0]), { headers: { 'Content-Type': 'application/json', }, - }); + }) } catch (e) { - console.error(e); + console.error(e) return new Response('Could not get publishers', { status: 500, - }); + }) } } -export async function createPublisher( - locals: App.Locals, - publisher: Publishers, - externalId: string, -) { +export async function createPublisher(locals: App.Locals, publisher: Publishers, externalId: string) { if (!publisher || !externalId || externalId === '') { - error(400, 'Invalid Request'); + error(400, 'Invalid Request') } try { const dbExternalId = await db.query.externalIds.findFirst({ where: eq(externalIds.externalId, externalId), - }); + }) if (dbExternalId) { const foundPublisher = await db @@ -66,22 +62,22 @@ export async function createPublisher( slug: publishers.slug, }) .from(publishers) - .leftJoin(publishersToExternalIds, eq(publishersToExternalIds.externalId, externalId)); - console.log('Publisher already exists', foundPublisher); + .leftJoin(publishersToExternalIds, eq(publishersToExternalIds.externalId, externalId)) + console.log('Publisher already exists', foundPublisher) if (foundPublisher.length > 0) { - console.log('Publisher name', foundPublisher[0].name); + console.log('Publisher name', foundPublisher[0].name) return new Response('Publisher already exists', { headers: { 'Content-Type': 'application/json', Location: `${PUBLIC_SITE_URL}/api/publisher/${foundPublisher[0].id}`, }, status: 409, - }); + }) } } - let dbPublishers: Publishers[] = []; - console.log('Creating publisher', JSON.stringify(publisher, null, 2)); + let dbPublishers: Publishers[] = [] + console.log('Creating publisher', JSON.stringify(publisher, null, 2)) await db.transaction(async (transaction) => { dbPublishers = await transaction .insert(publishers) @@ -89,32 +85,32 @@ export async function createPublisher( name: publisher.name, slug: kebabCase(publisher.name || publisher.slug || ''), }) - .returning(); + .returning() const dbExternalIds = await transaction .insert(externalIds) .values({ externalId, type: 'publisher', }) - .returning({ id: externalIds.id }); + .returning({ id: externalIds.id }) await transaction.insert(publishersToExternalIds).values({ publisherId: dbPublishers[0].id, externalId: dbExternalIds[0].id, - }); - }); + }) + }) if (dbPublishers.length === 0) { return new Response('Could not create publisher', { status: 500, - }); + }) } - console.log('Created publisher', JSON.stringify(dbPublishers[0], null, 2)); + console.log('Created publisher', JSON.stringify(dbPublishers[0], null, 2)) return new Response(JSON.stringify(dbPublishers[0]), { status: 201, - }); + }) } catch (e) { - console.error(e); - throw new Error('Something went wrong creating Publisher'); + console.error(e) + throw new Error('Something went wrong creating Publisher') } } diff --git a/src/routes/(app)/(protected)/admin/+layout.server.ts b/src/routes/(app)/(protected)/admin/+layout.server.ts index 5cb8b59..9d42e4e 100644 --- a/src/routes/(app)/(protected)/admin/+layout.server.ts +++ b/src/routes/(app)/(protected)/admin/+layout.server.ts @@ -1,19 +1,19 @@ -import { redirect, loadFlash } from 'sveltekit-flash-message/server'; -import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'; -import { eq } from 'drizzle-orm'; -import db from '../../../../db'; -import { userRoles } from '$db/schema'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; +import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages' +import { user_roles } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { eq } from 'drizzle-orm' +import { loadFlash, redirect } from 'sveltekit-flash-message/server' export const load = loadFlash(async (event) => { - const { locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); + const { locals } = event + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - const dbUserRoles = await db.query.userRoles.findMany({ - where: eq(userRoles.user_id, user!.id!), + const dbUserRoles = await db.query.user_roles.findMany({ + where: eq(user_roles.user_id, authedUser.id), with: { role: { columns: { @@ -21,13 +21,13 @@ export const load = loadFlash(async (event) => { }, }, }, - }); + }) - const containsAdminRole = dbUserRoles.some((userRole) => userRole?.role?.name === 'admin'); + const containsAdminRole = dbUserRoles.some((userRole) => userRole?.role?.name === 'admin') if (!dbUserRoles?.length || !containsAdminRole) { - console.log('Not an admin'); - redirect(302, '/', forbiddenMessage, event); + console.log('Not an admin') + redirect(302, '/', forbiddenMessage, event) } - return {}; -}); + return {} +}) diff --git a/src/routes/(app)/(protected)/admin/users/+page.server.ts b/src/routes/(app)/(protected)/admin/users/+page.server.ts index b6e7d5a..065f749 100644 --- a/src/routes/(app)/(protected)/admin/users/+page.server.ts +++ b/src/routes/(app)/(protected)/admin/users/+page.server.ts @@ -1,22 +1,21 @@ -import { redirect } from 'sveltekit-flash-message/server'; -import type { PageServerLoad } from './$types'; -import db from '../../../../../db'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; -import { notSignedInMessage } from '$lib/flashMessages'; +import { notSignedInMessage } from '$lib/flashMessages' +import { redirect } from 'sveltekit-flash-message/server' +import type { PageServerLoad } from './$types' export const load: PageServerLoad = async (event) => { - const { locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); + const { locals } = event + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - const users = await db.query.users.findMany({ - limit: 10, - offset: 0, - }); + // const users = await db.query.users.findMany({ + // limit: 10, + // offset: 0, + // }); return { - users, - }; -}; + // users, + } +} diff --git a/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts b/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts index 5d18bf0..85bdbd7 100644 --- a/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts +++ b/src/routes/(app)/(protected)/admin/users/[id]/+page.server.ts @@ -1,17 +1,17 @@ -import { and, eq, inArray, not } from 'drizzle-orm'; -import { redirect } from 'sveltekit-flash-message/server'; -import type { PageServerLoad } from './$types'; -import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'; -import db from '../../../../../../db'; -import { roles, userRoles, usersTable } from '$db/schema'; +import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages' +import { roles, user_roles, usersTable } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { and, eq, inArray, not } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import type { PageServerLoad } from './$types' export const load: PageServerLoad = async (event) => { - const { params } = event; - const { id } = params; + const { params, locals } = event + const { id } = params - // TODO: Ensure admin user - if (!event.locals.user) { - redirect(302, '/login', notSignedInMessage, event); + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } const foundUser = await db.query.usersTable.findFirst({ @@ -28,18 +28,16 @@ export const load: PageServerLoad = async (event) => { }, }, }, - }); + }) - const containsAdminRole = foundUser?.user_roles?.some( - (user_role) => user_role?.role?.name === 'admin', - ); + const containsAdminRole = foundUser?.user_roles?.some((user_role) => user_role?.role?.name === 'admin') if (!containsAdminRole) { - console.log('Not an admin'); - redirect(302, '/login', notSignedInMessage, event); + console.log('Not an admin') + redirect(302, '/login', notSignedInMessage, event) } - const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || []; - let availableRoles: { name: string; cuid: string }[] = []; + const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || [] + let availableRoles: { name: string; cuid: string }[] = [] if (currentRoleIds?.length > 0) { availableRoles = await db.query.roles.findMany({ where: not(inArray(roles.cuid, currentRoleIds)), @@ -47,26 +45,26 @@ export const load: PageServerLoad = async (event) => { name: true, cuid: true, }, - }); + }) } return { user: foundUser, availableRoles, - }; -}; + } +} export const actions = { addRole: async (event) => { - const { request, locals } = event; - const { user } = locals; + const { request, locals } = event + const { user } = locals if (!user) { - redirect(302, '/login', notSignedInMessage, event); + redirect(302, '/login', notSignedInMessage, event) } - const userRolesList = await db.query.userRoles.findMany({ - where: eq(userRoles.user_id, user.id), + const userRolesList = await db.query.user_roles.findMany({ + where: eq(user_roles.user_id, user.id), with: { role: { columns: { @@ -75,41 +73,41 @@ export const actions = { }, }, }, - }); + }) - console.log('userRoles', userRolesList); + console.log('userRoles', userRolesList) - const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin'); - console.log('containsAdminRole', containsAdminRole); + const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin') + console.log('containsAdminRole', containsAdminRole) if (!containsAdminRole) { - redirect(302, '/', forbiddenMessage, event); + redirect(302, '/', forbiddenMessage, event) } - const data = await request.formData(); - const role = data.get('role'); + const data = await request.formData() + const role = data.get('role') const dbRole = await db.query.roles.findFirst({ where: eq(roles.cuid, role?.toString() ?? ''), - }); - console.log('dbRole', dbRole); + }) + console.log('dbRole', dbRole) if (dbRole) { - await db.insert(userRoles).values({ + await db.insert(user_roles).values({ user_id: user.id, role_id: dbRole.id, - }); - redirect({ type: 'success', message: `Successfully added role ${dbRole.name}!` }, event); + }) + redirect({ type: 'success', message: `Successfully added role ${dbRole.name}!` }, event) } else { - redirect({ type: 'error', message: `Failed to add role ${dbRole.name}!` }, event); + redirect({ type: 'error', message: `Failed to add role ${role?.toString()}!` }, event) } }, removeRole: async (event) => { - const { request, locals } = event; - const { user } = locals; + const { request, locals } = event + const { user } = locals if (!user) { - redirect(302, '/login', notSignedInMessage, event); + redirect(302, '/login', notSignedInMessage, event) } - const userRolesList = await db.query.userRoles.findMany({ - where: eq(userRoles.user_id, user.id), + const userRolesList = await db.query.user_roles.findMany({ + where: eq(user_roles.user_id, user.id), with: { role: { columns: { @@ -118,26 +116,24 @@ export const actions = { }, }, }, - }); + }) - const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin'); + const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin') if (!containsAdminRole) { - redirect(302, '/', forbiddenMessage, event); + redirect(302, '/', forbiddenMessage, event) } - const data = await request.formData(); - const role = data.get('role'); + const data = await request.formData() + const role = data.get('role') const dbRole = await db.query.roles.findFirst({ where: eq(roles.cuid, role?.toString() ?? ''), - }); - console.log('dbRole', dbRole); + }) + console.log('dbRole', dbRole) if (dbRole) { - await db - .delete(userRoles) - .where(and(eq(userRoles.user_id, user.id), eq(userRoles.role_id, dbRole.id))); - redirect({ type: 'success', message: `Successfully removed role ${dbRole.name}!` }, event); + await db.delete(user_roles).where(and(eq(user_roles.user_id, user.id), eq(user_roles.role_id, dbRole.id))) + redirect({ type: 'success', message: `Successfully removed role ${dbRole.name}!` }, event) } else { - redirect({ type: 'error', message: `Failed to remove role ${role?.toString()} !` }, event); + redirect({ type: 'error', message: `Failed to remove role ${role?.toString()} !` }, event) } }, -}; +} diff --git a/src/routes/(app)/(protected)/collections/+page.server.ts b/src/routes/(app)/(protected)/collections/+page.server.ts index 07e63b5..55bad0b 100644 --- a/src/routes/(app)/(protected)/collections/+page.server.ts +++ b/src/routes/(app)/(protected)/collections/+page.server.ts @@ -1,18 +1,18 @@ -import { type Actions, error, fail } from '@sveltejs/kit'; -import { and, eq } from 'drizzle-orm'; -import { superValidate } from 'sveltekit-superforms/server'; -import { zod } from 'sveltekit-superforms/adapters'; -import { redirect } from 'sveltekit-flash-message/server'; -import { modifyListGameSchema } from '$lib/validations/zod-schemas'; -import { db } from '$lib/server/api/infrastructure/database'; -import { collection_items, collections, games } from '$lib/server/api/infrastructure/database/tables'; -import { notSignedInMessage } from '$lib/flashMessages'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; +import { notSignedInMessage } from '$lib/flashMessages' +import { db } from '$lib/server/api/packages/drizzle' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import { modifyListGameSchema } from '$lib/validations/zod-schemas' +import { type Actions, error, fail } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { superValidate } from 'sveltekit-superforms/server' +import { collection_items, collections, games } from '../../../../lib/server/api/databases/tables' export async function load(event) { - const { user, session } = event.locals; + const { user, session } = event.locals if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); + redirect(302, '/login', notSignedInMessage, event) } try { @@ -23,39 +23,39 @@ export async function load(event) { created_at: true, }, where: eq(collections.user_id, user!.id!), - }); - console.log('collections', userCollections); + }) + console.log('collections', userCollections) if (userCollections?.length === 0) { - console.log('Collection was not found'); - return fail(404, {}); + console.log('Collection was not found') + return fail(404, {}) } return { collections: userCollections, - }; + } } catch (e) { - console.error(e); + console.error(e) } return { collections: [], - }; + } } export const actions: Actions = { // Add game to a wishlist add: async (event) => { - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) if (!event.locals.user) { - throw fail(401); + throw fail(401) } - const user = event.locals.user; + const user = event.locals.user const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { // game = await prisma.game.create({ @@ -63,91 +63,84 @@ export const actions: Actions = { // name: form.name // } // }); - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } try { const collection = await db.query.collections.findFirst({ where: eq(collections.user_id, user.id), - }); + }) if (!collection) { - console.log('Wishlist not found'); - return error(404, 'Wishlist not found'); + console.log('Wishlist not found') + return error(404, 'Wishlist not found') } await db.insert(collection_items).values({ game_id: game.id, collection_id: collection.id, times_played: 0, - }); + }) return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, // Create new wishlist create: async ({ locals }) => { if (!locals.user) { - throw fail(401); + throw fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Delete a wishlist delete: async ({ locals }) => { if (!locals.user) { - throw fail(401); + throw fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Remove game from a wishlist remove: async (event) => { - const { locals } = event; - const form = await superValidate(event, zod(modifyListGameSchema)); + const { locals } = event + const form = await superValidate(event, zod(modifyListGameSchema)) if (!locals.user) { - throw fail(401); + throw fail(401) } const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } try { const collection = await db.query.collections.findFirst({ where: eq(collections.user_id, locals.user.id), - }); + }) if (!collection) { - console.log('Collection not found'); - return error(404, 'Collection not found'); + console.log('Collection not found') + return error(404, 'Collection not found') } - await db - .delete(collection_items) - .where( - and( - eq(collection_items.collection_id, collection.id), - eq(collection_items.game_id, game.id), - ), - ); + await db.delete(collection_items).where(and(eq(collection_items.collection_id, collection.id), eq(collection_items.game_id, game.id))) return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, -}; +} diff --git a/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts b/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts index c8e3e45..fff5ac2 100644 --- a/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts +++ b/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts @@ -1,48 +1,50 @@ -import { type Actions, error, fail } from '@sveltejs/kit'; -import { and, eq } from 'drizzle-orm'; -import { zod } from 'sveltekit-superforms/adapters'; -import { superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { modifyListGameSchema } from '$lib/validations/zod-schemas'; -import { db } from '$lib/server/api/infrastructure/database'; -import { notSignedInMessage } from '$lib/flashMessages.js'; -import { collections, games, collection_items } from '$lib/server/api/infrastructure/database/tables'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; +import { notSignedInMessage } from '$lib/flashMessages.js' +import { db } from '$lib/server/api/packages/drizzle' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import { modifyListGameSchema } from '$lib/validations/zod-schemas' +import { type Actions, error, fail } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { superValidate } from 'sveltekit-superforms/server' +import { collection_items, collections, games } from '../../../../../lib/server/api/databases/tables' export async function load(event) { - const { params, locals } = event; - const { cuid } = params; + const { params, locals } = event + const { cuid } = params - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } try { - const { data, errors } = await locals.api.collections[':cuid'].$get({ - param: { cuid } - }).then(locals.parseApiResponse); + const { data, errors } = await locals.api.collections[':cuid'] + .$get({ + param: { cuid }, + }) + .then(locals.parseApiResponse) if (errors) { - return error(500, 'Failed to fetch collection'); + return error(500, 'Failed to fetch collection') } - const { collection } = data; + const { collection } = data if (!collection) { - redirect(302, '/404'); + redirect(302, '/404') } - console.log('collection', collection); + console.log('collection', collection) return { collection, - }; + } } catch (e) { - console.error(e); + console.error(e) } - redirect(302, '/404'); + redirect(302, '/404') // const searchParams = Object.fromEntries(url?.searchParams); // console.log('searchParams', searchParams); @@ -128,17 +130,17 @@ export async function load(event) { export const actions: Actions = { // Add game to a wishlist add: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { // game = await prisma.game.create({ @@ -146,95 +148,88 @@ export const actions: Actions = { // name: form.name // } // }); - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } try { const collection = await db.query.collections.findFirst({ where: eq(collections.user_id, user!.id!), - }); + }) if (!collection) { - console.log('Wishlist not found'); - return error(404, 'Wishlist not found'); + console.log('Wishlist not found') + return error(404, 'Wishlist not found') } await db.insert(collection_items).values({ game_id: game.id, collection_id: collection.id, times_played: 0, - }); + }) return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, // Create new wishlist create: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Delete a wishlist delete: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Remove game from a wishlist remove: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } try { const collection = await db.query.collections.findFirst({ where: eq(collections.user_id, user!.id!), - }); + }) if (!collection) { - console.log('Collection not found'); - return error(404, 'Collection not found'); + console.log('Collection not found') + return error(404, 'Collection not found') } - await db - .delete(collection_items) - .where( - and( - eq(collection_items.collection_id, collection.id), - eq(collection_items.game_id, game.id), - ), - ); + await db.delete(collection_items).where(and(eq(collection_items.collection_id, collection.id), eq(collection_items.game_id, game.id))) return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, -}; +} diff --git a/src/routes/(app)/(protected)/list/+layout.server.ts b/src/routes/(app)/(protected)/list/+layout.server.ts index c9b2c17..30ca610 100644 --- a/src/routes/(app)/(protected)/list/+layout.server.ts +++ b/src/routes/(app)/(protected)/list/+layout.server.ts @@ -1,29 +1,29 @@ -import { redirect } from 'sveltekit-flash-message/server'; -import { eq } from 'drizzle-orm'; -import { db } from '$lib/server/api/infrastructure/database'; -import { wishlists } from '$lib/server/api/infrastructure/database/tables'; -import { notSignedInMessage } from '$lib/flashMessages'; +import { notSignedInMessage } from '$lib/flashMessages' +import { wishlists } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' export async function load(event) { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } try { const dbWishlists = await db.query.wishlists.findMany({ where: eq(wishlists.user_id, authedUser.id), - }); + }) return { wishlists: dbWishlists, - }; + } } catch (e) { - console.error(e); + console.error(e) } return { wishlists: [], - }; + } } diff --git a/src/routes/(app)/(protected)/list/[id]/+page.server.ts b/src/routes/(app)/(protected)/list/[id]/+page.server.ts index ff59f4d..147492d 100644 --- a/src/routes/(app)/(protected)/list/[id]/+page.server.ts +++ b/src/routes/(app)/(protected)/list/[id]/+page.server.ts @@ -1,19 +1,20 @@ -import { type Actions, fail } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import { zod } from 'sveltekit-superforms/adapters'; -import { superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import db from '../../../../../db'; -import { modifyListGameSchema } from '$lib/validations/zod-schemas'; -import { games, wishlist_items, wishlists } from '$db/schema'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; -import { notSignedInMessage } from '$lib/flashMessages'; +import { notSignedInMessage } from '$lib/flashMessages' +import { games, wishlist_items, wishlists } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import { modifyListGameSchema } from '$lib/validations/zod-schemas' +import { type Actions, fail } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { superValidate } from 'sveltekit-superforms/server' export async function load(event) { - const { params, locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); + const { params, locals } = event + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } try { @@ -30,97 +31,97 @@ export async function load(event) { .from(wishlists) .leftJoin(wishlist_items, eq(wishlists.id, wishlist_items.wishlist_id)) .leftJoin(games, eq(games.id, wishlist_items.game_id)) - .where(eq(wishlists.id, params.id)); + .where(eq(wishlists.id, params.id)) return { wishlist, - }; + } } catch (e) { - console.error(e); - return {}; + console.error(e) + return {} } } export const actions: Actions = { // Add game to a wishlist add: async (event) => { - const { params, locals } = event; - const { user, session } = locals; + const { params, locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) if (!locals.user) { - throw fail(401); + throw fail(401) } if (!params?.id) { throw fail(400, { message: 'Invalid Request', - }); + }) } const game = await db.query.games.findFirst({ where: eq(games.id, form.id), - }); + }) if (!game) { return fail(400, { message: 'Game not found', - }); + }) } const wishlist = await db.query.wishlists.findFirst({ where: eq(wishlists.id, params.id), - }); + }) if (wishlist?.user_id !== locals.user.id) { return fail(401, { message: 'Unauthorized', - }); + }) } if (!wishlist) { - redirect(302, '/404'); + redirect(302, '/404') } const wishlistItem = await db.insert(wishlist_items).values({ game_id: game.id, wishlist_id: wishlist.id, - }); + }) if (!wishlistItem) { return fail(500, { message: 'Something went wrong', - }); + }) } return { form, - }; + } }, // Create new wishlist create: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } }, // Delete a wishlist delete: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } }, // Remove game from a wishlist remove: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } }, -}; +} diff --git a/src/routes/(app)/(protected)/profile/+page.server.ts b/src/routes/(app)/(protected)/profile/+page.server.ts index 0c732cd..15e4e46 100644 --- a/src/routes/(app)/(protected)/profile/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/+page.server.ts @@ -1,138 +1,129 @@ -import { fail, type Actions } from '@sveltejs/kit'; -import { z } from 'zod'; -import { eq } from 'drizzle-orm'; -import { zod } from 'sveltekit-superforms/adapters'; -import { message, setError, superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { changeEmailSchema, profileSchema } from '$lib/validations/account'; -import { notSignedInMessage } from '$lib/flashMessages'; -import { db } from '$lib/server/api/infrastructure/database'; -import type { PageServerLoad } from './$types'; -import { usersTable, credentialsTable } from '$lib/server/api/infrastructure/database/tables'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; -import { updateProfileDto } from "$lib/dtos/update-profile.dto"; -import { updateEmailDto } from "$lib/dtos/update-email.dto"; +import { updateEmailDto } from '$lib/dtos/update-email.dto' +import { updateProfileDto } from '$lib/dtos/update-profile.dto' +import { notSignedInMessage } from '$lib/flashMessages' +import { usersTable } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { changeEmailSchema, profileSchema } from '$lib/validations/account' +import { type Actions, fail } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { message, setError, superValidate } from 'sveltekit-superforms/server' +import { z } from 'zod' +import type { PageServerLoad } from './$types' export const load: PageServerLoad = async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); - if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); - } + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) + } - console.log('authedUser', authedUser); - // if (userNotAuthenticated(user, session)) { - // redirect(302, '/login', notSignedInMessage, event); - // } - // const dbUser = await db.query.usersTable.findFirst({ - // where: eq(usersTable.id, user!.id!), - // }); + console.log('authedUser', authedUser) + // if (userNotAuthenticated(user, session)) { + // redirect(302, '/login', notSignedInMessage, event); + // } + // const dbUser = await db.query.usersTable.findFirst({ + // where: eq(usersTable.id, user!.id!), + // }); - const profileForm = await superValidate(zod(profileSchema), { - defaults: { - firstName: authedUser?.firstName ?? '', - lastName: authedUser?.lastName ?? '', - username: authedUser?.username ?? '', - }, - }); - const emailForm = await superValidate(zod(changeEmailSchema), { - defaults: { - email: authedUser?.email ?? '', - }, - }); + const profileForm = await superValidate(zod(profileSchema), { + defaults: { + firstName: authedUser?.firstName ?? '', + lastName: authedUser?.lastName ?? '', + username: authedUser?.username ?? '', + }, + }) + const emailForm = await superValidate(zod(changeEmailSchema), { + defaults: { + email: authedUser?.email ?? '', + }, + }) - // const twoFactorDetails = await db.query.twoFactor.findFirst({ - // where: eq(twoFactor.userId, authedUser!.id!), - // }); + // const twoFactorDetails = await db.query.twoFactor.findFirst({ + // where: eq(twoFactor.userId, authedUser!.id!), + // }); - return { - profileForm, - emailForm, - hasSetupTwoFactor: false //!!twoFactorDetails?.enabled, - }; -}; + return { + profileForm, + emailForm, + hasSetupTwoFactor: false, //!!twoFactorDetails?.enabled, + } +} const changeEmailIfNotEmpty = z.object({ - email: z - .string() - .trim() - .max(64, { message: 'Email must be less than 64 characters' }) - .email({ message: 'Please enter a valid email' }), -}); + email: z.string().trim().max(64, { message: 'Email must be less than 64 characters' }).email({ message: 'Please enter a valid email' }), +}) export const actions: Actions = { - profileUpdate: async (event) => { - const { locals } = event; + profileUpdate: async (event) => { + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() - if (!authedUser) { - redirect(302, '/login', notSignedInMessage, event); - } + if (!authedUser) { + redirect(302, '/login', notSignedInMessage, event) + } - const form = await superValidate(event, zod(updateProfileDto)); + const form = await superValidate(event, zod(updateProfileDto)) - const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parseApiResponse); - console.log('data from profile update', error); - if (error) { - return setError(form, 'username', error); - } + const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parseApiResponse) + console.log('data from profile update', error) + if (error) { + return setError(form, 'username', error) + } - if (!form.valid) { - return fail(400, { - form, - }); - } + if (!form.valid) { + return fail(400, { + form, + }) + } - console.log('profile updated successfully'); - return message(form, { type: 'success', message: 'Profile updated successfully!' }); - }, - changeEmail: async (event) => { - const form = await superValidate(event, zod(updateEmailDto)); + console.log('profile updated successfully') + return message(form, { type: 'success', message: 'Profile updated successfully!' }) + }, + changeEmail: async (event) => { + const form = await superValidate(event, zod(updateEmailDto)) - const newEmail = form.data?.email; - if ( - !form.valid || - !newEmail || - (newEmail !== '' && !changeEmailIfNotEmpty.safeParse(form.data).success) - ) { - return fail(400, { - form, - }); - } + const newEmail = form.data?.email + if (!form.valid || !newEmail || (newEmail !== '' && !changeEmailIfNotEmpty.safeParse(form.data).success)) { + return fail(400, { + form, + }) + } - if (!event.locals.user) { - redirect(302, '/login', notSignedInMessage, event); - } + if (!event.locals.user) { + redirect(302, '/login', notSignedInMessage, event) + } - const user = event.locals.user; - const existingUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.email, newEmail), - }); + const user = event.locals.user + const existingUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.email, newEmail), + }) - if (existingUser && existingUser.id !== user.id) { - return setError(form, 'email', 'That email is already taken'); - } + if (existingUser && existingUser.id !== user.id) { + return setError(form, 'email', 'That email is already taken') + } - await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id)); + await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id)) - // if (user.email !== form.data.email) { - // Send email to confirm new email? - // auth.update - // await locals.prisma.key.update({ - // where: { - // id: 'emailpassword:' + user.email - // }, - // data: { - // id: 'emailpassword:' + form.data.email - // } - // }); - // auth.updateUserAttributes(user.user_id, { - // receiveEmail: false - // }); - // } + // if (user.email !== form.data.email) { + // Send email to confirm new email? + // auth.update + // await locals.prisma.key.update({ + // where: { + // id: 'emailpassword:' + user.email + // }, + // data: { + // id: 'emailpassword:' + form.data.email + // } + // }); + // auth.updateUserAttributes(user.user_id, { + // receiveEmail: false + // }); + // } - return message(form, { type: 'success', message: 'Email updated successfully!' }); - }, -}; + return message(form, { type: 'success', message: 'Email updated successfully!' }) + }, +} diff --git a/src/routes/(app)/(protected)/profile/+page.svelte b/src/routes/(app)/(protected)/profile/+page.svelte index c6bc370..7434d86 100644 --- a/src/routes/(app)/(protected)/profile/+page.svelte +++ b/src/routes/(app)/(protected)/profile/+page.svelte @@ -1,42 +1,49 @@ diff --git a/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts b/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts index cbb2dc9..59cf933 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts @@ -1,22 +1,22 @@ -import { type Actions, fail, error } from '@sveltejs/kit' +import { StatusCodes } from '$lib/constants/status-codes' +import { notSignedInMessage } from '$lib/flashMessages' +import { db } from '$lib/server/api/packages/drizzle' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account' +import env from '$src/env' +import { type Actions, error, fail } from '@sveltejs/kit' import { eq } from 'drizzle-orm' -import { encodeHex, decodeHex } from 'oslo/encoding' -import { Argon2id } from 'oslo/password' -import { createTOTPKeyURI, TOTPController } from 'oslo/otp' -import { HMAC } from 'oslo/crypto' import kebabCase from 'just-kebab-case' +import { HMAC } from 'oslo/crypto' +import { decodeHex, encodeHex } from 'oslo/encoding' +import { TOTPController, createTOTPKeyURI } from 'oslo/otp' +import { Argon2id } from 'oslo/password' import QRCode from 'qrcode' +import { redirect, setFlash } from 'sveltekit-flash-message/server' import { zod } from 'sveltekit-superforms/adapters' import { setError, superValidate } from 'sveltekit-superforms/server' -import { redirect, setFlash } from 'sveltekit-flash-message/server' import type { PageServerLoad } from '../../$types' -import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account' -import { notSignedInMessage } from '$lib/flashMessages' -import { db } from '$lib/server/api/infrastructure/database' -import { recoveryCodesTable, credentialsTable, usersTable, type Credentials } from '$lib/server/api/infrastructure/database/tables' -import { userNotAuthenticated } from '$lib/server/auth-utils' -import env from '$src/env' -import { StatusCodes } from '$lib/constants/status-codes' +import { type Credentials, credentialsTable, recoveryCodesTable, usersTable } from '../../../../../../lib/server/api/databases/tables' export const load: PageServerLoad = async (event) => { const { locals } = event @@ -102,9 +102,11 @@ export const actions: Actions = { }) } - const { error: verifyPasswordError } = await locals.api.me.verify.password.$post({ - json: { password: addTwoFactorForm.data.current_password }, - }).then(locals.parseApiResponse) + const { error: verifyPasswordError } = await locals.api.me.verify.password + .$post({ + json: { password: addTwoFactorForm.data.current_password }, + }) + .then(locals.parseApiResponse) if (verifyPasswordError) { console.log(verifyPasswordError) diff --git a/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts b/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts index 84ceece..663f788 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts @@ -1,18 +1,18 @@ -import { eq } from 'drizzle-orm'; -import { Argon2id } from 'oslo/password'; -import { alphabet, generateRandomString } from 'oslo/crypto'; -import { redirect } from 'sveltekit-flash-message/server'; -import { db } from '$lib/server/api/infrastructure/database'; -import { notSignedInMessage } from '$lib/flashMessages'; -import type { PageServerLoad } from '../../../$types'; -import { recoveryCodesTable } from '$lib/server/api/infrastructure/database/tables'; +import { notSignedInMessage } from '$lib/flashMessages' +import { db } from '$lib/server/api/packages/drizzle' +import { eq } from 'drizzle-orm' +import { alphabet, generateRandomString } from 'oslo/crypto' +import { Argon2id } from 'oslo/password' +import { redirect } from 'sveltekit-flash-message/server' +import type { PageServerLoad } from '../../../$types' +import { recoveryCodesTable } from '../../../../../../../lib/server/api/databases/tables' export const load: PageServerLoad = async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } if (authedUser.mfa_enabled) { @@ -42,4 +42,4 @@ export const load: PageServerLoad = async (event) => { } console.error('2FA not enabled') redirect(302, '/profile', { message: 'Two-Factor Authentication is not enabled', type: 'error' }, event) -}; +} diff --git a/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts b/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts index ed158f6..c2b9f75 100644 --- a/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/password/change/+page.server.ts @@ -1,65 +1,65 @@ -import { fail, type Actions } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import { zod } from 'sveltekit-superforms/adapters'; -import { setError, superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { Argon2id } from 'oslo/password'; -import type { PageServerLoad } from '../../../$types'; -import { db } from '$lib/server/api/infrastructure/database'; -import { changeUserPasswordSchema } from '$lib/validations/account'; -import { usersTable } from '$lib/server/api/infrastructure/database/tables'; -import { notSignedInMessage } from '$lib/flashMessages'; -import type { Cookie } from 'lucia'; +import { notSignedInMessage } from '$lib/flashMessages' +import { db } from '$lib/server/api/packages/drizzle' +import { changeUserPasswordSchema } from '$lib/validations/account' +import { type Actions, fail } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import type { Cookie } from 'lucia' +import { Argon2id } from 'oslo/password' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { setError, superValidate } from 'sveltekit-superforms/server' +import type { PageServerLoad } from '../../../$types' +import { usersTable } from '../../../../../../../lib/server/api/databases/tables' export const load: PageServerLoad = async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } - const form = await superValidate(event, zod(changeUserPasswordSchema)); + const form = await superValidate(event, zod(changeUserPasswordSchema)) form.data = { current_password: '', password: '', confirm_password: '', - }; + } return { form, - }; -}; + } +} export const actions: Actions = { default: async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } - const form = await superValidate(event, zod(changeUserPasswordSchema)); + const form = await superValidate(event, zod(changeUserPasswordSchema)) if (!form.valid) { return fail(400, { form, - }); + }) } - console.log('updating profile'); + console.log('updating profile') if (!event.locals.user) { - redirect(302, '/login', notSignedInMessage, event); + redirect(302, '/login', notSignedInMessage, event) } if (!event.locals.session) { - return fail(401); + return fail(401) } const dbUser = await db.query.usersTable.findFirst({ where: eq(usersTable.id, authedUser.id), - }); + }) // if (!dbUser?.hashed_password) { // form.data.password = ''; @@ -74,53 +74,50 @@ export const actions: Actions = { const currentPasswordVerified = await new Argon2id().verify( // dbUser.hashed_password, form.data.current_password, - ); + ) if (!currentPasswordVerified) { - return setError(form, 'current_password', 'Your password is incorrect'); + return setError(form, 'current_password', 'Your password is incorrect') } if (authedUser?.username) { - let sessionCookie: Cookie; + let sessionCookie: Cookie try { if (form.data.password !== form.data.confirm_password) { - return setError(form, 'Password and confirm password do not match'); + return setError(form, 'Password and confirm password do not match') } - const hashedPassword = await new Argon2id().hash(form.data.password); - await lucia.invalidateUserSessions(authedUser.id); + const hashedPassword = await new Argon2id().hash(form.data.password) + await lucia.invalidateUserSessions(authedUser.id) // await db // .update(usersTable) // .set({ hashed_password: hashedPassword }) // .where(eq(usersTable.id, user.id)); await lucia.createSession(user.id, { country: event.locals.session?.ipCountry ?? 'unknown', - }); - sessionCookie = lucia.createBlankSessionCookie(); + }) + sessionCookie = lucia.createBlankSessionCookie() } catch (e) { - console.error(e); - form.data.password = ''; - form.data.confirm_password = ''; - form.data.current_password = ''; - return setError(form, 'current_password', 'Your password is incorrect.'); + console.error(e) + form.data.password = '' + form.data.confirm_password = '' + form.data.current_password = '' + return setError(form, 'current_password', 'Your password is incorrect.') } event.cookies.set(sessionCookie.name, sessionCookie.value, { path: '.', ...sessionCookie.attributes, - }); + }) const message = { type: 'success', message: 'Password Updated. Please sign in.', - } as const; - redirect(302, '/login', message, event); + } as const + redirect(302, '/login', message, event) } - return setError( - form, - 'Error occurred. Please try again or contact support if you need further help.', - ); + return setError(form, 'Error occurred. Please try again or contact support if you need further help.') // TODO: Add toast instead? // form.data.password = ''; // form.data.confirm_password = ''; // form.data.current_password = ''; // return message(form, 'Profile updated successfully.'); }, -}; +} diff --git a/src/routes/(app)/(protected)/wishlists/+page.server.ts b/src/routes/(app)/(protected)/wishlists/+page.server.ts index 9c60379..db80d2c 100644 --- a/src/routes/(app)/(protected)/wishlists/+page.server.ts +++ b/src/routes/(app)/(protected)/wishlists/+page.server.ts @@ -1,55 +1,57 @@ -import { fail, error, type Actions } from '@sveltejs/kit'; -import { and, eq } from 'drizzle-orm'; -import { zod } from 'sveltekit-superforms/adapters'; -import { superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { modifyListGameSchema } from '$lib/validations/zod-schemas'; -import db from '../../../../db'; -import { notSignedInMessage } from '$lib/flashMessages.js'; -import { games, wishlist_items, wishlists } from '$db/schema'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; +import { notSignedInMessage } from '$lib/flashMessages.js' +import { games, wishlist_items, wishlists } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import { modifyListGameSchema } from '$lib/validations/zod-schemas' +import { type Actions, error, fail } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { superValidate } from 'sveltekit-superforms/server' export async function load(event) { - const { locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - redirect(302, '/login', notSignedInMessage, event); + const { locals } = event + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } const userWishlists = await db.query.wishlists.findMany({ columns: { cuid: true, name: true, - created_at: true, + createdAt: true, }, - where: eq(wishlists.user_id, user!.id!), - }); - console.log('wishlists', userWishlists); + where: eq(wishlists.user_id, authedUser.id), + }) + console.log('wishlists', userWishlists) if (userWishlists?.length === 0) { - console.log('Wishlists not found'); - return fail(404, {}); + console.log('Wishlists not found') + return fail(404, {}) } return { wishlists: userWishlists, - }; + } } export const actions: Actions = { // Add game to a wishlist add: async (event) => { - const { locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - return fail(401); + const { locals } = event + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) try { const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { // game = await prisma.game.create({ @@ -57,65 +59,66 @@ export const actions: Actions = { // name: form.name // } // }); - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } if (game) { const wishlist = await db.query.wishlists.findFirst({ - where: eq(wishlists.user_id, user!.id!), - }); + where: eq(wishlists.user_id, authedUser.id), + }) if (!wishlist) { - console.log('Wishlist not found'); - return error(404, 'Wishlist not found'); + console.log('Wishlist not found') + return error(404, 'Wishlist not found') } await db.insert(wishlist_items).values({ game_id: game.id, wishlist_id: wishlist.id, - }); + }) } return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, // Create new wishlist create: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Delete a wishlist delete: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Remove game from a wishlist remove: async (event) => { - const { locals } = event; - const { user, session } = locals; - if (userNotAuthenticated(user, session)) { - return fail(401); + const { locals } = event + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) try { const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { // game = await prisma.game.create({ @@ -123,33 +126,29 @@ export const actions: Actions = { // name: form.name // } // }); - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } if (game) { const wishlist = await db.query.wishlists.findFirst({ - where: eq(wishlists.user_id, user!.id!), - }); + where: eq(wishlists.user_id, authedUser.id), + }) if (!wishlist) { - console.log('Wishlist not found'); - return error(404, 'Wishlist not found'); + console.log('Wishlist not found') + return error(404, 'Wishlist not found') } - await db - .delete(wishlist_items) - .where( - and(eq(wishlist_items.wishlist_id, wishlist.id), eq(wishlist_items.game_id, game.id)), - ); + await db.delete(wishlist_items).where(and(eq(wishlist_items.wishlist_id, wishlist.id), eq(wishlist_items.game_id, game.id))) } return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, -}; +} diff --git a/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts b/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts index 1e60741..5093585 100644 --- a/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts +++ b/src/routes/(app)/(protected)/wishlists/[cuid]/+page.server.ts @@ -1,64 +1,66 @@ -import { error, type Actions, fail } from '@sveltejs/kit'; -import { and, eq } from 'drizzle-orm'; -import { zod } from 'sveltekit-superforms/adapters'; -import { superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { modifyListGameSchema } from '$lib/validations/zod-schemas'; -import { db } from '$lib/server/api/infrastructure/database'; -import { notSignedInMessage } from '$lib/flashMessages.js'; -import { games, wishlist_items, wishlists } from '$lib/server/api/infrastructure/database/tables'; -import { userNotAuthenticated } from '$lib/server/auth-utils'; +import { notSignedInMessage } from '$lib/flashMessages.js' +import { db } from '$lib/server/api/packages/drizzle' +import { userNotAuthenticated } from '$lib/server/auth-utils' +import { modifyListGameSchema } from '$lib/validations/zod-schemas' +import { type Actions, error, fail } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { superValidate } from 'sveltekit-superforms/server' +import { games, wishlist_items, wishlists } from '../../../../../lib/server/api/databases/tables' export async function load(event) { - const { params, locals } = event; - const { cuid } = params; + const { params, locals } = event + const { cuid } = params - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event); + throw redirect(302, '/login', notSignedInMessage, event) } try { - const { data, errors } = await locals.api.wishlists[':cuid'].$get({ - param: { cuid } - }).then(locals.parseApiResponse); + const { data, errors } = await locals.api.wishlists[':cuid'] + .$get({ + param: { cuid }, + }) + .then(locals.parseApiResponse) // const wishlist = await db.query.wishlists.findMany({ // where: and(eq(wishlists.user_id, authedUser.id), eq(wishlists.cuid, cuid)), // }); if (errors) { - return error(500, 'Failed to fetch wishlist'); + return error(500, 'Failed to fetch wishlist') } - const { wishlist } = data; + const { wishlist } = data if (!wishlist) { - redirect(302, '/404'); + redirect(302, '/404') } - console.log('wishlist', wishlist); + console.log('wishlist', wishlist) return { wishlist, - }; + } } catch (e) { - console.error(e); + console.error(e) } - redirect(302, '/404'); + redirect(302, '/404') } export const actions: Actions = { // Add game to a wishlist add: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) try { const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { // game = await prisma.game.create({ @@ -66,65 +68,65 @@ export const actions: Actions = { // name: form.name // } // }); - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } if (game) { const wishlist = await db.query.wishlists.findFirst({ where: eq(wishlists.user_id, user!.id!), - }); + }) if (!wishlist) { - console.log('Wishlist not found'); - return error(404, 'Wishlist not found'); + console.log('Wishlist not found') + return error(404, 'Wishlist not found') } await db.insert(wishlist_items).values({ game_id: game.id, wishlist_id: wishlist.id, - }); + }) } return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, // Create new wishlist create: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Delete a wishlist delete: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - return error(405, 'Method not allowed'); + return error(405, 'Method not allowed') }, // Remove game from a wishlist remove: async (event) => { - const { locals } = event; - const { user, session } = locals; + const { locals } = event + const { user, session } = locals if (userNotAuthenticated(user, session)) { - return fail(401); + return fail(401) } - const form = await superValidate(event, zod(modifyListGameSchema)); + const form = await superValidate(event, zod(modifyListGameSchema)) try { const game = await db.query.games.findFirst({ where: eq(games.id, form.data.id), - }); + }) if (!game) { // game = await prisma.game.create({ @@ -132,33 +134,29 @@ export const actions: Actions = { // name: form.name // } // }); - console.log('game not found'); - redirect(302, '/404'); + console.log('game not found') + redirect(302, '/404') } if (game) { const wishlist = await db.query.wishlists.findFirst({ where: eq(wishlists.user_id, user!.id!), - }); + }) if (!wishlist) { - console.log('Wishlist not found'); - return error(404, 'Wishlist not found'); + console.log('Wishlist not found') + return error(404, 'Wishlist not found') } - await db - .delete(wishlist_items) - .where( - and(eq(wishlist_items.wishlist_id, wishlist.id), eq(wishlist_items.game_id, game.id)), - ); + await db.delete(wishlist_items).where(and(eq(wishlist_items.wishlist_id, wishlist.id), eq(wishlist_items.game_id, game.id))) } return { form, - }; + } } catch (e) { - console.error(e); - return error(500, 'Something went wrong'); + console.error(e) + return error(500, 'Something went wrong') } }, -}; +} diff --git a/src/routes/(app)/+page.server.ts b/src/routes/(app)/+page.server.ts index 2e54d8b..ad7d0cc 100644 --- a/src/routes/(app)/+page.server.ts +++ b/src/routes/(app)/+page.server.ts @@ -1,23 +1,21 @@ -import { fail } from '@sveltejs/kit'; -import type { MetaTagsProps } from 'svelte-meta-tags'; -import { eq } from 'drizzle-orm'; -import type { PageServerLoad } from './$types'; -import {db} from '$lib/server/api/infrastructure/database/index'; -import { collections, usersTable, wishlists } from '$lib/server/api/infrastructure/database/tables'; +import { db } from '$lib/server/api/packages/drizzle' +import { fail } from '@sveltejs/kit' +import { eq } from 'drizzle-orm' +import type { MetaTagsProps } from 'svelte-meta-tags' +import { collections, usersTable, wishlists } from '../../lib/server/api/databases/tables' +import type { PageServerLoad } from './$types' // import { userFullyAuthenticated } from '$lib/server/auth-utils'; export const load: PageServerLoad = async (event) => { - const { locals, url } = event; + const { locals, url } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() const image = { - url: `${ - new URL(url.pathname, url.origin).href - }og?header=Bored Game&page=Home&content=Keep track of your games`, + url: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`, width: 1200, height: 630, - }; + } const metaTags: MetaTagsProps = Object.freeze({ title: 'Home', description: 'Home page', @@ -36,23 +34,21 @@ export const load: PageServerLoad = async (event) => { cardType: 'summary_large_image', title: 'Home | Bored Game', description: 'Bored Game, keep track of your games', - image: `${ - new URL(url.pathname, url.origin).href - }og?header=Bored Game&page=Home&content=Keep track of your games`, + image: `${new URL(url.pathname, url.origin).href}og?header=Bored Game&page=Home&content=Keep track of your games`, imageAlt: 'Home | Bored Game', }, - }); + }) if (authedUser) { - const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse); - const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse); + const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse) + const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse) if (wishlistsError || collectionsError) { - return fail(500, 'Failed to fetch wishlists or collections'); + return fail(500, 'Failed to fetch wishlists or collections') } - console.log('Wishlists', wishlistsData.wishlists); - console.log('Collections', collectionsData.collections); + console.log('Wishlists', wishlistsData.wishlists) + console.log('Collections', collectionsData.collections) return { metaTagsChild: metaTags, user: { @@ -62,8 +58,8 @@ export const load: PageServerLoad = async (event) => { }, wishlists: wishlistsData.wishlists, collections: collectionsData.collections, - }; + } } - return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] }; -}; + return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] } +} diff --git a/src/routes/(app)/game/[id]/+page.server.ts b/src/routes/(app)/game/[id]/+page.server.ts index 7ac273c..8fd02b9 100644 --- a/src/routes/(app)/game/[id]/+page.server.ts +++ b/src/routes/(app)/game/[id]/+page.server.ts @@ -1,26 +1,19 @@ -import { error } from '@sveltejs/kit'; -import { mapAPIGameToBoredGame } from '$lib/utils/gameMapper.js'; -import type { PageServerLoad } from './$types'; -import { createCategory } from '$lib/utils/db/categoryUtils'; -import { createMechanic } from '$lib/utils/db/mechanicUtils'; -import { createPublisher } from '$lib/utils/db/publisherUtils'; -import { createExpansion } from '$lib/utils/db/expansionUtils'; -import { createOrUpdateGame } from '$lib/utils/db/gameUtils'; -import db from '../../../../db'; -import { and, eq } from 'drizzle-orm'; -import { - collection_items, - collections, - expansions, - games, - wishlist_items, - wishlists, -} from '$db/schema'; +import { collection_items, collections, expansions, games, wishlist_items, wishlists } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { createCategory } from '$lib/utils/db/categoryUtils' +import { createExpansion } from '$lib/utils/db/expansionUtils' +import { createOrUpdateGame } from '$lib/utils/db/gameUtils' +import { createMechanic } from '$lib/utils/db/mechanicUtils' +import { createPublisher } from '$lib/utils/db/publisherUtils' +import { mapAPIGameToBoredGame } from '$lib/utils/gameMapper.js' +import { error } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' +import type { PageServerLoad } from './$types' export const load: PageServerLoad = async ({ params, locals, fetch }) => { try { - const { user } = locals; - const { id } = params; + const { user } = locals + const { id } = params const game = await db.query.games.findFirst({ where: eq(games.id, id), with: { @@ -55,20 +48,17 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => { }, }, }, - }); - console.log('found game', game); + }) + console.log('found game', game) if (!game) { - error(404, 'not found'); + error(404, 'not found') } - const currentDate = new Date(); - if ( - game.last_sync_at === null || - currentDate.getDate() - game.last_sync_at.getDate() > 7 * 24 * 60 * 60 * 1000 - ) { - console.log('Syncing details because last sync is out of date'); - await syncGameAndConnectedData(locals, game, fetch); + const currentDate = new Date() + if (game.last_sync_at === null || currentDate.getDate() - game.last_sync_at.getDate() > 7 * 24 * 60 * 60 * 1000) { + console.log('Syncing details because last sync is out of date') + await syncGameAndConnectedData(locals, game, fetch) } const gameExpansions = await db.query.expansions.findMany({ @@ -82,42 +72,36 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => { }, }, }, - }); + }) - let collectionItem; - let wishlistItem; + let collectionItem + let wishlistItem if (user) { const wishlist = await db.query.wishlists.findFirst({ where: eq(wishlists.user_id, user.id), - }); + }) // TODO: Select wishlist items based on wishlist if (wishlist) { wishlistItem = await db.query.wishlist_items.findFirst({ - where: and( - eq(wishlist_items.wishlist_id, wishlist.id), - eq(wishlist_items.game_id, game.id), - ), - }); + where: and(eq(wishlist_items.wishlist_id, wishlist.id), eq(wishlist_items.game_id, game.id)), + }) } const collection = await db.query.collections.findFirst({ where: eq(collections.user_id, user.id), - }); + }) // TODO: Select collection items based on collection if (collection) { collectionItem = await db.query.collection_items.findFirst({ - where: and( - eq(collection_items.collection_id, collection.id), - eq(collection_items.game_id, game.id), - ), - }); + where: and(eq(collection_items.collection_id, collection.id), eq(collection_items.game_id, game.id)), + }) } } - console.log('Returning game', game); + console.log('Returning game', game) return { game, @@ -125,55 +109,53 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => { user, in_wishlist: wishlistItem !== undefined || false, in_collection: collectionItem !== undefined || false, - }; + } } catch (error) { - console.log(error); + console.log(error) } - error(404, 'not found'); -}; + error(404, 'not found') +} async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFetch: Function) { - console.log( - `Retrieving full external game details for external id: ${game.external_id} with name ${game.name}`, - ); - const externalGameResponse = await eventFetch(`/api/external/game/${game.external_id}`); + console.log(`Retrieving full external game details for external id: ${game.external_id} with name ${game.name}`) + const externalGameResponse = await eventFetch(`/api/external/game/${game.external_id}`) if (externalGameResponse.ok) { - const externalGame = await externalGameResponse.json(); - console.log('externalGame', externalGame); - const categories = []; - const mechanics = []; - const publishers = []; + const externalGame = await externalGameResponse.json() + console.log('externalGame', externalGame) + const categories = [] + const mechanics = [] + const publishers = [] for (const externalCategory of externalGame.categories) { - const category = await createCategory(locals, externalCategory, externalGame.external_id); + const category = await createCategory(locals, externalCategory, externalGame.external_id) categories.push({ id: category.id, - }); + }) } for (const externalMechanic of externalGame.mechanics) { - const mechanic = await createMechanic(locals, externalMechanic, externalGame.external_id); - mechanics.push({ id: mechanic.id }); + const mechanic = await createMechanic(locals, externalMechanic, externalGame.external_id) + mechanics.push({ id: mechanic.id }) } for (const externalPublisher of externalGame.publishers) { - const publisher = await createPublisher(locals, externalPublisher, externalGame.external_id); - publishers.push({ id: publisher.id }); + const publisher = await createPublisher(locals, externalPublisher, externalGame.external_id) + publishers.push({ id: publisher.id }) } for (const externalExpansion of externalGame.expansions) { - console.log('Inbound?', externalExpansion.inbound); + console.log('Inbound?', externalExpansion.inbound) if (externalExpansion?.inbound === true) { - createExpansion(locals, externalExpansion); + createExpansion(locals, externalExpansion) } else { - createExpansion(locals, externalExpansion); + createExpansion(locals, externalExpansion) } } - const boredGame = mapAPIGameToBoredGame(externalGame); + const boredGame = mapAPIGameToBoredGame(externalGame) - boredGame.categories = categories; - boredGame.mechanics = mechanics; - boredGame.publishers = publishers; + boredGame.categories = categories + boredGame.mechanics = mechanics + boredGame.publishers = publishers // boredGame.expansions = expansions; - return createOrUpdateGame(locals, boredGame, externalGame.external_id); + return createOrUpdateGame(locals, boredGame, externalGame.external_id) } } diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index b5f7757..ab0a9a1 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -1,14 +1,14 @@ -import { fail, type Actions } from '@sveltejs/kit' +import { signinUsernameDto } from '$lib/dtos/signin-username.dto' +import { db } from '$lib/server/api/packages/drizzle' +import { lucia } from '$lib/server/api/packages/lucia' +import { type Actions, fail } from '@sveltejs/kit' import { eq, or } from 'drizzle-orm' import { Argon2id } from 'oslo/password' +import { redirect } from 'sveltekit-flash-message/server' import { zod } from 'sveltekit-superforms/adapters' import { setError, superValidate } from 'sveltekit-superforms/server' -import { redirect } from 'sveltekit-flash-message/server' -import { db } from '../../../lib/server/api/infrastructure/database/index' -import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia' -import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables' +import { credentialsTable, usersTable } from '../../../lib/server/api/databases/tables' import type { PageServerLoad } from './$types' -import { signinUsernameDto } from '$lib/dtos/signin-username.dto' export const load: PageServerLoad = async (event) => { const { locals } = event diff --git a/src/routes/(auth)/signup/+page.server.ts b/src/routes/(auth)/signup/+page.server.ts index abc18b3..593388f 100644 --- a/src/routes/(auth)/signup/+page.server.ts +++ b/src/routes/(auth)/signup/+page.server.ts @@ -1,9 +1,9 @@ -import { fail, error, type Actions } from '@sveltejs/kit'; -import { zod } from 'sveltekit-superforms/adapters'; -import { setError, superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import type { PageServerLoad } from './$types'; -import {signupUsernameEmailDto} from "$lib/dtos/signup-username-email.dto"; +import { signupUsernameEmailDto } from '$lib/dtos/signup-username-email.dto' +import { type Actions, error, fail } from '@sveltejs/kit' +import { redirect } from 'sveltekit-flash-message/server' +import { zod } from 'sveltekit-superforms/adapters' +import { setError, superValidate } from 'sveltekit-superforms/server' +import type { PageServerLoad } from './$types' const signUpDefaults = { firstName: '', @@ -13,16 +13,16 @@ const signUpDefaults = { password: '', confirm_password: '', terms: true, -}; +} export const load: PageServerLoad = async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (authedUser) { - const message = { type: 'success', message: 'You are already signed in' } as const; - throw redirect('/', message, event); + const message = { type: 'success', message: 'You are already signed in' } as const + throw redirect('/', message, event) } // if (userFullyAuthenticated(user, session)) { @@ -45,31 +45,31 @@ export const load: PageServerLoad = async (event) => { form: await superValidate(zod(signupUsernameEmailDto), { defaults: signUpDefaults, }), - }; -}; + } +} export const actions: Actions = { default: async (event) => { - const { locals } = event; + const { locals } = event - const authedUser = await locals.getAuthedUser(); + const authedUser = await locals.getAuthedUser() if (authedUser) { - const message = { type: 'success', message: 'You are already signed in' } as const; - throw redirect('/', message, event); + const message = { type: 'success', message: 'You are already signed in' } as const + throw redirect('/', message, event) } - const form = await superValidate(event, zod(signupUsernameEmailDto)); + const form = await superValidate(event, zod(signupUsernameEmailDto)) - const { error } = await locals.api.signup.$post({ json: form.data }).then(locals.parseApiResponse); - if (error) return setError(form, 'username', error); + const { error } = await locals.api.signup.$post({ json: form.data }).then(locals.parseApiResponse) + if (error) return setError(form, 'username', error) if (!form.valid) { - form.data.password = ''; - form.data.confirm_password = ''; + form.data.password = '' + form.data.confirm_password = '' return fail(400, { form, - }); + }) } // let session; @@ -147,8 +147,8 @@ export const actions: Actions = { // ...sessionCookie.attributes, // }); - redirect(302, '/'); + redirect(302, '/') // const message = { type: 'success', message: 'Signed Up!' } as const; // throw flashRedirect(message, event); }, -}; +} diff --git a/src/routes/(auth)/signup/+page.svelte b/src/routes/(auth)/signup/+page.svelte index 54c563b..d0f2491 100644 --- a/src/routes/(auth)/signup/+page.svelte +++ b/src/routes/(auth)/signup/+page.svelte @@ -1,39 +1,39 @@ diff --git a/src/routes/(auth)/totp/+page.server.ts b/src/routes/(auth)/totp/+page.server.ts index 4e6f719..f6dc984 100644 --- a/src/routes/(auth)/totp/+page.server.ts +++ b/src/routes/(auth)/totp/+page.server.ts @@ -1,326 +1,297 @@ -import { fail, error, type Actions } from '@sveltejs/kit'; -import { and, eq } from 'drizzle-orm'; -import { Argon2id } from 'oslo/password'; -import { decodeHex } from 'oslo/encoding'; -import { TOTPController } from 'oslo/otp'; -import { zod } from 'sveltekit-superforms/adapters'; -import { setError, superValidate } from 'sveltekit-superforms/server'; -import { redirect } from 'sveltekit-flash-message/server'; -import { RateLimiter } from 'sveltekit-rate-limiter/server'; -import db from '../../../db'; -import { lucia } from '$lib/server/auth'; -import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth'; -import { usersTable, twoFactor, recoveryCodes } from '$db/schema'; -import type {PageServerLoad, RequestEvent} from './$types'; -import { notSignedInMessage } from '$lib/flashMessages'; -import env from '../../../env'; +import { notSignedInMessage } from '$lib/flashMessages' +import { recoveryCodesTable, twoFactorTable, usersTable } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { lucia } from '$lib/server/api/packages/lucia' +import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth' +import { type Actions, error, fail } from '@sveltejs/kit' +import { and, eq } from 'drizzle-orm' +import { decodeHex } from 'oslo/encoding' +import { TOTPController } from 'oslo/otp' +import { Argon2id } from 'oslo/password' +import { redirect } from 'sveltekit-flash-message/server' +import { RateLimiter } from 'sveltekit-rate-limiter/server' +import { zod } from 'sveltekit-superforms/adapters' +import { setError, superValidate } from 'sveltekit-superforms/server' +import env from '../../../env' +import type { PageServerLoad, RequestEvent } from './$types' export const load: PageServerLoad = async (event) => { - const { cookies, locals } = event; - const { user, session } = locals; + const { cookies, locals } = event - if (!user || !session) { - redirect(302, '/login', notSignedInMessage, event); + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - if (user && session) { - const dbUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.username, user.username), - }); + const dbUser = await db.query.usersTable.findFirst({ + where: eq(usersTable.username, authedUser.username), + }) - const twoFactorDetails = await db.query.twoFactor.findFirst({ - where: eq(twoFactor.userId, dbUser!.id!), - }); + const twoFactorDetails = await db.query.twoFactorTable.findFirst({ + where: eq(twoFactorTable.userId, authedUser.id), + }) - if (!twoFactorDetails || !twoFactorDetails.enabled) { - const message = { - type: 'error', - message: 'Two factor authentication is not enabled', - } as const; - redirect(302, '/login', message, event); - } - - let twoFactorInitiatedTime = twoFactorDetails.initiatedTime; - if (twoFactorInitiatedTime === null) { - console.log('twoFactorInitiatedTime is null'); - twoFactorInitiatedTime = new Date(); - console.log('twoFactorInitiatedTime', twoFactorInitiatedTime); - await db - .update(twoFactor) - .set({ initiatedTime: twoFactorInitiatedTime }) - .where(eq(twoFactor.userId, dbUser!.id!)); - } - - // Check if two factor started less than TWO_FACTOR_TIMEOUT - const totpElapsed = totpTimeElapsed(twoFactorInitiatedTime); - if (totpElapsed) { - console.log( - 'Time elapsed was more than TWO_FACTOR_TIMEOUT', - totpElapsed, - env.TWO_FACTOR_TIMEOUT, - ); - await lucia.invalidateSession(session!.id!); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - const message = { type: 'error', message: 'Two factor authentication has expired' } as const; - redirect(302, '/login', message, event); - } - - const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated; - - console.log('session', session); - console.log('isTwoFactorAuthenticated', isTwoFactorAuthenticated); - - if (isTwoFactorAuthenticated && twoFactorDetails?.enabled && twoFactorDetails?.secret !== '') { - const message = { type: 'success', message: 'You are already signed in' } as const; - throw redirect('/', message, event); - } + if (!twoFactorDetails || !twoFactorDetails.enabled) { + const message = { + type: 'error', + message: 'Two factor authentication is not enabled', + } as const + redirect(302, '/login', message, event) } + let twoFactorInitiatedTime = twoFactorDetails.initiatedTime + if (twoFactorInitiatedTime === null) { + console.log('twoFactorInitiatedTime is null') + twoFactorInitiatedTime = new Date() + console.log('twoFactorInitiatedTime', twoFactorInitiatedTime) + await db.update(twoFactorTable).set({ initiatedTime: twoFactorInitiatedTime }).where(eq(twoFactorTable.userId, dbUser!.id!)) + } + + // Check if two factor started less than TWO_FACTOR_TIMEOUT + // const totpElapsed = totpTimeElapsed(twoFactorInitiatedTime) + // if (totpElapsed) { + // console.log('Time elapsed was more than TWO_FACTOR_TIMEOUT', totpElapsed, env.TWO_FACTOR_TIMEOUT) + // await lucia.invalidateSession(session!.id!) + // const sessionCookie = lucia.createBlankSessionCookie() + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }) + // const message = { type: 'error', message: 'Two factor authentication has expired' } as const + // redirect(302, '/login', message, event) + // } + // + // const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated + // + // console.log('session', session) + // console.log('isTwoFactorAuthenticated', isTwoFactorAuthenticated) + + // if (isTwoFactorAuthenticated && twoFactorDetails?.enabled && twoFactorDetails?.secret !== '') { + // const message = { type: 'success', message: 'You are already signed in' } as const + // throw redirect('/', message, event) + // } + return { totpForm: await superValidate(event, zod(totpSchema)), recoveryCodeForm: await superValidate(event, zod(recoveryCodeSchema)), - }; -}; + } +} const limiter = new RateLimiter({ // A rate is defined by [number, unit] IPUA: [5, 'm'], -}); +}) export const actions: Actions = { validateTotp: async (event) => { - const { cookies, locals } = event; - const session = locals.session; - const user = locals.user; + const { cookies, locals } = event - if (await limiter.isLimited(event)) { - throw error(429); + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - if (!user || !session) { - throw fail(401); - } + const { dbUser, twoFactorDetails } = await validateUserData(event, locals) - const { dbUser, twoFactorDetails } = await validateUserData(event, locals); - - const totpForm = await superValidate(event, zod(totpSchema)); + const totpForm = await superValidate(event, zod(totpSchema)) if (!totpForm.valid) { - totpForm.data.totpToken = ''; - return fail(400, { totpForm }); + totpForm.data.totpToken = '' + return fail(400, { totpForm }) } - let sessionCookie; - const totpToken = totpForm?.data?.totpToken; - - const twoFactorSecretPopulated = - twoFactorDetails.secret !== '' && twoFactorDetails.secret !== null; - if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !totpToken) { - return fail(400, { totpForm }); - } else if (twoFactorSecretPopulated && totpToken) { - // Check if two factor started less than TWO_FACTOR_TIMEOUT - const totpElapsed = totpTimeElapsed(twoFactorDetails.initiatedTime ?? new Date()); - if (totpElapsed) { - await lucia.invalidateSession(session!.id!); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - const message = { - type: 'error', - message: 'Two factor authentication has expired', - } as const; - redirect(302, '/login', message, event); - } - - console.log('totpToken', totpToken); - const validOTP = await new TOTPController().verify( - totpToken, - decodeHex(twoFactorDetails.secret ?? ''), - ); - console.log('validOTP', validOTP); - - if (!validOTP) { - console.log('invalid TOTP code'); - totpForm.data.totpToken = ''; - return setError(totpForm, 'totpToken', 'Invalid code.'); - } - } - console.log('ip', locals.ip); - console.log('country', locals.country); - await lucia.invalidateSession(session.id); - const newSession = await lucia.createSession(dbUser.id, { - ip_country: locals.country, - ip_address: locals.ip, - twoFactorAuthEnabled: true, - isTwoFactorAuthenticated: true, - }); - console.log('logging in session', newSession); - sessionCookie = lucia.createSessionCookie(newSession.id); - console.log('logging in session cookie', sessionCookie); - - console.log('setting session cookie', sessionCookie); - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - - totpForm.data.totpToken = ''; - const message = { type: 'success', message: 'Signed In!' } as const; - redirect(302, '/', message, event); + // let sessionCookie + // const totpToken = totpForm?.data?.totpToken + // + // const twoFactorSecretPopulated = twoFactorDetails.secret !== '' && twoFactorDetails.secret !== null + // if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !totpToken) { + // return fail(400, { totpForm }) + // } else if (twoFactorSecretPopulated && totpToken) { + // // Check if two factor started less than TWO_FACTOR_TIMEOUT + // const totpElapsed = totpTimeElapsed(twoFactorDetails.initiatedTime ?? new Date()) + // if (totpElapsed) { + // await lucia.invalidateSession(session!.id!) + // const sessionCookie = lucia.createBlankSessionCookie() + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }) + // const message = { + // type: 'error', + // message: 'Two factor authentication has expired', + // } as const + // redirect(302, '/login', message, event) + // } + // + // console.log('totpToken', totpToken) + // const validOTP = await new TOTPController().verify(totpToken, decodeHex(twoFactorDetails.secret ?? '')) + // console.log('validOTP', validOTP) + // + // if (!validOTP) { + // console.log('invalid TOTP code') + // totpForm.data.totpToken = '' + // return setError(totpForm, 'totpToken', 'Invalid code.') + // } + // } + // console.log('ip', locals.ip) + // console.log('country', locals.country) + // await lucia.invalidateSession(session.id) + // const newSession = await lucia.createSession(dbUser.id, { + // ip_country: locals.country, + // ip_address: locals.ip, + // twoFactorAuthEnabled: true, + // isTwoFactorAuthenticated: true, + // }) + // console.log('logging in session', newSession) + // sessionCookie = lucia.createSessionCookie(newSession.id) + // console.log('logging in session cookie', sessionCookie) + // + // console.log('setting session cookie', sessionCookie) + // event.cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }) + // + // totpForm.data.totpToken = '' + // const message = { type: 'success', message: 'Signed In!' } as const + redirect(302, '/', message, event) }, validateRecoveryCode: async (event) => { - const { cookies, locals } = event; - const session = locals.session; - const user = locals.user; + const { cookies, locals } = event - if (await limiter.isLimited(event)) { - throw error(429); + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) } - if (!user || !session) { - throw fail(401); - } + const { dbUser, twoFactorDetails } = await validateUserData(event, locals) - const { dbUser, twoFactorDetails } = await validateUserData(event, locals); - - const recoveryCodeForm = await superValidate(event, zod(recoveryCodeSchema)); + const recoveryCodeForm = await superValidate(event, zod(recoveryCodeSchema)) if (!recoveryCodeForm.valid) { return fail(400, { form: recoveryCodeForm, - }); + }) } - let sessionCookie; - const recoveryCode = recoveryCodeForm?.data?.recoveryCode; + // let sessionCookie + // const recoveryCode = recoveryCodeForm?.data?.recoveryCode + // + // const twoFactorSecretPopulated = twoFactorDetails.secret !== '' && twoFactorDetails.secret !== null + // if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !recoveryCode) { + // return fail(400, { recoveryCodeForm }) + // } else if (twoFactorSecretPopulated && recoveryCode) { + // // Check if two factor started less than TWO_FACTOR_TIMEOUT + // const totpElapsed = totpTimeElapsed(twoFactorDetails.initiatedTime ?? new Date()) + // if (totpElapsed) { + // await lucia.invalidateSession(session!.id!) + // const sessionCookie = lucia.createBlankSessionCookie() + // cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }) + // const message = { + // type: 'error', + // message: 'Two factor authentication has expired', + // } as const + // redirect(302, '/login', message, event) + // } + // + // console.log('recoveryCode', recoveryCode) + // + // console.log('Check for recovery codes') + // const usedRecoveryCode = await checkRecoveryCode(recoveryCode, dbUser.id) + // if (!usedRecoveryCode) { + // console.log('invalid recovery code') + // recoveryCodeForm.data.recoveryCode = '' + // return setError(recoveryCodeForm, 'recoveryCode', 'Invalid code.') + // } + // } + // console.log('ip', locals.ip) + // console.log('country', locals.country) + // await lucia.invalidateSession(session.id) + // const newSession = await lucia.createSession(dbUser.id, { + // ip_country: locals.country, + // ip_address: locals.ip, + // twoFactorAuthEnabled: true, + // isTwoFactorAuthenticated: true, + // }) + // console.log('logging in session', newSession) + // sessionCookie = lucia.createSessionCookie(newSession.id) + // console.log('logging in session cookie', sessionCookie) + // + // console.log('setting session cookie', sessionCookie) + // event.cookies.set(sessionCookie.name, sessionCookie.value, { + // path: '.', + // ...sessionCookie.attributes, + // }) - const twoFactorSecretPopulated = - twoFactorDetails.secret !== '' && twoFactorDetails.secret !== null; - if (twoFactorDetails.enabled && !twoFactorSecretPopulated && !recoveryCode) { - return fail(400, { recoveryCodeForm }); - } else if (twoFactorSecretPopulated && recoveryCode) { - // Check if two factor started less than TWO_FACTOR_TIMEOUT - const totpElapsed = totpTimeElapsed(twoFactorDetails.initiatedTime ?? new Date()); - if (totpElapsed) { - await lucia.invalidateSession(session!.id!); - const sessionCookie = lucia.createBlankSessionCookie(); - cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - const message = { - type: 'error', - message: 'Two factor authentication has expired', - } as const; - redirect(302, '/login', message, event); - } - - console.log('recoveryCode', recoveryCode); - - console.log('Check for recovery codes'); - const usedRecoveryCode = await checkRecoveryCode(recoveryCode, dbUser.id); - if (!usedRecoveryCode) { - console.log('invalid recovery code'); - recoveryCodeForm.data.recoveryCode = ''; - return setError(recoveryCodeForm, 'recoveryCode', 'Invalid code.'); - } - } - console.log('ip', locals.ip); - console.log('country', locals.country); - await lucia.invalidateSession(session.id); - const newSession = await lucia.createSession(dbUser.id, { - ip_country: locals.country, - ip_address: locals.ip, - twoFactorAuthEnabled: true, - isTwoFactorAuthenticated: true, - }); - console.log('logging in session', newSession); - sessionCookie = lucia.createSessionCookie(newSession.id); - console.log('logging in session cookie', sessionCookie); - - console.log('setting session cookie', sessionCookie); - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }); - - recoveryCodeForm.data.recoveryCode = ''; - const message = { type: 'success', message: 'Signed In!' } as const; - redirect(302, '/', message, event); - } -}; + recoveryCodeForm.data.recoveryCode = '' + const message = { type: 'success', message: 'Signed In!' } as const + redirect(302, '/', message, event) + }, +} async function validateUserData(event: RequestEvent, locals: App.Locals) { - const { user, session } = locals; + const { user, session } = locals if (!user || !session) { - throw fail(401); + throw fail(401) } const dbUser = await db.query.usersTable.findFirst({ where: eq(usersTable.username, user.username), - }); + }) if (!dbUser) { - throw fail(401); + throw fail(401) } - const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated; - const twoFactorDetails = await db.query.twoFactor.findFirst({ - where: eq(twoFactor.userId, dbUser!.id!), - }); + const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated + const twoFactorDetails = await db.query.twoFactorTable.findFirst({ + where: eq(twoFactorTable.userId, dbUser!.id!), + }) if (!twoFactorDetails) { - const message = {type: 'error', message: 'Unable to process request'} as const; - throw redirect(302, '/login', message, event); + const message = { type: 'error', message: 'Unable to process request' } as const + throw redirect(302, '/login', message, event) } if (isTwoFactorAuthenticated && twoFactorDetails.enabled && twoFactorDetails.secret !== '') { - const message = {type: 'success', message: 'You are already signed in'} as const; - throw redirect('/', message, event); + const message = { type: 'success', message: 'You are already signed in' } as const + throw redirect('/', message, event) } - return { dbUser, twoFactorDetails }; + return { dbUser, twoFactorDetails } } - function totpTimeElapsed(initiatedTime: Date) { if (initiatedTime === null || initiatedTime === undefined) { - return true; + return true } - const timeElapsed = Date.now() - initiatedTime.getTime(); - console.log('Time elapsed', timeElapsed); + const timeElapsed = Date.now() - initiatedTime.getTime() + console.log('Time elapsed', timeElapsed) if (timeElapsed > env.TWO_FACTOR_TIMEOUT) { - console.log( - 'Time elapsed was more than TWO_FACTOR_TIMEOUT', - timeElapsed, - env.TWO_FACTOR_TIMEOUT, - ); - return true; + console.log('Time elapsed was more than TWO_FACTOR_TIMEOUT', timeElapsed, env.TWO_FACTOR_TIMEOUT) + return true } - return false; + return false } async function checkRecoveryCode(recoveryCode: string, userId: string) { - const userRecoveryCodes = await db.query.recoveryCodes.findMany({ - where: and(eq(recoveryCodes.used, false), eq(recoveryCodes.userId, userId)), - }); + const userRecoveryCodes = await db.query.recoveryCodesTable.findMany({ + where: and(eq(recoveryCodesTable.used, false), eq(recoveryCodesTable.userId, userId)), + }) for (const code of userRecoveryCodes) { - const validRecoveryCode = await new Argon2id().verify(code.code, recoveryCode); + const validRecoveryCode = await new Argon2id().verify(code.code, recoveryCode) if (validRecoveryCode) { await db - .update(recoveryCodes) + .update(recoveryCodesTable) .set({ used: true, }) - .where(eq(recoveryCodes.id, code.id)); - return true; + .where(eq(recoveryCodesTable.id, code.id)) + return true } } - return false; + return false } From 679f88d50d265d6f1f6e0e7d23ce285af10dc2e4 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 3 Sep 2024 17:22:27 -0700 Subject: [PATCH 34/34] Fixing totp mfa enable, disable, and recovery codes. --- package.json | 14 +- pnpm-lock.yaml | 750 +++++++++--------- src/lib/components/LeftNav.svelte | 77 ++ .../server/api/controllers/mfa.controller.ts | 21 +- .../repositories/credentials.repository.ts | 1 + .../repositories/recovery-codes.repository.ts | 27 + .../api/services/recovery-codes.service.ts | 38 + .../profile/security/+layout.server.ts | 3 + .../profile/security/+layout.svelte | 15 + .../profile/security/mfa/+page.server.ts | 76 +- .../profile/security/mfa/+page.svelte | 62 +- .../mfa/recovery-codes/+page.server.ts | 31 +- 12 files changed, 631 insertions(+), 484 deletions(-) create mode 100644 src/lib/components/LeftNav.svelte create mode 100644 src/lib/server/api/repositories/recovery-codes.repository.ts create mode 100644 src/lib/server/api/services/recovery-codes.service.ts create mode 100644 src/routes/(app)/(protected)/profile/security/+layout.server.ts create mode 100644 src/routes/(app)/(protected)/profile/security/+layout.svelte diff --git a/package.json b/package.json index 7fea7c8..9bc112e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@sveltejs/kit": "^2.5.25", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/cookie": "^0.6.0", - "@types/node": "^20.16.2", + "@types/node": "^20.16.3", "@types/pg": "^8.11.8", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", @@ -46,8 +46,8 @@ "just-debounce-it": "^3.2.0", "lucia": "3.2.0", "lucide-svelte": "^0.408.0", - "nodemailer": "^6.9.14", - "postcss": "^8.4.41", + "nodemailer": "^6.9.15", + "postcss": "^8.4.44", "postcss-import": "^16.1.0", "postcss-load-config": "^5.1.0", "postcss-preset-env": "^9.6.0", @@ -70,7 +70,7 @@ "tslib": "^2.7.0", "tsx": "^4.19.0", "typescript": "^5.5.4", - "vite": "^5.4.2", + "vite": "^5.4.3", "vitest": "^1.6.0", "zod": "^3.23.8" }, @@ -85,7 +85,7 @@ "@internationalized/date": "^3.5.5", "@lucia-auth/adapter-drizzle": "^1.1.0", "@lukeed/uuid": "^2.0.1", - "@neondatabase/serverless": "^0.9.4", + "@neondatabase/serverless": "^0.9.5", "@paralleldrive/cuid2": "^2.2.2", "@resvg/resvg-js": "^2.6.2", "@sveltejs/adapter-node": "^5.2.2", @@ -95,7 +95,7 @@ "arctic": "^1.9.2", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.12.12", + "bullmq": "^5.12.13", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -106,7 +106,7 @@ "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.5.9", + "hono": "^4.5.11", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd902d0..d6ffa34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: version: 5.0.14 '@hono/swagger-ui': specifier: ^0.4.1 - version: 0.4.1(hono@4.5.9) + version: 0.4.1(hono@4.5.11) '@hono/zod-openapi': specifier: ^0.15.3 - version: 0.15.3(hono@4.5.9)(zod@3.23.8) + version: 0.15.3(hono@4.5.11)(zod@3.23.8) '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.9)(zod@3.23.8) + version: 0.2.2(hono@4.5.11)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -31,13 +31,13 @@ importers: version: 3.5.5 '@lucia-auth/adapter-drizzle': specifier: ^1.1.0 - version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) + version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) '@lukeed/uuid': specifier: ^2.0.1 version: 2.0.1 '@neondatabase/serverless': - specifier: ^0.9.4 - version: 0.9.4 + specifier: ^0.9.5 + version: 0.9.5 '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 @@ -46,10 +46,10 @@ importers: version: 2.6.2 '@sveltejs/adapter-node': specifier: ^5.2.2 - version: 5.2.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) + version: 5.2.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))) '@sveltejs/adapter-vercel': specifier: ^5.4.3 - version: 5.4.3(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) + version: 5.4.3(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -66,8 +66,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.12.12 - version: 5.12.12 + specifier: ^5.12.13 + version: 5.12.13 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -85,25 +85,25 @@ importers: version: 11.0.6 drizzle-orm: specifier: ^0.32.2 - version: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) + version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.5.9 - version: 4.5.9 + specifier: ^4.5.11 + version: 4.5.11 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.9) + version: 0.4.0(hono@4.5.11) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -157,10 +157,10 @@ importers: version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -185,22 +185,22 @@ importers: version: 1.46.1 '@sveltejs/adapter-auto': specifier: ^3.2.4 - version: 3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) + version: 3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))) '@sveltejs/enhanced-img': specifier: ^0.3.4 - version: 0.3.4(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + version: 0.3.4(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) '@sveltejs/kit': specifier: ^2.5.25 - version: 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + version: 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.16.2 - version: 20.16.2 + specifier: ^20.16.3 + version: 20.16.3 '@types/pg': specifier: ^8.11.8 version: 8.11.8 @@ -212,7 +212,7 @@ importers: version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) autoprefixer: specifier: ^10.4.20 - version: 10.4.20(postcss@8.4.41) + version: 10.4.20(postcss@8.4.44) drizzle-kit: specifier: ^0.23.2 version: 0.23.2 @@ -224,7 +224,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: ^2.43.0 - version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) + version: 2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -238,20 +238,20 @@ importers: specifier: ^0.408.0 version: 0.408.0(svelte@5.0.0-next.175) nodemailer: - specifier: ^6.9.14 - version: 6.9.14 + specifier: ^6.9.15 + version: 6.9.15 postcss: - specifier: ^8.4.41 - version: 8.4.41 + specifier: ^8.4.44 + version: 8.4.44 postcss-import: specifier: ^16.1.0 - version: 16.1.0(postcss@8.4.41) + version: 16.1.0(postcss@8.4.44) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0) postcss-preset-env: specifier: ^9.6.0 - version: 9.6.0(postcss@8.4.41) + version: 9.6.0(postcss@8.4.44) prettier: specifier: ^3.3.3 version: 3.3.3 @@ -272,7 +272,7 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175) + version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0))(postcss@8.4.44)(sass@1.77.8)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) @@ -281,25 +281,25 @@ importers: version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0))(postcss@8.4.44)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))) + version: 0.5.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))) sveltekit-superforms: specifier: ^2.17.0 - version: 2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175) + version: 2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) + version: 3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) + version: 10.9.2(@types/node@20.16.3)(typescript@5.5.4) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -310,11 +310,11 @@ importers: specifier: ^5.5.4 version: 5.5.4 vite: - specifier: ^5.4.2 - version: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + specifier: ^5.4.3 + version: 5.4.3(@types/node@20.16.3)(sass@1.77.8) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.16.2)(sass@1.77.8) + version: 1.6.0(@types/node@20.16.3)(sass@1.77.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -1524,8 +1524,8 @@ packages: cpu: [x64] os: [win32] - '@neondatabase/serverless@0.9.4': - resolution: {integrity: sha512-D0AXgJh6xkf+XTlsO7iwE2Q1w8981E1cLCPAALMU2YKtkF/1SF6BiAzYARZFYo175ON+b1RNIy9TdSFHm5nteg==} + '@neondatabase/serverless@0.9.5': + resolution: {integrity: sha512-siFas6gItqv6wD/pZnvdu34wEqgG3nSE6zWZdq5j2DEsa+VvX8i/5HXJOo06qrw5axPXn+lGCxeR+NLaSPIXug==} '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} @@ -2114,8 +2114,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.16.2': - resolution: {integrity: sha512-91s/n4qUPV/wg8eE9KHYW1kouTfDk2FPGjXbBMfRWP/2vg1rCXNQL1OCabwGs0XSdukuK+MwCDXE30QpSeMUhQ==} + '@types/node@20.16.3': + resolution: {integrity: sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==} '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} @@ -2405,8 +2405,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.12.12: - resolution: {integrity: sha512-xrWKDj1ZwnGKmrlmFqF6Vmub3WqDFfdBcIRLCooIs5+jeVzbHK7/1usgYSFg2pZiwK6h6eMivTb9WvcKkNW/+w==} + bullmq@5.12.13: + resolution: {integrity: sha512-bFk0s1U9eQ8vKrhH9zYg/1H0+puSLVXuuq/pIW2jxgUmtLebRUBZr0cHJx35azTf2oPUJ+xXfpfHWaUtm4ZveA==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3181,8 +3181,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.9: - resolution: {integrity: sha512-zz8ktqMDRrZETjxBrv8C5PQRFbrTRCLNVAjD1SNQyOzv4VjmX68Uxw83xQ6oxdAB60HiWnGEatiKA8V3SZLDkQ==} + hono@4.5.11: + resolution: {integrity: sha512-62FcjLPtjAFwISVBUshryl+vbHOjg8rE4uIK/dxyR8GpLztunZpwFmfEvmJCUI7xoGh/Sr3CGCDPCmYxVw7wUQ==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -3615,8 +3615,8 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - nodemailer@6.9.14: - resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==} + nodemailer@6.9.15: + resolution: {integrity: sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==} engines: {node: '>=6.0.0'} nopt@5.0.0: @@ -4079,8 +4079,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.41: - resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + postcss@8.4.44: + resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -4844,8 +4844,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.2: - resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} + vite@5.4.3: + resolution: {integrity: sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5114,201 +5114,201 @@ snapshots: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.41)': + '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.44)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.1.0 - '@csstools/postcss-color-function@3.0.19(postcss@8.4.41)': + '@csstools/postcss-color-function@3.0.19(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.41)': + '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.41)': + '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.44)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.41)': + '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.44)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.41)': + '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.44)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.41)': + '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.41)': + '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.41)': + '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.41)': + '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.44)': dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@1.0.1(postcss@8.4.41)': + '@csstools/postcss-initial@1.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.41)': + '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.44)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.1.0 - '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.41)': + '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.44)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.41)': + '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.41)': + '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.41)': + '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.41)': + '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.41)': + '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.44)': dependencies: '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.41)': + '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.44)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.41)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.44)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.41)': + '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.44)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.41)': + '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.41)': + '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.41)': + '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.41)': + '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.44)': dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.41)': + '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 - '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.41)': + '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.44)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.41)': + '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.44)': dependencies: '@csstools/color-helpers': 4.2.1 - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.41)': + '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.44)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.41 + postcss: 8.4.44 - '@csstools/postcss-unset-value@3.0.1(postcss@8.4.41)': + '@csstools/postcss-unset-value@3.0.1(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 '@csstools/selector-resolve-nested@1.1.0(postcss-selector-parser@6.1.0)': dependencies: @@ -5318,9 +5318,9 @@ snapshots: dependencies: postcss-selector-parser: 6.1.0 - '@csstools/utilities@1.0.0(postcss@8.4.41)': + '@csstools/utilities@1.0.0(postcss@8.4.44)': dependencies: - postcss: 8.4.41 + postcss: 8.4.44 '@drizzle-team/brocli@0.8.2': {} @@ -5698,20 +5698,20 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/swagger-ui@0.4.1(hono@4.5.9)': + '@hono/swagger-ui@0.4.1(hono@4.5.11)': dependencies: - hono: 4.5.9 + hono: 4.5.11 - '@hono/zod-openapi@0.15.3(hono@4.5.9)(zod@3.23.8)': + '@hono/zod-openapi@0.15.3(hono@4.5.11)(zod@3.23.8)': dependencies: '@asteasolutions/zod-to-openapi': 7.1.1(zod@3.23.8) - '@hono/zod-validator': 0.2.2(hono@4.5.9)(zod@3.23.8) - hono: 4.5.9 + '@hono/zod-validator': 0.2.2(hono@4.5.11)(zod@3.23.8) + hono: 4.5.11 zod: 3.23.8 - '@hono/zod-validator@0.2.2(hono@4.5.9)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.5.11)(zod@3.23.8)': dependencies: - hono: 4.5.9 + hono: 4.5.11 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -5854,9 +5854,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) lucia: 3.2.0 '@lukeed/csprng@1.1.0': {} @@ -5925,7 +5925,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@neondatabase/serverless@0.9.4': + '@neondatabase/serverless@0.9.5': dependencies: '@types/pg': 8.11.6 @@ -6301,41 +6301,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))': + '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))': + '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1) '@rollup/plugin-json': 6.1.0(rollup@4.18.1) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1) - '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) rollup: 4.18.1 - '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))': + '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))': dependencies: - '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.4(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': + '@sveltejs/enhanced-img@0.3.4(rollup@4.18.1)(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))': dependencies: magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) vite-imagetools: 7.0.2(rollup@4.18.1) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': + '@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6349,28 +6349,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) debug: 4.3.4 svelte: 5.0.0-next.175 - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) - vitefu: 0.2.5(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) + vitefu: 0.2.5(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -6400,19 +6400,19 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.16.2': + '@types/node@20.16.3': dependencies: undici-types: 6.19.6 '@types/pg@8.11.6': dependencies: - '@types/node': 20.16.2 + '@types/node': 20.16.3 pg-protocol: 1.6.1 pg-types: 4.0.2 '@types/pg@8.11.8': dependencies: - '@types/node': 20.16.2 + '@types/node': 20.16.3 pg-protocol: 1.6.1 pg-types: 4.0.2 @@ -6666,14 +6666,14 @@ snapshots: async-sema@3.1.1: {} - autoprefixer@10.4.20(postcss@8.4.41): + autoprefixer@10.4.20(postcss@8.4.44): dependencies: browserslist: 4.23.3 caniuse-lite: 1.0.30001649 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 axobject-query@4.0.0: @@ -6759,7 +6759,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.12.12: + bullmq@5.12.13: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -6911,25 +6911,25 @@ snapshots: css-background-parser@0.1.0: {} - css-blank-pseudo@6.0.2(postcss@8.4.41): + css-blank-pseudo@6.0.2(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 css-box-shadow@1.0.0-3: {} css-color-keywords@1.0.0: {} - css-has-pseudo@6.0.5(postcss@8.4.41): + css-has-pseudo@6.0.5(postcss@8.4.44): dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.1.0 postcss-value-parser: 4.2.0 - css-prefers-color-scheme@9.0.1(postcss@8.4.41): + css-prefers-color-scheme@9.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 css-to-react-native@3.2.0: dependencies: @@ -7030,16 +7030,16 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4): optionalDependencies: - '@neondatabase/serverless': 0.9.4 + '@neondatabase/serverless': 0.9.5 '@types/pg': 8.11.8 pg: 8.12.0 postgres: 3.4.4 - drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.4)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) zod: 3.23.8 eastasianwidth@0.2.0: {} @@ -7199,7 +7199,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): + eslint-plugin-svelte@2.43.0(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -7207,9 +7207,9 @@ snapshots: eslint-compat-utils: 0.5.1(eslint@8.57.0) esutils: 2.0.3 known-css-properties: 0.34.0 - postcss: 8.4.41 - postcss-load-config: 3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) - postcss-safe-parser: 6.0.0(postcss@8.4.41) + postcss: 8.4.44 + postcss-load-config: 3.1.4(postcss@8.4.44)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) + postcss-safe-parser: 6.0.0(postcss@8.4.44) postcss-selector-parser: 6.1.0 semver: 7.6.2 svelte-eslint-parser: 0.41.0(svelte@5.0.0-next.175) @@ -7435,11 +7435,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7571,11 +7571,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.9): + hono-rate-limiter@0.4.0(hono@4.5.11): dependencies: - hono: 4.5.9 + hono: 4.5.11 - hono@4.5.9: {} + hono@4.5.11: {} html-entities@2.5.2: {} @@ -7968,7 +7968,7 @@ snapshots: node-releases@2.0.18: {} - nodemailer@6.9.14: {} + nodemailer@6.9.15: {} nopt@5.0.0: dependencies: @@ -8174,271 +8174,271 @@ snapshots: pngjs@5.0.0: {} - postcss-attribute-case-insensitive@6.0.3(postcss@8.4.41): + postcss-attribute-case-insensitive@6.0.3(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 - postcss-clamp@4.1.0(postcss@8.4.41): + postcss-clamp@4.1.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@6.0.14(postcss@8.4.41): + postcss-color-functional-notation@6.0.14(postcss@8.4.44): dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - postcss-color-hex-alpha@9.0.4(postcss@8.4.41): + postcss-color-hex-alpha@9.0.4(postcss@8.4.44): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@9.0.3(postcss@8.4.41): + postcss-color-rebeccapurple@9.0.3(postcss@8.4.44): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-custom-media@10.0.8(postcss@8.4.41): + postcss-custom-media@10.0.8(postcss@8.4.44): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.41 + postcss: 8.4.44 - postcss-custom-properties@13.3.12(postcss@8.4.41): + postcss-custom-properties@13.3.12(postcss@8.4.44): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-custom-selectors@7.1.12(postcss@8.4.41): + postcss-custom-selectors@7.1.12(postcss@8.4.44): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.1.0 - postcss-dir-pseudo-class@8.0.1(postcss@8.4.41): + postcss-dir-pseudo-class@8.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 - postcss-double-position-gradients@5.0.7(postcss@8.4.41): + postcss-double-position-gradients@5.0.7(postcss@8.4.44): dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-focus-visible@9.0.1(postcss@8.4.41): + postcss-focus-visible@9.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 - postcss-focus-within@8.0.1(postcss@8.4.41): + postcss-focus-within@8.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 - postcss-font-variant@5.0.0(postcss@8.4.41): + postcss-font-variant@5.0.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-gap-properties@5.0.1(postcss@8.4.41): + postcss-gap-properties@5.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-image-set-function@6.0.3(postcss@8.4.41): + postcss-image-set-function@6.0.3(postcss@8.4.44): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.4.41): + postcss-import@15.1.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-import@16.1.0(postcss@8.4.41): + postcss-import@16.1.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.41): + postcss-js@4.0.1(postcss@8.4.44): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.41 + postcss: 8.4.44 - postcss-lab-function@6.0.19(postcss@8.4.41): + postcss-lab-function@6.0.19(postcss@8.4.44): dependencies: '@csstools/css-color-parser': 2.0.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/utilities': 1.0.0(postcss@8.4.41) - postcss: 8.4.41 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/utilities': 1.0.0(postcss@8.4.44) + postcss: 8.4.44 - postcss-load-config@3.1.4(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): + postcss-load-config@3.1.4(postcss@8.4.44)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) + postcss: 8.4.44 + ts-node: 10.9.2(@types/node@20.16.3)(typescript@5.5.4) - postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.44)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: - postcss: 8.4.41 - ts-node: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) + postcss: 8.4.44 + ts-node: 10.9.2(@types/node@20.16.3)(typescript@5.5.4) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0): dependencies: lilconfig: 3.1.1 yaml: 2.4.2 optionalDependencies: jiti: 1.21.6 - postcss: 8.4.41 + postcss: 8.4.44 tsx: 4.19.0 - postcss-logical@7.0.1(postcss@8.4.41): + postcss-logical@7.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-nested@6.0.1(postcss@8.4.41): + postcss-nested@6.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.1.0 - postcss-nesting@12.1.5(postcss@8.4.41): + postcss-nesting@12.1.5(postcss@8.4.44): dependencies: '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.0) '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.0) - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.1.0 - postcss-opacity-percentage@2.0.0(postcss@8.4.41): + postcss-opacity-percentage@2.0.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-overflow-shorthand@5.0.1(postcss@8.4.41): + postcss-overflow-shorthand@5.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.4.41): + postcss-page-break@3.0.4(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-place@9.0.1(postcss@8.4.41): + postcss-place@9.0.1(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-value-parser: 4.2.0 - postcss-preset-env@9.6.0(postcss@8.4.41): + postcss-preset-env@9.6.0(postcss@8.4.44): dependencies: - '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.41) - '@csstools/postcss-color-function': 3.0.19(postcss@8.4.41) - '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.41) - '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.41) - '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.41) - '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.41) - '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.41) - '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.41) - '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.41) - '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.41) - '@csstools/postcss-initial': 1.0.1(postcss@8.4.41) - '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.41) - '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.41) - '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.41) - '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.41) - '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.41) - '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.41) - '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.41) - '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.41) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.41) - '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.41) - '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.41) - '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.41) - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.41) - '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.41) - '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.41) - '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.41) - '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.41) - '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.41) - '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.41) - autoprefixer: 10.4.20(postcss@8.4.41) + '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.44) + '@csstools/postcss-color-function': 3.0.19(postcss@8.4.44) + '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.44) + '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.44) + '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.44) + '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.44) + '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.44) + '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.44) + '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.44) + '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.44) + '@csstools/postcss-initial': 1.0.1(postcss@8.4.44) + '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.44) + '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.44) + '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.44) + '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.44) + '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.44) + '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.44) + '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.44) + '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.44) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.44) + '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.44) + '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.44) + '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.44) + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.44) + '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.44) + '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.44) + '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.44) + '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.44) + '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.44) + '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.44) + autoprefixer: 10.4.20(postcss@8.4.44) browserslist: 4.23.1 - css-blank-pseudo: 6.0.2(postcss@8.4.41) - css-has-pseudo: 6.0.5(postcss@8.4.41) - css-prefers-color-scheme: 9.0.1(postcss@8.4.41) + css-blank-pseudo: 6.0.2(postcss@8.4.44) + css-has-pseudo: 6.0.5(postcss@8.4.44) + css-prefers-color-scheme: 9.0.1(postcss@8.4.44) cssdb: 8.1.0 - postcss: 8.4.41 - postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.41) - postcss-clamp: 4.1.0(postcss@8.4.41) - postcss-color-functional-notation: 6.0.14(postcss@8.4.41) - postcss-color-hex-alpha: 9.0.4(postcss@8.4.41) - postcss-color-rebeccapurple: 9.0.3(postcss@8.4.41) - postcss-custom-media: 10.0.8(postcss@8.4.41) - postcss-custom-properties: 13.3.12(postcss@8.4.41) - postcss-custom-selectors: 7.1.12(postcss@8.4.41) - postcss-dir-pseudo-class: 8.0.1(postcss@8.4.41) - postcss-double-position-gradients: 5.0.7(postcss@8.4.41) - postcss-focus-visible: 9.0.1(postcss@8.4.41) - postcss-focus-within: 8.0.1(postcss@8.4.41) - postcss-font-variant: 5.0.0(postcss@8.4.41) - postcss-gap-properties: 5.0.1(postcss@8.4.41) - postcss-image-set-function: 6.0.3(postcss@8.4.41) - postcss-lab-function: 6.0.19(postcss@8.4.41) - postcss-logical: 7.0.1(postcss@8.4.41) - postcss-nesting: 12.1.5(postcss@8.4.41) - postcss-opacity-percentage: 2.0.0(postcss@8.4.41) - postcss-overflow-shorthand: 5.0.1(postcss@8.4.41) - postcss-page-break: 3.0.4(postcss@8.4.41) - postcss-place: 9.0.1(postcss@8.4.41) - postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.41) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.41) - postcss-selector-not: 7.0.2(postcss@8.4.41) + postcss: 8.4.44 + postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.44) + postcss-clamp: 4.1.0(postcss@8.4.44) + postcss-color-functional-notation: 6.0.14(postcss@8.4.44) + postcss-color-hex-alpha: 9.0.4(postcss@8.4.44) + postcss-color-rebeccapurple: 9.0.3(postcss@8.4.44) + postcss-custom-media: 10.0.8(postcss@8.4.44) + postcss-custom-properties: 13.3.12(postcss@8.4.44) + postcss-custom-selectors: 7.1.12(postcss@8.4.44) + postcss-dir-pseudo-class: 8.0.1(postcss@8.4.44) + postcss-double-position-gradients: 5.0.7(postcss@8.4.44) + postcss-focus-visible: 9.0.1(postcss@8.4.44) + postcss-focus-within: 8.0.1(postcss@8.4.44) + postcss-font-variant: 5.0.0(postcss@8.4.44) + postcss-gap-properties: 5.0.1(postcss@8.4.44) + postcss-image-set-function: 6.0.3(postcss@8.4.44) + postcss-lab-function: 6.0.19(postcss@8.4.44) + postcss-logical: 7.0.1(postcss@8.4.44) + postcss-nesting: 12.1.5(postcss@8.4.44) + postcss-opacity-percentage: 2.0.0(postcss@8.4.44) + postcss-overflow-shorthand: 5.0.1(postcss@8.4.44) + postcss-page-break: 3.0.4(postcss@8.4.44) + postcss-place: 9.0.1(postcss@8.4.44) + postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.44) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.44) + postcss-selector-not: 7.0.2(postcss@8.4.44) - postcss-pseudo-class-any-link@9.0.2(postcss@8.4.41): + postcss-pseudo-class-any-link@9.0.2(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 - postcss-replace-overflow-wrap@4.0.0(postcss@8.4.41): + postcss-replace-overflow-wrap@4.0.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-safe-parser@6.0.0(postcss@8.4.41): + postcss-safe-parser@6.0.0(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-scss@4.0.9(postcss@8.4.41): + postcss-scss@4.0.9(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 - postcss-selector-not@7.0.2(postcss@8.4.41): + postcss-selector-not@7.0.2(postcss@8.4.44): dependencies: - postcss: 8.4.41 + postcss: 8.4.44 postcss-selector-parser: 6.0.16 postcss-selector-parser@6.0.16: @@ -8453,7 +8453,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.41: + postcss@8.4.44: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 @@ -8887,14 +8887,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175): + svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0))(postcss@8.4.44)(sass@1.77.8)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.0.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0))(postcss@8.4.44)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - '@babel/core' @@ -8912,8 +8912,8 @@ snapshots: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.41 - postcss-scss: 4.0.9(postcss@8.4.41) + postcss: 8.4.44 + postcss-scss: 4.0.9(postcss@8.4.44) optionalDependencies: svelte: 5.0.0-next.175 @@ -8950,7 +8950,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0))(postcss@8.4.44)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8959,17 +8959,17 @@ snapshots: strip-indent: 3.0.0 svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0) + postcss: 8.4.44 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0) sass: 1.77.8 typescript: 5.5.4 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0))(postcss@8.4.41)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0))(postcss@8.4.44)(sass@1.77.8)(svelte@5.0.0-next.175)(typescript@5.5.4): dependencies: svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.41 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0) + postcss: 8.4.44 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0) sass: 1.77.8 typescript: 5.5.4 @@ -9024,19 +9024,19 @@ snapshots: magic-string: 0.30.10 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) - sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)) + '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -9061,16 +9061,16 @@ snapshots: tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4))): dependencies: tailwind-merge: 2.5.2 - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) + tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) - tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)): + tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9086,11 +9086,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 - postcss: 8.4.41 - postcss-import: 15.1.0(postcss@8.4.41) - postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4)) - postcss-nested: 6.0.1(postcss@8.4.41) + postcss: 8.4.44 + postcss-import: 15.1.0(postcss@8.4.44) + postcss-js: 4.0.1(postcss@8.4.44) + postcss-load-config: 4.0.2(postcss@8.4.44)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) + postcss-nested: 6.0.1(postcss@8.4.44) postcss-selector-parser: 6.1.0 resolve: 1.22.8 sucrase: 3.35.0 @@ -9156,14 +9156,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.2 + '@types/node': 20.16.3 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -9272,13 +9272,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.16.2)(sass@1.77.8): + vite-node@1.6.0(@types/node@20.16.3)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -9290,21 +9290,21 @@ snapshots: - supports-color - terser - vite@5.4.2(@types/node@20.16.2)(sass@1.77.8): + vite@5.4.3(@types/node@20.16.3)(sass@1.77.8): dependencies: esbuild: 0.21.5 - postcss: 8.4.41 + postcss: 8.4.44 rollup: 4.21.0 optionalDependencies: - '@types/node': 20.16.2 + '@types/node': 20.16.3 fsevents: 2.3.3 sass: 1.77.8 - vitefu@0.2.5(vite@5.4.2(@types/node@20.16.2)(sass@1.77.8)): + vitefu@0.2.5(vite@5.4.3(@types/node@20.16.3)(sass@1.77.8)): optionalDependencies: - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) - vitest@1.6.0(@types/node@20.16.2)(sass@1.77.8): + vitest@1.6.0(@types/node@20.16.3)(sass@1.77.8): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9323,11 +9323,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.4.2(@types/node@20.16.2)(sass@1.77.8) - vite-node: 1.6.0(@types/node@20.16.2)(sass@1.77.8) + vite: 5.4.3(@types/node@20.16.3)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.16.3)(sass@1.77.8) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.16.2 + '@types/node': 20.16.3 transitivePeerDependencies: - less - lightningcss diff --git a/src/lib/components/LeftNav.svelte b/src/lib/components/LeftNav.svelte new file mode 100644 index 0000000..40f2ac0 --- /dev/null +++ b/src/lib/components/LeftNav.svelte @@ -0,0 +1,77 @@ + + +
+ +
+ {@render children()} +
+
+ + \ No newline at end of file diff --git a/src/lib/server/api/controllers/mfa.controller.ts b/src/lib/server/api/controllers/mfa.controller.ts index 148b390..9285b79 100644 --- a/src/lib/server/api/controllers/mfa.controller.ts +++ b/src/lib/server/api/controllers/mfa.controller.ts @@ -2,13 +2,15 @@ import 'reflect-metadata' import { StatusCodes } from '$lib/constants/status-codes' import type { Controller } from '$lib/server/api/common/interfaces/controller.interface' import { verifyTotpDto } from '$lib/server/api/dtos/verify-totp.dto' +import { db } from '$lib/server/api/packages/drizzle' +import { RecoveryCodesService } from '$lib/server/api/services/recovery-codes.service' import { TotpService } from '$lib/server/api/services/totp.service' +import { UsersService } from '$lib/server/api/services/users.service' import { zValidator } from '@hono/zod-validator' import { Hono } from 'hono' import { inject, injectable } from 'tsyringe' import { CredentialsType } from '../databases/tables' import { requireAuth } from '../middleware/auth.middleware' -import { UsersService } from '../services/users.service' import type { HonoTypes } from '../types' @injectable() @@ -16,6 +18,7 @@ export class MfaController implements Controller { controller = new Hono() constructor( + @inject(RecoveryCodesService) private readonly recoveryCodesService: RecoveryCodesService, @inject(TotpService) private readonly totpService: TotpService, @inject(UsersService) private readonly usersService: UsersService, ) {} @@ -36,6 +39,8 @@ export class MfaController implements Controller { const user = c.var.user try { await this.totpService.deleteOneByUserIdAndType(user.id, CredentialsType.TOTP) + await this.recoveryCodesService.deleteAllRecoveryCodesByUserId(user.id) + await this.usersService.updateUser(user.id, { mfa_enabled: false }) console.log('TOTP deleted') return c.body(null, StatusCodes.NO_CONTENT) } catch (e) { @@ -43,16 +48,26 @@ export class MfaController implements Controller { return c.status(StatusCodes.INTERNAL_SERVER_ERROR) } }) + .get('/totp/recoveryCodes', requireAuth, async (c) => { + const user = c.var.user + // You can only view recovery codes once and that is on creation + const existingCodes = await this.recoveryCodesService.findAllRecoveryCodesByUserId(user.id) + if (existingCodes) { + return c.body('You have already generated recovery codes', StatusCodes.BAD_REQUEST) + } + const recoveryCodes = await this.recoveryCodesService.createRecoveryCodes(user.id) + return c.json({ recoveryCodes }) + }) .post('/totp/verify', requireAuth, zValidator('json', verifyTotpDto), async (c) => { try { const user = c.var.user const { code } = c.req.valid('json') const verified = await this.totpService.verify(user.id, code) if (verified) { - this.usersService.updateUser(user.id, { mfa_enabled: true }) + await this.usersService.updateUser(user.id, { mfa_enabled: true }) return c.json({}, StatusCodes.OK) } - return c.json({}, StatusCodes.BAD_REQUEST) + return c.json('Invalid code', StatusCodes.BAD_REQUEST) } catch (e) { console.error(e) return c.status(StatusCodes.INTERNAL_SERVER_ERROR) diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 7042384..1ff99e9 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata' import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' import { CredentialsType, credentialsTable } from '$lib/server/api/databases/tables/credentials.table' import { DatabaseProvider } from '$lib/server/api/providers/database.provider' diff --git a/src/lib/server/api/repositories/recovery-codes.repository.ts b/src/lib/server/api/repositories/recovery-codes.repository.ts new file mode 100644 index 0000000..6fdc7f0 --- /dev/null +++ b/src/lib/server/api/repositories/recovery-codes.repository.ts @@ -0,0 +1,27 @@ +import 'reflect-metadata' +import type { Repository } from '$lib/server/api/common/interfaces/repository.interface' +import { DatabaseProvider } from '$lib/server/api/providers/database.provider' +import { type InferInsertModel, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { recoveryCodesTable } from '../databases/tables' + +export type CreateRecoveryCodes = InferInsertModel + +@injectable() +export class RecoveryCodesRepository implements Repository { + constructor(@inject(DatabaseProvider) private readonly db: DatabaseProvider) {} + + async findAllByUserId(userId: string) { + return this.db.query.recoveryCodesTable.findFirst({ + where: eq(recoveryCodesTable.userId, userId), + }) + } + + async deleteAllByUserId(userId: string) { + return this.db.delete(recoveryCodesTable).where(eq(recoveryCodesTable.userId, userId)) + } + + trxHost(trx: DatabaseProvider) { + return new RecoveryCodesRepository(trx) + } +} diff --git a/src/lib/server/api/services/recovery-codes.service.ts b/src/lib/server/api/services/recovery-codes.service.ts new file mode 100644 index 0000000..e1f5567 --- /dev/null +++ b/src/lib/server/api/services/recovery-codes.service.ts @@ -0,0 +1,38 @@ +import 'reflect-metadata' +import { recoveryCodesTable } from '$lib/server/api/databases/tables' +import { db } from '$lib/server/api/packages/drizzle' +import { RecoveryCodesRepository } from '$lib/server/api/repositories/recovery-codes.repository' +import { alphabet, generateRandomString } from 'oslo/crypto' +import { Argon2id } from 'oslo/password' +import { inject, injectable } from 'tsyringe' + +@injectable() +export class RecoveryCodesService { + constructor(@inject(RecoveryCodesRepository) private readonly recoveryCodesRepository: RecoveryCodesRepository) {} + + async findAllRecoveryCodesByUserId(userId: string) { + return this.recoveryCodesRepository.findAllByUserId(userId) + } + + async createRecoveryCodes(userId: string) { + const createdRecoveryCodes = Array.from({ length: 5 }, () => generateRandomString(10, alphabet('A-Z', '0-9'))) + if (createdRecoveryCodes && userId) { + for (const code of createdRecoveryCodes) { + const hashedCode = await new Argon2id().hash(code) + console.log('Inserting recovery code', code, hashedCode) + await db.insert(recoveryCodesTable).values({ + userId, + code: hashedCode, + }) + } + + return createdRecoveryCodes + } + + return [] + } + + async deleteAllRecoveryCodesByUserId(userId: string) { + return this.recoveryCodesRepository.deleteAllByUserId(userId) + } +} diff --git a/src/routes/(app)/(protected)/profile/security/+layout.server.ts b/src/routes/(app)/(protected)/profile/security/+layout.server.ts new file mode 100644 index 0000000..e2ef8d4 --- /dev/null +++ b/src/routes/(app)/(protected)/profile/security/+layout.server.ts @@ -0,0 +1,3 @@ +export const load = async () => { + return {} +} diff --git a/src/routes/(app)/(protected)/profile/security/+layout.svelte b/src/routes/(app)/(protected)/profile/security/+layout.svelte new file mode 100644 index 0000000..c3382ba --- /dev/null +++ b/src/routes/(app)/(protected)/profile/security/+layout.svelte @@ -0,0 +1,15 @@ + + + + {@render children()} + diff --git a/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts b/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts index 59cf933..c7fb19e 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/mfa/+page.server.ts @@ -1,22 +1,15 @@ -import { StatusCodes } from '$lib/constants/status-codes' import { notSignedInMessage } from '$lib/flashMessages' -import { db } from '$lib/server/api/packages/drizzle' -import { userNotAuthenticated } from '$lib/server/auth-utils' import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account' import env from '$src/env' -import { type Actions, error, fail } from '@sveltejs/kit' -import { eq } from 'drizzle-orm' +import { type Actions, fail } from '@sveltejs/kit' import kebabCase from 'just-kebab-case' -import { HMAC } from 'oslo/crypto' -import { decodeHex, encodeHex } from 'oslo/encoding' -import { TOTPController, createTOTPKeyURI } from 'oslo/otp' -import { Argon2id } from 'oslo/password' +import { base32, decodeHex } from 'oslo/encoding' +import { createTOTPKeyURI } from 'oslo/otp' import QRCode from 'qrcode' -import { redirect, setFlash } from 'sveltekit-flash-message/server' +import { redirect } from 'sveltekit-flash-message/server' import { zod } from 'sveltekit-superforms/adapters' import { setError, superValidate } from 'sveltekit-superforms/server' import type { PageServerLoad } from '../../$types' -import { type Credentials, credentialsTable, recoveryCodesTable, usersTable } from '../../../../../../lib/server/api/databases/tables' export const load: PageServerLoad = async (event) => { const { locals } = event @@ -69,7 +62,11 @@ export const load: PageServerLoad = async (event) => { addTwoFactorForm, }) } - const totpUri = createTOTPKeyURI(issuer, accountName, decodeHex(createdTotpCredentials.secret_data)) + const decodedHexSecret = decodeHex(createdTotpCredentials.secret_data) + const secret = base32.encode(new Uint8Array(decodedHexSecret), { + includePadding: false, + }) + const totpUri = createTOTPKeyURI(issuer, accountName, decodedHexSecret) addTwoFactorForm.data = { current_password: '', @@ -82,6 +79,7 @@ export const load: PageServerLoad = async (event) => { recoveryCodes: [], totpUri, qrCode: await QRCode.toDataURL(totpUri), + secret, } } @@ -118,21 +116,25 @@ export const actions: Actions = { } const twoFactorCode = addTwoFactorForm.data.two_factor_code - const { error: verifyTotpError } = locals.api.mfa.totp.verify + const { error: verifyTotpError } = await locals.api.mfa.totp.verify .$post({ json: { code: twoFactorCode }, }) .then(locals.parseApiResponse) - if (verifyTotpError) { return setError(addTwoFactorForm, 'two_factor_code', 'Invalid code') } - redirect(302, '/profile/security/two-factor/recovery-codes') + redirect(302, '/profile/security/mfa/recovery-codes') }, disableTotp: async (event) => { const { locals } = event - const { user, session } = locals + + const authedUser = await locals.getAuthedUser() + if (!authedUser) { + throw redirect(302, '/login', notSignedInMessage, event) + } + const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema)) if (!removeTwoFactorForm.valid) { @@ -140,51 +142,27 @@ export const actions: Actions = { removeTwoFactorForm, }) } - - if (!user || !session) { - return fail(401, { - removeTwoFactorForm, + const { error: verifyPasswordError } = await locals.api.me.verify.password + .$post({ + json: { password: removeTwoFactorForm.data.current_password }, }) - } + .then(locals.parseApiResponse) - const dbUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.id, user.id), - }) - - // if (!dbUser?.hashed_password) { - // removeTwoFactorForm.data.current_password = ''; - // return setError( - // removeTwoFactorForm, - // 'Error occurred. Please try again or contact support if you need further help.', - // ); - // } - - const currentPasswordVerified = await new Argon2id().verify( - // dbUser.hashed_password, - removeTwoFactorForm.data.current_password, - ) - - if (!currentPasswordVerified) { + if (verifyPasswordError) { + console.log(verifyPasswordError) return setError(removeTwoFactorForm, 'current_password', 'Your password is incorrect') } - const twoFactorDetails = await db.query.twoFactor.findFirst({ - where: eq(twoFactor.userId, dbUser.id), - }) - - if (!twoFactorDetails) { + const { error: deleteTotpError } = await locals.api.mfa.totp.$delete().then(locals.parseApiResponse) + if (deleteTotpError) { return fail(500, { removeTwoFactorForm, }) } - await db.update(twoFactor).set({ enabled: false }).where(eq(twoFactor.userId, user.id)) - await db.delete(recoveryCodes).where(eq(recoveryCodes.userId, user.id)) - - // setFlash({ type: 'success', message: 'Two-Factor Authentication has been disabled.' }, cookies); redirect( 302, - '/profile/security/two-factor', + '/profile/security/mfa', { type: 'success', message: 'Two-Factor Authentication has been disabled.', diff --git a/src/routes/(app)/(protected)/profile/security/mfa/+page.svelte b/src/routes/(app)/(protected)/profile/security/mfa/+page.svelte index 3270b03..0182587 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/+page.svelte +++ b/src/routes/(app)/(protected)/profile/security/mfa/+page.svelte @@ -1,37 +1,38 @@ +

Two-Factor Authentication

{#if twoFactorEnabled} @@ -70,4 +71,13 @@ Submit -{/if} \ No newline at end of file + Secret: {secret} +{/if} +
+ + \ No newline at end of file diff --git a/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts b/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts index 663f788..c3c6533 100644 --- a/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts +++ b/src/routes/(app)/(protected)/profile/security/mfa/recovery-codes/+page.server.ts @@ -1,11 +1,6 @@ import { notSignedInMessage } from '$lib/flashMessages' -import { db } from '$lib/server/api/packages/drizzle' -import { eq } from 'drizzle-orm' -import { alphabet, generateRandomString } from 'oslo/crypto' -import { Argon2id } from 'oslo/password' import { redirect } from 'sveltekit-flash-message/server' import type { PageServerLoad } from '../../../$types' -import { recoveryCodesTable } from '../../../../../../../lib/server/api/databases/tables' export const load: PageServerLoad = async (event) => { const { locals } = event @@ -16,30 +11,18 @@ export const load: PageServerLoad = async (event) => { } if (authedUser.mfa_enabled) { - const dbRecoveryCodes = await db.query.recoveryCodesTable.findMany({ - where: eq(recoveryCodesTable.userId, authedUser.id), - }) - - if (dbRecoveryCodes.length === 0) { - const createdRecoveryCodes = Array.from({ length: 5 }, () => generateRandomString(10, alphabet('A-Z', '0-9'))) - if (createdRecoveryCodes) { - for (const code of createdRecoveryCodes) { - const hashedCode = await new Argon2id().hash(code) - console.log('Inserting recovery code', code, hashedCode) - await db.insert(recoveryCodesTable).values({ - userId: authedUser.id, - code: hashedCode, - }) - } - } + const { data: recoveryCodesData, error: recoveryCodesError } = await locals.api.mfa.totp.recoveryCodes.$get().then(locals.parseApiResponse) + console.log('recoveryCodesData', recoveryCodesData) + console.log('recoveryCodesError', recoveryCodesError) + if (recoveryCodesError || !recoveryCodesData || !recoveryCodesData.recoveryCodes) { return { - recoveryCodes: createdRecoveryCodes, + recoveryCodes: [], } } return { - recoveryCodes: [], + recoveryCodes: recoveryCodesData.recoveryCodes, } } - console.error('2FA not enabled') + redirect(302, '/profile', { message: 'Two-Factor Authentication is not enabled', type: 'error' }, event) }