Fixing build but node version 22 is needed and pre-render fails.

This commit is contained in:
Bradley Shellnut 2024-08-01 16:46:29 -07:00
parent dbdac430ef
commit 4dbc93f819
37 changed files with 370 additions and 133 deletions

3
Caddyfile Normal file
View file

@ -0,0 +1,3 @@
boredgame.localhost {
reverse_proxy / localhost:4173
}

View file

@ -15,6 +15,20 @@ services:
- '6379:6379' - '6379:6379'
volumes: volumes:
- redis_data:/data - redis_data:/data
# caddy:
# image: caddy:latest
# restart: unless-stopped
# ports:
# - "80:80"
# - "443:443"
# - "443:443/udp"
# volumes:
# - ./Caddyfile:/etc/caddy/Caddyfile
# - ./site:/srv
# - caddy_data:/data
# - caddy_config:/config
volumes: volumes:
postgres_data: postgres_data:
redis_data: redis_data:
# caddy_data:
# caddy_config:

View file

@ -22,11 +22,13 @@
"site:update": "pnpm update -i -L", "site:update": "pnpm update -i -L",
"test:unit": "vitest" "test:unit": "vitest"
}, },
"engines": {
"node": "22.x"
},
"devDependencies": { "devDependencies": {
"@melt-ui/pp": "^0.3.2", "@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.83.0", "@melt-ui/svelte": "^0.83.0",
"@playwright/test": "^1.45.3", "@playwright/test": "^1.45.3",
"@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/adapter-auto": "^3.2.2",
"@sveltejs/enhanced-img": "^0.3.1", "@sveltejs/enhanced-img": "^0.3.1",
"@sveltejs/kit": "^2.5.18", "@sveltejs/kit": "^2.5.18",
@ -46,7 +48,6 @@
"lucia": "3.2.0", "lucia": "3.2.0",
"lucide-svelte": "^0.408.0", "lucide-svelte": "^0.408.0",
"nodemailer": "^6.9.14", "nodemailer": "^6.9.14",
"oslo": "^1.2.1",
"postcss": "^8.4.40", "postcss": "^8.4.40",
"postcss-import": "^16.1.0", "postcss-import": "^16.1.0",
"postcss-load-config": "^5.1.0", "postcss-load-config": "^5.1.0",
@ -85,6 +86,7 @@
"@lukeed/uuid": "^2.0.1", "@lukeed/uuid": "^2.0.1",
"@neondatabase/serverless": "^0.9.4", "@neondatabase/serverless": "^0.9.4",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@sveltejs/adapter-node": "^5.2.0",
"@sveltejs/adapter-vercel": "^5.4.1", "@sveltejs/adapter-vercel": "^5.4.1",
"@types/feather-icons": "^4.29.4", "@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.20", "@vercel/og": "^0.5.20",
@ -110,12 +112,14 @@
"just-kebab-case": "^4.2.0", "just-kebab-case": "^4.2.0",
"loader": "^2.1.1", "loader": "^2.1.1",
"open-props": "^1.7.5", "open-props": "^1.7.5",
"oslo": "^1.2.1",
"pg": "^8.12.0", "pg": "^8.12.0",
"postgres": "^3.4.4", "postgres": "^3.4.4",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"radix-svelte": "^0.9.0", "radix-svelte": "^0.9.0",
"rate-limit-redis": "^4.2.0", "rate-limit-redis": "^4.2.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"@resvg/resvg-js": "^2.6.2",
"svelte-french-toast": "^1.2.0", "svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0", "svelte-lazy-loader": "^1.0.0",
"tailwind-merge": "^2.4.0", "tailwind-merge": "^2.4.0",

View file

@ -35,6 +35,12 @@ importers:
'@paralleldrive/cuid2': '@paralleldrive/cuid2':
specifier: ^2.2.2 specifier: ^2.2.2
version: 2.2.2 version: 2.2.2
'@resvg/resvg-js':
specifier: ^2.6.2
version: 2.6.2
'@sveltejs/adapter-node':
specifier: ^5.2.0
version: 5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))
'@sveltejs/adapter-vercel': '@sveltejs/adapter-vercel':
specifier: ^5.4.1 specifier: ^5.4.1
version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) version: 5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))
@ -110,6 +116,9 @@ importers:
open-props: open-props:
specifier: ^1.7.5 specifier: ^1.7.5
version: 1.7.5 version: 1.7.5
oslo:
specifier: ^1.2.1
version: 1.2.1
pg: pg:
specifier: ^8.12.0 specifier: ^8.12.0
version: 8.12.0 version: 8.12.0
@ -156,9 +165,6 @@ importers:
'@playwright/test': '@playwright/test':
specifier: ^1.45.3 specifier: ^1.45.3
version: 1.45.3 version: 1.45.3
'@resvg/resvg-js':
specifier: ^2.6.2
version: 2.6.2
'@sveltejs/adapter-auto': '@sveltejs/adapter-auto':
specifier: ^3.2.2 specifier: ^3.2.2
version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))) version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))
@ -216,9 +222,6 @@ importers:
nodemailer: nodemailer:
specifier: ^6.9.14 specifier: ^6.9.14
version: 6.9.14 version: 6.9.14
oslo:
specifier: ^1.2.1
version: 1.2.1
postcss: postcss:
specifier: ^8.4.40 specifier: ^8.4.40
version: 8.4.40 version: 8.4.40
@ -1717,6 +1720,33 @@ packages:
resolution: {integrity: sha512-iDkBM6Ivex8nULtBu8cX670/lfsGxq8U1cuqE+qS9xFpPQP1enPdVm/33Kq3+B+bAldA+AHNZnCgpmlHo/fZrQ==} resolution: {integrity: sha512-iDkBM6Ivex8nULtBu8cX670/lfsGxq8U1cuqE+qS9xFpPQP1enPdVm/33Kq3+B+bAldA+AHNZnCgpmlHo/fZrQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
'@rollup/plugin-commonjs@26.0.1':
resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==}
engines: {node: '>=16.0.0 || 14 >= 14.17'}
peerDependencies:
rollup: ^2.68.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/plugin-json@6.1.0':
resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/plugin-node-resolve@15.2.3':
resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.78.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/pluginutils@4.2.1': '@rollup/pluginutils@4.2.1':
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
engines: {node: '>= 8.0.0'} engines: {node: '>= 8.0.0'}
@ -1918,6 +1948,11 @@ packages:
peerDependencies: peerDependencies:
'@sveltejs/kit': ^2.0.0 '@sveltejs/kit': ^2.0.0
'@sveltejs/adapter-node@5.2.0':
resolution: {integrity: sha512-HVZoei2078XSyPmvdTHE03VXDUD0ytTvMuMHMQP0j6zX4nPDpCcKrgvU7baEblMeCCMdM/shQvstFxOJPQKlUQ==}
peerDependencies:
'@sveltejs/kit': ^2.4.0
'@sveltejs/adapter-vercel@5.4.1': '@sveltejs/adapter-vercel@5.4.1':
resolution: {integrity: sha512-JLcD1OgMnu9lQ8EssxVGxv7w0waWuyVzItTT1eqIH98Krufd9qfr1uC9zgo82z3dJ9v1AfPEbvIX5tonceg7XQ==} resolution: {integrity: sha512-JLcD1OgMnu9lQ8EssxVGxv7w0waWuyVzItTT1eqIH98Krufd9qfr1uC9zgo82z3dJ9v1AfPEbvIX5tonceg7XQ==}
peerDependencies: peerDependencies:
@ -1992,6 +2027,9 @@ packages:
'@types/pug@2.0.10': '@types/pug@2.0.10':
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@types/validator@13.12.0': '@types/validator@13.12.0':
resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==} resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==}
@ -2264,6 +2302,10 @@ packages:
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'}
bullmq@5.11.0: bullmq@5.11.0:
resolution: {integrity: sha512-qVzyWGZqie3VHaYEgRXhId/j8ebfmj6MExEJyUByMsUJA5pVciVle3hKLer5fyMwtQ8lTMP7GwhXV/NZ+HzlRA==} resolution: {integrity: sha512-qVzyWGZqie3VHaYEgRXhId/j8ebfmj6MExEJyUByMsUJA5pVciVle3hKLer5fyMwtQ8lTMP7GwhXV/NZ+HzlRA==}
@ -2369,6 +2411,9 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@ -3094,6 +3139,10 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'} engines: {node: '>=8'}
is-builtin-module@3.2.1:
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
engines: {node: '>=6'}
is-core-module@2.13.1: is-core-module@2.13.1:
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
@ -3109,6 +3158,9 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
is-number@7.0.0: is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
@ -3117,6 +3169,9 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
is-reference@3.0.2: is-reference@3.0.2:
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
@ -5900,6 +5955,34 @@ snapshots:
'@resvg/resvg-wasm@2.6.0': {} '@resvg/resvg-wasm@2.6.0': {}
'@rollup/plugin-commonjs@26.0.1(rollup@4.18.1)':
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.18.1)
commondir: 1.0.1
estree-walker: 2.0.2
glob: 10.4.1
is-reference: 1.2.1
magic-string: 0.30.10
optionalDependencies:
rollup: 4.18.1
'@rollup/plugin-json@6.1.0(rollup@4.18.1)':
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.18.1)
optionalDependencies:
rollup: 4.18.1
'@rollup/plugin-node-resolve@15.2.3(rollup@4.18.1)':
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.18.1)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-builtin-module: 3.2.1
is-module: 1.0.0
resolve: 1.22.8
optionalDependencies:
rollup: 4.18.1
'@rollup/pluginutils@4.2.1': '@rollup/pluginutils@4.2.1':
dependencies: dependencies:
estree-walker: 2.0.2 estree-walker: 2.0.2
@ -6038,6 +6121,14 @@ snapshots:
'@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))
import-meta-resolve: 4.1.0 import-meta-resolve: 4.1.0
'@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))':
dependencies:
'@rollup/plugin-commonjs': 26.0.1(rollup@4.18.1)
'@rollup/plugin-json': 6.1.0(rollup@4.18.1)
'@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.1)
'@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))
rollup: 4.18.1
'@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))': '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))':
dependencies: dependencies:
'@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)) '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8))
@ -6136,6 +6227,8 @@ snapshots:
'@types/pug@2.0.10': {} '@types/pug@2.0.10': {}
'@types/resolve@1.20.2': {}
'@types/validator@13.12.0': '@types/validator@13.12.0':
optional: true optional: true
@ -6473,6 +6566,8 @@ snapshots:
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
builtin-modules@3.3.0: {}
bullmq@5.11.0: bullmq@5.11.0:
dependencies: dependencies:
cron-parser: 4.9.0 cron-parser: 4.9.0
@ -6591,6 +6686,8 @@ snapshots:
commander@4.1.1: {} commander@4.1.1: {}
commondir@1.0.1: {}
concat-map@0.0.1: {} concat-map@0.0.1: {}
confbox@0.1.7: {} confbox@0.1.7: {}
@ -7355,6 +7452,10 @@ snapshots:
dependencies: dependencies:
binary-extensions: 2.3.0 binary-extensions: 2.3.0
is-builtin-module@3.2.1:
dependencies:
builtin-modules: 3.3.0
is-core-module@2.13.1: is-core-module@2.13.1:
dependencies: dependencies:
hasown: 2.0.2 hasown: 2.0.2
@ -7367,10 +7468,16 @@ snapshots:
dependencies: dependencies:
is-extglob: 2.1.1 is-extglob: 2.1.1
is-module@1.0.0: {}
is-number@7.0.0: {} is-number@7.0.0: {}
is-path-inside@3.0.3: {} is-path-inside@3.0.3: {}
is-reference@1.2.1:
dependencies:
'@types/estree': 1.0.5
is-reference@3.0.2: is-reference@3.0.2:
dependencies: dependencies:
'@types/estree': 1.0.5 '@types/estree': 1.0.5
@ -8327,7 +8434,6 @@ snapshots:
'@rollup/rollup-win32-ia32-msvc': 4.18.1 '@rollup/rollup-win32-ia32-msvc': 4.18.1
'@rollup/rollup-win32-x64-msvc': 4.18.1 '@rollup/rollup-win32-x64-msvc': 4.18.1
fsevents: 2.3.3 fsevents: 2.3.3
optional: true
run-parallel@1.2.0: run-parallel@1.2.0:
dependencies: dependencies:

View file

@ -1,4 +1,5 @@
// import * as Sentry from '@sentry/sveltekit'; // import * as Sentry from '@sentry/sveltekit';
import 'reflect-metadata'
import { hc } from 'hono/client'; import { hc } from 'hono/client';
import { sequence } from '@sveltejs/kit/hooks'; import { sequence } from '@sveltejs/kit/hooks';
import { redirect, type Handle } from '@sveltejs/kit'; import { redirect, type Handle } from '@sveltejs/kit';

View file

@ -1,3 +1,4 @@
import 'reflect-metadata';
import { Hono } from 'hono'; import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator'; import { zValidator } from '@hono/zod-validator';
import { inject, injectable } from 'tsyringe'; import { inject, injectable } from 'tsyringe';
@ -5,14 +6,14 @@ import type { HonoTypes } from '../types';
import { limiter } from '../middleware/rate-limiter.middleware'; import { limiter } from '../middleware/rate-limiter.middleware';
import type { Controller } from '../interfaces/controller.interface'; import type { Controller } from '../interfaces/controller.interface';
import { signInEmailDto } from '$lib/dtos/signin-email.dto'; import { signInEmailDto } from '$lib/dtos/signin-email.dto';
import type { LoginRequestsService } from '../services/loginrequest.service'; import { LoginRequestsService } from '../services/loginrequest.service';
@injectable() @injectable()
export class LoginController implements Controller { export class LoginController implements Controller {
controller = new Hono<HonoTypes>(); controller = new Hono<HonoTypes>();
constructor( constructor(
@inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService @inject(LoginRequestsService) private readonly loginRequestsService: LoginRequestsService
) { } ) { }
routes() { routes() {

View file

@ -1,9 +1,7 @@
import 'reflect-metadata';
import { Hono } from 'hono'; import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator'; import { injectable } from 'tsyringe';
import { inject, injectable } from 'tsyringe';
import { requireAuth } from "../middleware/auth.middleware"; import { requireAuth } from "../middleware/auth.middleware";
import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto';
import { limiter } from '../middleware/rate-limiter.middleware';
import type { HonoTypes } from '../types'; import type { HonoTypes } from '../types';
import type { Controller } from '../interfaces/controller.interface'; import type { Controller } from '../interfaces/controller.interface';
@ -12,7 +10,6 @@ export class UserController implements Controller {
controller = new Hono<HonoTypes>(); controller = new Hono<HonoTypes>();
constructor( constructor(
@inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService
) { } ) { }
routes() { routes() {
@ -24,11 +21,6 @@ export class UserController implements Controller {
.get('/user', requireAuth, async (c) => { .get('/user', requireAuth, async (c) => {
const user = c.var.user; const user = c.var.user;
return c.json({ user }); return c.json({ user });
})
.post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const { email } = c.req.valid('json');
await this.loginRequestsService.create({ email });
return c.json({ message: 'Verification email sent' });
}); });
} }
} }

View file

@ -1,10 +1,13 @@
import 'reflect-metadata'
import { Hono } from 'hono'; import { Hono } from 'hono';
import { hc } from 'hono/client'; import { hc } from 'hono/client';
import { cors } from 'hono/cors'; import { cors } from 'hono/cors';
import { logger } from 'hono/logger'; import { logger } from 'hono/logger';
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware'; import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware';
import users from './controllers/user.controller';
import { config } from './common/config'; import { config } from './common/config';
import { container } from 'tsyringe';
import { IamController } from './controllers/iam.controller';
import { LoginController } from './controllers/login.controller';
/* ----------------------------------- Api ---------------------------------- */ /* ----------------------------------- Api ---------------------------------- */
const app = new Hono().basePath('/api'); const app = new Hono().basePath('/api');
@ -31,7 +34,8 @@ app.use(
/* --------------------------------- Routes --------------------------------- */ /* --------------------------------- Routes --------------------------------- */
const routes = app const routes = app
.route('/user', users) .route('/user', container.resolve(IamController).routes())
.route('/login', container.resolve(LoginController).routes())
.get('/', (c) => c.json({ message: 'Server is healthy' })); .get('/', (c) => c.json({ message: 'Server is healthy' }));
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

View file

@ -1,7 +1,7 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm'; import { type InferSelectModel, relations } from 'drizzle-orm';
import usersTable from './users.table'; import { usersTable } from './users.table';
import { timestamps } from '../utils'; import { timestamps } from '../utils';
const collections = pgTable('collections', { const collections = pgTable('collections', {

View file

@ -1,4 +1,4 @@
export { default as usersTable, userRelations as user_relations, type Users } from './users.table'; export { usersTable, userRelations as user_relations, type Users } from './users.table';
export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes'; export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes';
export { export {
default as password_reset_tokens, default as password_reset_tokens,

View file

@ -1,7 +1,7 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm'; import { type InferSelectModel, relations } from 'drizzle-orm';
import usersTable from './users.table'; import { usersTable } from './users.table';
import { timestamps } from '../utils'; import { timestamps } from '../utils';
const password_reset_tokens = pgTable('password_reset_tokens', { const password_reset_tokens = pgTable('password_reset_tokens', {

View file

@ -1,6 +1,6 @@
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
import type { InferSelectModel } from 'drizzle-orm'; import type { InferSelectModel } from 'drizzle-orm';
import usersTable from './users.table'; import { usersTable } from './users.table';
import { timestamps } from '../utils'; import { timestamps } from '../utils';
const recovery_codes = pgTable('recovery_codes', { const recovery_codes = pgTable('recovery_codes', {

View file

@ -1,6 +1,6 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { relations, type InferSelectModel } from 'drizzle-orm'; import { relations, type InferSelectModel } from 'drizzle-orm';
import usersTable from './users.table'; import { usersTable } from './users.table';
const sessionsTable = pgTable('sessions', { const sessionsTable = pgTable('sessions', {
id: text('id').primaryKey(), id: text('id').primaryKey(),

View file

@ -2,7 +2,7 @@ import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm'; import { type InferSelectModel, relations } from 'drizzle-orm';
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { timestamps } from '../utils'; import { timestamps } from '../utils';
import usersTable from './users.table'; import { usersTable } from './users.table';
const twoFactorTable = pgTable('two_factor', { const twoFactorTable = pgTable('two_factor', {
id: uuid('id').primaryKey().defaultRandom(), id: uuid('id').primaryKey().defaultRandom(),

View file

@ -1,7 +1,7 @@
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm'; import { type InferSelectModel, relations } from 'drizzle-orm';
import usersTable from './users.table'; import { usersTable } from './users.table';
import roles from './roles'; import roles from './roles';
import { timestamps } from '../utils'; import { timestamps } from '../utils';

View file

@ -10,7 +10,6 @@ export const usersTable = pgTable('users', {
.unique() .unique()
.$defaultFn(() => cuid2()), .$defaultFn(() => cuid2()),
username: text('username').unique(), username: text('username').unique(),
hashed_password: text('hashed_password'),
email: text('email').unique(), email: text('email').unique(),
first_name: text('first_name'), first_name: text('first_name'),
last_name: text('last_name'), last_name: text('last_name'),

View file

@ -1,7 +1,7 @@
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'; import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm'; import { type InferSelectModel, relations } from 'drizzle-orm';
import usersTable from './users.table'; import { usersTable } from './users.table';
import { timestamps } from '../utils'; import { timestamps } from '../utils';
const wishlists = pgTable('wishlists', { const wishlists = pgTable('wishlists', {

View file

@ -6,7 +6,6 @@ import { TokensService } from './tokens.service';
import { LuciaProvider } from '../providers/lucia.provider'; import { LuciaProvider } from '../providers/lucia.provider';
import { UsersRepository } from '../repositories/users.repository'; import { UsersRepository } from '../repositories/users.repository';
import type { SignInEmailDto } from '../../../dtos/signin-email.dto'; import type { SignInEmailDto } from '../../../dtos/signin-email.dto';
import type { RegisterEmailDto } from '../../../dtos/register-email.dto';
import { CredentialsRepository } from '../repositories/credentials.repository'; import { CredentialsRepository } from '../repositories/credentials.repository';
import type { HonoRequest } from 'hono'; import type { HonoRequest } from 'hono';
@ -21,17 +20,17 @@ export class LoginRequestsService {
@inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository,
) { } ) { }
async create(data: RegisterEmailDto) { // async create(data: RegisterEmailDto) {
// generate a token, expiry date, and hash // // generate a token, expiry date, and hash
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm'); // const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
// save the login request to the database - ensuring we save the hashedToken // // save the login request to the database - ensuring we save the hashedToken
await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry }); // await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
// send the login request email // // send the login request email
await this.mailerService.sendLoginRequest({ // await this.mailerService.sendLoginRequest({
to: data.email, // to: data.email,
props: { token: token } // props: { token: token }
}); // });
} // }
async verify(data: SignInEmailDto, req: HonoRequest) { async verify(data: SignInEmailDto, req: HonoRequest) {
const requestIpAddress = req.header('x-real-ip'); const requestIpAddress = req.header('x-real-ip');
@ -74,19 +73,19 @@ export class LoginRequestsService {
} }
// Fetch a valid request from the database, verify the token and burn the request if it is valid // Fetch a valid request from the database, verify the token and burn the request if it is valid
private async fetchValidRequest(email: string, token: string) { // private async fetchValidRequest(email: string, token: string) {
return await this.db.transaction(async (trx) => { // return await this.db.transaction(async (trx) => {
// fetch the login request // // fetch the login request
const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email) // const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email)
if (!loginRequest) return null; // if (!loginRequest) return null;
// check if the token is valid // // check if the token is valid
const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token); // const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token);
if (!isValidRequest) return null // if (!isValidRequest) return null
// if the token is valid, burn the request // // if the token is valid, burn the request
await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id); // await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id);
return loginRequest // return loginRequest
}) // })
} // }
} }

View file

@ -0,0 +1,72 @@
// import 'reflect-metadata';
// import { LoginRequestsService } from '../services/login-requests.service';
// import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
// import { TokensService } from '../services/tokens.service';
// import { MailerService } from '../services/mailer.service';
// import { UsersRepository } from '../repositories/users.repository';
// import { DatabaseProvider, LuciaProvider } from '../providers';
// import { LoginRequestsRepository } from '../repositories/login-requests.repository';
// import { PgDatabase } from 'drizzle-orm/pg-core';
// import { container } from 'tsyringe';
// describe('LoginRequestService', () => {
// let service: LoginRequestsService;
// let tokensService = vi.mocked(TokensService.prototype)
// let mailerService = vi.mocked(MailerService.prototype);
// let usersRepository = vi.mocked(UsersRepository.prototype);
// let loginRequestsRepository = vi.mocked(LoginRequestsRepository.prototype);
// let luciaProvider = vi.mocked(LuciaProvider);
// let databaseProvider = vi.mocked(PgDatabase);
// beforeAll(() => {
// service = container
// .register<TokensService>(TokensService, { useValue: tokensService })
// .register<MailerService>(MailerService, { useValue: mailerService })
// .register<UsersRepository>(UsersRepository, { useValue: usersRepository })
// .register(LoginRequestsRepository, { useValue: loginRequestsRepository })
// .register(LuciaProvider, { useValue: luciaProvider })
// .register(DatabaseProvider, { useValue: databaseProvider })
// .resolve(LoginRequestsService);
// });
// afterAll(() => {
// vi.resetAllMocks()
// })
// describe('Create', () => {
// tokensService.generateTokenWithExpiryAndHash = vi.fn().mockResolvedValue({
// token: "1",
// expiry: new Date(),
// hashedToken: "xyz"
// } satisfies Awaited<ReturnType<typeof tokensService.generateTokenWithExpiryAndHash>>)
// loginRequestsRepository.create = vi.fn().mockResolvedValue({
// createdAt: new Date(),
// email: 'me@test.com',
// expiresAt: new Date(),
// hashedToken: '111',
// id: '1',
// updatedAt: new Date()
// } satisfies Awaited<ReturnType<typeof loginRequestsRepository.create>>)
// mailerService.sendLoginRequest = vi.fn().mockResolvedValue(null)
// const spy_mailerService_sendLoginRequest = vi.spyOn(mailerService, 'sendLoginRequest')
// const spy_tokensService_generateTokenWithExpiryAndHash = vi.spyOn(tokensService, 'generateTokenWithExpiryAndHash')
// const spy_loginRequestsRepository_create = vi.spyOn(loginRequestsRepository, 'create')
// it('should resolve', async () => {
// await expect(service.create({ email: "test" })).resolves.toBeUndefined()
// })
// it('should generate a token with expiry and hash', async () => {
// expect(spy_tokensService_generateTokenWithExpiryAndHash).toBeCalledTimes(1)
// })
// it('should send an email with token', async () => {
// expect(spy_mailerService_sendLoginRequest).toHaveBeenCalledTimes(1)
// })
// it('should create a new login request record', async () => {
// expect(spy_loginRequestsRepository_create).toBeCalledTimes(1)
// })
// })
// });

View file

@ -3,7 +3,7 @@ import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'; import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../../../db'; import db from '../../../../../../db';
import { roles, userRoles, users } from '$db/schema'; import { roles, userRoles, usersTable } from '$db/schema';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const { params } = event; const { params } = event;
@ -14,10 +14,10 @@ export const load: PageServerLoad = async (event) => {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const foundUser = await db.query.users.findFirst({ const foundUser = await db.query.usersTable.findFirst({
where: eq(users.cuid, id), where: eq(usersTable.cuid, id),
with: { with: {
userRoles: { user_roles: {
with: { with: {
role: { role: {
columns: { columns: {
@ -30,7 +30,7 @@ export const load: PageServerLoad = async (event) => {
}, },
}); });
const containsAdminRole = foundUser?.userRoles?.some( const containsAdminRole = foundUser?.user_roles?.some(
(user_role) => user_role?.role?.name === 'admin', (user_role) => user_role?.role?.name === 'admin',
); );
if (!containsAdminRole) { if (!containsAdminRole) {
@ -38,7 +38,7 @@ export const load: PageServerLoad = async (event) => {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const currentRoleIds = foundUser?.userRoles?.map((user_role) => user_role?.role.cuid) || []; const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || [];
let availableRoles: { name: string; cuid: string }[] = []; let availableRoles: { name: string; cuid: string }[] = [];
if (currentRoleIds?.length > 0) { if (currentRoleIds?.length > 0) {
availableRoles = await db.query.roles.findMany({ availableRoles = await db.query.roles.findMany({
@ -65,7 +65,7 @@ export const actions = {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const userRoles = await db.query.userRoles.findMany({ const userRolesList = await db.query.userRoles.findMany({
where: eq(userRoles.user_id, user.id), where: eq(userRoles.user_id, user.id),
with: { with: {
role: { role: {
@ -77,9 +77,9 @@ export const actions = {
}, },
}); });
console.log('userRoles', userRoles); console.log('userRoles', userRolesList);
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin'); const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin');
console.log('containsAdminRole', containsAdminRole); console.log('containsAdminRole', containsAdminRole);
if (!containsAdminRole) { if (!containsAdminRole) {
redirect(302, '/', forbiddenMessage, event); redirect(302, '/', forbiddenMessage, event);
@ -108,7 +108,7 @@ export const actions = {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const userRoles = await db.query.userRoles.findMany({ const userRolesList = await db.query.userRoles.findMany({
where: eq(userRoles.user_id, user.id), where: eq(userRoles.user_id, user.id),
with: { with: {
role: { role: {
@ -120,7 +120,7 @@ export const actions = {
}, },
}); });
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin'); const containsAdminRole = userRolesList.some((user_role) => user_role?.role?.name === 'admin');
if (!containsAdminRole) { if (!containsAdminRole) {
redirect(302, '/', forbiddenMessage, event); redirect(302, '/', forbiddenMessage, event);
} }

View file

@ -8,7 +8,7 @@ import { changeEmailSchema, profileSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../db'; import db from '../../../../db';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { users, twoFactor } from '$db/schema'; import { usersTable, twoFactor } from '$db/schema';
import { userNotAuthenticated } from '$lib/server/auth-utils'; import { userNotAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
@ -18,8 +18,8 @@ export const load: PageServerLoad = async (event) => {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user!.id!), where: eq(usersTable.id, user!.id!),
}); });
const profileForm = await superValidate(zod(profileSchema), { const profileForm = await superValidate(zod(profileSchema), {
@ -72,8 +72,8 @@ export const actions: Actions = {
const user = event.locals.user; const user = event.locals.user;
const newUsername = form.data.username; const newUsername = form.data.username;
const existingUser = await db.query.users.findFirst({ const existingUser = await db.query.usersTable.findFirst({
where: eq(users.username, newUsername), where: eq(usersTable.username, newUsername),
}); });
if (existingUser && existingUser.id !== user.id) { if (existingUser && existingUser.id !== user.id) {
@ -81,13 +81,13 @@ export const actions: Actions = {
} }
await db await db
.update(users) .update(usersTable)
.set({ .set({
first_name: form.data.firstName, first_name: form.data.firstName,
last_name: form.data.lastName, last_name: form.data.lastName,
username: form.data.username, username: form.data.username,
}) })
.where(eq(users.id, user.id)); .where(eq(usersTable.id, user.id));
} catch (e) { } catch (e) {
// @ts-expect-error // @ts-expect-error
if (e.message === `AUTH_INVALID_USER_ID`) { if (e.message === `AUTH_INVALID_USER_ID`) {
@ -119,17 +119,17 @@ export const actions: Actions = {
} }
const user = event.locals.user; const user = event.locals.user;
const existingUser = await db.query.users.findFirst({ const existingUser = await db.query.usersTable.findFirst({
where: eq(users.email, newEmail), where: eq(usersTable.email, newEmail),
}); });
if (existingUser && existingUser.id !== user.id) { if (existingUser && existingUser.id !== user.id) {
return setError(form, 'email', 'That email is already taken'); return setError(form, 'email', 'That email is already taken');
} }
await db.update(users).set({ email: form.data.email }).where(eq(users.id, user.id)); await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id));
if (user.email !== form.data.email) { // if (user.email !== form.data.email) {
// Send email to confirm new email? // Send email to confirm new email?
// auth.update // auth.update
// await locals.prisma.key.update({ // await locals.prisma.key.update({
@ -143,7 +143,7 @@ export const actions: Actions = {
// auth.updateUserAttributes(user.user_id, { // auth.updateUserAttributes(user.user_id, {
// receiveEmail: false // receiveEmail: false
// }); // });
} // }
return message(form, { type: 'success', message: 'Email updated successfully!' }); return message(form, { type: 'success', message: 'Email updated successfully!' });
}, },

View file

@ -8,7 +8,7 @@ import type { PageServerLoad } from '../../../$types';
import db from '../../../../../../../db'; import db from '../../../../../../../db';
import { changeUserPasswordSchema } from '$lib/validations/account'; import { changeUserPasswordSchema } from '$lib/validations/account';
import { lucia } from '$lib/server/auth.js'; import { lucia } from '$lib/server/auth.js';
import { users } from '$db/schema'; import { usersTable } from '$db/schema';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import type { Cookie } from 'lucia'; import type { Cookie } from 'lucia';
import { userNotAuthenticated } from '$lib/server/auth-utils'; import { userNotAuthenticated } from '$lib/server/auth-utils';
@ -56,8 +56,8 @@ export const actions: Actions = {
return fail(401); return fail(401);
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user!.id), where: eq(usersTable.id, user!.id),
}); });
if (!dbUser?.hashed_password) { if (!dbUser?.hashed_password) {
@ -87,9 +87,9 @@ export const actions: Actions = {
const hashedPassword = await new Argon2id().hash(form.data.password); const hashedPassword = await new Argon2id().hash(form.data.password);
await lucia.invalidateUserSessions(user.id); await lucia.invalidateUserSessions(user.id);
await db await db
.update(users) .update(usersTable)
.set({ hashed_password: hashedPassword }) .set({ hashed_password: hashedPassword })
.where(eq(users.id, user.id)); .where(eq(usersTable.id, user.id));
await lucia.createSession(user.id, { await lucia.createSession(user.id, {
country: event.locals.session?.ipCountry ?? 'unknown', country: event.locals.session?.ipCountry ?? 'unknown',
}); });

View file

@ -13,7 +13,7 @@ import type { PageServerLoad } from '../../$types';
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account'; import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../../../db'; import db from '../../../../../../db';
import { recoveryCodes, twoFactor, users } from '$db/schema'; import { recoveryCodes, twoFactor, usersTable } from '$db/schema';
import { userNotAuthenticated } from '$lib/server/auth-utils'; import { userNotAuthenticated } from '$lib/server/auth-utils';
import env from '../../../../../../env'; import env from '../../../../../../env';
@ -27,8 +27,8 @@ export const load: PageServerLoad = async (event) => {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user!.id!), where: eq(usersTable.id, user!.id!),
}); });
const twoFactorDetails = await db.query.twoFactor.findFirst({ const twoFactorDetails = await db.query.twoFactor.findFirst({
@ -111,8 +111,8 @@ export const actions: Actions = {
return fail(401); return fail(401);
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user!.id!), where: eq(usersTable.id, user!.id!),
}); });
if (!dbUser?.hashed_password) { if (!dbUser?.hashed_password) {
@ -190,8 +190,8 @@ export const actions: Actions = {
}); });
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user.id), where: eq(usersTable.id, user.id),
}); });
if (!dbUser?.hashed_password) { if (!dbUser?.hashed_password) {

View file

@ -5,7 +5,7 @@ import { alphabet, generateRandomString } from 'oslo/crypto';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import type { PageServerLoad } from '../../../$types'; import type { PageServerLoad } from '../../../$types';
import {recoveryCodes, twoFactor, users} from '$db/schema'; import {recoveryCodes, twoFactor, usersTable} from '$db/schema';
import { userNotAuthenticated } from '$lib/server/auth-utils'; import { userNotAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
@ -15,8 +15,8 @@ export const load: PageServerLoad = async (event) => {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user!.id), where: eq(usersTable.id, user!.id),
}); });
const twoFactorDetails = await db.query.twoFactor.findFirst({ const twoFactorDetails = await db.query.twoFactor.findFirst({

View file

@ -2,7 +2,7 @@ import type { MetaTagsProps } from 'svelte-meta-tags';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import db from '../../db'; import db from '../../db';
import { collections, users, wishlists } from '$db/schema'; import { collections, usersTable, wishlists } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils'; import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
@ -42,8 +42,8 @@ export const load: PageServerLoad = async (event) => {
}); });
if (userFullyAuthenticated(user, session)) { if (userFullyAuthenticated(user, session)) {
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.id, user!.id!), where: eq(usersTable.id, user!.id!),
}); });
console.log('Sending back user details'); console.log('Sending back user details');

View file

@ -1,9 +1,9 @@
import { dev } from '$app/environment'; // import { dev } from '$app/environment';
// we don't need any JS on this page, though we'll load // // we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement... // // it in dev so that we get hot module replacement...
export const csr = dev; // export const csr = dev;
// since there's no dynamic data here, we can prerender // // since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod // // it so that it gets served as a static asset in prod
export const prerender = true; // export const prerender = true;

View file

@ -1 +1 @@
export const prerender = true; // export const prerender = true;

View file

@ -1 +1 @@
export const prerender = true; // export const prerender = true;

View file

@ -8,7 +8,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '../../../db'; import db from '../../../db';
import { lucia } from '$lib/server/auth'; import { lucia } from '$lib/server/auth';
import { signInSchema } from '$lib/validations/auth'; import { signInSchema } from '$lib/validations/auth';
import { twoFactor, users, type Users } from '$db/schema'; import { twoFactor, usersTable, type Users } from '$db/schema';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
@ -57,8 +57,8 @@ export const actions: Actions = {
let session; let session;
let sessionCookie; let sessionCookie;
const user: Users | undefined = await db.query.users.findFirst({ const user: Users | undefined = await db.query.usersTable.findFirst({
where: or(eq(users.username, form.data.username), eq(users.email, form.data.username)), where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)),
}); });
if (!user) { if (!user) {

View file

@ -10,7 +10,7 @@ import { lucia } from '$lib/server/auth';
import { signUpSchema } from '$lib/validations/auth'; import { signUpSchema } from '$lib/validations/auth';
import { add_user_to_role } from '$server/roles'; import { add_user_to_role } from '$server/roles';
import db from '../../../db'; import db from '../../../db';
import { collections, users, wishlists } from '$db/schema'; import { collections, usersTable, wishlists } from '$db/schema';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils';
@ -76,8 +76,8 @@ export const actions: Actions = {
// Adding user to the db // Adding user to the db
console.log('Check if user already exists'); console.log('Check if user already exists');
const existing_user = await db.query.users.findFirst({ const existing_user = await db.query.usersTable.findFirst({
where: eq(users.username, form.data.username), where: eq(usersTable.username, form.data.username),
}); });
if (existing_user) { if (existing_user) {
@ -89,7 +89,7 @@ export const actions: Actions = {
const hashedPassword = await new Argon2id().hash(form.data.password); const hashedPassword = await new Argon2id().hash(form.data.password);
const user = await db const user = await db
.insert(users) .insert(usersTable)
.values({ .values({
username: form.data.username, username: form.data.username,
hashed_password: hashedPassword, hashed_password: hashedPassword,

View file

@ -10,7 +10,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '../../../db'; import db from '../../../db';
import { lucia } from '$lib/server/auth'; import { lucia } from '$lib/server/auth';
import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth'; import { recoveryCodeSchema, totpSchema } from '$lib/validations/auth';
import { users, twoFactor, recoveryCodes } from '$db/schema'; import { usersTable, twoFactor, recoveryCodes } from '$db/schema';
import type {PageServerLoad, RequestEvent} from './$types'; import type {PageServerLoad, RequestEvent} from './$types';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import env from '../../../env'; import env from '../../../env';
@ -24,8 +24,8 @@ export const load: PageServerLoad = async (event) => {
} }
if (user && session) { if (user && session) {
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.username, user.username), where: eq(usersTable.username, user.username),
}); });
const twoFactorDetails = await db.query.twoFactor.findFirst({ const twoFactorDetails = await db.query.twoFactor.findFirst({
@ -262,8 +262,8 @@ async function validateUserData(event: RequestEvent, locals: App.Locals) {
throw fail(401); throw fail(401);
} }
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.usersTable.findFirst({
where: eq(users.username, user.username), where: eq(usersTable.username, user.username),
}); });
if (!dbUser) { if (!dbUser) {

View file

@ -1,7 +1,7 @@
import db from '../../../../db'; import db from '../../../../db';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { users } from '$db/schema'; import { usersTable } from '$db/schema';
import { createPasswordResetToken } from '$lib/server/auth-utils.js'; import { createPasswordResetToken } from '$lib/server/auth-utils.js';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
@ -12,8 +12,8 @@ export async function POST({ locals, request }) {
error(401, { message: 'Unauthorized' }); error(401, { message: 'Unauthorized' });
} }
const user = await db.query.users.findFirst({ const user = await db.query.usersTable.findFirst({
where: eq(users.email, email), where: eq(usersTable.email, email),
}); });
if (!user) { if (!user) {

View file

@ -1,9 +1,9 @@
import db from '../../../../../db';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { password_reset_tokens, users } from '$db/schema'; import { password_reset_tokens, usersTable } from '$db/schema';
import { isWithinExpirationDate } from 'oslo'; import { isWithinExpirationDate } from 'oslo';
import { lucia } from '$lib/server/auth.js'; import { lucia } from '$lib/server/auth.js';
import { Argon2id } from 'oslo/password'; import { Argon2id } from 'oslo/password';
import db from '$db';
export async function POST({ request, params }) { export async function POST({ request, params }) {
const { password } = await request.json(); const { password } = await request.json();
@ -34,7 +34,7 @@ export async function POST({ request, params }) {
await lucia.invalidateUserSessions(token.user_id); await lucia.invalidateUserSessions(token.user_id);
const hashPassword = await new Argon2id().hash(password); const hashPassword = await new Argon2id().hash(password);
await db.update(users).set({ hashed_password: hashPassword }).where(eq(users.id, token.user_id)); await db.update(usersTable).set({ hashed_password: hashPassword }).where(eq(usersTable.id, token.user_id));
const session = await lucia.createSession(token.user_id, {}); const session = await lucia.createSession(token.user_id, {});
const sessionCookie = lucia.createSessionCookie(session.id); const sessionCookie = lucia.createSessionCookie(session.id);

View file

@ -1,7 +1,7 @@
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '../../../../../db'; import db from '../../../../../db';
import { collection_items, users } from '$db/schema'; import { collection_items, usersTable } from '$db/schema';
// Search a user's collection // Search a user's collection
export async function GET({ url, locals, params }) { export async function GET({ url, locals, params }) {
@ -20,7 +20,7 @@ export async function GET({ url, locals, params }) {
} }
const collection = await db.query.collections.findFirst({ const collection = await db.query.collections.findFirst({
where: eq(users.id, locals?.user?.id), where: eq(usersTable.id, locals?.user?.id),
}); });
console.log('collection', collection); console.log('collection', collection);

View file

@ -1,17 +1,17 @@
import db from '../db'; import db from '../db';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { users, type Users } from '$db/schema'; import { usersTable, type Users } from '$db/schema';
import { add_user_to_role } from './roles'; import { add_user_to_role } from './roles';
export function create_user(user: Users) { export function create_user(user: Users) {
return db.insert(users).values({ return db.insert(usersTable).values({
username: user.username, username: user.username,
}); });
} }
export async function find_or_create_user(user: Users) { export async function find_or_create_user(user: Users) {
const existing_user = await db.query.users.findFirst({ const existing_user = await db.query.usersTable.findFirst({
where: eq(users.username, user.username), where: eq(usersTable.username, user.username),
}); });
if (existing_user) { if (existing_user) {
return existing_user; return existing_user;
@ -23,8 +23,8 @@ export async function find_or_create_user(user: Users) {
} }
export async function find_user_with_roles(user_id: string) { export async function find_user_with_roles(user_id: string) {
const user_with_roles = await db.query.users.findFirst({ const user_with_roles = await db.query.usersTable.findFirst({
where: eq(users.id, user_id), where: eq(usersTable.id, user_id),
with: { with: {
user_roles: { user_roles: {
with: { with: {

View file

@ -1,3 +1,4 @@
import 'reflect-metadata'
import adapter from '@sveltejs/adapter-vercel'; import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { preprocessMeltUI } from '@melt-ui/pp'; import { preprocessMeltUI } from '@melt-ui/pp';

View file

@ -0,0 +1,41 @@
// vite.config.ts
import { sveltekit } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/@sveltejs+kit@2.5.18_@sveltejs+vite-plugin-svelte@3.1.1_svelte@5.0.0-next.175_vite@5.3.5_@typ_ps3ubydtq463bedtodypwfb5fu/node_modules/@sveltejs/kit/src/exports/vite/index.js";
import { defineConfig } from "file:///home/bshellnu/projects/websites/boredgame/node_modules/.pnpm/vite@5.3.5_@types+node@20.14.13_sass@1.77.8/node_modules/vite/dist/node/index.js";
var vite_config_default = defineConfig({
plugins: [
// sentrySvelteKit({
// sourceMapsUploadOptions: {
// org: process.env.SENTRY_ORG,
// project: process.env.SENTRY_PROJECT,
// authToken: process.env.SENTRY_AUTH_TOKEN,
// cleanArtifacts: true,
// }
// }),
sveltekit()
],
test: {
include: ["src/**/*.{test,spec}.{js,ts}"]
},
css: {
devSourcemap: true,
preprocessorOptions: {
postcss: {
additionalData: `
@custom-media --below_small (width < 400px);
@custom-media --below_med (width < 700px);
@custom-media --below_large (width < 900px);
@custom-media --below_xlarge (width < 1200px);
@custom-media --above_small (width > 400px);
@custom-media --above_med (width > 700px);
@custom-media --above_large (width > 900px);
@custom-media --above_xlarge (width > 1200px);
`
}
}
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWVcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL2JzaGVsbG51L3Byb2plY3RzL3dlYnNpdGVzL2JvcmVkZ2FtZS92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ic2hlbGxudS9wcm9qZWN0cy93ZWJzaXRlcy9ib3JlZGdhbWUvdml0ZS5jb25maWcudHNcIjsvLyBpbXBvcnQgeyBzZW50cnlTdmVsdGVLaXQgfSBmcm9tIFwiQHNlbnRyeS9zdmVsdGVraXRcIjtcbmltcG9ydCB7IHN2ZWx0ZWtpdCB9IGZyb20gJ0BzdmVsdGVqcy9raXQvdml0ZSc7XG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcblxuLy8gVE9ETzogRml4IFNlbnRyeVxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcblx0cGx1Z2luczogW1xuXHRcdC8vIHNlbnRyeVN2ZWx0ZUtpdCh7XG5cdFx0Ly8gXHRzb3VyY2VNYXBzVXBsb2FkT3B0aW9uczoge1xuXHRcdC8vIFx0XHRvcmc6IHByb2Nlc3MuZW52LlNFTlRSWV9PUkcsXG5cdFx0Ly8gXHRcdHByb2plY3Q6IHByb2Nlc3MuZW52LlNFTlRSWV9QUk9KRUNULFxuXHRcdC8vIFx0XHRhdXRoVG9rZW46IHByb2Nlc3MuZW52LlNFTlRSWV9BVVRIX1RPS0VOLFxuXHRcdC8vIFx0XHRjbGVhbkFydGlmYWN0czogdHJ1ZSxcblx0XHQvLyBcdH1cblx0XHQvLyB9KSxcblx0XHRzdmVsdGVraXQoKVxuXHRdLFxuXHR0ZXN0OiB7XG5cdFx0aW5jbHVkZTogWydzcmMvKiovKi57dGVzdCxzcGVjfS57anMsdHN9J11cblx0fSxcblx0Y3NzOiB7XG5cdFx0ZGV2U291cmNlbWFwOiB0cnVlLFxuXHRcdHByZXByb2Nlc3Nvck9wdGlvbnM6IHtcblx0XHRcdHBvc3Rjc3M6IHtcblx0XHRcdFx0YWRkaXRpb25hbERhdGE6IGBcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X3NtYWxsICh3aWR0aCA8IDQwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWJlbG93X21lZCAod2lkdGggPCA3MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd19sYXJnZSAod2lkdGggPCA5MDBweCk7XG5cdFx0XHRcdEBjdXN0b20tbWVkaWEgLS1iZWxvd194bGFyZ2UgKHdpZHRoIDwgMTIwMHB4KTtcblxuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfc21hbGwgKHdpZHRoID4gNDAwcHgpO1xuXHRcdFx0XHRAY3VzdG9tLW1lZGlhIC0tYWJvdmVfbWVkICh3aWR0aCA+IDcwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX2xhcmdlICh3aWR0aCA+IDkwMHB4KTtcblx0XHRcdFx0QGN1c3RvbS1tZWRpYSAtLWFib3ZlX3hsYXJnZSAod2lkdGggPiAxMjAwcHgpO1xuXHRcdFx0XHRgXG5cdFx0XHR9XG5cdFx0fVxuXHR9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyxpQkFBaUI7QUFDMUIsU0FBUyxvQkFBb0I7QUFHN0IsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDM0IsU0FBUztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQVNSLFVBQVU7QUFBQSxFQUNYO0FBQUEsRUFDQSxNQUFNO0FBQUEsSUFDTCxTQUFTLENBQUMsOEJBQThCO0FBQUEsRUFDekM7QUFBQSxFQUNBLEtBQUs7QUFBQSxJQUNKLGNBQWM7QUFBQSxJQUNkLHFCQUFxQjtBQUFBLE1BQ3BCLFNBQVM7QUFBQSxRQUNSLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFXakI7QUFBQSxJQUNEO0FBQUEsRUFDRDtBQUNELENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==