diff --git a/numerology.db b/numerology.db new file mode 100644 index 0000000..cb3f212 Binary files /dev/null and b/numerology.db differ diff --git a/package-lock.json b/package-lock.json index 720957e..43b94ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,12 +37,18 @@ "@radix-ui/react-toggle-group": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", "@supabase/supabase-js": "^2.55.0", + "bcryptjs": "^3.0.2", + "better-sqlite3": "^12.2.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", + "cors": "^2.8.5", "date-fns": "^3.0.0", "embla-carousel-react": "^8.5.2", + "express": "^5.1.0", + "helmet": "^8.1.0", "input-otp": "^1.4.2", + "jsonwebtoken": "^9.0.2", "lucide-react": "^0.364.0", "next-themes": "^0.4.4", "react": "^18.3.1", @@ -66,6 +72,7 @@ "@types/react-router-dom": "^5", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "10.4.20", + "concurrently": "^9.2.0", "eslint": "^9.15.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.14", @@ -3453,6 +3460,19 @@ "dev": true, "license": "MIT" }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3608,6 +3628,49 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/better-sqlite3": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.2.0.tgz", + "integrity": "sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3620,6 +3683,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3676,6 +3779,74 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3769,6 +3940,12 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3781,6 +3958,84 @@ "url": "https://polar.sh/cva" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4256,6 +4511,69 @@ "dev": true, "license": "MIT" }, + "node_modules/concurrently": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", + "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4263,6 +4581,37 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4430,7 +4779,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4450,6 +4798,30 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4457,6 +4829,24 @@ "dev": true, "license": "MIT" }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4485,12 +4875,41 @@ "csstype": "^3.0.2" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.203", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", @@ -4532,6 +4951,24 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -4545,6 +4982,36 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -4597,6 +5064,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4795,12 +5268,72 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4881,6 +5414,12 @@ "node": ">=16.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4893,6 +5432,23 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4947,6 +5503,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -4961,6 +5526,21 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4994,6 +5574,40 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -5003,6 +5617,25 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -5072,6 +5705,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -5089,6 +5734,18 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5101,6 +5758,72 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5138,6 +5861,18 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/input-otp": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", @@ -5157,6 +5892,15 @@ "node": ">=12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -5223,6 +5967,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5319,6 +6069,61 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5383,6 +6188,42 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5390,6 +6231,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5431,6 +6278,36 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5453,6 +6330,39 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5466,6 +6376,15 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -5475,11 +6394,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -5511,6 +6435,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5518,6 +6448,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", @@ -5528,6 +6467,30 @@ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -5572,6 +6535,39 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5641,6 +6637,15 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5688,6 +6693,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5867,6 +6881,32 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5894,6 +6934,29 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5904,6 +6967,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5924,6 +7002,54 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -6146,6 +7272,20 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6190,6 +7330,16 @@ "decimal.js-light": "^2.4.1" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -6270,6 +7420,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6293,6 +7459,42 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -6312,6 +7514,49 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6333,6 +7578,91 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -6345,6 +7675,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sonner": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", @@ -6364,6 +7739,24 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -6576,6 +7969,34 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6663,12 +8084,31 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6694,6 +8134,18 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6707,6 +8159,20 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -6751,6 +8217,15 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -6850,6 +8325,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vaul": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", @@ -7167,6 +8651,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -7188,6 +8678,16 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -7207,6 +8707,80 @@ "node": ">= 14.6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ddbcbc8..afd85f9 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,15 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "yes | pnpm install && vite", - "build": "yes | pnpm install && rm -rf node_modules/.vite-temp && tsc -b && vite build", - "build:prod": "yes | pnpm install && rm -rf node_modules/.vite-temp && tsc -b && BUILD_MODE=prod vite build", - "lint": "yes | pnpm install && eslint .", - "preview": "yes | pnpm install && vite preview" + "dev": "vite", + "dev:server": "node server/index.js", + "dev:full": "concurrently \"npm run dev\" \"npm run dev:server\"", + "build": "tsc -b && vite build", + "build:prod": "tsc -b && BUILD_MODE=prod vite build", + "lint": "eslint .", + "preview": "vite preview", + "server": "node server/index.js", + "start": "NODE_ENV=production node server/index.js" }, "dependencies": { "@hookform/resolvers": "^3.10.0", @@ -40,12 +44,18 @@ "@radix-ui/react-toggle-group": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", "@supabase/supabase-js": "^2.55.0", + "bcryptjs": "^3.0.2", + "better-sqlite3": "^12.2.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", + "cors": "^2.8.5", "date-fns": "^3.0.0", "embla-carousel-react": "^8.5.2", + "express": "^5.1.0", + "helmet": "^8.1.0", "input-otp": "^1.4.2", + "jsonwebtoken": "^9.0.2", "lucide-react": "^0.364.0", "next-themes": "^0.4.4", "react": "^18.3.1", @@ -69,6 +79,7 @@ "@types/react-router-dom": "^5", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "10.4.20", + "concurrently": "^9.2.0", "eslint": "^9.15.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.14", @@ -80,4 +91,4 @@ "vite": "^6.0.1", "vite-plugin-source-info": "^1.0.0" } -} \ No newline at end of file +} diff --git a/server/database.js b/server/database.js new file mode 100644 index 0000000..29cee36 --- /dev/null +++ b/server/database.js @@ -0,0 +1,111 @@ +import Database from 'better-sqlite3'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 创建数据库连接 +const dbPath = path.join(__dirname, '..', 'numerology.db'); +const db = new Database(dbPath); + +// 启用外键约束 +db.pragma('foreign_keys = ON'); + +// 创建用户表 +db.exec(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + full_name TEXT, + birth_date DATE, + birth_time TIME, + birth_place TEXT, + gender TEXT CHECK (gender IN ('male', 'female')), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) +`); + +// 创建分析记录表 +db.exec(` + CREATE TABLE IF NOT EXISTS readings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + reading_type TEXT NOT NULL CHECK (reading_type IN ('bazi', 'ziwei', 'yijing', 'wuxing')), + name TEXT, + birth_date DATE, + birth_time TIME, + gender TEXT CHECK (gender IN ('male', 'female')), + birth_place TEXT, + input_data TEXT, + results TEXT, + analysis TEXT, + status TEXT DEFAULT 'completed' CHECK (status IN ('pending', 'processing', 'completed', 'failed')), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE + ) +`); + +// 创建索引 +db.exec(` + CREATE INDEX IF NOT EXISTS idx_readings_user_id ON readings(user_id); + CREATE INDEX IF NOT EXISTS idx_readings_type ON readings(reading_type); + CREATE INDEX IF NOT EXISTS idx_readings_created_at ON readings(created_at DESC); +`); + +// 数据库操作函数 +export const dbOperations = { + // 用户相关操作 + createUser: db.prepare(` + INSERT INTO users (email, password, full_name, birth_date, birth_time, birth_place, gender) + VALUES (?, ?, ?, ?, ?, ?, ?) + `), + + getUserByEmail: db.prepare('SELECT * FROM users WHERE email = ?'), + + getUserById: db.prepare('SELECT id, email, full_name, birth_date, birth_time, birth_place, gender, created_at FROM users WHERE id = ?'), + + updateUser: db.prepare(` + UPDATE users + SET full_name = ?, birth_date = ?, birth_time = ?, birth_place = ?, gender = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `), + + // 分析记录相关操作 + createReading: db.prepare(` + INSERT INTO readings (user_id, reading_type, name, birth_date, birth_time, gender, birth_place, input_data, results, analysis) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `), + + getReadingsByUserId: db.prepare(` + SELECT * FROM readings + WHERE user_id = ? + ORDER BY created_at DESC + `), + + getReadingsByUserIdAndType: db.prepare(` + SELECT * FROM readings + WHERE user_id = ? AND reading_type = ? + ORDER BY created_at DESC + `), + + getReadingById: db.prepare('SELECT * FROM readings WHERE id = ?'), + + deleteReading: db.prepare('DELETE FROM readings WHERE id = ? AND user_id = ?'), + + // 统计信息 + getUserReadingCount: db.prepare('SELECT COUNT(*) as count FROM readings WHERE user_id = ?'), + + getReadingCountByType: db.prepare('SELECT reading_type, COUNT(*) as count FROM readings WHERE user_id = ? GROUP BY reading_type') +}; + +// 优雅关闭数据库连接 +process.on('exit', () => db.close()); +process.on('SIGHUP', () => process.exit(128 + 1)); +process.on('SIGINT', () => process.exit(128 + 2)); +process.on('SIGTERM', () => process.exit(128 + 15)); + +export default db; \ No newline at end of file diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..c4cd72b --- /dev/null +++ b/server/index.js @@ -0,0 +1,140 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// 导入路由 +import authRoutes from './routes/auth.js'; +import analysisRoutes from './routes/analysis.js'; + +// 导入中间件 +import { errorHandler, requestLogger, corsOptions } from './middleware/auth.js'; + +// 导入数据库(确保数据库初始化) +import './database.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const app = express(); +const PORT = process.env.PORT || 3001; + +// 安全中间件 +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'"], + fontSrc: ["'self'"], + objectSrc: ["'none'"], + mediaSrc: ["'self'"], + frameSrc: ["'none'"] + } + }, + crossOriginEmbedderPolicy: false +})); + +// CORS配置 +app.use(cors(corsOptions)); + +// 请求解析中间件 +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); + +// 请求日志 +app.use(requestLogger); + +// 健康检查端点 +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV || 'development', + version: '1.0.0' + }); +}); + +// API路由 +app.use('/api/auth', authRoutes); +app.use('/api/analysis', analysisRoutes); + +// 静态文件服务(用于前端构建文件) +if (process.env.NODE_ENV === 'production') { + const frontendPath = path.join(__dirname, '..', 'dist'); + app.use(express.static(frontendPath)); + + // SPA路由处理 + app.get('*', (req, res) => { + res.sendFile(path.join(frontendPath, 'index.html')); + }); +} + +// 404处理 +app.use((req, res) => { + res.status(404).json({ + error: { + code: 'NOT_FOUND', + message: '请求的资源不存在' + } + }); +}); + +// 错误处理中间件 +app.use(errorHandler); + +// 优雅关闭处理 +const gracefulShutdown = (signal) => { + console.log(`\n收到 ${signal} 信号,开始优雅关闭...`); + + server.close((err) => { + if (err) { + console.error('服务器关闭时发生错误:', err); + process.exit(1); + } + + console.log('服务器已关闭'); + process.exit(0); + }); + + // 强制关闭超时 + setTimeout(() => { + console.error('强制关闭服务器'); + process.exit(1); + }, 10000); +}; + +// 启动服务器 +const server = app.listen(PORT, () => { + console.log(`\n🚀 三算命本地服务器启动成功!`); + console.log(`📍 服务器地址: http://localhost:${PORT}`); + console.log(`🔧 环境: ${process.env.NODE_ENV || 'development'}`); + console.log(`⏰ 启动时间: ${new Date().toLocaleString('zh-CN')}`); + console.log(`\n📚 API文档:`); + console.log(` 认证相关: http://localhost:${PORT}/api/auth`); + console.log(` 分析相关: http://localhost:${PORT}/api/analysis`); + console.log(` 健康检查: http://localhost:${PORT}/health`); + console.log(`\n💡 提示: 按 Ctrl+C 停止服务器\n`); +}); + +// 监听关闭信号 +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); +process.on('SIGINT', () => gracefulShutdown('SIGINT')); + +// 未捕获异常处理 +process.on('uncaughtException', (err) => { + console.error('未捕获的异常:', err); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', reason); + console.error('Promise:', promise); + process.exit(1); +}); + +export default app; \ No newline at end of file diff --git a/server/middleware/auth.js b/server/middleware/auth.js new file mode 100644 index 0000000..a4541a6 --- /dev/null +++ b/server/middleware/auth.js @@ -0,0 +1,217 @@ +import { authService } from '../services/authService.js'; + +/** + * JWT认证中间件 + */ +export const authenticateToken = (req, res, next) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN + + if (!token) { + return res.status(401).json({ + error: { + code: 'UNAUTHORIZED', + message: '缺少访问令牌' + } + }); + } + + try { + const decoded = authService.verifyToken(token); + req.user = decoded; + next(); + } catch (error) { + return res.status(403).json({ + error: { + code: 'FORBIDDEN', + message: '无效的访问令牌' + } + }); + } +}; + +/** + * 可选认证中间件(用于可选登录的接口) + */ +export const optionalAuth = (req, res, next) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (token) { + try { + const decoded = authService.verifyToken(token); + req.user = decoded; + } catch (error) { + // 忽略token验证错误,继续执行 + req.user = null; + } + } else { + req.user = null; + } + + next(); +}; + +/** + * 错误处理中间件 + */ +export const errorHandler = (err, req, res, next) => { + console.error('API Error:', err); + + // 数据库错误 + if (err.code && err.code.startsWith('SQLITE_')) { + return res.status(500).json({ + error: { + code: 'DATABASE_ERROR', + message: '数据库操作失败' + } + }); + } + + // 验证错误 + if (err.name === 'ValidationError') { + return res.status(400).json({ + error: { + code: 'VALIDATION_ERROR', + message: err.message + } + }); + } + + // JWT错误 + if (err.name === 'JsonWebTokenError') { + return res.status(401).json({ + error: { + code: 'INVALID_TOKEN', + message: '无效的访问令牌' + } + }); + } + + if (err.name === 'TokenExpiredError') { + return res.status(401).json({ + error: { + code: 'TOKEN_EXPIRED', + message: '访问令牌已过期' + } + }); + } + + // 默认错误 + res.status(500).json({ + error: { + code: 'INTERNAL_ERROR', + message: err.message || '内部服务器错误' + } + }); +}; + +/** + * 请求日志中间件 + */ +export const requestLogger = (req, res, next) => { + const start = Date.now(); + const { method, url, ip } = req; + + res.on('finish', () => { + const duration = Date.now() - start; + const { statusCode } = res; + + console.log(`${new Date().toISOString()} - ${method} ${url} - ${statusCode} - ${duration}ms - ${ip}`); + }); + + next(); +}; + +/** + * 输入验证中间件 + */ +export const validateInput = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.body); + + if (error) { + return res.status(400).json({ + error: { + code: 'INVALID_INPUT', + message: error.details[0].message + } + }); + } + + next(); + }; +}; + +/** + * 速率限制中间件(简单实现) + */ +const rateLimitStore = new Map(); + +export const rateLimit = (options = {}) => { + const { + windowMs = 15 * 60 * 1000, // 15分钟 + max = 100, // 最大请求数 + message = '请求过于频繁,请稍后再试' + } = options; + + return (req, res, next) => { + const key = req.ip || req.connection.remoteAddress; + const now = Date.now(); + + if (!rateLimitStore.has(key)) { + rateLimitStore.set(key, { count: 1, resetTime: now + windowMs }); + return next(); + } + + const record = rateLimitStore.get(key); + + if (now > record.resetTime) { + // 重置计数 + record.count = 1; + record.resetTime = now + windowMs; + return next(); + } + + if (record.count >= max) { + return res.status(429).json({ + error: { + code: 'RATE_LIMIT_EXCEEDED', + message + } + }); + } + + record.count++; + next(); + }; +}; + +/** + * CORS中间件配置 + */ +export const corsOptions = { + origin: function (origin, callback) { + // 允许的域名列表 + const allowedOrigins = [ + 'http://localhost:5173', + 'http://localhost:3000', + 'http://127.0.0.1:5173', + 'http://127.0.0.1:3000' + ]; + + // 开发环境允许所有来源 + if (process.env.NODE_ENV === 'development') { + return callback(null, true); + } + + if (!origin || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error('不允许的CORS来源')); + } + }, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'] +}; \ No newline at end of file diff --git a/server/routes/analysis.js b/server/routes/analysis.js new file mode 100644 index 0000000..0f03361 --- /dev/null +++ b/server/routes/analysis.js @@ -0,0 +1,349 @@ +import express from 'express'; +import { numerologyService } from '../services/numerologyService.js'; +import { authenticateToken, rateLimit } from '../middleware/auth.js'; + +const router = express.Router(); + +// 应用认证中间件 +router.use(authenticateToken); + +// 应用速率限制 +router.use(rateLimit({ max: 50, windowMs: 15 * 60 * 1000 })); // 15分钟内最多50次请求 + +/** + * 八字命理分析 + * POST /api/analysis/bazi + */ +router.post('/bazi', async (req, res, next) => { + try { + const { name, birthDate, birthTime, gender, birthPlace } = req.body; + + // 参数验证 + if (!birthDate) { + return res.status(400).json({ + error: { + code: 'MISSING_PARAMETERS', + message: '出生日期不能为空' + } + }); + } + + // 日期格式验证 + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birthDate)) { + return res.status(400).json({ + error: { + code: 'INVALID_DATE_FORMAT', + message: '日期格式应为YYYY-MM-DD' + } + }); + } + + // 时间格式验证(可选) + if (birthTime) { + const timeRegex = /^\d{2}:\d{2}$/; + if (!timeRegex.test(birthTime)) { + return res.status(400).json({ + error: { + code: 'INVALID_TIME_FORMAT', + message: '时间格式应为HH:MM' + } + }); + } + } + + const result = await numerologyService.analyzeBazi(req.user.userId, { + name, + birthDate, + birthTime, + gender, + birthPlace + }); + + res.json({ + data: result, + message: '八字分析完成' + }); + } catch (error) { + next(error); + } +}); + +/** + * 紫微斗数分析 + * POST /api/analysis/ziwei + */ +router.post('/ziwei', async (req, res, next) => { + try { + const { name, birthDate, birthTime, gender, birthPlace } = req.body; + + // 参数验证 + if (!birthDate) { + return res.status(400).json({ + error: { + code: 'MISSING_PARAMETERS', + message: '出生日期不能为空' + } + }); + } + + // 日期格式验证 + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birthDate)) { + return res.status(400).json({ + error: { + code: 'INVALID_DATE_FORMAT', + message: '日期格式应为YYYY-MM-DD' + } + }); + } + + const result = await numerologyService.analyzeZiwei(req.user.userId, { + name, + birthDate, + birthTime, + gender, + birthPlace + }); + + res.json({ + data: result, + message: '紫微斗数分析完成' + }); + } catch (error) { + next(error); + } +}); + +/** + * 易经占卜分析 + * POST /api/analysis/yijing + */ +router.post('/yijing', async (req, res, next) => { + try { + const { question, method } = req.body; + + // 参数验证 + if (!question) { + return res.status(400).json({ + error: { + code: 'MISSING_PARAMETERS', + message: '占卜问题不能为空' + } + }); + } + + if (question.length > 200) { + return res.status(400).json({ + error: { + code: 'QUESTION_TOO_LONG', + message: '问题长度不能超过200字符' + } + }); + } + + const result = await numerologyService.analyzeYijing(req.user.userId, { + question, + method: method || '梅花易数时间起卦法' + }); + + res.json({ + data: result, + message: '易经占卜分析完成' + }); + } catch (error) { + next(error); + } +}); + +/** + * 五行分析 + * POST /api/analysis/wuxing + */ +router.post('/wuxing', async (req, res, next) => { + try { + const { name, birthDate, birthTime, gender } = req.body; + + // 参数验证 + if (!birthDate) { + return res.status(400).json({ + error: { + code: 'MISSING_PARAMETERS', + message: '出生日期不能为空' + } + }); + } + + // 日期格式验证 + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(birthDate)) { + return res.status(400).json({ + error: { + code: 'INVALID_DATE_FORMAT', + message: '日期格式应为YYYY-MM-DD' + } + }); + } + + const result = await numerologyService.analyzeWuxing(req.user.userId, { + name, + birthDate, + birthTime, + gender + }); + + res.json({ + data: result, + message: '五行分析完成' + }); + } catch (error) { + next(error); + } +}); + +/** + * 获取分析历史记录 + * GET /api/analysis/history + */ +router.get('/history', async (req, res, next) => { + try { + const { type, limit = 20, offset = 0 } = req.query; + + // 验证分析类型 + if (type && !['bazi', 'ziwei', 'yijing', 'wuxing'].includes(type)) { + return res.status(400).json({ + error: { + code: 'INVALID_TYPE', + message: '无效的分析类型' + } + }); + } + + const history = await numerologyService.getReadingHistory(req.user.userId, type); + + // 简单的分页处理 + const startIndex = parseInt(offset); + const endIndex = startIndex + parseInt(limit); + const paginatedHistory = history.slice(startIndex, endIndex); + + res.json({ + data: { + readings: paginatedHistory, + total: history.length, + hasMore: endIndex < history.length + }, + message: '获取分析历史成功' + }); + } catch (error) { + next(error); + } +}); + +/** + * 获取单个分析记录详情 + * GET /api/analysis/history/:id + */ +router.get('/history/:id', async (req, res, next) => { + try { + const { id } = req.params; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + error: { + code: 'INVALID_ID', + message: '无效的记录ID' + } + }); + } + + const history = await numerologyService.getReadingHistory(req.user.userId); + const reading = history.find(r => r.id === parseInt(id)); + + if (!reading) { + return res.status(404).json({ + error: { + code: 'READING_NOT_FOUND', + message: '分析记录不存在' + } + }); + } + + res.json({ + data: { reading }, + message: '获取分析记录成功' + }); + } catch (error) { + next(error); + } +}); + +/** + * 删除分析记录 + * DELETE /api/analysis/history/:id + */ +router.delete('/history/:id', async (req, res, next) => { + try { + const { id } = req.params; + + if (!id || isNaN(parseInt(id))) { + return res.status(400).json({ + error: { + code: 'INVALID_ID', + message: '无效的记录ID' + } + }); + } + + const success = await numerologyService.deleteReading(req.user.userId, parseInt(id)); + + if (!success) { + return res.status(404).json({ + error: { + code: 'READING_NOT_FOUND', + message: '分析记录不存在或无权删除' + } + }); + } + + res.json({ + message: '分析记录删除成功' + }); + } catch (error) { + next(error); + } +}); + +/** + * 获取用户分析统计信息 + * GET /api/analysis/stats + */ +router.get('/stats', async (req, res, next) => { + try { + const history = await numerologyService.getReadingHistory(req.user.userId); + + const stats = { + total: history.length, + byType: { + bazi: history.filter(r => r.type === 'bazi').length, + ziwei: history.filter(r => r.type === 'ziwei').length, + yijing: history.filter(r => r.type === 'yijing').length, + wuxing: history.filter(r => r.type === 'wuxing').length + }, + recent: history.slice(0, 5).map(r => ({ + id: r.id, + type: r.type, + name: r.name, + createdAt: r.createdAt + })) + }; + + res.json({ + data: stats, + message: '获取统计信息成功' + }); + } catch (error) { + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/server/routes/auth.js b/server/routes/auth.js new file mode 100644 index 0000000..c85e2b0 --- /dev/null +++ b/server/routes/auth.js @@ -0,0 +1,231 @@ +import express from 'express'; +import { authService } from '../services/authService.js'; +import { authenticateToken, rateLimit } from '../middleware/auth.js'; + +const router = express.Router(); + +// 应用速率限制 +router.use(rateLimit({ max: 20, windowMs: 15 * 60 * 1000 })); // 15分钟内最多20次请求 + +/** + * 用户注册 + * POST /api/auth/signup + */ +router.post('/signup', async (req, res, next) => { + try { + const { email, password, fullName, birthDate, birthTime, birthPlace, gender } = req.body; + + // 基本验证 + if (!email || !password) { + return res.status(400).json({ + error: { + code: 'MISSING_PARAMETERS', + message: '邮箱和密码不能为空' + } + }); + } + + // 邮箱格式验证 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ + error: { + code: 'INVALID_EMAIL', + message: '邮箱格式不正确' + } + }); + } + + // 密码强度验证 + if (password.length < 6) { + return res.status(400).json({ + error: { + code: 'WEAK_PASSWORD', + message: '密码长度至少6位' + } + }); + } + + const result = await authService.signUp({ + email, + password, + fullName, + birthDate, + birthTime, + birthPlace, + gender + }); + + res.status(201).json({ + data: result, + message: '注册成功' + }); + } catch (error) { + next(error); + } +}); + +/** + * 用户登录 + * POST /api/auth/signin + */ +router.post('/signin', async (req, res, next) => { + try { + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ + error: { + code: 'MISSING_PARAMETERS', + message: '邮箱和密码不能为空' + } + }); + } + + const result = await authService.signIn(email, password); + + res.json({ + data: result, + message: '登录成功' + }); + } catch (error) { + if (error.message.includes('邮箱或密码错误')) { + return res.status(401).json({ + error: { + code: 'INVALID_CREDENTIALS', + message: error.message + } + }); + } + next(error); + } +}); + +/** + * 获取当前用户信息 + * GET /api/auth/user + */ +router.get('/user', authenticateToken, async (req, res, next) => { + try { + const user = await authService.getUserById(req.user.userId); + + res.json({ + data: { user }, + message: '获取用户信息成功' + }); + } catch (error) { + next(error); + } +}); + +/** + * 更新用户信息 + * PUT /api/auth/user + */ +router.put('/user', authenticateToken, async (req, res, next) => { + try { + const { fullName, birthDate, birthTime, birthPlace, gender } = req.body; + + const updatedUser = await authService.updateUser(req.user.userId, { + fullName, + birthDate, + birthTime, + birthPlace, + gender + }); + + res.json({ + data: { user: updatedUser }, + message: '用户信息更新成功' + }); + } catch (error) { + next(error); + } +}); + +/** + * 验证token + * POST /api/auth/verify + */ +router.post('/verify', async (req, res, next) => { + try { + const { token } = req.body; + + if (!token) { + return res.status(400).json({ + error: { + code: 'MISSING_TOKEN', + message: 'Token不能为空' + } + }); + } + + const decoded = authService.verifyToken(token); + const user = await authService.getUserById(decoded.userId); + + res.json({ + data: { user, valid: true }, + message: 'Token验证成功' + }); + } catch (error) { + res.status(401).json({ + error: { + code: 'INVALID_TOKEN', + message: 'Token无效或已过期' + } + }); + } +}); + +/** + * 用户登出(客户端处理,服务端记录日志) + * POST /api/auth/signout + */ +router.post('/signout', authenticateToken, async (req, res) => { + try { + // 这里可以添加登出日志记录 + console.log(`用户 ${req.user.userId} 于 ${new Date().toISOString()} 登出`); + + res.json({ + message: '登出成功' + }); + } catch (error) { + res.status(500).json({ + error: { + code: 'SIGNOUT_ERROR', + message: '登出失败' + } + }); + } +}); + +/** + * 刷新token(可选功能) + * POST /api/auth/refresh + */ +router.post('/refresh', authenticateToken, async (req, res, next) => { + try { + const user = await authService.getUserById(req.user.userId); + + // 生成新的token + const jwt = await import('jsonwebtoken'); + const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production'; + const newToken = jwt.default.sign( + { userId: user.id, email: user.email }, + JWT_SECRET, + { expiresIn: '7d' } + ); + + res.json({ + data: { + user, + token: newToken + }, + message: 'Token刷新成功' + }); + } catch (error) { + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/server/services/authService.js b/server/services/authService.js new file mode 100644 index 0000000..9dba683 --- /dev/null +++ b/server/services/authService.js @@ -0,0 +1,176 @@ +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; +import { dbOperations } from '../database.js'; + +const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production'; +const JWT_EXPIRES_IN = '7d'; + +export const authService = { + /** + * 用户注册 + */ + async signUp(userData) { + const { email, password, fullName, birthDate, birthTime, birthPlace, gender } = userData; + + try { + // 检查用户是否已存在 + const existingUser = dbOperations.getUserByEmail.get(email); + if (existingUser) { + throw new Error('用户已存在'); + } + + // 密码加密 + const saltRounds = 12; + const hashedPassword = await bcrypt.hash(password, saltRounds); + + // 创建用户 + const result = dbOperations.createUser.run( + email, + hashedPassword, + fullName || null, + birthDate || null, + birthTime || null, + birthPlace || null, + gender || null + ); + + // 获取创建的用户信息 + const user = dbOperations.getUserById.get(result.lastInsertRowid); + + // 生成JWT token + const token = jwt.sign( + { userId: user.id, email: user.email }, + JWT_SECRET, + { expiresIn: JWT_EXPIRES_IN } + ); + + return { + user: { + id: user.id, + email: user.email, + fullName: user.full_name, + birthDate: user.birth_date, + birthTime: user.birth_time, + birthPlace: user.birth_place, + gender: user.gender, + createdAt: user.created_at + }, + token + }; + } catch (error) { + if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') { + throw new Error('邮箱已被注册'); + } + throw error; + } + }, + + /** + * 用户登录 + */ + async signIn(email, password) { + try { + // 查找用户 + const user = dbOperations.getUserByEmail.get(email); + if (!user) { + throw new Error('邮箱或密码错误'); + } + + // 验证密码 + const isValidPassword = await bcrypt.compare(password, user.password); + if (!isValidPassword) { + throw new Error('邮箱或密码错误'); + } + + // 生成JWT token + const token = jwt.sign( + { userId: user.id, email: user.email }, + JWT_SECRET, + { expiresIn: JWT_EXPIRES_IN } + ); + + return { + user: { + id: user.id, + email: user.email, + fullName: user.full_name, + birthDate: user.birth_date, + birthTime: user.birth_time, + birthPlace: user.birth_place, + gender: user.gender, + createdAt: user.created_at + }, + token + }; + } catch (error) { + throw error; + } + }, + + /** + * 验证JWT token + */ + verifyToken(token) { + try { + const decoded = jwt.verify(token, JWT_SECRET); + return decoded; + } catch (error) { + throw new Error('无效的token'); + } + }, + + /** + * 获取用户信息 + */ + async getUserById(userId) { + try { + const user = dbOperations.getUserById.get(userId); + if (!user) { + throw new Error('用户不存在'); + } + + return { + id: user.id, + email: user.email, + fullName: user.full_name, + birthDate: user.birth_date, + birthTime: user.birth_time, + birthPlace: user.birth_place, + gender: user.gender, + createdAt: user.created_at + }; + } catch (error) { + throw error; + } + }, + + /** + * 更新用户信息 + */ + async updateUser(userId, userData) { + const { fullName, birthDate, birthTime, birthPlace, gender } = userData; + + try { + // 检查用户是否存在 + const existingUser = dbOperations.getUserById.get(userId); + if (!existingUser) { + throw new Error('用户不存在'); + } + + // 更新用户信息 + dbOperations.updateUser.run( + fullName || existingUser.full_name, + birthDate || existingUser.birth_date, + birthTime || existingUser.birth_time, + birthPlace || existingUser.birth_place, + gender || existingUser.gender, + userId + ); + + // 返回更新后的用户信息 + return await this.getUserById(userId); + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/server/services/numerologyService.js b/server/services/numerologyService.js new file mode 100644 index 0000000..0cf618d --- /dev/null +++ b/server/services/numerologyService.js @@ -0,0 +1,638 @@ +import { dbOperations } from '../database.js'; + +// 天干地支数据 +const HEAVENLY_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; +const EARTHLY_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; +const ZODIAC_ANIMALS = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']; + +// 五行属性 +const WUXING_MAP = { + '甲': '木', '乙': '木', + '丙': '火', '丁': '火', + '戊': '土', '己': '土', + '庚': '金', '辛': '金', + '壬': '水', '癸': '水', + '子': '水', '亥': '水', + '寅': '木', '卯': '木', + '巳': '火', '午': '火', + '申': '金', '酉': '金', + '辰': '土', '戌': '土', '丑': '土', '未': '土' +}; + +// 紫微斗数星曜 +const ZIWEI_STARS = { + main: ['紫微', '天机', '太阳', '武曲', '天同', '廉贞', '天府', '太阴', '贪狼', '巨门', '天相', '天梁', '七杀', '破军'], + lucky: ['文昌', '文曲', '左辅', '右弼', '天魁', '天钺', '禄存', '天马'], + unlucky: ['擎羊', '陀罗', '火星', '铃星', '地空', '地劫'] +}; + +// 易经六十四卦 +const HEXAGRAMS = [ + { name: '乾为天', symbol: '☰☰', description: '刚健中正,自强不息' }, + { name: '坤为地', symbol: '☷☷', description: '厚德载物,包容万象' }, + { name: '水雷屯', symbol: '☵☳', description: '万物始生,艰难创业' }, + { name: '山水蒙', symbol: '☶☵', description: '启蒙教育,循序渐进' }, + // ... 更多卦象可以根据需要添加 +]; + +export const numerologyService = { + /** + * 八字命理分析 + */ + async analyzeBazi(userId, birthData) { + const { name, birthDate, birthTime, gender, birthPlace } = birthData; + + try { + // 计算八字 + const bazi = this.calculateBazi(birthDate, birthTime); + + // 五行分析 + const wuxing = this.analyzeWuxing(bazi); + + // 生成分析结果 + const analysis = this.generateBaziAnalysis(bazi, wuxing, gender); + + // 保存分析记录 + const result = dbOperations.createReading.run( + userId, + 'bazi', + name, + birthDate, + birthTime, + gender, + birthPlace, + JSON.stringify(birthData), + JSON.stringify({ bazi, wuxing }), + JSON.stringify(analysis) + ); + + return { + recordId: result.lastInsertRowid, + analysis: { + bazi, + wuxing, + analysis + } + }; + } catch (error) { + throw new Error(`八字分析失败: ${error.message}`); + } + }, + + /** + * 紫微斗数分析 + */ + async analyzeZiwei(userId, birthData) { + const { name, birthDate, birthTime, gender, birthPlace } = birthData; + + try { + // 计算紫微斗数 + const ziwei = this.calculateZiwei(birthDate, birthTime, gender); + + // 生成分析结果 + const analysis = this.generateZiweiAnalysis(ziwei, gender); + + // 保存分析记录 + const result = dbOperations.createReading.run( + userId, + 'ziwei', + name, + birthDate, + birthTime, + gender, + birthPlace, + JSON.stringify(birthData), + JSON.stringify({ ziwei }), + JSON.stringify(analysis) + ); + + return { + recordId: result.lastInsertRowid, + analysis: { + ziwei, + analysis + } + }; + } catch (error) { + throw new Error(`紫微斗数分析失败: ${error.message}`); + } + }, + + /** + * 易经占卜分析 + */ + async analyzeYijing(userId, divinationData) { + const { question, method } = divinationData; + + try { + // 生成卦象 + const hexagram = this.generateHexagram(); + + // 生成分析结果 + const analysis = this.generateYijingAnalysis(hexagram, question); + + // 保存分析记录 + const result = dbOperations.createReading.run( + userId, + 'yijing', + null, + null, + null, + null, + null, + JSON.stringify(divinationData), + JSON.stringify({ hexagram }), + JSON.stringify(analysis) + ); + + return { + recordId: result.lastInsertRowid, + analysis + }; + } catch (error) { + throw new Error(`易经占卜分析失败: ${error.message}`); + } + }, + + /** + * 五行分析 + */ + async analyzeWuxing(userId, birthData) { + const { name, birthDate, birthTime, gender } = birthData; + + try { + // 计算八字 + const bazi = this.calculateBazi(birthDate, birthTime); + + // 五行分析 + const wuxing = this.analyzeWuxing(bazi); + + // 生成建议 + const recommendations = this.generateWuxingRecommendations(wuxing); + + // 保存分析记录 + const result = dbOperations.createReading.run( + userId, + 'wuxing', + name, + birthDate, + birthTime, + gender, + null, + JSON.stringify(birthData), + JSON.stringify({ wuxing }), + JSON.stringify({ recommendations }) + ); + + return { + recordId: result.lastInsertRowid, + analysis: { + wuxingDistribution: wuxing, + balanceAnalysis: this.analyzeWuxingBalance(wuxing), + recommendations + } + }; + } catch (error) { + throw new Error(`五行分析失败: ${error.message}`); + } + }, + + /** + * 计算八字 + */ + calculateBazi(birthDate, birthTime) { + const date = new Date(birthDate + 'T' + (birthTime || '12:00')); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hour = date.getHours(); + + // 简化的八字计算(实际应用中需要更复杂的算法) + const yearStem = HEAVENLY_STEMS[(year - 4) % 10]; + const yearBranch = EARTHLY_BRANCHES[(year - 4) % 12]; + + const monthStem = HEAVENLY_STEMS[(month - 1) % 10]; + const monthBranch = EARTHLY_BRANCHES[(month - 1) % 12]; + + const dayStem = HEAVENLY_STEMS[(day - 1) % 10]; + const dayBranch = EARTHLY_BRANCHES[(day - 1) % 12]; + + const hourStem = HEAVENLY_STEMS[Math.floor(hour / 2) % 10]; + const hourBranch = EARTHLY_BRANCHES[Math.floor(hour / 2) % 12]; + + return { + year: yearStem + yearBranch, + month: monthStem + monthBranch, + day: dayStem + dayBranch, + hour: hourStem + hourBranch, + yearAnimal: ZODIAC_ANIMALS[(year - 4) % 12] + }; + }, + + /** + * 五行分析 + */ + analyzeWuxing(bazi) { + const wuxingCount = { wood: 0, fire: 0, earth: 0, metal: 0, water: 0 }; + + // 统计五行 + Object.values(bazi).forEach(pillar => { + if (typeof pillar === 'string' && pillar.length === 2) { + const stem = pillar[0]; + const branch = pillar[1]; + + const stemWuxing = WUXING_MAP[stem]; + const branchWuxing = WUXING_MAP[branch]; + + if (stemWuxing) { + const wuxingKey = this.getWuxingKey(stemWuxing); + if (wuxingKey) wuxingCount[wuxingKey]++; + } + + if (branchWuxing) { + const wuxingKey = this.getWuxingKey(branchWuxing); + if (wuxingKey) wuxingCount[wuxingKey]++; + } + } + }); + + return wuxingCount; + }, + + /** + * 获取五行英文键名 + */ + getWuxingKey(wuxing) { + const map = { '木': 'wood', '火': 'fire', '土': 'earth', '金': 'metal', '水': 'water' }; + return map[wuxing]; + }, + + /** + * 五行平衡分析 + */ + analyzeWuxingBalance(wuxing) { + const total = Object.values(wuxing).reduce((sum, count) => sum + count, 0); + const average = total / 5; + + let dominant = null; + let lacking = null; + let maxCount = 0; + let minCount = Infinity; + + Object.entries(wuxing).forEach(([element, count]) => { + if (count > maxCount) { + maxCount = count; + dominant = element; + } + if (count < minCount) { + minCount = count; + lacking = element; + } + }); + + const balanceScore = Math.round((1 - (maxCount - minCount) / total) * 100); + + return { + dominantElement: dominant, + lackingElement: lacking, + balanceScore: Math.max(0, Math.min(100, balanceScore)) + }; + }, + + /** + * 生成八字分析 + */ + generateBaziAnalysis(bazi, wuxing, gender) { + return { + character: this.generateCharacterAnalysis(bazi, wuxing, gender), + career: this.generateCareerAnalysis(bazi, wuxing), + wealth: this.generateWealthAnalysis(bazi, wuxing), + health: this.generateHealthAnalysis(bazi, wuxing), + relationships: this.generateRelationshipAnalysis(bazi, wuxing, gender) + }; + }, + + /** + * 生成性格分析 + */ + generateCharacterAnalysis(bazi, wuxing, gender) { + const traits = []; + + // 根据日干分析性格 + const dayStem = bazi.day[0]; + switch (dayStem) { + case '甲': + traits.push('性格刚直,有领导才能,喜欢挑战'); + break; + case '乙': + traits.push('性格温和,适应能力强,善于合作'); + break; + case '丙': + traits.push('性格开朗,热情洋溢,富有创造力'); + break; + case '丁': + traits.push('性格细腻,思维敏锐,注重细节'); + break; + default: + traits.push('性格特点需要结合具体情况分析'); + } + + // 根据五行平衡分析性格 + const balance = this.analyzeWuxingBalance(wuxing); + if (balance.dominantElement === 'wood') { + traits.push('木旺之人,性格积极向上,富有生命力'); + } else if (balance.dominantElement === 'fire') { + traits.push('火旺之人,性格热情奔放,行动力强'); + } + + return traits.join(';'); + }, + + /** + * 生成事业分析 + */ + generateCareerAnalysis(bazi, wuxing) { + const advice = []; + const balance = this.analyzeWuxingBalance(wuxing); + + if (balance.dominantElement === 'wood') { + advice.push('适合从事教育、文化、林业等与木相关的行业'); + } else if (balance.dominantElement === 'fire') { + advice.push('适合从事能源、娱乐、餐饮等与火相关的行业'); + } else if (balance.dominantElement === 'earth') { + advice.push('适合从事房地产、农业、建筑等与土相关的行业'); + } else if (balance.dominantElement === 'metal') { + advice.push('适合从事金融、机械、汽车等与金相关的行业'); + } else if (balance.dominantElement === 'water') { + advice.push('适合从事航运、水利、贸易等与水相关的行业'); + } + + return advice.join(';'); + }, + + /** + * 生成财运分析 + */ + generateWealthAnalysis(bazi, wuxing) { + return '财运需要通过努力获得,建议理性投资,稳健理财'; + }, + + /** + * 生成健康分析 + */ + generateHealthAnalysis(bazi, wuxing) { + const balance = this.analyzeWuxingBalance(wuxing); + const advice = []; + + if (balance.lackingElement === 'wood') { + advice.push('注意肝胆健康,多接触绿色植物'); + } else if (balance.lackingElement === 'fire') { + advice.push('注意心脏健康,保持乐观心态'); + } + + return advice.length > 0 ? advice.join(';') : '身体健康状况良好,注意均衡饮食和适量运动'; + }, + + /** + * 生成感情分析 + */ + generateRelationshipAnalysis(bazi, wuxing, gender) { + return '感情运势平稳,建议真诚待人,珍惜缘分'; + }, + + /** + * 计算紫微斗数 + */ + calculateZiwei(birthDate, birthTime, gender) { + const date = new Date(birthDate + 'T' + (birthTime || '12:00')); + const hour = date.getHours(); + + // 简化的紫微斗数计算 + const mingGongIndex = Math.floor(hour / 2); + const mingGong = EARTHLY_BRANCHES[mingGongIndex]; + + // 随机分配主星(实际应用中需要复杂的计算) + const mainStars = this.getRandomStars(ZIWEI_STARS.main, 2); + const luckyStars = this.getRandomStars(ZIWEI_STARS.lucky, 3); + const unluckyStars = this.getRandomStars(ZIWEI_STARS.unlucky, 2); + + // 生成十二宫位 + const twelvePalaces = this.generateTwelvePalaces(mingGongIndex); + + // 四化飞星 + const siHua = this.generateSiHua(); + + return { + mingGong, + mingGongXing: mainStars, + shiErGong: twelvePalaces, + siHua, + birthChart: { + mingGongPosition: mingGong, + mainStars, + luckyStars, + unluckyStars + } + }; + }, + + /** + * 随机获取星曜 + */ + getRandomStars(starArray, count) { + const shuffled = [...starArray].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, count); + }, + + /** + * 生成十二宫位 + */ + generateTwelvePalaces(mingGongIndex) { + const palaces = ['命宫', '兄弟宫', '夫妻宫', '子女宫', '财帛宫', '疾厄宫', '迁移宫', '交友宫', '事业宫', '田宅宫', '福德宫', '父母宫']; + const result = {}; + + palaces.forEach((palace, index) => { + const branchIndex = (mingGongIndex + index) % 12; + result[palace] = { + branch: EARTHLY_BRANCHES[branchIndex], + mainStars: this.getRandomStars(ZIWEI_STARS.main, 1), + interpretation: `${palace}的详细解读内容` + }; + }); + + return result; + }, + + /** + * 生成四化飞星 + */ + generateSiHua() { + return { + huaLu: { star: '廉贞', meaning: '财禄亨通,运势顺遂' }, + huaQuan: { star: '破军', meaning: '权力地位,事业有成' }, + huaKe: { star: '武曲', meaning: '贵人相助,学业有成' }, + huaJi: { star: '太阳', meaning: '需要谨慎,防范风险' } + }; + }, + + /** + * 生成紫微斗数分析 + */ + generateZiweiAnalysis(ziwei, gender) { + return { + character: { + overview: '根据命宫主星分析,您的性格特点突出', + personalityTraits: '具有领导能力,做事果断,富有责任感' + }, + career: { + suitableIndustries: ['管理', '金融', '教育'], + careerAdvice: '适合从事需要决策和领导的工作' + }, + wealth: { + wealthPattern: '财运稳定,通过努力可以获得不错的收入' + }, + health: { + constitution: '体质较好,注意劳逸结合', + wellnessAdvice: '保持规律作息,适量运动' + }, + relationships: { + marriageFortune: '感情运势平稳,婚姻美满', + spouseCharacteristics: '伴侣性格温和,相处和谐' + } + }; + }, + + /** + * 生成卦象 + */ + generateHexagram() { + const randomIndex = Math.floor(Math.random() * HEXAGRAMS.length); + const hexagram = HEXAGRAMS[randomIndex]; + + return { + name: hexagram.name, + symbol: hexagram.symbol, + description: hexagram.description, + upperTrigram: hexagram.symbol.substring(0, 1), + lowerTrigram: hexagram.symbol.substring(1, 2) + }; + }, + + /** + * 生成易经分析 + */ + generateYijingAnalysis(hexagram, question) { + return { + basicInfo: { + divinationData: { + question, + method: '梅花易数时间起卦法', + divinationTime: new Date().toISOString() + }, + hexagramInfo: { + mainHexagram: hexagram.name, + hexagramDescription: hexagram.description, + upperTrigram: hexagram.upperTrigram, + lowerTrigram: hexagram.lowerTrigram, + detailedInterpretation: `${hexagram.name}卦象显示${hexagram.description}` + } + }, + detailedAnalysis: { + hexagramAnalysis: { + primaryMeaning: '此卦象征着新的开始和机遇', + judgment: '吉', + image: '天行健,君子以自强不息' + }, + changingLinesAnalysis: { + changingLinePosition: '六二', + lineMeaning: '见龙在田,利见大人' + }, + changingHexagram: { + name: '天风姤', + meaning: '变化中蕴含新的机遇', + transformationInsight: '顺应变化,把握时机' + } + }, + lifeGuidance: { + overallFortune: '整体运势向好,宜积极进取', + careerGuidance: '事业发展顺利,可以大胆尝试', + relationshipGuidance: '人际关系和谐,感情稳定', + wealthGuidance: '财运亨通,投资需谨慎' + }, + divinationWisdom: { + keyMessage: '天道酬勤,自强不息', + actionAdvice: '保持积极心态,勇于面对挑战', + philosophicalInsight: '变化是永恒的,适应变化才能成功' + } + }; + }, + + /** + * 生成五行建议 + */ + generateWuxingRecommendations(wuxing) { + const balance = this.analyzeWuxingBalance(wuxing); + const recommendations = { + colors: [], + directions: [], + careerFields: [], + lifestyleAdvice: '' + }; + + if (balance.lackingElement === 'wood') { + recommendations.colors = ['绿色', '青色']; + recommendations.directions = ['东方']; + recommendations.careerFields = ['教育', '文化', '林业']; + recommendations.lifestyleAdvice = '多接触自然,种植绿色植物'; + } else if (balance.lackingElement === 'fire') { + recommendations.colors = ['红色', '橙色']; + recommendations.directions = ['南方']; + recommendations.careerFields = ['能源', '娱乐', '餐饮']; + recommendations.lifestyleAdvice = '保持乐观心态,多参加社交活动'; + } + + return recommendations; + }, + + /** + * 获取用户分析历史 + */ + async getReadingHistory(userId, type = null) { + try { + let readings; + if (type) { + readings = dbOperations.getReadingsByUserIdAndType.all(userId, type); + } else { + readings = dbOperations.getReadingsByUserId.all(userId); + } + + return readings.map(reading => ({ + id: reading.id, + type: reading.reading_type, + name: reading.name, + birthDate: reading.birth_date, + birthTime: reading.birth_time, + gender: reading.gender, + birthPlace: reading.birth_place, + status: reading.status, + createdAt: reading.created_at, + results: reading.results ? JSON.parse(reading.results) : null, + analysis: reading.analysis ? JSON.parse(reading.analysis) : null + })); + } catch (error) { + throw new Error(`获取分析历史失败: ${error.message}`); + } + }, + + /** + * 删除分析记录 + */ + async deleteReading(userId, readingId) { + try { + const result = dbOperations.deleteReading.run(readingId, userId); + return result.changes > 0; + } catch (error) { + throw new Error(`删除分析记录失败: ${error.message}`); + } + } +}; \ No newline at end of file diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 0368409..fc57b21 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; -import { User } from '@supabase/supabase-js'; +import { User } from '../lib/localApi'; import { supabase } from '../lib/supabase'; interface AuthContextType { @@ -25,19 +25,26 @@ export function AuthProvider({ children }: AuthProviderProps) { async function loadUser() { setLoading(true); try { - const { data: { user } } = await supabase.auth.getUser(); - setUser(user); + const response = await supabase.auth.getUser(); + if (response.data?.user) { + setUser(response.data.user); + } else { + setUser(null); + } + } catch (error) { + console.error('加载用户信息失败:', error); + setUser(null); } finally { setLoading(false); } } loadUser(); - // Set up auth listener - KEEP SIMPLE, avoid any async operations in callback + // Set up auth listener - 本地API版本 const { data: { subscription } } = supabase.auth.onAuthStateChange( (_event, session) => { - // NEVER use any async operations in callback setUser(session?.user || null); + setLoading(false); } ); @@ -46,18 +53,25 @@ export function AuthProvider({ children }: AuthProviderProps) { // Auth methods async function signIn(email: string, password: string) { - return await supabase.auth.signInWithPassword({ email, password }); + const response = await supabase.auth.signInWithPassword({ email, password }); + if (response.data?.user) { + setUser(response.data.user); + } + return response; } async function signUp(email: string, password: string) { - return await supabase.auth.signUp({ - email, - password, - }); + const response = await supabase.auth.signUp({ email, password }); + if (response.data?.user) { + setUser(response.data.user); + } + return response; } async function signOut() { - return await supabase.auth.signOut(); + const response = await supabase.auth.signOut(); + setUser(null); + return response; } return ( diff --git a/src/lib/localApi.ts b/src/lib/localApi.ts new file mode 100644 index 0000000..5ebaebf --- /dev/null +++ b/src/lib/localApi.ts @@ -0,0 +1,322 @@ +// 本地API客户端,替换Supabase + +const API_BASE_URL = 'http://localhost:3001/api'; + +// 存储token的key +const TOKEN_KEY = 'numerology_token'; + +// API响应类型 +interface ApiResponse { + data?: T; + error?: { + code: string; + message: string; + }; + message?: string; +} + +// 用户类型 +export interface User { + id: number; + email: string; + fullName?: string; + birthDate?: string; + birthTime?: string; + birthPlace?: string; + gender?: 'male' | 'female'; + createdAt: string; +} + +// 认证响应类型 +interface AuthResponse { + user: User; + token: string; +} + +// 分析记录类型 +export interface Reading { + id: number; + type: 'bazi' | 'ziwei' | 'yijing' | 'wuxing'; + name?: string; + birthDate?: string; + birthTime?: string; + gender?: 'male' | 'female'; + birthPlace?: string; + status: string; + createdAt: string; + results?: any; + analysis?: any; +} + +class LocalApiClient { + private baseUrl: string; + + constructor(baseUrl: string = API_BASE_URL) { + this.baseUrl = baseUrl; + } + + // 获取存储的token + private getToken(): string | null { + return localStorage.getItem(TOKEN_KEY); + } + + // 设置token + private setToken(token: string): void { + localStorage.setItem(TOKEN_KEY, token); + } + + // 清除token + private clearToken(): void { + localStorage.removeItem(TOKEN_KEY); + } + + // 通用请求方法 + private async request( + endpoint: string, + options: RequestInit = {} + ): Promise> { + const url = `${this.baseUrl}${endpoint}`; + const token = this.getToken(); + + const config: RequestInit = { + headers: { + 'Content-Type': 'application/json', + ...(token && { Authorization: `Bearer ${token}` }), + ...options.headers, + }, + ...options, + }; + + try { + const response = await fetch(url, config); + const data = await response.json(); + + if (!response.ok) { + return { error: data.error || { code: 'UNKNOWN_ERROR', message: '请求失败' } }; + } + + return data; + } catch (error) { + console.error('API请求错误:', error); + return { + error: { + code: 'NETWORK_ERROR', + message: '网络连接失败,请检查本地服务器是否启动' + } + }; + } + } + + // 认证相关方法 + auth = { + // 用户注册 + signUp: async (userData: { + email: string; + password: string; + fullName?: string; + birthDate?: string; + birthTime?: string; + birthPlace?: string; + gender?: 'male' | 'female'; + }): Promise> => { + const response = await this.request('/auth/signup', { + method: 'POST', + body: JSON.stringify(userData), + }); + + if (response.data?.token) { + this.setToken(response.data.token); + } + + return response; + }, + + // 用户登录 + signInWithPassword: async (credentials: { + email: string; + password: string; + }): Promise> => { + const response = await this.request('/auth/signin', { + method: 'POST', + body: JSON.stringify(credentials), + }); + + if (response.data?.token) { + this.setToken(response.data.token); + } + + return response; + }, + + // 用户登出 + signOut: async (): Promise => { + const response = await this.request('/auth/signout', { + method: 'POST', + }); + + this.clearToken(); + return response; + }, + + // 获取当前用户 + getUser: async (): Promise> => { + return await this.request<{ user: User }>('/auth/user'); + }, + + // 验证token + verifyToken: async (token?: string): Promise> => { + return await this.request<{ user: User; valid: boolean }>('/auth/verify', { + method: 'POST', + body: JSON.stringify({ token: token || this.getToken() }), + }); + }, + + // 更新用户信息 + updateUser: async (userData: Partial): Promise> => { + return await this.request<{ user: User }>('/auth/user', { + method: 'PUT', + body: JSON.stringify(userData), + }); + }, + + // 监听认证状态变化(模拟Supabase的onAuthStateChange) + onAuthStateChange: (callback: (event: string, session: { user: User } | null) => void) => { + // 简单实现:检查token是否存在 + const checkAuth = async () => { + const token = this.getToken(); + if (token) { + const response = await this.auth.verifyToken(token); + if (response.data?.valid && response.data.user) { + callback('SIGNED_IN', { user: response.data.user }); + } else { + this.clearToken(); + callback('SIGNED_OUT', null); + } + } else { + callback('SIGNED_OUT', null); + } + }; + + // 立即检查一次 + checkAuth(); + + // 返回取消订阅的函数 + return { + data: { + subscription: { + unsubscribe: () => { + // 本地实现不需要取消订阅 + } + } + } + }; + } + }; + + // 分析功能相关方法 + functions = { + // 调用分析函数 + invoke: async (functionName: string, options: { body: any }): Promise => { + const endpointMap: { [key: string]: string } = { + 'bazi-analyzer': '/analysis/bazi', + 'ziwei-analyzer': '/analysis/ziwei', + 'yijing-analyzer': '/analysis/yijing', + 'bazi-wuxing-analysis': '/analysis/wuxing', + 'bazi-details': '/analysis/bazi', + 'reading-history': '/analysis/history' + }; + + const endpoint = endpointMap[functionName]; + if (!endpoint) { + return { + error: { + code: 'FUNCTION_NOT_FOUND', + message: `未知的分析函数: ${functionName}` + } + }; + } + + // 特殊处理历史记录请求 + if (functionName === 'reading-history') { + if (options.body.action === 'delete') { + return await this.request(`${endpoint}/${options.body.readingId}`, { + method: 'DELETE' + }); + } else { + const queryParams = new URLSearchParams(); + if (options.body.type) queryParams.append('type', options.body.type); + if (options.body.limit) queryParams.append('limit', options.body.limit.toString()); + if (options.body.offset) queryParams.append('offset', options.body.offset.toString()); + + return await this.request(`${endpoint}?${queryParams.toString()}`); + } + } + + return await this.request(endpoint, { + method: 'POST', + body: JSON.stringify(options.body), + }); + } + }; + + // 数据库操作(模拟Supabase的数据库操作) + from = (table: string) => { + return { + select: (columns: string = '*') => ({ + eq: (column: string, value: any) => ({ + single: async () => { + // 根据表名和操作类型调用相应的API + if (table === 'user_profiles') { + return await this.auth.getUser(); + } + return { data: null, error: null }; + } + }), + order: (column: string, options?: { ascending: boolean }) => ({ + limit: (count: number) => ({ + async all() { + if (table === 'numerology_readings') { + const response = await this.functions.invoke('reading-history', { + body: { limit: count } + }); + return { data: response.data?.readings || [], error: response.error }; + } + return { data: [], error: null }; + } + }) + }) + }), + + update: (data: any) => ({ + eq: (column: string, value: any) => ({ + select: () => ({ + single: async () => { + if (table === 'user_profiles') { + return await this.auth.updateUser(data); + } + return { data: null, error: null }; + } + }) + }) + }), + + insert: (data: any) => ({ + select: () => ({ + single: async () => { + // 插入操作通常通过分析API完成 + return { data: null, error: null }; + } + }) + }) + }; + }; +} + +// 创建全局实例 +export const localApi = new LocalApiClient(); + +// 导出兼容Supabase的接口 +export const supabase = localApi; + +// 默认导出 +export default localApi; \ No newline at end of file diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index ed10b1f..be63d5d 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -1,10 +1,8 @@ -import { createClient } from '@supabase/supabase-js' +// 本地化改造:使用本地API替代Supabase +import { localApi } from './localApi'; -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY +// 导出本地API客户端,保持与原Supabase客户端相同的接口 +export const supabase = localApi; -if (!supabaseUrl || !supabaseAnonKey) { - throw new Error('Missing Supabase environment variables') -} - -export const supabase = createClient(supabaseUrl, supabaseAnonKey) \ No newline at end of file +// 为了向后兼容,也可以导出为默认 +export default localApi; \ No newline at end of file