From a5aebca54cc38f3f057551614f0803a6e1c0feea Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Sun, 17 Aug 2025 10:50:32 -0400 Subject: [PATCH 01/13] install packages --- bun.lock | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++ 2 files changed, 166 insertions(+) diff --git a/bun.lock b/bun.lock index 4304c63..e1d55a9 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,8 @@ "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dotenv": "^17.2.1", + "drizzle-orm": "^0.44.4", "ical.js": "^2.2.1", "idb": "^8.0.3", "lucide-react": "^0.539.0", @@ -19,6 +21,7 @@ "next": "15.4.6", "next-auth": "^5.0.0-beta.29", "next-themes": "^0.4.6", + "pg": "^8.16.3", "react": "19.1.0", "react-dom": "19.1.0", "tailwind-merge": "^3.3.1", @@ -27,11 +30,14 @@ "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", "@types/node": "^20", + "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", + "drizzle-kit": "^0.31.4", "eslint": "^9", "eslint-config-next": "15.4.6", "tailwindcss": "^4", + "tsx": "^4.20.4", "tw-animate-css": "^1.3.6", "typescript": "^5", }, @@ -45,12 +51,70 @@ "@auth/core": ["@auth/core@0.40.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-n53uJE0RH5SqZ7N1xZoMKekbHfQgjd0sAEyUbE+IYJnmuQkbvuZnXItCU7d+i7Fj8VGOgqvNO7Mw4YfBTlZeQw=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], "@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="], + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], @@ -285,6 +349,8 @@ "@types/node": ["@types/node@20.19.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ=="], + "@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="], + "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="], "@types/react-dom": ["@types/react-dom@19.1.7", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw=="], @@ -393,6 +459,8 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -449,6 +517,12 @@ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dotenv": ["dotenv@17.2.1", "", {}, "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ=="], + + "drizzle-kit": ["drizzle-kit@0.31.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="], + + "drizzle-orm": ["drizzle-orm@0.44.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-ZyzKFpTC/Ut3fIqc2c0dPZ6nhchQXriTsqTNs4ayRgl6sZcFlMs9QZKPSHXK4bdOf41GHGWf+FrpcDDYwW+W6Q=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -471,6 +545,10 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.33.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA=="], @@ -529,6 +607,8 @@ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], @@ -765,6 +845,22 @@ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -773,6 +869,14 @@ "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=="], + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "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=="], @@ -843,8 +947,14 @@ "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], @@ -889,6 +999,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsx": ["tsx@4.20.4", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg=="], + "tw-animate-css": ["tw-animate-css@1.3.6", "", {}, "sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -927,10 +1039,14 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "@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=="], "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], @@ -975,6 +1091,50 @@ "sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], diff --git a/package.json b/package.json index e2e8458..7ea4d45 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dotenv": "^17.2.1", + "drizzle-orm": "^0.44.4", "ical.js": "^2.2.1", "idb": "^8.0.3", "lucide-react": "^0.539.0", @@ -24,6 +26,7 @@ "next": "15.4.6", "next-auth": "^5.0.0-beta.29", "next-themes": "^0.4.6", + "pg": "^8.16.3", "react": "19.1.0", "react-dom": "19.1.0", "tailwind-merge": "^3.3.1" @@ -32,11 +35,14 @@ "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", "@types/node": "^20", + "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", + "drizzle-kit": "^0.31.4", "eslint": "^9", "eslint-config-next": "15.4.6", "tailwindcss": "^4", + "tsx": "^4.20.4", "tw-animate-css": "^1.3.6", "typescript": "^5" }, From 20b26274d475ce73195de4063008c4f3056d79ec Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Sun, 17 Aug 2025 11:15:17 -0400 Subject: [PATCH 02/13] create example .env files --- .env.db.example | 3 +++ .env.production.example | 8 ++++++++ .gitignore | 1 + 3 files changed, 12 insertions(+) create mode 100644 .env.db.example create mode 100644 .env.production.example diff --git a/.env.db.example b/.env.db.example new file mode 100644 index 0000000..37f6cec --- /dev/null +++ b/.env.db.example @@ -0,0 +1,3 @@ +POSTGRES_PASSWORD= +POSTGRES_USER= +POSTGRES_DB= diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..a3379c0 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,8 @@ +OPENROUTER_API_KEY= +AUTH_AUTHENTIK_CLIENT_ID= +AUTH_AUTHENTIK_CLIENT_SECRET=notsosupersecret +AUTH_AUTHENTIK_ISSUER=https://example.com + +NEXTAUTH_URL=https://example.com +AUTH_SECRET=supersecret +NEXTAUTH_SECRET=supersecret diff --git a/.gitignore b/.gitignore index ff31605..d51089b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # dotenv environment variable files .env* +!.env*.example # caches .eslintcache From 3bb320b59f5fd8e4a9758ac85ffe8da63b90df13 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Sun, 17 Aug 2025 11:15:30 -0400 Subject: [PATCH 03/13] update docker-compose with a db --- docker-compose.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index d2e1207..b1e0bec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,9 @@ services: build: . container_name: local-ical restart: unless-stopped + depends_on: + postgresql: + condition: service_started networks: - traefik - internal @@ -14,6 +17,36 @@ services: 'traefik.http.routers.ical-local.tls.certresolver': 'letsencrypt' 'traefik.http.routers.ical-local.service': 'ical-local-service' 'traefik.http.services.ical-local-service.loadbalancer.server.port': '3000' + postgresql: + image: docker.io/library/postgres:16-alpine + restart: unless-stopped + container_name: local-ical-db + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 5s + # ports: + # - '5432:5432' + volumes: + - ical-local-postges:/var/lib/postgresql/data + env_file: + - .env.db + networks: + - traefik + - internal + labels: + 'traefik.enable': 'true' + 'traefik.docker.network': 'traefik' + 'traefik.http.routers.ical-local-db.rule': 'Host(`db.cal.cloud.dmytros.dev`)' + 'traefik.http.routers.ical-local-db.entrypoints': 'websecure' + 'traefik.http.routers.ical-local-db.tls.certresolver': 'letsencrypt' + 'traefik.http.routers.ical-local-db.service': 'ical-local-db-service' + 'traefik.http.services.ical-local-db-service.loadbalancer.server.port': '5432' +volumes: + ical-local-postges: + driver: local networks: traefik: external: true From 42f3521cd709c8b24b2376363cf049b98ec6bcc9 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Sun, 17 Aug 2025 11:41:27 -0400 Subject: [PATCH 04/13] update .env file --- .env.production.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.production.example b/.env.production.example index a3379c0..07bfde2 100644 --- a/.env.production.example +++ b/.env.production.example @@ -6,3 +6,4 @@ AUTH_AUTHENTIK_ISSUER=https://example.com NEXTAUTH_URL=https://example.com AUTH_SECRET=supersecret NEXTAUTH_SECRET=supersecret +DB_URL=postgres://:@:/ From c0740877b554ab8b1956dfb8b9fe04a820c513dd Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Mon, 18 Aug 2025 17:05:02 -0400 Subject: [PATCH 05/13] minor style adjustment --- FIXME.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIXME.md b/FIXME.md index f74148d..27f1084 100644 --- a/FIXME.md +++ b/FIXME.md @@ -1,4 +1,4 @@ # FIXME - - [] minimatch types + - [ ] minimatch types https://github.com/strapi/strapi/issues/23859 From 8808daead3a4a4ef347219ea68cd331aa3363875 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Mon, 18 Aug 2025 17:05:18 -0400 Subject: [PATCH 06/13] use independent db easier for local development --- docker-compose.yml | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b1e0bec..d2e1207 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,6 @@ services: build: . container_name: local-ical restart: unless-stopped - depends_on: - postgresql: - condition: service_started networks: - traefik - internal @@ -17,36 +14,6 @@ services: 'traefik.http.routers.ical-local.tls.certresolver': 'letsencrypt' 'traefik.http.routers.ical-local.service': 'ical-local-service' 'traefik.http.services.ical-local-service.loadbalancer.server.port': '3000' - postgresql: - image: docker.io/library/postgres:16-alpine - restart: unless-stopped - container_name: local-ical-db - healthcheck: - test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] - start_period: 20s - interval: 30s - retries: 5 - timeout: 5s - # ports: - # - '5432:5432' - volumes: - - ical-local-postges:/var/lib/postgresql/data - env_file: - - .env.db - networks: - - traefik - - internal - labels: - 'traefik.enable': 'true' - 'traefik.docker.network': 'traefik' - 'traefik.http.routers.ical-local-db.rule': 'Host(`db.cal.cloud.dmytros.dev`)' - 'traefik.http.routers.ical-local-db.entrypoints': 'websecure' - 'traefik.http.routers.ical-local-db.tls.certresolver': 'letsencrypt' - 'traefik.http.routers.ical-local-db.service': 'ical-local-db-service' - 'traefik.http.services.ical-local-db-service.loadbalancer.server.port': '5432' -volumes: - ical-local-postges: - driver: local networks: traefik: external: true From 9942a11c0d444f5e90778c488e7890ce223aaec4 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Mon, 18 Aug 2025 17:06:11 -0400 Subject: [PATCH 07/13] drizzle setup --- drizzle.config.ts | 17 +++++++++++++++++ src/db.ts | 10 ++++++++++ src/db/schema.ts | 7 +++++++ 3 files changed, 34 insertions(+) create mode 100644 drizzle.config.ts create mode 100644 src/db.ts create mode 100644 src/db/schema.ts diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..99a6eae --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,17 @@ +"use server"; + +import { config } from "dotenv"; +import { type Config } from "drizzle-kit"; + +config({ path: ".env" }); + +export default { + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "postgresql", + dbCredentials: { + url: process.env.DATABASE_URL!, + }, + verbose: true, + strict: true, +} satisfies Config; diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..842cf89 --- /dev/null +++ b/src/db.ts @@ -0,0 +1,10 @@ +import "dotenv/config"; +import { drizzle } from "drizzle-orm/node-postgres"; + +// You can specify any property from the node-postgres connection options +const db = drizzle({ + connection: { + connectionString: process.env.DATABASE_URL!, + ssl: true, + }, +}); diff --git a/src/db/schema.ts b/src/db/schema.ts new file mode 100644 index 0000000..e66b799 --- /dev/null +++ b/src/db/schema.ts @@ -0,0 +1,7 @@ +import { integer, pgTable, varchar } from "drizzle-orm/pg-core"; + +export const usersTable = pgTable("users", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar({ length: 255 }).notNull(), + email: varchar({ length: 255 }).notNull().unique(), +}); From 92535f7e54320c2d370d8d301c430fe69197d2d4 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Tue, 19 Aug 2025 01:48:23 -0400 Subject: [PATCH 08/13] claude drizzle integration --- bun.lock | 6 +++++ drizzle.config.ts | 28 ++++++++++---------- package.json | 2 ++ src/app/page.tsx | 9 +++---- src/auth.ts | 3 +++ src/db.ts | 10 ------- src/db/index.ts | 12 +++++++++ src/db/schema.ts | 39 ++++++++++++++++++++++++---- src/lib/db.ts | 55 --------------------------------------- src/lib/events-db.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 135 insertions(+), 91 deletions(-) delete mode 100644 src/db.ts create mode 100644 src/db/index.ts delete mode 100644 src/lib/db.ts create mode 100644 src/lib/events-db.ts diff --git a/bun.lock b/bun.lock index e1d55a9..b3fdda0 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "": { "name": "ical-pwa", "dependencies": { + "@auth/drizzle-adapter": "^1.10.0", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -22,6 +23,7 @@ "next-auth": "^5.0.0-beta.29", "next-themes": "^0.4.6", "pg": "^8.16.3", + "postgres": "^3.4.7", "react": "19.1.0", "react-dom": "19.1.0", "tailwind-merge": "^3.3.1", @@ -51,6 +53,8 @@ "@auth/core": ["@auth/core@0.40.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-n53uJE0RH5SqZ7N1xZoMKekbHfQgjd0sAEyUbE+IYJnmuQkbvuZnXItCU7d+i7Fj8VGOgqvNO7Mw4YfBTlZeQw=="], + "@auth/drizzle-adapter": ["@auth/drizzle-adapter@1.10.0", "", { "dependencies": { "@auth/core": "0.40.0" } }, "sha512-3MKsdAINTfvV4QKev8PFMNG93HJEUHh9sggDXnmUmriFogRf8qLvgqnPsTlfUyWcLwTzzrrYjeu8CGM+4IxHwQ=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], "@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], @@ -869,6 +873,8 @@ "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=="], + "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], diff --git a/drizzle.config.ts b/drizzle.config.ts index 99a6eae..7502c55 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,17 +1,15 @@ -"use server"; +import { defineConfig } from 'drizzle-kit'; +import * as dotenv from 'dotenv'; -import { config } from "dotenv"; -import { type Config } from "drizzle-kit"; +dotenv.config({ path: '.env.local' }); -config({ path: ".env" }); - -export default { - out: "./drizzle", - schema: "./src/db/schema.ts", - dialect: "postgresql", - dbCredentials: { - url: process.env.DATABASE_URL!, - }, - verbose: true, - strict: true, -} satisfies Config; +export default defineConfig({ + dialect: 'postgresql', + schema: './src/db/schema.ts', + out: './drizzle', + dbCredentials: { + url: process.env.DATABASE_URL!, + }, + verbose: true, + strict: true, +}); \ No newline at end of file diff --git a/package.json b/package.json index 7ea4d45..ee6131d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@auth/drizzle-adapter": "^1.10.0", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -27,6 +28,7 @@ "next-auth": "^5.0.0-beta.29", "next-themes": "^0.4.6", "pg": "^8.16.3", + "postgres": "^3.4.7", "react": "19.1.0", "react-dom": "19.1.0", "tailwind-merge": "^3.3.1" diff --git a/src/app/page.tsx b/src/app/page.tsx index 794cb56..d03ca69 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,7 +9,7 @@ import { Card } from '@/components/ui/card' import { RecurrencePicker } from '@/components/recurrence-picker' import { IcsFilePicker } from '@/components/ics-file-picker' -import { addEvent, deleteEvent, getAllEvents, clearEvents, getDB } from '@/lib/db' +import { saveEvent as addEvent, deleteEvent, getEvents as getAllEvents, clearEvents, updateEvent } from '@/lib/events-db' import { parseICS, generateICS } from '@/lib/ical' import type { CalendarEvent } from '@/lib/types' import { Textarea } from '@/components/ui/textarea' @@ -74,11 +74,8 @@ export default function HomePage() { lastModified: new Date().toISOString(), } if (editingId) { - const db = await getDB() - if (db) { - await db.put('events', eventData) - setEvents(prev => prev.map(e => (e.id === editingId ? eventData : e))) - } + await updateEvent(eventData) + setEvents(prev => prev.map(e => (e.id === editingId ? eventData : e))) } else { await addEvent(eventData) setEvents(prev => [...prev, eventData]) diff --git a/src/auth.ts b/src/auth.ts index e3daa56..74b1faa 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,6 +1,8 @@ import NextAuth, { NextAuthConfig, NextAuthResult } from "next-auth"; import Authentik from "next-auth/providers/authentik"; import type { Provider } from "next-auth/providers"; +import { DrizzleAdapter } from "@auth/drizzle-adapter"; +import { db } from "@/db/index"; const providers: Provider[] = [ Authentik({ @@ -20,6 +22,7 @@ export const providerMap = providers.map((provider) => { }); const config = { + adapter: DrizzleAdapter(db), providers, pages: { signIn: "/signin", diff --git a/src/db.ts b/src/db.ts deleted file mode 100644 index 842cf89..0000000 --- a/src/db.ts +++ /dev/null @@ -1,10 +0,0 @@ -import "dotenv/config"; -import { drizzle } from "drizzle-orm/node-postgres"; - -// You can specify any property from the node-postgres connection options -const db = drizzle({ - connection: { - connectionString: process.env.DATABASE_URL!, - ssl: true, - }, -}); diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..ecc23cf --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,12 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from './schema'; + +const connectionString = process.env.DATABASE_URL!; + +const client = postgres(connectionString, { + prepare: false, + connect_timeout: 30, + idle_timeout: 30, +}); +export const db = drizzle(client, { schema }); \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index e66b799..ed1a815 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,7 +1,36 @@ -import { integer, pgTable, varchar } from "drizzle-orm/pg-core"; +import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'; -export const usersTable = pgTable("users", { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar({ length: 255 }).notNull(), - email: varchar({ length: 255 }).notNull().unique(), +export const users = pgTable('users', { + id: uuid('id').primaryKey().defaultRandom(), + email: text('email').notNull().unique(), + name: text('name'), + image: text('image'), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), }); + +export const accounts = pgTable('accounts', { + id: uuid('id').primaryKey().defaultRandom(), + userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + type: text('type').notNull(), + provider: text('provider').notNull(), + providerAccountId: text('provider_account_id').notNull(), + refreshToken: text('refresh_token'), + accessToken: text('access_token'), + expiresAt: timestamp('expires_at'), + tokenType: text('token_type'), + scope: text('scope'), + idToken: text('id_token'), + sessionState: text('session_state'), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +export const sessions = pgTable('sessions', { + id: uuid('id').primaryKey().defaultRandom(), + sessionToken: text('session_token').notNull().unique(), + userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + expires: timestamp('expires').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); \ No newline at end of file diff --git a/src/lib/db.ts b/src/lib/db.ts deleted file mode 100644 index a81dc04..0000000 --- a/src/lib/db.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { openDB, DBSchema, IDBPDatabase } from "idb"; -import { type CalendarEvent } from "./types"; - -interface ICalDB extends DBSchema { - events: { - key: string; - value: CalendarEvent; - }; -} - -let dbPromise: Promise> | null = null; - -async function initDB() { - return openDB("icalPWA", 1, { - upgrade(db) { - if (!db.objectStoreNames.contains("events")) { - db.createObjectStore("events", { keyPath: "id" }); - } - }, - }); -} - -// Get the database in a browser-safe way -export async function getDB() { - if (typeof window === "undefined") return null; - if (!dbPromise) { - dbPromise = initDB(); - } - return dbPromise; -} - -// CRUD operations — all SSR-safe -export async function getAllEvents() { - const db = await getDB(); - if (!db) return []; - return db.getAll("events"); -} - -export async function addEvent(event: ICalDB["events"]["value"]) { - const db = await getDB(); - if (!db) return; - return db.put("events", event); -} - -export async function deleteEvent(id: string) { - const db = await getDB(); - if (!db) return; - return db.delete("events", id); -} - -export async function clearEvents() { - const db = await getDB(); - if (!db) return; - return db.clear("events"); -} diff --git a/src/lib/events-db.ts b/src/lib/events-db.ts new file mode 100644 index 0000000..7094213 --- /dev/null +++ b/src/lib/events-db.ts @@ -0,0 +1,62 @@ +import { openDB, type IDBPDatabase } from 'idb'; +import type { CalendarEvent } from '@/lib/types'; + +const DB_NAME = 'LocalCalEvents'; +const DB_VERSION = 1; +const EVENTS_STORE = 'events'; + +let dbPromise: Promise | null = null; + +function getDB() { + if (!dbPromise) { + dbPromise = openDB(DB_NAME, DB_VERSION, { + upgrade(db) { + if (!db.objectStoreNames.contains(EVENTS_STORE)) { + const store = db.createObjectStore(EVENTS_STORE, { keyPath: 'id' }); + store.createIndex('start', 'start'); + store.createIndex('title', 'title'); + } + }, + }); + } + return dbPromise; +} + +export async function saveEvent(event: CalendarEvent): Promise { + const db = await getDB(); + await db.put(EVENTS_STORE, event); +} + +export async function getEvents(): Promise { + const db = await getDB(); + return db.getAll(EVENTS_STORE); +} + +export async function getEvent(id: string): Promise { + const db = await getDB(); + return db.get(EVENTS_STORE, id); +} + +export async function deleteEvent(id: string): Promise { + const db = await getDB(); + await db.delete(EVENTS_STORE, id); +} + +export async function updateEvent(event: CalendarEvent): Promise { + const db = await getDB(); + await db.put(EVENTS_STORE, event); +} + +export async function getEventsByDateRange(startDate: string, endDate: string): Promise { + const db = await getDB(); + const tx = db.transaction(EVENTS_STORE, 'readonly'); + const index = tx.store.index('start'); + const events = await index.getAll(IDBKeyRange.bound(startDate, endDate)); + await tx.done; + return events; +} + +export async function clearEvents(): Promise { + const db = await getDB(); + await db.clear(EVENTS_STORE); +} \ No newline at end of file From 1a13013b45a1ac818a40d4cc9c762a6c7429fc5c Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Tue, 19 Aug 2025 01:53:27 -0400 Subject: [PATCH 09/13] use different env files based on node env --- drizzle.config.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drizzle.config.ts b/drizzle.config.ts index 7502c55..433f699 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,7 +1,11 @@ import { defineConfig } from 'drizzle-kit'; import * as dotenv from 'dotenv'; -dotenv.config({ path: '.env.local' }); +if (process.env.NODE_ENV === "production") { + dotenv.config({ path: '.env.production' }); +} else { + dotenv.config({ path: '.env.local' }); +} export default defineConfig({ dialect: 'postgresql', @@ -12,4 +16,4 @@ export default defineConfig({ }, verbose: true, strict: true, -}); \ No newline at end of file +}); From 12e9ec5d854dc756d432a638aa3596a643e68bec Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Tue, 19 Aug 2025 03:40:06 -0400 Subject: [PATCH 10/13] proper user/session creation and auth integration into UI --- drizzle/0000_loose_catseye.sql | 56 ++++++ drizzle/meta/0000_snapshot.json | 344 ++++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 13 ++ drizzle/relations.ts | 29 +++ drizzle/schema.ts | 72 +++++++ src/app/api/ai-event/route.ts | 10 + src/app/components/sign-in.tsx | 54 ++--- src/app/page.tsx | 52 +++-- src/app/signin/page.tsx | 45 +++++ src/app/signout/page.tsx | 49 +++++ src/db/schema.ts | 71 ++++--- src/middleware.ts | 25 --- 12 files changed, 726 insertions(+), 94 deletions(-) create mode 100644 drizzle/0000_loose_catseye.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json create mode 100644 drizzle/relations.ts create mode 100644 drizzle/schema.ts create mode 100644 src/app/signin/page.tsx create mode 100644 src/app/signout/page.tsx delete mode 100644 src/middleware.ts diff --git a/drizzle/0000_loose_catseye.sql b/drizzle/0000_loose_catseye.sql new file mode 100644 index 0000000..e7d7fd7 --- /dev/null +++ b/drizzle/0000_loose_catseye.sql @@ -0,0 +1,56 @@ +-- Current sql file was generated after introspecting 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; +*/ \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..2034f91 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,344 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "prevId": "", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "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": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.authenticator": { + "name": "authenticator", + "schema": "", + "columns": { + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "authenticator_userId_user_id_fk": { + "name": "authenticator_userId_user_id_fk", + "tableFrom": "authenticator", + "tableTo": "user", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "authenticator_userId_credentialID_pk": { + "name": "authenticator_userId_credentialID_pk", + "columns": [ + "credentialID", + "userId" + ] + } + }, + "uniqueConstraints": { + "authenticator_credentialID_unique": { + "columns": [ + "credentialID" + ], + "nullsNotDistinct": false, + "name": "authenticator_credentialID_unique" + } + }, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..612f104 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1755586325384, + "tag": "0000_loose_catseye", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/drizzle/relations.ts b/drizzle/relations.ts new file mode 100644 index 0000000..9be20fe --- /dev/null +++ b/drizzle/relations.ts @@ -0,0 +1,29 @@ +import { relations } from "drizzle-orm/relations"; +import { user, session, authenticator, account } from "./schema"; + +export const sessionRelations = relations(session, ({one}) => ({ + user: one(user, { + fields: [session.userId], + references: [user.id] + }), +})); + +export const userRelations = relations(user, ({many}) => ({ + sessions: many(session), + authenticators: many(authenticator), + accounts: many(account), +})); + +export const authenticatorRelations = relations(authenticator, ({one}) => ({ + user: one(user, { + fields: [authenticator.userId], + references: [user.id] + }), +})); + +export const accountRelations = relations(account, ({one}) => ({ + user: one(user, { + fields: [account.userId], + references: [user.id] + }), +})); \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts new file mode 100644 index 0000000..84beebe --- /dev/null +++ b/drizzle/schema.ts @@ -0,0 +1,72 @@ +import { pgTable, foreignKey, text, timestamp, primaryKey, unique, integer, boolean } from "drizzle-orm/pg-core" +import { sql } from "drizzle-orm" + + + +export const session = pgTable("session", { + sessionToken: text().primaryKey().notNull(), + userId: text().notNull(), + expires: timestamp({ mode: 'string' }).notNull(), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "session_userId_user_id_fk" + }).onDelete("cascade"), +]); + +export const user = pgTable("user", { + id: text().primaryKey().notNull(), + name: text(), + email: text().notNull(), + emailVerified: timestamp({ mode: 'string' }), + image: text(), +}); + +export const verificationToken = pgTable("verificationToken", { + identifier: text().notNull(), + token: text().notNull(), + expires: timestamp({ mode: 'string' }).notNull(), +}, (table) => [ + primaryKey({ columns: [table.identifier, table.token], name: "verificationToken_identifier_token_pk"}), +]); + +export const authenticator = pgTable("authenticator", { + credentialId: text().notNull(), + userId: text().notNull(), + providerAccountId: text().notNull(), + credentialPublicKey: text().notNull(), + counter: integer().notNull(), + credentialDeviceType: text().notNull(), + credentialBackedUp: boolean().notNull(), + transports: text(), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "authenticator_userId_user_id_fk" + }).onDelete("cascade"), + primaryKey({ columns: [table.credentialId, table.userId], name: "authenticator_userId_credentialID_pk"}), + unique("authenticator_credentialID_unique").on(table.credentialId), +]); + +export const account = pgTable("account", { + userId: text().notNull(), + type: text().notNull(), + provider: text().notNull(), + providerAccountId: text().notNull(), + refreshToken: text("refresh_token"), + accessToken: text("access_token"), + expiresAt: text("expires_at"), + tokenType: text("token_type"), + scope: text(), + idToken: text("id_token"), + sessionState: text("session_state"), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "account_userId_user_id_fk" + }).onDelete("cascade"), + primaryKey({ columns: [table.provider, table.providerAccountId], name: "account_provider_providerAccountId_pk"}), +]); diff --git a/src/app/api/ai-event/route.ts b/src/app/api/ai-event/route.ts index 25ddc16..b355a95 100644 --- a/src/app/api/ai-event/route.ts +++ b/src/app/api/ai-event/route.ts @@ -1,6 +1,16 @@ import { NextResponse } from "next/server"; +import { auth } from "@/auth"; export async function POST(request: Request) { + const session = await auth(); + + if (!session?.user) { + return NextResponse.json( + { error: "Authentication required" }, + { status: 401 } + ); + } + const { prompt } = await request.json(); const systemPrompt = ` diff --git a/src/app/components/sign-in.tsx b/src/app/components/sign-in.tsx index e393d37..3d6c66f 100644 --- a/src/app/components/sign-in.tsx +++ b/src/app/components/sign-in.tsx @@ -1,37 +1,43 @@ -import { signIn, signOut } from "@/auth" -import { auth } from "@/auth" -import { Button } from "@/components/ui/button" +"use client" -export default async function SignIn() { - const session = await auth() +import { signOut, useSession } from "next-auth/react" +import { Button } from "@/components/ui/button" +import { useRouter } from "next/navigation" + +export default function SignIn() { + const { data: session, status } = useSession() + const router = useRouter() + + const handleSignOut = async () => { + await signOut({ redirect: false }) + router.push("/") + router.refresh() + } + + if (status === "loading") { + return
+ } if (session?.user) { return (
-
{ - "use server" - await signOut() - }} - > - -
+ + {session.user.name || session.user.email} + +
) } return ( -
{ - "use server" - await signIn("authentik") - }} + -
+ Sign In + ) } diff --git a/src/app/page.tsx b/src/app/page.tsx index d03ca69..058be86 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -143,6 +143,13 @@ export default function HomePage() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: aiPrompt }) }) + + if (res.status === 401) { + alert('Please sign in to use AI features.') + setAiLoading(false) + return + } + const data = await res.json() if (Array.isArray(data) && data.length > 0) { @@ -225,25 +232,32 @@ export default function HomePage() {
Drag & Drop *.ics here
{/* AI Toolbar */} -
- {session?.user && ( - <> -
-