Merge branch 'betterauth-migration'
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
OPENROUTER_API_KEY=
|
OPENROUTER_API_KEY=
|
||||||
AUTH_AUTHENTIK_CLIENT_ID=
|
AUTH_AUTHENTIK_CLIENT_ID=
|
||||||
AUTH_AUTHENTIK_CLIENT_SECRET=notsosupersecret
|
AUTH_AUTHENTIK_CLIENT_SECRET=XXXXXXXXXXXXXXXX
|
||||||
AUTH_AUTHENTIK_ISSUER=https://example.com
|
AUTH_AUTHENTIK_ISSUER=XXXXXXXXXXXXXXXXXXX
|
||||||
|
|
||||||
NEXTAUTH_URL=https://example.com
|
BETTER_AUTH_URL=XXXXXXXXXXXXXXXXXXX
|
||||||
AUTH_SECRET=supersecret
|
BETTER_AUTH_SECRET=XXXXXXXXXXX
|
||||||
NEXTAUTH_SECRET=supersecret
|
DB_URL=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
DB_URL=postgres://<user>:<password>@<host>:<port>/<database>
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -75,3 +75,4 @@ public/workbox-*.js
|
|||||||
/opencode.json
|
/opencode.json
|
||||||
/opencode.json.bak
|
/opencode.json.bak
|
||||||
# END Ruler Generated Files
|
# END Ruler Generated Files
|
||||||
|
/debug.log
|
||||||
|
|||||||
4
FIXME.md
4
FIXME.md
@@ -1,4 +0,0 @@
|
|||||||
# FIXME
|
|
||||||
|
|
||||||
- [ ] minimatch types
|
|
||||||
https://github.com/strapi/strapi/issues/23859
|
|
||||||
59
bun.lock
59
bun.lock
@@ -5,13 +5,13 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "ical-pwa",
|
"name": "ical-pwa",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/drizzle-adapter": "^1.10.0",
|
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"better-auth": "^1.6.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"next": "15.4.10",
|
"next": "15.4.10",
|
||||||
"next-auth": "^5.0.0-beta.29",
|
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
@@ -55,9 +54,23 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||||
|
|
||||||
"@auth/core": ["@auth/core@0.41.1", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^7.0.7" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw=="],
|
"@better-auth/core": ["@better-auth/core@1.6.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-LmdPTyKRDn6iCcXBGlOHOyzpJl1W/3w64zrEbhhHaWmtdpzQWlY8awlWBoDTL9eL4TAusr9dDvwIbMYTvEqaeA=="],
|
||||||
|
|
||||||
"@auth/drizzle-adapter": ["@auth/drizzle-adapter@1.11.1", "", { "dependencies": { "@auth/core": "0.41.1" } }, "sha512-cQTvDZqsyF7RPhDm/B6SvqdVP9EzQhy3oM4Muu7fjjmSYFLbSR203E6dH631ZHSKDn2b4WZkfMnjPDzRsPSAeA=="],
|
"@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.0", "", { "peerDependencies": { "@better-auth/core": "^1.6.0", "@better-auth/utils": "0.4.0", "drizzle-orm": ">=0.41.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-iMgvZlrL4FI63CGaxLqE5rgA2Q9VVmc2fQIP7N5E79nGAEpHtztstHFPlen9RDLRJA4xa3wuyVaPSILylwE+LA=="],
|
||||||
|
|
||||||
|
"@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.0", "", { "peerDependencies": { "@better-auth/core": "^1.6.0", "@better-auth/utils": "0.4.0", "kysely": "^0.27.0 || ^0.28.0" }, "optionalPeers": ["kysely"] }, "sha512-ZLEp2j3jquX7wrPQ7tPOSRAjmMoHhdrsgkuH9Bp/fgNZV7M1eiwAY6fHRGKad6KIldoI+iazMUIm60v11fIHCg=="],
|
||||||
|
|
||||||
|
"@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.0", "", { "peerDependencies": { "@better-auth/core": "^1.6.0", "@better-auth/utils": "0.4.0" } }, "sha512-FbLmz6ujltw8RDUkBzutwIfoV+q9Mu0gLVrfhDAb9INe+jLcaQikiIjFdVwPzpx+bOs6bWTDfylrlI6+Ytxs3Q=="],
|
||||||
|
|
||||||
|
"@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.0", "", { "peerDependencies": { "@better-auth/core": "^1.6.0", "@better-auth/utils": "0.4.0", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-EYZwMpcpoaLRnfhEr+k+MTKS8SKi51TWh1b7bLSy+yHLL0PdbadFsGYZPgzLbZEaq4kUP0asMzXxA+blutjOQQ=="],
|
||||||
|
|
||||||
|
"@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.0", "", { "peerDependencies": { "@better-auth/core": "^1.6.0", "@better-auth/utils": "0.4.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-8x/aqR1NckGiC49P02cxuH0wLzbJXvE/v2NnMEFo6h3uWq4ESYL0jTY9vNlFeVIKDyGSzrbteofzzG+yQv0wAQ=="],
|
||||||
|
|
||||||
|
"@better-auth/telemetry": ["@better-auth/telemetry@1.6.0", "", { "peerDependencies": { "@better-auth/core": "^1.6.0", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-JrJyx1ioswEAh8rB7mVxEFIDLl6AK3W3rtqc2MK6BgvcmKveWJ730Eoi/PNvi0b4tFk4kczmuQITm69uMbnTvQ=="],
|
||||||
|
|
||||||
|
"@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
|
||||||
|
|
||||||
|
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
|
||||||
|
|
||||||
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
||||||
|
|
||||||
@@ -241,6 +254,10 @@
|
|||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg=="],
|
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg=="],
|
||||||
|
|
||||||
|
"@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="],
|
||||||
|
|
||||||
|
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||||
@@ -249,7 +266,9 @@
|
|||||||
|
|
||||||
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
||||||
|
|
||||||
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||||
|
|
||||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||||
|
|
||||||
@@ -321,6 +340,8 @@
|
|||||||
|
|
||||||
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.15.0", "", {}, "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw=="],
|
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.15.0", "", {}, "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw=="],
|
||||||
|
|
||||||
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
|
||||||
@@ -469,6 +490,10 @@
|
|||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
|
"better-auth": ["better-auth@1.6.0", "", { "dependencies": { "@better-auth/core": "1.6.0", "@better-auth/drizzle-adapter": "1.6.0", "@better-auth/kysely-adapter": "1.6.0", "@better-auth/memory-adapter": "1.6.0", "@better-auth/mongo-adapter": "1.6.0", "@better-auth/prisma-adapter": "1.6.0", "@better-auth/telemetry": "1.6.0", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.5", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.14", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-reEK4X37w/X0Wi0ZpNSo6w3j9F2tsA7ebWn2AmWTzkceiatkxcadRg9aK+Mirw2PY56GQqX9dBgqBG6XMNU/Zg=="],
|
||||||
|
|
||||||
|
"better-call": ["better-call@1.3.5", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="],
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||||
|
|
||||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||||
@@ -523,6 +548,8 @@
|
|||||||
|
|
||||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||||
|
|
||||||
|
"defu": ["defu@6.1.6", "", {}, "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug=="],
|
||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||||
@@ -737,7 +764,7 @@
|
|||||||
|
|
||||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||||
|
|
||||||
"jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
"jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
|
||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
@@ -755,6 +782,8 @@
|
|||||||
|
|
||||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||||
|
|
||||||
|
"kysely": ["kysely@0.28.15", "", {}, "sha512-r2clcf7HLWvDXaVUEvQymXJY4i3bSOIV3xsL/Upy3ZfSv5HeKsk9tsqbBptLvth5qHEIhxeHTA2jNLyQABkLBA=="],
|
||||||
|
|
||||||
"language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
|
"language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
|
||||||
|
|
||||||
"language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="],
|
"language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="],
|
||||||
@@ -809,18 +838,16 @@
|
|||||||
|
|
||||||
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
||||||
|
|
||||||
|
"nanostores": ["nanostores@1.2.0", "", {}, "sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg=="],
|
||||||
|
|
||||||
"napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="],
|
"napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="],
|
||||||
|
|
||||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
"next": ["next@15.4.10", "", { "dependencies": { "@next/env": "15.4.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.8", "@next/swc-darwin-x64": "15.4.8", "@next/swc-linux-arm64-gnu": "15.4.8", "@next/swc-linux-arm64-musl": "15.4.8", "@next/swc-linux-x64-gnu": "15.4.8", "@next/swc-linux-x64-musl": "15.4.8", "@next/swc-win32-arm64-msvc": "15.4.8", "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ=="],
|
"next": ["next@15.4.10", "", { "dependencies": { "@next/env": "15.4.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.8", "@next/swc-darwin-x64": "15.4.8", "@next/swc-linux-arm64-gnu": "15.4.8", "@next/swc-linux-arm64-musl": "15.4.8", "@next/swc-linux-x64-gnu": "15.4.8", "@next/swc-linux-x64-musl": "15.4.8", "@next/swc-win32-arm64-msvc": "15.4.8", "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ=="],
|
||||||
|
|
||||||
"next-auth": ["next-auth@5.0.0-beta.30", "", { "dependencies": { "@auth/core": "0.41.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", "nodemailer": "^7.0.7", "react": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg=="],
|
|
||||||
|
|
||||||
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||||
|
|
||||||
"oauth4webapi": ["oauth4webapi@3.8.3", "", {}, "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw=="],
|
|
||||||
|
|
||||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||||
@@ -887,10 +914,6 @@
|
|||||||
|
|
||||||
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||||
|
|
||||||
"preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="],
|
|
||||||
|
|
||||||
"preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="],
|
|
||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
@@ -925,6 +948,8 @@
|
|||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
|
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
||||||
|
|
||||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||||
|
|
||||||
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
||||||
@@ -937,6 +962,8 @@
|
|||||||
|
|
||||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
|
||||||
|
|
||||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||||
|
|
||||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||||
@@ -1053,6 +1080,8 @@
|
|||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
|
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
@@ -1103,8 +1132,6 @@
|
|||||||
|
|
||||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||||
|
|
||||||
"next-auth/@auth/core": ["@auth/core@0.41.0", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ=="],
|
|
||||||
|
|
||||||
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||||
|
|||||||
20
compose.dev.yml
Normal file
20
compose.dev.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17
|
||||||
|
container_name: local-cal-postgres
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: localcal
|
||||||
|
POSTGRES_PASSWORD: localcal
|
||||||
|
POSTGRES_DB: localcal
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U localcal"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
@@ -1,56 +1 @@
|
|||||||
-- Current sql file was generated after introspecting the database
|
-- Baseline snapshot: tables already exist in the database
|
||||||
-- If you want to run this migration please uncomment this code before executing migrations
|
|
||||||
/*
|
|
||||||
CREATE TABLE "session" (
|
|
||||||
"sessionToken" text PRIMARY KEY NOT NULL,
|
|
||||||
"userId" text NOT NULL,
|
|
||||||
"expires" timestamp NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "user" (
|
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
|
||||||
"name" text,
|
|
||||||
"email" text NOT NULL,
|
|
||||||
"emailVerified" timestamp,
|
|
||||||
"image" text
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "verificationToken" (
|
|
||||||
"identifier" text NOT NULL,
|
|
||||||
"token" text NOT NULL,
|
|
||||||
"expires" timestamp NOT NULL,
|
|
||||||
CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "authenticator" (
|
|
||||||
"credentialID" text NOT NULL,
|
|
||||||
"userId" text NOT NULL,
|
|
||||||
"providerAccountId" text NOT NULL,
|
|
||||||
"credentialPublicKey" text NOT NULL,
|
|
||||||
"counter" integer NOT NULL,
|
|
||||||
"credentialDeviceType" text NOT NULL,
|
|
||||||
"credentialBackedUp" boolean NOT NULL,
|
|
||||||
"transports" text,
|
|
||||||
CONSTRAINT "authenticator_userId_credentialID_pk" PRIMARY KEY("credentialID","userId"),
|
|
||||||
CONSTRAINT "authenticator_credentialID_unique" UNIQUE("credentialID")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "account" (
|
|
||||||
"userId" text NOT NULL,
|
|
||||||
"type" text NOT NULL,
|
|
||||||
"provider" text NOT NULL,
|
|
||||||
"providerAccountId" text NOT NULL,
|
|
||||||
"refresh_token" text,
|
|
||||||
"access_token" text,
|
|
||||||
"expires_at" text,
|
|
||||||
"token_type" text,
|
|
||||||
"scope" text,
|
|
||||||
"id_token" text,
|
|
||||||
"session_state" text,
|
|
||||||
CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "authenticator" ADD CONSTRAINT "authenticator_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
|
||||||
*/
|
|
||||||
|
|||||||
50
drizzle/0001_great_sentry.sql
Normal file
50
drizzle/0001_great_sentry.sql
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Ensure pgcrypto extension is available for UUID generation
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
|
|
||||||
|
ALTER TABLE "authenticator" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||||
|
DROP TABLE "authenticator" CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "verificationToken" RENAME TO "verification";--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" RENAME COLUMN "expires" TO "expiresAt";--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" RENAME COLUMN "sessionToken" TO "token";--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" RENAME COLUMN "token" TO "value";--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" RENAME COLUMN "expires" TO "expiresAt";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" RENAME COLUMN "providerAccountId" TO "accountId";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" RENAME COLUMN "provider" TO "providerId";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" RENAME COLUMN "access_token" TO "accessToken";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" RENAME COLUMN "refresh_token" TO "refreshToken";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" RENAME COLUMN "expires_at" TO "accessTokenExpiresAt";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" RENAME COLUMN "id_token" TO "idToken";--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" DROP CONSTRAINT "verificationToken_identifier_token_pk";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" DROP CONSTRAINT "account_provider_providerAccountId_pk";--> statement-breakpoint
|
||||||
|
-- Mark OAuth users as verified before type conversion
|
||||||
|
UPDATE "user" SET "emailVerified" = NOW() WHERE "emailVerified" IS NULL AND id IN (SELECT "userId" FROM account);
|
||||||
|
ALTER TABLE "user" ALTER COLUMN "emailVerified" SET DATA TYPE boolean USING ("emailVerified" IS NOT NULL);--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" DROP CONSTRAINT "session_pkey";--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD COLUMN "id" text PRIMARY KEY NOT NULL DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD COLUMN "createdAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD COLUMN "updatedAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD COLUMN "ipAddress" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD COLUMN "userAgent" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "user" ADD COLUMN "createdAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "user" ADD COLUMN "updatedAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" ADD COLUMN "id" text PRIMARY KEY NOT NULL DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" ADD COLUMN "createdAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" ADD COLUMN "updatedAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD COLUMN "id" text PRIMARY KEY NOT NULL DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD COLUMN "refreshTokenExpiresAt" timestamp;--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD COLUMN "password" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD COLUMN "createdAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD COLUMN "updatedAt" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" DROP COLUMN "type";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" DROP COLUMN "token_type";--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" DROP COLUMN "session_state";--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD CONSTRAINT "session_token_unique" UNIQUE("token");--> statement-breakpoint
|
||||||
|
ALTER TABLE "user" ADD CONSTRAINT "user_email_unique" UNIQUE("email");
|
||||||
|
-- Drop the uuid defaults so future inserts rely on app-provided values
|
||||||
|
ALTER TABLE "session" ALTER COLUMN "id" DROP DEFAULT;--> statement-breakpoint
|
||||||
|
ALTER TABLE "verification" ALTER COLUMN "id" DROP DEFAULT;--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ALTER COLUMN "id" DROP DEFAULT;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
328
drizzle/meta/0001_snapshot.json
Normal file
328
drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
{
|
||||||
|
"id": "69e7666b-0b8c-4658-906d-993870a0b539",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"accountId": {
|
||||||
|
"name": "accountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"providerId": {
|
||||||
|
"name": "providerId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"accessToken": {
|
||||||
|
"name": "accessToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refreshToken": {
|
||||||
|
"name": "refreshToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"accessTokenExpiresAt": {
|
||||||
|
"name": "accessTokenExpiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refreshTokenExpiresAt": {
|
||||||
|
"name": "refreshTokenExpiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"idToken": {
|
||||||
|
"name": "idToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_userId_user_id_fk": {
|
||||||
|
"name": "account_userId_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.session": {
|
||||||
|
"name": "session",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"ipAddress": {
|
||||||
|
"name": "ipAddress",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"userAgent": {
|
||||||
|
"name": "userAgent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_userId_user_id_fk": {
|
||||||
|
"name": "session_userId_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"name": "emailVerified",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
"when": 1755586325384,
|
"when": 1755586325384,
|
||||||
"tag": "0000_loose_catseye",
|
"tag": "0000_loose_catseye",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1775526538601,
|
||||||
|
"tag": "0001_great_sentry",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm/relations";
|
import { relations } from "drizzle-orm/relations";
|
||||||
import { user, session, authenticator, account } from "./schema";
|
import { user, session, account } from "./schema";
|
||||||
|
|
||||||
export const sessionRelations = relations(session, ({one}) => ({
|
export const sessionRelations = relations(session, ({one}) => ({
|
||||||
user: one(user, {
|
user: one(user, {
|
||||||
@@ -10,17 +10,9 @@ export const sessionRelations = relations(session, ({one}) => ({
|
|||||||
|
|
||||||
export const userRelations = relations(user, ({many}) => ({
|
export const userRelations = relations(user, ({many}) => ({
|
||||||
sessions: many(session),
|
sessions: many(session),
|
||||||
authenticators: many(authenticator),
|
|
||||||
accounts: many(account),
|
accounts: many(account),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const authenticatorRelations = relations(authenticator, ({one}) => ({
|
|
||||||
user: one(user, {
|
|
||||||
fields: [authenticator.userId],
|
|
||||||
references: [user.id]
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const accountRelations = relations(account, ({one}) => ({
|
export const accountRelations = relations(account, ({one}) => ({
|
||||||
user: one(user, {
|
user: one(user, {
|
||||||
fields: [account.userId],
|
fields: [account.userId],
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/drizzle-adapter": "^1.10.0",
|
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"better-auth": "^1.6.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"next": "15.4.10",
|
"next": "15.4.10",
|
||||||
"next-auth": "^5.0.0-beta.29",
|
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const session = await auth();
|
const session = await auth.api.getSession({
|
||||||
|
headers: await headers(),
|
||||||
|
});
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -13,6 +16,20 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
const { prompt } = await request.json();
|
const { prompt } = await request.json();
|
||||||
|
|
||||||
|
// Validate prompt input
|
||||||
|
if (!prompt || typeof prompt !== "string" || prompt.trim().length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Prompt is required and must be a non-empty string" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (prompt.length > 2000) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Prompt must be less than 2000 characters" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const systemPrompt = `
|
const systemPrompt = `
|
||||||
You are an assistant that converts natural language into an ARRAY of calendar events.
|
You are an assistant that converts natural language into an ARRAY of calendar events.
|
||||||
TypeScript type:
|
TypeScript type:
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: await headers(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Authentication required" },
|
||||||
|
{ status: 401 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { events } = await request.json();
|
const { events } = await request.json();
|
||||||
|
|
||||||
@@ -10,6 +23,12 @@ export async function POST(request: Request) {
|
|||||||
{ status: 400 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (events.length > 100) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Events array must contain 100 or fewer items" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
4
src/app/api/auth/[...all]/route.ts
Normal file
4
src/app/api/auth/[...all]/route.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { auth } from "@/auth";
|
||||||
|
import { toNextJsHandler } from "better-auth/next-js";
|
||||||
|
|
||||||
|
export const { GET, POST } = toNextJsHandler(auth);
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import { handlers } from "@/auth";
|
|
||||||
export const { GET, POST } = handlers;
|
|
||||||
@@ -9,9 +9,14 @@ import { Suspense } from "react"
|
|||||||
function Search() {
|
function Search() {
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const errorMessage = searchParams.get('error')
|
const errorMessage = searchParams.get('error')
|
||||||
|
|
||||||
|
// Sanitize error message to prevent XSS
|
||||||
|
const sanitizedError = errorMessage
|
||||||
|
? errorMessage.replace(/[<>]/g, '')
|
||||||
|
: 'An authentication error occurred'
|
||||||
|
|
||||||
return (<div className="text-center p-3 bg-background rounded-lg">
|
return (<div className="text-center p-3 bg-background rounded-lg">
|
||||||
{errorMessage}
|
{sanitizedError}
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,44 @@
|
|||||||
import { signIn, auth } from "@/auth"
|
"use client"
|
||||||
|
|
||||||
|
import { signIn, useSession } from "@/lib/auth-client"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { redirect } from "next/navigation"
|
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
export default async function SignInPage() {
|
export default function SignInPage() {
|
||||||
const session = await auth()
|
const { data: session, isPending } = useSession()
|
||||||
|
const router = useRouter()
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session?.user) {
|
||||||
|
router.push("/")
|
||||||
|
}
|
||||||
|
}, [session, router])
|
||||||
|
|
||||||
|
const handleSignIn = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
await signIn.oauth2({
|
||||||
|
providerId: "authentik",
|
||||||
|
callbackURL: "/",
|
||||||
|
})
|
||||||
|
} catch (_error) {
|
||||||
|
toast.error("Failed to sign in. Please try again.")
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// If already signed in, redirect to home
|
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
redirect("/")
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -22,16 +51,9 @@ export default async function SignInPage() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<form
|
<Button onClick={handleSignIn} className="w-full" size="lg" disabled={isLoading}>
|
||||||
action={async () => {
|
{isLoading ? "Signing in..." : "Continue with Authentik"}
|
||||||
"use server"
|
</Button>
|
||||||
await signIn("authentik", { redirectTo: "/" })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="submit" className="w-full" size="lg">
|
|
||||||
Continue with Authentik
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link href="/" className="text-sm text-muted-foreground hover:underline">
|
<Link href="/" className="text-sm text-muted-foreground hover:underline">
|
||||||
|
|||||||
@@ -1,14 +1,29 @@
|
|||||||
import { signOut, auth } from "@/auth"
|
"use client"
|
||||||
|
|
||||||
|
import { signOut, useSession } from "@/lib/auth-client"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { redirect } from "next/navigation"
|
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
export default async function SignOutPage() {
|
export default function SignOutPage() {
|
||||||
const session = await auth()
|
const { data: session, isPending } = useSession()
|
||||||
|
const router = useRouter()
|
||||||
if (!session) {
|
|
||||||
redirect("/")
|
useEffect(() => {
|
||||||
|
if (!session?.user) {
|
||||||
|
router.push("/")
|
||||||
|
}
|
||||||
|
}, [session, router])
|
||||||
|
|
||||||
|
const handleSignOut = async () => {
|
||||||
|
await signOut()
|
||||||
|
router.push("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPending || !session?.user) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -25,19 +40,12 @@ export default async function SignOutPage() {
|
|||||||
<div className="text-sm text-muted-foreground">Currently signed in as</div>
|
<div className="text-sm text-muted-foreground">Currently signed in as</div>
|
||||||
<div className="font-medium">{session.user?.name || session.user?.email}</div>
|
<div className="font-medium">{session.user?.name || session.user?.email}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<form
|
<Button onClick={handleSignOut} variant="destructive" className="w-full">
|
||||||
action={async () => {
|
Sign Out
|
||||||
"use server"
|
</Button>
|
||||||
await signOut({ redirectTo: "/" })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="submit" variant="destructive" className="w-full">
|
|
||||||
Sign Out
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link href="/">Cancel</Link>
|
<Link href="/">Cancel</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -46,4 +54,4 @@ export default async function SignOutPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import "./globals.css";
|
|||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
import { ModeToggle } from "@/components/mode-toggle";
|
import { ModeToggle } from "@/components/mode-toggle";
|
||||||
import SignIn from "@/components/sign-in";
|
import SignIn from "@/components/sign-in";
|
||||||
import AuthSessionProvider from "@/components/SessionProvider";
|
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
@@ -28,28 +27,26 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geist.variable} antialiased min-h-screen flex flex-col dark:text-gray-300 --color-background`}
|
className={`${geist.variable} antialiased min-h-screen flex flex-col dark:text-gray-300 --color-background`}
|
||||||
>
|
>
|
||||||
<AuthSessionProvider>
|
<ThemeProvider
|
||||||
<ThemeProvider
|
attribute="class"
|
||||||
attribute="class"
|
defaultTheme="system"
|
||||||
defaultTheme="system"
|
enableSystem
|
||||||
enableSystem
|
disableTransitionOnChange
|
||||||
disableTransitionOnChange
|
>
|
||||||
>
|
<header className="dark:text-white text-gray-900 px-4 py-3 font-bold flex justify-between items-center-safe">
|
||||||
<header className="dark:text-white text-gray-900 px-4 py-3 font-bold flex justify-between items-center-safe">
|
<Link href={"/"}>
|
||||||
<Link href={"/"}>
|
<p className={`${magra.variable}`}>
|
||||||
<p className={`${magra.variable}`}>
|
{metadata.title as string || "iCal PWA"}
|
||||||
{metadata.title as string || "iCal PWA"}
|
</p>
|
||||||
</p>
|
</Link>
|
||||||
</Link>
|
<div className="flex flex-row gap-2">
|
||||||
<div className="flex flex-row gap-2">
|
<SignIn />
|
||||||
<SignIn />
|
<ModeToggle />
|
||||||
<ModeToggle />
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</header>
|
<main className="flex-1 p-4">{children}</main>
|
||||||
<main className="flex-1 p-4">{children}</main>
|
<Toaster closeButton richColors />
|
||||||
<Toaster closeButton richColors />
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
|
||||||
</AuthSessionProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from '@/lib/auth-client'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import { saveEvent as addEvent, deleteEvent, getEvents as getAllEvents, clearEvents, updateEvent } from '@/lib/events-db'
|
import { saveEvent as addEvent, deleteEvent, getEvents as getAllEvents, clearEvents, updateEvent } from '@/lib/events-db'
|
||||||
@@ -44,7 +44,7 @@ export default function HomePage() {
|
|||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const { data: session, status } = useSession()
|
const { data: session, isPending } = useSession()
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setTitle('')
|
setTitle('')
|
||||||
@@ -256,8 +256,8 @@ export default function HomePage() {
|
|||||||
onImport={handleImport}
|
onImport={handleImport}
|
||||||
>
|
>
|
||||||
<AIToolbar
|
<AIToolbar
|
||||||
session={session}
|
isAuthenticated={!!session?.user}
|
||||||
status={status}
|
isPending={isPending}
|
||||||
aiPrompt={aiPrompt}
|
aiPrompt={aiPrompt}
|
||||||
setAiPrompt={setAiPrompt}
|
setAiPrompt={setAiPrompt}
|
||||||
aiLoading={aiLoading}
|
aiLoading={aiLoading}
|
||||||
|
|||||||
72
src/auth.ts
72
src/auth.ts
@@ -1,35 +1,45 @@
|
|||||||
import NextAuth, { NextAuthConfig, NextAuthResult } from "next-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import Authentik from "next-auth/providers/authentik";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import type { Provider } from "next-auth/providers";
|
import { genericOAuth } from "better-auth/plugins";
|
||||||
import { DrizzleAdapter } from "@auth/drizzle-adapter";
|
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
|
import * as schema from "@/db/schema";
|
||||||
|
|
||||||
const providers: Provider[] = [
|
// Validate required environment variables
|
||||||
Authentik({
|
if (!process.env.BETTER_AUTH_SECRET) {
|
||||||
clientId: process.env.AUTH_AUTHENTIK_CLIENT_ID,
|
throw new Error("BETTER_AUTH_SECRET is required");
|
||||||
clientSecret: process.env.AUTH_AUTHENTIK_CLIENT_SECRET,
|
}
|
||||||
issuer: process.env.AUTH_AUTHENTIK_ISSUER,
|
if (!process.env.BETTER_AUTH_URL) {
|
||||||
|
throw new Error("BETTER_AUTH_URL is required");
|
||||||
|
}
|
||||||
|
if (!process.env.AUTH_AUTHENTIK_CLIENT_ID) {
|
||||||
|
throw new Error("AUTH_AUTHENTIK_CLIENT_ID is required");
|
||||||
|
}
|
||||||
|
if (!process.env.AUTH_AUTHENTIK_CLIENT_SECRET) {
|
||||||
|
throw new Error("AUTH_AUTHENTIK_CLIENT_SECRET is required");
|
||||||
|
}
|
||||||
|
if (!process.env.AUTH_AUTHENTIK_ISSUER) {
|
||||||
|
throw new Error("AUTH_AUTHENTIK_ISSUER is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
secret: process.env.BETTER_AUTH_SECRET,
|
||||||
|
baseURL: process.env.BETTER_AUTH_URL,
|
||||||
|
trustedOrigins: [process.env.BETTER_AUTH_URL],
|
||||||
|
database: drizzleAdapter(db, {
|
||||||
|
provider: "pg",
|
||||||
|
schema,
|
||||||
}),
|
}),
|
||||||
];
|
plugins: [
|
||||||
|
genericOAuth({
|
||||||
export const providerMap = providers.map((provider) => {
|
config: [
|
||||||
if (typeof provider === "function") {
|
{
|
||||||
const providerData = provider();
|
providerId: "authentik",
|
||||||
return { id: providerData.id, name: providerData.name };
|
clientId: process.env.AUTH_AUTHENTIK_CLIENT_ID,
|
||||||
} else {
|
clientSecret: process.env.AUTH_AUTHENTIK_CLIENT_SECRET,
|
||||||
return { id: provider.id, name: provider.name };
|
discoveryUrl: `${process.env.AUTH_AUTHENTIK_ISSUER}/.well-known/openid-configuration`,
|
||||||
}
|
scopes: ["openid", "email", "profile"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const config = {
|
|
||||||
adapter: DrizzleAdapter(db),
|
|
||||||
providers,
|
|
||||||
pages: {
|
|
||||||
signIn: "/auth/signin",
|
|
||||||
signOut: "/auth/signout",
|
|
||||||
error: "/auth/error",
|
|
||||||
},
|
|
||||||
trustHost: true,
|
|
||||||
} satisfies NextAuthConfig;
|
|
||||||
export const { handlers, signIn, signOut, auth }: NextAuthResult =
|
|
||||||
NextAuth(config);
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { SessionProvider } from "next-auth/react"
|
|
||||||
import { ReactNode } from "react"
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AuthSessionProvider({ children }: Props) {
|
|
||||||
return <SessionProvider>{children}</SessionProvider>
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
import { Card } from '@/components/ui/card'
|
import { Card } from '@/components/ui/card'
|
||||||
import { Session } from 'next-auth'
|
|
||||||
|
|
||||||
interface AIToolbarProps {
|
interface AIToolbarProps {
|
||||||
session: Session | null
|
isAuthenticated: boolean
|
||||||
status: 'loading' | 'authenticated' | 'unauthenticated'
|
isPending: boolean
|
||||||
aiPrompt: string
|
aiPrompt: string
|
||||||
setAiPrompt: (prompt: string) => void
|
setAiPrompt: (prompt: string) => void
|
||||||
aiLoading: boolean
|
aiLoading: boolean
|
||||||
@@ -16,8 +15,8 @@ interface AIToolbarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AIToolbar = ({
|
export const AIToolbar = ({
|
||||||
session,
|
isAuthenticated,
|
||||||
status,
|
isPending,
|
||||||
aiPrompt,
|
aiPrompt,
|
||||||
setAiPrompt,
|
setAiPrompt,
|
||||||
aiLoading,
|
aiLoading,
|
||||||
@@ -28,17 +27,15 @@ export const AIToolbar = ({
|
|||||||
}: AIToolbarProps) => {
|
}: AIToolbarProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* AI Toolbar */}
|
{isPending ? (
|
||||||
{status === "loading" ? (
|
|
||||||
<div className='mb-4 p-4 text-center animate-pulse bg-muted'>Loading...</div>
|
<div className='mb-4 p-4 text-center animate-pulse bg-muted'>Loading...</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
{session?.user ? (
|
{isAuthenticated ? (
|
||||||
<div className="flex flex-col sm:flex-row gap-4 mb-4 items-start">
|
<div className="flex flex-col sm:flex-row gap-4 mb-4 items-start">
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="wrap-anywhere field-sizing-content resize-none w-full min-h-[2.5rem] max-h-64 overflow-y-auto sm:overflow-y-visible px-3 py-2 scroll-p-8 placeholder:italic"
|
className="wrap-anywhere field-sizing-content resize-none w-full min-h-[2.5rem] max-h-64 overflow-y-auto sm:overflow-y-visible px-3 py-2 scroll-p-8 placeholder:italic"
|
||||||
// Band-aid for scrollbar clipping out of the box
|
|
||||||
style={{ clipPath: "inset(0 round 1rem)" }}
|
style={{ clipPath: "inset(0 round 1rem)" }}
|
||||||
placeholder='Describe event for AI to create'
|
placeholder='Describe event for AI to create'
|
||||||
value={aiPrompt}
|
value={aiPrompt}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { signOut, useSession } from "next-auth/react"
|
import { signOut, useSession } from "@/lib/auth-client"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
export default function SignIn() {
|
export default function SignIn() {
|
||||||
const { data: session, status } = useSession()
|
const { data: session, isPending } = useSession()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
const handleSignOut = async () => {
|
||||||
await signOut({ redirect: false })
|
try {
|
||||||
router.push("/")
|
await signOut()
|
||||||
router.refresh()
|
router.push("/")
|
||||||
|
} catch (_error) {
|
||||||
|
toast.error("Failed to sign out. Please try again.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === "loading") {
|
if (isPending) {
|
||||||
return <div className="h-8 w-16 bg-muted animate-pulse rounded"></div>
|
return <div className="h-8 w-16 bg-muted animate-pulse rounded"></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,47 @@
|
|||||||
import { pgTable, text, timestamp, integer, boolean, primaryKey } from 'drizzle-orm/pg-core';
|
import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
export const users = pgTable('user', {
|
export const user = pgTable('user', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
name: text('name'),
|
name: text('name'),
|
||||||
email: text('email').notNull(),
|
email: text('email').notNull().unique(),
|
||||||
emailVerified: timestamp('emailVerified', { mode: 'string' }),
|
emailVerified: boolean('emailVerified').default(false),
|
||||||
image: text('image'),
|
image: text('image'),
|
||||||
|
createdAt: timestamp('createdAt').defaultNow(),
|
||||||
|
updatedAt: timestamp('updatedAt').defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const accounts = pgTable('account', {
|
export const session = pgTable('session', {
|
||||||
userId: text('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
id: text('id').primaryKey(),
|
||||||
type: text('type').notNull(),
|
expiresAt: timestamp('expiresAt').notNull(),
|
||||||
provider: text('provider').notNull(),
|
token: text('token').notNull().unique(),
|
||||||
providerAccountId: text('providerAccountId').notNull(),
|
createdAt: timestamp('createdAt').defaultNow(),
|
||||||
refresh_token: text('refresh_token'),
|
updatedAt: timestamp('updatedAt').defaultNow(),
|
||||||
access_token: text('access_token'),
|
ipAddress: text('ipAddress'),
|
||||||
expires_at: text('expires_at'),
|
userAgent: text('userAgent'),
|
||||||
token_type: text('token_type'),
|
userId: text('userId').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const account = pgTable('account', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
accountId: text('accountId').notNull(),
|
||||||
|
providerId: text('providerId').notNull(),
|
||||||
|
userId: text('userId').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
||||||
|
accessToken: text('accessToken'),
|
||||||
|
refreshToken: text('refreshToken'),
|
||||||
|
accessTokenExpiresAt: timestamp('accessTokenExpiresAt'),
|
||||||
|
refreshTokenExpiresAt: timestamp('refreshTokenExpiresAt'),
|
||||||
scope: text('scope'),
|
scope: text('scope'),
|
||||||
id_token: text('id_token'),
|
idToken: text('idToken'),
|
||||||
session_state: text('session_state'),
|
password: text('password'),
|
||||||
}, (account) => ({
|
createdAt: timestamp('createdAt').defaultNow(),
|
||||||
compoundKey: primaryKey({ columns: [account.provider, account.providerAccountId] })
|
updatedAt: timestamp('updatedAt').defaultNow(),
|
||||||
}));
|
|
||||||
|
|
||||||
export const sessions = pgTable('session', {
|
|
||||||
sessionToken: text().primaryKey().notNull(),
|
|
||||||
userId: text().notNull().references(() => users.id, { onDelete: 'cascade' }),
|
|
||||||
expires: timestamp({ mode: 'string' }).notNull(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const verificationTokens = pgTable('verificationToken', {
|
export const verification = pgTable('verification', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
identifier: text('identifier').notNull(),
|
identifier: text('identifier').notNull(),
|
||||||
token: text('token').notNull(),
|
value: text('value').notNull(),
|
||||||
expires: timestamp('expires', { mode: 'string' }).notNull(),
|
expiresAt: timestamp('expiresAt').notNull(),
|
||||||
}, (vt) => ({
|
createdAt: timestamp('createdAt').defaultNow(),
|
||||||
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] })
|
updatedAt: timestamp('updatedAt').defaultNow(),
|
||||||
}));
|
});
|
||||||
|
|
||||||
export const authenticators = pgTable('authenticator', {
|
|
||||||
credentialID: text('credentialID').notNull().unique(),
|
|
||||||
userId: text('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
|
||||||
providerAccountId: text('providerAccountId').notNull(),
|
|
||||||
credentialPublicKey: text('credentialPublicKey').notNull(),
|
|
||||||
counter: integer('counter').notNull(),
|
|
||||||
credentialDeviceType: text('credentialDeviceType').notNull(),
|
|
||||||
credentialBackedUp: boolean('credentialBackedUp').notNull(),
|
|
||||||
transports: text('transports'),
|
|
||||||
}, (authenticator) => ({
|
|
||||||
compositePK: primaryKey({
|
|
||||||
columns: [authenticator.credentialID, authenticator.userId],
|
|
||||||
name: "authenticator_userId_credentialID_pk"
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|||||||
8
src/lib/auth-client.ts
Normal file
8
src/lib/auth-client.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { createAuthClient } from "better-auth/react";
|
||||||
|
import { genericOAuthClient } from "better-auth/client/plugins";
|
||||||
|
|
||||||
|
export const authClient = createAuthClient({
|
||||||
|
plugins: [genericOAuthClient()],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { useSession, signIn, signOut } = authClient;
|
||||||
Reference in New Issue
Block a user