reimplemented auth
This commit is contained in:
parent
c925697c46
commit
2a6875016e
35 changed files with 906 additions and 233 deletions
13
.env.example
13
.env.example
|
@ -0,0 +1,13 @@
|
||||||
|
DATABASE_URL="./data/db.sqlite"
|
||||||
|
|
||||||
|
# Admin user is seeded
|
||||||
|
# Generate id With `bun -e "console.log(Bun.randomUUIDv7('base64url'))";`
|
||||||
|
ADMIN_USER_ID="some unique id"
|
||||||
|
ADMIN_USER_EMAIL="admin"
|
||||||
|
ADMIN_USER_PASSWORD="password"
|
||||||
|
|
||||||
|
#SESSION_DURATION_IN_DAYS=31
|
||||||
|
|
||||||
|
BEARER_TOKEN="Token"
|
||||||
|
API_SERVICE_URL="http://localhost:3000"
|
||||||
|
#API_SERVICE_URL="https://pt-api.skaarup.dev"
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -34,3 +34,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
build
|
build
|
||||||
|
|
||||||
|
data/*
|
||||||
|
!data/.gitkeep
|
||||||
|
|
148
bun.lock
148
bun.lock
|
@ -6,24 +6,78 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bun-plugin-svelte": "^0.0.6",
|
"bun-plugin-svelte": "^0.0.6",
|
||||||
"bun-plugin-tailwind": "^0.0.15",
|
"bun-plugin-tailwind": "^0.0.15",
|
||||||
"svelte": "^5.25.10",
|
"drizzle-orm": "^0.41.0",
|
||||||
|
"svelte": "^5.26.1",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
"drizzle-kit": "^0.30.6",
|
||||||
"oxlint": "latest",
|
"oxlint": "latest",
|
||||||
"prettier": "^4.0.0-alpha.12",
|
"prettier": "^4.0.0-alpha.12",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
|
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||||
|
|
||||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
@ -50,6 +104,8 @@
|
||||||
|
|
||||||
"@oxlint/win32-x64": ["@oxlint/win32-x64@0.16.5", "", { "os": "win32", "cpu": "x64" }, "sha512-cHJJRyVA2XlsGjIVKqw2DC5dkzWGOH6gxQwf6StTHn8F4i5P8gksV70VoNW5mwEXefF2USDX7H43YVIDG5E/Yw=="],
|
"@oxlint/win32-x64": ["@oxlint/win32-x64@0.16.5", "", { "os": "win32", "cpu": "x64" }, "sha512-cHJJRyVA2XlsGjIVKqw2DC5dkzWGOH6gxQwf6StTHn8F4i5P8gksV70VoNW5mwEXefF2USDX7H43YVIDG5E/Yw=="],
|
||||||
|
|
||||||
|
"@petamoriken/float16": ["@petamoriken/float16@3.9.2", "", {}, "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog=="],
|
||||||
|
|
||||||
"@prettier/cli": ["@prettier/cli@0.7.1", "", { "dependencies": { "atomically": "^2.0.3", "fast-ignore": "^1.1.3", "find-up-json": "^2.0.4", "function-once": "^3.0.0", "import-meta-resolve": "^4.1.0", "is-binary-path": "^2.1.0", "js-yaml": "^4.1.0", "json-sorted-stringify": "^1.0.0", "json5": "^2.2.3", "kasi": "^1.1.0", "lomemo": "^1.0.0", "pioppo": "^1.2.0", "promise-resolve-timeout": "^2.0.0", "smol-toml": "^1.3.1", "specialist": "^1.4.5", "tiny-editorconfig": "^1.0.0", "tiny-jsonc": "^1.0.1", "tiny-readdir": "^2.7.4", "tiny-readdir-glob": "^1.23.1", "tiny-spinner": "^2.0.4", "worktank": "^2.7.3", "zeptomatch": "^2.0.0", "zeptomatch-escape": "^1.0.0", "zeptomatch-is-static": "^1.0.0" }, "peerDependencies": { "prettier": "^3.1.0 || ^4.0.0-alpha" }, "bin": { "prettier-next": "dist/bin.js" } }, "sha512-YoXPLOLmEEHP4MKgzcEilzaUtlo80Qm5Pb+59QbgDeOsIExGBkRqZmC2+iwzSwEhlhTEpGqDwqb3+nP/dPay9A=="],
|
"@prettier/cli": ["@prettier/cli@0.7.1", "", { "dependencies": { "atomically": "^2.0.3", "fast-ignore": "^1.1.3", "find-up-json": "^2.0.4", "function-once": "^3.0.0", "import-meta-resolve": "^4.1.0", "is-binary-path": "^2.1.0", "js-yaml": "^4.1.0", "json-sorted-stringify": "^1.0.0", "json5": "^2.2.3", "kasi": "^1.1.0", "lomemo": "^1.0.0", "pioppo": "^1.2.0", "promise-resolve-timeout": "^2.0.0", "smol-toml": "^1.3.1", "specialist": "^1.4.5", "tiny-editorconfig": "^1.0.0", "tiny-jsonc": "^1.0.1", "tiny-readdir": "^2.7.4", "tiny-readdir-glob": "^1.23.1", "tiny-spinner": "^2.0.4", "worktank": "^2.7.3", "zeptomatch": "^2.0.0", "zeptomatch-escape": "^1.0.0", "zeptomatch-is-static": "^1.0.0" }, "peerDependencies": { "prettier": "^3.1.0 || ^4.0.0-alpha" }, "bin": { "prettier-next": "dist/bin.js" } }, "sha512-YoXPLOLmEEHP4MKgzcEilzaUtlo80Qm5Pb+59QbgDeOsIExGBkRqZmC2+iwzSwEhlhTEpGqDwqb3+nP/dPay9A=="],
|
||||||
|
|
||||||
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
|
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
|
||||||
|
@ -58,9 +114,9 @@
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="],
|
"@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="],
|
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||||
|
|
||||||
|
@ -78,6 +134,8 @@
|
||||||
|
|
||||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||||
|
|
||||||
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"bun-plugin-svelte": ["bun-plugin-svelte@0.0.6", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-HuEDvOieVwXvhpcHLcASeQIOVgje2GRO3Tu0ypJh3MkjGLkEBOQ1+6FWsMgb54FxfbPw9JP3q0WlBd8SjgrnGQ=="],
|
"bun-plugin-svelte": ["bun-plugin-svelte@0.0.6", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-HuEDvOieVwXvhpcHLcASeQIOVgje2GRO3Tu0ypJh3MkjGLkEBOQ1+6FWsMgb54FxfbPw9JP3q0WlBd8SjgrnGQ=="],
|
||||||
|
|
||||||
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
||||||
|
@ -86,8 +144,20 @@
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||||
|
|
||||||
"dettle": ["dettle@1.0.5", "", {}, "sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA=="],
|
"dettle": ["dettle@1.0.5", "", {}, "sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA=="],
|
||||||
|
|
||||||
|
"drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="],
|
||||||
|
|
||||||
|
"drizzle-orm": ["drizzle-orm@0.41.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q=="],
|
||||||
|
|
||||||
|
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
|
||||||
|
|
||||||
|
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||||
|
|
||||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||||
|
|
||||||
"esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="],
|
"esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="],
|
||||||
|
@ -104,8 +174,12 @@
|
||||||
|
|
||||||
"function-once": ["function-once@3.0.1", "", {}, "sha512-bE3E8REk4jANDot3l0sLFkXgywBwzFKsmbwdnVHLJUnt/3kV6dNG0oJJqoRBuS1Z9Lr4ZoQgwV0ZNLDgWDbv7Q=="],
|
"function-once": ["function-once@3.0.1", "", {}, "sha512-bE3E8REk4jANDot3l0sLFkXgywBwzFKsmbwdnVHLJUnt/3kV6dNG0oJJqoRBuS1Z9Lr4ZoQgwV0ZNLDgWDbv7Q=="],
|
||||||
|
|
||||||
|
"gel": ["gel@2.0.2", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-XTKpfNR9HZOw+k0Bl04nETZjuP5pypVAXsZADSdwr3EtyygTTe1RqvftU2FjGu7Tp9e576a9b/iIOxWrRBxMiQ=="],
|
||||||
|
|
||||||
"get-current-package": ["get-current-package@1.0.1", "", { "dependencies": { "find-up-json": "^2.0.5" } }, "sha512-c/Rw5ByDQ+zg+Lh/emBWv0bDpugEFdmXPR6/srIemVtIvol0XbT0JAr8Db0cX+Jj/xY9wj1wdjeq2qNB35Tayg=="],
|
"get-current-package": ["get-current-package@1.0.1", "", { "dependencies": { "find-up-json": "^2.0.5" } }, "sha512-c/Rw5ByDQ+zg+Lh/emBWv0bDpugEFdmXPR6/srIemVtIvol0XbT0JAr8Db0cX+Jj/xY9wj1wdjeq2qNB35Tayg=="],
|
||||||
|
|
||||||
|
"get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
|
||||||
|
|
||||||
"grammex": ["grammex@3.1.10", "", {}, "sha512-UCfMsV/sfqk4TN1+m5ehSOXuADyLUgSuwMI2vCVlbN/REoSmTl4eagswC9DzzVxtsKv7Yp2CmIJNn4fMk8PaQA=="],
|
"grammex": ["grammex@3.1.10", "", {}, "sha512-UCfMsV/sfqk4TN1+m5ehSOXuADyLUgSuwMI2vCVlbN/REoSmTl4eagswC9DzzVxtsKv7Yp2CmIJNn4fMk8PaQA=="],
|
||||||
|
|
||||||
"import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="],
|
"import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="],
|
||||||
|
@ -118,6 +192,8 @@
|
||||||
|
|
||||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||||
|
|
||||||
|
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||||
|
|
||||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||||
|
|
||||||
"json-sorted-stringify": ["json-sorted-stringify@1.0.1", "", {}, "sha512-pWv9hqWho37EpwpBgqDYVPKPCgT/ytuvqtlBvb6M44BrnvooTk/5D/aSeohsGDLp+g8waP5dUUGODR+Ley+Idg=="],
|
"json-sorted-stringify": ["json-sorted-stringify@1.0.1", "", {}, "sha512-pWv9hqWho37EpwpBgqDYVPKPCgT/ytuvqtlBvb6M44BrnvooTk/5D/aSeohsGDLp+g8waP5dUUGODR+Ley+Idg=="],
|
||||||
|
@ -132,6 +208,8 @@
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"oxlint": ["oxlint@0.16.5", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.16.5", "@oxlint/darwin-x64": "0.16.5", "@oxlint/linux-arm64-gnu": "0.16.5", "@oxlint/linux-arm64-musl": "0.16.5", "@oxlint/linux-x64-gnu": "0.16.5", "@oxlint/linux-x64-musl": "0.16.5", "@oxlint/win32-arm64": "0.16.5", "@oxlint/win32-x64": "0.16.5" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-z5MX2v4KUqzZQTnRkHHBPE4qEo08f5mJ4dQxi+r5t3xpGspVH/pBzWQ0UUy2xO3JHO8H6wpoeoh8bGs6jEygvQ=="],
|
"oxlint": ["oxlint@0.16.5", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.16.5", "@oxlint/darwin-x64": "0.16.5", "@oxlint/linux-arm64-gnu": "0.16.5", "@oxlint/linux-arm64-musl": "0.16.5", "@oxlint/linux-x64-gnu": "0.16.5", "@oxlint/linux-x64-musl": "0.16.5", "@oxlint/win32-arm64": "0.16.5", "@oxlint/win32-x64": "0.16.5" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-z5MX2v4KUqzZQTnRkHHBPE4qEo08f5mJ4dQxi+r5t3xpGspVH/pBzWQ0UUy2xO3JHO8H6wpoeoh8bGs6jEygvQ=="],
|
||||||
|
|
||||||
"pioppo": ["pioppo@1.2.1", "", { "dependencies": { "dettle": "^1.0.5", "when-exit": "^2.1.4" } }, "sha512-1oErGVWD6wFDPmrJWEY1Cj2p829UGT6Fw9OItYFxLkWtBjCvQSMC8wA5IcAR5ms/6gqiY8pnJvIV/+/Imyobew=="],
|
"pioppo": ["pioppo@1.2.1", "", { "dependencies": { "dettle": "^1.0.5", "when-exit": "^2.1.4" } }, "sha512-1oErGVWD6wFDPmrJWEY1Cj2p829UGT6Fw9OItYFxLkWtBjCvQSMC8wA5IcAR5ms/6gqiY8pnJvIV/+/Imyobew=="],
|
||||||
|
@ -148,8 +226,18 @@
|
||||||
|
|
||||||
"promise-resolve-timeout": ["promise-resolve-timeout@2.0.1", "", {}, "sha512-90Qzzu5SmR+ksmTPsc79121NZGtEiPvKACQLCl6yofknRx5xJI9kNj3oDVSX6dVTneF8Ju6+xpVFdDSzb7cNcg=="],
|
"promise-resolve-timeout": ["promise-resolve-timeout@2.0.1", "", {}, "sha512-90Qzzu5SmR+ksmTPsc79121NZGtEiPvKACQLCl6yofknRx5xJI9kNj3oDVSX6dVTneF8Ju6+xpVFdDSzb7cNcg=="],
|
||||||
|
|
||||||
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
|
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||||
|
|
||||||
|
"shell-quote": ["shell-quote@1.8.2", "", {}, "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA=="],
|
||||||
|
|
||||||
"smol-toml": ["smol-toml@1.3.1", "", {}, "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ=="],
|
"smol-toml": ["smol-toml@1.3.1", "", {}, "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ=="],
|
||||||
|
|
||||||
|
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
|
|
||||||
|
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||||
|
|
||||||
"specialist": ["specialist@1.4.5", "", { "dependencies": { "tiny-bin": "^1.10.3", "tiny-colors": "^2.2.2", "tiny-parse-argv": "^2.8.1", "tiny-updater": "^3.5.3" } }, "sha512-4mPQEREzBUW2hzlXX/dWFbQdUWzpkqvMFVpUAdRlo1lUlhKMObDHiAo09oZ94x4cS3uWMJebPOTn+GaQYLfv3Q=="],
|
"specialist": ["specialist@1.4.5", "", { "dependencies": { "tiny-bin": "^1.10.3", "tiny-colors": "^2.2.2", "tiny-parse-argv": "^2.8.1", "tiny-updater": "^3.5.3" } }, "sha512-4mPQEREzBUW2hzlXX/dWFbQdUWzpkqvMFVpUAdRlo1lUlhKMObDHiAo09oZ94x4cS3uWMJebPOTn+GaQYLfv3Q=="],
|
||||||
|
|
||||||
"stdin-blocker": ["stdin-blocker@2.0.1", "", {}, "sha512-NEcAEpag+gE/Iivx1prq1AFPwnmgmcyHNvGZLUqGBoOE/7DZtmhtP9iYqJt8ymueFL+kknhfEebAMWbrWp3FJw=="],
|
"stdin-blocker": ["stdin-blocker@2.0.1", "", {}, "sha512-NEcAEpag+gE/Iivx1prq1AFPwnmgmcyHNvGZLUqGBoOE/7DZtmhtP9iYqJt8ymueFL+kknhfEebAMWbrWp3FJw=="],
|
||||||
|
@ -186,14 +274,16 @@
|
||||||
|
|
||||||
"tiny-updater": ["tiny-updater@3.5.3", "", { "dependencies": { "ionstore": "^1.0.1", "tiny-colors": "^2.2.2", "when-exit": "^2.1.4" } }, "sha512-wEUssfOOkVLg2raSaRbyZDHpVCDj6fnp7UjynpNE4XGuF+Gkj8GRRMoHdfk73VzLQs/AHKsbY8fCxXNz8Hx4Qg=="],
|
"tiny-updater": ["tiny-updater@3.5.3", "", { "dependencies": { "ionstore": "^1.0.1", "tiny-colors": "^2.2.2", "when-exit": "^2.1.4" } }, "sha512-wEUssfOOkVLg2raSaRbyZDHpVCDj6fnp7UjynpNE4XGuF+Gkj8GRRMoHdfk73VzLQs/AHKsbY8fCxXNz8Hx4Qg=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
"webworker-shim": ["webworker-shim@1.1.1", "", {}, "sha512-XCWuBjJH3Xn/7SbyUF1WrrCbe6ZEsgaD7kxlFhxIwdkljGYX3BqP/dhG6ge0NBT+V7ZPjR4/BXq5BvbdaxrpKg=="],
|
"webworker-shim": ["webworker-shim@1.1.1", "", {}, "sha512-XCWuBjJH3Xn/7SbyUF1WrrCbe6ZEsgaD7kxlFhxIwdkljGYX3BqP/dhG6ge0NBT+V7ZPjR4/BXq5BvbdaxrpKg=="],
|
||||||
|
|
||||||
"when-exit": ["when-exit@2.1.4", "", {}, "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg=="],
|
"when-exit": ["when-exit@2.1.4", "", {}, "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg=="],
|
||||||
|
|
||||||
|
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||||
|
|
||||||
"worktank": ["worktank@2.7.3", "", { "dependencies": { "promise-make-naked": "^2.0.0", "webworker-shim": "^1.1.0" } }, "sha512-M0fesnpttBPdvNYBdzRvLDsacN0na9RYWFxwmM/x1+/6mufjduv9/9vBObK8EXDqxRMX/SOYJabpo0UCYYBUdQ=="],
|
"worktank": ["worktank@2.7.3", "", { "dependencies": { "promise-make-naked": "^2.0.0", "webworker-shim": "^1.1.0" } }, "sha512-M0fesnpttBPdvNYBdzRvLDsacN0na9RYWFxwmM/x1+/6mufjduv9/9vBObK8EXDqxRMX/SOYJabpo0UCYYBUdQ=="],
|
||||||
|
|
||||||
"zeptomatch": ["zeptomatch@2.0.1", "", { "dependencies": { "grammex": "^3.1.10" } }, "sha512-nbnIYF2n3o3EqV36HkIhEMLIDFbG3M6RUjhkdKIn6qqIJkdkL7bgVSfTTCEXBJpk1T45tLfEYfStndJc2lUEnA=="],
|
"zeptomatch": ["zeptomatch@2.0.1", "", { "dependencies": { "grammex": "^3.1.10" } }, "sha512-nbnIYF2n3o3EqV36HkIhEMLIDFbG3M6RUjhkdKIn6qqIJkdkL7bgVSfTTCEXBJpk1T45tLfEYfStndJc2lUEnA=="],
|
||||||
|
@ -208,10 +298,56 @@
|
||||||
|
|
||||||
"zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
|
"zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||||
|
|
||||||
"promise-make-counter/promise-make-naked": ["promise-make-naked@3.0.2", "", {}, "sha512-B+b+kQ1YrYS7zO7P7bQcoqqMUizP06BOyNSBEnB5VJKDSWo8fsVuDkfSmwdjF0JsRtaNh83so5MMFJ95soH5jg=="],
|
"promise-make-counter/promise-make-naked": ["promise-make-naked@3.0.2", "", {}, "sha512-B+b+kQ1YrYS7zO7P7bQcoqqMUizP06BOyNSBEnB5VJKDSWo8fsVuDkfSmwdjF0JsRtaNh83so5MMFJ95soH5jg=="],
|
||||||
|
|
||||||
"tiny-editorconfig/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="],
|
"tiny-editorconfig/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="],
|
||||||
|
|
||||||
"tiny-readdir-glob/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="],
|
"tiny-readdir-glob/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
14
drizzle.config.ts
Normal file
14
drizzle.config.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { env } from "bun";
|
||||||
|
import type { Config } from "drizzle-kit";
|
||||||
|
|
||||||
|
const url = process.env.DATABASE_URL;
|
||||||
|
if (!url) {
|
||||||
|
throw new Error('DATABASE_URL is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
schema: 'src/server/db/schema.ts',
|
||||||
|
out: './drizzle',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
dbCredentials: { url }
|
||||||
|
} satisfies Config;
|
20
drizzle/0000_gigantic_namorita.sql
Normal file
20
drizzle/0000_gigantic_namorita.sql
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
CREATE TABLE `user_sessions` (
|
||||||
|
`token` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`max_age` integer NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_sessions_token_unique` ON `user_sessions` (`token`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`hash` text NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
|
149
drizzle/meta/0000_snapshot.json
Normal file
149
drizzle/meta/0000_snapshot.json
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "d689db90-3590-437d-a697-5a073797bd45",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"user_sessions": {
|
||||||
|
"name": "user_sessions",
|
||||||
|
"columns": {
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"max_age": {
|
||||||
|
"name": "max_age",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch('subsec') * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch('subsec') * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_sessions_token_unique": {
|
||||||
|
"name": "user_sessions_token_unique",
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_sessions_user_id_users_id_fk": {
|
||||||
|
"name": "user_sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "user_sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"hash": {
|
||||||
|
"name": "hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch('subsec') * 1000)"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch('subsec') * 1000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_email_unique": {
|
||||||
|
"name": "users_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1744418681004,
|
||||||
|
"tag": "0000_gigantic_namorita",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
20
package.json
20
package.json
|
@ -1,18 +1,23 @@
|
||||||
{
|
{
|
||||||
"name": "progress-tracker-v3",
|
"name": "progress-tracker-v3",
|
||||||
"module": "src/server.ts",
|
"module": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun src/server.ts",
|
"start": "bun src/index.ts",
|
||||||
"dev": "bun --watch run src/server.ts",
|
"dev": "bun --watch run src/index.ts",
|
||||||
"hot": "bun --hot run src/server.ts",
|
"hot": "bun --hot run src/index.ts",
|
||||||
"build": "bun build src/server.ts --compile --minify --sourcemap --target=bun-linux-x64-modern --outfile server",
|
"build": "bun build src/index.ts --compile --minify --sourcemap --target=bun-linux-x64-modern --outfile dist/app",
|
||||||
"lint": "oxlint .",
|
"lint": "oxlint .",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"migrate": "bun --bun run ./src/server/db/migrate.ts",
|
||||||
|
"generate": "bunx --bun drizzle-kit generate --config=./drizzle.config.ts",
|
||||||
|
"studio": "bunx --bun drizzle-kit studio --config=./drizzle.config.ts",
|
||||||
|
"up": "drizzle-kit up --config=./drizzle.config.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
"drizzle-kit": "^0.30.6",
|
||||||
"oxlint": "latest",
|
"oxlint": "latest",
|
||||||
"prettier": "^4.0.0-alpha.12",
|
"prettier": "^4.0.0-alpha.12",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
|
@ -21,10 +26,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bun-plugin-svelte": "^0.0.6",
|
"bun-plugin-svelte": "^0.0.6",
|
||||||
"bun-plugin-tailwind": "^0.0.15",
|
"bun-plugin-tailwind": "^0.0.15",
|
||||||
|
"drizzle-orm": "^0.41.0",
|
||||||
"svelte": "^5.26.1",
|
"svelte": "^5.26.1",
|
||||||
"tailwindcss": "^4.1.3"
|
"tailwindcss": "^4.1.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
class="absolute top-0 left-0 z-50 rounded-b-sm bg-pink-600 px-1 pt-0.5 text-xs text-gray-100 not-focus:sr-only focus:outline-none"
|
||||||
|
>
|
||||||
|
Skip to main content
|
||||||
|
</a>
|
||||||
|
|
||||||
<header
|
<header
|
||||||
class="content sticky top-0 z-20 grid h-(--header-height) border-b border-gray-950 bg-gray-950 py-2 font-mono sm:px-2"
|
class="content sticky top-0 z-20 grid h-(--header-height) border-b border-gray-950 bg-gray-950 py-2 font-mono sm:px-2"
|
||||||
>
|
>
|
|
@ -1,12 +1,11 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { userstate } from '../shared.svelte';
|
import { userstate } from '../shared.svelte';
|
||||||
import InputEmail from './input-email.svelte';
|
import InputEmail from './input-email.svelte';
|
||||||
import InputPassword from './input-password.svelte';
|
import InputPassword from './input-password.svelte';
|
||||||
|
|
||||||
async function handleSubmit(event) {
|
async function handleSubmit(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const form = event.target;
|
const form = event.currentTarget;
|
||||||
|
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
const data = Object.fromEntries(formData.entries());
|
const data = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
|
@ -19,10 +18,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// window.location.reload();
|
await userstate.checkIsLoggedIn();
|
||||||
|
|
||||||
userstate.checkIsLoggedIn();
|
|
||||||
} else {
|
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
File diff suppressed because one or more lines are too long
|
@ -5,20 +5,18 @@
|
||||||
import ProgressTable from './components/progress-table.svelte';
|
import ProgressTable from './components/progress-table.svelte';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import { userstate } from './shared.svelte';
|
import { userstate } from './shared.svelte';
|
||||||
|
let promise = userstate.checkIsLoggedIn();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a
|
|
||||||
href="#main-content"
|
|
||||||
class="absolute top-0 left-0 z-50 rounded-b-sm bg-pink-600 px-1 pt-0.5 text-xs text-gray-100 not-focus:sr-only focus:outline-none"
|
|
||||||
>
|
|
||||||
Skip to main content
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<Header></Header>
|
<Header></Header>
|
||||||
<Main>
|
<Main>
|
||||||
|
{#await promise}
|
||||||
|
<div>loading</div>
|
||||||
|
{:then _}
|
||||||
{#if !userstate.isLoggedIn}
|
{#if !userstate.isLoggedIn}
|
||||||
<Login></Login>
|
<Login></Login>
|
||||||
{:else}
|
{:else}
|
||||||
<ProgressTable></ProgressTable>
|
<ProgressTable></ProgressTable>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/await}
|
||||||
</Main>
|
</Main>
|
30
src/client/index.ts
Normal file
30
src/client/index.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { mount, unmount } from 'svelte';
|
||||||
|
import index from './index.svelte';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var didMount: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let app: Record<string, any> | undefined;
|
||||||
|
|
||||||
|
// mount the application entrypoint to the DOM on first load. On subsequent hot
|
||||||
|
// updates, the app will be unmounted and re-mounted via the accept handler.
|
||||||
|
|
||||||
|
const target = document.querySelector<HTMLDivElement>('body>div')!;
|
||||||
|
if (!globalThis.didMount) {
|
||||||
|
app = mount(index, { target });
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.didMount = true;
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(async () => {
|
||||||
|
// avoid unmounting twice when another update gets accepted while outros are playing
|
||||||
|
if (!app) return;
|
||||||
|
const prevApp = app;
|
||||||
|
app = undefined;
|
||||||
|
await unmount(prevApp, { outro: true });
|
||||||
|
app = mount(index, { target });
|
||||||
|
});
|
||||||
|
}
|
16
src/client/shared.svelte.ts
Normal file
16
src/client/shared.svelte.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
class UserState {
|
||||||
|
isLoggedIn = $state(false);
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
async checkIsLoggedIn() {
|
||||||
|
const res = await fetch('/auth/verify', {
|
||||||
|
mode: 'no-cors',
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isLoggedIn = res.ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userstate = new UserState();
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
@ -1,5 +1,5 @@
|
||||||
import { userstate } from './shared.svelte';
|
import { userstate } from './shared.svelte';
|
||||||
import type { SelectEntry } from './server/pt-api';
|
import type { SelectEntry } from '../server/pt-api';
|
||||||
|
|
||||||
export async function fetchEntries(retry: boolean = false): Promise<SelectEntry[]> {
|
export async function fetchEntries(retry: boolean = false): Promise<SelectEntry[]> {
|
||||||
try {
|
try {
|
117
src/index.ts
117
src/index.ts
|
@ -1,30 +1,107 @@
|
||||||
import { mount, unmount } from 'svelte';
|
import { env } from 'bun';
|
||||||
import index from './index.svelte';
|
import homepage from './client/index.html';
|
||||||
import './index.css';
|
import robotsTxt from './client/static/robots.txt';
|
||||||
|
import sitemapTxt from './client/static/sitemap.txt';
|
||||||
|
// @ts-ignore ts2307
|
||||||
|
import icon from './client/static/favicon.png' with { type: 'file' };
|
||||||
|
const favicon = await Bun.file(icon).bytes();
|
||||||
|
const development = env.NODE_ENV !== 'production';
|
||||||
|
import { randomUUIDv7 } from 'bun';
|
||||||
|
import ptApi from './server/pt-api';
|
||||||
|
import auth from './server/auth';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var didMount: boolean | undefined;
|
var loginTokens: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let app: Record<string, any> | undefined;
|
let entriesCache = '';
|
||||||
|
let entriesCacheTime = 0;
|
||||||
|
const entriesCacheTimeLimit = 1000 * 15;
|
||||||
|
|
||||||
// mount the application entrypoint to the DOM on first load. On subsequent hot
|
async function getEntriesCached() {
|
||||||
// updates, the app will be unmounted and re-mounted via the accept handler.
|
if (entriesCacheTime + entriesCacheTimeLimit < Date.now()) {
|
||||||
|
const data = await ptApi.query();
|
||||||
const target = document.querySelector<HTMLDivElement>('body>div')!;
|
entriesCache = JSON.stringify(data);
|
||||||
if (!globalThis.didMount) {
|
entriesCacheTime = Date.now();
|
||||||
app = mount(index, { target });
|
}
|
||||||
|
return entriesCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.didMount = true;
|
Bun.serve({
|
||||||
|
routes: {
|
||||||
|
'/': homepage,
|
||||||
|
'/robots.txt': new Response(robotsTxt, {
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
|
}),
|
||||||
|
'/sitemap.txt': new Response(sitemapTxt, {
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
|
}),
|
||||||
|
'/favicon.ico': new Response(favicon, {
|
||||||
|
headers: { 'Content-Type': 'image/png' },
|
||||||
|
}),
|
||||||
|
'/health': new Response('OK'),
|
||||||
|
'/api/entries': {
|
||||||
|
async GET(req) {
|
||||||
|
const session = await auth.verify(req.headers);
|
||||||
|
if (!session) {
|
||||||
|
return auth.verifyFailResponse();
|
||||||
|
}
|
||||||
|
|
||||||
if (import.meta.hot) {
|
return new Response(await getEntriesCached(), {
|
||||||
import.meta.hot.accept(async () => {
|
headers: {
|
||||||
// avoid unmounting twice when another update gets accepted while outros are playing
|
'Content-Type': 'application/json'
|
||||||
if (!app) return;
|
}
|
||||||
const prevApp = app;
|
});
|
||||||
app = undefined;
|
},
|
||||||
await unmount(prevApp, { outro: true });
|
},
|
||||||
app = mount(index, { target });
|
|
||||||
|
'/auth/logout': {
|
||||||
|
async POST() {
|
||||||
|
return auth.logoutResponse();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/auth/verify': {
|
||||||
|
async GET(req) {
|
||||||
|
const session = await auth.verify(req.headers);
|
||||||
|
if (!session) {
|
||||||
|
return auth.verifyFailResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.verifyResponse(session.token);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/auth/login': {
|
||||||
|
async POST(req) {
|
||||||
|
const json = await req.json();
|
||||||
|
const data = json as { email?: string, password?: string };
|
||||||
|
const email = data.email?.toLocaleLowerCase();
|
||||||
|
const password = data.password;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof email !== 'string' ||
|
||||||
|
typeof password !== 'string' ||
|
||||||
|
email.length < 3 ||
|
||||||
|
password.length === 0
|
||||||
|
) {
|
||||||
|
return new Response('Missing email or password', { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await auth.login(email, password);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, (Math.random() * 200) + 800));
|
||||||
|
return new Response('Incorrect email or password', {
|
||||||
|
status: 400,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return auth.loginResponse(token)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
development,
|
||||||
|
reusePort: true,
|
||||||
|
// async fetch(req, server) {
|
||||||
|
// return new Response("Not found", { status: 404 });
|
||||||
|
// },
|
||||||
|
});
|
||||||
|
|
148
src/server.ts
148
src/server.ts
|
@ -1,148 +0,0 @@
|
||||||
import { env } from 'bun';
|
|
||||||
import homepage from './index.html';
|
|
||||||
import robotsTxt from './static/robots.txt';
|
|
||||||
import sitemapTxt from './static/sitemap.txt';
|
|
||||||
// @ts-ignore ts2307
|
|
||||||
import icon from './static/favicon.png' with { type: 'file' };
|
|
||||||
const favicon = await Bun.file(icon).bytes();
|
|
||||||
const development = env.NODE_ENV !== 'production';
|
|
||||||
import { randomUUIDv7 } from 'bun';
|
|
||||||
import ptApi from './server/pt-api';
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
var loginTokens: Set<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!globalThis.loginTokens) {
|
|
||||||
globalThis.loginTokens = new Set<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
const authCookie = 'pt-auth';
|
|
||||||
|
|
||||||
let entriesCache = '';
|
|
||||||
let entriesCacheTime = 0;
|
|
||||||
const entriesCacheTimeLimit = 1000 * 15;
|
|
||||||
|
|
||||||
async function getEntriesCached() {
|
|
||||||
if (entriesCacheTime + entriesCacheTimeLimit < Date.now()) {
|
|
||||||
const data = await ptApi.query();
|
|
||||||
entriesCache = JSON.stringify(data);
|
|
||||||
entriesCacheTime = Date.now();
|
|
||||||
}
|
|
||||||
return entriesCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bun.serve({
|
|
||||||
routes: {
|
|
||||||
'/': homepage,
|
|
||||||
'/robots.txt': new Response(robotsTxt, {
|
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
}),
|
|
||||||
'/sitemap.txt': new Response(sitemapTxt, {
|
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
}),
|
|
||||||
'/favicon.ico': new Response(favicon, {
|
|
||||||
headers: { 'Content-Type': 'image/png' },
|
|
||||||
}),
|
|
||||||
'/health': new Response('OK'),
|
|
||||||
'/api/entries': {
|
|
||||||
async GET(req) {
|
|
||||||
if (!isLoggedIn(req)) return unauthorizedResp();
|
|
||||||
|
|
||||||
return new Response(await getEntriesCached(), {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'/auth/logout': {
|
|
||||||
async POST() {
|
|
||||||
return new Response('Logout successful', { headers: logoutHeaders() });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'/auth/login': {
|
|
||||||
async POST(req) {
|
|
||||||
const json = await req.json();
|
|
||||||
const data = json as Record<string, any>;
|
|
||||||
const email = data.email;
|
|
||||||
const password = data.password;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof email !== 'string' ||
|
|
||||||
typeof password !== 'string' ||
|
|
||||||
email.length < 3 ||
|
|
||||||
password.length === 0
|
|
||||||
) {
|
|
||||||
return new Response('Missing email or password', { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
||||||
|
|
||||||
if (email === env.EMAIL && password === env.PASSWORD) {
|
|
||||||
let token = randomUUIDv7('base64url');
|
|
||||||
while (globalThis.loginTokens.has(token)) {
|
|
||||||
// generate a new token if it already exists
|
|
||||||
// this is unlikely to happen, but just in case
|
|
||||||
token = randomUUIDv7();
|
|
||||||
}
|
|
||||||
|
|
||||||
const cookie = new Bun.Cookie({
|
|
||||||
name: authCookie,
|
|
||||||
value: token,
|
|
||||||
path: '/',
|
|
||||||
maxAge: 60 * 60 * 24 * 31,
|
|
||||||
secure: true,
|
|
||||||
sameSite: 'strict',
|
|
||||||
});
|
|
||||||
|
|
||||||
const headers = new Headers({ 'Set-Cookie': cookie.toString() });
|
|
||||||
|
|
||||||
globalThis.loginTokens.add(token);
|
|
||||||
|
|
||||||
setTimeout(getEntriesCached, 1);
|
|
||||||
|
|
||||||
return new Response('Login successful', { headers });
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
||||||
|
|
||||||
return new Response('Incorrect email or password', {
|
|
||||||
status: 400,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
development,
|
|
||||||
reusePort: true,
|
|
||||||
// async fetch(req, server) {
|
|
||||||
// return new Response("Not found", { status: 404 });
|
|
||||||
// },
|
|
||||||
});
|
|
||||||
|
|
||||||
function isLoggedIn(req: Request) {
|
|
||||||
const cookie = new Bun.CookieMap(req.headers.get('cookie') || '');
|
|
||||||
const token = cookie.get(authCookie);
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return globalThis.loginTokens.has(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
function logoutHeaders() {
|
|
||||||
return new Headers({
|
|
||||||
'Set-Cookie': new Bun.Cookie({
|
|
||||||
name: authCookie,
|
|
||||||
path: '/',
|
|
||||||
maxAge: -1,
|
|
||||||
secure: true,
|
|
||||||
sameSite: 'strict',
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function unauthorizedResp() {
|
|
||||||
return new Response('Unauthorized', { status: 401, headers: logoutHeaders() });
|
|
||||||
}
|
|
161
src/server/auth.ts
Normal file
161
src/server/auth.ts
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { drizzleDB } from "./db";
|
||||||
|
import { users, userSessions } from "./db/schema";
|
||||||
|
import { env } from "bun";
|
||||||
|
|
||||||
|
const authCookie = 'pt-auth';
|
||||||
|
|
||||||
|
const day = 1000 * 60 * 60 * 24;
|
||||||
|
let days = Number.parseInt(env.SESSION_DURATION_IN_DAYS ?? '31', 10);
|
||||||
|
if (Number.isNaN(days)) {
|
||||||
|
days = 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxAge = days * day;
|
||||||
|
const renewalTime = Math.abs(Math.floor(maxAge / 7));
|
||||||
|
|
||||||
|
async function login(email: string, password: string): Promise<string | null> {
|
||||||
|
const result = await drizzleDB.select().from(users).where(eq(users.email, email.toLocaleLowerCase()));
|
||||||
|
if (result.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = result[0];
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify password
|
||||||
|
const valid = await Bun.password.verify(password, user.hash);
|
||||||
|
if (!valid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = Bun.randomUUIDv7('base64url');
|
||||||
|
|
||||||
|
await drizzleDB.insert(userSessions).values({
|
||||||
|
token,
|
||||||
|
maxAge: Date.now() + maxAge,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loginResponse(token: string) {
|
||||||
|
const cookie = new Bun.Cookie({
|
||||||
|
name: authCookie,
|
||||||
|
value: token,
|
||||||
|
path: '/',
|
||||||
|
maxAge,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
httpOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response('Login successful', {
|
||||||
|
headers: new Headers({
|
||||||
|
'Set-Cookie': cookie.toString(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logoutHeaders() {
|
||||||
|
return new Headers({
|
||||||
|
'Set-Cookie': new Bun.Cookie({
|
||||||
|
name: authCookie,
|
||||||
|
path: '/',
|
||||||
|
maxAge: -1,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
httpOnly: true,
|
||||||
|
}).toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logoutResponse() {
|
||||||
|
return new Response('Logout successful', { status: 303, headers: logoutHeaders() });
|
||||||
|
}
|
||||||
|
|
||||||
|
function unAuthorizedResponse() {
|
||||||
|
return new Response('Unauthorized', { status: 401, headers: logoutHeaders() });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verify(headers: Headers) {
|
||||||
|
const cookieMap = new Bun.CookieMap(headers.get('cookie') || '');
|
||||||
|
const token = cookieMap.get(authCookie);
|
||||||
|
if (!token) {
|
||||||
|
// cookie not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await drizzleDB.select().from(userSessions).where(eq(userSessions.token, token));
|
||||||
|
if (result.length === 0) {
|
||||||
|
// session not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = result[0];
|
||||||
|
if (!session) {
|
||||||
|
// session not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
if (session.maxAge <= now) {
|
||||||
|
// session expired
|
||||||
|
await drizzleDB.delete(userSessions).where(eq(userSessions.token, token));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.maxAge <= now - renewalTime) {
|
||||||
|
// renew session
|
||||||
|
const newMaxAge = now + maxAge;
|
||||||
|
await drizzleDB.update(userSessions).set({ maxAge: newMaxAge }).where(eq(userSessions.token, token));
|
||||||
|
session.maxAge = newMaxAge; // renew session
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyResponse(token: string) {
|
||||||
|
const cookie = new Bun.Cookie({
|
||||||
|
name: authCookie,
|
||||||
|
value: token,
|
||||||
|
path: '/',
|
||||||
|
maxAge,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
httpOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response('Verify successful', {
|
||||||
|
headers: new Headers({ 'Set-Cookie': cookie.toString() }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyFailResponse() {
|
||||||
|
const cookie = new Bun.Cookie({
|
||||||
|
name: authCookie,
|
||||||
|
path: '/',
|
||||||
|
maxAge: -1,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
httpOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response('Verify failure', {
|
||||||
|
status: 401,
|
||||||
|
headers: new Headers({ 'Set-Cookie': cookie.toString() }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
login,
|
||||||
|
loginResponse,
|
||||||
|
logoutResponse,
|
||||||
|
unAuthorizedResponse,
|
||||||
|
verify,
|
||||||
|
verifyResponse,
|
||||||
|
verifyFailResponse,
|
||||||
|
}
|
71
src/server/db/index.ts
Normal file
71
src/server/db/index.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { Database } from 'bun:sqlite';
|
||||||
|
import { env } from 'bun';
|
||||||
|
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
||||||
|
import { createWrappedTimer } from '../wrapped-timer';
|
||||||
|
import { users } from './schema';
|
||||||
|
|
||||||
|
function initDb() {
|
||||||
|
// global.db.exec('PRAGMA journal_mode = delete');
|
||||||
|
global.db.exec('PRAGMA journal_mode = WAL');
|
||||||
|
global.db.exec('PRAGMA synchronous = NORMAL');
|
||||||
|
global.db.exec('PRAGMA auto_vacuum = INCREMENTAL');
|
||||||
|
global.db.exec('PRAGMA wal_autocheckpoint = 1000');
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementalVacuumDb() {
|
||||||
|
global.db.exec('PRAGMA incremental_vacuum');
|
||||||
|
}
|
||||||
|
|
||||||
|
function optimizeDb() {
|
||||||
|
global.db.exec('PRAGMA optimize');
|
||||||
|
}
|
||||||
|
|
||||||
|
function vacuumDb() {
|
||||||
|
global.db.exec('vacuum');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedDB(drizzle: typeof global.drizzleDB) {
|
||||||
|
let id = env.ADMIN_USER_ID;
|
||||||
|
let email = env.ADMIN_USER_EMAIL;
|
||||||
|
let name = env.ADMIN_USER_NAME ?? 'admin';
|
||||||
|
let password = env.ADMIN_USER_PASSWORD;
|
||||||
|
|
||||||
|
if (!id || !email || !password) {
|
||||||
|
console.warn('Missing environment variables for seeding database');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = await Bun.password.hash(password);
|
||||||
|
|
||||||
|
await drizzle.insert(users).values({
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
hash
|
||||||
|
}).onConflictDoUpdate({
|
||||||
|
target: users.id,
|
||||||
|
set: { email, name, hash }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.db === undefined || global.drizzleDB === undefined) {
|
||||||
|
global.db = new Database(env.DATABASE_URL, { create: true, strict: true });
|
||||||
|
initDb();
|
||||||
|
global.drizzleDB = drizzle(global.db);
|
||||||
|
await seedDB(global.drizzleDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
const incrementalVacuumInterval = 1000 * 30; // 30 seconds
|
||||||
|
const incrementalVacuumRunnable = createWrappedTimer('databaseIncrementalVacuum', incrementalVacuumDb, incrementalVacuumInterval);
|
||||||
|
|
||||||
|
setTimeout(incrementalVacuumRunnable.callback, 0);
|
||||||
|
const optimizeInterval = 1000 * 60 * 30; // 30 minutes
|
||||||
|
const optimizeRunnable = createWrappedTimer('databaseOptimize', optimizeDb, optimizeInterval);
|
||||||
|
setTimeout(optimizeRunnable.callback, 0);
|
||||||
|
|
||||||
|
const vacuumInterval = 1000 * 60 * 60 * 24; // 24 hours
|
||||||
|
const vacuumRunnable = createWrappedTimer('databaseVacuum', vacuumDb, vacuumInterval);
|
||||||
|
setTimeout(vacuumRunnable.callback, 0);
|
||||||
|
|
||||||
|
export const db = global.db;
|
||||||
|
export const drizzleDB = global.drizzleDB;
|
10
src/server/db/migrate.ts
Normal file
10
src/server/db/migrate.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Database } from 'bun:sqlite';
|
||||||
|
import { env } from 'bun';
|
||||||
|
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
||||||
|
import { migrate } from 'drizzle-orm/bun-sqlite/migrator';
|
||||||
|
|
||||||
|
const sqlite = new Database(env.DATABASE_URL, { create: true, strict: true });
|
||||||
|
|
||||||
|
const db = drizzle(sqlite);
|
||||||
|
|
||||||
|
migrate(db, { migrationsFolder: './drizzle' });
|
37
src/server/db/schema.ts
Normal file
37
src/server/db/schema.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { sql, type InferSelectModel } from 'drizzle-orm';
|
||||||
|
import { foreignKey, index, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||||
|
|
||||||
|
const createdAt = integer('created_at', { mode: 'timestamp_ms' })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch('subsec') * 1000)`)
|
||||||
|
.$defaultFn(() => new Date());
|
||||||
|
const updatedAt = integer('updated_at', { mode: 'timestamp_ms' })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch('subsec') * 1000)`)
|
||||||
|
.$onUpdateFn(() => new Date());
|
||||||
|
|
||||||
|
export const users = sqliteTable(
|
||||||
|
'users',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
email: text('email').notNull().unique(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
hash: text('hash').notNull(),
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const userSessions = sqliteTable(
|
||||||
|
'user_sessions',
|
||||||
|
{
|
||||||
|
token: text('token').primaryKey().unique(),
|
||||||
|
userId: text('user_id').notNull(),
|
||||||
|
maxAge: integer('max_age', { mode: 'number' }).notNull(),
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
foreignKey({ columns: [t.userId], foreignColumns: [users.id] }).onDelete('cascade'),
|
||||||
|
],
|
||||||
|
);
|
10
src/server/index.d.ts
vendored
Normal file
10
src/server/index.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import type { Database } from 'bun:sqlite';
|
||||||
|
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
||||||
|
import type { WrappedTimer } from './wrapped-timer';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var db: Database;
|
||||||
|
var drizzleDB: BunSQLiteDatabase;
|
||||||
|
var performanceObserver: PerformanceObserver;
|
||||||
|
var wrappedTimers: Map<string, WrappedTimer>;
|
||||||
|
}
|
78
src/server/wrapped-timer.ts
Normal file
78
src/server/wrapped-timer.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
if (global.wrappedTimers === undefined) {
|
||||||
|
global.wrappedTimers = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WrappedTimer = { timer: Timer | undefined; running: boolean; };
|
||||||
|
type WrappedTimerResult = { callback: ReturnType<typeof getCallbackHandler>; wrappedTimer: WrappedTimer; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a callback handler for a wrapped timer that prevents the callback from running if it is already running
|
||||||
|
* and prevents the callback from running if it is already running.
|
||||||
|
* @template TArgs
|
||||||
|
* @param {string} key unique identifier for the timer
|
||||||
|
* @param {(...args: Array<TArgs>) => (Promise<void> | void)} callback function to run
|
||||||
|
* @param {Array<TArgs>} args arguments to pass to the callback
|
||||||
|
* @returns {() => Promise<void>}
|
||||||
|
*/
|
||||||
|
function getCallbackHandler<TArgs>(key: string, callback: (...args: Array<TArgs>) => (Promise<void> | void), ...args: Array<TArgs>): () => Promise<void> {
|
||||||
|
return async () => {
|
||||||
|
const thisTimer = global.wrappedTimers.get(key);
|
||||||
|
if (thisTimer === undefined) {
|
||||||
|
// console.debug(`Wrapped timer ${key} does not exist`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thisTimer.running) {
|
||||||
|
// console.debug(`Wrapped timer ${key} is already running`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
thisTimer.running = true;
|
||||||
|
await callback(...args);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
thisTimer.running = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wrapped timer aka interval
|
||||||
|
* @template TArgs
|
||||||
|
* @param {string} key unique identifier for the timer
|
||||||
|
* @param {number} interval in milliseconds
|
||||||
|
* @param {(...args: Array<TArgs>) => (Promise<void> | void)} callback function to run
|
||||||
|
* @param {Array<TArgs>} args arguments to pass to the callback
|
||||||
|
* @returns {WrappedTimerResult}
|
||||||
|
*/
|
||||||
|
export function createWrappedTimer<TArgs>(key: string, callback: (...args: Array<TArgs>) => (Promise<void> | void), interval: number, ...args: Array<TArgs>): WrappedTimerResult {
|
||||||
|
const thisTimer = global.wrappedTimers.get(key);
|
||||||
|
const handler = getCallbackHandler(key, callback, ...args);
|
||||||
|
|
||||||
|
if (thisTimer !== undefined) {
|
||||||
|
// console.debug(`Wrapped timer ${key} already exists, clearing timer`);
|
||||||
|
clearInterval(thisTimer.timer);
|
||||||
|
// console.debug(`Wrapped timer ${key} set with interval ${interval}ms`);
|
||||||
|
thisTimer.timer = setInterval(handler, interval);
|
||||||
|
|
||||||
|
return {
|
||||||
|
callback: handler,
|
||||||
|
wrappedTimer: thisTimer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.debug(`Wrapped timer ${key} created with interval ${interval}ms`);
|
||||||
|
|
||||||
|
const wrappedTimer: WrappedTimer = {
|
||||||
|
timer: setInterval(handler, interval),
|
||||||
|
running: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
global.wrappedTimers.set(key, wrappedTimer);
|
||||||
|
|
||||||
|
return {
|
||||||
|
callback: handler,
|
||||||
|
wrappedTimer,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
function getCookieMap() {
|
|
||||||
const cookies = document.cookie.split('; ');
|
|
||||||
const cookieMap = new Map<string, string>();
|
|
||||||
for (let i = 0; i < cookies.length; i++) {
|
|
||||||
const cookie = cookies[i];
|
|
||||||
if (!cookie) continue;
|
|
||||||
let [key, value] = cookie.split('=');
|
|
||||||
if (!key || !value) continue;
|
|
||||||
cookieMap.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cookieMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserState {
|
|
||||||
isLoggedIn = $state(false);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.checkIsLoggedIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
checkIsLoggedIn() {
|
|
||||||
const cookieVal = getCookieMap().get('pt-auth');
|
|
||||||
this.isLoggedIn = cookieVal !== undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const userstate = new UserState();
|
|
|
@ -24,5 +24,8 @@
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false
|
||||||
}
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue