Compare commits

..

2 Commits

Author SHA1 Message Date
EL
6e144fc657 测试 2026-01-22 14:06:39 +08:00
EL
1352d62a5d 完善部分接口 2026-01-19 02:10:46 +08:00
33 changed files with 2329 additions and 7 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=postgres://postgres:lyh1234@192.168.0.180:5432/rbac

240
bun.lock
View File

@ -5,16 +5,92 @@
"": {
"name": "api",
"dependencies": {
"elysia": "latest",
"@elysiajs/cors": "^1.4.1",
"@elysiajs/eden": "^1.4.6",
"@elysiajs/jwt": "^1.4.0",
"@sinclair/typebox": "^0.34.47",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1",
"drizzle-typebox": "^0.3.3",
"drizzle-zod": "^0.8.3",
"elysia": "^1.4.22",
"pg": "^8.17.1",
},
"devDependencies": {
"@types/pg": "^8.16.0",
"bun-types": "latest",
"drizzle-kit": "^0.31.8",
"tsx": "^4.21.0",
},
},
},
"packages": {
"@borewit/text-codec": ["@borewit/text-codec@0.2.1", "https://registry.npmmirror.com/@borewit/text-codec/-/text-codec-0.2.1.tgz", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "https://registry.npmmirror.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@elysiajs/cors": ["@elysiajs/cors@1.4.1", "https://registry.npmmirror.com/@elysiajs/cors/-/cors-1.4.1.tgz", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-lQfad+F3r4mNwsxRKbXyJB8Jg43oAOXjRwn7sKUL6bcOW3KjUqUimTS+woNpO97efpzjtDE0tEjGk9DTw8lqTQ=="],
"@elysiajs/eden": ["@elysiajs/eden@1.4.6", "https://registry.npmmirror.com/@elysiajs/eden/-/eden-1.4.6.tgz", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q=="],
"@elysiajs/jwt": ["@elysiajs/jwt@1.4.0", "https://registry.npmmirror.com/@elysiajs/jwt/-/jwt-1.4.0.tgz", { "dependencies": { "jose": "^6.0.11" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Z0PvZhQxdDeKZ8HslXzDoXXD83NKExNPmoiAPki3nI2Xvh5wtUrBH+zWOD17yP14IbRo8fxGj3L25MRCAPsgPA=="],
"@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.12", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@sinclair/typebox": ["@sinclair/typebox@0.34.47", "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.34.47.tgz", {}, "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw=="],
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "https://registry.npmmirror.com/@tokenizer/inflate/-/inflate-0.4.1.tgz", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
@ -23,34 +99,196 @@
"@types/node": ["@types/node@25.0.9", "https://registry.npmmirror.com/@types/node/-/node-25.0.9.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
"@types/pg": ["@types/pg@8.16.0", "https://registry.npmmirror.com/@types/pg/-/pg-8.16.0.tgz", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="],
"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=="],
"bun-types": ["bun-types@1.3.6", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.6.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
"cookie": ["cookie@1.1.1", "https://registry.npmmirror.com/cookie/-/cookie-1.1.1.tgz", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"dotenv": ["dotenv@17.2.3", "https://registry.npmmirror.com/dotenv/-/dotenv-17.2.3.tgz", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"drizzle-kit": ["drizzle-kit@0.31.8", "https://registry.npmmirror.com/drizzle-kit/-/drizzle-kit-0.31.8.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-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="],
"drizzle-orm": ["drizzle-orm@0.45.1", "https://registry.npmmirror.com/drizzle-orm/-/drizzle-orm-0.45.1.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-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
"drizzle-typebox": ["drizzle-typebox@0.3.3", "https://registry.npmmirror.com/drizzle-typebox/-/drizzle-typebox-0.3.3.tgz", { "peerDependencies": { "@sinclair/typebox": ">=0.34.8", "drizzle-orm": ">=0.36.0" } }, "sha512-iJpW9K+BaP8+s/ImHxOFVjoZk9G5N/KXFTOpWcFdz9SugAOWv2fyGaH7FmqgdPo+bVNYQW0OOI3U9dkFIVY41w=="],
"drizzle-zod": ["drizzle-zod@0.8.3", "https://registry.npmmirror.com/drizzle-zod/-/drizzle-zod-0.8.3.tgz", { "peerDependencies": { "drizzle-orm": ">=0.36.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww=="],
"elysia": ["elysia@1.4.22", "https://registry.npmmirror.com/elysia/-/elysia-1.4.22.tgz", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="],
"esbuild": ["esbuild@0.25.12", "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"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=="],
"exact-mirror": ["exact-mirror@0.2.6", "https://registry.npmmirror.com/exact-mirror/-/exact-mirror-0.2.6.tgz", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "https://registry.npmmirror.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
"file-type": ["file-type@21.3.0", "https://registry.npmmirror.com/file-type/-/file-type-21.3.0.tgz", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="],
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-tsconfig": ["get-tsconfig@4.13.0", "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
"ieee754": ["ieee754@1.2.1", "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"jose": ["jose@6.1.3", "https://registry.npmmirror.com/jose/-/jose-6.1.3.tgz", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
"memoirist": ["memoirist@0.4.0", "https://registry.npmmirror.com/memoirist/-/memoirist-0.4.0.tgz", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
"ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"openapi-types": ["openapi-types@12.1.3", "https://registry.npmmirror.com/openapi-types/-/openapi-types-12.1.3.tgz", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
"pg": ["pg@8.17.1", "https://registry.npmmirror.com/pg/-/pg-8.17.1.tgz", { "dependencies": { "pg-connection-string": "^2.10.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ=="],
"pg-cloudflare": ["pg-cloudflare@1.3.0", "https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="],
"pg-connection-string": ["pg-connection-string@2.10.0", "https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.10.0.tgz", {}, "sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg=="],
"pg-int8": ["pg-int8@1.0.1", "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
"pg-pool": ["pg-pool@3.11.0", "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.11.0.tgz", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w=="],
"pg-protocol": ["pg-protocol@1.11.0", "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.11.0.tgz", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="],
"pg-types": ["pg-types@2.2.0", "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"pgpass": ["pgpass@1.0.5", "https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
"postgres-array": ["postgres-array@2.0.0", "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"postgres-bytea": ["postgres-bytea@1.0.1", "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"postgres-date": ["postgres-date@1.0.7", "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"postgres-interval": ["postgres-interval@1.2.0", "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"source-map": ["source-map@0.6.1", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"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=="],
"split2": ["split2@4.2.0", "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"strtok3": ["strtok3@10.3.4", "https://registry.npmmirror.com/strtok3/-/strtok3-10.3.4.tgz", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
"token-types": ["token-types@6.1.2", "https://registry.npmmirror.com/token-types/-/token-types-6.1.2.tgz", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
"tsx": ["tsx@4.21.0", "https://registry.npmmirror.com/tsx/-/tsx-4.21.0.tgz", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
"uint8array-extras": ["uint8array-extras@1.5.0", "https://registry.npmmirror.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
"undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"xtend": ["xtend@4.0.2", "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
"zod": ["zod@4.3.5", "https://registry.npmmirror.com/zod/-/zod-4.3.5.tgz", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
"@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=="],
"tsx/esbuild": ["esbuild@0.27.2", "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"@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=="],
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
}
}

11
drizzle.config.ts Normal file
View File

@ -0,0 +1,11 @@
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/modules/index.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});

View File

@ -0,0 +1,67 @@
CREATE TABLE "department" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"hospital" uuid NOT NULL,
"description" varchar,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "doctor" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"username" varchar NOT NULL,
"password" varchar NOT NULL,
"phone" varchar NOT NULL,
"hospitalId" uuid NOT NULL,
"departmentId" uuid,
"groupId" uuid,
"roleId" uuid NOT NULL,
"isDoctor" boolean DEFAULT true NOT NULL,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "doctor_username_unique" UNIQUE("username")
);
--> statement-breakpoint
CREATE TABLE "group" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"departmentId" uuid NOT NULL,
"description" varchar,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "hospital" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"description" text,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"chiefDoctorId" uuid NOT NULL,
"sharedWith" text,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "role" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"code" varchar NOT NULL,
"description" text,
"permissions" text,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "role_name_unique" UNIQUE("name"),
CONSTRAINT "role_code_unique" UNIQUE("code")
);

View File

@ -0,0 +1,426 @@
{
"id": "89cec236-6382-4cab-b163-f1fbd0cfdddb",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.department": {
"name": "department",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"hospital": {
"name": "hospital",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.doctor": {
"name": "doctor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"phone": {
"name": "phone",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"hospitalId": {
"name": "hospitalId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"departmentId": {
"name": "departmentId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"groupId": {
"name": "groupId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"roleId": {
"name": "roleId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"isDoctor": {
"name": "isDoctor",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"doctor_username_unique": {
"name": "doctor_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.group": {
"name": "group",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"departmentId": {
"name": "departmentId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.hospital": {
"name": "hospital",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.patient": {
"name": "patient",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"chiefDoctorId": {
"name": "chiefDoctorId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"sharedWith": {
"name": "sharedWith",
"type": "text",
"primaryKey": false,
"notNull": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.role": {
"name": "role",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"permissions": {
"name": "permissions",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"role_name_unique": {
"name": "role_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
},
"role_code_unique": {
"name": "role_code_unique",
"nullsNotDistinct": false,
"columns": [
"code"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1768760286048,
"tag": "0000_stormy_falcon",
"breakpoints": true
}
]
}

View File

@ -6,10 +6,22 @@
"dev": "bun run --watch src/index.ts"
},
"dependencies": {
"elysia": "latest"
"@elysiajs/cors": "^1.4.1",
"@elysiajs/eden": "^1.4.6",
"@elysiajs/jwt": "^1.4.0",
"@sinclair/typebox": "^0.34.47",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1",
"drizzle-typebox": "^0.3.3",
"drizzle-zod": "^0.8.3",
"elysia": "^1.4.22",
"pg": "^8.17.1"
},
"devDependencies": {
"bun-types": "latest"
"@types/pg": "^8.16.0",
"bun-types": "latest",
"drizzle-kit": "^0.31.8",
"tsx": "^4.21.0"
},
"module": "src/index.js"
}

93
seed.ts Normal file
View File

@ -0,0 +1,93 @@
import { db } from "./src/index";
import { roleTable } from "./src/modules";
const roles = [
{
name: "超级管理员",
code: "ADMIN",
description: "平台最高管理员,拥有所有权限",
permissions: JSON.stringify([
"view_all",
"manage_all",
"view_hospitals",
"manage_hospitals",
"view_departments",
"manage_departments",
"view_groups",
"manage_groups",
"view_doctors",
"manage_doctors",
"view_patients",
"manage_patients",
"view_roles",
"manage_roles",
]),
},
{
name: "普通医生",
code: "DOCTOR",
description: "可以查看和管理自己的患者",
permissions: JSON.stringify(["view_own_patients", "manage_own_patients"]),
},
{
name: "组长",
code: "GROUP_LEADER",
description: "可以查看和管理小组内所有医生的患者",
permissions: JSON.stringify([
"view_own_patients",
"manage_own_patients",
"view_group_patients",
"manage_group_patients",
]),
},
{
name: "科室主任",
code: "DIRECTOR",
description: "可以查看和管理科室内所有医生的患者",
permissions: JSON.stringify([
"view_own_patients",
"manage_own_patients",
"view_group_patients",
"manage_group_patients",
"view_department_patients",
"manage_department_patients",
]),
},
{
name: "工程师",
code: "ENGINEER",
description: "可以接收和处理调压任务",
permissions: JSON.stringify([
"view_tasks",
"accept_tasks",
"cancel_tasks",
"update_tasks",
]),
},
];
async function seed() {
console.log("开始插入角色数据...");
for (const role of roles) {
try {
const existing = await db.query.roleTable.findFirst({
where: (table, { eq }) => eq(table.code, role.code),
});
if (!existing) {
await db.insert(roleTable).values(role);
console.log(`✓ 创建角色: ${role.name}`);
} else {
console.log(`- 角色已存在: ${role.name}`);
}
} catch (error) {
console.error(`✗ 创建角色失败: ${role.name}`, error);
}
}
console.log("角色数据插入完成!");
process.exit(0);
}
seed();

60
src/controllers/auth.ts Normal file
View File

@ -0,0 +1,60 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { doctorTable } from "../modules";
import bcrypt from "bcrypt";
// 登录
export const login = async ({
body,
}: {
body: {
username: string;
password: string;
};
}) => {
const { username, password } = body;
// 查找医生
const doctor = await db.query.doctorTable.findFirst({
with: {
role: true,
hospital: true,
},
where: (table, { eq }) => eq(table.username, username),
});
if (!doctor) {
throw new Error("用户名或密码错误");
}
// 验证密码(实际项目中应该使用加密密码)
if (doctor.password !== password) {
throw new Error("用户名或密码错误");
}
if (!doctor.isActive) {
throw new Error("账号已被禁用");
}
// 返回医生信息(不包含密码)
const { password: _, ...doctorWithoutPassword } = doctor;
return doctorWithoutPassword;
};
// 获取当前用户信息
export const me = async ({ doctorId }: { doctorId: string }) => {
const doctor = await db.query.doctorTable.findFirst({
with: {
role: true,
hospital: true,
},
where: (table, { eq }) => eq(table.id, doctorId),
});
if (!doctor) {
throw new Error("用户不存在");
}
const { password: _, ...doctorWithoutPassword } = doctor;
return doctorWithoutPassword;
};

View File

@ -0,0 +1,85 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { departmentTable } from "../modules";
// 获取科室列表
export const list = async () => {
const departments = await db.query.departmentTable.findMany({
with: {
hospital: true,
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
return departments;
};
// 获取单个科室
export const one = async ({ params }: { params: { id: string } }) => {
const department = await db.query.departmentTable.findFirst({
with: {
hospital: true,
},
where: (table, { eq }) => eq(table.id, params.id),
});
if (!department) {
throw new Error("科室不存在");
}
return department;
};
// 创建科室
export const create = async ({
body,
}: {
body: {
name: string;
hospital: string;
description?: string;
};
}) => {
// 判断医院是否存在
const isHospital = await db.query.hospitalTable.findFirst({
where: (table, { eq }) => eq(table.id, body.hospital),
});
if (!isHospital) {
throw new Error("所属医院不存在");
}
const department = await db.insert(departmentTable).values(body).returning();
return department[0];
};
// 更新科室
export const update = async ({
params,
body,
}: {
params: { id: string };
body: {
name?: string;
hospital?: string;
description?: string;
isActive?: boolean;
};
}) => {
const department = await db
.update(departmentTable)
.set({ ...body, updatedAt: new Date() })
.where(eq(departmentTable.id, params.id))
.returning();
if (!department) {
throw new Error("科室不存在");
}
return department;
};
// 删除科室
export const remove = async ({ params }: { params: { id: string } }) => {
const department = await db
.delete(departmentTable)
.where(eq(departmentTable.id, params.id))
.returning();
if (!department || department.length === 0) {
throw new Error("科室不存在");
}
return department[0];
};

186
src/controllers/doctor.ts Normal file
View File

@ -0,0 +1,186 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { doctorTable } from "../modules";
// 获取医生列表
export const list = async () => {
const doctors = await db.query.doctorTable.findMany({
with: {
hospital: true,
role: true,
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
return doctors;
};
// 获取单个医生
export const one = async ({ params }: { params: { id: string } }) => {
const doctor = await db.query.doctorTable.findFirst({
with: {
hospital: true,
role: true,
},
where: (table, { eq }) => eq(table.id, params.id),
});
if (!doctor) {
throw new Error("医生不存在");
}
return doctor;
};
// 创建医生
export const create = async ({
body,
}: {
body: {
name: string;
username: string;
password: string;
phone: string;
hospitalId?: string;
departmentId?: string;
groupId?: string;
roleId?: string;
isDoctor?: boolean;
};
}) => {
// 验证医院是否存在如果提供了医院ID
if (body.hospitalId) {
const hospital = await db.query.hospitalTable.findFirst({
where: (table, { eq }) => eq(table.id, body.hospitalId!),
});
if (!hospital) {
throw new Error("所属医院不存在");
}
}
// 验证科室是否存在如果提供了科室ID
if (body.departmentId) {
const department = await db.query.departmentTable.findFirst({
where: (table, { eq }) => eq(table.id, body.departmentId!),
});
if (!department) {
throw new Error("所属科室不存在");
}
}
// 验证小组是否存在如果提供了小组ID
if (body.groupId) {
const group = await db.query.groupTable.findFirst({
where: (table, { eq }) => eq(table.id, body.groupId!),
});
if (!group) {
throw new Error("所属小组不存在");
}
}
// 验证角色是否存在如果提供了角色ID
if (body.roleId) {
const role = await db.query.roleTable.findFirst({
where: (table, { eq }) => eq(table.id, body.roleId!),
});
if (!role) {
throw new Error("角色不存在");
}
}
// 构建插入数据,只包含提供的字段
const insertData: any = {
name: body.name,
username: body.username,
password: body.password,
phone: body.phone,
};
if (body.hospitalId) insertData.hospitalId = body.hospitalId;
if (body.departmentId) insertData.departmentId = body.departmentId;
if (body.groupId) insertData.groupId = body.groupId;
if (body.roleId) insertData.roleId = body.roleId;
if (body.isDoctor !== undefined) insertData.isDoctor = body.isDoctor;
const doctor = await db.insert(doctorTable).values(insertData).returning();
return doctor[0];
};
// 更新医生
export const update = async ({
params,
body,
}: {
params: { id: string };
body: {
name?: string;
username?: string;
password?: string;
phone?: string;
hospitalId?: string;
departmentId?: string;
groupId?: string;
roleId?: string;
isDoctor?: boolean;
isActive?: boolean;
};
}) => {
// 如果要更新医院ID验证医院是否存在
if (body.hospitalId) {
const hospital = await db.query.hospitalTable.findFirst({
where: (table, { eq }) => eq(table.id, body.hospitalId!),
});
if (!hospital) {
throw new Error("所属医院不存在");
}
}
// 如果要更新科室ID验证科室是否存在
if (body.departmentId) {
const department = await db.query.departmentTable.findFirst({
where: (table, { eq }) => eq(table.id, body.departmentId!),
});
if (!department) {
throw new Error("所属科室不存在");
}
}
// 如果要更新小组ID验证小组是否存在
if (body.groupId) {
const group = await db.query.groupTable.findFirst({
where: (table, { eq }) => eq(table.id, body.groupId!),
});
if (!group) {
throw new Error("所属小组不存在");
}
}
// 如果要更新角色ID验证角色是否存在
if (body.roleId) {
const role = await db.query.roleTable.findFirst({
where: (table, { eq }) => eq(table.id, body.roleId!),
});
if (!role) {
throw new Error("角色不存在");
}
}
const doctor = await db
.update(doctorTable)
.set({ ...body, updatedAt: new Date() })
.where(eq(doctorTable.id, params.id))
.returning();
if (!doctor || doctor.length === 0) {
throw new Error("医生不存在");
}
return doctor[0];
};
// 删除医生
export const remove = async ({ params }: { params: { id: string } }) => {
const doctor = await db
.delete(doctorTable)
.where(eq(doctorTable.id, params.id))
.returning();
if (!doctor || doctor.length === 0) {
throw new Error("医生不存在");
}
return doctor[0];
};

94
src/controllers/group.ts Normal file
View File

@ -0,0 +1,94 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { groupTable } from "../modules";
// 获取小组列表
export const list = async () => {
const groups = await db.query.groupTable.findMany({
with: {
department: true,
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
return groups;
};
// 获取单个小组
export const one = async ({ params }: { params: { id: string } }) => {
const group = await db.query.groupTable.findFirst({
with: {
department: true,
},
where: (table, { eq }) => eq(table.id, params.id),
});
if (!group) {
throw new Error("小组不存在");
}
return group;
};
// 创建小组
export const create = async ({
body,
}: {
body: {
name: string;
departmentId: string;
description?: string;
};
}) => {
// 判断科室是否存在
const isDepartment = await db.query.departmentTable.findFirst({
where: (table, { eq }) => eq(table.id, body.departmentId),
});
if (!isDepartment) {
throw new Error("所属科室不存在");
}
const group = await db.insert(groupTable).values(body).returning();
return group[0];
};
// 更新小组
export const update = async ({
params,
body,
}: {
params: { id: string };
body: {
name?: string;
departmentId?: string;
description?: string;
isActive?: boolean;
};
}) => {
// 如果要更新科室ID判断科室是否存在
if (body.departmentId) {
const isDepartment = await db.query.departmentTable.findFirst({
where: (table, { eq }) => eq(table.id, body.departmentId!),
});
if (!isDepartment) {
throw new Error("所属科室不存在");
}
}
const group = await db
.update(groupTable)
.set({ ...body, updatedAt: new Date() })
.where(eq(groupTable.id, params.id))
.returning();
if (!group) {
throw new Error("小组不存在");
}
return group;
};
// 删除小组
export const remove = async ({ params }: { params: { id: string } }) => {
const group = await db
.delete(groupTable)
.where(eq(groupTable.id, params.id))
.returning();
if (!group || group.length === 0) {
throw new Error("小组不存在");
}
return group[0];
};

View File

@ -0,0 +1,74 @@
import { asc, eq } from "drizzle-orm";
import { db } from "../index";
import { hospitalTable } from "../modules";
// 获取医院列表
export const list = async () => {
// const hospitals = await db
// .select()
// .from(hospitalTable)
// .orderBy(hospitalTable.createdAt);
const hospitals = await db.query.hospitalTable.findMany({
orderBy: (table, { asc }) => asc(table.createdAt),
});
return hospitals;
};
// 获取单个医院
export const one = async ({ params }: { params: { id: string } }) => {
const hospital = await db.query.hospitalTable.findFirst({
where: (table, { eq }) => eq(table.id, params.id),
});
if (!hospital) {
throw new Error("医院不存在");
}
return hospital;
};
// 创建医院
export const create = async ({
body,
}: {
body: {
name: string;
description?: string;
};
}) => {
const hospital = await db.insert(hospitalTable).values(body).returning();
return hospital[0];
};
// 更新医院
export const update = async ({
params,
body,
}: {
params: { id: string };
body: {
name?: string;
description?: string;
isActive?: boolean;
};
}) => {
const hospital = await db
.update(hospitalTable)
.set({ ...body, updatedAt: new Date() })
.where(eq(hospitalTable.id, params.id))
.returning();
if (!hospital) {
throw new Error("医院不存在");
}
return hospital;
};
// 删除医院
export const remove = async ({ params }: { params: { id: string } }) => {
const hospital = await db
.delete(hospitalTable)
.where(eq(hospitalTable.id, params.id))
.returning();
if (!hospital || hospital.length === 0) {
throw new Error("医院不存在");
}
return hospital[0];
};

335
src/controllers/patient.ts Normal file
View File

@ -0,0 +1,335 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { patientTable } from "../modules";
// 获取患者列表(带权限过滤)
export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
if (!doctorId) {
// 未登录,返回空
return [];
}
// 获取当前医生信息
const currentDoctor = await db.query.doctorTable.findFirst({
with: {
role: true,
hospital: true,
},
where: (table, { eq }) => eq(table.id, doctorId),
});
if (!currentDoctor) {
return [];
}
// 调试日志
console.log("当前医生信息:", {
id: currentDoctor.id,
name: currentDoctor.name,
roleCode: currentDoctor.role?.code,
roleName: currentDoctor.role?.name,
departmentId: currentDoctor.departmentId,
groupId: currentDoctor.groupId,
});
// 超级管理员可以看到所有患者
if (currentDoctor.role?.code === "ADMIN") {
const patients = await db.query.patientTable.findMany({
with: {
chiefDoctor: {
with: {
role: true,
},
},
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
return patients;
}
// 科室主任可以看到本科室所有医生的患者 + 分享给自己的患者
if (currentDoctor.role?.code === "DIRECTOR" && currentDoctor.departmentId) {
// 获取本科室所有医生
const departmentDoctors = await db.query.doctorTable.findMany({
where: (table, { eq }) => eq(table.departmentId, currentDoctor.departmentId!),
});
const doctorIds = departmentDoctors.map((d) => d.id);
if (doctorIds.length === 0) return [];
// 获取本科室所有医生创建的患者
const allPatients = await db.query.patientTable.findMany({
with: {
chiefDoctor: {
with: {
role: true,
},
},
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
// 过滤出本科室医生创建的患者 + 分享给自己的患者
const filteredPatients = allPatients.filter((patient: any) => {
// 本科室医生创建的患者
if (doctorIds.includes(patient.chiefDoctorId)) return true;
// 分享给自己的患者
if (patient.sharedWith) {
try {
const sharedWith = JSON.parse(patient.sharedWith);
return sharedWith.includes(doctorId);
} catch (error) {
console.error("解析 sharedWith 失败:", patient.sharedWith, error);
return false;
}
}
return false;
});
console.log("科室主任看到的患者数量:", {
total: allPatients.length,
filtered: filteredPatients.length,
doctorId,
departmentId: currentDoctor.departmentId,
});
return filteredPatients;
}
// 组长可以看到本小组所有医生的患者 + 分享给自己的患者
if (currentDoctor.role?.code === "GROUP_LEADER" && currentDoctor.groupId) {
// 获取本小组所有医生
const groupDoctors = await db.query.doctorTable.findMany({
where: (table, { eq }) => eq(table.groupId, currentDoctor.groupId!),
});
const doctorIds = groupDoctors.map((d) => d.id);
if (doctorIds.length === 0) return [];
// 获取本小组所有医生创建的患者
const allPatients = await db.query.patientTable.findMany({
with: {
chiefDoctor: {
with: {
role: true,
},
},
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
// 过滤出本小组医生创建的患者 + 分享给自己的患者
const filteredPatients = allPatients.filter((patient: any) => {
// 本小组医生创建的患者
if (doctorIds.includes(patient.chiefDoctorId)) return true;
// 分享给自己的患者
if (patient.sharedWith) {
try {
const sharedWith = JSON.parse(patient.sharedWith);
return sharedWith.includes(doctorId);
} catch (error) {
console.error("解析 sharedWith 失败:", patient.sharedWith, error);
return false;
}
}
return false;
});
console.log("组长看到的患者数量:", {
total: allPatients.length,
filtered: filteredPatients.length,
doctorId,
groupId: currentDoctor.groupId,
});
return filteredPatients;
}
// 普通医生只能看到自己的患者 + 分享给自己的患者
const allPatients = await db.query.patientTable.findMany({
with: {
chiefDoctor: {
with: {
role: true,
},
},
},
orderBy: (table, { asc }) => asc(table.createdAt),
});
// 过滤出分享给自己的患者
const filteredPatients = allPatients.filter((patient: any) => {
// 自己创建的患者
if (patient.chiefDoctorId === doctorId) return true;
// 分享给自己的患者
if (patient.sharedWith) {
try {
const sharedWith = JSON.parse(patient.sharedWith);
return sharedWith.includes(doctorId);
} catch (error) {
console.error("解析 sharedWith 失败:", patient.sharedWith, error);
return false;
}
}
return false;
});
console.log("普通医生看到的患者数量:", {
total: allPatients.length,
filtered: filteredPatients.length,
doctorId,
});
return filteredPatients;
};
// 获取单个患者
export const one = async ({ params }: { params: { id: string } }) => {
const patient = await db.query.patientTable.findFirst({
with: {
chiefDoctor: {
with: {
role: true,
},
},
},
where: (table, { eq }) => eq(table.id, params.id),
});
if (!patient) {
throw new Error("患者不存在");
}
return patient;
};
// 创建患者
export const create = async ({
body,
}: {
body: {
name: string;
chiefDoctorId: string;
};
}) => {
// 验证医生是否存在
const doctor = await db.query.doctorTable.findFirst({
where: (table, { eq }) => eq(table.id, body.chiefDoctorId),
});
if (!doctor) {
throw new Error("主刀医生不存在");
}
const patient = await db.insert(patientTable).values(body).returning();
return patient[0];
};
// 更新患者
export const update = async ({
params,
body,
}: {
params: { id: string };
body: {
name?: string;
chiefDoctorId?: string;
};
}) => {
// 如果要更新医生ID验证医生是否存在
if (body.chiefDoctorId) {
const doctor = await db.query.doctorTable.findFirst({
where: (table, { eq }) => eq(table.id, body.chiefDoctorId!),
});
if (!doctor) {
throw new Error("主刀医生不存在");
}
}
const patient = await db
.update(patientTable)
.set({ ...body, updatedAt: new Date() })
.where(eq(patientTable.id, params.id))
.returning();
if (!patient || patient.length === 0) {
throw new Error("患者不存在");
}
return patient[0];
};
// 删除患者
export const remove = async ({ params }: { params: { id: string } }) => {
const patient = await db
.delete(patientTable)
.where(eq(patientTable.id, params.id))
.returning();
if (!patient || patient.length === 0) {
throw new Error("患者不存在");
}
return patient[0];
};
// 分享患者给其他医生
export const share = async ({
params,
body,
currentDoctorId,
}: {
params: { id: string };
body: { sharedWith: string[] };
currentDoctorId?: string;
}) => {
// 获取患者信息
const patient = await db.query.patientTable.findFirst({
where: (table, { eq }) => eq(table.id, params.id),
with: {
chiefDoctor: true,
},
});
if (!patient) {
throw new Error("患者不存在");
}
// 验证权限:只有患者的主刀医生才能分享
if (patient.chiefDoctorId !== currentDoctorId) {
throw new Error("无权限分享此患者");
}
// 验证:只能分享给同医院的医生
const currentDoctor = await db.query.doctorTable.findFirst({
where: (table, { eq }) => eq(table.id, currentDoctorId!),
});
if (!currentDoctor) {
throw new Error("当前医生不存在");
}
// 验证所有要分享的医生都在同一家医院
if (body.sharedWith && body.sharedWith.length > 0) {
const targetDoctors = await db.query.doctorTable.findMany({
where: (table, { inArray }) => inArray(table.id, body.sharedWith),
});
for (const targetDoctor of targetDoctors) {
if (targetDoctor.hospitalId !== currentDoctor.hospitalId) {
throw new Error(`只能分享给同医院的医生:${targetDoctor.name} 不在同一家医院`);
}
}
}
// 将分享的医生ID列表转换为JSON字符串存储
const updatedPatient = await db
.update(patientTable)
.set({
sharedWith: JSON.stringify(body.sharedWith),
updatedAt: new Date(),
})
.where(eq(patientTable.id, params.id))
.returning();
return updatedPatient[0];
};

74
src/controllers/role.ts Normal file
View File

@ -0,0 +1,74 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { roleTable } from "../modules";
// 获取角色列表
export const list = async () => {
const roles = await db.query.roleTable.findMany({
orderBy: (table, { asc }) => asc(table.createdAt),
});
return roles;
};
// 获取单个角色
export const one = async ({ params }: { params: { id: string } }) => {
const role = await db.query.roleTable.findFirst({
where: (table, { eq }) => eq(table.id, params.id),
});
if (!role) {
throw new Error("角色不存在");
}
return role;
};
// 创建角色
export const create = async ({
body,
}: {
body: {
name: string;
code: string;
description?: string;
permissions?: string;
};
}) => {
const role = await db.insert(roleTable).values(body).returning();
return role[0];
};
// 更新角色
export const update = async ({
params,
body,
}: {
params: { id: string };
body: {
name?: string;
code?: string;
description?: string;
permissions?: string;
isActive?: boolean;
};
}) => {
const role = await db
.update(roleTable)
.set({ ...body, updatedAt: new Date() })
.where(eq(roleTable.id, params.id))
.returning();
if (!role || role.length === 0) {
throw new Error("角色不存在");
}
return role[0];
};
// 删除角色
export const remove = async ({ params }: { params: { id: string } }) => {
const role = await db
.delete(roleTable)
.where(eq(roleTable.id, params.id))
.returning();
if (!role || role.length === 0) {
throw new Error("角色不存在");
}
return role[0];
};

View File

@ -1,7 +1,16 @@
import { Elysia } from "elysia";
import { router } from "./routes";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./modules";
import cors from "@elysiajs/cors";
const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
export const db = drizzle(pool, { schema });
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
const app = new Elysia({ prefix: "/api" }).use(router);
app.use(cors());
app.listen(3000);
export type Api = typeof app;

View File

@ -0,0 +1,24 @@
import { eq } from "drizzle-orm";
import { db } from "../index";
import { doctorTable } from "../modules";
// 获取当前请求的医生ID
export const getDoctorId = async (token: string) => {
// 这里应该验证JWT token暂时简化处理
// 实际应该从JWT payload中提取doctorId
const jwt = (await import("@elysiajs/jwt")).jwt;
return token;
};
// 检查医生权限
export const checkPermission = (doctor: any, requiredPermission: string) => {
if (!doctor.role) return false;
// 超级管理员拥有所有权限
if (doctor.role.code === "ADMIN") return true;
// 解析角色权限
const permissions = JSON.parse(doctor.role.permissions || "[]");
return permissions.includes(requiredPermission);
};

11
src/modules/department.ts Normal file
View File

@ -0,0 +1,11 @@
import { uuid, varchar, timestamp, boolean } from "drizzle-orm/pg-core";
export const Department = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull(), // 科室名称
hospital: uuid().notNull(), // 所属医院
description: varchar(), // 科室描述
isActive: boolean().notNull().default(true), // 是否启用
createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间
};

18
src/modules/doctor.ts Normal file
View File

@ -0,0 +1,18 @@
import { uuid, varchar, timestamp, boolean } from "drizzle-orm/pg-core";
export const Doctor = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull(), // 医生姓名
username: varchar().notNull().unique(), // 医生用户名,唯一标识
password: varchar().notNull(), // 医生密码,建议加密存储
// wechatOpenId: varchar().notNull().unique(), // 微信小程序的唯一标识
phone: varchar().notNull(), // 手机号
hospitalId: uuid().notNull(), // 所属医院ID可选如果有多医院场景
departmentId: uuid(), // 所属科室ID虚拟外键
groupId: uuid(), // 所属小组ID虚拟外键
roleId: uuid().notNull(), // 角色ID虚拟外键
isDoctor: boolean().notNull().default(true), // 是否为医生false表示管理员
isActive: boolean().notNull().default(true), // 是否启用
createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间
};

11
src/modules/group.ts Normal file
View File

@ -0,0 +1,11 @@
import { uuid, varchar, timestamp, boolean } from "drizzle-orm/pg-core";
export const Group = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull(), // 小组名称
departmentId: uuid().notNull(), // 所属科室ID虚拟外键
description: varchar(), // 小组描述
isActive: boolean().notNull().default(true), // 是否启用
createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间
};

10
src/modules/hospital.ts Normal file
View File

@ -0,0 +1,10 @@
import { uuid, varchar, timestamp, boolean, text } from "drizzle-orm/pg-core";
export const Hospital = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull(), // 医院名称
description: text(), // 医院描述
isActive: boolean().notNull().default(true), // 是否启用
createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间
};

58
src/modules/index.ts Normal file
View File

@ -0,0 +1,58 @@
import { pgTable } from "drizzle-orm/pg-core";
import { Doctor } from "./doctor";
import { Hospital } from "./hospital";
import { Department } from "./department";
import { Group } from "./group";
import { Role } from "./role";
import { Patient } from "./patient";
import { relations } from "drizzle-orm";
export const hospitalTable = pgTable("hospital", Hospital);
export const doctorTable = pgTable("doctor", Doctor);
export const departmentTable = pgTable("department", Department);
export const groupTable = pgTable("group", Group);
export const roleTable = pgTable("role", Role);
export const patientTable = pgTable("patient", Patient);
// 定义关联关系
export const hospitalRelations = relations(hospitalTable, ({ many }) => ({
departments: many(departmentTable),
}));
export const departmentRelations = relations(departmentTable, ({ one, many }) => ({
hospital: one(hospitalTable, {
fields: [departmentTable.hospital],
references: [hospitalTable.id],
}),
groups: many(groupTable),
}));
export const groupRelations = relations(groupTable, ({ one }) => ({
department: one(departmentTable, {
fields: [groupTable.departmentId],
references: [departmentTable.id],
}),
}));
export const doctorRelations = relations(doctorTable, ({ one, many }) => ({
hospital: one(hospitalTable, {
fields: [doctorTable.hospitalId],
references: [hospitalTable.id],
}),
role: one(roleTable, {
fields: [doctorTable.roleId],
references: [roleTable.id],
}),
patients: many(patientTable),
}));
export const roleRelations = relations(roleTable, ({ many }) => ({
doctors: many(doctorTable),
}));
export const patientRelations = relations(patientTable, ({ one }) => ({
chiefDoctor: one(doctorTable, {
fields: [patientTable.chiefDoctorId],
references: [doctorTable.id],
}),
}));

10
src/modules/patient.ts Normal file
View File

@ -0,0 +1,10 @@
import { uuid, varchar, timestamp, text } from "drizzle-orm/pg-core";
export const Patient = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull(), // 患者姓名
chiefDoctorId: uuid().notNull(), // 主刀医生ID虚拟外键
sharedWith: text(), // 分享给其他医生的ID列表JSON数组格式存储
createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间
};

12
src/modules/role.ts Normal file
View File

@ -0,0 +1,12 @@
import { uuid, varchar, timestamp, text, boolean } from "drizzle-orm/pg-core";
export const Role = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull().unique(), // 角色名称(如:普通医生、组长、科室主任、工程师)
code: varchar().notNull().unique(), // 角色代码DOCTOR、GROUP_LEADER、DIRECTOR、ENGINEER
description: text(), // 角色描述
permissions: text(), // 权限列表JSON格式存储["view_own_patients", "view_group_patients", "view_department_patients"]
isActive: boolean().notNull().default(true), // 是否启用
createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间
};

69
src/routes/auth.ts Normal file
View File

@ -0,0 +1,69 @@
import { Elysia, t } from "elysia";
import { jwt } from "@elysiajs/jwt";
import * as authController from "../controllers/auth";
const authRoutes = new Elysia({ prefix: "/auth" })
.post(
"/login",
async ({ body, set, jwt }) => {
const doctor = await authController.login({ body });
// 生成JWT token
const token = await jwt.sign({
userId: doctor.id,
roleId: doctor.roleId,
roleCode: doctor.role?.code,
});
return {
token,
doctor,
};
},
{
body: t.Object({
username: t.String(),
password: t.String(),
}),
}
)
.get(
"/me",
async ({ doctorId }) => {
return await authController.me({ doctorId });
},
{
beforeHandle: [
async ({
jwt,
set,
cookie: { auth },
headers,
}: any) => {
// 尝试从 header 或 cookie 获取 token
const token = headers.authorization?.replace("Bearer ", "") || auth;
if (!token) {
set.status = 401;
return { error: "未登录" };
}
try {
const payload = await jwt.verify(token);
if (!payload) {
set.status = 401;
return { error: "Token无效" };
}
// 将用户信息注入到上下文
return { doctorId: payload.userId };
} catch (error) {
set.status = 401;
return { error: "Token无效" };
}
},
],
}
);
export default authRoutes;

42
src/routes/department.ts Normal file
View File

@ -0,0 +1,42 @@
import Elysia, { t } from "elysia";
import * as DepartmentController from "../controllers/department";
// 科室路由
export const departmentRouter = new Elysia()
.get("/department", DepartmentController.list) // 获取科室列表
.get("/department/:id", DepartmentController.one) // 获取单个科室
.post(
"/department",
async ({ body }) => DepartmentController.create({ body }),
{
body: t.Object({
name: t.String(),
hospital: t.String(),
description: t.Optional(t.String()),
}),
}
) // 创建科室
.put(
"/department/:id",
async ({ params, body }) => DepartmentController.update({ params, body }),
{
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.Optional(t.String()),
hospital: t.Optional(t.String()),
description: t.Optional(t.String()),
isActive: t.Optional(t.Boolean()),
}),
}
) // 更新科室
.delete(
"/department/:id",
async ({ params }) => DepartmentController.remove({ params }),
{
params: t.Object({
id: t.String(),
}),
}
); // 删除科室

58
src/routes/doctor.ts Normal file
View File

@ -0,0 +1,58 @@
import Elysia, { t } from "elysia";
import * as DoctorController from "../controllers/doctor";
// 医生路由
export const doctorRouter = new Elysia()
.get("/doctor", DoctorController.list) // 获取医生列表
.get("/doctor/:id", DoctorController.one, {
params: t.Object({
id: t.String(),
}),
}) // 获取单个医生
.post(
"/doctor",
async ({ body }) => DoctorController.create({ body }),
{
body: t.Object({
name: t.String(),
username: t.String(),
password: t.String(),
phone: t.String(),
hospitalId: t.Optional(t.String()),
departmentId: t.Optional(t.String()),
groupId: t.Optional(t.String()),
roleId: t.Optional(t.String()),
isDoctor: t.Optional(t.Boolean()),
}),
}
) // 创建医生
.put(
"/doctor/:id",
async ({ params, body }) => DoctorController.update({ params, body }),
{
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.Optional(t.String()),
username: t.Optional(t.String()),
password: t.Optional(t.String()),
phone: t.Optional(t.String()),
hospitalId: t.Optional(t.String()),
departmentId: t.Optional(t.String()),
groupId: t.Optional(t.String()),
roleId: t.Optional(t.String()),
isDoctor: t.Optional(t.Boolean()),
isActive: t.Optional(t.Boolean()),
}),
}
) // 更新医生
.delete(
"/doctor/:id",
async ({ params }) => DoctorController.remove({ params }),
{
params: t.Object({
id: t.String(),
}),
}
); // 删除医生

42
src/routes/group.ts Normal file
View File

@ -0,0 +1,42 @@
import Elysia, { t } from "elysia";
import * as GroupController from "../controllers/group";
// 小组路由
export const groupRouter = new Elysia()
.get("/group", GroupController.list) // 获取小组列表
.get("/group/:id", GroupController.one) // 获取单个小组
.post(
"/group",
async ({ body }) => GroupController.create({ body }),
{
body: t.Object({
name: t.String(),
departmentId: t.String(),
description: t.Optional(t.String()),
}),
}
) // 创建小组
.put(
"/group/:id",
async ({ params, body }) => GroupController.update({ params, body }),
{
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.Optional(t.String()),
departmentId: t.Optional(t.String()),
description: t.Optional(t.String()),
isActive: t.Optional(t.Boolean()),
}),
}
) // 更新小组
.delete(
"/group/:id",
async ({ params }) => GroupController.remove({ params }),
{
params: t.Object({
id: t.String(),
}),
}
); // 删除小组

42
src/routes/hospital.ts Normal file
View File

@ -0,0 +1,42 @@
import Elysia, { t } from "elysia";
import * as HospitalController from "../controllers/hospital";
// 医院路由
export const hospitalRouter = new Elysia()
.get("/hospital", HospitalController.list) // 获取医院列表
.get("/hospital/:id", HospitalController.one) // 获取单个医院
.post(
"/hospital",
async ({ body }) => HospitalController.create({ body }),
{
body: t.Object({
name: t.String(),
description: t.Optional(t.String()),
}),
}
) // 创建医院
.put(
"/hospital/:id",
async ({ params, body }) => HospitalController.update({ params, body }),
{
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.Optional(t.String()),
description: t.Optional(t.String()),
isActive: t.Optional(t.Boolean()),
}),
}
) // 更新医院
.delete(
"/hospital/:id",
async ({ params }) => HospitalController.remove({ params }),
{
params: t.Object({
id: t.String(),
}),
}
); // 删除医院

17
src/routes/index.ts Normal file
View File

@ -0,0 +1,17 @@
import Elysia from "elysia";
import { doctorRouter } from "./doctor";
import { hospitalRouter } from "./hospital";
import { departmentRouter } from "./department";
import { groupRouter } from "./group";
import { roleRoutes } from "./role";
import { patientRoutes } from "./patient";
import authRoutes from "./auth";
export const router = new Elysia()
.use(authRoutes)
.use(doctorRouter)
.use(hospitalRouter)
.use(departmentRouter)
.use(groupRouter)
.use(roleRoutes)
.use(patientRoutes);

75
src/routes/patient.ts Normal file
View File

@ -0,0 +1,75 @@
import { Elysia, t } from "elysia";
import { jwt } from "@elysiajs/jwt";
import * as patientController from "../controllers/patient";
export const patientRoutes = new Elysia({ prefix: "/patient" })
.use(jwt({
name: "jwt",
secret: process.env.JWT_SECRET || "your-secret-key",
}))
.get("/", async ({ headers, jwt }) => {
// 从 Authorization header 获取 token
const authHeader = headers.authorization;
const token = authHeader?.replace("Bearer ", "");
if (!token) {
return patientController.list({});
}
try {
const payload = await jwt.verify(token);
if (payload && typeof payload === "object" && "userId" in payload) {
return patientController.list({ doctorId: payload.userId as string });
}
} catch (error) {
// Token无效返回空列表
return [];
}
return patientController.list({});
})
.get("/:id", ({ params }) => patientController.one({ params }))
.post("/", ({ body }) => patientController.create({ body }), {
body: t.Object({
name: t.String({ minLength: 1 }),
chiefDoctorId: t.String(),
}),
})
.put(
"/:id",
({ params, body }) => patientController.update({ params, body }),
{
body: t.Object({
name: t.Optional(t.String({ minLength: 1 })),
chiefDoctorId: t.Optional(t.String()),
}),
},
)
.delete("/:id", ({ params }) => patientController.remove({ params }))
.post(
"/:id/share",
async ({ params, body, headers, jwt }) => {
// 从 Authorization header 获取 token
const authHeader = headers.authorization;
const token = authHeader?.replace("Bearer ", "");
let currentDoctorId;
if (token) {
try {
const payload = await jwt.verify(token);
if (payload && typeof payload === "object" && "userId" in payload) {
currentDoctorId = payload.userId as string;
}
} catch (error) {
throw new Error("无效的token");
}
}
return patientController.share({ params, body, currentDoctorId });
},
{
body: t.Object({
sharedWith: t.Array(t.String()),
}),
}
);

32
src/routes/role.ts Normal file
View File

@ -0,0 +1,32 @@
import { Elysia, t } from "elysia";
import * as roleController from "../controllers/role";
export const roleRoutes = new Elysia({ prefix: "/role" })
.get("/", () => roleController.list())
.get("/:id", ({ params }) => roleController.one({ params }))
.post(
"/",
({ body }) => roleController.create({ body }),
{
body: t.Object({
name: t.String({ minLength: 1 }),
code: t.String({ minLength: 1 }),
description: t.Optional(t.String()),
permissions: t.Optional(t.String()),
}),
}
)
.put(
"/:id",
({ params, body }) => roleController.update({ params, body }),
{
body: t.Object({
name: t.Optional(t.String({ minLength: 1 })),
code: t.Optional(t.String({ minLength: 1 })),
description: t.Optional(t.String()),
permissions: t.Optional(t.String()),
isActive: t.Optional(t.Boolean()),
}),
}
)
.delete("/:id", ({ params }) => roleController.remove({ params }));

13
test.ts Normal file
View File

@ -0,0 +1,13 @@
import { treaty } from "@elysiajs/eden";
import type { Api } from "./src";
const client = treaty<Api>("localhost:3000");
const res = await client.api.patient.get({
headers: {
Authorization:
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI3MDE5OGMyZS1lM2EyLTRhZTUtOTdjMC01YWJlNGE0MmU3ZWQiLCJyb2xlSWQiOiI5MWU4MDE4Mi05MTcwLTRjMDEtYmYxNC1hNDQwM2FjZTAyMGIiLCJyb2xlQ29kZSI6IkRPQ1RPUiIsImlhdCI6MTc2ODc2MDY5MH0.TAjbfbP5nszzw9-keufpBcjbI45UnZm5hHJp5sHStRg",
},
});
console.log(res.data);