迁移 orm 依赖到 drizzle

This commit is contained in:
2025-09-27 10:37:27 +08:00
parent 7fa2fe67ca
commit 72ea29f435
19 changed files with 538 additions and 523 deletions

View File

@@ -14,7 +14,6 @@ WORKDIR /app
COPY --from=dep /app/node_modules ./node_modules COPY --from=dep /app/node_modules ./node_modules
COPY . . COPY . .
RUN bun prisma generate
RUN bun run build RUN bun run build
# 生产阶段 # 生产阶段

View File

@@ -13,7 +13,9 @@
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"drizzle-orm": "^0.44.5",
"lucide-react": "^0.541.0", "lucide-react": "^0.541.0",
"mysql2": "^3.15.1",
"next": "15.4.7", "next": "15.4.7",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.1.0", "react": "19.1.0",
@@ -26,16 +28,16 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@prisma/client": "^6.16.2",
"@stylistic/eslint-plugin": "^5.4.0", "@stylistic/eslint-plugin": "^5.4.0",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"drizzle-kit": "^0.31.4",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.5.0", "eslint-config-next": "15.5.0",
"eslint-plugin-drizzle": "^0.2.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prisma": "^6.16.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"tsx": "^4.20.4", "tsx": "^4.20.4",
"tw-animate-css": "^1.3.7", "tw-animate-css": "^1.3.7",
@@ -47,12 +49,18 @@
"packages": { "packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@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/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/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=="], "@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/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], "@esbuild/android-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=="], "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=="], "axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], "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=="], "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=="], "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=="], "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=="], "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=="], "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
@@ -551,6 +565,10 @@
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], "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=="], "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=="], "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": ["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=="], "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=="], "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-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-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=="], "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=="], "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-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=="], "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=="], "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=="], "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=="], "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-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-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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"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-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
@@ -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=="], "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-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=="], "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=="], "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=="], "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=="], "@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=="], "@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=="], "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/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=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],

View File

@@ -2,6 +2,7 @@ import { dirname } from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import { FlatCompat } from '@eslint/eslintrc' import { FlatCompat } from '@eslint/eslintrc'
import stylistic from '@stylistic/eslint-plugin' import stylistic from '@stylistic/eslint-plugin'
import drizzle from 'eslint-plugin-drizzle'
const __filename = fileURLToPath(import.meta.url) const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename) const __dirname = dirname(__filename)
@@ -23,6 +24,14 @@ const eslintConfig = [
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off',
}, },
}, },
{
plugins: {
drizzle,
},
rules: {
...drizzle.configs.recommended.rules,
},
},
] ]
export default eslintConfig export default eslintConfig

0
localhost.session.sql Normal file
View File

View File

@@ -18,7 +18,9 @@
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"drizzle-orm": "^0.44.5",
"lucide-react": "^0.541.0", "lucide-react": "^0.541.0",
"mysql2": "^3.15.1",
"next": "15.4.7", "next": "15.4.7",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.1.0", "react": "19.1.0",
@@ -31,16 +33,16 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@prisma/client": "^6.16.2",
"@stylistic/eslint-plugin": "^5.4.0", "@stylistic/eslint-plugin": "^5.4.0",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"drizzle-kit": "^0.31.4",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.5.0", "eslint-config-next": "15.5.0",
"eslint-plugin-drizzle": "^0.2.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prisma": "^6.16.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"tsx": "^4.20.4", "tsx": "^4.20.4",
"tw-animate-css": "^1.3.7", "tw-animate-css": "^1.3.7",

View File

@@ -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',
'管理员'
);

View File

@@ -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")
}

View File

@@ -1,58 +1,57 @@
'use server' 'use server'
import prisma, { User } from '@/lib/prisma' // 使用统一的prisma实例 import drizzle, { eq, sessions, users } from '@/lib/drizzle'
import { compare } from 'bcryptjs' import { compare } from 'bcryptjs'
import { randomUUID } from 'crypto'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import { z } from 'zod' import { z } from 'zod'
const loginSchema = z.object({ const loginProps = z.object({
account: z.string().min(3, '账号至少需要3个字符'), account: z.string().min(3, '账号至少需要3个字符').trim(),
password: z.string().min(6, '密码至少需要6个字符'), password: z.string().min(6, '密码至少需要6个字符').trim(),
}) })
export async function login(props: { export async function login(rawParams: z.infer<typeof loginProps>) {
account: string
password: string
}) {
try { try {
const result = loginSchema.parse(props) const params = loginProps.parse(rawParams)
const user: User | null = await prisma.user.findFirst({ // 查找用户
where: { const user = await drizzle.query.users.findFirst({
OR: [ where: eq(users.account, params.account),
{ account: result.account.trim() },
{ password: result.password.trim() },
],
},
}) })
if (!user) { if (!user) {
return { return {
success: false, success: false,
error: '用户不存在或密码未设置', error: '用户不存在或密码错误',
} }
} }
// 验证密码 // 验证密码
const passwordMatch = await compare(result.password, user.password || '') const passwordMatch = await compare(params.password, user.password || '')
if (!passwordMatch) { if (!passwordMatch) {
return { return {
success: false, success: false,
error: '密码错误', error: '用户不存在或密码错误',
} }
} }
// 创建会话 // 创建会话
const sessionToken = crypto.randomUUID() const sessionToken = randomUUID()
await prisma.session.create({ await drizzle.insert(sessions).values({
data: { id: sessionToken,
id: sessionToken, userid: user.id,
userId: user.id, expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
}) })
// 设置cookie // 设置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, success: true,
user: { user: {
id: user.id, id: user.id,
@@ -60,15 +59,6 @@ export async function login(props: {
name: user.name, 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) { catch (error) {
console.error('登录错误:', error) console.error('登录错误:', error)
@@ -86,24 +76,19 @@ export async function logout() {
// 删除数据库中的session如果存在 // 删除数据库中的session如果存在
if (sessionToken) { if (sessionToken) {
await prisma.session.deleteMany({ await drizzle.delete(sessions).where(eq(sessions.id, sessionToken))
where: { id: sessionToken },
}).catch(() => {
// 忽略删除错误确保cookie被清除
})
} }
// 清除cookie // 清除cookie
const response = { success: true }
cookieStore.set('session', '', { cookieStore.set('session', '', {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === 'production', secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 0, // 立即过期 maxAge: 0, // 立即过期
path: '/',
}) })
return response return {
success: true,
}
} }
catch (error) { catch (error) {
console.error('退出错误:', error) console.error('退出错误:', error)

View File

@@ -1,18 +1,47 @@
'use server' '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: { export async function findConfigs(params: {
macaddr: string macaddr: string
}) { }) {
try { try {
return await prisma.gateway.findMany({ return await drizzle.query.gateway.findMany({
where: { where: eq(gateway.macaddr, params.macaddr),
macaddr: params.macaddr,
},
}) })
} }
catch (e) { catch (e) {
throw new Error('查询配置失败: ' + (e as Error).message) 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<typeof pageConfigsParams>) {
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)
}
}

View File

@@ -1,49 +1,48 @@
'use server' '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 = { export type AllocationStatus = {
city: string city: string
count: bigint count: number
assigned: bigint assigned: number
} }
// 城市分配状态 // 城市分配状态
export async function getAllocationStatus(hours: number) { export async function getAllocationStatus(hours: number = 24) {
try { try {
const hoursNum = hours || 24 const c1 = drizzle
// 使用参数化查询防止SQL注入 .select({
const result: AllocationStatus[] = await prisma.$queryRaw` cityId: edge.cityId,
SELECT count: count().as('count'),
city, })
c1.count AS count, .from(edge)
c2.assigned AS assigned .where(eq(edge.active, 1))
FROM .groupBy(edge.cityId)
cityhash .as('c1')
LEFT JOIN (
SELECT const c2 = drizzle
city_id, .select({
COUNT(*) AS count cityId: change.city,
FROM assigned: count().as('assigned'),
edge })
WHERE .from(change)
active = 1 .where(sql`time > NOW() - INTERVAL ${hours} HOUR`)
GROUP BY .groupBy(change.city)
city_id .as('c2')
) c1 ON c1.city_id = cityhash.id
LEFT JOIN ( const result = await drizzle
SELECT .select({
city AS city_id, city: cityhash.city,
COUNT(*) AS assigned count: sql<number>`c1.count`,
FROM assigned: sql<number>`ifnull(c2.assigned, 0)`,
\`change\` })
WHERE .from(cityhash)
time > NOW() - INTERVAL ${hoursNum} HOUR .leftJoin(c1, eq(c1.cityId, cityhash.id))
GROUP BY .leftJoin(c2, eq(c2.cityId, cityhash.id))
city .where(sql`cityhash.macaddr is not null`)
) c2 ON c2.city_id = cityhash.id
WHERE
cityhash.macaddr IS NOT NULL;
`
return { return {
success: true, success: true,
data: result, data: result,
@@ -70,11 +69,16 @@ export type GatewayInfo = {
// 获取网关基本信息 // 获取网关基本信息
export async function getGatewayInfo() { export async function getGatewayInfo() {
try { try {
const result: GatewayInfo[] = await prisma.$queryRaw` const result = await drizzle
SELECT macaddr, inner_ip, setid, enable .select({
FROM token macaddr: token.macaddr,
ORDER BY macaddr inner_ip: token.innerIp,
` setid: token.setid,
enable: token.enable,
})
.from(token)
.orderBy(token.macaddr)
return { return {
success: true, success: true,
data: result, data: result,
@@ -91,88 +95,63 @@ export async function getGatewayInfo() {
} }
export type GatewayConfig = { export type GatewayConfig = {
city: string city: string | null
edge: string edge: string
user: string user: string
public: string public: string | null
inner_ip: string inner_ip: string
ischange: number ischange: number
isonline: number isonline: number
} }
// 网关配置 // 网关配置
export async function getGatewayConfig(off: number, itemsPerPage: number, mac?: string) { export async function getGatewayConfig(page?: number, mac?: string): Promise<Res<Page<GatewayConfig>>> {
try { try {
const offset = off || 0 if (!page && !mac) {
const limit = itemsPerPage || 100 throw new Error('页码和MAC地址不能同时为空')
// 获取总数
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
`
} }
const totalCountResult = await prisma.$queryRawUnsafe<[{ total: bigint }]>( page = mac ? 1 : Math.max(1, page || 1)
totalCountQuery, const [total, result] = await Promise.all([
...totalCountParams, drizzle.$count(gateway, mac ? eq(gateway.macaddr, mac) : undefined),
) drizzle
const totalCount = Number(totalCountResult[0]?.total || 0) .select({
city: cityhash.city,
// 获取分页数据 edge: gateway.edge,
let query = ` user: gateway.user,
select edge, city, user, public, inner_ip, ischange, isonline public: edge.public,
from inner_ip: gateway.network,
gateway ischange: gateway.ischange,
left join cityhash isonline: gateway.isonline,
on cityhash.hash = gateway.cityhash })
left join edge .from(gateway)
on edge.macaddr = gateway.edge .leftJoin(cityhash, eq(cityhash.hash, gateway.cityhash))
` .leftJoin(edge, eq(edge.macaddr, gateway.edge))
let params: (string | number)[] = [] .where(mac ? eq(gateway.macaddr, mac) : undefined)
.orderBy(gateway.macaddr, sql`cast(regexp_replace(gateway.network, '172.30.168.', '') as unsigned)`)
if (mac) { .offset((page - 1) * 250)
query += ' WHERE gateway.macaddr = ?' .limit(250),
params = [mac] ])
}
else {
query += ' LIMIT ? OFFSET ?'
params.push(limit, offset)
}
// 指定返回类型
const result: GatewayConfig[] = await prisma.$queryRawUnsafe<GatewayConfig[]>(query, ...params)
return { return {
data: result, success: true,
totalCount: totalCount, data: {
currentPage: Math.floor(offset / limit) + 1, total,
totalPages: Math.ceil(totalCount / limit), page,
size: 250,
items: result,
},
} }
} }
catch (error) { catch (error) {
console.error('Gateway config query error:', error) console.error('Gateway config query error:', error)
return { return {
success: false, success: false,
data: [],
error: '查询网关配置失败', error: '查询网关配置失败',
} }
} }
} }
export type CityNode = { export type CityNode = {
city: string city: string
count: number count: number
@@ -184,25 +163,29 @@ export type CityNode = {
// 城市节点数量分布 // 城市节点数量分布
export async function getCityNodeCount() { export async function getCityNodeCount() {
try { try {
const result: CityNode[] = await prisma.$queryRaw` const e = drizzle
select c.city, c.hash, c.label, e.count, c.\`offset\` .select({
from cityId: edge.cityId,
cityhash c count: count().as('count'),
left join ( })
select city_id, count(*) as count .from(edge)
from .where(eq(edge.active, 1))
edge .groupBy(edge.cityId)
where .as('e')
edge.active is true
group by const result = await drizzle
city_id .select({
) e city: cityhash.city,
on c.id = e.city_id hash: cityhash.hash,
group by label: cityhash.label,
c.hash count: sql<number>`ifnull(e.count, 0)`,
order by offset: cityhash.offset,
count desc })
` .from(cityhash)
.leftJoin(e, eq(e.cityId, cityhash.id))
.groupBy(cityhash.hash)
.orderBy(desc(sql`e.count`))
return { return {
success: true, success: true,
data: result, 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 { try {
const offset = off || 0 const offset = Math.max(0, (page - 1)) * size
const limit = itemsPerPage || 100 const limit = Math.min(100, Math.max(10, size))
// 获取总数 - 使用类型断言
const totalCountResult = await prisma.$queryRaw<[{ total: bigint }]>` const [total, data] = await Promise.all([
SELECT COUNT(*) as total drizzle.$count(edge, eq(edge.active, 1)),
FROM edge drizzle
WHERE active = true .select({
` id: edge.id,
const totalCount = Number(totalCountResult[0]?.total || 0) 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 { return {
data: result, data,
totalCount: totalCount, totalCount: total,
currentPage: Math.floor(offset / limit) + 1, currentPage: Math.floor(offset / limit) + 1,
totalPages: Math.ceil(totalCount / limit), totalPages: Math.ceil(total / limit),
} }
} }
catch (error) { catch (error) {

View File

@@ -1,5 +1,6 @@
'use server' 'use server'
import prisma from '@/lib/prisma' import drizzle, { desc, eq, users } from '@/lib/drizzle'
import { first } from '@/lib/utils'
import { hash } from 'bcryptjs' import { hash } from 'bcryptjs'
type User = { type User = {
@@ -17,22 +18,20 @@ export async function findUsers(): Promise<{
error: string error: string
}> { }> {
try { try {
const users = await prisma.user.findMany({ const result = await drizzle
select: { .select({
id: true, id: users.id,
account: true, account: users.account,
name: true, name: users.name,
createdAt: true, createdAt: users.createdat,
updatedAt: true, updatedAt: users.updatedat,
}, })
orderBy: { .from(users)
createdAt: 'desc', .orderBy(desc(users.createdat))
},
})
return { return {
success: true, success: true,
data: users, data: result,
error: '', error: '',
} }
} }
@@ -54,11 +53,10 @@ export async function createUser(params: {
}) { }) {
try { try {
// 检查用户是否已存在 // 检查用户是否已存在
const existingUser = await prisma.user.findUnique({ const user = await drizzle.query.users.findFirst({
where: { account: params.account }, where: eq(users.account, params.account),
}) })
if (user) {
if (existingUser) {
return { return {
success: false, success: false,
error: '用户账号已存在', error: '用户账号已存在',
@@ -69,20 +67,31 @@ export async function createUser(params: {
const hashedPassword = await hash(params.password, 10) const hashedPassword = await hash(params.password, 10)
// 创建用户 // 创建用户
const user = await prisma.user.create({ const id = first(
data: { await drizzle
account: params.account, .insert(users)
password: hashedPassword, .values({
name: params.name || params.account, 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 { return {
success: true, success: true,
user: userWithoutPassword, user: {
id,
account: params.account,
name: params.name || params.account,
},
} }
} }
catch (error) { catch (error) {
@@ -97,38 +106,10 @@ export async function createUser(params: {
// 删除用户 // 删除用户
export async function removeUser(id: number) { export async function removeUser(id: number) {
try { try {
if (!id) { await drizzle
return { .delete(users)
success: false, .where(eq(users.id, id))
error: '用户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 { return {
success: true, success: true,
message: '用户删除成功', message: '用户删除成功',

View File

@@ -40,11 +40,11 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
// 数据验证 // 数据验证
const validatedData = (result.data).map(item => ({ const validatedData = (result.data).map(item => ({
city: item.city || '未知', city: item.city || '未知',
count: item.count || BigInt(0), count: item.count,
assigned: item.assigned || BigInt(0), 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) setData(sortedData)
} }

View File

@@ -1,7 +1,6 @@
'use client' 'use client'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { validateNumber } from '@/lib/utils'
import { Pagination } from '@/components/ui/pagination' import { Pagination } from '@/components/ui/pagination'
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
import { getEdgeNodes } from '@/actions/stats' import { getEdgeNodes } from '@/actions/stats'
@@ -56,15 +55,15 @@ export default function Edge() {
} }
const validatedData = (result.data as ResultEdge[]).map(item => ({ const validatedData = (result.data as ResultEdge[]).map(item => ({
id: validateNumber(item.id), id: item.id,
macaddr: item.macaddr || '', macaddr: item.macaddr || '',
city: item.city || '', city: item.city || '',
public: item.public || '', public: item.public || '',
isp: item.isp || '', isp: item.isp || '',
single: item.single, single: item.single,
sole: item.sole, sole: item.sole,
arch: validateNumber(item.arch), arch: item.arch,
online: validateNumber(item.online), online: item.online,
})) }))
setData(validatedData) setData(validatedData)

View File

@@ -4,79 +4,56 @@ import { useSearchParams } from 'next/navigation'
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
import { Pagination } from '@/components/ui/pagination' import { Pagination } from '@/components/ui/pagination'
import { getGatewayConfig, type GatewayConfig } from '@/actions/stats' import { getGatewayConfig, type GatewayConfig } from '@/actions/stats'
import { toast } from 'sonner'
// interface GatewayConfig {
// city: string
// edge: string
// user: string
// public: string
// inner_ip: string
// ischange: number
// isonline: number
// }
function GatewayConfigContent() { function GatewayConfigContent() {
const [data, setData] = useState<GatewayConfig[]>([]) const [data, setData] = useState<GatewayConfig[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [macAddress, setMacAddress] = useState('') const [macAddress, setMacAddress] = useState('')
const [error, setError] = useState('')
const searchParams = useSearchParams() const searchParams = useSearchParams()
// 分页状态 // 分页状态
const [currentPage, setCurrentPage] = useState(1) const [page, setPage] = useState(1)
const [itemsPerPage, setItemsPerPage] = useState(100) const [total, setTotal] = useState(0)
const [totalItems, setTotalItems] = useState(0)
// 判断是否为MAC地址查询用于控制分页显示
const isMacQuery = !!macAddress
// 监听URL的mac参数变化 // 监听URL的mac参数变化
useEffect(() => { useEffect(() => {
const urlMac = searchParams.get('mac') const urlMac = searchParams.get('mac')
if (urlMac) { if (urlMac) {
setMacAddress(urlMac) setMacAddress(urlMac)
setCurrentPage(1) // 重置到第一页 setPage(1) // 重置到第一页
fetchData(urlMac, 1, itemsPerPage) fetchData(urlMac, 1)
} }
else { else {
setMacAddress('') setMacAddress('')
setCurrentPage(1) // 重置到第一页 setPage(1) // 重置到第一页
fetchData('', 1, itemsPerPage) fetchData('', 1)
} }
}, [searchParams]) }, [searchParams])
const fetchData = async (mac: string, page: number = 1, limit: number = itemsPerPage) => { const fetchData = async (mac: string, page: number = 1) => {
setLoading(true) setLoading(true)
setError('')
try { try {
// 计算偏移量 // 计算偏移量
const offset = (page - 1) * limit const result = await getGatewayConfig(page, mac)
if (!result.success) {
const result = await getGatewayConfig(offset, limit, mac) throw new Error(result.error || '查询网关配置失败')
// 检查返回的数据是否有效
if (!result.data || result.data.length === 0) {
if (mac.trim()) {
setError(`未找到MAC地址为 ${mac} 的网关配置信息`)
}
else {
setError('未找到任何网关配置信息')
}
setData([])
setTotalItems(0)
return
} }
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) { catch (error) {
console.error('获取网关配置失败:', error) toast.error((error as Error).message || '获取网关配置失败')
setError(error instanceof Error ? error.message : '获取网关配置失败')
setData([])
setTotalItems(0)
} }
finally { finally {
setLoading(false) setLoading(false)
@@ -85,28 +62,26 @@ function GatewayConfigContent() {
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
setCurrentPage(1) // 重置到第一页 setPage(1) // 重置到第一页
fetchData(macAddress, 1, itemsPerPage) fetchData(macAddress, 1)
} }
// 处理页码变化 // 处理页码变化
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
setCurrentPage(page) setPage(page)
fetchData(macAddress, page, itemsPerPage) fetchData(macAddress, page)
} }
// 处理每页显示数量变化 // 处理每页显示数量变化
const handleSizeChange = (size: number) => { const handleSizeChange = (size: number) => {
setItemsPerPage(size) setPage(1)
setCurrentPage(1) fetchData(macAddress, 1)
fetchData(macAddress, 1, size)
} }
const getStatusBadge = (value: number, trueText: string = '是', falseText: string = '否') => { const getStatusBadge = (value: number, trueText: string = '是', falseText: string = '否') => {
// 0是正常1是更新正常绿+ 更新(红) // 0是正常1是更新正常绿+ 更新(红)
return ( return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${value === 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
value === 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
{value === 0 ? trueText : falseText} {value === 0 ? trueText : falseText}
</span> </span>
) )
@@ -139,7 +114,7 @@ function GatewayConfigContent() {
value={macAddress} value={macAddress}
onChange={e => setMacAddress(e.target.value)} onChange={e => setMacAddress(e.target.value)}
placeholder="输入MAC地址查询" 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"
/> />
<button <button
type="submit" type="submit"
@@ -148,17 +123,6 @@ function GatewayConfigContent() {
</button> </button>
</div> </div>
{error && (
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-md">
<div className="flex items-center text-red-800">
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
{error}
</div>
</div>
)}
</form> </form>
{loading ? ( {loading ? (
@@ -173,11 +137,11 @@ function GatewayConfigContent() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="bg-gray-50"> <TableRow className="bg-gray-50">
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MAC地址</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">IP地址</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">线</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead> <TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">线</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MAC</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">IP</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead> <TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead> <TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
</TableRow> </TableRow>
@@ -185,21 +149,11 @@ function GatewayConfigContent() {
<TableBody> <TableBody>
{data.map((item, index) => ( {data.map((item, index) => (
<TableRow key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}> <TableRow key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<TableCell> <TableCell>{item.inner_ip}</TableCell>
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-xs"> <TableCell>{item.user}</TableCell>
{item.city} <TableCell>{item.city}</TableCell>
</span> <TableCell>{item.edge}</TableCell>
</TableCell> <TableCell>{item.public}</TableCell>
<TableCell>
<div className="font-mono text-sm text-blue-600 font-medium">{item.edge}</div>
</TableCell>
<TableCell>
<div className="font-mono text-sm text-green-600">{item.public}</div>
</TableCell>
<TableCell className="text-sm text-gray-900">{item.user}</TableCell>
<TableCell>
<div className="font-mono text-sm text-purple-600">{item.inner_ip}</div>
</TableCell>
<TableCell> <TableCell>
{getStatusBadge(item.ischange, '正常', '更新')} {getStatusBadge(item.ischange, '正常', '更新')}
</TableCell> </TableCell>
@@ -213,7 +167,7 @@ function GatewayConfigContent() {
</div> </div>
<div className="flex flex-1 flex-col gap-4 mb-6"> <div className="flex flex-1 flex-col gap-4 mb-6">
<div className="bg-blue-50 p-4 rounded-lg border border-blue-100"> <div className="bg-blue-50 p-4 rounded-lg border border-blue-100">
<div className="text-2xl font-bold text-blue-600">{totalItems}</div> <div className="text-2xl font-bold text-blue-600">{total}</div>
<div className="text-sm text-blue-800"></div> <div className="text-sm text-blue-800"></div>
</div> </div>
<div className="bg-green-50 p-4 rounded-lg border border-green-100"> <div className="bg-green-50 p-4 rounded-lg border border-green-100">
@@ -231,12 +185,13 @@ function GatewayConfigContent() {
</div> </div>
</div> </div>
{/* 分页组件 - 仅在非MAC查询时显示 */} {/* 分页组件 */}
{!isMacQuery && ( {!macAddress && (
<Pagination <Pagination
page={currentPage} total={total}
size={itemsPerPage} page={page}
total={totalItems} size={250}
sizeOptions={[250]}
onPageChange={handlePageChange} onPageChange={handlePageChange}
onSizeChange={handleSizeChange} onSizeChange={handleSizeChange}
className="mt-4" className="mt-4"

16
src/lib/api.ts Normal file
View File

@@ -0,0 +1,16 @@
export type Res<T> = {
success: true
data: T
} | {
success: false
error: string
}
export type Page<T> = {
total: number
page: number
size: number
items: T[]
}
export type ResData<T extends (...args: never) => unknown> = Awaited<ReturnType<T>> extends Res<infer D> ? D : never

21
src/lib/drizzle/index.ts Normal file
View File

@@ -0,0 +1,21 @@
import 'dotenv/config'
import { drizzle as client } from 'drizzle-orm/mysql2'
import * as schema from './schema'
declare global {
var drizzle: ReturnType<typeof client<typeof schema>> | undefined
}
const { DATABASE_URL } = process.env
if (!DATABASE_URL) {
throw new Error('DATABASE_URL is not set')
}
const drizzle = global.drizzle || client(DATABASE_URL, { mode: 'default', schema })
if (process.env.NODE_ENV !== 'production') {
global.drizzle = drizzle
}
export default drizzle
export * from './schema'
export * from 'drizzle-orm'

95
src/lib/drizzle/schema.ts Normal file
View File

@@ -0,0 +1,95 @@
import { mysqlTable, int, timestamp, varchar, datetime, text, tinyint } from 'drizzle-orm/mysql-core'
export const change = mysqlTable('change', {
id: int().autoincrement().notNull().primaryKey(),
time: timestamp({ mode: 'date' }),
city: int(),
macaddr: varchar({ length: 20 }).notNull(),
edgeNew: varchar('edge_new', { length: 20 }).notNull(),
edgeOld: varchar('edge_old', { length: 20 }),
info: varchar({ length: 500 }).notNull(),
network: varchar({ length: 20 }).notNull(),
createtime: datetime({ mode: 'date' }).default(new Date()).notNull(),
})
export const cityhash = mysqlTable('cityhash', {
id: int().autoincrement().notNull().primaryKey(),
index: int(),
macaddr: varchar({ length: 20 }),
city: varchar({ length: 20 }).notNull(),
num: int().notNull(),
hash: varchar({ length: 100 }).notNull(),
label: varchar({ length: 20 }),
count: int().default(0).notNull(),
offset: int().default(0).notNull(),
createtime: datetime({ mode: 'date' }).default(new Date()).notNull(),
updatetime: datetime({ mode: 'date' }).default(new Date()).notNull(),
})
export const edge = mysqlTable('edge', {
id: int().autoincrement().notNull().primaryKey(),
macaddr: varchar({ length: 17 }).notNull(),
public: varchar({ length: 255 }).notNull(),
isp: varchar({ length: 255 }).notNull(),
single: tinyint().notNull(),
sole: tinyint().notNull(),
arch: tinyint().notNull(),
online: int().default(0).notNull(),
cityId: int('city_id').notNull(),
active: tinyint().notNull(),
})
export const gateway = mysqlTable('gateway', {
id: int().autoincrement().notNull().primaryKey(),
macaddr: varchar({ length: 20 }).notNull(),
table: int().notNull(),
edge: varchar({ length: 20 }).notNull(),
network: varchar({ length: 20 }).notNull(),
cityhash: varchar({ length: 100 }).notNull(),
label: varchar({ length: 20 }),
user: varchar({ length: 20 }).notNull(),
innerIp: varchar('inner_ip', { length: 20 }).notNull(),
ischange: tinyint().default(0).notNull(),
isonline: tinyint().default(0).notNull(),
onlinenum: int().default(0).notNull(),
createtime: datetime({ mode: 'date' }).default(new Date()).notNull(),
updatetime: datetime({ mode: 'date' }).default(new Date()).notNull(),
})
export const sessions = mysqlTable('sessions', {
id: varchar({ length: 191 }).notNull().primaryKey(),
expires: datetime({ mode: 'date' }).notNull(),
userid: int().notNull().references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
createdat: datetime({ mode: 'date' }).default(new Date()).notNull(),
})
export const submit = mysqlTable('submit', {
id: int().autoincrement().notNull().primaryKey(),
time: datetime({ mode: 'date' }).notNull(),
gateway: varchar({ length: 20 }).notNull(),
config: text(),
})
export const token = mysqlTable('token', {
id: int().autoincrement().notNull().primaryKey(),
setid: int().default(1).notNull(),
changeCount: int('change_count').notNull(),
limitCount: int('limit_count').default(32000).notNull(),
token: varchar({ length: 1000 }).notNull(),
macaddr: varchar({ length: 100 }).notNull(),
tokenTime: datetime('token_time', { mode: 'date' }).notNull(),
innerIp: varchar('inner_ip', { length: 20 }).notNull(),
l2Ip: varchar({ length: 20 }),
enable: tinyint().default(1).notNull(),
createtime: datetime({ mode: 'date' }).default(new Date()).notNull(),
updatetime: datetime({ mode: 'date' }).default(new Date()).notNull(),
})
export const users = mysqlTable('users', {
id: int().autoincrement().notNull().primaryKey(),
name: varchar({ length: 191 }),
password: varchar({ length: 191 }).notNull(),
account: varchar({ length: 191 }).notNull(),
createdat: datetime({ mode: 'date' }).default(new Date()).notNull(),
updatedat: datetime({ mode: 'date' }).default(new Date()).notNull(),
})

View File

@@ -1,15 +0,0 @@
import 'server-only'
import { PrismaClient } from '@/generated/prisma/client'
const globalForPrisma = global as unknown as {
prisma: PrismaClient | undefined
}
const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
export default prisma
export * from '@/generated/prisma/client'

View File

@@ -6,12 +6,11 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }
// 数据验证函数 // 取数组第一个元素,支持映射
export const validateNumber = (value: unknown): number => { export function first<T>(array: T[]): T | undefined
if (typeof value === 'number') return value export function first<T, I>(array: T[], select: (item: T) => I): I | undefined
if (typeof value === 'string') {
const num = parseInt(value) export function first<T, I>(array: T[], select?: (item: T) => I) {
return isNaN(num) ? 0 : num const item = array.length > 0 ? array[0] : undefined
} return select && item ? select(item) : item
return 0
} }