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
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

View file

@ -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",

View file

@ -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

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 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,
},
}
};

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 { 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<typeof EnvSchema>
export type env = z.infer<typeof EnvSchema>;
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;

View file

@ -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<RateLimitInfo | undefined>;
resetKey: (key: string) => Promisify<void>;
};
rateLimitStore: {
getKey?: (key: string) => Promisify<RateLimitInfo | undefined>;
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 { 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' });
});
}
}

View file

@ -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 });
});
}
}

View file

@ -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,
})
});
}),
)
);
}),
)
);
}

View file

@ -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<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
}),
)
// 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<typeof routes>(config.api.origin)
export type ApiClient = typeof rpc
export type ApiRoutes = typeof routes
export const rpc = hc<typeof routes>(config.api.origin);
export type ApiClient = typeof rpc;
export type ApiRoutes = typeof routes;

View file

@ -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<HonoTypes> = createMiddleware(async (c, next) => {
export const verifyOrigin: MiddlewareHandler<AppBindings> = 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<HonoTypes> = createMiddleware(async (c, next) => {
const sessionId = lucia.readSessionCookie(c.req.header('Cookie') ?? '')
export const validateAuthSession: MiddlewareHandler<AppBindings> = 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();
});

View file

@ -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(),
}
},
});
}
}

View file

@ -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,
})
});
}

View file

@ -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<typeof schema>
readonly schema: typeof schema = schema
protected readonly pool: pg.Pool;
db: NodePgDatabase<typeof schema>;
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> | void {
this.pool.end()
this.pool.end();
}
}

View file

@ -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);
}
}

View file

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