diff --git a/.env b/.env new file mode 100644 index 0000000..a43d7ae --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=postgres://postgres:lyh1234@192.168.0.180:5432/rbac \ No newline at end of file diff --git a/bun.lock b/bun.lock index b1e4d6c..2a5f201 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], } } diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..1c5244e --- /dev/null +++ b/drizzle.config.ts @@ -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!, + }, +}); diff --git a/package.json b/package.json index 9e66a96..6649c91 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/seed.ts b/seed.ts new file mode 100644 index 0000000..eec15cd --- /dev/null +++ b/seed.ts @@ -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(); diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts new file mode 100644 index 0000000..b20bcef --- /dev/null +++ b/src/controllers/auth.ts @@ -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; +}; diff --git a/src/controllers/department.ts b/src/controllers/department.ts new file mode 100644 index 0000000..1029067 --- /dev/null +++ b/src/controllers/department.ts @@ -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]; +}; diff --git a/src/controllers/doctor.ts b/src/controllers/doctor.ts new file mode 100644 index 0000000..cacbdc5 --- /dev/null +++ b/src/controllers/doctor.ts @@ -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]; +}; diff --git a/src/controllers/group.ts b/src/controllers/group.ts new file mode 100644 index 0000000..9c36a6a --- /dev/null +++ b/src/controllers/group.ts @@ -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]; +}; diff --git a/src/controllers/hospital.ts b/src/controllers/hospital.ts new file mode 100644 index 0000000..4aa07af --- /dev/null +++ b/src/controllers/hospital.ts @@ -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]; +}; diff --git a/src/controllers/patient.ts b/src/controllers/patient.ts new file mode 100644 index 0000000..f9a65e2 --- /dev/null +++ b/src/controllers/patient.ts @@ -0,0 +1,194 @@ +import { eq, inArray } 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 patients = await db.query.patientTable.findMany({ + with: { + chiefDoctor: { + with: { + role: true, + }, + }, + }, + where: (table, { inArray }) => inArray(table.chiefDoctorId, doctorIds), + orderBy: (table, { asc }) => asc(table.createdAt), + }); + return patients; + } + + // 组长可以看到本小组所有医生的患者 + 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 patients = await db.query.patientTable.findMany({ + with: { + chiefDoctor: { + with: { + role: true, + }, + }, + }, + where: (table, { inArray }) => inArray(table.chiefDoctorId, doctorIds), + orderBy: (table, { asc }) => asc(table.createdAt), + }); + return patients; + } + + // 普通医生只能看到自己的患者 + const patients = await db.query.patientTable.findMany({ + with: { + chiefDoctor: { + with: { + role: true, + }, + }, + }, + where: (table, { eq }) => eq(table.chiefDoctorId, doctorId), + orderBy: (table, { asc }) => asc(table.createdAt), + }); + return patients; +}; + +// 获取单个患者 +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]; +}; diff --git a/src/controllers/role.ts b/src/controllers/role.ts new file mode 100644 index 0000000..d81796f --- /dev/null +++ b/src/controllers/role.ts @@ -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]; +}; diff --git a/src/index.ts b/src/index.ts index 9c1f7a1..8ce8a91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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; diff --git a/src/middleware/permission.ts b/src/middleware/permission.ts new file mode 100644 index 0000000..55c89f1 --- /dev/null +++ b/src/middleware/permission.ts @@ -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); +}; diff --git a/src/modules/department.ts b/src/modules/department.ts new file mode 100644 index 0000000..bd6c875 --- /dev/null +++ b/src/modules/department.ts @@ -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(), // 更新时间 +}; diff --git a/src/modules/doctor.ts b/src/modules/doctor.ts new file mode 100644 index 0000000..c3edd93 --- /dev/null +++ b/src/modules/doctor.ts @@ -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(), // 更新时间 +}; diff --git a/src/modules/group.ts b/src/modules/group.ts new file mode 100644 index 0000000..df9f013 --- /dev/null +++ b/src/modules/group.ts @@ -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(), // 更新时间 +}; diff --git a/src/modules/hospital.ts b/src/modules/hospital.ts new file mode 100644 index 0000000..51b32c3 --- /dev/null +++ b/src/modules/hospital.ts @@ -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(), // 更新时间 +}; \ No newline at end of file diff --git a/src/modules/index.ts b/src/modules/index.ts new file mode 100644 index 0000000..eea1865 --- /dev/null +++ b/src/modules/index.ts @@ -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], + }), +})); diff --git a/src/modules/patient.ts b/src/modules/patient.ts new file mode 100644 index 0000000..1d8fd0e --- /dev/null +++ b/src/modules/patient.ts @@ -0,0 +1,9 @@ +import { uuid, varchar, timestamp } from "drizzle-orm/pg-core"; + +export const Patient = { + id: uuid().primaryKey().defaultRandom(), // 主键,UUID + name: varchar().notNull(), // 患者姓名 + chiefDoctorId: uuid().notNull(), // 主刀医生ID,虚拟外键 + createdAt: timestamp().notNull().defaultNow(), // 创建时间 + updatedAt: timestamp().notNull().defaultNow(), // 更新时间 +}; diff --git a/src/modules/role.ts b/src/modules/role.ts new file mode 100644 index 0000000..3c5ae75 --- /dev/null +++ b/src/modules/role.ts @@ -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(), // 更新时间 +}; diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 0000000..fca4e7c --- /dev/null +++ b/src/routes/auth.ts @@ -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; diff --git a/src/routes/department.ts b/src/routes/department.ts new file mode 100644 index 0000000..11dedd4 --- /dev/null +++ b/src/routes/department.ts @@ -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(), + }), + } + ); // 删除科室 diff --git a/src/routes/doctor.ts b/src/routes/doctor.ts new file mode 100644 index 0000000..cb53d45 --- /dev/null +++ b/src/routes/doctor.ts @@ -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(), + }), + } + ); // 删除医生 diff --git a/src/routes/group.ts b/src/routes/group.ts new file mode 100644 index 0000000..a575744 --- /dev/null +++ b/src/routes/group.ts @@ -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(), + }), + } + ); // 删除小组 diff --git a/src/routes/hospital.ts b/src/routes/hospital.ts new file mode 100644 index 0000000..21a0f7f --- /dev/null +++ b/src/routes/hospital.ts @@ -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(), + }), + } + ); // 删除医院 + + diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..467762c --- /dev/null +++ b/src/routes/index.ts @@ -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); diff --git a/src/routes/patient.ts b/src/routes/patient.ts new file mode 100644 index 0000000..3bff225 --- /dev/null +++ b/src/routes/patient.ts @@ -0,0 +1,48 @@ +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 })); diff --git a/src/routes/role.ts b/src/routes/role.ts new file mode 100644 index 0000000..6d0b62c --- /dev/null +++ b/src/routes/role.ts @@ -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 })); diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..c23cc8d --- /dev/null +++ b/test.ts @@ -0,0 +1,13 @@ +import { treaty } from "@elysiajs/eden"; +import type { Api } from "./src"; + +const client = treaty("localhost:3000"); + +const res = await client.api.patient.get({ + headers: { + Authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJiMzcwMzgwNi1jNDRjLTRkMTEtYWMzYy03NDAwYzViODYyNmEiLCJyb2xlSWQiOiIzOTA0YmQ0OC01MzdhLTQ0MzgtODE4Yi01NDJlYjcyNjA4YmUiLCJyb2xlQ29kZSI6IkdST1VQX0xFQURFUiIsImlhdCI6MTc2ODc1OTM1Mn0.IkoYXCQy44HFG2Y7dZWHGJAmieYEuCSqAZE0oG46z40", + }, +}); + +console.log(res.data);