Updating env, adding pino logger and providing to Hono, fixing biome formatting, and starting to maybe add some OpenAPI middleware.

This commit is contained in:
Bradley Shellnut 2024-10-09 19:32:36 -07:00
parent 27e17933af
commit 47ae91e015
19 changed files with 526 additions and 407 deletions

View file

@ -2,7 +2,8 @@
DOMAIN=localhost DOMAIN=localhost
ORIGIN=http://$DOMAIN:5173 ORIGIN=http://$DOMAIN:5173
NODE_ENV=development NODE_ENV=
LOG_LEVEL=
DATABASE_USER='postgres' DATABASE_USER='postgres'
DATABASE_PASSWORD='postgres' DATABASE_PASSWORD='postgres'
@ -32,7 +33,6 @@ PUBLIC_SITE_URL='http://$DOMAIN:5173'
PUBLIC_UMAMI_DO_NOT_TRACK=true PUBLIC_UMAMI_DO_NOT_TRACK=true
PUBLIC_UMAMI_URL= PUBLIC_UMAMI_URL=
PUBLIC_UMAMI_ID= PUBLIC_UMAMI_ID=
PUBLIC_PLAUSIBLE_URL=
# quick setting for key-combo only # quick setting for key-combo only
SVELTE_INSPECTOR_TOGGLE=control-shift-i SVELTE_INSPECTOR_TOGGLE=control-shift-i

View file

@ -1,6 +1,6 @@
{ {
"name": "boredgame", "name": "boredgame",
"version": "0.0.2", "version": "0.0.5",
"private": "true", "private": "true",
"scripts": { "scripts": {
"db:push": "drizzle-kit push", "db:push": "drizzle-kit push",
@ -30,10 +30,10 @@
"@playwright/test": "^1.47.1", "@playwright/test": "^1.47.1",
"@sveltejs/adapter-auto": "^3.2.5", "@sveltejs/adapter-auto": "^3.2.5",
"@sveltejs/enhanced-img": "^0.3.8", "@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", "@sveltejs/vite-plugin-svelte": "4.0.0-next.7",
"@types/cookie": "^0.6.0", "@types/cookie": "^0.6.0",
"@types/node": "^20.16.10", "@types/node": "^20.16.11",
"@types/pg": "^8.11.10", "@types/pg": "^8.11.10",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
@ -92,12 +92,13 @@
"@oslojs/otp": "^1.0.0", "@oslojs/otp": "^1.0.0",
"@oslojs/webauthn": "^1.0.0", "@oslojs/webauthn": "^1.0.0",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@scalar/hono-api-reference": "^0.5.152",
"@sveltejs/adapter-node": "^5.2.5", "@sveltejs/adapter-node": "^5.2.5",
"@sveltejs/adapter-vercel": "^5.4.4", "@sveltejs/adapter-vercel": "^5.4.5",
"@types/feather-icons": "^4.29.4", "@types/feather-icons": "^4.29.4",
"bits-ui": "^0.21.16", "bits-ui": "^0.21.16",
"boardgamegeekclient": "^1.9.1", "boardgamegeekclient": "^1.9.1",
"bullmq": "^5.16.0", "bullmq": "^5.17.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cookie": "^0.6.0", "cookie": "^0.6.0",
@ -111,6 +112,7 @@
"hono": "^4.6.3", "hono": "^4.6.3",
"hono-pino": "^0.3.0", "hono-pino": "^0.3.0",
"hono-rate-limiter": "^0.4.0", "hono-rate-limiter": "^0.4.0",
"hono-zod-openapi": "^0.2.0",
"html-entities": "^2.5.2", "html-entities": "^2.5.2",
"iconify-icon": "^2.1.0", "iconify-icon": "^2.1.0",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",

View file

@ -62,12 +62,15 @@ importers:
'@paralleldrive/cuid2': '@paralleldrive/cuid2':
specifier: ^2.2.2 specifier: ^2.2.2
version: 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': '@sveltejs/adapter-node':
specifier: ^5.2.5 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': '@sveltejs/adapter-vercel':
specifier: ^5.4.4 specifier: ^5.4.5
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))) 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': '@types/feather-icons':
specifier: ^4.29.4 specifier: ^4.29.4
version: 4.29.4 version: 4.29.4
@ -78,8 +81,8 @@ importers:
specifier: ^1.9.1 specifier: ^1.9.1
version: 1.9.1 version: 1.9.1
bullmq: bullmq:
specifier: ^5.16.0 specifier: ^5.17.1
version: 5.16.0 version: 5.17.1
class-variance-authority: class-variance-authority:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
@ -106,7 +109,7 @@ importers:
version: 4.29.2 version: 4.29.2
formsnap: formsnap:
specifier: ^1.0.1 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: handlebars:
specifier: ^4.7.8 specifier: ^4.7.8
version: 4.7.8 version: 4.7.8
@ -119,6 +122,9 @@ importers:
hono-rate-limiter: hono-rate-limiter:
specifier: ^0.4.0 specifier: ^0.4.0
version: 0.4.0(hono@4.6.3) 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: html-entities:
specifier: ^2.5.2 specifier: ^2.5.2
version: 2.5.2 version: 2.5.2
@ -184,10 +190,10 @@ importers:
version: 2.5.3 version: 2.5.3
tailwind-variants: tailwind-variants:
specifier: ^0.2.1 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: tailwindcss-animate:
specifier: ^1.0.7 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: tsyringe:
specifier: ^4.8.0 specifier: ^4.8.0
version: 4.8.0 version: 4.8.0
@ -212,22 +218,22 @@ importers:
version: 1.47.2 version: 1.47.2
'@sveltejs/adapter-auto': '@sveltejs/adapter-auto':
specifier: ^3.2.5 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': '@sveltejs/enhanced-img':
specifier: ^0.3.8 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': '@sveltejs/kit':
specifier: ^2.6.2 specifier: ^2.6.3
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)) 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': '@sveltejs/vite-plugin-svelte':
specifier: 4.0.0-next.7 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': '@types/cookie':
specifier: ^0.6.0 specifier: ^0.6.0
version: 0.6.0 version: 0.6.0
'@types/node': '@types/node':
specifier: ^20.16.10 specifier: ^20.16.11
version: 20.16.10 version: 20.16.11
'@types/pg': '@types/pg':
specifier: ^8.11.10 specifier: ^8.11.10
version: 8.11.10 version: 8.11.10
@ -257,7 +263,7 @@ importers:
version: 9.1.0(eslint@8.57.1) version: 9.1.0(eslint@8.57.1)
eslint-plugin-svelte: eslint-plugin-svelte:
specifier: 2.36.0-next.13 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: just-clone:
specifier: ^6.2.0 specifier: ^6.2.0
version: 6.2.0 version: 6.2.0
@ -311,16 +317,16 @@ importers:
version: 2.0.2 version: 2.0.2
sveltekit-flash-message: sveltekit-flash-message:
specifier: ^2.4.4 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: sveltekit-superforms:
specifier: ^2.19.1 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: tailwindcss:
specifier: ^3.4.13 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: ts-node:
specifier: ^10.9.2 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: tslib:
specifier: ^2.7.0 specifier: ^2.7.0
version: 2.7.0 version: 2.7.0
@ -332,10 +338,10 @@ importers:
version: 5.6.2 version: 5.6.2
vite: vite:
specifier: ^5.4.8 specifier: ^5.4.8
version: 5.4.8(@types/node@20.16.10) version: 5.4.8(@types/node@20.16.11)
vitest: vitest:
specifier: ^1.6.0 specifier: ^1.6.0
version: 1.6.0(@types/node@20.16.10) version: 1.6.0(@types/node@20.16.11)
zod: zod:
specifier: ^3.23.8 specifier: ^3.23.8
version: 3.23.8 version: 3.23.8
@ -1447,6 +1453,12 @@ packages:
hono: '>=3.9.0' hono: '>=3.9.0'
zod: ^3.19.1 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': '@humanwhocodes/config-array@0.13.0':
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@ -2131,6 +2143,20 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] 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': '@sideway/address@4.1.5':
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
@ -2156,8 +2182,8 @@ packages:
peerDependencies: peerDependencies:
'@sveltejs/kit': ^2.4.0 '@sveltejs/kit': ^2.4.0
'@sveltejs/adapter-vercel@5.4.4': '@sveltejs/adapter-vercel@5.4.5':
resolution: {integrity: sha512-KORoxxqB2H5DrxpCHc9Yfijcgvmoaaz6G6eKHEg9fRlTsujJkxN26C0x4YlcgxqDU4dLIi1wfSLHpuZD0E4Irg==} resolution: {integrity: sha512-SROpUbjSZ1Xni4xuS22dunXFLjYzvTZwppqixIQFzTrf9oWcZEm2OfO+VgnrOT67LOcWQefJp7VSrpmjV691yQ==}
peerDependencies: peerDependencies:
'@sveltejs/kit': ^2.4.0 '@sveltejs/kit': ^2.4.0
@ -2167,8 +2193,8 @@ packages:
svelte: ^4.0.0 || ^5.0.0-next.0 svelte: ^4.0.0 || ^5.0.0-next.0
vite: '>= 5.0.0' vite: '>= 5.0.0'
'@sveltejs/kit@2.6.2': '@sveltejs/kit@2.6.3':
resolution: {integrity: sha512-ruogrSPXjckn5poUiZU8VYNCSPHq66SFR1AATvOikQxtP6LNI4niAZVX/AWZRe/EPDG3oY2DNJ9c5z7u0t2NAQ==} resolution: {integrity: sha512-baIAnmfMqAISrPtTC/22w6ay5kTEIQ/vq9bctiaQgRIoLCPBNhb6LEidTuWQS7OzPYCDBMuMX1t/fMvi4r3q/g==}
engines: {node: '>=18.13'} engines: {node: '>=18.13'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -2224,8 +2250,8 @@ packages:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/node@20.16.10': '@types/node@20.16.11':
resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==}
'@types/pg@8.11.10': '@types/pg@8.11.10':
resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==}
@ -2322,6 +2348,9 @@ packages:
'@ungap/structured-clone@1.2.0': '@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
'@unhead/schema@1.11.7':
resolution: {integrity: sha512-j9uN7T63aUXrZ6yx2CfjVT7xZHjn0PZO7TPMaWqMFjneIH/NONKvDVCMEqDlXeqdSIERIYtk/xTHgCUMer5eyw==}
'@vercel/nft@0.27.4': '@vercel/nft@0.27.4':
resolution: {integrity: sha512-Rioz3LJkEKicKCi9BSyc1RXZ5R6GmXosFMeBSThh6msWSOiArKhb7c75MiWwZEgPL7x0/l3TAfH/l0cxKNuUFA==} resolution: {integrity: sha512-Rioz3LJkEKicKCi9BSyc1RXZ5R6GmXosFMeBSThh6msWSOiArKhb7c75MiWwZEgPL7x0/l3TAfH/l0cxKNuUFA==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -2525,8 +2554,8 @@ packages:
buffer@6.0.3: buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bullmq@5.16.0: bullmq@5.17.1:
resolution: {integrity: sha512-7FaZzHsRXFOxrxCQTNKowuo9PPRwnAOpYXB5tcfk8vg0IbuVQ/je1Bf228Zy29gCS/5ytIEJNVB/DDGwZM0wbA==} resolution: {integrity: sha512-wqjhdJptb3KLeuUO+ZfsjVHqHuPtHhhoAE2XM8zvnidk6uU/ELo16uU6CVTBhliAupgvPE9jWE7tchR0GCtsdA==}
bytes@3.1.2: bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
@ -2657,10 +2686,6 @@ packages:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
core-js@3.38.1: core-js@3.38.1:
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
@ -3312,10 +3337,19 @@ packages:
peerDependencies: peerDependencies:
hono: ^4.1.1 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: hono@4.6.3:
resolution: {integrity: sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==} resolution: {integrity: sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==}
engines: {node: '>=16.9.0'} engines: {node: '>=16.9.0'}
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
html-entities@2.5.2: html-entities@2.5.2:
resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==}
@ -5110,9 +5144,18 @@ packages:
yup@1.4.0: yup@1.4.0:
resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==}
zhead@2.2.4:
resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==}
zimmerframe@1.1.2: zimmerframe@1.1.2:
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} 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: zod-to-json-schema@3.23.3:
resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==}
peerDependencies: peerDependencies:
@ -5885,6 +5928,11 @@ snapshots:
hono: 4.6.3 hono: 4.6.3
zod: 3.23.8 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': '@humanwhocodes/config-array@0.13.0':
dependencies: dependencies:
'@humanwhocodes/object-schema': 2.0.3 '@humanwhocodes/object-schema': 2.0.3
@ -6459,6 +6507,18 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.24.0': '@rollup/rollup-win32-x64-msvc@4.24.0':
optional: true 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': '@sideway/address@4.1.5':
dependencies: dependencies:
'@hapi/hoek': 9.3.0 '@hapi/hoek': 9.3.0
@ -6475,43 +6535,43 @@ snapshots:
'@sinclair/typebox@0.32.35': '@sinclair/typebox@0.32.35':
optional: true 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: 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 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: dependencies:
'@rollup/plugin-commonjs': 28.0.0(rollup@4.24.0) '@rollup/plugin-commonjs': 28.0.0(rollup@4.24.0)
'@rollup/plugin-json': 6.1.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) '@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 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: 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 '@vercel/nft': 0.27.4
esbuild: 0.21.5 esbuild: 0.21.5
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- supports-color - 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: dependencies:
magic-string: 0.30.11 magic-string: 0.30.11
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
svelte-parse-markup: 0.1.5(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) vite-imagetools: 7.0.4(rollup@4.24.0)
transitivePeerDependencies: transitivePeerDependencies:
- rollup - 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: 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 '@types/cookie': 0.6.0
cookie: 0.7.2 cookie: 0.6.0
devalue: 5.1.1 devalue: 5.1.1
esm-env: 1.0.0 esm-env: 1.0.0
import-meta-resolve: 4.1.0 import-meta-resolve: 4.1.0
@ -6523,27 +6583,27 @@ snapshots:
sirv: 2.0.4 sirv: 2.0.4
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
tiny-glob: 0.2.9 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: 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 debug: 4.3.7
svelte: 5.0.0-next.175 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: transitivePeerDependencies:
- supports-color - 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: 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 debug: 4.3.7
deepmerge: 4.3.1 deepmerge: 4.3.1
kleur: 4.1.5 kleur: 4.1.5
magic-string: 0.30.11 magic-string: 0.30.11
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
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.10)) vitefu: 1.0.2(vite@5.4.8(@types/node@20.16.11))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -6578,19 +6638,19 @@ snapshots:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
optional: true optional: true
'@types/node@20.16.10': '@types/node@20.16.11':
dependencies: dependencies:
undici-types: 6.19.8 undici-types: 6.19.8
'@types/pg@8.11.10': '@types/pg@8.11.10':
dependencies: dependencies:
'@types/node': 20.16.10 '@types/node': 20.16.11
pg-protocol: 1.7.0 pg-protocol: 1.7.0
pg-types: 4.0.2 pg-types: 4.0.2
'@types/pg@8.11.6': '@types/pg@8.11.6':
dependencies: dependencies:
'@types/node': 20.16.10 '@types/node': 20.16.11
pg-protocol: 1.7.0 pg-protocol: 1.7.0
pg-types: 4.0.2 pg-types: 4.0.2
@ -6598,7 +6658,7 @@ snapshots:
'@types/qrcode@1.5.5': '@types/qrcode@1.5.5':
dependencies: dependencies:
'@types/node': 20.16.10 '@types/node': 20.16.11
'@types/resolve@1.20.2': {} '@types/resolve@1.20.2': {}
@ -6702,6 +6762,11 @@ snapshots:
'@ungap/structured-clone@1.2.0': {} '@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': '@vercel/nft@0.27.4':
dependencies: dependencies:
'@mapbox/node-pre-gyp': 1.0.11 '@mapbox/node-pre-gyp': 1.0.11
@ -6943,7 +7008,7 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
bullmq@5.16.0: bullmq@5.17.1:
dependencies: dependencies:
cron-parser: 4.9.0 cron-parser: 4.9.0
ioredis: 5.4.1 ioredis: 5.4.1
@ -7084,8 +7149,6 @@ snapshots:
cookie@0.6.0: {} cookie@0.6.0: {}
cookie@0.7.2: {}
core-js@3.38.1: {} core-js@3.38.1: {}
create-require@1.1.1: {} create-require@1.1.1: {}
@ -7404,7 +7467,7 @@ snapshots:
dependencies: dependencies:
eslint: 8.57.1 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: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1)
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
@ -7414,7 +7477,7 @@ snapshots:
esutils: 2.0.3 esutils: 2.0.3
known-css-properties: 0.30.0 known-css-properties: 0.30.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))
postcss-safe-parser: 6.0.0(postcss@8.4.47) postcss-safe-parser: 6.0.0(postcss@8.4.47)
postcss-selector-parser: 6.1.2 postcss-selector-parser: 6.1.2
semver: 7.6.3 semver: 7.6.3
@ -7650,11 +7713,11 @@ snapshots:
cross-spawn: 7.0.3 cross-spawn: 7.0.3
signal-exit: 4.1.0 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: dependencies:
nanoid: 5.0.7 nanoid: 5.0.7
svelte: 5.0.0-next.175 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: {} forwarded@0.2.0: {}
@ -7797,8 +7860,17 @@ snapshots:
dependencies: dependencies:
hono: 4.6.3 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: {} hono@4.6.3: {}
hookable@5.5.3: {}
html-entities@2.5.2: {} html-entities@2.5.2: {}
http-errors@2.0.0: http-errors@2.0.0:
@ -8531,21 +8603,21 @@ snapshots:
'@csstools/utilities': 1.0.0(postcss@8.4.47) '@csstools/utilities': 1.0.0(postcss@8.4.47)
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: dependencies:
lilconfig: 2.1.0 lilconfig: 2.1.0
yaml: 1.10.2 yaml: 1.10.2
optionalDependencies: optionalDependencies:
postcss: 8.4.47 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: dependencies:
lilconfig: 3.1.2 lilconfig: 3.1.2
yaml: 2.5.1 yaml: 2.5.1
optionalDependencies: optionalDependencies:
postcss: 8.4.47 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): postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1):
dependencies: dependencies:
@ -9220,14 +9292,14 @@ snapshots:
magic-string: 0.30.11 magic-string: 0.30.11
zimmerframe: 1.1.2 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: 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 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: 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 devalue: 5.1.1
just-clone: 6.2.0 just-clone: 6.2.0
memoize-weak: 1.0.2 memoize-weak: 1.0.2
@ -9255,16 +9327,16 @@ snapshots:
tailwind-merge@2.5.3: {} 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: dependencies:
tailwind-merge: 2.5.3 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: 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: dependencies:
'@alloc/quick-lru': 5.2.0 '@alloc/quick-lru': 5.2.0
arg: 5.0.2 arg: 5.0.2
@ -9283,7 +9355,7 @@ snapshots:
postcss: 8.4.47 postcss: 8.4.47
postcss-import: 15.1.0(postcss@8.4.47) postcss-import: 15.1.0(postcss@8.4.47)
postcss-js: 4.0.1(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-nested: 6.2.0(postcss@8.4.47)
postcss-selector-parser: 6.1.2 postcss-selector-parser: 6.1.2
resolve: 1.22.8 resolve: 1.22.8
@ -9352,14 +9424,14 @@ snapshots:
ts-interface-checker@0.1.13: {} 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: dependencies:
'@cspotcode/source-map-support': 0.8.1 '@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11 '@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11 '@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3 '@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4 '@tsconfig/node16': 1.0.4
'@types/node': 20.16.10 '@types/node': 20.16.11
acorn: 8.12.1 acorn: 8.12.1
acorn-walk: 8.3.4 acorn-walk: 8.3.4
arg: 4.1.3 arg: 4.1.3
@ -9454,13 +9526,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
vite-node@1.6.0(@types/node@20.16.10): vite-node@1.6.0(@types/node@20.16.11):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
debug: 4.3.7 debug: 4.3.7
pathe: 1.1.2 pathe: 1.1.2
picocolors: 1.1.0 picocolors: 1.1.0
vite: 5.4.8(@types/node@20.16.10) vite: 5.4.8(@types/node@20.16.11)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- less - less
@ -9472,20 +9544,20 @@ snapshots:
- supports-color - supports-color
- terser - terser
vite@5.4.8(@types/node@20.16.10): vite@5.4.8(@types/node@20.16.11):
dependencies: dependencies:
esbuild: 0.21.5 esbuild: 0.21.5
postcss: 8.4.47 postcss: 8.4.47
rollup: 4.24.0 rollup: 4.24.0
optionalDependencies: optionalDependencies:
'@types/node': 20.16.10 '@types/node': 20.16.11
fsevents: 2.3.3 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: 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: dependencies:
'@vitest/expect': 1.6.0 '@vitest/expect': 1.6.0
'@vitest/runner': 1.6.0 '@vitest/runner': 1.6.0
@ -9504,11 +9576,11 @@ snapshots:
strip-literal: 2.1.0 strip-literal: 2.1.0
tinybench: 2.9.0 tinybench: 2.9.0
tinypool: 0.8.4 tinypool: 0.8.4
vite: 5.4.8(@types/node@20.16.10) vite: 5.4.8(@types/node@20.16.11)
vite-node: 1.6.0(@types/node@20.16.10) vite-node: 1.6.0(@types/node@20.16.11)
why-is-node-running: 2.3.0 why-is-node-running: 2.3.0
optionalDependencies: optionalDependencies:
'@types/node': 20.16.10 '@types/node': 20.16.11
transitivePeerDependencies: transitivePeerDependencies:
- less - less
- lightningcss - lightningcss
@ -9608,8 +9680,14 @@ snapshots:
type-fest: 2.19.0 type-fest: 2.19.0
optional: true optional: true
zhead@2.2.4: {}
zimmerframe@1.1.2: {} 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): zod-to-json-schema@3.23.3(zod@3.23.8):
dependencies: dependencies:
zod: 3.23.8 zod: 3.23.8

View file

@ -1,13 +0,0 @@
<script lang="ts">
import { PUBLIC_PLAUSIBLE_URL, PUBLIC_SITE_URL } from '$env/static/public'
const src = `${PUBLIC_PLAUSIBLE_URL}/js/script.js`
const dataDomain = PUBLIC_SITE_URL.replace('https://', '').replace('http://', '')
</script>
<svelte:head>
<script
defer
data-domain={dataDomain}
{src}
></script>
</svelte:head>

View file

@ -1,8 +1,8 @@
import env from './env' import env from './env';
import type { Config } from './types/config' import type { Config } from './types/config';
export const config: Config = { export const config: Config = {
isProduction: process.env.NODE_ENV === 'production', isProduction: env.NODE_ENV === 'production',
domain: env.DOMAIN, domain: env.DOMAIN,
api: { api: {
origin: env.ORIGIN, origin: env.ORIGIN,
@ -19,4 +19,4 @@ export const config: Config = {
ssl: false, // env.DATABASE_HOST !== 'localhost', ssl: false, // env.DATABASE_HOST !== 'localhost',
max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined, max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined,
}, },
} };

View file

@ -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<AppBindings>({
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;
}

View file

@ -1,13 +1,15 @@
import { config } from 'dotenv' import { config } from 'dotenv';
import { expand } from 'dotenv-expand' import { expand } from 'dotenv-expand';
import { ZodError, z } from 'zod' import { type ZodError, z } from 'zod';
expand(config());
const stringBoolean = z.coerce const stringBoolean = z.coerce
.string() .string()
.transform((val) => { .transform((val) => {
return val === 'true' return val === 'true';
}) })
.default('false') .default('false');
const EnvSchema = z.object({ const EnvSchema = z.object({
DATABASE_USER: z.string(), DATABASE_USER: z.string(),
@ -22,34 +24,29 @@ const EnvSchema = z.object({
GITHUB_CLIENT_SECRET: z.string(), GITHUB_CLIENT_SECRET: z.string(),
GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: 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'), NODE_ENV: z.string().default('development'),
ORIGIN: z.string(), ORIGIN: z.string(),
PUBLIC_SITE_NAME: z.string(), PUBLIC_SITE_NAME: z.string(),
PUBLIC_SITE_URL: 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_ID: z.string(),
PUBLIC_UMAMI_URL: z.string(), PUBLIC_UMAMI_URL: z.string(),
REDIS_URL: z.string(), REDIS_URL: z.string(),
TWO_FACTOR_TIMEOUT: z.coerce.number().default(300000), TWO_FACTOR_TIMEOUT: z.coerce.number().default(300000),
}) });
export type EnvSchema = z.infer<typeof EnvSchema> export type env = z.infer<typeof EnvSchema>;
expand(config()) let env: env;
try { try {
EnvSchema.parse(process.env) env = EnvSchema.parse(process.env);
} catch (error) { } catch (e) {
if (error instanceof ZodError) { const error = e as ZodError;
let message = 'Missing required values in .env:\n' console.error('❌ Missing required values in .env:\n');
for (const issue of error.issues) { console.error(error.flatten().fieldErrors);
message += `${issue.path[0]}\n` process.exit(1);
}
const e = new Error(message)
e.stack = ''
throw e
}
console.error(error)
} }
export default EnvSchema.parse(process.env) export default env;

View file

@ -1,14 +1,29 @@
import type { PinoLogger } from 'hono-pino';
import type { Promisify, RateLimitInfo } from 'hono-rate-limiter'; import type { Promisify, RateLimitInfo } from 'hono-rate-limiter';
import type { Session, User } from 'lucia'; import type { Session, User } from 'lucia';
export type HonoTypes = { export type AppBindings = {
Variables: { Variables: {
logger: PinoLogger;
session: Session | null; session: Session | null;
user: User | null; user: User | null;
rateLimit: RateLimitInfo; rateLimit: RateLimitInfo;
rateLimitStore: { rateLimitStore: {
getKey?: (key: string) => Promisify<RateLimitInfo | undefined>; getKey?: (key: string) => Promisify<RateLimitInfo | undefined>;
resetKey: (key: string) => Promisify<void>; resetKey: (key: string) => Promisify<void>;
}; };
};
};
export type HonoTypes = {
Variables: {
logger: PinoLogger;
session: Session | null;
user: User | null;
rateLimit: RateLimitInfo;
rateLimitStore: {
getKey?: (key: string) => Promisify<RateLimitInfo | undefined>;
resetKey: (key: string) => Promisify<void>;
};
}; };
}; };

View file

@ -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<AppBindings>) {
// createOpenApiDocument(app, {
// info: {
// title: 'Example API',
// version: '1.0.0',
// },
// });
// }

View file

@ -1,17 +1,17 @@
import { StatusCodes } from '$lib/constants/status-codes' import { StatusCodes } from '$lib/constants/status-codes';
import { Controller } from '$lib/server/api/common/types/controller' import { Controller } from '$lib/server/api/common/types/controller';
import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto' import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto';
import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto' import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto';
import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto' import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto';
import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto' import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto';
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware' import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware';
import { IamService } from '$lib/server/api/services/iam.service' import { IamService } from '$lib/server/api/services/iam.service';
import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service' import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service';
import { LuciaService } from '$lib/server/api/services/lucia.service' import { LuciaService } from '$lib/server/api/services/lucia.service';
import { zValidator } from '@hono/zod-validator' import { zValidator } from '@hono/zod-validator';
import { setCookie } from 'hono/cookie' import { setCookie } from 'hono/cookie';
import { inject, injectable } from 'tsyringe' import { inject, injectable } from 'tsyringe';
import { requireAuth } from '../middleware/require-auth.middleware' import { requireAuth } from '../middleware/require-auth.middleware';
@injectable() @injectable()
export class IamController extends Controller { export class IamController extends Controller {
@ -20,45 +20,47 @@ export class IamController extends Controller {
@inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService, @inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService,
@inject(LuciaService) private luciaService: LuciaService, @inject(LuciaService) private luciaService: LuciaService,
) { ) {
super() super();
} }
routes() { routes() {
const tags = ['IAM'];
return this.controller return this.controller
.get('/', requireAuth, async (c) => { .get('/', requireAuth, async (c) => {
const user = c.var.user const user = c.var.user;
return c.json({ user }) return c.json({ user });
}) })
.put('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 30, minutes: 60 }), async (c) => { .put('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 30, minutes: 60 }), async (c) => {
const user = c.var.user const user = c.var.user;
const { firstName, lastName, username } = c.req.valid('json') const { firstName, lastName, username } = c.req.valid('json');
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username }) const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username });
if (!updatedUser) { 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) => { .post('/verify/password', requireAuth, zValidator('json', verifyPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const user = c.var.user const user = c.var.user;
const { password } = c.req.valid('json') const { password } = c.req.valid('json');
const passwordVerified = await this.iamService.verifyPassword(user.id, { password }) const passwordVerified = await this.iamService.verifyPassword(user.id, { password });
if (!passwordVerified) { if (!passwordVerified) {
console.log('Incorrect password') console.log('Incorrect password');
return c.json('Incorrect password', StatusCodes.BAD_REQUEST) 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) => { .put('/update/password', requireAuth, zValidator('json', changePasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const user = c.var.user const user = c.var.user;
const { password, confirm_password } = c.req.valid('json') const { password, confirm_password } = c.req.valid('json');
if (password !== confirm_password) { 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 { try {
await this.iamService.updatePassword(user.id, { password, confirm_password }) await this.iamService.updatePassword(user.id, { password, confirm_password });
await this.luciaService.lucia.invalidateUserSessions(user.id) await this.luciaService.lucia.invalidateUserSessions(user.id);
await this.loginRequestService.createUserSession(user.id, c.req, undefined) await this.loginRequestService.createUserSession(user.id, c.req, undefined);
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie() const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
setCookie(c, sessionCookie.name, sessionCookie.value, { setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path, path: sessionCookie.attributes.path,
maxAge: sessionCookie.attributes.maxAge, maxAge: sessionCookie.attributes.maxAge,
@ -67,26 +69,26 @@ export class IamController extends Controller {
secure: sessionCookie.attributes.secure, secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly, httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires, expires: sessionCookie.attributes.expires,
}) });
return c.json({ status: 'success' }) return c.json({ status: 'success' });
} catch (error) { } catch (error) {
console.error('Error updating password', error) console.error('Error updating password', error);
return c.json('Error updating password', StatusCodes.BAD_REQUEST) return c.json('Error updating password', StatusCodes.BAD_REQUEST);
} }
}) })
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { .post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const user = c.var.user const user = c.var.user;
const { email } = c.req.valid('json') const { email } = c.req.valid('json');
const updatedUser = await this.iamService.updateEmail(user.id, { email }) const updatedUser = await this.iamService.updateEmail(user.id, { email });
if (!updatedUser) { 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) => { .post('/logout', requireAuth, async (c) => {
const sessionId = c.var.session.id const sessionId = c.var.session.id;
await this.iamService.logout(sessionId) await this.iamService.logout(sessionId);
const sessionCookie = this.luciaService.lucia.createBlankSessionCookie() const sessionCookie = this.luciaService.lucia.createBlankSessionCookie();
setCookie(c, sessionCookie.name, sessionCookie.value, { setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path, path: sessionCookie.attributes.path,
maxAge: sessionCookie.attributes.maxAge, maxAge: sessionCookie.attributes.maxAge,
@ -95,8 +97,8 @@ export class IamController extends Controller {
secure: sessionCookie.attributes.secure, secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly, httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires, expires: sessionCookie.attributes.expires,
}) });
return c.json({ status: 'success' }) return c.json({ status: 'success' });
}) });
} }
} }

View file

@ -1,30 +1,30 @@
import 'reflect-metadata' import 'reflect-metadata';
import { Controller } from '$lib/server/api/common/types/controller' import { Controller } from '$lib/server/api/common/types/controller';
import { UsersService } from '$lib/server/api/services/users.service' import { UsersService } from '$lib/server/api/services/users.service';
import { inject, injectable } from 'tsyringe' import { inject, injectable } from 'tsyringe';
import { requireAuth } from '../middleware/require-auth.middleware' import { requireAuth } from '../middleware/require-auth.middleware';
@injectable() @injectable()
export class UserController extends Controller { export class UserController extends Controller {
constructor(@inject(UsersService) private readonly usersService: UsersService) { constructor(@inject(UsersService) private readonly usersService: UsersService) {
super() super();
} }
routes() { routes() {
return this.controller return this.controller
.get('/', async (c) => { .get('/', async (c) => {
const user = c.var.user const user = c.var.user;
return c.json({ user }) return c.json({ user });
}) })
.get('/:id', requireAuth, async (c) => { .get('/:id', requireAuth, async (c) => {
const id = c.req.param('id') const id = c.req.param('id');
const user = await this.usersService.findOneById(id) const user = await this.usersService.findOneById(id);
return c.json({ user }) return c.json({ user });
}) })
.get('/username/:userName', requireAuth, async (c) => { .get('/username/:userName', requireAuth, async (c) => {
const userName = c.req.param('userName') const userName = c.req.param('userName');
const user = await this.usersService.findOneByUsername(userName) const user = await this.usersService.findOneByUsername(userName);
return c.json({ user }) return c.json({ user });
}) });
} }
} }

View file

@ -1,20 +1,19 @@
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm';
import type { db } from '../../packages/drizzle' import type { db } from '../../packages/drizzle';
import { HashingService } from '../../services/hashing.service' import { HashingService } from '../../services/hashing.service';
import * as schema from '../tables' import * as schema from '../tables';
import users from './data/users.json' import users from './data/users.json';
type JsonRole = { type JsonRole = {
name: string name: string;
primary: boolean primary: boolean;
} };
export default async function seed(db: db) { export default async function seed(db: db) {
const hashingService = new HashingService() const hashingService = new HashingService();
const adminRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'admin')) 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 userRole = await db.select().from(schema.rolesTable).where(eq(schema.rolesTable.name, 'user'));
console.log('Admin Role: ', adminRole)
const adminUser = await db const adminUser = await db
.insert(schema.usersTable) .insert(schema.usersTable)
.values({ .values({
@ -25,19 +24,17 @@ export default async function seed(db: db) {
verified: true, verified: true,
}) })
.returning() .returning()
.onConflictDoNothing() .onConflictDoNothing();
console.log('Admin user created.', adminUser)
await db.insert(schema.credentialsTable).values({ await db.insert(schema.credentialsTable).values({
user_id: adminUser[0].id, user_id: adminUser[0].id,
type: schema.CredentialsType.PASSWORD, type: schema.CredentialsType.PASSWORD,
secret_data: await hashingService.hash(`${process.env.ADMIN_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 await db
.insert(schema.user_roles) .insert(schema.user_roles)
@ -46,9 +43,7 @@ export default async function seed(db: db) {
role_id: adminRole[0].id, role_id: adminRole[0].id,
primary: true, primary: true,
}) })
.onConflictDoNothing() .onConflictDoNothing();
console.log('Admin user given admin role.')
await db await db
.insert(schema.user_roles) .insert(schema.user_roles)
@ -57,10 +52,8 @@ export default async function seed(db: db) {
role_id: userRole[0].id, role_id: userRole[0].id,
primary: false, primary: false,
}) })
.onConflictDoNothing() .onConflictDoNothing();
console.log('Admin user given user role.')
const hasingService = new HashingService()
await Promise.all( await Promise.all(
users.map(async (user) => { users.map(async (user) => {
const [insertedUser] = await db const [insertedUser] = await db
@ -68,29 +61,29 @@ export default async function seed(db: db) {
.values({ .values({
...user, ...user,
}) })
.returning() .returning();
await db.insert(schema.credentialsTable).values({ await db.insert(schema.credentialsTable).values({
user_id: insertedUser?.id, user_id: insertedUser?.id,
type: schema.CredentialsType.PASSWORD, type: schema.CredentialsType.PASSWORD,
secret_data: await hasingService.hash(user.password), secret_data: await hashingService.hash(user.password),
}) });
await db.insert(schema.collections).values({ user_id: insertedUser?.id }) await db.insert(schema.collections).values({ user_id: insertedUser?.id });
await db.insert(schema.wishlistsTable).values({ user_id: insertedUser?.id }) await db.insert(schema.wishlistsTable).values({ user_id: insertedUser?.id });
await Promise.all( await Promise.all(
user.roles.map(async (role: JsonRole) => { user.roles.map(async (role: JsonRole) => {
const foundRole = await db.query.rolesTable.findFirst({ const foundRole = await db.query.rolesTable.findFirst({
where: eq(schema.rolesTable.name, role.name), where: eq(schema.rolesTable.name, role.name),
}) });
if (!foundRole) { if (!foundRole) {
throw new Error('Role not found') throw new Error('Role not found');
} }
await db.insert(schema.user_roles).values({ await db.insert(schema.user_roles).values({
user_id: insertedUser?.id, user_id: insertedUser?.id,
role_id: foundRole?.id, role_id: foundRole?.id,
primary: role?.primary, primary: role?.primary,
}) });
}), }),
) );
}), }),
) );
} }

View file

@ -1,53 +1,21 @@
import 'reflect-metadata' import 'reflect-metadata';
import { CollectionController } from '$lib/server/api/controllers/collection.controller' import createApp from '$lib/server/api/common/create-app';
import { MfaController } from '$lib/server/api/controllers/mfa.controller' import { CollectionController } from '$lib/server/api/controllers/collection.controller';
import { OAuthController } from '$lib/server/api/controllers/oauth.controller' import { MfaController } from '$lib/server/api/controllers/mfa.controller';
import { SignupController } from '$lib/server/api/controllers/signup.controller' import { OAuthController } from '$lib/server/api/controllers/oauth.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 { UserController } from '$lib/server/api/controllers/user.controller';
import { AuthCleanupJobs } from '$lib/server/api/jobs/auth-cleanup.job' import { WishlistController } from '$lib/server/api/controllers/wishlist.controller';
import { OpenAPIHono } from '@hono/zod-openapi' import { AuthCleanupJobs } from '$lib/server/api/jobs/auth-cleanup.job';
import type { PinoLogger } from 'hono-pino' import { hc } from 'hono/client';
import { hc } from 'hono/client' import { container } from 'tsyringe';
import { cors } from 'hono/cors' import { config } from './common/config';
import { notFound, onError } from 'stoker/middlewares'; import { IamController } from './controllers/iam.controller';
import { container } from 'tsyringe' import { LoginController } from './controllers/login.controller';
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'
type AppBindings = { export const app = createApp();
Variables: {
logger: PinoLogger
}
}
/* -------------------------------------------------------------------------- */ // configureOpenAPI(app);
/* App */
/* -------------------------------------------------------------------------- */
export const app = new OpenAPIHono<AppBindings>().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
}),
)
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Routes */ /* Routes */
@ -61,22 +29,17 @@ const routes = app
.route('/wishlists', container.resolve(WishlistController).routes()) .route('/wishlists', container.resolve(WishlistController).routes())
.route('/collections', container.resolve(CollectionController).routes()) .route('/collections', container.resolve(CollectionController).routes())
.route('/mfa', container.resolve(MfaController).routes()) .route('/mfa', container.resolve(MfaController).routes())
.get('/', (c) => c.json({ message: 'Server is healthy' })) .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')
})
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Cron Jobs */ /* Cron Jobs */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests() container.resolve(AuthCleanupJobs).deleteStaleEmailVerificationRequests();
container.resolve(AuthCleanupJobs).deleteStaleLoginRequests() container.resolve(AuthCleanupJobs).deleteStaleLoginRequests();
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Exports */ /* Exports */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
export const rpc = hc<typeof routes>(config.api.origin) export const rpc = hc<typeof routes>(config.api.origin);
export type ApiClient = typeof rpc export type ApiClient = typeof rpc;
export type ApiRoutes = typeof routes export type ApiRoutes = typeof routes;

View file

@ -1,41 +1,41 @@
import { LuciaService } from '$lib/server/api/services/lucia.service' import { LuciaService } from '$lib/server/api/services/lucia.service';
import type { MiddlewareHandler } from 'hono' import type { MiddlewareHandler } from 'hono';
import { createMiddleware } from 'hono/factory' import { createMiddleware } from 'hono/factory';
import { verifyRequestOrigin } from 'oslo/request' import { verifyRequestOrigin } from 'oslo/request';
import { container } from 'tsyringe' import { container } from 'tsyringe';
import type { HonoTypes } from '../common/types/hono' import type { AppBindings } from '../common/types/hono';
// resolve dependencies from the container // resolve dependencies from the container
const { lucia } = container.resolve(LuciaService) const { lucia } = container.resolve(LuciaService);
export const verifyOrigin: MiddlewareHandler<HonoTypes> = createMiddleware(async (c, next) => { export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
if (c.req.method === 'GET') { if (c.req.method === 'GET') {
return next() return next();
} }
const originHeader = c.req.header('Origin') ?? null const originHeader = c.req.header('Origin') ?? null;
const hostHeader = c.req.header('Host') ?? null const hostHeader = c.req.header('Host') ?? null;
if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) {
return c.body(null, 403) return c.body(null, 403);
} }
return next() return next();
}) });
export const validateAuthSession: MiddlewareHandler<HonoTypes> = createMiddleware(async (c, next) => { export const validateAuthSession: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
const sessionId = lucia.readSessionCookie(c.req.header('Cookie') ?? '') const sessionId = lucia.readSessionCookie(c.req.header('Cookie') ?? '');
if (!sessionId) { if (!sessionId) {
c.set('user', null) c.set('user', null);
c.set('session', null) c.set('session', null);
return next() return next();
} }
const { session, user } = await lucia.validateSession(sessionId) const { session, user } = await lucia.validateSession(sessionId);
if (session?.fresh) { 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) { 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('session', session);
c.set('user', user) c.set('user', user);
return next() return next();
}) });

View file

@ -1,14 +1,18 @@
import env from '$lib/server/api/common/env';
import { logger } from 'hono-pino'; import { logger } from 'hono-pino';
import pino from 'pino'; import pino from 'pino';
import pretty from 'pino-pretty'; import pretty from 'pino-pretty';
export function pinoLogger() { export function pinoLogger() {
return logger({ return logger({
pino: pino({ pino: pino(
level: process.env.LOG_LEVEL || 'info', {
}, process.env.NODE_ENV === 'production' ? undefined : pretty()), level: env.LOG_LEVEL || 'info',
},
env.NODE_ENV === 'production' ? undefined : pretty(),
),
http: { http: {
reqId: () => crypto.randomUUID(), reqId: () => crypto.randomUUID(),
} },
}); });
} }

View file

@ -1,35 +1,35 @@
import { rateLimiter } from 'hono-rate-limiter' import { rateLimiter } from 'hono-rate-limiter';
import { RedisStore } from 'rate-limit-redis' import { RedisStore } from 'rate-limit-redis';
import { container } from 'tsyringe' import { container } from 'tsyringe';
import type { HonoTypes } from '../common/types/hono' import type { AppBindings } from '../common/types/hono';
import { RedisService } from '../services/redis.service' import { RedisService } from '../services/redis.service';
// resolve dependencies from the container // resolve dependencies from the container
const { client } = container.resolve(RedisService) const { client } = container.resolve(RedisService);
export function limiter({ export function limiter({
limit, limit,
minutes, minutes,
key = '', key = '',
}: { }: {
limit: number limit: number;
minutes: number minutes: number;
key?: string key?: string;
}) { }) {
return rateLimiter({ return rateLimiter({
windowMs: minutes * 60 * 1000, // every x minutes windowMs: minutes * 60 * 1000, // every x minutes
limit, // Limit each IP to 100 requests per `window` (here, per 15 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 standardHeaders: 'draft-6', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
keyGenerator: (c) => { keyGenerator: (c) => {
const vars = c.var as HonoTypes['Variables'] const vars = c.var as AppBindings['Variables'];
const clientKey = vars.user?.id || c.req.header('x-forwarded-for') const clientKey = vars.user?.id || c.req.header('x-forwarded-for');
const pathKey = key || c.req.routePath const pathKey = key || c.req.routePath;
return `${clientKey}_${pathKey}` return `${clientKey}_${pathKey}`;
}, // Method to generate custom identifiers for clients. }, // Method to generate custom identifiers for clients.
// Redis store configuration // Redis store configuration
store: new RedisStore({ store: new RedisStore({
// @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis // @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
sendCommand: (...args: string[]) => client.call(...args), sendCommand: (...args: string[]) => client.call(...args),
}) as any, }) as any,
}) });
} }

View file

@ -1,14 +1,15 @@
import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres' import env from '$lib/server/api/common/env';
import pg from 'pg' import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres';
import { type Disposable, injectable } from 'tsyringe' import pg from 'pg';
import { config } from '../common/config' import { type Disposable, injectable } from 'tsyringe';
import * as schema from '../databases/tables' import { config } from '../common/config';
import * as schema from '../databases/tables';
@injectable() @injectable()
export class DrizzleService implements Disposable { export class DrizzleService implements Disposable {
protected readonly pool: pg.Pool protected readonly pool: pg.Pool;
db: NodePgDatabase<typeof schema> db: NodePgDatabase<typeof schema>;
readonly schema: typeof schema = schema readonly schema: typeof schema = schema;
constructor() { constructor() {
const pool = new pg.Pool({ const pool = new pg.Pool({
@ -19,15 +20,15 @@ export class DrizzleService implements Disposable {
database: config.postgres.database, database: config.postgres.database,
ssl: config.postgres.ssl, ssl: config.postgres.ssl,
max: config.postgres.max, max: config.postgres.max,
}) });
this.pool = pool this.pool = pool;
this.db = drizzle(pool, { this.db = drizzle(pool, {
schema, schema,
logger: process.env.NODE_ENV === 'development', logger: env.NODE_ENV === 'development',
}) });
} }
dispose(): Promise<void> | void { dispose(): Promise<void> | void {
this.pool.end() this.pool.end();
} }
} }

View file

@ -1,52 +1,52 @@
import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository';
import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding' import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding';
import { verifyTOTP } from '@oslojs/otp' import { verifyTOTP } from '@oslojs/otp';
import { inject, injectable } from 'tsyringe' import { inject, injectable } from 'tsyringe';
import type { CredentialsType } from '../databases/tables' import type { CredentialsType } from '../databases/tables';
@injectable() @injectable()
export class TotpService { export class TotpService {
constructor(@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository) {} constructor(@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository) {}
async findOneByUserId(userId: string) { async findOneByUserId(userId: string) {
return this.credentialsRepository.findTOTPCredentialsByUserId(userId) return this.credentialsRepository.findTOTPCredentialsByUserId(userId);
} }
async findOneByUserIdOrThrow(userId: string) { async findOneByUserIdOrThrow(userId: string) {
const credential = await this.findOneByUserId(userId) const credential = await this.findOneByUserId(userId);
if (!credential) { if (!credential) {
throw new Error('TOTP credential not found') throw new Error('TOTP credential not found');
} }
return credential return credential;
} }
async create(userId: string) { async create(userId: string) {
const secret = new Uint8Array(20) const secret = new Uint8Array(20);
try { try {
return await this.credentialsRepository.create({ return await this.credentialsRepository.create({
user_id: userId, user_id: userId,
secret_data: encodeHexLowerCase(crypto.getRandomValues(secret)), secret_data: encodeHexLowerCase(crypto.getRandomValues(secret)),
type: 'totp', type: 'totp',
}) });
} catch (e) { } catch (e) {
console.error(e) console.error(e);
return null return null;
} }
} }
async deleteOneByUserId(userId: string) { async deleteOneByUserId(userId: string) {
return this.credentialsRepository.deleteByUserId(userId) return this.credentialsRepository.deleteByUserId(userId);
} }
async deleteOneByUserIdAndType(userId: string, type: CredentialsType) { async deleteOneByUserIdAndType(userId: string, type: CredentialsType) {
return this.credentialsRepository.deleteByUserIdAndType(userId, type) return this.credentialsRepository.deleteByUserIdAndType(userId, type);
} }
async verify(userId: string, code: string) { async verify(userId: string, code: string) {
const credential = await this.credentialsRepository.findTOTPCredentialsByUserId(userId) const credential = await this.credentialsRepository.findTOTPCredentialsByUserId(userId);
if (!credential) { 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);
} }
} }

View file

@ -1,23 +1,22 @@
<script lang="ts"> <script lang="ts">
import '$lib/styles/app.pcss' import '$lib/styles/app.pcss';
import { onMount } from 'svelte' import { onMount } from 'svelte';
import { MetaTags } from 'svelte-meta-tags' import { MetaTags } from 'svelte-meta-tags';
import { getFlash } from 'sveltekit-flash-message/client' import { getFlash } from 'sveltekit-flash-message/client';
import 'iconify-icon' import 'iconify-icon';
import { onNavigate } from '$app/navigation' import { onNavigate } from '$app/navigation';
import { page } from '$app/stores' import { page } from '$app/stores';
import Analytics from '$components/Analytics.svelte' import Analytics from '$components/Analytics.svelte';
import PlausibleAnalytics from '$components/PlausibleAnalytics.svelte' import { Toaster } from '$lib/components/ui/sonner';
import { Toaster } from '$lib/components/ui/sonner' import PageLoadingIndicator from '$lib/page_loading_indicator.svelte';
import PageLoadingIndicator from '$lib/page_loading_indicator.svelte' import { toastMessage } from '$lib/utils/superforms.js';
import { toastMessage } from '$lib/utils/superforms.js' import { theme } from '$state/theme';
import { theme } from '$state/theme'
// import { ModeWatcher } from 'mode-watcher' // import { ModeWatcher } from 'mode-watcher'
const dev = process.env.NODE_ENV !== 'production' const dev = process.env.NODE_ENV !== 'production';
const { data, children } = $props() const { data, children } = $props();
const { user } = data const { user } = data;
const metaTags = $derived({ const metaTags = $derived({
titleTemplate: '%s | Bored Game', titleTemplate: '%s | Bored Game',
@ -29,45 +28,44 @@ const metaTags = $derived({
description: 'Bored Game, keep track of your games', description: 'Bored Game, keep track of your games',
}, },
...$page.data.metaTagsChild, ...$page.data.metaTagsChild,
}) });
const flash = getFlash(page, { const flash = getFlash(page, {
clearOnNavigate: true, clearOnNavigate: true,
clearAfterMs: 3000, clearAfterMs: 3000,
clearArray: true, clearArray: true,
}) });
onMount(() => { onMount(() => {
// set the theme to the user's active theme // set the theme to the user's active theme
$theme = user?.theme || 'system' $theme = user?.theme || 'system';
document.querySelector('html')?.setAttribute('data-theme', $theme) document.querySelector('html')?.setAttribute('data-theme', $theme);
}) });
$effect(() => { $effect(() => {
console.log('flash', $flash) console.log('flash', $flash);
if ($flash) { if ($flash) {
toastMessage({ type: $flash.type, text: $flash.message }) toastMessage({ type: $flash.type, text: $flash.message });
// Clearing the flash message could sometimes // Clearing the flash message could sometimes
// be required here to avoid double-toasting. // be required here to avoid double-toasting.
flash.set(undefined) flash.set(undefined);
} }
}) });
onNavigate(async (navigation) => { onNavigate(async (navigation) => {
if (!document.startViewTransition) return if (!document.startViewTransition) return;
return new Promise((oldStateCaptureResolve) => { return new Promise((oldStateCaptureResolve) => {
document.startViewTransition(async () => { document.startViewTransition(async () => {
oldStateCaptureResolve() oldStateCaptureResolve();
await navigation.complete await navigation.complete;
}) });
}) });
}) });
</script> </script>
{#if !dev} {#if !dev}
<Analytics /> <Analytics />
<PlausibleAnalytics />
{/if} {/if}
<MetaTags {...metaTags} /> <MetaTags {...metaTags} />