From 72ea29f4350de7e077b44c2a04efef16d0e4b824 Mon Sep 17 00:00:00 2001 From: luorijun Date: Sat, 27 Sep 2025 10:37:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=20orm=20=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E5=88=B0=20drizzle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 - bun.lock | 98 ++++++- eslint.config.mjs | 9 + localhost.session.sql | 0 package.json | 6 +- prisma/init.sql | 29 -- prisma/schema.prisma | 115 -------- src/actions/auth.ts | 75 ++--- src/actions/config.ts | 41 ++- src/actions/stats.ts | 276 +++++++++--------- src/actions/user.ts | 99 +++---- .../dashboard/components/allocationStatus.tsx | 6 +- src/app/dashboard/components/edge.tsx | 7 +- .../dashboard/components/gatewayConfig.tsx | 137 +++------ src/lib/api.ts | 16 + src/lib/drizzle/index.ts | 21 ++ src/lib/drizzle/schema.ts | 95 ++++++ src/lib/prisma.ts | 15 - src/lib/utils.ts | 15 +- 19 files changed, 538 insertions(+), 523 deletions(-) create mode 100644 localhost.session.sql delete mode 100644 prisma/init.sql delete mode 100644 prisma/schema.prisma create mode 100644 src/lib/api.ts create mode 100644 src/lib/drizzle/index.ts create mode 100644 src/lib/drizzle/schema.ts delete mode 100644 src/lib/prisma.ts diff --git a/Dockerfile b/Dockerfile index cd8c386..2d75825 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,6 @@ WORKDIR /app COPY --from=dep /app/node_modules ./node_modules COPY . . -RUN bun prisma generate RUN bun run build # 生产阶段 diff --git a/bun.lock b/bun.lock index c759d09..3283db6 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,9 @@ "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "drizzle-orm": "^0.44.5", "lucide-react": "^0.541.0", + "mysql2": "^3.15.1", "next": "15.4.7", "next-themes": "^0.4.6", "react": "19.1.0", @@ -26,16 +28,16 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", - "@prisma/client": "^6.16.2", "@stylistic/eslint-plugin": "^5.4.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "drizzle-kit": "^0.31.4", "eslint": "^9", "eslint-config-next": "15.5.0", + "eslint-plugin-drizzle": "^0.2.3", "postcss": "^8.5.6", - "prisma": "^6.16.1", "tailwindcss": "^4", "tsx": "^4.20.4", "tw-animate-css": "^1.3.7", @@ -47,12 +49,18 @@ "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "https://registry.npmmirror.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], "@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="], + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "https://registry.npmmirror.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "https://registry.npmmirror.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], @@ -463,6 +471,8 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + "axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -475,6 +485,8 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "buffer-from": ["buffer-from@1.1.2", "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], @@ -541,6 +553,8 @@ "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "denque": ["denque@2.1.0", "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], @@ -551,6 +565,10 @@ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "drizzle-kit": ["drizzle-kit@0.31.4", "https://registry.npmmirror.com/drizzle-kit/-/drizzle-kit-0.31.4.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="], + + "drizzle-orm": ["drizzle-orm@0.44.5", "https://registry.npmmirror.com/drizzle-orm/-/drizzle-orm-0.44.5.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "effect": ["effect@3.16.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg=="], @@ -579,6 +597,8 @@ "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], + "esbuild-register": ["esbuild-register@3.6.0", "https://registry.npmmirror.com/esbuild-register/-/esbuild-register-3.6.0.tgz", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.33.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA=="], @@ -591,6 +611,8 @@ "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], + "eslint-plugin-drizzle": ["eslint-plugin-drizzle@0.2.3", "https://registry.npmmirror.com/eslint-plugin-drizzle/-/eslint-plugin-drizzle-0.2.3.tgz", { "peerDependencies": { "eslint": ">=8.0.0" } }, "sha512-BO+ymHo33IUNoJlC0rbd7HP9EwwpW4VIp49R/tWQF/d2E1K2kgTf0tCXT0v9MSiBr6gGR1LtPwMLapTKEWSg9A=="], + "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], @@ -649,6 +671,8 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + "generate-function": ["generate-function@2.3.1", "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], @@ -687,6 +711,8 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "iconv-lite": ["iconv-lite@0.7.0", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -731,6 +757,8 @@ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-property": ["is-property@1.0.2", "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], @@ -805,8 +833,14 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "long": ["long@5.3.2", "https://registry.npmmirror.com/long/-/long-5.3.2.tgz", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lru-cache": ["lru-cache@7.18.3", "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "lru.min": ["lru.min@1.1.2", "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.2.tgz", {}, "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg=="], + "lucide-react": ["lucide-react@0.541.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-s0Vircsu5WaGv2KoJZ5+SoxiAJ3UXV5KqEM3eIFDHaHkcLIFdIWgXtZ412+Gh02UsdS7Was+jvEpBvPCWQISlg=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], @@ -829,6 +863,10 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mysql2": ["mysql2@3.15.1", "https://registry.npmmirror.com/mysql2/-/mysql2-3.15.1.tgz", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-WZMIRZstT2MFfouEaDz/AGFnGi1A2GwaDe7XvKTdRJEYiAHbOrh4S3d8KFmQeh11U85G+BFjIvS1Di5alusZsw=="], + + "named-placeholders": ["named-placeholders@1.1.3", "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.3.tgz", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "napi-postinstall": ["napi-postinstall@0.3.3", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow=="], @@ -943,10 +981,14 @@ "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "seq-queue": ["seq-queue@0.0.5", "https://registry.npmmirror.com/seq-queue/-/seq-queue-0.0.5.tgz", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], @@ -971,8 +1013,14 @@ "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + "source-map": ["source-map@0.6.1", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "sqlstring": ["sqlstring@2.3.3", "https://registry.npmmirror.com/sqlstring/-/sqlstring-2.3.3.tgz", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], + "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], @@ -1067,6 +1115,8 @@ "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], @@ -1127,6 +1177,50 @@ "sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], diff --git a/eslint.config.mjs b/eslint.config.mjs index 0568e14..83c8eba 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' import { FlatCompat } from '@eslint/eslintrc' import stylistic from '@stylistic/eslint-plugin' +import drizzle from 'eslint-plugin-drizzle' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -23,6 +24,14 @@ const eslintConfig = [ '@typescript-eslint/no-unused-vars': 'off', }, }, + { + plugins: { + drizzle, + }, + rules: { + ...drizzle.configs.recommended.rules, + }, + }, ] export default eslintConfig diff --git a/localhost.session.sql b/localhost.session.sql new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 8b55614..2cd1d5a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "drizzle-orm": "^0.44.5", "lucide-react": "^0.541.0", + "mysql2": "^3.15.1", "next": "15.4.7", "next-themes": "^0.4.6", "react": "19.1.0", @@ -31,16 +33,16 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", - "@prisma/client": "^6.16.2", "@stylistic/eslint-plugin": "^5.4.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "drizzle-kit": "^0.31.4", "eslint": "^9", "eslint-config-next": "15.5.0", + "eslint-plugin-drizzle": "^0.2.3", "postcss": "^8.5.6", - "prisma": "^6.16.1", "tailwindcss": "^4", "tsx": "^4.20.4", "tw-animate-css": "^1.3.7", diff --git a/prisma/init.sql b/prisma/init.sql deleted file mode 100644 index 0a8fc7b..0000000 --- a/prisma/init.sql +++ /dev/null @@ -1,29 +0,0 @@ --- jdbox.sessions definition -CREATE TABLE `sessions` ( - `id` varchar(191) NOT NULL, - `expires` datetime(3) NOT NULL, - `createdAt` datetime(3) NOT NULL DEFAULT current_timestamp(3), - `userId` int(11) NOT NULL, - PRIMARY KEY (`id`), - KEY `sessions_userId_idx` (`userId`) USING BTREE -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; - --- jdbox.users definition -CREATE TABLE `users` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(191) DEFAULT NULL, - `createdAt` datetime(3) NOT NULL DEFAULT current_timestamp(3), - `password` varchar(191) NOT NULL, - `account` varchar(191) NOT NULL, - `updatedAt` datetime(3) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `users_phone_key` (`phone`) -) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; - --- 插入初始用户 -INSERT INTO users(phone, password, name) -VALUES( - 'admin', - '$2a$10$k.p3.s28OdLmGCMtuvBoqOxABp03h0Zhmop4eqqlR8sIjkThCcsnS', - '管理员' - ); diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index 73ae3f6..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,115 +0,0 @@ -generator client { - provider = "prisma-client" - output = "../src/generated/prisma" -} - -datasource db { - provider = "mysql" - url = env("DATABASE_URL") -} - -model change { - id Int @id @default(autoincrement()) - time DateTime? @db.Timestamp(0) - city Int? - macaddr String @db.VarChar(20) - edge_new String @db.VarChar(20) - edge_old String? @db.VarChar(20) - info String @db.VarChar(500) - network String @db.VarChar(20) - createtime DateTime @default(now()) @db.DateTime(0) - - @@index([edge_new], map: "edge_new") - @@index([time], map: "change_time_index") -} - -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model cityhash { - id Int @id @default(autoincrement()) @db.UnsignedInt - macaddr String? @db.VarChar(20) - city String @db.VarChar(20) - num Int - hash String @db.VarChar(100) - label String? @db.VarChar(20) - count Int @default(0) - offset Int @default(0) - createtime DateTime @default(now()) @db.DateTime(0) - updatetime DateTime @default(now()) @db.DateTime(0) -} - -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model edge { - id Int @id @default(autoincrement()) - macaddr String @unique(map: "edge_macaddr_idx") @db.VarChar(17) - public String @db.VarChar(255) - isp String @db.VarChar(255) - single Boolean - sole Boolean - arch Boolean - online Int @default(0) - city_id Int - active Boolean - - @@index([active], map: "edge_active_index") - @@index([city_id]) - @@index([isp], map: "edge_isp_index") - @@index([public], map: "edge_public_index") -} - -model gateway { - id Int @id @default(autoincrement()) @db.UnsignedInt - macaddr String @db.VarChar(20) - table Int - edge String @db.VarChar(20) - network String @db.VarChar(20) - cityhash String @db.VarChar(100) - label String? @db.VarChar(20) - user String? @db.VarChar(20) - inner_ip String? @db.VarChar(20) - ischange Int @default(0) @db.TinyInt - isonline Int @default(0) @db.TinyInt - onlinenum Int @default(0) - createtime DateTime @default(now()) @db.DateTime(0) - updatetime DateTime @default(now()) @db.DateTime(0) - - @@index([inner_ip], map: "inner_ip") -} - -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model token { - id Int @id @default(autoincrement()) @db.UnsignedInt - setid Int @default(1) - change_count Int - limit_count Int @default(32000) - token String @db.VarChar(1000) - macaddr String @db.VarChar(100) - token_time DateTime @db.DateTime(0) - inner_ip String? @db.VarChar(20) - l2ip String? @db.VarChar(20) - enable Boolean @default(true) - createtime DateTime @default(now()) @db.DateTime(0) - updatetime DateTime @default(now()) @db.DateTime(0) -} - -model User { - id Int @id @default(autoincrement()) - account String @unique - password String - name String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - sessions Session[] - - @@map("users") -} - -model Session { - id String @id - userId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - expires DateTime - createdAt DateTime @default(now()) - - @@index([userId]) - @@map("sessions") -} diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 8bcbaa5..f8947c1 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -1,58 +1,57 @@ 'use server' -import prisma, { User } from '@/lib/prisma' // 使用统一的prisma实例 +import drizzle, { eq, sessions, users } from '@/lib/drizzle' import { compare } from 'bcryptjs' +import { randomUUID } from 'crypto' import { cookies } from 'next/headers' import { z } from 'zod' -const loginSchema = z.object({ - account: z.string().min(3, '账号至少需要3个字符'), - password: z.string().min(6, '密码至少需要6个字符'), +const loginProps = z.object({ + account: z.string().min(3, '账号至少需要3个字符').trim(), + password: z.string().min(6, '密码至少需要6个字符').trim(), }) -export async function login(props: { - account: string - password: string -}) { +export async function login(rawParams: z.infer) { try { - const result = loginSchema.parse(props) + const params = loginProps.parse(rawParams) - const user: User | null = await prisma.user.findFirst({ - where: { - OR: [ - { account: result.account.trim() }, - { password: result.password.trim() }, - ], - }, + // 查找用户 + const user = await drizzle.query.users.findFirst({ + where: eq(users.account, params.account), }) if (!user) { return { success: false, - error: '用户不存在或密码未设置', + error: '用户不存在或密码错误', } } // 验证密码 - const passwordMatch = await compare(result.password, user.password || '') + const passwordMatch = await compare(params.password, user.password || '') if (!passwordMatch) { return { success: false, - error: '密码错误', + error: '用户不存在或密码错误', } } // 创建会话 - const sessionToken = crypto.randomUUID() - await prisma.session.create({ - data: { - id: sessionToken, - userId: user.id, - expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - }, + const sessionToken = randomUUID() + await drizzle.insert(sessions).values({ + id: sessionToken, + userid: user.id, + expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }) // 设置cookie - const response = { + const cookieStore = await cookies() + cookieStore.set('session', sessionToken, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + maxAge: 60 * 60 * 24 * 7, + }) + + return { success: true, user: { id: user.id, @@ -60,15 +59,6 @@ export async function login(props: { name: user.name, }, } - - const cookieStore = await cookies() - cookieStore.set('session', sessionToken, { - httpOnly: true, - // secure: process.env.NODE_ENV === 'production', - maxAge: 60 * 60 * 24 * 7, - }) - - return response } catch (error) { console.error('登录错误:', error) @@ -86,24 +76,19 @@ export async function logout() { // 删除数据库中的session(如果存在) if (sessionToken) { - await prisma.session.deleteMany({ - where: { id: sessionToken }, - }).catch(() => { - // 忽略删除错误,确保cookie被清除 - }) + await drizzle.delete(sessions).where(eq(sessions.id, sessionToken)) } // 清除cookie - const response = { success: true } cookieStore.set('session', '', { httpOnly: true, secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', maxAge: 0, // 立即过期 - path: '/', }) - return response + return { + success: true, + } } catch (error) { console.error('退出错误:', error) diff --git a/src/actions/config.ts b/src/actions/config.ts index 4c31e8b..d428bcb 100644 --- a/src/actions/config.ts +++ b/src/actions/config.ts @@ -1,18 +1,47 @@ 'use server' -import prisma from '@/lib/prisma' -import { log } from 'console' + +import drizzle, { eq, gateway } from '@/lib/drizzle' +import z from 'zod' export async function findConfigs(params: { macaddr: string }) { try { - return await prisma.gateway.findMany({ - where: { - macaddr: params.macaddr, - }, + return await drizzle.query.gateway.findMany({ + where: eq(gateway.macaddr, params.macaddr), }) } catch (e) { throw new Error('查询配置失败: ' + (e as Error).message) } } + +const pageConfigsParams = z.object({ + macaddr: z.string().min(1, 'MAC地址不能为空').trim(), + page: z.number().min(1).default(1), + size: z.number().min(10).max(250).default(100), +}) + +export async function pageConfigs(rawParams: z.infer) { + try { + const params = pageConfigsParams.parse(rawParams) + const offset = (params.page - 1) * params.size + const limit = params.size + + const [data, total] = await Promise.all([ + drizzle.select().from(gateway).where(eq(gateway.macaddr, params.macaddr)).offset(offset).limit(limit), + drizzle.$count(gateway, eq(gateway.macaddr, params.macaddr)), + ]) + + return { + success: true, + data, + total, + page: params.page, + size: params.size, + } + } + catch (e) { + throw new Error('获取配置失败: ' + (e as Error).message) + } +} diff --git a/src/actions/stats.ts b/src/actions/stats.ts index 6399f45..2cdb7d2 100644 --- a/src/actions/stats.ts +++ b/src/actions/stats.ts @@ -1,49 +1,48 @@ 'use server' -import prisma from '@/lib/prisma' + +import { Page, Res } from '@/lib/api' +import drizzle, { change, cityhash, count, desc, edge, eq, gateway, is, sql, token } from '@/lib/drizzle' export type AllocationStatus = { city: string - count: bigint - assigned: bigint + count: number + assigned: number } // 城市分配状态 -export async function getAllocationStatus(hours: number) { +export async function getAllocationStatus(hours: number = 24) { try { - const hoursNum = hours || 24 - // 使用参数化查询防止SQL注入 - const result: AllocationStatus[] = await prisma.$queryRaw` - SELECT - city, - c1.count AS count, - c2.assigned AS assigned - FROM - cityhash - LEFT JOIN ( - SELECT - city_id, - COUNT(*) AS count - FROM - edge - WHERE - active = 1 - GROUP BY - city_id - ) c1 ON c1.city_id = cityhash.id - LEFT JOIN ( - SELECT - city AS city_id, - COUNT(*) AS assigned - FROM - \`change\` - WHERE - time > NOW() - INTERVAL ${hoursNum} HOUR - GROUP BY - city - ) c2 ON c2.city_id = cityhash.id - WHERE - cityhash.macaddr IS NOT NULL; - ` + const c1 = drizzle + .select({ + cityId: edge.cityId, + count: count().as('count'), + }) + .from(edge) + .where(eq(edge.active, 1)) + .groupBy(edge.cityId) + .as('c1') + + const c2 = drizzle + .select({ + cityId: change.city, + assigned: count().as('assigned'), + }) + .from(change) + .where(sql`time > NOW() - INTERVAL ${hours} HOUR`) + .groupBy(change.city) + .as('c2') + + const result = await drizzle + .select({ + city: cityhash.city, + count: sql`c1.count`, + assigned: sql`ifnull(c2.assigned, 0)`, + }) + .from(cityhash) + .leftJoin(c1, eq(c1.cityId, cityhash.id)) + .leftJoin(c2, eq(c2.cityId, cityhash.id)) + .where(sql`cityhash.macaddr is not null`) + return { success: true, data: result, @@ -70,11 +69,16 @@ export type GatewayInfo = { // 获取网关基本信息 export async function getGatewayInfo() { try { - const result: GatewayInfo[] = await prisma.$queryRaw` - SELECT macaddr, inner_ip, setid, enable - FROM token - ORDER BY macaddr - ` + const result = await drizzle + .select({ + macaddr: token.macaddr, + inner_ip: token.innerIp, + setid: token.setid, + enable: token.enable, + }) + .from(token) + .orderBy(token.macaddr) + return { success: true, data: result, @@ -91,88 +95,63 @@ export async function getGatewayInfo() { } export type GatewayConfig = { - city: string + city: string | null edge: string user: string - public: string + public: string | null inner_ip: string ischange: number isonline: number } // 网关配置 -export async function getGatewayConfig(off: number, itemsPerPage: number, mac?: string) { +export async function getGatewayConfig(page?: number, mac?: string): Promise>> { try { - const offset = off || 0 - const limit = itemsPerPage || 100 - - // 获取总数 - let totalCountQuery = '' - let totalCountParams: (string | number)[] = [] - - if (mac) { - totalCountQuery = ` - SELECT COUNT(*) as total - FROM gateway - LEFT JOIN cityhash ON cityhash.hash = gateway.cityhash - LEFT JOIN edge ON edge.macaddr = gateway.edge - WHERE gateway.macaddr = ? - ` - totalCountParams = [mac] - } - else { - totalCountQuery = ` - SELECT COUNT(*) as total - FROM gateway - ` + if (!page && !mac) { + throw new Error('页码和MAC地址不能同时为空') } - const totalCountResult = await prisma.$queryRawUnsafe<[{ total: bigint }]>( - totalCountQuery, - ...totalCountParams, - ) - const totalCount = Number(totalCountResult[0]?.total || 0) - - // 获取分页数据 - let query = ` - select edge, city, user, public, inner_ip, ischange, isonline - from - gateway - left join cityhash - on cityhash.hash = gateway.cityhash - left join edge - on edge.macaddr = gateway.edge - ` - let params: (string | number)[] = [] - - if (mac) { - query += ' WHERE gateway.macaddr = ?' - params = [mac] - } - else { - query += ' LIMIT ? OFFSET ?' - params.push(limit, offset) - } - - // 指定返回类型 - const result: GatewayConfig[] = await prisma.$queryRawUnsafe(query, ...params) + page = mac ? 1 : Math.max(1, page || 1) + const [total, result] = await Promise.all([ + drizzle.$count(gateway, mac ? eq(gateway.macaddr, mac) : undefined), + drizzle + .select({ + city: cityhash.city, + edge: gateway.edge, + user: gateway.user, + public: edge.public, + inner_ip: gateway.network, + ischange: gateway.ischange, + isonline: gateway.isonline, + }) + .from(gateway) + .leftJoin(cityhash, eq(cityhash.hash, gateway.cityhash)) + .leftJoin(edge, eq(edge.macaddr, gateway.edge)) + .where(mac ? eq(gateway.macaddr, mac) : undefined) + .orderBy(gateway.macaddr, sql`cast(regexp_replace(gateway.network, '172.30.168.', '') as unsigned)`) + .offset((page - 1) * 250) + .limit(250), + ]) return { - data: result, - totalCount: totalCount, - currentPage: Math.floor(offset / limit) + 1, - totalPages: Math.ceil(totalCount / limit), + success: true, + data: { + total, + page, + size: 250, + items: result, + }, } } catch (error) { console.error('Gateway config query error:', error) return { success: false, - data: [], error: '查询网关配置失败', } } } + export type CityNode = { city: string count: number @@ -184,25 +163,29 @@ export type CityNode = { // 城市节点数量分布 export async function getCityNodeCount() { try { - const result: CityNode[] = await prisma.$queryRaw` - select c.city, c.hash, c.label, e.count, c.\`offset\` - from - cityhash c - left join ( - select city_id, count(*) as count - from - edge - where - edge.active is true - group by - city_id - ) e - on c.id = e.city_id - group by - c.hash - order by - count desc - ` + const e = drizzle + .select({ + cityId: edge.cityId, + count: count().as('count'), + }) + .from(edge) + .where(eq(edge.active, 1)) + .groupBy(edge.cityId) + .as('e') + + const result = await drizzle + .select({ + city: cityhash.city, + hash: cityhash.hash, + label: cityhash.label, + count: sql`ifnull(e.count, 0)`, + offset: cityhash.offset, + }) + .from(cityhash) + .leftJoin(e, eq(e.cityId, cityhash.id)) + .groupBy(cityhash.hash) + .orderBy(desc(sql`e.count`)) + return { success: true, data: result, @@ -219,32 +202,39 @@ export async function getCityNodeCount() { } // 获取节点信息 -export async function getEdgeNodes(off: number, itemsPerPage: number) { +export async function getEdgeNodes(page: number, size: number) { try { - const offset = off || 0 - const limit = itemsPerPage || 100 - // 获取总数 - 使用类型断言 - const totalCountResult = await prisma.$queryRaw<[{ total: bigint }]>` - SELECT COUNT(*) as total - FROM edge - WHERE active = true - ` - const totalCount = Number(totalCountResult[0]?.total || 0) + const offset = Math.max(0, (page - 1)) * size + const limit = Math.min(100, Math.max(10, size)) + + const [total, data] = await Promise.all([ + drizzle.$count(edge, eq(edge.active, 1)), + drizzle + .select({ + id: edge.id, + macaddr: edge.macaddr, + city: cityhash.city, + public: edge.public, + isp: edge.isp, + single: edge.single, + sole: edge.sole, + arch: edge.arch, + online: edge.online, + }) + .from(edge) + .leftJoin(cityhash, eq(cityhash.id, edge.cityId)) + .where(eq(edge.active, 1)) + .orderBy(edge.id) + .offset(offset) + .limit(limit), + + ]) - // 获取分页数据 - const result = await prisma.$queryRaw` - SELECT edge.id, edge.macaddr, city, public, isp, single, sole, arch, online - FROM edge - LEFT JOIN cityhash ON cityhash.id = edge.city_id - WHERE edge.active = true - ORDER BY edge.id - LIMIT ${limit} OFFSET ${offset} - ` return { - data: result, - totalCount: totalCount, + data, + totalCount: total, currentPage: Math.floor(offset / limit) + 1, - totalPages: Math.ceil(totalCount / limit), + totalPages: Math.ceil(total / limit), } } catch (error) { diff --git a/src/actions/user.ts b/src/actions/user.ts index 767e6aa..c93e368 100644 --- a/src/actions/user.ts +++ b/src/actions/user.ts @@ -1,5 +1,6 @@ 'use server' -import prisma from '@/lib/prisma' +import drizzle, { desc, eq, users } from '@/lib/drizzle' +import { first } from '@/lib/utils' import { hash } from 'bcryptjs' type User = { @@ -17,22 +18,20 @@ export async function findUsers(): Promise<{ error: string }> { try { - const users = await prisma.user.findMany({ - select: { - id: true, - account: true, - name: true, - createdAt: true, - updatedAt: true, - }, - orderBy: { - createdAt: 'desc', - }, - }) + const result = await drizzle + .select({ + id: users.id, + account: users.account, + name: users.name, + createdAt: users.createdat, + updatedAt: users.updatedat, + }) + .from(users) + .orderBy(desc(users.createdat)) return { success: true, - data: users, + data: result, error: '', } } @@ -54,11 +53,10 @@ export async function createUser(params: { }) { try { // 检查用户是否已存在 - const existingUser = await prisma.user.findUnique({ - where: { account: params.account }, + const user = await drizzle.query.users.findFirst({ + where: eq(users.account, params.account), }) - - if (existingUser) { + if (user) { return { success: false, error: '用户账号已存在', @@ -69,20 +67,31 @@ export async function createUser(params: { const hashedPassword = await hash(params.password, 10) // 创建用户 - const user = await prisma.user.create({ - data: { - account: params.account, - password: hashedPassword, - name: params.name || params.account, - }, - }) + const id = first( + await drizzle + .insert(users) + .values({ + account: params.account, + password: hashedPassword, + name: params.name || params.account, + }).$returningId(), + r => r.id, + ) + if (!id) { + return { + success: false, + error: '创建用户失败', + } + } // 不返回密码字段 - const { password: _, ...userWithoutPassword } = user - return { success: true, - user: userWithoutPassword, + user: { + id, + account: params.account, + name: params.name || params.account, + }, } } catch (error) { @@ -97,38 +106,10 @@ export async function createUser(params: { // 删除用户 export async function removeUser(id: number) { try { - if (!id) { - return { - success: false, - error: '用户ID不能为空', - } - } + await drizzle + .delete(users) + .where(eq(users.id, id)) - // const userId = parseInt(id) - if (isNaN(id)) { - return { - success: false, - error: '无效的用户ID', - } - } - - // 检查用户是否存在 - const existingUser = await prisma.user.findUnique({ - where: { id: id }, - }) - - if (!existingUser) { - return { - status: 404, - success: false, - error: '用户不存在', - } - } - - // 删除用户 - await prisma.user.delete({ - where: { id: id }, - }) return { success: true, message: '用户删除成功', diff --git a/src/app/dashboard/components/allocationStatus.tsx b/src/app/dashboard/components/allocationStatus.tsx index 9a23252..1c45837 100644 --- a/src/app/dashboard/components/allocationStatus.tsx +++ b/src/app/dashboard/components/allocationStatus.tsx @@ -40,11 +40,11 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool // 数据验证 const validatedData = (result.data).map(item => ({ city: item.city || '未知', - count: item.count || BigInt(0), - assigned: item.assigned || BigInt(0), + count: item.count, + assigned: item.assigned, })) - const sortedData = validatedData.sort((a, b) => Number(b.count - a.count)) + const sortedData = validatedData.sort((a, b) => b.count - a.count) setData(sortedData) } diff --git a/src/app/dashboard/components/edge.tsx b/src/app/dashboard/components/edge.tsx index c377f77..0aac9fc 100644 --- a/src/app/dashboard/components/edge.tsx +++ b/src/app/dashboard/components/edge.tsx @@ -1,7 +1,6 @@ 'use client' import { useEffect, useState } from 'react' -import { validateNumber } from '@/lib/utils' import { Pagination } from '@/components/ui/pagination' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' import { getEdgeNodes } from '@/actions/stats' @@ -56,15 +55,15 @@ export default function Edge() { } const validatedData = (result.data as ResultEdge[]).map(item => ({ - id: validateNumber(item.id), + id: item.id, macaddr: item.macaddr || '', city: item.city || '', public: item.public || '', isp: item.isp || '', single: item.single, sole: item.sole, - arch: validateNumber(item.arch), - online: validateNumber(item.online), + arch: item.arch, + online: item.online, })) setData(validatedData) diff --git a/src/app/dashboard/components/gatewayConfig.tsx b/src/app/dashboard/components/gatewayConfig.tsx index e0d877b..42939da 100644 --- a/src/app/dashboard/components/gatewayConfig.tsx +++ b/src/app/dashboard/components/gatewayConfig.tsx @@ -4,79 +4,56 @@ import { useSearchParams } from 'next/navigation' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' import { Pagination } from '@/components/ui/pagination' import { getGatewayConfig, type GatewayConfig } from '@/actions/stats' - -// interface GatewayConfig { -// city: string -// edge: string -// user: string -// public: string -// inner_ip: string -// ischange: number -// isonline: number -// } +import { toast } from 'sonner' function GatewayConfigContent() { const [data, setData] = useState([]) const [loading, setLoading] = useState(false) const [macAddress, setMacAddress] = useState('') - const [error, setError] = useState('') const searchParams = useSearchParams() // 分页状态 - const [currentPage, setCurrentPage] = useState(1) - const [itemsPerPage, setItemsPerPage] = useState(100) - const [totalItems, setTotalItems] = useState(0) - - // 判断是否为MAC地址查询(用于控制分页显示) - const isMacQuery = !!macAddress + const [page, setPage] = useState(1) + const [total, setTotal] = useState(0) // 监听URL的mac参数变化 useEffect(() => { const urlMac = searchParams.get('mac') if (urlMac) { setMacAddress(urlMac) - setCurrentPage(1) // 重置到第一页 - fetchData(urlMac, 1, itemsPerPage) + setPage(1) // 重置到第一页 + fetchData(urlMac, 1) } else { setMacAddress('') - setCurrentPage(1) // 重置到第一页 - fetchData('', 1, itemsPerPage) + setPage(1) // 重置到第一页 + fetchData('', 1) } }, [searchParams]) - const fetchData = async (mac: string, page: number = 1, limit: number = itemsPerPage) => { + const fetchData = async (mac: string, page: number = 1) => { setLoading(true) - setError('') - try { // 计算偏移量 - const offset = (page - 1) * limit - - const result = await getGatewayConfig(offset, limit, mac) - - // 检查返回的数据是否有效 - if (!result.data || result.data.length === 0) { - if (mac.trim()) { - setError(`未找到MAC地址为 ${mac} 的网关配置信息`) - } - else { - setError('未找到任何网关配置信息') - } - setData([]) - setTotalItems(0) - return + const result = await getGatewayConfig(page, mac) + if (!result.success) { + throw new Error(result.error || '查询网关配置失败') } - setData(result.data) + const shrink = ['黔东南', '延边'] + result.data.items.forEach((item) => { + shrink.forEach((s) => { + if (item.city?.startsWith(s)) { + item.city = s + } + }) + }) - setTotalItems(result.totalCount || 0) + setData(result.data.items) + setTotal(result.data.total) } catch (error) { - console.error('获取网关配置失败:', error) - setError(error instanceof Error ? error.message : '获取网关配置失败') - setData([]) - setTotalItems(0) + toast.error((error as Error).message || '获取网关配置失败') } finally { setLoading(false) @@ -85,28 +62,26 @@ function GatewayConfigContent() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() - setCurrentPage(1) // 重置到第一页 - fetchData(macAddress, 1, itemsPerPage) + setPage(1) // 重置到第一页 + fetchData(macAddress, 1) } // 处理页码变化 const handlePageChange = (page: number) => { - setCurrentPage(page) - fetchData(macAddress, page, itemsPerPage) + setPage(page) + fetchData(macAddress, page) } // 处理每页显示数量变化 const handleSizeChange = (size: number) => { - setItemsPerPage(size) - setCurrentPage(1) - fetchData(macAddress, 1, size) + setPage(1) + fetchData(macAddress, 1) } const getStatusBadge = (value: number, trueText: string = '是', falseText: string = '否') => { // 0是正常1是更新,正常(绿)+ 更新(红) return ( - + {value === 0 ? trueText : falseText} ) @@ -139,7 +114,7 @@ function GatewayConfigContent() { value={macAddress} onChange={e => setMacAddress(e.target.value)} placeholder="输入MAC地址查询" - className="px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + className="px-4 py-2 h-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" />