Starting over migrations, revert schema date string type, using two variables in session for knowing if 2FA is valid, adding checks everywhere on protected routes and APIs.

This commit is contained in:
Bradley Shellnut 2024-06-11 19:12:12 -07:00
parent 36aa0636a3
commit e4ff068fe1
52 changed files with 718 additions and 4900 deletions

View file

@ -10,7 +10,7 @@ export default defineConfig({
port: Number(process.env.DATABASE_PORT) || 5432,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE || 'boredgame',
database: process.env.DATABASE_DB || 'boredgame',
ssl: process.env.DATABASE_HOST !== 'localhost',
},
// Print all statements

View file

@ -36,10 +36,10 @@
"@typescript-eslint/parser": "^7.12.0",
"autoprefixer": "^10.4.19",
"dotenv": "^16.4.5",
"drizzle-kit": "^0.22.6",
"drizzle-kit": "^0.22.7",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.39.0",
"eslint-plugin-svelte": "^2.39.3",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0",
"postcss": "^8.4.38",
@ -59,11 +59,11 @@
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.4",
"sveltekit-rate-limiter": "^0.5.1",
"sveltekit-superforms": "^2.14.0",
"sveltekit-superforms": "^2.15.1",
"tailwindcss": "^3.4.4",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"tsx": "^4.14.1",
"tsx": "^4.15.1",
"typescript": "^5.4.5",
"vite": "^5.2.13",
"vitest": "^1.6.0",

View file

@ -61,7 +61,7 @@ importers:
version: 4.29.2
formsnap:
specifier: ^1.0.0
version: 1.0.0(svelte@4.2.18)(sveltekit-superforms@2.14.0(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(svelte@4.2.18))
version: 1.0.0(svelte@4.2.18)(sveltekit-superforms@2.15.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18))
html-entities:
specifier: ^2.5.2
version: 2.5.2
@ -166,8 +166,8 @@ importers:
specifier: ^16.4.5
version: 16.4.5
drizzle-kit:
specifier: ^0.22.6
version: 0.22.6
specifier: ^0.22.7
version: 0.22.7
eslint:
specifier: ^8.57.0
version: 8.57.0
@ -175,8 +175,8 @@ importers:
specifier: ^9.1.0
version: 9.1.0(eslint@8.57.0)
eslint-plugin-svelte:
specifier: ^2.39.0
version: 2.39.0(eslint@8.57.0)(svelte@4.2.18)(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))
specifier: ^2.39.3
version: 2.39.3(eslint@8.57.0)(svelte@4.2.18)(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))
just-clone:
specifier: ^6.2.0
version: 6.2.0
@ -191,7 +191,7 @@ importers:
version: 16.1.0(postcss@8.4.38)
postcss-load-config:
specifier: ^5.1.0
version: 5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1)
version: 5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1)
postcss-preset-env:
specifier: ^9.5.14
version: 9.5.14(postcss@8.4.38)
@ -215,7 +215,7 @@ importers:
version: 4.2.18
svelte-check:
specifier: ^3.8.0
version: 3.8.0(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)
version: 3.8.0(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)
svelte-headless-table:
specifier: ^0.18.2
version: 0.18.2(svelte@4.2.18)
@ -224,7 +224,7 @@ importers:
version: 3.1.2(svelte@4.2.18)(typescript@5.4.5)
svelte-preprocess:
specifier: ^5.1.4
version: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)(typescript@5.4.5)
version: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)(typescript@5.4.5)
svelte-sequential-preprocessor:
specifier: ^2.0.1
version: 2.0.1
@ -235,8 +235,8 @@ importers:
specifier: ^0.5.1
version: 0.5.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))
sveltekit-superforms:
specifier: ^2.14.0
version: 2.14.0(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(svelte@4.2.18)
specifier: ^2.15.1
version: 2.15.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)
tailwindcss:
specifier: ^3.4.4
version: 3.4.4(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))
@ -247,8 +247,8 @@ importers:
specifier: ^2.6.3
version: 2.6.3
tsx:
specifier: ^4.14.1
version: 4.14.1
specifier: ^4.15.1
version: 4.15.1
typescript:
specifier: ^5.4.5
version: 5.4.5
@ -272,11 +272,11 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
'@arktype/schema@0.1.7':
resolution: {integrity: sha512-xeaS/0EiuT5kUQGC9DqLv0vnjFpbEILmaBQF9CrLhamR0v8c+eUNM6z5u6DgzqPZbDFMmtfiCdikUTT1VueWXw==}
'@arktype/schema@0.1.13':
resolution: {integrity: sha512-qZjtCAKrnhsixDWsEGJtosWfi4bLpAg4OnnICVYTer/6v5hwlhsdYpYobTSJUc5eiBoI5Ai/kcNfYaQISshY2g==}
'@arktype/util@0.0.45':
resolution: {integrity: sha512-WPzoElBZK1NxYzT8PnoNsnulohgRU7PRKkJUoqeGvuFqP/Egv7tRNnvcJCE0MboHUnWaPTy/5Psjm/4iOvbWiw==}
'@arktype/util@0.0.48':
resolution: {integrity: sha512-U5FO5EUAJ4LoYtLSyAMmTf6CEVgslObfSQuua2zoK5Tv2FB3aESVQ3rdLfhuz+coRhlzlynbkmimyoQWwQT+aQ==}
'@babel/runtime@7.24.5':
resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==}
@ -547,6 +547,12 @@ packages:
cpu: [ppc64]
os: [aix]
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.18.20':
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
engines: {node: '>=12'}
@ -565,6 +571,12 @@ packages:
cpu: [arm64]
os: [android]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.18.20':
resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
engines: {node: '>=12'}
@ -583,6 +595,12 @@ packages:
cpu: [arm]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.18.20':
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
engines: {node: '>=12'}
@ -601,6 +619,12 @@ packages:
cpu: [x64]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.18.20':
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
engines: {node: '>=12'}
@ -619,6 +643,12 @@ packages:
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.18.20':
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
engines: {node: '>=12'}
@ -637,6 +667,12 @@ packages:
cpu: [x64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.18.20':
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
engines: {node: '>=12'}
@ -655,6 +691,12 @@ packages:
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.18.20':
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
engines: {node: '>=12'}
@ -673,6 +715,12 @@ packages:
cpu: [x64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.18.20':
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
engines: {node: '>=12'}
@ -691,6 +739,12 @@ packages:
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.18.20':
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
engines: {node: '>=12'}
@ -709,6 +763,12 @@ packages:
cpu: [arm]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.18.20':
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
engines: {node: '>=12'}
@ -727,6 +787,12 @@ packages:
cpu: [ia32]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.18.20':
resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
engines: {node: '>=12'}
@ -745,6 +811,12 @@ packages:
cpu: [loong64]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.18.20':
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
engines: {node: '>=12'}
@ -763,6 +835,12 @@ packages:
cpu: [mips64el]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.18.20':
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
engines: {node: '>=12'}
@ -781,6 +859,12 @@ packages:
cpu: [ppc64]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.18.20':
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
engines: {node: '>=12'}
@ -799,6 +883,12 @@ packages:
cpu: [riscv64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.18.20':
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
engines: {node: '>=12'}
@ -817,6 +907,12 @@ packages:
cpu: [s390x]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.18.20':
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
engines: {node: '>=12'}
@ -835,6 +931,12 @@ packages:
cpu: [x64]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.18.20':
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
engines: {node: '>=12'}
@ -853,6 +955,12 @@ packages:
cpu: [x64]
os: [netbsd]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.18.20':
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
engines: {node: '>=12'}
@ -871,6 +979,12 @@ packages:
cpu: [x64]
os: [openbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.18.20':
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
engines: {node: '>=12'}
@ -889,6 +1003,12 @@ packages:
cpu: [x64]
os: [sunos]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.18.20':
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
engines: {node: '>=12'}
@ -907,6 +1027,12 @@ packages:
cpu: [arm64]
os: [win32]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.18.20':
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
engines: {node: '>=12'}
@ -925,6 +1051,12 @@ packages:
cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.18.20':
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
engines: {node: '>=12'}
@ -943,6 +1075,12 @@ packages:
cpu: [x64]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -979,14 +1117,9 @@ packages:
'@fontsource/fira-mono@5.0.13':
resolution: {integrity: sha512-fZDjR2BdAqmauEbTjcIT62zYzbOgDa5+IQH34D2k8Pxmy1T815mAqQkZciWZVQ9dc/BgdTtTUV9HJ2ulBNwchg==}
'@gcornut/valibot-json-schema@0.0.27':
resolution: {integrity: sha512-xcMaUStVgQzPrK3d7PuLFbQ+3qSp6LzaLExAm52E3FKmUfjQa7Sw5cDK6Hfu/8WT0yfGsuSCuJ5uT1sosjR9Qg==}
'@gcornut/valibot-json-schema@0.31.0':
resolution: {integrity: sha512-3xGptCurm23e7nuPQkdrE5rEs1FeTPHhAUsBuwwqG4/YeZLwJOoYZv+fmsppUEfo5y9lzUwNQrNqLS/q7HMc7g==}
hasBin: true
peerDependencies:
'@types/json-schema': '>= 7.0.14'
esbuild: '>= 0.18.20'
esbuild-runner: '>= 2.2.2'
valibot: '>= 0.21.0'
'@hapi/hoek@9.3.0':
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
@ -1935,8 +2068,8 @@ packages:
aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
arktype@2.0.0-dev.15:
resolution: {integrity: sha512-V8/jyfU/ISl9uSzTNZMgj/sYOI1QNxhVaqcS+spWamF/jx4eDFMqBVdhAGysJhKyLC+Qi2yNw5f1YQuaOMEeGw==}
arktype@2.0.0-dev.21:
resolution: {integrity: sha512-dgHCjb3FK4BGvG2LuXqgdWXstbFmiYowSy0jiKnyk4KVcMT5DyIJ9d1nbQM3ztiAL3hIPmPdkmpfxUqR+BoOBQ==}
array-union@2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
@ -2242,8 +2375,8 @@ packages:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
drizzle-kit@0.22.6:
resolution: {integrity: sha512-TE4mBMeVJvySjYxwKeUOMrzytFSKnpMyp6kBnm+aascu9P+hCMrGvnJhOAO94C3wmt144CoHZQWQCOoP/EFl8A==}
drizzle-kit@0.22.7:
resolution: {integrity: sha512-9THPCb2l1GPt7wxhws9LvTR0YG565ZlVgTuqGMwjs590Kch1pXu4GyjEArVijSF5m0OBj3qgdeKmuJXhKXgWFw==}
hasBin: true
drizzle-orm@0.31.2:
@ -2376,6 +2509,11 @@ packages:
engines: {node: '>=12'}
hasBin: true
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
hasBin: true
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
@ -2399,8 +2537,8 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
eslint-plugin-svelte@2.39.0:
resolution: {integrity: sha512-FXktBLXsrxbA+6ZvJK2z/sQOrUKyzSg3fNWK5h0reSCjr2fjAsc9ai/s/JvSl4Hgvz3nYVtTIMwarZH5RcB7BA==}
eslint-plugin-svelte@2.39.3:
resolution: {integrity: sha512-uXsHW+VOSHRI3VgoDit4CURKos9wDque6CuaBNw8z6UyF5Rfc2XHmNEsRvvOd+VmMUtS+wC9bvwArv2tt4TFGA==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0
@ -3713,8 +3851,8 @@ packages:
peerDependencies:
svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0
svelte-eslint-parser@0.36.0:
resolution: {integrity: sha512-/6YmUSr0FAVxW8dXNdIMydBnddPMHzaHirAZ7RrT21XYdgGGZMh0LQG6CZsvAFS4r2Y4ItUuCQc8TQ3urB30mQ==}
svelte-eslint-parser@0.37.0:
resolution: {integrity: sha512-AXd5ar7dcOK+H86JomxcSaWevhs2J7o/xOwg+kDQu98uuATpm+tE5Twp7u8UQCdbWKB34Idu/CZyHmTOxfSQMw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.115
@ -3827,8 +3965,8 @@ packages:
peerDependencies:
'@sveltejs/kit': 1.x || 2.x
sveltekit-superforms@2.14.0:
resolution: {integrity: sha512-TRN+x2+ENCnvDw70U5HLfmGQGFi4kpevpWaPpQ06AB0Wf5qCYxshbZBofMAXb8KOyetw8dhWpj86AQRPNwhzDg==}
sveltekit-superforms@2.15.1:
resolution: {integrity: sha512-rLzcJTGEzt2oFC1fNYn+ddM25uoCawudHBU7qoLo5gp/JLMRNhtX9gbBMt8imMLo4VcB8339VtxBRcWiMV1faQ==}
peerDependencies:
'@sveltejs/kit': 1.x || 2.x
svelte: 3.x || 4.x || >=5.0.0-next.51
@ -3939,8 +4077,8 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tsx@4.14.1:
resolution: {integrity: sha512-GU8pPJq8DdxcJDSK6Bc64c2jW8zBK2hb0jzwHZDfjapbwu6AqvFnAElnzZ17Xb9TH5a/j6/sicTCVYF+eO/cmA==}
tsx@4.15.1:
resolution: {integrity: sha512-k/6h17jA1KfUR7SpcteOa880zGmF56s8gMIcSqUR5avyNFi9nlCEKpMiHLrzrqyARGr52A/JablmGey1DEWbCA==}
engines: {node: '>=18.0.0'}
hasBin: true
@ -3995,8 +4133,8 @@ packages:
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
valibot@0.30.0:
resolution: {integrity: sha512-5POBdbSkM+3nvJ6ZlyQHsggisfRtyT4tVTo1EIIShs6qCdXJnyWU5TJ68vr8iTg5zpOLjXLRiBqNx+9zwZz/rA==}
valibot@0.31.1:
resolution: {integrity: sha512-2YYIhPrnVSz/gfT2/iXVTrSj92HwchCt9Cga/6hX4B26iCz9zkIsGTS0HjDYTZfTi1Un0X6aRvhBi1cfqs/i0Q==}
validator@13.11.0:
resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
@ -4180,12 +4318,12 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
'@arktype/schema@0.1.7':
'@arktype/schema@0.1.13':
dependencies:
'@arktype/util': 0.0.45
'@arktype/util': 0.0.48
optional: true
'@arktype/util@0.0.45':
'@arktype/util@0.0.48':
optional: true
'@babel/runtime@7.24.5':
@ -4457,6 +4595,9 @@ snapshots:
'@esbuild/aix-ppc64@0.20.2':
optional: true
'@esbuild/aix-ppc64@0.21.5':
optional: true
'@esbuild/android-arm64@0.18.20':
optional: true
@ -4466,6 +4607,9 @@ snapshots:
'@esbuild/android-arm64@0.20.2':
optional: true
'@esbuild/android-arm64@0.21.5':
optional: true
'@esbuild/android-arm@0.18.20':
optional: true
@ -4475,6 +4619,9 @@ snapshots:
'@esbuild/android-arm@0.20.2':
optional: true
'@esbuild/android-arm@0.21.5':
optional: true
'@esbuild/android-x64@0.18.20':
optional: true
@ -4484,6 +4631,9 @@ snapshots:
'@esbuild/android-x64@0.20.2':
optional: true
'@esbuild/android-x64@0.21.5':
optional: true
'@esbuild/darwin-arm64@0.18.20':
optional: true
@ -4493,6 +4643,9 @@ snapshots:
'@esbuild/darwin-arm64@0.20.2':
optional: true
'@esbuild/darwin-arm64@0.21.5':
optional: true
'@esbuild/darwin-x64@0.18.20':
optional: true
@ -4502,6 +4655,9 @@ snapshots:
'@esbuild/darwin-x64@0.20.2':
optional: true
'@esbuild/darwin-x64@0.21.5':
optional: true
'@esbuild/freebsd-arm64@0.18.20':
optional: true
@ -4511,6 +4667,9 @@ snapshots:
'@esbuild/freebsd-arm64@0.20.2':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
optional: true
'@esbuild/freebsd-x64@0.18.20':
optional: true
@ -4520,6 +4679,9 @@ snapshots:
'@esbuild/freebsd-x64@0.20.2':
optional: true
'@esbuild/freebsd-x64@0.21.5':
optional: true
'@esbuild/linux-arm64@0.18.20':
optional: true
@ -4529,6 +4691,9 @@ snapshots:
'@esbuild/linux-arm64@0.20.2':
optional: true
'@esbuild/linux-arm64@0.21.5':
optional: true
'@esbuild/linux-arm@0.18.20':
optional: true
@ -4538,6 +4703,9 @@ snapshots:
'@esbuild/linux-arm@0.20.2':
optional: true
'@esbuild/linux-arm@0.21.5':
optional: true
'@esbuild/linux-ia32@0.18.20':
optional: true
@ -4547,6 +4715,9 @@ snapshots:
'@esbuild/linux-ia32@0.20.2':
optional: true
'@esbuild/linux-ia32@0.21.5':
optional: true
'@esbuild/linux-loong64@0.18.20':
optional: true
@ -4556,6 +4727,9 @@ snapshots:
'@esbuild/linux-loong64@0.20.2':
optional: true
'@esbuild/linux-loong64@0.21.5':
optional: true
'@esbuild/linux-mips64el@0.18.20':
optional: true
@ -4565,6 +4739,9 @@ snapshots:
'@esbuild/linux-mips64el@0.20.2':
optional: true
'@esbuild/linux-mips64el@0.21.5':
optional: true
'@esbuild/linux-ppc64@0.18.20':
optional: true
@ -4574,6 +4751,9 @@ snapshots:
'@esbuild/linux-ppc64@0.20.2':
optional: true
'@esbuild/linux-ppc64@0.21.5':
optional: true
'@esbuild/linux-riscv64@0.18.20':
optional: true
@ -4583,6 +4763,9 @@ snapshots:
'@esbuild/linux-riscv64@0.20.2':
optional: true
'@esbuild/linux-riscv64@0.21.5':
optional: true
'@esbuild/linux-s390x@0.18.20':
optional: true
@ -4592,6 +4775,9 @@ snapshots:
'@esbuild/linux-s390x@0.20.2':
optional: true
'@esbuild/linux-s390x@0.21.5':
optional: true
'@esbuild/linux-x64@0.18.20':
optional: true
@ -4601,6 +4787,9 @@ snapshots:
'@esbuild/linux-x64@0.20.2':
optional: true
'@esbuild/linux-x64@0.21.5':
optional: true
'@esbuild/netbsd-x64@0.18.20':
optional: true
@ -4610,6 +4799,9 @@ snapshots:
'@esbuild/netbsd-x64@0.20.2':
optional: true
'@esbuild/netbsd-x64@0.21.5':
optional: true
'@esbuild/openbsd-x64@0.18.20':
optional: true
@ -4619,6 +4811,9 @@ snapshots:
'@esbuild/openbsd-x64@0.20.2':
optional: true
'@esbuild/openbsd-x64@0.21.5':
optional: true
'@esbuild/sunos-x64@0.18.20':
optional: true
@ -4628,6 +4823,9 @@ snapshots:
'@esbuild/sunos-x64@0.20.2':
optional: true
'@esbuild/sunos-x64@0.21.5':
optional: true
'@esbuild/win32-arm64@0.18.20':
optional: true
@ -4637,6 +4835,9 @@ snapshots:
'@esbuild/win32-arm64@0.20.2':
optional: true
'@esbuild/win32-arm64@0.21.5':
optional: true
'@esbuild/win32-ia32@0.18.20':
optional: true
@ -4646,6 +4847,9 @@ snapshots:
'@esbuild/win32-ia32@0.20.2':
optional: true
'@esbuild/win32-ia32@0.21.5':
optional: true
'@esbuild/win32-x64@0.18.20':
optional: true
@ -4655,6 +4859,9 @@ snapshots:
'@esbuild/win32-x64@0.20.2':
optional: true
'@esbuild/win32-x64@0.21.5':
optional: true
'@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
dependencies:
eslint: 8.57.0
@ -4698,12 +4905,13 @@ snapshots:
'@fontsource/fira-mono@5.0.13': {}
'@gcornut/valibot-json-schema@0.0.27(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(valibot@0.30.0)':
'@gcornut/valibot-json-schema@0.31.0':
dependencies:
valibot: 0.31.1
optionalDependencies:
'@types/json-schema': 7.0.15
esbuild: 0.19.12
esbuild-runner: 2.2.2(esbuild@0.19.12)
valibot: 0.30.0
esbuild: 0.21.5
esbuild-runner: 2.2.2(esbuild@0.21.5)
optional: true
'@hapi/hoek@9.3.0':
@ -5575,10 +5783,10 @@ snapshots:
dependencies:
dequal: 2.0.3
arktype@2.0.0-dev.15:
arktype@2.0.0-dev.21:
dependencies:
'@arktype/schema': 0.1.7
'@arktype/util': 0.0.45
'@arktype/schema': 0.1.13
'@arktype/util': 0.0.48
optional: true
array-union@2.1.0: {}
@ -5852,7 +6060,7 @@ snapshots:
dotenv@16.4.5: {}
drizzle-kit@0.22.6:
drizzle-kit@0.22.7:
dependencies:
'@esbuild-kit/esm-loader': 2.6.5
esbuild: 0.19.12
@ -5888,9 +6096,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
esbuild-runner@2.2.2(esbuild@0.19.12):
esbuild-runner@2.2.2(esbuild@0.21.5):
dependencies:
esbuild: 0.19.12
esbuild: 0.21.5
source-map-support: 0.5.21
tslib: 2.4.0
optional: true
@ -5972,6 +6180,32 @@ snapshots:
'@esbuild/win32-ia32': 0.20.2
'@esbuild/win32-x64': 0.20.2
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
'@esbuild/android-arm': 0.21.5
'@esbuild/android-arm64': 0.21.5
'@esbuild/android-x64': 0.21.5
'@esbuild/darwin-arm64': 0.21.5
'@esbuild/darwin-x64': 0.21.5
'@esbuild/freebsd-arm64': 0.21.5
'@esbuild/freebsd-x64': 0.21.5
'@esbuild/linux-arm': 0.21.5
'@esbuild/linux-arm64': 0.21.5
'@esbuild/linux-ia32': 0.21.5
'@esbuild/linux-loong64': 0.21.5
'@esbuild/linux-mips64el': 0.21.5
'@esbuild/linux-ppc64': 0.21.5
'@esbuild/linux-riscv64': 0.21.5
'@esbuild/linux-s390x': 0.21.5
'@esbuild/linux-x64': 0.21.5
'@esbuild/netbsd-x64': 0.21.5
'@esbuild/openbsd-x64': 0.21.5
'@esbuild/sunos-x64': 0.21.5
'@esbuild/win32-arm64': 0.21.5
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
escalade@3.1.2: {}
escape-html@1.0.3: {}
@ -5987,11 +6221,10 @@ snapshots:
dependencies:
eslint: 8.57.0
eslint-plugin-svelte@2.39.0(eslint@8.57.0)(svelte@4.2.18)(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5)):
eslint-plugin-svelte@2.39.3(eslint@8.57.0)(svelte@4.2.18)(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@jridgewell/sourcemap-codec': 1.4.15
debug: 4.3.5
eslint: 8.57.0
eslint-compat-utils: 0.5.1(eslint@8.57.0)
esutils: 2.0.3
@ -6001,11 +6234,10 @@ snapshots:
postcss-safe-parser: 6.0.0(postcss@8.4.38)
postcss-selector-parser: 6.1.0
semver: 7.6.2
svelte-eslint-parser: 0.36.0(svelte@4.2.18)
svelte-eslint-parser: 0.37.0(svelte@4.2.18)
optionalDependencies:
svelte: 4.2.18
transitivePeerDependencies:
- supports-color
- ts-node
eslint-scope@7.2.2:
@ -6166,11 +6398,11 @@ snapshots:
cross-spawn: 7.0.3
signal-exit: 4.1.0
formsnap@1.0.0(svelte@4.2.18)(sveltekit-superforms@2.14.0(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(svelte@4.2.18)):
formsnap@1.0.0(svelte@4.2.18)(sveltekit-superforms@2.15.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)):
dependencies:
nanoid: 5.0.7
svelte: 4.2.18
sveltekit-superforms: 2.14.0(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(svelte@4.2.18)
sveltekit-superforms: 2.15.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)
fraction.js@4.3.7: {}
@ -6883,14 +7115,14 @@ snapshots:
postcss: 8.4.38
ts-node: 10.9.2(@types/node@20.14.2)(typescript@5.4.5)
postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1):
postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1):
dependencies:
lilconfig: 3.1.1
yaml: 2.4.2
optionalDependencies:
jiti: 1.21.3
postcss: 8.4.38
tsx: 4.14.1
tsx: 4.15.1
postcss-logical@7.0.1(postcss@8.4.38):
dependencies:
@ -7379,7 +7611,7 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
svelte-check@3.8.0(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18):
svelte-check@3.8.0(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
chokidar: 3.6.0
@ -7388,7 +7620,7 @@ snapshots:
picocolors: 1.0.0
sade: 1.8.1
svelte: 4.2.18
svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)(typescript@5.4.5)
svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)(typescript@5.4.5)
typescript: 5.4.5
transitivePeerDependencies:
- '@babel/core'
@ -7401,7 +7633,7 @@ snapshots:
- stylus
- sugarss
svelte-eslint-parser@0.36.0(svelte@4.2.18):
svelte-eslint-parser@0.37.0(svelte@4.2.18):
dependencies:
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
@ -7444,7 +7676,7 @@ snapshots:
dependencies:
svelte: 4.2.18
svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)(typescript@5.4.5):
svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1))(postcss@8.4.38)(sass@1.77.4)(svelte@4.2.18)(typescript@5.4.5):
dependencies:
'@types/pug': 2.0.10
detect-indent: 6.1.0
@ -7454,7 +7686,7 @@ snapshots:
svelte: 4.2.18
optionalDependencies:
postcss: 8.4.38
postcss-load-config: 5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.14.1)
postcss-load-config: 5.1.0(jiti@1.21.3)(postcss@8.4.38)(tsx@4.15.1)
sass: 1.77.4
typescript: 5.4.5
@ -7503,7 +7735,7 @@ snapshots:
'@isaacs/ttlcache': 1.4.1
'@sveltejs/kit': 2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4))
sveltekit-superforms@2.14.0(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(svelte@4.2.18):
sveltekit-superforms@2.15.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18):
dependencies:
'@sveltejs/kit': 2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)))(svelte@4.2.18)(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4))
devalue: 5.0.0
@ -7513,22 +7745,18 @@ snapshots:
ts-deepmerge: 7.0.0
optionalDependencies:
'@exodus/schemasafe': 1.3.0
'@gcornut/valibot-json-schema': 0.0.27(@types/json-schema@7.0.15)(esbuild-runner@2.2.2(esbuild@0.19.12))(esbuild@0.19.12)(valibot@0.30.0)
'@gcornut/valibot-json-schema': 0.31.0
'@sinclair/typebox': 0.32.31
'@sodaru/yup-to-json-schema': 2.0.1
'@vinejs/vine': 1.8.0
arktype: 2.0.0-dev.15
arktype: 2.0.0-dev.21
joi: 17.13.1
json-schema-to-ts: 3.1.0
superstruct: 1.0.4
valibot: 0.30.0
valibot: 0.31.1
yup: 1.4.0
zod: 3.23.8
zod-to-json-schema: 3.23.0(zod@3.23.8)
transitivePeerDependencies:
- '@types/json-schema'
- esbuild
- esbuild-runner
tabbable@6.2.0: {}
@ -7652,9 +7880,9 @@ snapshots:
tslib@2.6.3: {}
tsx@4.14.1:
tsx@4.15.1:
dependencies:
esbuild: 0.20.2
esbuild: 0.21.5
get-tsconfig: 4.7.5
optionalDependencies:
fsevents: 2.3.3
@ -7699,7 +7927,7 @@ snapshots:
v8-compile-cache-lib@3.0.1: {}
valibot@0.30.0:
valibot@0.31.1:
optional: true
validator@13.11.0:

View file

@ -1,5 +1,6 @@
import 'dotenv/config';
import postgres from 'postgres';
import config from '../../drizzle.config';
import { drizzle } from 'drizzle-orm/postgres-js';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
@ -10,12 +11,12 @@ const connection = postgres({
password: process.env.DATABASE_PASSWORD || '',
database: process.env.DATABASE_DB || 'boredgame',
ssl: process.env.NODE_ENV === 'development' ? false : 'require',
max: 1
max: 1,
});
const db = drizzle(connection);
try {
await migrate(db, { migrationsFolder: 'drizzle' });
await migrate(db, { migrationsFolder: config.out! });
console.log('Migrations complete');
} catch (e) {
console.error(e);

View file

@ -52,8 +52,8 @@ CREATE TABLE IF NOT EXISTS "external_ids" (
CREATE TABLE IF NOT EXISTS "games" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"cuid" text,
"name" text,
"slug" text,
"name" text NOT NULL,
"slug" text NOT NULL,
"description" text,
"year_published" integer,
"min_players" integer,
@ -152,7 +152,9 @@ CREATE TABLE IF NOT EXISTS "sessions" (
"user_id" uuid NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"ip_country" text,
"ip_address" text
"ip_address" text,
"two_factor_auth_enabled" boolean DEFAULT false,
"is_two_factor_authenticated" boolean DEFAULT false
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "user_roles" (

View file

@ -1 +0,0 @@
ALTER TABLE "sessions" ADD COLUMN "is_two_factor_authenticated" boolean DEFAULT false;

View file

@ -1,7 +0,0 @@
DROP INDEX IF EXISTS "search_index";--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "name" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "games" ALTER COLUMN "slug" SET NOT NULL;--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "search_index" ON "games" USING gin ((
setweight(to_tsvector('english', "name"), 'A') ||
setweight(to_tsvector('english', "slug"), 'B')
));

View file

@ -1,5 +0,0 @@
DROP INDEX IF EXISTS "search_index";--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "search_index" ON "games" USING gin ((
setweight(to_tsvector('english', "name"), 'A') ||
setweight(to_tsvector('english', "slug"), 'B')
));

View file

@ -1,5 +1,5 @@
{
"id": "9622fc3a-51a1-4f66-8535-fd1ceaf46fc2",
"id": "d62b05e0-f6b5-4fad-b160-bb378e0ad3a0",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@ -403,13 +403,13 @@
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": false
"notNull": true
},
"description": {
"name": "description",
@ -503,7 +503,7 @@
"name": "search_index",
"columns": [
{
"expression": "(\n\t\t\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n\t\t\t\t\t\tsetweight(to_tsvector('english', \"slug\"), 'B')\n\t\t\t\t\t)",
"expression": "(\n setweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )",
"asc": true,
"isExpression": true,
"nulls": "last"
@ -1130,6 +1130,20 @@
"type": "text",
"primaryKey": false,
"notNull": false
},
"two_factor_auth_enabled": {
"name": "two_factor_auth_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"is_two_factor_authenticated": {
"name": "is_two_factor_authenticated",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"indexes": {},

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,29 +5,8 @@
{
"idx": 0,
"version": "7",
"when": 1717548517806,
"tag": "0000_spotty_changeling",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1717891533784,
"tag": "0001_acoustic_thunderbolt",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1717891822622,
"tag": "0002_striped_darkhawk",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1717891852881,
"tag": "0003_flippant_avengers",
"when": 1718038929675,
"tag": "0000_flippant_slyde",
"breakpoints": true
}
]

View file

@ -11,8 +11,8 @@ const categories = pgTable('categories', {
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Categories = InferSelectModel<typeof categories>;

View file

@ -16,8 +16,8 @@ const collection_items = pgTable('collection_items', {
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
times_played: integer('times_played').default(0),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type CollectionItems = InferSelectModel<typeof collection_items>;

View file

@ -12,8 +12,8 @@ const collections = pgTable('collections', {
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Collection'),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const collection_relations = relations(collections, ({ one }) => ({

View file

@ -14,8 +14,8 @@ export const expansions = pgTable('expansions', {
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Expansions = InferSelectModel<typeof expansions>;

View file

@ -27,8 +27,8 @@ const games = pgTable(
thumb_url: text('thumb_url'),
url: text('url'),
last_sync_at: timestamp('last_sync_at'),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
},
(table) => ({
searchIndex: index('search_index').using(
@ -36,9 +36,9 @@ const games = pgTable(
sql`(
setweight(to_tsvector('english', ${table.name}), 'A') ||
setweight(to_tsvector('english', ${table.slug}), 'B')
)`
)`,
),
})
}),
);
export const gameRelations = relations(games, ({ many }) => ({

View file

@ -5,7 +5,7 @@ export {
password_reset_token_relations,
type PasswordResetTokens,
} from './passwordResetTokens';
export { default as sessions } from './sessions';
export { default as sessions, type Sessions } from './sessions';
export { default as roles, role_relations, type Roles } from './roles';
export { default as userRoles, user_role_relations, type UserRoles } from './userRoles';
export { default as collections, collection_relations, type Collections } from './collections';
@ -23,6 +23,7 @@ export {
export { default as externalIds, type ExternalIds, type ExternalIdType } from './externalIds';
export { default as games, gameRelations, type Games } from './games';
export { default as gamesToExternalIds } from './gamesToExternalIds';
export { default as expansions, expansions_relations, type Expansions } from './expansions';
export { default as publishers, publishers_relations, type Publishers } from './publishers';
export { default as publishers_to_games, publishers_to_games_relations } from './publishersToGames';
export { default as publishersToExternalIds } from './publishersToExternalIds';

View file

@ -11,8 +11,8 @@ const mechanics = pgTable('mechanics', {
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Mechanics = InferSelectModel<typeof mechanics>;

View file

@ -10,8 +10,8 @@ const password_reset_tokens = pgTable('password_reset_tokens', {
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
expires_at: timestamp('expires_at', { mode: 'string' }),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
expires_at: timestamp('expires_at'),
created_at: timestamp('created_at').notNull().defaultNow(),
});
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>;

View file

@ -11,8 +11,8 @@ const publishers = pgTable('publishers', {
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Publishers = InferSelectModel<typeof publishers>;

View file

@ -9,8 +9,8 @@ const recovery_codes = pgTable('recovery_codes', {
.references(() => users.id),
code: text('code').notNull(),
used: boolean('used').default(false),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;

View file

@ -10,8 +10,8 @@ const roles = pgTable('roles', {
.$defaultFn(() => cuid2())
.notNull(),
name: text('name').unique().notNull(),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Roles = InferSelectModel<typeof roles>;

View file

@ -1,4 +1,5 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { type InferSelectModel } from 'drizzle-orm';
import users from './users';
const sessions = pgTable('sessions', {
@ -12,7 +13,10 @@ const sessions = pgTable('sessions', {
}).notNull(),
ipCountry: text('ip_country'),
ipAddress: text('ip_address'),
twoFactorAuthEnabled: boolean('two_factor_auth_enabled').default(false),
isTwoFactorAuthenticated: boolean('is_two_factor_authenticated').default(false),
});
export type Sessions = InferSelectModel<typeof sessions>;
export default sessions;

View file

@ -16,8 +16,8 @@ const user_roles = pgTable('user_roles', {
.notNull()
.references(() => roles.id, { onDelete: 'cascade' }),
primary: boolean('primary').default(false),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const user_role_relations = relations(user_roles, ({ one }) => ({

View file

@ -18,8 +18,8 @@ const users = pgTable('users', {
theme: text('theme').default('system'),
two_factor_secret: text('two_factor_secret').default(''),
two_factor_enabled: boolean('two_factor_enabled').default(false),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const user_relations = relations(users, ({ many }) => ({

View file

@ -15,8 +15,8 @@ const wishlist_items = pgTable('wishlist_items', {
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type WishlistItems = InferSelectModel<typeof wishlist_items>;

View file

@ -12,8 +12,8 @@ const wishlists = pgTable('wishlists', {
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Wishlist'),
created_at: timestamp('created_at', { mode: 'string' }).notNull().defaultNow(),
updated_at: timestamp('updated_at', { mode: 'string' }).notNull().defaultNow(),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Wishlists = InferSelectModel<typeof wishlists>;

View file

@ -1,20 +1,50 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from './schema';
import { Table, getTableName, sql } from 'drizzle-orm';
import { Argon2id } from 'oslo/password';
import { ADMIN_USERNAME, DB_SEEDING } from '$env/static/private';
import { db } from '../db';
import * as schema from './schema';
import * as seeds from './seeds';
// create the connection
const pool = new pg.Pool({
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
host: process.env.DATABASE_HOST,
port: Number(process.env.DATABASE_PORT).valueOf(),
database: process.env.DATABASE_DB,
ssl: process.env.DATABASE_HOST !== 'localhost',
});
if (!DB_SEEDING) {
throw new Error('You must set DB_SEEDING to "true" when running seeds');
}
const db = drizzle(pool, { schema: schema });
async function resetTable(db: db, table: Table) {
return db.execute(sql.raw(`TRUNCATE TABLE ${getTableName(table)} RESTART IDENTITY CASCADE`));
}
for (const table of [
schema.categories,
schema.categoriesToExternalIds,
schema.categories_to_games,
schema.collection_items,
schema.collections,
schema.expansions,
schema.externalIds,
schema.games,
schema.gamesToExternalIds,
schema.mechanics,
schema.mechanicsToExternalIds,
schema.mechanics_to_games,
schema.password_reset_tokens,
schema.publishers,
schema.publishersToExternalIds,
schema.publishers_to_games,
schema.recoveryCodes,
schema.roles,
schema.sessions,
schema.userRoles,
schema.users,
schema.wishlists,
schema.wishlist_items,
]) {
// await db.delete(table); // clear tables without truncating / resetting ids
await resetTable(db, table);
}
await seeds.roles(db);
await connection.end();
console.log('Creating roles ...');
const adminRole = await db
@ -42,7 +72,7 @@ console.log('Admin Role: ', adminRole);
const adminUser = await db
.insert(schema.users)
.values({
username: `${process.env.ADMIN_USERNAME}`,
username: `${ADMIN_USERNAME}`,
email: '',
hashed_password: await new Argon2id().hash(`${process.env.ADMIN_PASSWORD}`),
first_name: 'Brad',

View file

View file

@ -0,0 +1,23 @@
[
{
"first_name": "John",
"last_name": "Smith",
"username": "john.smith",
"email": "john.smith@example.com",
"password": "password"
},
{
"first_name": "Jane",
"last_name": "Doe",
"username": "jane.doe",
"email": "jane.doe@example.com",
"password": "password"
},
{
"first_name": "Michael",
"last_name": "Jones",
"username": "michael.jones",
"email": "michael.jones@example.com",
"password": "password"
}
]

30
src/db/seeds/roles.ts Normal file
View file

@ -0,0 +1,30 @@
import { eq } from 'drizzle-orm';
import type db from '@/db';
import * as schema from '@/db/schema';
import roles from './data/roles.json';
// console.log('Creating roles ...');
// const adminRole = await db
// .insert(schema.roles)
// .values([{ name: 'admin' }])
// .onConflictDoNothing()
// .returning();
// const userRole = await db
// .insert(schema.roles)
// .values([{ name: 'user' }])
// .onConflictDoNothing()
// .returning();
// await db
// .insert(schema.roles)
// .values([{ name: 'editor' }])
// .onConflictDoNothing();
// await db
// .insert(schema.roles)
// .values([{ name: 'moderator' }])
// .onConflictDoNothing();
// console.log('Roles created.');
export default async function seed(db: db) {
await db.insert(schema.roles).values({ name: 'user' });
await db.insert(schema.roles).values({ name: 'admin' });
}

41
src/db/seeds/users.ts Normal file
View file

@ -0,0 +1,41 @@
import { eq } from 'drizzle-orm';
import * as argon2 from 'argon2';
import type db from '@/db';
import * as schema from '@/db/schema';
import users from './data/users.json';
async function getCityId(db: db, cityName: string) {
const city = await db.query.city.findFirst({
where: eq(schema.city.name, cityName),
});
if (!city) {
throw new Error('Unknown city name: ' + cityName);
}
return city.id;
}
export default async function seed(db: db) {
await Promise.all(
users.map(async (user) => {
const [insertedUser] = await db
.insert(schema.user)
.values({
...user,
emailVerified: true,
phoneVerified: true,
password: await argon2.hash(user.password),
})
.returning();
await Promise.all(
user.addresses.map(async (address) => {
await db.insert(schema.address).values({
...address,
streetAddress1: address.street_address,
userId: insertedUser.id,
cityId: await getCityId(db, address.city),
});
}),
);
}),
);
}

View file

@ -1,4 +1,4 @@
import { generateIdFromEntropySize } from 'lucia';
import { generateIdFromEntropySize, type Session, type User } from 'lucia';
import { TimeSpan, createDate } from 'oslo';
import { eq } from 'drizzle-orm';
import db from '../../db';
@ -15,3 +15,14 @@ export async function createPasswordResetToken(userId: string): Promise<string>
});
return tokenId;
}
/**
* Checks if the user is fully authenticated.
*
* @param user - The user object.
* @param session - The session object.
* @returns True if the user is fully authenticated, otherwise false.
*/
export function userFullyAuthenticated(user: User | null, session: Session | null) {
return user && session && (!session.isTwoFactorAuthEnabled || session.isTwoFactorAuthenticated);
}

View file

@ -20,6 +20,7 @@ export const lucia = new Lucia(adapter, {
return {
ipCountry: attributes.ip_country,
ipAddress: attributes.ip_address,
isTwoFactorAuthEnabled: attributes.twoFactorAuthEnabled,
isTwoFactorAuthenticated: attributes.isTwoFactorAuthenticated,
};
},
@ -54,6 +55,7 @@ declare module 'lucia' {
interface DatabaseSessionAttributes {
ip_country: string;
ip_address: string;
twoFactorAuthEnabled: boolean;
isTwoFactorAuthenticated: boolean;
}
interface DatabaseUserAttributes {

View file

@ -3,14 +3,15 @@ import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import { eq } from 'drizzle-orm';
import db from '../../../../db';
import { user_roles } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load = loadFlash(async (event) => {
const { locals } = event;
if (!locals?.user) {
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
const { user } = locals;
const userRoles = await db.query.user_roles.findMany({
where: eq(user_roles.user_id, user.id),
with: {

View file

@ -2,11 +2,13 @@ import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../../db';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
// TODO: Ensure admin user
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const users = await db.query.users.findMany({

View file

@ -8,10 +8,11 @@ import { search_schema } from '$lib/zodValidation.js';
import db from '../../../../db';
import { collection_items, collections, games } from '$db/schema';
import { notSignedInMessage } from '$lib/flashMessages';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export async function load(event) {
const user = event.locals.user;
if (!user) {
const { user, session } = event.locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}

View file

@ -8,16 +8,16 @@ import db from '../../../../../db';
import { notSignedInMessage } from '$lib/flashMessages.js';
import { collections, games, collection_items } from '$db/schema';
import { search_schema } from '$lib/zodValidation';
import type { UICollection } from '$lib/types';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export async function load(event) {
const { locals, params, url } = event;
const { user } = locals;
const { user, session } = locals;
const { id } = params;
if (!user) {
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
const searchParams = Object.fromEntries(url?.searchParams);
console.log('searchParams', searchParams);
const q = searchParams?.q;
@ -102,12 +102,14 @@ export async function load(event) {
export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const form = await superValidate(event, zod(modifyListGameSchema));
if (!event.locals.user) {
return fail(401, {});
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
const user = event.locals.user;
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id),
@ -148,15 +150,19 @@ export const actions: Actions = {
}
},
// Create new wishlist
create: async ({ locals }) => {
if (!locals.user) {
create: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
return error(405, 'Method not allowed');
},
// Delete a wishlist
delete: async ({ locals }) => {
if (!locals.user) {
delete: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
return error(405, 'Method not allowed');
@ -164,11 +170,11 @@ export const actions: Actions = {
// Remove game from a wishlist
remove: async (event) => {
const { locals } = event;
const form = await superValidate(event, zod(modifyListGameSchema));
if (!locals.user) {
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id),

View file

@ -1,13 +1,14 @@
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "../$types";
import { notSignedInMessage } from "$lib/flashMessages";
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from '../$types';
import { notSignedInMessage } from '$lib/flashMessages';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export async function load(event) {
const { locals } = event;
const user = locals.user;
if (!user) {
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
return {}
return {};
}

View file

@ -1,16 +1,19 @@
import { redirect } from "@sveltejs/kit";
import { superValidate } from "sveltekit-superforms/server";
import { redirect } from '@sveltejs/kit';
import { superValidate } from 'sveltekit-superforms/server';
import { zod } from 'sveltekit-superforms/adapters';
import type { PageServerLoad } from "../$types";
import { BggForm } from "$lib/zodValidation";
import type { PageServerLoad } from '../$types';
import { BggForm } from '$lib/zodValidation';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
import { notSignedInMessage } from '$lib/flashMessages';
export const load: PageServerLoad = async ({ locals, fetch }) => {
const user = locals.user;
if (!user) {
redirect(302, '/login');
export const load: PageServerLoad = async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
const form = await superValidate({}, zod(BggForm));
return { form };
}
};

View file

@ -2,9 +2,12 @@ import { fail } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import db from '../../../../db';
import { wishlists } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
import { notSignedInMessage } from '$lib/flashMessages';
export async function load({ locals }) {
if (!locals.user) {
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
throw fail(401);
}

View file

@ -5,11 +5,14 @@ import { superValidate } from 'sveltekit-superforms/server';
import db from '../../../../../db';
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import { games, wishlist_items, wishlists } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
import { notSignedInMessage } from '$lib/flashMessages';
export async function load({ params, locals }) {
const user = locals.user;
if (!user) {
redirect(302, '/login');
export async function load(event) {
const { params, locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
try {
@ -40,6 +43,10 @@ export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { params, locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
if (!locals.user) {
@ -92,21 +99,27 @@ export const actions: Actions = {
};
},
// Create new wishlist
create: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
create: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
},
// Delete a wishlist
delete: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
delete: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
},
// Remove game from a wishlist
remove: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
remove: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
},
};

View file

@ -9,9 +9,12 @@ import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../db';
import type { PageServerLoad } from './$types';
import { users } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
if (!event.locals.user) {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}

View file

@ -11,12 +11,13 @@ import { lucia } from '$lib/server/auth.js';
import { users } from '$db/schema';
import { notSignedInMessage } from '$lib/flashMessages';
import type { Cookie } from 'lucia';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
const form = await superValidate(event, zod(changeUserPasswordSchema));
const user = event.locals.user;
if (!user) {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
@ -32,6 +33,12 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = {
default: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(changeUserPasswordSchema));
if (!form.valid) {

View file

@ -13,13 +13,15 @@ import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/acco
import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../../../db';
import { recovery_codes, users } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
const removeTwoFactorForm = await superValidate(event, zod(removeTwoFactorSchema));
const user = event.locals.user;
if (!user) {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
@ -67,6 +69,12 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = {
enableTwoFactor: async (event) => {
const { params, locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));
if (!addTwoFactorForm.valid) {

View file

@ -6,11 +6,12 @@ import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages';
import type { PageServerLoad } from '../../../$types';
import { recovery_codes, users } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
const user = event.locals.user;
if (!user) {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}

View file

@ -7,10 +7,12 @@ import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '../../../../db';
import { notSignedInMessage } from '$lib/flashMessages.js';
import { games, wishlist_items, wishlists } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export async function load(event) {
const user = event.locals.user;
if (!user) {
const { params, locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
@ -38,13 +40,13 @@ export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
try {
if (!locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id),
});
@ -86,28 +88,31 @@ export const actions: Actions = {
// Create new wishlist
create: async (event) => {
const { locals } = event;
if (!locals.user) {
redirect(302, '/login', notSignedInMessage, event);
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
return error(405, 'Method not allowed');
},
// Delete a wishlist
delete: async ({ locals }) => {
if (!locals.user) {
redirect(302, '/login');
delete: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
return error(405, 'Method not allowed');
},
// Remove game from a wishlist
remove: async (event) => {
const { locals } = event;
const { params, locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
try {
if (!locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id),
});

View file

@ -7,11 +7,13 @@ import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '../../../../../db';
import { notSignedInMessage } from '$lib/flashMessages.js';
import { games, wishlist_items, wishlists } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export async function load(event) {
const { params, locals } = event;
const { user, session } = locals;
const { id } = params;
if (!locals.user) {
if (userFullyAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
}
@ -41,13 +43,13 @@ export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
try {
if (!locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id),
});
@ -89,28 +91,31 @@ export const actions: Actions = {
// Create new wishlist
create: async (event) => {
const { locals } = event;
if (!locals.user) {
redirect(302, '/login', notSignedInMessage, event);
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
return error(405, 'Method not allowed');
},
// Delete a wishlist
delete: async ({ locals }) => {
if (!locals.user) {
redirect(302, '/login');
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
return error(405, 'Method not allowed');
},
// Remove game from a wishlist
remove: async (event) => {
const { locals } = event;
const { user, session } = locals;
if (userFullyAuthenticated(user, session)) {
return fail(401);
}
const form = await superValidate(event, zod(modifyListGameSchema));
try {
if (!locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.data.id),
});

View file

@ -10,7 +10,7 @@ import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '../../../db';
import { lucia } from '$lib/server/auth';
import { signInSchema } from '$lib/validations/auth';
import { users, recovery_codes } from '$db/schema';
import { users, recoveryCodes, type Users } from '$db/schema';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async (event) => {
@ -49,10 +49,14 @@ export const actions: Actions = {
let session;
let sessionCookie;
const user = await db.query.users.findFirst({
const user: Users | undefined = await db.query.users.findFirst({
where: eq(users.username, form.data.username),
});
if (!user) {
form.data.password = '';
return setError(form, 'username', 'Your username or password is incorrect.');
}
try {
const password = form.data.password;
@ -76,6 +80,10 @@ export const actions: Actions = {
session = await lucia.createSession(user.id, {
ip_country: locals.country,
ip_address: locals.ip,
twoFactorAuthEnabled:
user?.two_factor_enabled &&
user?.two_factor_secret !== null &&
user?.two_factor_secret !== '',
isTwoFactorAuthenticated: false,
});
console.log('logging in session', session);
@ -97,7 +105,11 @@ export const actions: Actions = {
form.data.username = '';
form.data.password = '';
if (user?.two_factor_enabled && user?.two_factor_secret) {
if (
user?.two_factor_enabled &&
user?.two_factor_secret !== null &&
user?.two_factor_secret !== ''
) {
console.log('redirecting to TOTP page');
const message = { type: 'success', message: 'Please enter your TOTP code.' } as const;
redirect(302, '/totp', message, event);

View file

@ -12,21 +12,30 @@ import { lucia } from '$lib/server/auth';
import { totpSchema } from '$lib/validations/auth';
import { users, recovery_codes } from '$db/schema';
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
export const load: PageServerLoad = async (event) => {
if (event.locals.user) {
const user = event.locals.user;
const { user, session } = event.locals;
if (!user || !session) {
redirect(302, '/login', notSignedInMessage, event);
}
if (user && session) {
const dbUser = await db.query.users.findFirst({
where: eq(users.username, user.username),
});
const session = event.locals.session;
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
console.log('session', session);
console.log('isTwoFactorAuthenticated', isTwoFactorAuthenticated);
if (isTwoFactorAuthenticated && dbUser?.two_factor_enabled && dbUser?.two_factor_secret) {
if (
isTwoFactorAuthenticated &&
dbUser?.two_factor_enabled &&
dbUser?.two_factor_secret !== ''
) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
}
@ -68,7 +77,11 @@ export const actions: Actions = {
const isTwoFactorAuthenticated = session?.isTwoFactorAuthenticated;
if (isTwoFactorAuthenticated && dbUser?.two_factor_enabled && dbUser?.two_factor_secret) {
if (
isTwoFactorAuthenticated &&
dbUser?.two_factor_enabled &&
dbUser?.two_factor_secret !== ''
) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
}
@ -86,12 +99,12 @@ export const actions: Actions = {
try {
const totpToken = form?.data?.totpToken;
if (dbUser?.two_factor_enabled && dbUser?.two_factor_secret && !totpToken) {
if (dbUser?.two_factor_enabled && dbUser?.two_factor_secret !== '' && !totpToken) {
return fail(400, {
form,
});
} else if (dbUser?.two_factor_enabled && dbUser?.two_factor_secret && totpToken) {
console.log('totpToken',totpToken);
} else if (dbUser?.two_factor_enabled && dbUser?.two_factor_secret !== '' && totpToken) {
console.log('totpToken', totpToken);
const validOTP = await new TOTPController().verify(
totpToken,
decodeHex(dbUser.two_factor_secret),
@ -113,6 +126,7 @@ export const actions: Actions = {
const newSession = await lucia.createSession(dbUser.id, {
ip_country: locals.country,
ip_address: locals.ip,
twoFactorAuthEnabled: true,
isTwoFactorAuthenticated: true,
});
console.log('logging in session', newSession);