From 47ae91e01574849b3fa4357bd04fcfd997277acb Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Wed, 9 Oct 2024 19:32:36 -0700 Subject: [PATCH] Updating env, adding pino logger and providing to Hono, fixing biome formatting, and starting to maybe add some OpenAPI middleware. --- .env.example | 4 +- package.json | 12 +- pnpm-lock.yaml | 258 ++++++++++++------ src/lib/components/PlausibleAnalytics.svelte | 13 - src/lib/server/api/common/config.ts | 8 +- src/lib/server/api/common/create-app.ts | 37 +++ src/lib/server/api/common/env.ts | 41 ++- src/lib/server/api/common/types/hono.ts | 25 +- src/lib/server/api/configure-open-api.ts | 42 +++ .../server/api/controllers/iam.controller.ts | 102 +++---- .../server/api/controllers/user.controller.ts | 30 +- src/lib/server/api/databases/seeds/users.ts | 61 ++--- src/lib/server/api/index.ts | 81 ++---- .../server/api/middleware/auth.middleware.ts | 52 ++-- .../api/middleware/pino-logger.middleware.ts | 14 +- .../api/middleware/rate-limiter.middleware.ts | 28 +- .../server/api/services/drizzle.service.ts | 27 +- src/lib/server/api/services/totp.service.ts | 36 +-- src/routes/+layout.svelte | 62 ++--- 19 files changed, 526 insertions(+), 407 deletions(-) delete mode 100644 src/lib/components/PlausibleAnalytics.svelte create mode 100644 src/lib/server/api/common/create-app.ts create mode 100644 src/lib/server/api/configure-open-api.ts diff --git a/.env.example b/.env.example index a126d1b..3b2e6f6 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,8 @@ DOMAIN=localhost ORIGIN=http://$DOMAIN:5173 -NODE_ENV=development +NODE_ENV= +LOG_LEVEL= DATABASE_USER='postgres' DATABASE_PASSWORD='postgres' @@ -32,7 +33,6 @@ PUBLIC_SITE_URL='http://$DOMAIN:5173' PUBLIC_UMAMI_DO_NOT_TRACK=true PUBLIC_UMAMI_URL= PUBLIC_UMAMI_ID= -PUBLIC_PLAUSIBLE_URL= # quick setting for key-combo only SVELTE_INSPECTOR_TOGGLE=control-shift-i diff --git a/package.json b/package.json index 1371b65..1e9fc78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "boredgame", - "version": "0.0.2", + "version": "0.0.5", "private": "true", "scripts": { "db:push": "drizzle-kit push", @@ -30,10 +30,10 @@ "@playwright/test": "^1.47.1", "@sveltejs/adapter-auto": "^3.2.5", "@sveltejs/enhanced-img": "^0.3.8", - "@sveltejs/kit": "^2.6.2", + "@sveltejs/kit": "^2.6.3", "@sveltejs/vite-plugin-svelte": "4.0.0-next.7", "@types/cookie": "^0.6.0", - "@types/node": "^20.16.10", + "@types/node": "^20.16.11", "@types/pg": "^8.11.10", "@types/qrcode": "^1.5.5", "@typescript-eslint/eslint-plugin": "^7.18.0", @@ -92,12 +92,13 @@ "@oslojs/otp": "^1.0.0", "@oslojs/webauthn": "^1.0.0", "@paralleldrive/cuid2": "^2.2.2", + "@scalar/hono-api-reference": "^0.5.152", "@sveltejs/adapter-node": "^5.2.5", - "@sveltejs/adapter-vercel": "^5.4.4", + "@sveltejs/adapter-vercel": "^5.4.5", "@types/feather-icons": "^4.29.4", "bits-ui": "^0.21.16", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.16.0", + "bullmq": "^5.17.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -111,6 +112,7 @@ "hono": "^4.6.3", "hono-pino": "^0.3.0", "hono-rate-limiter": "^0.4.0", + "hono-zod-openapi": "^0.2.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", "ioredis": "^5.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85ebb3c..2e860d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,12 +62,15 @@ importers: '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 + '@scalar/hono-api-reference': + specifier: ^0.5.152 + version: 0.5.152(hono@4.6.3) '@sveltejs/adapter-node': specifier: ^5.2.5 - version: 5.2.5(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))) + version: 5.2.5(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))) '@sveltejs/adapter-vercel': - specifier: ^5.4.4 - version: 5.4.4(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))) + specifier: ^5.4.5 + version: 5.4.5(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -78,8 +81,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.16.0 - version: 5.16.0 + specifier: ^5.17.1 + version: 5.17.1 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -106,7 +109,7 @@ importers: version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.19.1(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.19.1(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -119,6 +122,9 @@ importers: hono-rate-limiter: specifier: ^0.4.0 version: 0.4.0(hono@4.6.3) + hono-zod-openapi: + specifier: ^0.2.0 + version: 0.2.0(hono@4.6.3)(zod@3.23.8) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -184,10 +190,10 @@ importers: version: 2.5.3 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))) + version: 0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))) + version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -212,22 +218,22 @@ importers: version: 1.47.2 '@sveltejs/adapter-auto': specifier: ^3.2.5 - version: 3.2.5(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))) + version: 3.2.5(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))) '@sveltejs/enhanced-img': specifier: ^0.3.8 - version: 0.3.8(rollup@4.24.0)(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + version: 0.3.8(rollup@4.24.0)(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) '@sveltejs/kit': - specifier: ^2.6.2 - version: 2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + specifier: ^2.6.3 + version: 2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) '@sveltejs/vite-plugin-svelte': specifier: 4.0.0-next.7 - version: 4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + version: 4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^20.16.10 - version: 20.16.10 + specifier: ^20.16.11 + version: 20.16.11 '@types/pg': specifier: ^8.11.10 version: 8.11.10 @@ -257,7 +263,7 @@ importers: version: 9.1.0(eslint@8.57.1) eslint-plugin-svelte: specifier: 2.36.0-next.13 - version: 2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)) + version: 2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -311,16 +317,16 @@ importers: version: 2.0.2 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175) sveltekit-superforms: specifier: ^2.19.1 - version: 2.19.1(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) + version: 2.19.1(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.13 - version: 3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)) + version: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) + version: 10.9.2(@types/node@20.16.11)(typescript@5.6.2) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -332,10 +338,10 @@ importers: version: 5.6.2 vite: specifier: ^5.4.8 - version: 5.4.8(@types/node@20.16.10) + version: 5.4.8(@types/node@20.16.11) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.16.10) + version: 1.6.0(@types/node@20.16.11) zod: specifier: ^3.23.8 version: 3.23.8 @@ -1447,6 +1453,12 @@ packages: hono: '>=3.9.0' zod: ^3.19.1 + '@hono/zod-validator@0.4.1': + resolution: {integrity: sha512-I8LyfeJfvVmC5hPjZ2Iij7RjexlgSBT7QJudZ4JvNPLxn0JQ3sqclz2zydlwISAnw21D2n4LQ0nfZdoiv9fQQA==} + peerDependencies: + hono: '>=3.9.0' + zod: ^3.19.1 + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -2131,6 +2143,20 @@ packages: cpu: [x64] os: [win32] + '@scalar/hono-api-reference@0.5.152': + resolution: {integrity: sha512-mSRpicEjr3q3ulXHV9fpPKgq+Mju0/A9jyB4NvvZ9GitunTm6P4urr/RKzgESDTqYK3zK/VGahZWeQFLi6PSKQ==} + engines: {node: '>=18'} + peerDependencies: + hono: ^4.0.0 + + '@scalar/openapi-types@0.1.1': + resolution: {integrity: sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg==} + engines: {node: '>=18'} + + '@scalar/types@0.0.13': + resolution: {integrity: sha512-4baCQ3uXTQsT/da5X3+yO34VEu057Jjw0SJFkaaFmXTqHXVZM+SaDHSyTvT6tyKvgDpPpn6iD22uiFCdldEzRQ==} + engines: {node: '>=18'} + '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -2156,8 +2182,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/adapter-vercel@5.4.4': - resolution: {integrity: sha512-KORoxxqB2H5DrxpCHc9Yfijcgvmoaaz6G6eKHEg9fRlTsujJkxN26C0x4YlcgxqDU4dLIi1wfSLHpuZD0E4Irg==} + '@sveltejs/adapter-vercel@5.4.5': + resolution: {integrity: sha512-SROpUbjSZ1Xni4xuS22dunXFLjYzvTZwppqixIQFzTrf9oWcZEm2OfO+VgnrOT67LOcWQefJp7VSrpmjV691yQ==} peerDependencies: '@sveltejs/kit': ^2.4.0 @@ -2167,8 +2193,8 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: '>= 5.0.0' - '@sveltejs/kit@2.6.2': - resolution: {integrity: sha512-ruogrSPXjckn5poUiZU8VYNCSPHq66SFR1AATvOikQxtP6LNI4niAZVX/AWZRe/EPDG3oY2DNJ9c5z7u0t2NAQ==} + '@sveltejs/kit@2.6.3': + resolution: {integrity: sha512-baIAnmfMqAISrPtTC/22w6ay5kTEIQ/vq9bctiaQgRIoLCPBNhb6LEidTuWQS7OzPYCDBMuMX1t/fMvi4r3q/g==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2224,8 +2250,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.16.10': - resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} + '@types/node@20.16.11': + resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==} '@types/pg@8.11.10': resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} @@ -2322,6 +2348,9 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@unhead/schema@1.11.7': + resolution: {integrity: sha512-j9uN7T63aUXrZ6yx2CfjVT7xZHjn0PZO7TPMaWqMFjneIH/NONKvDVCMEqDlXeqdSIERIYtk/xTHgCUMer5eyw==} + '@vercel/nft@0.27.4': resolution: {integrity: sha512-Rioz3LJkEKicKCi9BSyc1RXZ5R6GmXosFMeBSThh6msWSOiArKhb7c75MiWwZEgPL7x0/l3TAfH/l0cxKNuUFA==} engines: {node: '>=16'} @@ -2525,8 +2554,8 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bullmq@5.16.0: - resolution: {integrity: sha512-7FaZzHsRXFOxrxCQTNKowuo9PPRwnAOpYXB5tcfk8vg0IbuVQ/je1Bf228Zy29gCS/5ytIEJNVB/DDGwZM0wbA==} + bullmq@5.17.1: + resolution: {integrity: sha512-wqjhdJptb3KLeuUO+ZfsjVHqHuPtHhhoAE2XM8zvnidk6uU/ELo16uU6CVTBhliAupgvPE9jWE7tchR0GCtsdA==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -2657,10 +2686,6 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - core-js@3.38.1: resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} @@ -3312,10 +3337,19 @@ packages: peerDependencies: hono: ^4.1.1 + hono-zod-openapi@0.2.0: + resolution: {integrity: sha512-LYQXLb+shxIdS5PLFAaeelQg5BQ3rph5kB1rpUbqRW4Pr4JxHA8u7sjNNs0g7H/sXOiFLNAZRhF3yFsgBEoybA==} + peerDependencies: + hono: ^4.6.3 + zod: ^3.21.4 + hono@4.6.3: resolution: {integrity: sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==} engines: {node: '>=16.9.0'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} @@ -5110,9 +5144,18 @@ packages: yup@1.4.0: resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} + zhead@2.2.4: + resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zod-openapi@3.1.1: + resolution: {integrity: sha512-jn5udnrGTAmTY7nj/E6ggKBsqIi9Vzj5u0RQpDyOOpzmz2ijcBssSeEjnQFbriqG2PY2JJBP5Gi4umRrhVxghQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.21.4 + zod-to-json-schema@3.23.3: resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} peerDependencies: @@ -5885,6 +5928,11 @@ snapshots: hono: 4.6.3 zod: 3.23.8 + '@hono/zod-validator@0.4.1(hono@4.6.3)(zod@3.23.8)': + dependencies: + hono: 4.6.3 + zod: 3.23.8 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -6459,6 +6507,18 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true + '@scalar/hono-api-reference@0.5.152(hono@4.6.3)': + dependencies: + '@scalar/types': 0.0.13 + hono: 4.6.3 + + '@scalar/openapi-types@0.1.1': {} + + '@scalar/types@0.0.13': + dependencies: + '@scalar/openapi-types': 0.1.1 + '@unhead/schema': 1.11.7 + '@sideway/address@4.1.5': dependencies: '@hapi/hoek': 9.3.0 @@ -6475,43 +6535,43 @@ snapshots: '@sinclair/typebox@0.32.35': optional: true - '@sveltejs/adapter-auto@3.2.5(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))': + '@sveltejs/adapter-auto@3.2.5(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))': dependencies: - '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/kit': 2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.5(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))': + '@sveltejs/adapter-node@5.2.5(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))': dependencies: '@rollup/plugin-commonjs': 28.0.0(rollup@4.24.0) '@rollup/plugin-json': 6.1.0(rollup@4.24.0) '@rollup/plugin-node-resolve': 15.3.0(rollup@4.24.0) - '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/kit': 2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) rollup: 4.24.0 - '@sveltejs/adapter-vercel@5.4.4(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))': + '@sveltejs/adapter-vercel@5.4.5(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))': dependencies: - '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/kit': 2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) '@vercel/nft': 0.27.4 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.8(rollup@4.24.0)(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))': + '@sveltejs/enhanced-img@0.3.8(rollup@4.24.0)(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))': dependencies: magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.8(@types/node@20.16.10) + vite: 5.4.8(@types/node@20.16.11) vite-imagetools: 7.0.4(rollup@4.24.0) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))': + '@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/vite-plugin-svelte': 4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) '@types/cookie': 0.6.0 - cookie: 0.7.2 + cookie: 0.6.0 devalue: 5.1.1 esm-env: 1.0.0 import-meta-resolve: 4.1.0 @@ -6523,27 +6583,27 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.8(@types/node@20.16.10) + vite: 5.4.8(@types/node@20.16.11) - '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/vite-plugin-svelte': 4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) debug: 4.3.7 svelte: 5.0.0-next.175 - vite: 5.4.8(@types/node@20.16.10) + vite: 5.4.8(@types/node@20.16.11) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10))': + '@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) debug: 4.3.7 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 5.0.0-next.175 - vite: 5.4.8(@types/node@20.16.10) - vitefu: 1.0.2(vite@5.4.8(@types/node@20.16.10)) + vite: 5.4.8(@types/node@20.16.11) + vitefu: 1.0.2(vite@5.4.8(@types/node@20.16.11)) transitivePeerDependencies: - supports-color @@ -6578,19 +6638,19 @@ snapshots: '@types/json-schema@7.0.15': optional: true - '@types/node@20.16.10': + '@types/node@20.16.11': dependencies: undici-types: 6.19.8 '@types/pg@8.11.10': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 pg-protocol: 1.7.0 pg-types: 4.0.2 '@types/pg@8.11.6': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 pg-protocol: 1.7.0 pg-types: 4.0.2 @@ -6598,7 +6658,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/resolve@1.20.2': {} @@ -6702,6 +6762,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@unhead/schema@1.11.7': + dependencies: + hookable: 5.5.3 + zhead: 2.2.4 + '@vercel/nft@0.27.4': dependencies: '@mapbox/node-pre-gyp': 1.0.11 @@ -6943,7 +7008,7 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bullmq@5.16.0: + bullmq@5.17.1: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -7084,8 +7149,6 @@ snapshots: cookie@0.6.0: {} - cookie@0.7.2: {} - core-js@3.38.1: {} create-require@1.1.1: {} @@ -7404,7 +7467,7 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-svelte@2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)): + eslint-plugin-svelte@2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@jridgewell/sourcemap-codec': 1.5.0 @@ -7414,7 +7477,7 @@ snapshots: esutils: 2.0.3 known-css-properties: 0.30.0 postcss: 8.4.47 - postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)) + postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)) postcss-safe-parser: 6.0.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 semver: 7.6.3 @@ -7650,11 +7713,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.19.1(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.19.1(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.19.1(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) + sveltekit-superforms: 2.19.1(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7797,8 +7860,17 @@ snapshots: dependencies: hono: 4.6.3 + hono-zod-openapi@0.2.0(hono@4.6.3)(zod@3.23.8): + dependencies: + '@hono/zod-validator': 0.4.1(hono@4.6.3)(zod@3.23.8) + hono: 4.6.3 + zod: 3.23.8 + zod-openapi: 3.1.1(zod@3.23.8) + hono@4.6.3: {} + hookable@5.5.3: {} + html-entities@2.5.2: {} http-errors@2.0.0: @@ -8531,21 +8603,21 @@ snapshots: '@csstools/utilities': 1.0.0(postcss@8.4.47) postcss: 8.4.47 - postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)): + postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) + ts-node: 10.9.2(@types/node@20.16.11)(typescript@5.6.2) - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)): dependencies: lilconfig: 3.1.2 yaml: 2.5.1 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) + ts-node: 10.9.2(@types/node@20.16.11)(typescript@5.6.2) postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1): dependencies: @@ -9220,14 +9292,14 @@ snapshots: magic-string: 0.30.11 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/kit': 2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) svelte: 5.0.0-next.175 - sveltekit-superforms@2.19.1(@sveltejs/kit@2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): + sveltekit-superforms@2.19.1(@sveltejs/kit@2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.10)) + '@sveltejs/kit': 2.6.3(@sveltejs/vite-plugin-svelte@4.0.0-next.7(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)))(svelte@5.0.0-next.175)(vite@5.4.8(@types/node@20.16.11)) devalue: 5.1.1 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -9255,16 +9327,16 @@ snapshots: tailwind-merge@2.5.3: {} - tailwind-variants@0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))): + tailwind-variants@0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2))): dependencies: tailwind-merge: 2.5.3 - tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)) + tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2))): dependencies: - tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)) + tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)) - tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)): + tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9283,7 +9355,7 @@ snapshots: postcss: 8.4.47 postcss-import: 15.1.0(postcss@8.4.47) postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2)) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2)) postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -9352,14 +9424,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2): + ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.2): 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.10 + '@types/node': 20.16.11 acorn: 8.12.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -9454,13 +9526,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.16.10): + vite-node@1.6.0(@types/node@20.16.11): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.8(@types/node@20.16.10) + vite: 5.4.8(@types/node@20.16.11) transitivePeerDependencies: - '@types/node' - less @@ -9472,20 +9544,20 @@ snapshots: - supports-color - terser - vite@5.4.8(@types/node@20.16.10): + vite@5.4.8(@types/node@20.16.11): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 fsevents: 2.3.3 - vitefu@1.0.2(vite@5.4.8(@types/node@20.16.10)): + vitefu@1.0.2(vite@5.4.8(@types/node@20.16.11)): optionalDependencies: - vite: 5.4.8(@types/node@20.16.10) + vite: 5.4.8(@types/node@20.16.11) - vitest@1.6.0(@types/node@20.16.10): + vitest@1.6.0(@types/node@20.16.11): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9504,11 +9576,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.8(@types/node@20.16.10) - vite-node: 1.6.0(@types/node@20.16.10) + vite: 5.4.8(@types/node@20.16.11) + vite-node: 1.6.0(@types/node@20.16.11) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 transitivePeerDependencies: - less - lightningcss @@ -9608,8 +9680,14 @@ snapshots: type-fest: 2.19.0 optional: true + zhead@2.2.4: {} + zimmerframe@1.1.2: {} + zod-openapi@3.1.1(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod-to-json-schema@3.23.3(zod@3.23.8): dependencies: zod: 3.23.8 diff --git a/src/lib/components/PlausibleAnalytics.svelte b/src/lib/components/PlausibleAnalytics.svelte deleted file mode 100644 index ae64c61..0000000 --- a/src/lib/components/PlausibleAnalytics.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/lib/server/api/common/config.ts b/src/lib/server/api/common/config.ts index 4c8b15b..083e0b6 100644 --- a/src/lib/server/api/common/config.ts +++ b/src/lib/server/api/common/config.ts @@ -1,8 +1,8 @@ -import env from './env' -import type { Config } from './types/config' +import env from './env'; +import type { Config } from './types/config'; export const config: Config = { - isProduction: process.env.NODE_ENV === 'production', + isProduction: env.NODE_ENV === 'production', domain: env.DOMAIN, api: { origin: env.ORIGIN, @@ -19,4 +19,4 @@ export const config: Config = { ssl: false, // env.DATABASE_HOST !== 'localhost', max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined, }, -} +}; diff --git a/src/lib/server/api/common/create-app.ts b/src/lib/server/api/common/create-app.ts new file mode 100644 index 0000000..8df3077 --- /dev/null +++ b/src/lib/server/api/common/create-app.ts @@ -0,0 +1,37 @@ +import env from '$lib/server/api/common/env'; +import type { AppBindings } from '$lib/server/api/common/types/hono'; +import { validateAuthSession, verifyOrigin } from '$lib/server/api/middleware/auth.middleware'; +import { pinoLogger } from '$lib/server/api/middleware/pino-logger.middleware'; +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { notFound, onError, serveEmojiFavicon } from 'stoker/middlewares'; + +export function createRouter() { + return new Hono({ + strict: false, + }).basePath('/api'); +} + +export default function createApp() { + const app = createRouter(); + + app.use(verifyOrigin).use(validateAuthSession); + app.use(serveEmojiFavicon('📝')); + app.use(pinoLogger()); + + app.notFound(notFound); + app.onError(onError); + + app.use( + '/*', + cors({ + origin: [env.ORIGIN], + + allowMethods: ['POST'], + allowHeaders: ['Content-Type'], + // credentials: true, // If you need to send cookies or HTTP authentication + }), + ); + + return app; +} diff --git a/src/lib/server/api/common/env.ts b/src/lib/server/api/common/env.ts index ea4b523..ad99f4d 100644 --- a/src/lib/server/api/common/env.ts +++ b/src/lib/server/api/common/env.ts @@ -1,13 +1,15 @@ -import { config } from 'dotenv' -import { expand } from 'dotenv-expand' -import { ZodError, z } from 'zod' +import { config } from 'dotenv'; +import { expand } from 'dotenv-expand'; +import { type ZodError, z } from 'zod'; + +expand(config()); const stringBoolean = z.coerce .string() .transform((val) => { - return val === 'true' + return val === 'true'; }) - .default('false') + .default('false'); const EnvSchema = z.object({ DATABASE_USER: z.string(), @@ -22,34 +24,29 @@ const EnvSchema = z.object({ GITHUB_CLIENT_SECRET: z.string(), GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_SECRET: z.string(), + LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), 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(), + PUBLIC_UMAMI_DO_NOT_TRACK: z.string().default('true'), PUBLIC_UMAMI_ID: z.string(), PUBLIC_UMAMI_URL: z.string(), REDIS_URL: z.string(), TWO_FACTOR_TIMEOUT: z.coerce.number().default(300000), -}) +}); -export type EnvSchema = z.infer +export type env = z.infer; -expand(config()) +let env: env; try { - EnvSchema.parse(process.env) -} catch (error) { - if (error instanceof ZodError) { - let message = 'Missing required values in .env:\n' - for (const issue of error.issues) { - message += `${issue.path[0]}\n` - } - const e = new Error(message) - e.stack = '' - throw e - } - console.error(error) + env = EnvSchema.parse(process.env); +} catch (e) { + const error = e as ZodError; + console.error('❌ Missing required values in .env:\n'); + console.error(error.flatten().fieldErrors); + process.exit(1); } -export default EnvSchema.parse(process.env) +export default env; diff --git a/src/lib/server/api/common/types/hono.ts b/src/lib/server/api/common/types/hono.ts index b0fcae1..0974d5b 100644 --- a/src/lib/server/api/common/types/hono.ts +++ b/src/lib/server/api/common/types/hono.ts @@ -1,14 +1,29 @@ +import type { PinoLogger } from 'hono-pino'; import type { Promisify, RateLimitInfo } from 'hono-rate-limiter'; import type { Session, User } from 'lucia'; -export type HonoTypes = { +export type AppBindings = { Variables: { + logger: PinoLogger; session: Session | null; user: User | null; rateLimit: RateLimitInfo; - rateLimitStore: { - getKey?: (key: string) => Promisify; - resetKey: (key: string) => Promisify; - }; + rateLimitStore: { + getKey?: (key: string) => Promisify; + resetKey: (key: string) => Promisify; + }; + }; +}; + +export type HonoTypes = { + Variables: { + logger: PinoLogger; + session: Session | null; + user: User | null; + rateLimit: RateLimitInfo; + rateLimitStore: { + getKey?: (key: string) => Promisify; + resetKey: (key: string) => Promisify; + }; }; }; diff --git a/src/lib/server/api/configure-open-api.ts b/src/lib/server/api/configure-open-api.ts new file mode 100644 index 0000000..ae692f3 --- /dev/null +++ b/src/lib/server/api/configure-open-api.ts @@ -0,0 +1,42 @@ +// // import type { AppOpenAPI } from '$lib/server/api/common/types/hono'; +// import { apiReference } from '@scalar/hono-api-reference'; +// import { Hono } from 'hono'; +// +// import type { AppBindings } from '$lib/server/api/common/types/hono'; +// import { createOpenApiDocument } from 'hono-zod-openapi'; +// import packageJSON from '../../../../package.json'; +// +// // export default function configureOpenAPI(app: AppOpenAPI) { +// // app.doc('/doc', { +// // openapi: '3.0.0', +// // info: { +// // title: 'Bored Game API', +// // description: 'Bored Game API', +// // version: packageJSON.version, +// // }, +// // }); +// // +// // app.get( +// // '/reference', +// // apiReference({ +// // theme: 'kepler', +// // layout: 'classic', +// // defaultHttpClient: { +// // targetKey: 'javascript', +// // clientKey: 'fetch', +// // }, +// // spec: { +// // url: '/api/doc', +// // }, +// // }), +// // ); +// // } +// +// export default function configureOpenAPI(app: Hono) { +// createOpenApiDocument(app, { +// info: { +// title: 'Example API', +// version: '1.0.0', +// }, +// }); +// } diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 0d2010c..32f650a 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,17 +1,17 @@ -import { StatusCodes } from '$lib/constants/status-codes' -import { Controller } from '$lib/server/api/common/types/controller' -import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto' -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 { IamService } from '$lib/server/api/services/iam.service' -import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service' -import { LuciaService } from '$lib/server/api/services/lucia.service' -import { zValidator } from '@hono/zod-validator' -import { setCookie } from 'hono/cookie' -import { inject, injectable } from 'tsyringe' -import { requireAuth } from '../middleware/require-auth.middleware' +import { StatusCodes } from '$lib/constants/status-codes'; +import { Controller } from '$lib/server/api/common/types/controller'; +import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto'; +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 { IamService } from '$lib/server/api/services/iam.service'; +import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'; +import { LuciaService } from '$lib/server/api/services/lucia.service'; +import { zValidator } from '@hono/zod-validator'; +import { setCookie } from 'hono/cookie'; +import { inject, injectable } from 'tsyringe'; +import { requireAuth } from '../middleware/require-auth.middleware'; @injectable() export class IamController extends Controller { @@ -20,45 +20,47 @@ export class IamController extends Controller { @inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService, @inject(LuciaService) private luciaService: LuciaService, ) { - super() + super(); } routes() { + const tags = ['IAM']; + return this.controller .get('/', requireAuth, async (c) => { - const user = c.var.user - return c.json({ user }) + const user = c.var.user; + return c.json({ user }); }) .put('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 30, minutes: 60 }), async (c) => { - const user = c.var.user - const { firstName, lastName, username } = c.req.valid('json') - const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username }) + const user = c.var.user; + 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) + return c.json({ user: updatedUser }, StatusCodes.OK); }) .post('/verify/password', requireAuth, zValidator('json', verifyPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const user = c.var.user - const { password } = c.req.valid('json') - const passwordVerified = await this.iamService.verifyPassword(user.id, { password }) + const user = c.var.user; + 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) + console.log('Incorrect password'); + return c.json('Incorrect password', StatusCodes.BAD_REQUEST); } - return c.json({}, StatusCodes.OK) + return c.json({}, StatusCodes.OK); }) .put('/update/password', requireAuth, zValidator('json', changePasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { - const user = c.var.user - const { password, confirm_password } = c.req.valid('json') + const user = c.var.user; + const { password, confirm_password } = c.req.valid('json'); if (password !== confirm_password) { - return c.json('Passwords do not match', StatusCodes.BAD_REQUEST) + return c.json('Passwords do not match', StatusCodes.BAD_REQUEST); } try { - await this.iamService.updatePassword(user.id, { password, confirm_password }) - await this.luciaService.lucia.invalidateUserSessions(user.id) - await this.loginRequestService.createUserSession(user.id, c.req, undefined) - const sessionCookie = this.luciaService.lucia.createBlankSessionCookie() + await this.iamService.updatePassword(user.id, { password, confirm_password }); + await this.luciaService.lucia.invalidateUserSessions(user.id); + await this.loginRequestService.createUserSession(user.id, c.req, undefined); + const sessionCookie = this.luciaService.lucia.createBlankSessionCookie(); setCookie(c, sessionCookie.name, sessionCookie.value, { path: sessionCookie.attributes.path, maxAge: sessionCookie.attributes.maxAge, @@ -67,26 +69,26 @@ export class IamController extends Controller { secure: sessionCookie.attributes.secure, httpOnly: sessionCookie.attributes.httpOnly, expires: sessionCookie.attributes.expires, - }) - return c.json({ status: 'success' }) + }); + return c.json({ status: 'success' }); } catch (error) { - console.error('Error updating password', error) - return c.json('Error updating password', StatusCodes.BAD_REQUEST) + console.error('Error updating password', error); + return c.json('Error updating password', StatusCodes.BAD_REQUEST); } }) .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 }) + 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) + return c.json({ user: updatedUser }, StatusCodes.OK); }) .post('/logout', requireAuth, async (c) => { - const sessionId = c.var.session.id - await this.iamService.logout(sessionId) - const sessionCookie = this.luciaService.lucia.createBlankSessionCookie() + const sessionId = c.var.session.id; + await this.iamService.logout(sessionId); + const sessionCookie = this.luciaService.lucia.createBlankSessionCookie(); setCookie(c, sessionCookie.name, sessionCookie.value, { path: sessionCookie.attributes.path, maxAge: sessionCookie.attributes.maxAge, @@ -95,8 +97,8 @@ export class IamController extends Controller { secure: sessionCookie.attributes.secure, httpOnly: sessionCookie.attributes.httpOnly, expires: sessionCookie.attributes.expires, - }) - return c.json({ status: 'success' }) - }) + }); + return c.json({ status: 'success' }); + }); } } diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index c05cd2e..46350e3 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -1,30 +1,30 @@ -import 'reflect-metadata' -import { Controller } from '$lib/server/api/common/types/controller' -import { UsersService } from '$lib/server/api/services/users.service' -import { inject, injectable } from 'tsyringe' -import { requireAuth } from '../middleware/require-auth.middleware' +import 'reflect-metadata'; +import { Controller } from '$lib/server/api/common/types/controller'; +import { UsersService } from '$lib/server/api/services/users.service'; +import { inject, injectable } from 'tsyringe'; +import { requireAuth } from '../middleware/require-auth.middleware'; @injectable() export class UserController extends Controller { constructor(@inject(UsersService) private readonly usersService: UsersService) { - super() + super(); } 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/databases/seeds/users.ts b/src/lib/server/api/databases/seeds/users.ts index ee039f2..b847cc2 100644 --- a/src/lib/server/api/databases/seeds/users.ts +++ b/src/lib/server/api/databases/seeds/users.ts @@ -1,20 +1,19 @@ -import { eq } from 'drizzle-orm' -import type { db } from '../../packages/drizzle' -import { HashingService } from '../../services/hashing.service' -import * as schema from '../tables' -import users from './data/users.json' +import { eq } from 'drizzle-orm'; +import type { db } from '../../packages/drizzle'; +import { HashingService } from '../../services/hashing.service'; +import * as schema from '../tables'; +import users from './data/users.json'; type JsonRole = { - name: string - primary: boolean -} + name: string; + primary: boolean; +}; export default async function seed(db: db) { - const hashingService = new HashingService() - const adminRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'admin')) - const userRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'user')) + const hashingService = new HashingService(); + const adminRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'admin')); + const userRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'user')); - console.log('Admin Role: ', adminRole) const adminUser = await db .insert(schema.usersTable) .values({ @@ -25,19 +24,17 @@ export default async function seed(db: db) { verified: true, }) .returning() - .onConflictDoNothing() - - console.log('Admin user created.', adminUser) + .onConflictDoNothing(); await db.insert(schema.credentialsTable).values({ user_id: adminUser[0].id, type: schema.CredentialsType.PASSWORD, secret_data: await hashingService.hash(`${process.env.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.wishlistsTable).values({ user_id: adminUser[0].id }).onConflictDoNothing() + await db.insert(schema.wishlistsTable).values({ user_id: adminUser[0].id }).onConflictDoNothing(); await db .insert(schema.user_roles) @@ -46,9 +43,7 @@ export default async function seed(db: db) { role_id: adminRole[0].id, primary: true, }) - .onConflictDoNothing() - - console.log('Admin user given admin role.') + .onConflictDoNothing(); await db .insert(schema.user_roles) @@ -57,10 +52,8 @@ export default async function seed(db: db) { role_id: userRole[0].id, primary: false, }) - .onConflictDoNothing() + .onConflictDoNothing(); - console.log('Admin user given user role.') - const hasingService = new HashingService() await Promise.all( users.map(async (user) => { const [insertedUser] = await db @@ -68,29 +61,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 hasingService.hash(user.password), - }) - await db.insert(schema.collections).values({ user_id: insertedUser?.id }) - await db.insert(schema.wishlistsTable).values({ user_id: insertedUser?.id }) + secret_data: await hashingService.hash(user.password), + }); + await db.insert(schema.collections).values({ user_id: insertedUser?.id }); + await db.insert(schema.wishlistsTable).values({ user_id: insertedUser?.id }); await Promise.all( user.roles.map(async (role: JsonRole) => { const foundRole = await db.query.rolesTable.findFirst({ where: eq(schema.rolesTable.name, role.name), - }) + }); if (!foundRole) { - throw new Error('Role not found') + 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/index.ts b/src/lib/server/api/index.ts index a057c76..28095bf 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,53 +1,21 @@ -import 'reflect-metadata' -import { CollectionController } from '$lib/server/api/controllers/collection.controller' -import { MfaController } from '$lib/server/api/controllers/mfa.controller' -import { OAuthController } from '$lib/server/api/controllers/oauth.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 { OpenAPIHono } from '@hono/zod-openapi' -import type { PinoLogger } from 'hono-pino' -import { hc } from 'hono/client' -import { cors } from 'hono/cors' -import { notFound, onError } from 'stoker/middlewares'; -import { container } from 'tsyringe' -import { config } from './common/config' -import { IamController } from './controllers/iam.controller' -import { LoginController } from './controllers/login.controller' -import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware' -import { pinoLogger } from './middleware/pino-logger.middleware' +import 'reflect-metadata'; +import createApp from '$lib/server/api/common/create-app'; +import { CollectionController } from '$lib/server/api/controllers/collection.controller'; +import { MfaController } from '$lib/server/api/controllers/mfa.controller'; +import { OAuthController } from '$lib/server/api/controllers/oauth.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 { hc } from 'hono/client'; +import { container } from 'tsyringe'; +import { config } from './common/config'; +import { IamController } from './controllers/iam.controller'; +import { LoginController } from './controllers/login.controller'; -type AppBindings = { - Variables: { - logger: PinoLogger - } -} +export const app = createApp(); -/* -------------------------------------------------------------------------- */ -/* App */ -/* -------------------------------------------------------------------------- */ -export const app = new OpenAPIHono().basePath('/api') - -/* -------------------------------------------------------------------------- */ -/* Global Middlewares */ -/* -------------------------------------------------------------------------- */ -app.use(verifyOrigin).use(validateAuthSession) -app.use(pinoLogger()) - -app.notFound(notFound) -app.onError(onError) - -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 - }), -) +// configureOpenAPI(app); /* -------------------------------------------------------------------------- */ /* Routes */ @@ -61,22 +29,17 @@ const routes = app .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' })) - .get('/error', (c) => { - c.status(422); - c.var.logger.info('Logged here'); - throw new Error('Test error') - }) + .get('/', (c) => c.json({ message: 'Server is healthy' })); /* -------------------------------------------------------------------------- */ /* Cron Jobs */ /* -------------------------------------------------------------------------- */ -container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests() -container.resolve(AuthCleanupJobs).deleteStaleLoginRequests() +container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests(); +container.resolve(AuthCleanupJobs).deleteStaleLoginRequests(); /* -------------------------------------------------------------------------- */ /* Exports */ /* -------------------------------------------------------------------------- */ -export const rpc = hc(config.api.origin) -export type ApiClient = typeof rpc -export type ApiRoutes = typeof routes +export const rpc = hc(config.api.origin); +export type ApiClient = typeof rpc; +export type ApiRoutes = typeof routes; diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index b88418b..fc4e076 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -1,41 +1,41 @@ -import { LuciaService } from '$lib/server/api/services/lucia.service' -import type { MiddlewareHandler } from 'hono' -import { createMiddleware } from 'hono/factory' -import { verifyRequestOrigin } from 'oslo/request' -import { container } from 'tsyringe' -import type { HonoTypes } from '../common/types/hono' +import { LuciaService } from '$lib/server/api/services/lucia.service'; +import type { MiddlewareHandler } from 'hono'; +import { createMiddleware } from 'hono/factory'; +import { verifyRequestOrigin } from 'oslo/request'; +import { container } from 'tsyringe'; +import type { AppBindings } from '../common/types/hono'; // resolve dependencies from the container -const { lucia } = container.resolve(LuciaService) +const { lucia } = container.resolve(LuciaService); -export const verifyOrigin: MiddlewareHandler = createMiddleware(async (c, next) => { +export const verifyOrigin: MiddlewareHandler = createMiddleware(async (c, next) => { if (c.req.method === 'GET') { - return next() + 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') ?? '') +export const validateAuthSession: MiddlewareHandler = createMiddleware(async (c, next) => { + 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?.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(); +}); diff --git a/src/lib/server/api/middleware/pino-logger.middleware.ts b/src/lib/server/api/middleware/pino-logger.middleware.ts index fc98d10..6cd74e6 100644 --- a/src/lib/server/api/middleware/pino-logger.middleware.ts +++ b/src/lib/server/api/middleware/pino-logger.middleware.ts @@ -1,14 +1,18 @@ +import env from '$lib/server/api/common/env'; import { logger } from 'hono-pino'; import pino from 'pino'; import pretty from 'pino-pretty'; export function pinoLogger() { return logger({ - pino: pino({ - level: process.env.LOG_LEVEL || 'info', - }, process.env.NODE_ENV === 'production' ? undefined : pretty()), + pino: pino( + { + level: env.LOG_LEVEL || 'info', + }, + env.NODE_ENV === 'production' ? undefined : pretty(), + ), http: { reqId: () => crypto.randomUUID(), - } + }, }); -} \ No newline at end of file +} diff --git a/src/lib/server/api/middleware/rate-limiter.middleware.ts b/src/lib/server/api/middleware/rate-limiter.middleware.ts index 97f0919..474bbe0 100644 --- a/src/lib/server/api/middleware/rate-limiter.middleware.ts +++ b/src/lib/server/api/middleware/rate-limiter.middleware.ts @@ -1,35 +1,35 @@ -import { rateLimiter } from 'hono-rate-limiter' -import { RedisStore } from 'rate-limit-redis' -import { container } from 'tsyringe' -import type { HonoTypes } from '../common/types/hono' -import { RedisService } from '../services/redis.service' +import { rateLimiter } from 'hono-rate-limiter'; +import { RedisStore } from 'rate-limit-redis'; +import { container } from 'tsyringe'; +import type { AppBindings } from '../common/types/hono'; +import { RedisService } from '../services/redis.service'; // resolve dependencies from the container -const { client } = container.resolve(RedisService) +const { client } = container.resolve(RedisService); export function limiter({ limit, minutes, key = '', }: { - limit: number - minutes: number - key?: string + 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}` + const vars = c.var as AppBindings['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/services/drizzle.service.ts b/src/lib/server/api/services/drizzle.service.ts index 93be44b..29e1f8a 100644 --- a/src/lib/server/api/services/drizzle.service.ts +++ b/src/lib/server/api/services/drizzle.service.ts @@ -1,14 +1,15 @@ -import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres' -import pg from 'pg' -import { type Disposable, injectable } from 'tsyringe' -import { config } from '../common/config' -import * as schema from '../databases/tables' +import env from '$lib/server/api/common/env'; +import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres'; +import pg from 'pg'; +import { type Disposable, injectable } from 'tsyringe'; +import { config } from '../common/config'; +import * as schema from '../databases/tables'; @injectable() export class DrizzleService implements Disposable { - protected readonly pool: pg.Pool - db: NodePgDatabase - readonly schema: typeof schema = schema + protected readonly pool: pg.Pool; + db: NodePgDatabase; + readonly schema: typeof schema = schema; constructor() { const pool = new pg.Pool({ @@ -19,15 +20,15 @@ export class DrizzleService implements Disposable { database: config.postgres.database, ssl: config.postgres.ssl, max: config.postgres.max, - }) - this.pool = pool + }); + this.pool = pool; this.db = drizzle(pool, { schema, - logger: process.env.NODE_ENV === 'development', - }) + logger: env.NODE_ENV === 'development', + }); } dispose(): Promise | void { - this.pool.end() + this.pool.end(); } } diff --git a/src/lib/server/api/services/totp.service.ts b/src/lib/server/api/services/totp.service.ts index 80480c0..6e8b7df 100644 --- a/src/lib/server/api/services/totp.service.ts +++ b/src/lib/server/api/services/totp.service.ts @@ -1,52 +1,52 @@ -import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' -import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding' -import { verifyTOTP } from '@oslojs/otp' -import { inject, injectable } from 'tsyringe' -import type { CredentialsType } from '../databases/tables' +import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository'; +import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding'; +import { verifyTOTP } from '@oslojs/otp'; +import { inject, injectable } from 'tsyringe'; +import type { CredentialsType } from '../databases/tables'; @injectable() export class TotpService { 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 secret = new Uint8Array(20) + const secret = new Uint8Array(20); try { return await this.credentialsRepository.create({ user_id: userId, secret_data: encodeHexLowerCase(crypto.getRandomValues(secret)), 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) { - return this.credentialsRepository.deleteByUserIdAndType(userId, type) + return this.credentialsRepository.deleteByUserIdAndType(userId, type); } 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 verifyTOTP(decodeHex(credential.secret_data), 30, 6, code) + return verifyTOTP(decodeHex(credential.secret_data), 30, 6, code); } } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 4c22a8f..45f7968 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,23 +1,22 @@ {#if !dev} - {/if}