Converting everything to the tsyringe IoC pattern.

This commit is contained in:
Bradley Shellnut 2024-07-30 18:50:46 -07:00
parent 3190e9601e
commit bf55b04de6
19 changed files with 415 additions and 57 deletions

View file

@ -1 +1 @@
20.15.1 22.1.0

View file

@ -43,6 +43,10 @@
"eslint-plugin-svelte": "^2.43.0", "eslint-plugin-svelte": "^2.43.0",
"just-clone": "^6.2.0", "just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0", "just-debounce-it": "^3.2.0",
"lucia": "3.2.0",
"lucide-svelte": "^0.408.0",
"nodemailer": "^6.9.14",
"oslo": "^1.2.1",
"postcss": "^8.4.40", "postcss": "^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",
@ -71,10 +75,6 @@
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"type": "module", "type": "module",
"engines": {
"node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0",
"pnpm": ">=8"
},
"dependencies": { "dependencies": {
"@fontsource/fira-mono": "^5.0.13", "@fontsource/fira-mono": "^5.0.13",
"@hono/zod-validator": "^0.2.2", "@hono/zod-validator": "^0.2.2",
@ -91,6 +91,7 @@
"arctic": "^1.9.2", "arctic": "^1.9.2",
"bits-ui": "^0.21.12", "bits-ui": "^0.21.12",
"boardgamegeekclient": "^1.9.1", "boardgamegeekclient": "^1.9.1",
"bullmq": "^5.11.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cookie": "^0.6.0", "cookie": "^0.6.0",
@ -99,6 +100,7 @@
"drizzle-orm": "^0.32.1", "drizzle-orm": "^0.32.1",
"feather-icons": "^4.29.2", "feather-icons": "^4.29.2",
"formsnap": "^1.0.1", "formsnap": "^1.0.1",
"handlebars": "^4.7.8",
"hono": "^4.5.2", "hono": "^4.5.2",
"hono-rate-limiter": "^0.4.0", "hono-rate-limiter": "^0.4.0",
"html-entities": "^2.5.2", "html-entities": "^2.5.2",
@ -107,10 +109,7 @@
"just-capitalize": "^3.2.0", "just-capitalize": "^3.2.0",
"just-kebab-case": "^4.2.0", "just-kebab-case": "^4.2.0",
"loader": "^2.1.1", "loader": "^2.1.1",
"lucia": "3.2.0",
"lucide-svelte": "^0.408.0",
"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",

View file

@ -53,6 +53,9 @@ importers:
boardgamegeekclient: boardgamegeekclient:
specifier: ^1.9.1 specifier: ^1.9.1
version: 1.9.1 version: 1.9.1
bullmq:
specifier: ^5.11.0
version: 5.11.0
class-variance-authority: class-variance-authority:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
@ -77,6 +80,9 @@ importers:
formsnap: formsnap:
specifier: ^1.0.1 specifier: ^1.0.1
version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)) version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.16.1(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175)(vite@5.3.5(@types/node@20.14.13)(sass@1.77.8)))(svelte@5.0.0-next.175))
handlebars:
specifier: ^4.7.8
version: 4.7.8
hono: hono:
specifier: ^4.5.2 specifier: ^4.5.2
version: 4.5.2 version: 4.5.2
@ -101,18 +107,9 @@ importers:
loader: loader:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
lucia:
specifier: 3.2.0
version: 3.2.0
lucide-svelte:
specifier: ^0.408.0
version: 0.408.0(svelte@5.0.0-next.175)
open-props: 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
@ -210,6 +207,18 @@ importers:
just-debounce-it: just-debounce-it:
specifier: ^3.2.0 specifier: ^3.2.0
version: 3.2.0 version: 3.2.0
lucia:
specifier: 3.2.0
version: 3.2.0
lucide-svelte:
specifier: ^0.408.0
version: 0.408.0(svelte@5.0.0-next.175)
nodemailer:
specifier: ^6.9.14
version: 6.9.14
oslo:
specifier: ^1.2.1
version: 1.2.1
postcss: postcss:
specifier: ^8.4.40 specifier: ^8.4.40
version: 8.4.40 version: 8.4.40
@ -1386,6 +1395,36 @@ packages:
peerDependencies: peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118 svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
cpu: [arm64]
os: [darwin]
'@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
cpu: [x64]
os: [darwin]
'@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
cpu: [arm64]
os: [linux]
'@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
cpu: [arm]
os: [linux]
'@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
cpu: [x64]
os: [linux]
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
cpu: [x64]
os: [win32]
'@neondatabase/serverless@0.9.4': '@neondatabase/serverless@0.9.4':
resolution: {integrity: sha512-D0AXgJh6xkf+XTlsO7iwE2Q1w8981E1cLCPAALMU2YKtkF/1SF6BiAzYARZFYo175ON+b1RNIy9TdSFHm5nteg==} resolution: {integrity: sha512-D0AXgJh6xkf+XTlsO7iwE2Q1w8981E1cLCPAALMU2YKtkF/1SF6BiAzYARZFYo175ON+b1RNIy9TdSFHm5nteg==}
@ -2225,6 +2264,9 @@ 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==}
bullmq@5.11.0:
resolution: {integrity: sha512-qVzyWGZqie3VHaYEgRXhId/j8ebfmj6MExEJyUByMsUJA5pVciVle3hKLer5fyMwtQ8lTMP7GwhXV/NZ+HzlRA==}
bytes@3.1.2: bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -2357,6 +2399,10 @@ packages:
create-require@1.1.1: create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
cron-parser@4.9.0:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
cross-spawn@7.0.3: cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -2943,6 +2989,11 @@ packages:
graphemer@1.4.0: graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
handlebars@4.7.8:
resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
engines: {node: '>=0.4.7'}
hasBin: true
has-flag@4.0.0: has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -3205,6 +3256,10 @@ packages:
peerDependencies: peerDependencies:
svelte: ^3 || ^4 || ^5.0.0-next.42 svelte: ^3 || ^4 || ^5.0.0-next.42
luxon@3.4.4:
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==}
engines: {node: '>=12'}
magic-string@0.30.10: magic-string@0.30.10:
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
@ -3338,6 +3393,13 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
msgpackr-extract@3.0.3:
resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
hasBin: true
msgpackr@1.11.0:
resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==}
mz@2.7.0: mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@ -3358,6 +3420,12 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
node-fetch@2.7.0: node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0} engines: {node: 4.x || >=6.0.0}
@ -3367,6 +3435,10 @@ packages:
encoding: encoding:
optional: true optional: true
node-gyp-build-optional-packages@5.2.2:
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
hasBin: true
node-gyp-build@4.8.1: node-gyp-build@4.8.1:
resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
hasBin: true hasBin: true
@ -3374,6 +3446,10 @@ packages:
node-releases@2.0.14: node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
nodemailer@6.9.14:
resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==}
engines: {node: '>=6.0.0'}
nopt@5.0.0: nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -4519,6 +4595,11 @@ packages:
ufo@1.5.3: ufo@1.5.3:
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
uglify-js@3.19.1:
resolution: {integrity: sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==}
engines: {node: '>=0.8.0'}
hasBin: true
ultrahtml@1.5.3: ultrahtml@1.5.3:
resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==} resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==}
@ -4557,6 +4638,10 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
v8-compile-cache-lib@3.0.1: v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
@ -4670,6 +4755,9 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
wordwrap@1.0.0:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
wrap-ansi@6.2.0: wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -5579,6 +5667,24 @@ snapshots:
nanoid: 5.0.7 nanoid: 5.0.7
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
optional: true
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
optional: true
'@neondatabase/serverless@0.9.4': '@neondatabase/serverless@0.9.4':
dependencies: dependencies:
'@types/pg': 8.11.6 '@types/pg': 8.11.6
@ -6367,6 +6473,18 @@ snapshots:
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
bullmq@5.11.0:
dependencies:
cron-parser: 4.9.0
ioredis: 5.4.1
msgpackr: 1.11.0
node-abort-controller: 3.1.1
semver: 7.6.3
tslib: 2.6.3
uuid: 9.0.1
transitivePeerDependencies:
- supports-color
bytes@3.1.2: {} bytes@3.1.2: {}
cac@6.7.14: {} cac@6.7.14: {}
@ -6493,6 +6611,10 @@ snapshots:
create-require@1.1.1: {} create-require@1.1.1: {}
cron-parser@4.9.0:
dependencies:
luxon: 3.4.4
cross-spawn@7.0.3: cross-spawn@7.0.3:
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1
@ -7127,6 +7249,15 @@ snapshots:
graphemer@1.4.0: {} graphemer@1.4.0: {}
handlebars@4.7.8:
dependencies:
minimist: 1.2.8
neo-async: 2.6.2
source-map: 0.6.1
wordwrap: 1.0.0
optionalDependencies:
uglify-js: 3.19.1
has-flag@4.0.0: {} has-flag@4.0.0: {}
has-property-descriptors@1.0.2: has-property-descriptors@1.0.2:
@ -7373,6 +7504,8 @@ snapshots:
dependencies: dependencies:
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
luxon@3.4.4: {}
magic-string@0.30.10: magic-string@0.30.10:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
@ -7481,6 +7614,22 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
msgpackr-extract@3.0.3:
dependencies:
node-gyp-build-optional-packages: 5.2.2
optionalDependencies:
'@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
'@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
'@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
'@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
'@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
'@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
optional: true
msgpackr@1.11.0:
optionalDependencies:
msgpackr-extract: 3.0.3
mz@2.7.0: mz@2.7.0:
dependencies: dependencies:
any-promise: 1.3.0 any-promise: 1.3.0
@ -7495,14 +7644,25 @@ snapshots:
negotiator@0.6.3: {} negotiator@0.6.3: {}
neo-async@2.6.2: {}
node-abort-controller@3.1.1: {}
node-fetch@2.7.0: node-fetch@2.7.0:
dependencies: dependencies:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
node-gyp-build-optional-packages@5.2.2:
dependencies:
detect-libc: 2.0.3
optional: true
node-gyp-build@4.8.1: {} node-gyp-build@4.8.1: {}
node-releases@2.0.14: {} node-releases@2.0.14: {}
nodemailer@6.9.14: {}
nopt@5.0.0: nopt@5.0.0:
dependencies: dependencies:
abbrev: 1.1.1 abbrev: 1.1.1
@ -8737,6 +8897,9 @@ snapshots:
ufo@1.5.3: {} ufo@1.5.3: {}
uglify-js@3.19.1:
optional: true
ultrahtml@1.5.3: {} ultrahtml@1.5.3: {}
undici-types@5.26.5: {} undici-types@5.26.5: {}
@ -8770,6 +8933,8 @@ snapshots:
utils-merge@1.0.1: {} utils-merge@1.0.1: {}
uuid@9.0.1: {}
v8-compile-cache-lib@3.0.1: {} v8-compile-cache-lib@3.0.1: {}
valibot@0.31.1: valibot@0.31.1:
@ -8878,6 +9043,8 @@ snapshots:
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
wordwrap@1.0.0: {}
wrap-ansi@6.2.0: wrap-ansi@6.2.0:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0

View file

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

View file

@ -1,13 +1,26 @@
import { Hono } from 'hono'; import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator'; import { zValidator } from '@hono/zod-validator';
import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto'; import { inject, injectable } from 'tsyringe';
import type { HonoTypes } from '../types';
import { limiter } from '../middleware/rate-limiter.middleware'; import { limiter } from '../middleware/rate-limiter.middleware';
import type { Controller } from '../interfaces/controller.interface';
import { signInEmailDto } from '$lib/dtos/signin-email.dto';
import type { LoginRequestsService } from '../services/loginrequest.service';
const app = new Hono() @injectable()
.post('/', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { export class LoginController implements Controller {
const { email } = c.req.valid('json'); controller = new Hono<HonoTypes>();
await loginRequestsService.create({ email });
return c.json({ message: 'Verification email sent' });
});
export default app; constructor(
@inject('LoginRequestsService') private readonly loginRequestsService: LoginRequestsService
) { }
routes() {
return this.controller
.post('/', zValidator('json', signInEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const { username, password } = c.req.valid('json');
await this.loginRequestsService.verify({ username, password });
return c.json({ message: 'Verification email sent' });
})
}
}

View file

@ -0,0 +1,20 @@
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
import { timestamps } from '../utils';
import { usersTable } from "./users.table";
enum CredentialsType {
SECRET = 'secret',
PASSWORD = 'password',
TOTP = 'totp',
HOTP = 'hotp'
}
export const credentialsTable = pgTable('credentials', {
id: uuid('id').primaryKey().defaultRandom(),
user_id: uuid('user_id')
.notNull()
.references(() => usersTable.id, { onDelete: 'cascade' }),
type: text('type').notNull().default(CredentialsType.PASSWORD),
secret_data: text('secret_data').notNull(),
...timestamps
});

View file

@ -1,4 +1,4 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm'; import { type InferSelectModel, relations } from 'drizzle-orm';
import games from './games'; import games from './games';

View file

@ -0,0 +1,14 @@
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
import { usersTable } from "./users.table";
import { timestamps } from '../utils';
export const federatedIdentityTable = pgTable('federated_identity', {
id: uuid('id').primaryKey().defaultRandom(),
user_id: uuid('user_id')
.notNull()
.references(() => usersTable.id, { onDelete: 'cascade' }),
idenitity_provider: text('idenitity_provider').notNull(),
federated_user_id: text('federated_user_id').notNull(),
federated_username: text('federated_username').notNull(),
...timestamps
});

View file

@ -0,0 +1,8 @@
import { Hono } from 'hono';
import type { HonoTypes } from '../types';
import type { BlankSchema } from 'hono/types';
export interface Controller {
controller: Hono<HonoTypes, BlankSchema, '/'>;
routes(): any;
}

View file

@ -0,0 +1,10 @@
import { Argon2id } from "oslo/password";
export async function hash(value: string) {
const argon2 = new Argon2id()
return argon2.hash(value);
}
export function verify(hashedValue: string, value: string) {
return new Argon2id().verify(hashedValue, value);
}

View file

@ -1,3 +1,4 @@
import { container } from 'tsyringe';
import { db } from '../infrastructure/database'; import { db } from '../infrastructure/database';
// Symbol // Symbol

View file

@ -1,10 +1,11 @@
// import { lucia } from '../infrastructure/auth/lucia'; import { container } from 'tsyringe';
import { lucia } from '../infrastructure/auth/lucia';
// // Symbol // Symbol
// export const LuciaProvider = Symbol('LUCIA_PROVIDER'); export const LuciaProvider = Symbol('LUCIA_PROVIDER');
// // Type // Type
// export type LuciaProvider = typeof lucia; export type LuciaProvider = typeof lucia;
// // Register // Register
// container.register<LuciaProvider>(LuciaProvider, { useValue: lucia }); container.register<LuciaProvider>(LuciaProvider, { useValue: lucia });

View file

@ -0,0 +1,35 @@
import { eq, type InferInsertModel } from "drizzle-orm";
import { credentialsTable } from "../infrastructure/database/tables/credentials.table";
import { db } from "../infrastructure/database";
import { takeFirstOrThrow } from "../infrastructure/database/utils";
export type CreateCredentials = InferInsertModel<typeof credentialsTable>;
export type UpdateCredentials = Partial<CreateCredentials>;
export class CredentialsRepository {
async findOneById(id: string) {
return db.query.credentialsTable.findFirst({
where: eq(credentialsTable.id, id)
});
}
async findOneByIdOrThrow(id: string) {
const credentials = await this.findOneById(id);
if (!credentials) throw Error('Credentials not found');
return credentials;
}
async create(data: CreateCredentials) {
return db.insert(credentialsTable).values(data).returning().then(takeFirstOrThrow);
}
async update(id: string, data: UpdateCredentials) {
return db
.update(credentialsTable)
.set(data)
.where(eq(credentialsTable.id, id))
.returning()
.then(takeFirstOrThrow);
}
}

View file

@ -35,6 +35,12 @@ export class UsersRepository {
return user; return user;
} }
async findOneByUsername(username: string) {
return db.query.usersTable.findFirst({
where: eq(usersTable.username, username)
});
}
async findOneByEmail(email: string) { async findOneByEmail(email: string) {
return db.query.usersTable.findFirst({ return db.query.usersTable.findFirst({
where: eq(usersTable.email, email) where: eq(usersTable.email, email)

View file

@ -1,3 +1,4 @@
import { injectable } from "tsyringe";
import { Argon2id } from "oslo/password"; import { Argon2id } from "oslo/password";
/* ---------------------------------- Note ---------------------------------- */ /* ---------------------------------- Note ---------------------------------- */
@ -19,6 +20,7 @@ node_modules/.pnpm/@node-rs+argon2@1.7.0/node_modules/@node-rs/argon2/index.js:1
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
// If you don't use a hasher from oslo, which are preconfigured with recommended parameters from OWASP, // If you don't use a hasher from oslo, which are preconfigured with recommended parameters from OWASP,
// ensure that you configure them properly. // ensure that you configure them properly.
@injectable()
export class HashingService { export class HashingService {
private readonly hasher = new Argon2id(); private readonly hasher = new Argon2id();

View file

@ -1,4 +1,5 @@
import { lucia } from '../infrastructure/auth/lucia'; import { inject, injectable } from 'tsyringe';
import { LuciaProvider } from '../providers/lucia.provider';
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Service */ /* Service */
@ -17,8 +18,13 @@ Create private functions to handle complex logic and keep the public methods as
simple as possible. This makes the service easier to read, test and understand. simple as possible. This makes the service easier to read, test and understand.
*/ */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@injectable()
export class IamService { export class IamService {
constructor(
@inject(LuciaProvider) private readonly lucia: LuciaProvider,
) { }
async logout(sessionId: string) { async logout(sessionId: string) {
return lucia.invalidateSession(sessionId); return this.lucia.invalidateSession(sessionId);
} }
} }

View file

@ -0,0 +1,75 @@
import { inject, injectable } from 'tsyringe';
import { BadRequest } from '../common/errors';
import { DatabaseProvider } from '../providers';
import { MailerService } from './mailer.service';
import { TokensService } from './tokens.service';
import { LuciaProvider } from '../providers/lucia.provider';
import { UsersRepository } from '../repositories/users.repository';
import type { SignInEmailDto } from '../../../dtos/signin-email.dto';
import type { RegisterEmailDto } from '../../../dtos/register-email.dto';
import { LoginRequestsRepository } from '../repositories/login-requests.repository';
@injectable()
export class LoginRequestsService {
constructor(
@inject(LuciaProvider) private readonly lucia: LuciaProvider,
@inject(DatabaseProvider) private readonly db: DatabaseProvider,
@inject(TokensService) private readonly tokensService: TokensService,
@inject(MailerService) private readonly mailerService: MailerService,
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
@inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository,
) { }
async validate(data: SignInEmailDto) {
}
async create(data: RegisterEmailDto) {
// generate a token, expiry date, and hash
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
// save the login request to the database - ensuring we save the hashedToken
await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
// send the login request email
await this.mailerService.sendLoginRequest({
to: data.email,
props: { token: token }
});
}
async verify(data: SignInEmailDto) {
let existingUser = await this.usersRepository.findOneByUsername(data.username);
if (!existingUser) {
throw BadRequest('User not found');
}
return this.lucia.createSession(existingUser.id, {});
}
// Create a new user and send a welcome email - or other onboarding process
private async handleNewUserRegistration(email: string) {
const newUser = await this.usersRepository.create({ email, verified: true, avatar: null })
this.mailerService.sendWelcome({ to: email, props: null });
// TODO: add whatever onboarding process or extra data you need here
return newUser
}
// Fetch a valid request from the database, verify the token and burn the request if it is valid
private async fetchValidRequest(email: string, token: string) {
return await this.db.transaction(async (trx) => {
// fetch the login request
const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email)
if (!loginRequest) return null;
// check if the token is valid
const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token);
if (!isValidRequest) return null
// if the token is valid, burn the request
await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id);
return loginRequest
})
}
}

View file

@ -11,14 +11,14 @@ import { injectable } from 'tsyringe';
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* ---------------------------------- About --------------------------------- */ /* ---------------------------------- About --------------------------------- */
/* /*
Services are responsible for handling business logic and data manipulation. Services are responsible for handling business logic and data manipulation.
They genreally call on repositories or other services to complete a use-case. They genreally call on repositories or other services to complete a use-case.
*/ */
/* ---------------------------------- Notes --------------------------------- */ /* ---------------------------------- Notes --------------------------------- */
/* /*
Services should be kept as clean and simple as possible. Services should be kept as clean and simple as possible.
Create private functions to handle complex logic and keep the public methods as Create private functions to handle complex logic and keep the public methods as
simple as possible. This makes the service easier to read, test and understand. simple as possible. This makes the service easier to read, test and understand.
*/ */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

View file

@ -1,9 +1,11 @@
import { inject, injectable } from "tsyringe";
import { generateRandomString } from "oslo/crypto"; import { generateRandomString } from "oslo/crypto";
import { TimeSpan, createDate, type TimeSpanUnit } from 'oslo'; import { TimeSpan, createDate, type TimeSpanUnit } from 'oslo';
import { HashingService } from "./hashing.service"; import { HashingService } from "./hashing.service";
@injectable()
export class TokensService { export class TokensService {
private readonly hashingService = new HashingService(); constructor(@inject(HashingService) private readonly hashingService: HashingService) { }
generateToken() { generateToken() {
const alphabet = '23456789ACDEFGHJKLMNPQRSTUVWXYZ'; // alphabet with removed look-alike characters (0, 1, O, I) const alphabet = '23456789ACDEFGHJKLMNPQRSTUVWXYZ'; // alphabet with removed look-alike characters (0, 1, O, I)