Compare commits
25 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
d9502cb13d | |
|
|
caee9f6fe9 | |
|
|
a6f22ea3c9 | |
|
|
2911f05dcd | |
|
|
b0b6557464 | |
|
|
d7dcbb3560 | |
|
|
16b5aba457 | |
|
|
976cd3edb9 | |
|
|
9c3f103758 | |
|
|
c4ee8fc372 | |
|
|
6566bb4403 | |
|
|
db44350bc9 | |
|
|
2feb97ddfe | |
|
|
8de9738d85 | |
|
|
1d6ca4abd8 | |
|
|
8c03efbfc0 | |
|
|
47c29ad10e | |
|
|
f5362f52e7 | |
|
|
78e9f268b6 | |
|
|
8eacebc306 | |
|
|
ecff8798fe | |
|
|
8e8c2bbf68 | |
|
|
0d5024f6d3 | |
|
|
3e582537df | |
|
|
e0d76a4cb0 |
64
bun.lock
64
bun.lock
|
|
@ -3,15 +3,19 @@
|
|||
"workspaces": {
|
||||
"": {
|
||||
"name": "todo",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.18.0",
|
||||
"@types/bun": "^1.3.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"prisma": "^6.16.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^7.0.4",
|
||||
"@sveltejs/adapter-auto": "^6.1.1",
|
||||
"@sveltejs/kit": "^2.47.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"prisma": "^6.18.0",
|
||||
"svelte": "^5.41.4",
|
||||
"svelte-check": "^4.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.12",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -80,17 +84,19 @@
|
|||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@prisma/config": ["@prisma/config@6.16.1", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.16.12", "empathic": "2.0.0" } }, "sha512-sz3uxRPNL62QrJ0EYiujCFkIGZ3hg+9hgC1Ae1HjoYuj0BxCqHua4JNijYvYCrh9LlofZDZcRBX3tHBfLvAngA=="],
|
||||
"@prisma/client": ["@prisma/client@6.18.0", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA=="],
|
||||
|
||||
"@prisma/debug": ["@prisma/debug@6.16.1", "", {}, "sha512-RWv/VisW5vJE4cDRTuAHeVedtGoItXTnhuLHsSlJ9202QKz60uiXWywBlVcqXVq8bFeIZoCoWH+R1duZJPwqLw=="],
|
||||
"@prisma/config": ["@prisma/config@6.18.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ=="],
|
||||
|
||||
"@prisma/engines": ["@prisma/engines@6.16.1", "", { "dependencies": { "@prisma/debug": "6.16.1", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/fetch-engine": "6.16.1", "@prisma/get-platform": "6.16.1" } }, "sha512-EOnEM5HlosPudBqbI+jipmaW/vQEaF0bKBo4gVkGabasINHR6RpC6h44fKZEqx4GD8CvH+einD2+b49DQrwrAg=="],
|
||||
"@prisma/debug": ["@prisma/debug@6.18.0", "", {}, "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg=="],
|
||||
|
||||
"@prisma/engines-version": ["@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "", {}, "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA=="],
|
||||
"@prisma/engines": ["@prisma/engines@6.18.0", "", { "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "@prisma/fetch-engine": "6.18.0", "@prisma/get-platform": "6.18.0" } }, "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA=="],
|
||||
|
||||
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.16.1", "", { "dependencies": { "@prisma/debug": "6.16.1", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/get-platform": "6.16.1" } }, "sha512-fl/PKQ8da5YTayw86WD3O9OmKJEM43gD3vANy2hS5S1CnfW2oPXk+Q03+gUWqcKK306QqhjjIHRFuTZ31WaosQ=="],
|
||||
"@prisma/engines-version": ["@prisma/engines-version@6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "", {}, "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ=="],
|
||||
|
||||
"@prisma/get-platform": ["@prisma/get-platform@6.16.1", "", { "dependencies": { "@prisma/debug": "6.16.1" } }, "sha512-kUfg4vagBG7dnaGRcGd1c0ytQFcDj2SUABiuveIpL3bthFdTLI6PJeLEia6Q8Dgh+WhPdo0N2q0Fzjk63XTyaA=="],
|
||||
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.18.0", "", { "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "@prisma/get-platform": "6.18.0" } }, "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A=="],
|
||||
|
||||
"@prisma/get-platform": ["@prisma/get-platform@6.18.0", "", { "dependencies": { "@prisma/debug": "6.18.0" } }, "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.1", "", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="],
|
||||
|
||||
|
|
@ -138,24 +144,32 @@
|
|||
|
||||
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
|
||||
|
||||
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@6.1.0", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-shOuLI5D2s+0zTv2ab5M5PqfknXqWbKi+0UwB9yLTRIdzsK1R93JOO8jNhIYSHdW+IYXIYnLniu+JZqXs7h9Wg=="],
|
||||
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@6.1.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-cBNt4jgH4KuaNO5gRSB2CZKkGtz+OCZ8lPjRQGjhvVUD4akotnj2weUia6imLl2v07K3IgsQRyM36909miSwoQ=="],
|
||||
|
||||
"@sveltejs/kit": ["@sveltejs/kit@2.39.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-8yZpfCfv9FQfJUBxtD8XMS+T/+ls5xdPZvMwKg4qNS9fmzfWsah3Dz8IR5cmbXtCzHkgr0j/+v6zq5Fk4IuTBA=="],
|
||||
"@sveltejs/kit": ["@sveltejs/kit@2.47.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-zN2yzBc2dIES2BSzLhNP2weYhwB77kgM/oAktICZVmmljyEmPZrlUwr14jjdK9/eDu7WdAuf6gTdYIJLTcN3Fw=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-nJsV36+o7rZUDlrnSduMNl11+RoDE1cKqOI0yUEBCcqFoAZOk47TwD3dPKS2WmRutke9StXnzsPBslY7prDM9w=="],
|
||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||
|
||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/node": ["@types/node@24.5.0", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
"c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
|
@ -170,6 +184,8 @@
|
|||
|
||||
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
|
@ -184,7 +200,7 @@
|
|||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
"effect": ["effect@3.16.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg=="],
|
||||
"effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="],
|
||||
|
||||
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
|
||||
|
||||
|
|
@ -240,7 +256,7 @@
|
|||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"prisma": ["prisma@6.16.1", "", { "dependencies": { "@prisma/config": "6.16.1", "@prisma/engines": "6.16.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-MFkMU0eaDDKAT4R/By2IA9oQmwLTxokqv2wegAErr9Rf+oIe7W2sYpE/Uxq0H2DliIR7vnV63PkC1bEwUtl98w=="],
|
||||
"prisma": ["prisma@6.18.0", "", { "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g=="],
|
||||
|
||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||
|
||||
|
|
@ -258,9 +274,9 @@
|
|||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"svelte": ["svelte@5.38.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-UY+OhrWK7WI22bCZ00P/M3HtyWgwJPi9IxSRkoAE2MeAy6kl7ZlZWJZ8RaB+X4KD/G+wjis+cGVnVYaoqbzBqg=="],
|
||||
"svelte": ["svelte@5.41.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-sBrVmskY0eij5+CSSuMK1kOk1xSiRh0BRTEhUnxnd1x3p42GbQmm4rwq4WMD23rUG3QR53Nfv8kpaER3tV5F4A=="],
|
||||
|
||||
"svelte-check": ["svelte-check@4.3.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": "bin/svelte-check" }, "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg=="],
|
||||
"svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
|
||||
|
||||
|
|
@ -268,9 +284,11 @@
|
|||
|
||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"vite": ["vite@7.1.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ=="],
|
||||
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
||||
|
||||
"vite": ["vite@7.1.12", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,14 @@
|
|||
"": {
|
||||
"name": "todo",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.16.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"prisma": "^6.16.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
|
|
@ -516,6 +520,91 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.1.tgz",
|
||||
"integrity": "sha512-QaBCOY29lLAxEFFJgBPyW3WInCW52fJeQTmWx/h6YsP5u0bwuqP51aP0uhqFvhK9DaZPwvai/M4tSDYLVE9vRg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prisma": "*",
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prisma": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.1.tgz",
|
||||
"integrity": "sha512-sz3uxRPNL62QrJ0EYiujCFkIGZ3hg+9hgC1Ae1HjoYuj0BxCqHua4JNijYvYCrh9LlofZDZcRBX3tHBfLvAngA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"c12": "3.1.0",
|
||||
"deepmerge-ts": "7.1.5",
|
||||
"effect": "3.16.12",
|
||||
"empathic": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.1.tgz",
|
||||
"integrity": "sha512-RWv/VisW5vJE4cDRTuAHeVedtGoItXTnhuLHsSlJ9202QKz60uiXWywBlVcqXVq8bFeIZoCoWH+R1duZJPwqLw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.1.tgz",
|
||||
"integrity": "sha512-EOnEM5HlosPudBqbI+jipmaW/vQEaF0bKBo4gVkGabasINHR6RpC6h44fKZEqx4GD8CvH+einD2+b49DQrwrAg==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.16.1",
|
||||
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
|
||||
"@prisma/fetch-engine": "6.16.1",
|
||||
"@prisma/get-platform": "6.16.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz",
|
||||
"integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.1.tgz",
|
||||
"integrity": "sha512-fl/PKQ8da5YTayw86WD3O9OmKJEM43gD3vANy2hS5S1CnfW2oPXk+Q03+gUWqcKK306QqhjjIHRFuTZ31WaosQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.16.1",
|
||||
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
|
||||
"@prisma/get-platform": "6.16.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.1.tgz",
|
||||
"integrity": "sha512-kUfg4vagBG7dnaGRcGd1c0ytQFcDj2SUABiuveIpL3bthFdTLI6PJeLEia6Q8Dgh+WhPdo0N2q0Fzjk63XTyaA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.16.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz",
|
||||
|
|
@ -814,7 +903,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sveltejs/acorn-typescript": {
|
||||
|
|
@ -962,11 +1051,40 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
|
||||
"integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
"confbox": "^0.2.2",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^16.6.1",
|
||||
"exsolve": "^1.0.7",
|
||||
"giget": "^2.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^2.2.0",
|
||||
"rc9": "^2.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"magicast": "^0.3.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"magicast": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
|
|
@ -978,6 +1096,16 @@
|
|||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/citty": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
|
|
@ -988,6 +1116,23 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
|
|
@ -1026,6 +1171,30 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge-ts": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/destr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz",
|
||||
|
|
@ -1033,6 +1202,40 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "3.16.12",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz",
|
||||
"integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"fast-check": "^3.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/empathic": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
|
||||
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
|
||||
|
|
@ -1092,6 +1295,36 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
|
||||
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "3.23.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
|
||||
"integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
|
|
@ -1125,6 +1358,24 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
"node-fetch-native": "^1.6.6",
|
||||
"nypm": "^0.6.0",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"bin": {
|
||||
"giget": "dist/cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
|
|
@ -1135,6 +1386,16 @@
|
|||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||
|
|
@ -1208,6 +1469,54 @@
|
|||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
|
||||
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
|
@ -1228,6 +1537,18 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.2",
|
||||
"exsolve": "^1.0.7",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
|
|
@ -1257,11 +1578,65 @@
|
|||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.1.tgz",
|
||||
"integrity": "sha512-MFkMU0eaDDKAT4R/By2IA9oQmwLTxokqv2wegAErr9Rf+oIe7W2sYpE/Uxq0H2DliIR7vnV63PkC1bEwUtl98w==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.16.1",
|
||||
"@prisma/engines": "6.16.1"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
|
|
@ -1407,6 +1782,13 @@
|
|||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
|
||||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
|
|
@ -1438,7 +1820,7 @@
|
|||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
|
|
|||
22
package.json
22
package.json
|
|
@ -2,14 +2,14 @@
|
|||
"name": "todo",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"prisma": "^6.16.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^7.0.4"
|
||||
"@sveltejs/adapter-auto": "^6.1.1",
|
||||
"@sveltejs/kit": "^2.47.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"prisma": "^6.18.0",
|
||||
"svelte": "^5.41.4",
|
||||
"svelte-check": "^4.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.12"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
@ -20,5 +20,9 @@
|
|||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.18.0",
|
||||
"@types/bun": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "public"."User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"display_name" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password_hash" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Task" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"checked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"checked_at" TIMESTAMP(3),
|
||||
"content" TEXT NOT NULL,
|
||||
"parentId" INTEGER,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Task_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "public"."User"("email");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Task" ADD CONSTRAINT "Task_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "public"."Task"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `content` on the `Task` table. All the data in the column will be lost.
|
||||
- Added the required column `topic` to the `Task` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."Task" RENAME COLUMN "content" TO "topic";
|
||||
ALTER TABLE "public"."Task" ADD COLUMN "description" TEXT;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
|
|
@ -1,15 +1,37 @@
|
|||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../.generated.prisma"
|
||||
output = "../node_modules/.prisma/client"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
display_name String
|
||||
email String @unique
|
||||
password_hash String
|
||||
tasks Task[]
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Task {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
checked Boolean @default(false)
|
||||
checked_at DateTime?
|
||||
topic String
|
||||
description String?
|
||||
|
||||
parentId Int?
|
||||
parent Task? @relation("Subtasks", fields: [parentId], references: [id])
|
||||
subtasks Task[] @relation("Subtasks")
|
||||
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,13 @@
|
|||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
interface Error {
|
||||
message: string
|
||||
cause?: number,
|
||||
}
|
||||
interface Locals {
|
||||
user?: User
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/global.css" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
import type { Handle } from "@sveltejs/kit"
|
||||
|
||||
import { error, redirect, json, fail } from "@sveltejs/kit"
|
||||
|
||||
import { Error401Cause } from "$lib/errors"
|
||||
|
||||
import Config from "$lib/server/config"
|
||||
import UserMgmt from "$lib/server/usermgmt"
|
||||
|
||||
function action_fail<T = undefined>(status: number, data: T) {
|
||||
return json(fail(status, data))
|
||||
}
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
const token = event.cookies.get("session")
|
||||
let session = await UserMgmt.session_login(token ?? "")
|
||||
|
||||
if (session && event.route.id !== "/api/refresh") {
|
||||
if (session.expires.getTime() < Date.now() + Config.session_refresh_grace) {
|
||||
|
||||
const response = await event.fetch("/api/refresh", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: token
|
||||
})
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const new_session = await response.json()
|
||||
|
||||
if (new_session && new_session.token !== null && new_session.expires !== null) {
|
||||
event.cookies.set("session", new_session.token, {
|
||||
expires: new Date(new_session.expires),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
path: "/"
|
||||
})
|
||||
// For mobile
|
||||
/*event.setHeaders({
|
||||
"X-Authorization-Refresh": new_session.token
|
||||
})*/
|
||||
|
||||
session = await UserMgmt.session_login(new_session.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.locals.user = session?.user
|
||||
|
||||
if (!event.route.id) {
|
||||
return error(404, { message: "Diese Seite existiert nicht" })
|
||||
}
|
||||
|
||||
if (event.route.id.startsWith("/api")) {
|
||||
if (event.route.id.startsWith("/api/users") && !event.locals.user) {
|
||||
return error(401, { cause: Error401Cause.NotLoggedIn, message: "Please log in" })
|
||||
}
|
||||
} else {
|
||||
if (!event.locals.user && !event.route.id.startsWith("/login")) {
|
||||
if (event.request.method === "POST") {
|
||||
if (event.request.headers.get("x-sveltekit-action")) {
|
||||
return action_fail(401, { cause: Error401Cause.NotLoggedIn, message: "Bitte melden Sie sich an." })
|
||||
}
|
||||
return error(401, { cause: Error401Cause.NotLoggedIn, message: "Bitte melden Sie sich an." })
|
||||
}
|
||||
return redirect(307, "/login")
|
||||
}
|
||||
}
|
||||
|
||||
return await resolve(event)
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
|
||||
let { checked = $bindable(), ...props} = $props()
|
||||
|
||||
let loaded = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
loaded = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="shell" class:loaded={loaded}>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked/>
|
||||
<svg width="10px" height="10px" viewBox="0 0 12 9">
|
||||
<polyline points="1,5 4,8 11,1" fill="none"></polyline>
|
||||
</svg>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
label {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
|
||||
border: 1px solid black;
|
||||
border-radius: 50%;
|
||||
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label:hover {
|
||||
border-color: gray;
|
||||
}
|
||||
|
||||
label:has(>input:checked) {
|
||||
background: green;
|
||||
}
|
||||
.loaded label:has(>input:checked) {
|
||||
animation: pop 0.6s ease;
|
||||
}
|
||||
|
||||
.loaded label::before {
|
||||
content: "";
|
||||
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
opacity: 1;
|
||||
|
||||
transform: scale(0);
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
.loaded label:has(>input:checked)::before {
|
||||
transform: scale(2.2);
|
||||
opacity: 0;
|
||||
transition: all 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes pop {
|
||||
50% {
|
||||
transform: translateY(-50%) translateX(-50%) scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
stroke: white;
|
||||
stroke-width: 1.5px;
|
||||
stroke-dasharray: 16px;
|
||||
stroke-dashoffset: 16px;
|
||||
}
|
||||
.loaded svg {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
label:has(> input:checked) > svg {
|
||||
stroke-dashoffset: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<script module lang="ts">
|
||||
import type { Task } from "@prisma/client";
|
||||
|
||||
export interface Props {
|
||||
task: Task | null
|
||||
form: string
|
||||
open?: boolean
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
let { task, form, open = $bindable() }: Props = $props();
|
||||
|
||||
let submitButton: HTMLButtonElement;
|
||||
let topicTextArea: HTMLTextAreaElement;
|
||||
let descriptionTextArea: HTMLTextAreaElement;
|
||||
|
||||
function cancelOverlay() {
|
||||
// add change alert
|
||||
open = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={(event) => {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
if (event.key === "Escape") {
|
||||
cancelOverlay()
|
||||
}
|
||||
}
|
||||
}} />
|
||||
|
||||
<div class="overlay" class:open={open}>
|
||||
|
||||
<div class="background" role="presentation" aria-hidden="true" onclick={() => { cancelOverlay() }}></div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<textarea class="topic" form={form} bind:this={topicTextArea} name="topic" placeholder="What would you like to do" rows="1">{task?.topic ?? ""}</textarea>
|
||||
|
||||
<textarea class="description" form={form} bind:this={descriptionTextArea} name="description" placeholder="Add a description here.">{task?.description ?? ""}</textarea>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
form={form}
|
||||
bind:this={submitButton}
|
||||
onclick={(event) => {
|
||||
if (topicTextArea.value.length != 0) {
|
||||
(event.target as HTMLButtonElement).form?.requestSubmit()
|
||||
}
|
||||
event.preventDefault()
|
||||
}}
|
||||
>Save and close</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:global(body:has(.overlay.open)) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-color: black;
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
|
||||
width: 75%;
|
||||
min-height: 20%;
|
||||
|
||||
background: darkgreen;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
min-width: 100%;
|
||||
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
field-sizing: content;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.topic {
|
||||
font-size: 24px;
|
||||
|
||||
&:focus {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
&.description {
|
||||
|
||||
min-height: 10em;
|
||||
|
||||
&:focus {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<script module>
|
||||
|
||||
import type { Task } from "@prisma/client"
|
||||
|
||||
import Checkbox from "./checkbox.svelte"
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export interface TaskComponentProps {
|
||||
task: Task
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
const { task }: TaskComponentProps = $props()
|
||||
|
||||
let loaded = $state(false)
|
||||
onMount(() => { loaded = true })
|
||||
|
||||
let checked = $state(task.checked)
|
||||
|
||||
</script>
|
||||
|
||||
<div class="container" class:loaded={loaded}>
|
||||
<div class="main-task">
|
||||
<div class="checkbox">
|
||||
<Checkbox bind:checked={checked}/>
|
||||
</div>
|
||||
<div class="taskheader">
|
||||
<div><h2 class="taskheader">{task.content}</h2></div>
|
||||
</div>
|
||||
{#if true}
|
||||
<div class="taskcontent">{@html [task.content, "<br>"].join().repeat(5)}</div>
|
||||
{/if}
|
||||
<div class="due">
|
||||
Due: <br/>
|
||||
{new Date(task.created_at).toLocaleDateString()} <br/>
|
||||
{new Date(task.created_at).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.container {
|
||||
width: var(--width, 100%);
|
||||
min-height: var(--min-height, 50px);
|
||||
|
||||
padding: 10px;
|
||||
border: 1px solid gray;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-items: center;
|
||||
justify-content: stretch;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.main-task {
|
||||
width: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 25px auto fit-content(11ch);
|
||||
grid-template-areas:
|
||||
'checkboxArea headerArea dueArea';
|
||||
|
||||
column-gap: 10px;
|
||||
|
||||
align-content: stretch;
|
||||
}
|
||||
.main-task:has(.taskcontent) {
|
||||
grid-template-areas:
|
||||
'checkboxArea headerArea dueArea'
|
||||
'. contentArea dueArea';
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
grid-area: checkboxArea;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.taskheader {
|
||||
grid-area: headerArea;
|
||||
width: fit-content;
|
||||
transform: translateY(0.075em);
|
||||
}
|
||||
.taskheader h2 {
|
||||
margin: 0;
|
||||
}
|
||||
.taskcontent {
|
||||
grid-area: contentArea;
|
||||
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.due {
|
||||
grid-area: dueArea;
|
||||
min-width: 11ch;
|
||||
}
|
||||
|
||||
.loaded {
|
||||
transition: none;
|
||||
}
|
||||
.loaded .checked {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
export class ArgumentError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateError extends Error {
|
||||
fields: Array<string>
|
||||
|
||||
constructor(fields: Array<string>, message: string) {
|
||||
super(message)
|
||||
this.fields = fields
|
||||
}
|
||||
}
|
||||
|
||||
export const enum Error401Cause {
|
||||
NotLoggedIn
|
||||
}
|
||||
|
||||
export const enum RegisterResponseCause {
|
||||
Server = 1,
|
||||
MalformedRequest,
|
||||
EmailLength,
|
||||
EmailFormat,
|
||||
EmailDuplicate,
|
||||
PasswordLength,
|
||||
DisplayNameLength
|
||||
}
|
||||
|
||||
export const enum LoginResponseCause {
|
||||
Server = 1,
|
||||
MalformedRequest,
|
||||
EmailLength,
|
||||
PasswordLength,
|
||||
NotFound,
|
||||
Timeout
|
||||
}
|
||||
|
||||
export const enum RefreshResponseCause {
|
||||
Server = 1,
|
||||
MalformedRequest,
|
||||
InvalidToken,
|
||||
InvalidSession
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import Bun from "bun"
|
||||
import path from "node:path"
|
||||
|
||||
const to_absolute_path = (p: string): string => {
|
||||
if (path.isAbsolute(p)) {
|
||||
return p
|
||||
}
|
||||
return path.resolve(process.cwd(), p)
|
||||
}
|
||||
|
||||
const resolve_env_to_path = (env: string|undefined, fallback: string): string => {
|
||||
if (!env) {
|
||||
return to_absolute_path(fallback)
|
||||
}
|
||||
return to_absolute_path(env)
|
||||
}
|
||||
|
||||
const resolve_env_to_boolean = (env: string|undefined, fallback: boolean): boolean => {
|
||||
if (!env) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
const str = env.toLowerCase()
|
||||
|
||||
switch (str) {
|
||||
case "false":
|
||||
case "no":
|
||||
case "off": return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
class Config {
|
||||
private _log_dir: string
|
||||
private _log_to_file_when_debug: boolean
|
||||
|
||||
readonly is_debug: boolean = process.env.NODE_ENV != "production"
|
||||
readonly is_production: boolean = process.env.NODE_ENV == "production"
|
||||
|
||||
private _session_timeout: number = 15 * 60 * 1000
|
||||
private _session_refresh_grace: number = 5 * 60 * 1000 // time until expiration
|
||||
|
||||
readonly bypass_login = this.is_debug && process.env.BYPASS_LOGIN == "true"
|
||||
|
||||
get log_dir(): string {
|
||||
return this._log_dir
|
||||
}
|
||||
get log_to_file_when_debug(): boolean {
|
||||
return this._log_to_file_when_debug
|
||||
}
|
||||
|
||||
get session_timeout(): number { return this._session_timeout }
|
||||
get session_refresh_grace(): number { return this._session_refresh_grace }
|
||||
|
||||
constructor() {
|
||||
this._log_dir = resolve_env_to_path(process.env.APP_LOG_DIR, "./data/logs")
|
||||
this._log_to_file_when_debug = resolve_env_to_boolean(process.env.LOG_TO_FILE_WHEN_DEBUG, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _config = new Config()
|
||||
|
||||
export default _config;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default prisma
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
export enum LogSeverity {
|
||||
FATAL,
|
||||
ERROR,
|
||||
WARN,
|
||||
INFO,
|
||||
DEBUG,
|
||||
TRACE
|
||||
}
|
||||
|
||||
export enum LogModule {
|
||||
PROCESS,
|
||||
USER,
|
||||
DATABASE
|
||||
}
|
||||
|
||||
abstract class Logger {
|
||||
readonly module = {
|
||||
...LogModule
|
||||
}
|
||||
|
||||
abstract log(severity: LogSeverity, module: LogModule, message: string): void
|
||||
fatal(module: LogModule, message: string): void { this.log(LogSeverity.FATAL, module, message) }
|
||||
error(module: LogModule, message: string): void { this.log(LogSeverity.ERROR, module, message) }
|
||||
warn(module: LogModule, message: string): void { this.log(LogSeverity.WARN, module, message) }
|
||||
info(module: LogModule, message: string): void { this.log(LogSeverity.INFO, module, message) }
|
||||
debug(module: LogModule, message: string): void { this.log(LogSeverity.DEBUG, module, message) }
|
||||
trace(module: LogModule, message: string): void { this.log(LogSeverity.TRACE, module, message) }
|
||||
}
|
||||
|
||||
function create_log_string(severity: LogSeverity, module: LogModule, message: string): string {
|
||||
return `${(new Date()).toISOString()} [${LogModule[module]}] [${LogSeverity[severity]}] ${message}`
|
||||
}
|
||||
|
||||
class DebugLogger extends Logger {
|
||||
|
||||
log(severity: LogSeverity, module: LogModule, message: string): void {
|
||||
console.log(create_log_string(severity, module, message))
|
||||
}
|
||||
}
|
||||
|
||||
const _logger: Logger = new DebugLogger()
|
||||
|
||||
|
||||
export default _logger
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
import Bun from "bun"
|
||||
import Crypto from "node:crypto"
|
||||
import { Prisma } from "@prisma/client"
|
||||
|
||||
import { ArgumentError, DuplicateError } from "$lib/errors"
|
||||
|
||||
import Config from "$lib/server/config"
|
||||
import Log from "$lib/server/log"
|
||||
import db from "$lib/server/database"
|
||||
|
||||
export type User = Prisma.UserGetPayload<Prisma.UserDefaultArgs>
|
||||
|
||||
export interface SessionData {
|
||||
user: User
|
||||
token: string
|
||||
issued: Date
|
||||
expires: Date
|
||||
}
|
||||
|
||||
|
||||
interface CacheUserInfo {
|
||||
user: User
|
||||
last_update: Date
|
||||
}
|
||||
|
||||
interface CacheSessionInfo {
|
||||
user_id: number
|
||||
issued: Date
|
||||
expires: Date
|
||||
}
|
||||
|
||||
class Cache {
|
||||
private _id_map: Map<number, CacheUserInfo> = new Map()
|
||||
private _session_map: Map<string, CacheSessionInfo> = new Map()
|
||||
|
||||
add(user: User, token: string, issued: Date, expires: Date): SessionData {
|
||||
this._id_map.set(user.id, {
|
||||
user: user,
|
||||
last_update: new Date()
|
||||
})
|
||||
this._session_map.set(token, {
|
||||
user_id: user.id,
|
||||
issued: issued,
|
||||
expires: expires
|
||||
})
|
||||
|
||||
return {
|
||||
user: user,
|
||||
token: token,
|
||||
issued: issued,
|
||||
expires: expires
|
||||
}
|
||||
}
|
||||
|
||||
invalidate_session(session: SessionData) {
|
||||
this._session_map.delete(session.token)
|
||||
}
|
||||
|
||||
get_session(token: string): SessionData|null {
|
||||
const session_info = this._session_map.get(token)
|
||||
if (!session_info) return null
|
||||
|
||||
const user_info = this._id_map.get(session_info.user_id)
|
||||
if (!user_info) return null
|
||||
|
||||
return {
|
||||
user: user_info.user,
|
||||
token: token,
|
||||
issued: session_info.issued,
|
||||
expires: session_info.expires
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class UserMgmt {
|
||||
_cache: Cache = new Cache()
|
||||
|
||||
async register(email: string, password: string, display_name: string): Promise<User|null> {
|
||||
if (email.length == 0 || password.length == 0 || display_name.length == 0) {
|
||||
throw new ArgumentError("No field may be empty")
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: email,
|
||||
password_hash: await Bun.password.hash(password, "argon2id"),
|
||||
display_name: display_name
|
||||
}
|
||||
})
|
||||
|
||||
Log.info(Log.module.USER, `Created user with id ${user.id}`)
|
||||
|
||||
return user;
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code == "P2002") {
|
||||
const duplicates = Array.isArray(e.meta?.target) ? e.meta.target as Array<string> : ["unknown"]
|
||||
throw new DuplicateError(duplicates, "Already exists")
|
||||
}
|
||||
|
||||
Log.error(Log.module.DATABASE, `Failed to create User in database! Error: ${JSON.stringify(e)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async login(email: string, password: string): Promise<SessionData|null> {
|
||||
const user = await db.user.findUnique({
|
||||
where: {
|
||||
email: email
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
// throw of timing attacks
|
||||
await Bun.password.verify("a", "$argon2id$v=19$m=16,t=2,p=1$ZHB6Zjd4NXV6RXZBZk9wRg$QaYjeAGLon+x3c5I1KB7UQ")
|
||||
return null
|
||||
}
|
||||
|
||||
if (!await Bun.password.verify(password, user.password_hash)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const session_info = this._generate_session_for_user(user)
|
||||
|
||||
return session_info
|
||||
}
|
||||
|
||||
async session_login(token: string): Promise<SessionData|null> {
|
||||
const session = this._cache.get_session(token)
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
if (session?.expires.getTime() < Date.now()) {
|
||||
return null
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
async refresh_session(token: string): Promise<SessionData|null> {
|
||||
const session = await this.session_login(token)
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
const new_session = this._generate_session_for_user(session.user)
|
||||
this._cache.invalidate_session(session)
|
||||
|
||||
return new_session
|
||||
}
|
||||
|
||||
private _generate_session_for_user(user: User): SessionData {
|
||||
const token = Crypto.randomBytes(32).toBase64()
|
||||
const session_info = this._cache.add(user, token, new Date(), new Date(Date.now() + Config.session_timeout))
|
||||
|
||||
return session_info
|
||||
}
|
||||
}
|
||||
|
||||
class _LOGIN_BYPASS_Mgmt extends UserMgmt {
|
||||
global_session: SessionData | null = null
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
db.user.findFirst().then((user) => {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
this.global_session = {
|
||||
token: "",
|
||||
user: user,
|
||||
expires: new Date(8640000000000000), // Max Time
|
||||
issued: new Date()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async login(): Promise<SessionData | null> {
|
||||
if (!this.global_session) {
|
||||
const user = await db.user.findFirst()
|
||||
if (user) {
|
||||
this.global_session = {
|
||||
token: "",
|
||||
user: user,
|
||||
expires: new Date(8640000000000000), // Max Time
|
||||
issued: new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.global_session
|
||||
}
|
||||
|
||||
async session_login(): Promise<SessionData | null> {
|
||||
return this.login()
|
||||
}
|
||||
}
|
||||
|
||||
const _manager = (Config.is_debug && Config.bypass_login) ? new _LOGIN_BYPASS_Mgmt() : new UserMgmt()
|
||||
|
||||
export default _manager
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
function check_email_format(email: string) {
|
||||
const trimmed = email.trim()
|
||||
|
||||
if (trimmed.length > 254) return false
|
||||
|
||||
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$/;
|
||||
return emailRegex.test(trimmed)
|
||||
}
|
||||
|
||||
export default {
|
||||
check_email_format,
|
||||
}
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
<script lang="ts">
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
{@render children?.()}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import type { Actions, PageServerLoad } from "./$types"
|
||||
|
||||
import { fail } from "@sveltejs/kit"
|
||||
|
||||
export const load: PageServerLoad = async ({ locals, fetch }) => {
|
||||
|
||||
const response = await fetch("/api/users/tasks", {
|
||||
method: "GET"
|
||||
})
|
||||
|
||||
return { tasks: await response.json() }
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
edit: async ({ request, fetch }) => {
|
||||
|
||||
const data = await request.formData()
|
||||
|
||||
const response = await fetch("/api/users/tasks/create", {
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
|
||||
return { }
|
||||
}
|
||||
} satisfies Actions
|
||||
|
|
@ -1,2 +1,100 @@
|
|||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms'
|
||||
import { pushState, replaceState } from '$app/navigation'
|
||||
import { page } from "$app/state"
|
||||
|
||||
import Task from '$lib/components/task.svelte'
|
||||
import TaskOverlay from '$lib/components/task-overlay.svelte'
|
||||
|
||||
const { data } = $props()
|
||||
$inspect(data)
|
||||
|
||||
let open = $state(false);
|
||||
let lastOpen = false
|
||||
let selectedTask = $state(null)
|
||||
|
||||
$effect(() => {
|
||||
open
|
||||
selectedTask
|
||||
|
||||
if (open != lastOpen) {
|
||||
lastOpen = open
|
||||
if (open) {
|
||||
//pushState(`/tasks/${data.tasks[0].id}`, {})
|
||||
} else {
|
||||
if (page.route.id !== "/") {
|
||||
history.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<form id="form_edit_task" method="POST"
|
||||
action="?/edit"
|
||||
use:enhance={({ }) => {
|
||||
|
||||
return async ({ result }) => {
|
||||
|
||||
if (result.type === 'success') {
|
||||
open = false
|
||||
} else /*if (result.type === 'error' || result.type === 'failure')*/ {
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
}
|
||||
}}
|
||||
></form>
|
||||
<div class="container">
|
||||
<h1>Tasks</h1>
|
||||
|
||||
<div class="create_task">
|
||||
<input form="form_edit_task" name="content" placeholder="Create task here" onclick={() => { open = true }}/>
|
||||
</div>
|
||||
|
||||
{#each data.tasks as task}
|
||||
<Task task={task} --width="75%" />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if open}
|
||||
<TaskOverlay task={selectedTask} bind:open={open} form="form_edit_task" />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.create_task {
|
||||
width: 75%;
|
||||
padding: 5px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 25px auto 11ch;
|
||||
grid-template-areas:
|
||||
'. headerArea .';
|
||||
|
||||
column-gap: 10px;
|
||||
|
||||
align-content: stretch;
|
||||
}
|
||||
|
||||
.create_task input {
|
||||
grid-area: headerArea;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import type { RequestHandler } from "./$types"
|
||||
|
||||
import { error, json, text } from "@sveltejs/kit"
|
||||
|
||||
import UserMgmt from "$lib/server/usermgmt"
|
||||
|
||||
import { LoginResponseCause } from "$lib/errors"
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
|
||||
const data = await request.formData()
|
||||
|
||||
const email = data.get("email")
|
||||
const password = data.get("password")
|
||||
|
||||
if (data.keys.length > 2 || !(typeof email === "string" && typeof password === "string")) {
|
||||
return error(400, { cause: LoginResponseCause.MalformedRequest, message: "Invalid request" })
|
||||
}
|
||||
|
||||
if (email.length == 0) {
|
||||
return error(400, { cause: LoginResponseCause.EmailLength, message: "Email must be provided" })
|
||||
}
|
||||
if (password.length == 0) {
|
||||
return error(400, { cause: LoginResponseCause.PasswordLength, message: "Password must be provided" })
|
||||
}
|
||||
|
||||
const session = await UserMgmt.login(email, password)
|
||||
if (!session) {
|
||||
return error(401, { message: "Invalid username or password" })
|
||||
}
|
||||
|
||||
return json({ token: session.token, expires: session.expires })
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import type { RequestHandler } from "./$types"
|
||||
|
||||
import { error, json } from "@sveltejs/kit"
|
||||
|
||||
import UserMgmt from "$lib/server/usermgmt"
|
||||
import { RefreshResponseCause } from "$lib/errors"
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
|
||||
const data = await request.json()
|
||||
|
||||
const token = data["token"]
|
||||
if (!token || typeof token !== "string") {
|
||||
return error(400, { cause: RefreshResponseCause.MalformedRequest, message: "token must be provided as string." })
|
||||
}
|
||||
|
||||
const new_session = await UserMgmt.refresh_session(token)
|
||||
|
||||
if (!new_session) {
|
||||
return error(401, { cause: RefreshResponseCause.InvalidSession, message: "No session for token" })
|
||||
}
|
||||
|
||||
return json({
|
||||
token: new_session.token,
|
||||
expires: new_session.expires
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import type { RequestHandler } from "./$types"
|
||||
|
||||
import { json, error } from "@sveltejs/kit"
|
||||
|
||||
import UserMgmt from "$lib/server/usermgmt"
|
||||
|
||||
import Util from "$lib/util"
|
||||
import { DuplicateError, RegisterResponseCause } from "$lib/errors"
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
|
||||
const data = await request.formData()
|
||||
|
||||
const email = data.get("email")
|
||||
const password = data.get("password")
|
||||
const display_name = data.get("display_name")
|
||||
|
||||
if (data.keys.length > 3 || !(typeof email === "string" && typeof password === "string" && typeof display_name === "string")) {
|
||||
return error(400, { cause: RegisterResponseCause.MalformedRequest, message: "Invalid field arguments" })
|
||||
}
|
||||
if (email.length == 0) {
|
||||
return error(400, { cause: RegisterResponseCause.EmailLength, message: "email must not be empty" })
|
||||
}
|
||||
if (password.length == 0) {
|
||||
return error(400, { cause: RegisterResponseCause.PasswordLength, message: "password must not be empty" })
|
||||
}
|
||||
if (display_name.length == 0) {
|
||||
return error(400, { cause: RegisterResponseCause.DisplayNameLength, message: "Display name must not be empty" })
|
||||
}
|
||||
|
||||
if (!Util.check_email_format(email)) {
|
||||
return error(400, { cause: RegisterResponseCause.EmailFormat, message: "invalid email format" })
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await UserMgmt.register(email, password, display_name)
|
||||
} catch (e) {
|
||||
if (e instanceof DuplicateError) {
|
||||
if (e.fields.includes("email")) {
|
||||
return error(409, { cause: RegisterResponseCause.EmailDuplicate, message: "email already in use" })
|
||||
}
|
||||
}
|
||||
|
||||
return error(500, { cause: RegisterResponseCause.Server, message: "Server failed to create user"})
|
||||
}
|
||||
|
||||
return json({})
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import type { RequestHandler } from "./$types";
|
||||
|
||||
import { json, error } from "@sveltejs/kit"
|
||||
|
||||
import { Error401Cause } from "$lib/errors";
|
||||
|
||||
import db from "$lib/server/database"
|
||||
|
||||
enum StatusFilterValues {
|
||||
all,
|
||||
open,
|
||||
done,
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ request, locals, url }) => {
|
||||
|
||||
const filter_param = url.searchParams.get("status")
|
||||
const filter = (filter_param && filter_param in StatusFilterValues)
|
||||
? StatusFilterValues[filter_param as keyof typeof StatusFilterValues]
|
||||
: StatusFilterValues.all
|
||||
|
||||
const tasks = await db.task.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
equals: locals.user.id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return json(tasks)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import type { RequestHandler } from "./$types";
|
||||
|
||||
import { json, error } from "@sveltejs/kit"
|
||||
|
||||
import db from "$lib/server/database"
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
|
||||
const data = await request.formData()
|
||||
|
||||
const content = data.get("content")
|
||||
|
||||
if (!content || typeof content !== "string") {
|
||||
return error(400, { message: "content must be specified" })
|
||||
}
|
||||
|
||||
const task = await db.task.create({
|
||||
data: {
|
||||
content: content,
|
||||
user: {
|
||||
connect: {
|
||||
id: locals.user.id
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return json(task)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import type { Actions } from "./$types"
|
||||
|
||||
import { fail } from "@sveltejs/kit"
|
||||
|
||||
import Log from "$lib/server/log"
|
||||
|
||||
export const actions = {
|
||||
login: async ({ request, fetch, cookies }) => {
|
||||
|
||||
console.log("login")
|
||||
|
||||
const formData = await request.formData()
|
||||
|
||||
const result = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!result.ok) {
|
||||
return fail(401, { email: formData.get("email") ?? "", message: "Benutzername oder Passwort ist falsch." })
|
||||
}
|
||||
|
||||
const body = await (async () => {
|
||||
try {
|
||||
return await result.json()
|
||||
} catch (e) {
|
||||
Log.error(Log.module.PROCESS, `Failed to parse body of login response from endpoint`)
|
||||
return null
|
||||
}
|
||||
})()
|
||||
|
||||
if (!body || !body.token || !body.expires) {
|
||||
return fail(502, "Invalid response from login endpoint")
|
||||
}
|
||||
|
||||
cookies.set("session", body.token, {
|
||||
expires: new Date(body.expires),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
path: "/"
|
||||
})
|
||||
|
||||
return { message: "Erfolg" }
|
||||
}
|
||||
} satisfies Actions;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts">
|
||||
|
||||
import type { PageProps } from "./$types"
|
||||
|
||||
const { form }: PageProps = $props()
|
||||
|
||||
</script>
|
||||
|
||||
<form action="?/login" method="POST" id="form_login"></form>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="grid">
|
||||
<h1>Login</h1>
|
||||
<p class="error_msg">{form?.message}</p>
|
||||
<label for="username">E-Mail: </label><input type="text" id="username" form="form_login" name="email" />
|
||||
<label for="password">Passwort: </label><input type="password" id="password" form="form_login" name="password" />
|
||||
<button type="submit" form="form_login">-></button>
|
||||
</div>
|
||||
|
||||
<p><a href="/login/register">Noch nicht registriert? Klicke hier!</a></p>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
padding-top: 100px;
|
||||
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 10px 50px;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid > h1 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.grid > p {
|
||||
width: 100%;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.grid > button {
|
||||
width: 50%;
|
||||
margin-top: 10px;
|
||||
margin-left: auto;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.error_msg {
|
||||
color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { RegisterResponseCause } from "$lib/errors";
|
||||
import type { Actions } from "./$types";
|
||||
|
||||
import { fail } from "@sveltejs/kit"
|
||||
|
||||
export const actions = {
|
||||
register: async ({ request, fetch }) => {
|
||||
|
||||
console.log("register")
|
||||
|
||||
const result = await fetch("/api/register", {
|
||||
method: "POST",
|
||||
body: await request.formData()
|
||||
})
|
||||
|
||||
const result_body = await result.json()
|
||||
|
||||
if (!result.ok) {
|
||||
if (!result_body.cause) {
|
||||
return fail(500, { message: "Interner Fehler, versuche es erneut." })
|
||||
}
|
||||
|
||||
switch (result_body.cause) {
|
||||
case RegisterResponseCause.MalformedRequest: return fail(400, { message: "Bitte versuche es erneut." })
|
||||
case RegisterResponseCause.EmailLength: return fail(400, { message: "Bitte gib eine Email ein." })
|
||||
case RegisterResponseCause.EmailFormat: return fail(400, { message: "Das Format der Email ist nicht gültig." })
|
||||
case RegisterResponseCause.EmailDuplicate: return fail(409, { message: "Email wird bereits verwendet." })
|
||||
case RegisterResponseCause.DisplayNameLength: return fail(400, { message: "Bitt gib einen Display Namen ein" })
|
||||
case RegisterResponseCause.PasswordLength: return fail(400, { message: "Bitte gib ein Passwort ein." })
|
||||
default:
|
||||
return fail(500, { message: "Ein interner Fehler ist aufgetreten. Bitte versuche es erneut."})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { message: "Erfolg" }
|
||||
}
|
||||
} satisfies Actions
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
|
||||
import type { PageProps } from "./$types"
|
||||
|
||||
const { form }: PageProps = $props()
|
||||
|
||||
</script>
|
||||
|
||||
<form action="?/register" method="POST" id="form_register"></form>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="grid">
|
||||
<h1>Login</h1>
|
||||
<p class="error_msg">{form?.message}</p>
|
||||
<label for="username">E-Mail: </label><input type="text" id="username" form="form_register" name="email" />
|
||||
<label for="password">Passwort: </label><input type="password" id="password" form="form_register" name="password" />
|
||||
<label for="display_name">Anzeigename: </label><input type="text" id="display_name" form="form_register" name="display_name" />
|
||||
<button type="submit" form="form_register">Registrieren</button>
|
||||
</div>
|
||||
|
||||
<p><a href="/login">Schon registriert? Klicke hier!</a></p>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
padding-top: 100px;
|
||||
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 10px 50px;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid > h1 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.grid > p {
|
||||
width: 100%;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.grid > button {
|
||||
width: 50%;
|
||||
margin-top: 10px;
|
||||
margin-left: auto;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.error_msg {
|
||||
color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
body {
|
||||
--primary-bg-color: darkgreen;
|
||||
--primary-text-color: black;
|
||||
|
||||
width: 100%;
|
||||
background-color: var(--primary-bg-color, white);
|
||||
|
||||
}
|
||||
|
||||
* {
|
||||
color: var(--primary-text-color, black);
|
||||
}
|
||||
Loading…
Reference in New Issue