create-fullstack-scaffold 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +439 -0
- package/dist/cli/index.js.map +1 -0
- package/package.json +171 -0
- package/template/.agents/skills/writing-hookify-rules/SKILL.md +408 -0
- package/template/.ai-context.md +368 -0
- package/template/.claude/hookify.block-dangerous-commands.local.md +41 -0
- package/template/.claude/hookify.check-api-types.local.md +111 -0
- package/template/.claude/hookify.check-duplicate-types.local.md +287 -0
- package/template/.claude/hookify.check-test-assertions.local.md +93 -0
- package/template/.claude/hookify.remind-add-tests.local.md +209 -0
- package/template/.claude/hookify.remind-db-migration.local.md +58 -0
- package/template/.claude/hookify.remind-run-tests.local.md +52 -0
- package/template/.claude/hookify.warn-any-type.local.md +203 -0
- package/template/.claude/hookify.warn-console-log.local.md +73 -0
- package/template/.claude/rules/00-project-config.md +147 -0
- package/template/.claude/rules/01-file-types.md +127 -0
- package/template/.claude/rules/02-git-workflow.md +167 -0
- package/template/.claude/rules/10-api-type-inference.md +555 -0
- package/template/.claude/rules/20-server-api.md +349 -0
- package/template/.claude/rules/21-server-entrypoint.md +257 -0
- package/template/.claude/rules/30-client-components.md +314 -0
- package/template/.claude/rules/31-client-services.md +768 -0
- package/template/.claude/rules/32-client-state-zustand.md +324 -0
- package/template/.claude/rules/33-client-app-entry.md +276 -0
- package/template/.claude/rules/34-client-admin-module.md +445 -0
- package/template/.claude/rules/35-cli-module.md +259 -0
- package/template/.claude/rules/40-admin-module.md +495 -0
- package/template/.claude/rules/40-shared-types.md +450 -0
- package/template/.claude/rules/50-websocket.md +245 -0
- package/template/.claude/rules/51-sse.md +372 -0
- package/template/.claude/rules/52-cloudflare-realtime.md +384 -0
- package/template/.claude/rules/60-testing-standards.md +245 -0
- package/template/.claude/rules/61-hono-testing.md +200 -0
- package/template/.claude/scripts/README.md +187 -0
- package/template/.claude/scripts/check-duplicate-types.ts +188 -0
- package/template/.claude/scripts/post-edit-check-incremental.sh +61 -0
- package/template/.claude/scripts/post-edit-check.sh +22 -0
- package/template/.claude/scripts/pre-tool-use-check.sh +86 -0
- package/template/.claude/scripts/test-pre-tool-use.sh +73 -0
- package/template/.claude/scripts/validate-git-changes.sh +38 -0
- package/template/.claude/settings.json +32 -0
- package/template/.claude/skills/writing-hookify-rules/SKILL.md +408 -0
- package/template/.dev-context-compact.md +54 -0
- package/template/.dev-context.md +621 -0
- package/template/.dev-guide.md +243 -0
- package/template/.env.example +60 -0
- package/template/.eslintrc.routes.json +39 -0
- package/template/.husky/_/husky.sh +27 -0
- package/template/.husky/commit-msg +4 -0
- package/template/.husky/post-commit +4 -0
- package/template/.husky/pre-commit +19 -0
- package/template/.husky/pre-push +12 -0
- package/template/.prettierignore +22 -0
- package/template/.prettierrc +8 -0
- package/template/.sessions/3/2026-03-26T05-41-09-154Z_03bb597f-3898-4a2e-8c08-5746bbe8d95e.jsonl +18 -0
- package/template/.sessions/3/2026-03-26T05-48-30-052Z_12de4774-9cb3-494c-9852-bd1e6b0003c3.jsonl +6 -0
- package/template/.vscode/extensions.json +8 -0
- package/template/.vscode/settings.json +4 -0
- package/template/.workspaces/3/hello.py +1 -0
- package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite +0 -0
- package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-shm +0 -0
- package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-wal +0 -0
- package/template/CLAUDE.md +296 -0
- package/template/DESIGN.md +260 -0
- package/template/DEV_CONTEXT_GUIDE.md +366 -0
- package/template/DEV_TOOLS_README.md +396 -0
- package/template/FRAMEWORK_HISTORY.md +34 -0
- package/template/PROJECT_SUMMARY.md +325 -0
- package/template/QUICKSTART.md +305 -0
- package/template/README.md +154 -0
- package/template/STRUCTURE.txt +81 -0
- package/template/admin.html +13 -0
- package/template/auth-inject.html +33 -0
- package/template/commitlint.config.js +12 -0
- package/template/docs/PERMISSION_ARCHITECTURE.md +279 -0
- package/template/docs/PERMISSION_EXAMPLES.md +230 -0
- package/template/docs/PERMISSION_SYSTEM.md +301 -0
- package/template/docs/SMART_TEST.md +171 -0
- package/template/docs/runtime-abstract.md +253 -0
- package/template/drizzle/0000_rainy_boomer.sql +101 -0
- package/template/drizzle/0001_add_todo_attachments.sql +12 -0
- package/template/drizzle/0002_chilly_magneto.sql +89 -0
- package/template/drizzle/meta/0000_snapshot.json +652 -0
- package/template/drizzle/meta/0001_snapshot.json +735 -0
- package/template/drizzle/meta/0002_snapshot.json +1198 -0
- package/template/drizzle/meta/_journal.json +27 -0
- package/template/drizzle.config.ts +13 -0
- package/template/eslint-rules/README-no-middleware-in-routes.md +154 -0
- package/template/eslint-rules/__tests__/enforce-valid-method.test.ts +251 -0
- package/template/eslint-rules/__tests__/layer-boundary.test.ts +102 -0
- package/template/eslint-rules/__tests__/module-boundary.test.ts +77 -0
- package/template/eslint-rules/__tests__/no-direct-zod-import-in-file-routes.test.ts +45 -0
- package/template/eslint-rules/__tests__/no-new-old-service-naming.test.ts +76 -0
- package/template/eslint-rules/e2e-test-location.js +153 -0
- package/template/eslint-rules/enforce-valid-method.js +217 -0
- package/template/eslint-rules/flat-routes-services.js +69 -0
- package/template/eslint-rules/framework-protect.js +186 -0
- package/template/eslint-rules/layer-boundary.js +164 -0
- package/template/eslint-rules/limit-type-complexity.js +130 -0
- package/template/eslint-rules/middleware-location.js +162 -0
- package/template/eslint-rules/module-boundary.js +125 -0
- package/template/eslint-rules/no-ambiguous-file-paths.js +64 -0
- package/template/eslint-rules/no-any-on-apiclient.js +173 -0
- package/template/eslint-rules/no-boolean-success.js +43 -0
- package/template/eslint-rules/no-deep-relative-imports.js +69 -0
- package/template/eslint-rules/no-direct-fetch.js +122 -0
- package/template/eslint-rules/no-direct-ws-sse.js +57 -0
- package/template/eslint-rules/no-direct-zod-import-in-file-routes.js +54 -0
- package/template/eslint-rules/no-disable-direct-fetch.js +57 -0
- package/template/eslint-rules/no-disable-type-safe-client.js +58 -0
- package/template/eslint-rules/no-inline-schema.js +220 -0
- package/template/eslint-rules/no-middleware-in-routes.js +81 -0
- package/template/eslint-rules/no-new-old-service-naming.js +123 -0
- package/template/eslint-rules/no-type-assertion-in-rpc.js +99 -0
- package/template/eslint-rules/no-type-assertion-on-shared-types.js +109 -0
- package/template/eslint-rules/no-util-functions-in-service.js +128 -0
- package/template/eslint-rules/prefer-shared-types.js +352 -0
- package/template/eslint-rules/protect-ws-sse-interface.js +66 -0
- package/template/eslint-rules/require-antd-generic-types.js +73 -0
- package/template/eslint-rules/require-file-openapi-props.js +114 -0
- package/template/eslint-rules/require-hono-chain-syntax.js +129 -0
- package/template/eslint-rules/require-nullable-for-optional.js +99 -0
- package/template/eslint-rules/require-response-helpers.js +140 -0
- package/template/eslint-rules/require-type-safe-test-client.js +106 -0
- package/template/eslint-rules/route-location.js +72 -0
- package/template/eslint.config.js +231 -0
- package/template/index.html +17 -0
- package/template/lint-scripts/DIRECTORY_RULES.md +335 -0
- package/template/lint-scripts/README.md +299 -0
- package/template/lint-scripts/TESTING.md +273 -0
- package/template/lint-scripts/ai-context-generator.ts +159 -0
- package/template/lint-scripts/check-framework-modify.sh +8 -0
- package/template/lint-scripts/check-menu-permissions.js +242 -0
- package/template/lint-scripts/check-refs.ts +165 -0
- package/template/lint-scripts/check-route-auth-complete.js +260 -0
- package/template/lint-scripts/check-route-auth.js +212 -0
- package/template/lint-scripts/check-route-permissions.js +210 -0
- package/template/lint-scripts/config/project.config.ts +358 -0
- package/template/lint-scripts/dev-context-analyzer.ts +707 -0
- package/template/lint-scripts/dev-guide-generator.ts +830 -0
- package/template/lint-scripts/dev-server.ts +58 -0
- package/template/lint-scripts/eslint-route-auth.js +158 -0
- package/template/lint-scripts/eslint-routes-permission.js +147 -0
- package/template/lint-scripts/fix-route-auth.js +245 -0
- package/template/lint-scripts/framework/check-modify.ts +152 -0
- package/template/lint-scripts/framework/hash-utils.ts +180 -0
- package/template/lint-scripts/framework/init-baseline.ts +84 -0
- package/template/lint-scripts/framework/update-baseline.ts +148 -0
- package/template/lint-scripts/post-commit-track.ts +66 -0
- package/template/lint-scripts/quick-test.sh +27 -0
- package/template/lint-scripts/smart-test.ts +268 -0
- package/template/lint-scripts/test-history.ts +360 -0
- package/template/lint-scripts/test-tracker.ts +350 -0
- package/template/lint-scripts/validate-all.ts +250 -0
- package/template/lint-scripts/validators/api-coverage.validator.ts +355 -0
- package/template/lint-scripts/validators/client-rpc.validator.ts +190 -0
- package/template/lint-scripts/validators/client-tests.validator.ts +155 -0
- package/template/lint-scripts/validators/console-log.validator.ts +122 -0
- package/template/lint-scripts/validators/directory-structure.validator.ts +181 -0
- package/template/lint-scripts/validators/imports.validator.ts +180 -0
- package/template/lint-scripts/validators/index.ts +271 -0
- package/template/lint-scripts/validators/md-refs.validator.ts +165 -0
- package/template/lint-scripts/validators/module-tests.validator.ts +112 -0
- package/template/lint-scripts/validators/sensitive.validator.ts +185 -0
- package/template/lint-scripts/validators/server-rpc.validator.ts +141 -0
- package/template/lint-scripts/validators/test-quality.validator.ts +257 -0
- package/template/lint-scripts/validators/todos.validator.ts +126 -0
- package/template/lint-scripts/watch-validator.ts +131 -0
- package/template/modules.config.ts +71 -0
- package/template/package-lock.json +14554 -0
- package/template/package.json +158 -0
- package/template/patches/@hono+zod-openapi+1.2.2.patch +67 -0
- package/template/patches/@hono+zod-validator+0.7.6.patch +13 -0
- package/template/patches/hono+4.12.16.patch +246 -0
- package/template/patches/typescript+5.8.3.patch +26 -0
- package/template/playwright.config.ts +60 -0
- package/template/pnpm-lock.yaml +7137 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/create-template.ts +1724 -0
- package/template/src/admin/App.tsx +59 -0
- package/template/src/admin/components/AccountSwitcher.tsx +104 -0
- package/template/src/admin/components/CaptchaModal.tsx +150 -0
- package/template/src/admin/components/NotificationDrawer.tsx +213 -0
- package/template/src/admin/components/PageHeader.tsx +45 -0
- package/template/src/admin/components/PermissionConfigEditor.tsx +237 -0
- package/template/src/admin/components/PermissionGuard.tsx +108 -0
- package/template/src/admin/components/PermissionTree.tsx +193 -0
- package/template/src/admin/components/ProtectedRoute.tsx +33 -0
- package/template/src/admin/components/StatsCard.tsx +44 -0
- package/template/src/admin/components/UserFormModal.tsx +67 -0
- package/template/src/admin/components/UserTable.tsx +72 -0
- package/template/src/admin/components/__tests__/PageHeader.test.tsx +48 -0
- package/template/src/admin/components/__tests__/PermissionConfigEditor.test.tsx +147 -0
- package/template/src/admin/components/__tests__/PermissionGuard.test.tsx +221 -0
- package/template/src/admin/components/__tests__/PermissionTree.test.tsx +187 -0
- package/template/src/admin/components/__tests__/ProtectedRoute.test.tsx +129 -0
- package/template/src/admin/components/__tests__/StatsCard.test.tsx +44 -0
- package/template/src/admin/components/__tests__/UserFormModal.test.tsx +125 -0
- package/template/src/admin/components/__tests__/UserTable.test.tsx +89 -0
- package/template/src/admin/components/index.ts +7 -0
- package/template/src/admin/hooks/__tests__/useAuditLogs.test.ts +109 -0
- package/template/src/admin/hooks/__tests__/usePermissions.test.tsx +327 -0
- package/template/src/admin/hooks/__tests__/usePermissionsBranches.test.ts +198 -0
- package/template/src/admin/hooks/__tests__/useRoles.test.ts +261 -0
- package/template/src/admin/hooks/useAdminNotifications.ts +156 -0
- package/template/src/admin/hooks/useAuditLogs.ts +44 -0
- package/template/src/admin/hooks/useConfig.ts +168 -0
- package/template/src/admin/hooks/usePermissions.ts +156 -0
- package/template/src/admin/hooks/useRoles.ts +120 -0
- package/template/src/admin/layouts/Header.tsx +93 -0
- package/template/src/admin/layouts/Layout.tsx +23 -0
- package/template/src/admin/layouts/Sidebar.tsx +120 -0
- package/template/src/admin/main.tsx +18 -0
- package/template/src/admin/pages/ContentPage.tsx +252 -0
- package/template/src/admin/pages/DashboardPage.tsx +141 -0
- package/template/src/admin/pages/DisputesPage.tsx +261 -0
- package/template/src/admin/pages/LoginPage.tsx +121 -0
- package/template/src/admin/pages/MediaTestPage.tsx +524 -0
- package/template/src/admin/pages/OrdersPage.tsx +302 -0
- package/template/src/admin/pages/PermissionsPage.tsx +95 -0
- package/template/src/admin/pages/RegisterPage.tsx +103 -0
- package/template/src/admin/pages/RolesPage.tsx +330 -0
- package/template/src/admin/pages/SettingsPage.tsx +73 -0
- package/template/src/admin/pages/SystemLogsPage.tsx +218 -0
- package/template/src/admin/pages/TestCaptchaPage.tsx +120 -0
- package/template/src/admin/pages/TicketsPage.tsx +292 -0
- package/template/src/admin/pages/UsersPage.tsx +282 -0
- package/template/src/admin/pages/__tests__/ContentPage.test.tsx +213 -0
- package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +153 -0
- package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +196 -0
- package/template/src/admin/pages/__tests__/LoginPage.test.tsx +112 -0
- package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +217 -0
- package/template/src/admin/pages/__tests__/PermissionsPage.test.tsx +119 -0
- package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +179 -0
- package/template/src/admin/pages/__tests__/RolesPage.test.tsx +256 -0
- package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +66 -0
- package/template/src/admin/pages/__tests__/SystemLogsPage.test.tsx +125 -0
- package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +202 -0
- package/template/src/admin/pages/__tests__/UsersPage.test.tsx +229 -0
- package/template/src/admin/services/README.md +263 -0
- package/template/src/admin/services/TESTING.md +323 -0
- package/template/src/admin/services/__tests__/skipLoading.test.ts +146 -0
- package/template/src/admin/services/apiClient.ts +66 -0
- package/template/src/admin/services/requestInterceptor.ts +127 -0
- package/template/src/admin/services/types.ts +5 -0
- package/template/src/admin/stores/__tests__/adminStore.test.ts +184 -0
- package/template/src/admin/stores/__tests__/adminStoreBranches.test.ts +148 -0
- package/template/src/admin/stores/__tests__/captchaStore.test.ts +75 -0
- package/template/src/admin/stores/__tests__/captchaStoreBranches.test.ts +37 -0
- package/template/src/admin/stores/__tests__/loadingStore.test.ts +68 -0
- package/template/src/admin/stores/adminStore.ts +88 -0
- package/template/src/admin/stores/captchaStore.ts +47 -0
- package/template/src/admin/stores/loadingStore.ts +27 -0
- package/template/src/cli/index.ts +22 -0
- package/template/src/cli/modules/config/index.ts +132 -0
- package/template/src/cli/modules/index.ts +12 -0
- package/template/src/cli/modules/notification/index.ts +86 -0
- package/template/src/cli/modules/todo/index.ts +60 -0
- package/template/src/cli/rpc/client.ts +23 -0
- package/template/src/cli/rpc/index.ts +1 -0
- package/template/src/cli/utils/api.ts +17 -0
- package/template/src/cli/utils/auto-command.ts +229 -0
- package/template/src/cli/utils/index.ts +8 -0
- package/template/src/cli/utils/logger.ts +79 -0
- package/template/src/client/App.tsx +24 -0
- package/template/src/client/Layout.tsx +27 -0
- package/template/src/client/components/AuthButton.tsx +41 -0
- package/template/src/client/components/ConnectionStatus.tsx +75 -0
- package/template/src/client/components/EmptyState.tsx +26 -0
- package/template/src/client/components/Footer.tsx +17 -0
- package/template/src/client/components/LoadingSpinner.tsx +26 -0
- package/template/src/client/components/MessageCard.tsx +61 -0
- package/template/src/client/components/Navigation.tsx +65 -0
- package/template/src/client/components/StatusBadge.tsx +50 -0
- package/template/src/client/components/__tests__/App.test.tsx +79 -0
- package/template/src/client/components/__tests__/AuthButton.test.tsx +119 -0
- package/template/src/client/components/__tests__/ConnectionStatus.test.tsx +188 -0
- package/template/src/client/components/__tests__/EmptyState.test.tsx +70 -0
- package/template/src/client/components/__tests__/Footer.test.tsx +22 -0
- package/template/src/client/components/__tests__/LoadingSpinner.test.tsx +75 -0
- package/template/src/client/components/__tests__/MessageCard.test.tsx +142 -0
- package/template/src/client/components/__tests__/Navigation.test.tsx +40 -0
- package/template/src/client/components/__tests__/StatusBadge.test.tsx +107 -0
- package/template/src/client/components/index.ts +6 -0
- package/template/src/client/index.css +27 -0
- package/template/src/client/main.tsx +25 -0
- package/template/src/client/pages/ContentDetailPage.tsx +142 -0
- package/template/src/client/pages/ContentListPage.tsx +169 -0
- package/template/src/client/pages/NotificationPage.tsx +288 -0
- package/template/src/client/pages/TodoPage.tsx +389 -0
- package/template/src/client/pages/WebSocketPage.tsx +239 -0
- package/template/src/client/pages/__tests__/ContentDetailPage.test.tsx +176 -0
- package/template/src/client/pages/__tests__/ContentListPage.test.tsx +202 -0
- package/template/src/client/pages/__tests__/NotificationPage.test.tsx +246 -0
- package/template/src/client/pages/__tests__/TodoPage.test.tsx +433 -0
- package/template/src/client/pages/__tests__/WebSocketPage.test.tsx +378 -0
- package/template/src/client/services/apiClient.ts +66 -0
- package/template/src/client/stores/__tests__/authStore.test.ts +139 -0
- package/template/src/client/stores/__tests__/chatWSStore.test.ts +416 -0
- package/template/src/client/stores/__tests__/notificationStore.test.ts +453 -0
- package/template/src/client/stores/__tests__/todoStore.test.ts +509 -0
- package/template/src/client/stores/authStore.ts +51 -0
- package/template/src/client/stores/chatWSStore.ts +122 -0
- package/template/src/client/stores/notificationStore.ts +199 -0
- package/template/src/client/stores/todoStore.ts +207 -0
- package/template/src/server/__tests__/integration/todos-api.test.ts +138 -0
- package/template/src/server/app.ts +105 -0
- package/template/src/server/config.ts +86 -0
- package/template/src/server/core/__tests__/realtime-core.test.ts +329 -0
- package/template/src/server/core/__tests__/realtime-scanner.test.ts +210 -0
- package/template/src/server/core/__tests__/runtime-node.test.ts +330 -0
- package/template/src/server/core/__tests__/runtime.test.ts +219 -0
- package/template/src/server/core/__tests__/typed-runtime.test.ts +178 -0
- package/template/src/server/core/durable-objects/RealtimeDO.ts +206 -0
- package/template/src/server/core/index.ts +83 -0
- package/template/src/server/core/module-loader.ts +281 -0
- package/template/src/server/core/realtime-core.ts +152 -0
- package/template/src/server/core/realtime-scanner.ts +99 -0
- package/template/src/server/core/runtime-cloudflare.ts +207 -0
- package/template/src/server/core/runtime-node.ts +170 -0
- package/template/src/server/core/runtime.ts +129 -0
- package/template/src/server/core/typed-runtime.ts +80 -0
- package/template/src/server/db/__tests__/driver.test.ts +203 -0
- package/template/src/server/db/config.ts +1 -0
- package/template/src/server/db/driver-cloudflare.ts +50 -0
- package/template/src/server/db/driver.ts +116 -0
- package/template/src/server/db/index.ts +3 -0
- package/template/src/server/db/init.ts +412 -0
- package/template/src/server/db/schema/api-endpoints.ts +23 -0
- package/template/src/server/db/schema/contents.ts +31 -0
- package/template/src/server/db/schema/disputes.ts +39 -0
- package/template/src/server/db/schema/index.ts +14 -0
- package/template/src/server/db/schema/notifications.ts +16 -0
- package/template/src/server/db/schema/orders.ts +30 -0
- package/template/src/server/db/schema/permission-audit-logs.ts +17 -0
- package/template/src/server/db/schema/permission-route-mappings.ts +22 -0
- package/template/src/server/db/schema/permissions.ts +17 -0
- package/template/src/server/db/schema/role-permissions.ts +22 -0
- package/template/src/server/db/schema/roles.ts +17 -0
- package/template/src/server/db/schema/tickets.ts +62 -0
- package/template/src/server/db/schema/todo-attachments.ts +22 -0
- package/template/src/server/db/schema/todos.ts +21 -0
- package/template/src/server/db/schema/user-roles.ts +17 -0
- package/template/src/server/db/seeds/index.ts +1 -0
- package/template/src/server/db/seeds/permission-data.ts +354 -0
- package/template/src/server/db/test-setup.ts +647 -0
- package/template/src/server/entries/cloudflare.ts +71 -0
- package/template/src/server/entries/node.ts +190 -0
- package/template/src/server/index.ts +62 -0
- package/template/src/server/middleware/__tests__/auth-simple.test.ts +147 -0
- package/template/src/server/middleware/__tests__/auth.test.ts +276 -0
- package/template/src/server/middleware/__tests__/captcha.test.ts +258 -0
- package/template/src/server/middleware/__tests__/error-handler.test.ts +364 -0
- package/template/src/server/middleware/__tests__/error-response-format.test.ts +80 -0
- package/template/src/server/middleware/__tests__/permission.test.ts +312 -0
- package/template/src/server/middleware/audit-log.ts +101 -0
- package/template/src/server/middleware/auth.ts +235 -0
- package/template/src/server/middleware/captcha.ts +166 -0
- package/template/src/server/middleware/cors.ts +30 -0
- package/template/src/server/middleware/error-handler.ts +135 -0
- package/template/src/server/middleware/index.ts +24 -0
- package/template/src/server/middleware/logger.ts +73 -0
- package/template/src/server/middleware/permission.ts +127 -0
- package/template/src/server/middleware/rate-limit.ts +69 -0
- package/template/src/server/middleware/realtime-env.ts +12 -0
- package/template/src/server/module-admin/__tests__/admin-routes.test.ts +360 -0
- package/template/src/server/module-admin/__tests__/admin-service.test.ts +222 -0
- package/template/src/server/module-admin/__tests__/audit-log-routes.test.ts +261 -0
- package/template/src/server/module-admin/__tests__/export-routes.test.ts +160 -0
- package/template/src/server/module-admin/__tests__/media-routes.test.ts +122 -0
- package/template/src/server/module-admin/module.ts +40 -0
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +197 -0
- package/template/src/server/module-admin/routes/admin-routes.ts +20 -0
- package/template/src/server/module-admin/routes/auth-routes.ts +86 -0
- package/template/src/server/module-admin/routes/export-routes.ts +207 -0
- package/template/src/server/module-admin/routes/media-routes.ts +78 -0
- package/template/src/server/module-admin/routes/system-routes.ts +89 -0
- package/template/src/server/module-admin/routes/user-management-routes.ts +143 -0
- package/template/src/server/module-admin/services/admin-service.ts +268 -0
- package/template/src/server/module-captcha/__tests__/captcha-route.test.ts +35 -0
- package/template/src/server/module-captcha/__tests__/captcha-service.test.ts +53 -0
- package/template/src/server/module-captcha/module.ts +35 -0
- package/template/src/server/module-captcha/routes/captcha-routes.ts +68 -0
- package/template/src/server/module-captcha/services/captcha-service.ts +3 -0
- package/template/src/server/module-chat/__tests__/chat-routes.test.ts +39 -0
- package/template/src/server/module-chat/__tests__/chat-rpc.test.ts +185 -0
- package/template/src/server/module-chat/__tests__/chat-service.test.ts +64 -0
- package/template/src/server/module-chat/module.ts +27 -0
- package/template/src/server/module-chat/routes/chat-routes.ts +46 -0
- package/template/src/server/module-chat/services/chat-service.ts +38 -0
- package/template/src/server/module-content/__tests__/content-route.test.ts +40 -0
- package/template/src/server/module-content/__tests__/content-service.test.ts +107 -0
- package/template/src/server/module-content/module.ts +39 -0
- package/template/src/server/module-content/routes/content-routes.ts +182 -0
- package/template/src/server/module-content/routes/public-content-routes.ts +58 -0
- package/template/src/server/module-content/services/content-service.ts +224 -0
- package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +43 -0
- package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +135 -0
- package/template/src/server/module-dispute/module.ts +30 -0
- package/template/src/server/module-dispute/routes/dispute-routes.ts +153 -0
- package/template/src/server/module-dispute/services/dispute-service.ts +255 -0
- package/template/src/server/module-file/__tests__/file-routes.test.ts +284 -0
- package/template/src/server/module-file/__tests__/file-storage-service.test.ts +323 -0
- package/template/src/server/module-file/module.ts +28 -0
- package/template/src/server/module-file/routes/file-routes.ts +288 -0
- package/template/src/server/module-notifications/__tests__/notification-route-rpc.test.ts +375 -0
- package/template/src/server/module-notifications/__tests__/notification-service.test.ts +241 -0
- package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +289 -0
- package/template/src/server/module-notifications/module.ts +32 -0
- package/template/src/server/module-notifications/routes/notification-routes.ts +190 -0
- package/template/src/server/module-notifications/services/notification-service.ts +104 -0
- package/template/src/server/module-order/__tests__/order-route.test.ts +258 -0
- package/template/src/server/module-order/__tests__/order-service.test.ts +222 -0
- package/template/src/server/module-order/module.ts +30 -0
- package/template/src/server/module-order/routes/order-routes.ts +202 -0
- package/template/src/server/module-order/services/order-service.ts +268 -0
- package/template/src/server/module-permission/__tests__/permission-middleware.test.ts +90 -0
- package/template/src/server/module-permission/__tests__/permission-routes.test.ts +330 -0
- package/template/src/server/module-permission/__tests__/permission-service-impl.test.ts +388 -0
- package/template/src/server/module-permission/__tests__/permission-service.test.ts +190 -0
- package/template/src/server/module-permission/__tests__/role-routes.test.ts +215 -0
- package/template/src/server/module-permission/module.ts +68 -0
- package/template/src/server/module-permission/routes/audit-log-routes.ts +76 -0
- package/template/src/server/module-permission/routes/permission-routes.ts +184 -0
- package/template/src/server/module-permission/routes/role-routes.ts +263 -0
- package/template/src/server/module-permission/services/audit-log-service.ts +108 -0
- package/template/src/server/module-permission/services/permission-service-impl.ts +233 -0
- package/template/src/server/module-permission/services/permission-service.ts +256 -0
- package/template/src/server/module-permission/services/role-service.ts +122 -0
- package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +173 -0
- package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +201 -0
- package/template/src/server/module-ticket/module.ts +30 -0
- package/template/src/server/module-ticket/routes/ticket-routes.ts +210 -0
- package/template/src/server/module-ticket/services/ticket-service.ts +297 -0
- package/template/src/server/module-todos/__tests__/todo-service.test.ts +196 -0
- package/template/src/server/module-todos/__tests__/todos-file-upload.test.ts +439 -0
- package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +575 -0
- package/template/src/server/module-todos/module.ts +30 -0
- package/template/src/server/module-todos/routes/todos-routes.ts +260 -0
- package/template/src/server/module-todos/services/todo-service.ts +202 -0
- package/template/src/server/route-registry.ts +47 -0
- package/template/src/server/test-utils/test-client.ts +66 -0
- package/template/src/server/test-utils/test-server.ts +131 -0
- package/template/src/server/types/bindings.ts +10 -0
- package/template/src/server/utils/__tests__/app-error.test.ts +374 -0
- package/template/src/server/utils/__tests__/auth.test.ts +76 -0
- package/template/src/server/utils/__tests__/captcha.test.ts +80 -0
- package/template/src/server/utils/__tests__/file-storage.test.ts +472 -0
- package/template/src/server/utils/__tests__/logger.test.ts +221 -0
- package/template/src/server/utils/__tests__/route-helpers.test.ts +264 -0
- package/template/src/server/utils/app-error.ts +355 -0
- package/template/src/server/utils/auth.ts +65 -0
- package/template/src/server/utils/captcha.ts +119 -0
- package/template/src/server/utils/date.ts +50 -0
- package/template/src/server/utils/env.ts +4 -0
- package/template/src/server/utils/file-storage.ts +462 -0
- package/template/src/server/utils/generate.ts +26 -0
- package/template/src/server/utils/id-helpers.ts +5 -0
- package/template/src/server/utils/logger.ts +139 -0
- package/template/src/server/utils/permission-utils.ts +6 -0
- package/template/src/server/utils/response.ts +40 -0
- package/template/src/server/utils/route-helpers.ts +200 -0
- package/template/src/server/utils/uuid.ts +8 -0
- package/template/src/server/utils/ws-helper.ts +30 -0
- package/template/src/shared/constants/index.ts +1 -0
- package/template/src/shared/constants/resource-types.ts +72 -0
- package/template/src/shared/core/__tests__/sse-client.test.ts +376 -0
- package/template/src/shared/core/__tests__/ws-client.test.ts +468 -0
- package/template/src/shared/core/api-request.ts +192 -0
- package/template/src/shared/core/api-schemas.ts +41 -0
- package/template/src/shared/core/index.ts +37 -0
- package/template/src/shared/core/module-manifest.ts +120 -0
- package/template/src/shared/core/protocol-types.ts +26 -0
- package/template/src/shared/core/sse-client.ts +185 -0
- package/template/src/shared/core/ws-client.ts +236 -0
- package/template/src/shared/hooks/index.ts +2 -0
- package/template/src/shared/hooks/useSSE.ts +57 -0
- package/template/src/shared/hooks/useWebSocket.ts +66 -0
- package/template/src/shared/index.ts +5 -0
- package/template/src/shared/modules/admin/index.ts +1 -0
- package/template/src/shared/modules/admin/schemas.ts +100 -0
- package/template/src/shared/modules/audit/index.ts +1 -0
- package/template/src/shared/modules/audit/schemas.ts +24 -0
- package/template/src/shared/modules/captcha/index.ts +1 -0
- package/template/src/shared/modules/captcha/schemas.ts +19 -0
- package/template/src/shared/modules/chat/index.ts +38 -0
- package/template/src/shared/modules/content/index.ts +1 -0
- package/template/src/shared/modules/content/schemas.ts +53 -0
- package/template/src/shared/modules/dispute/index.ts +1 -0
- package/template/src/shared/modules/dispute/schemas.ts +62 -0
- package/template/src/shared/modules/files/index.ts +11 -0
- package/template/src/shared/modules/files/schemas.ts +44 -0
- package/template/src/shared/modules/index.ts +70 -0
- package/template/src/shared/modules/notifications/index.ts +26 -0
- package/template/src/shared/modules/notifications/schemas.ts +73 -0
- package/template/src/shared/modules/order/index.ts +1 -0
- package/template/src/shared/modules/order/schemas.ts +63 -0
- package/template/src/shared/modules/permission/index.ts +42 -0
- package/template/src/shared/modules/permission/permission-dependencies.ts +50 -0
- package/template/src/shared/modules/permission/permissions.ts +275 -0
- package/template/src/shared/modules/permission/schemas.ts +92 -0
- package/template/src/shared/modules/permission/types.ts +23 -0
- package/template/src/shared/modules/role/index.ts +1 -0
- package/template/src/shared/modules/role/schemas.ts +46 -0
- package/template/src/shared/modules/ticket/index.ts +1 -0
- package/template/src/shared/modules/ticket/schemas.ts +77 -0
- package/template/src/shared/modules/todos/index.ts +20 -0
- package/template/src/shared/modules/todos/schemas.ts +65 -0
- package/template/src/shared/schemas/index.ts +79 -0
- package/template/src/types/global.d.ts +14 -0
- package/template/src/vite-env.d.ts +21 -0
- package/template/tailwind.config.js +11 -0
- package/template/tests/e2e/admin-crud.spec.ts +81 -0
- package/template/tests/e2e/case-o1.spec.ts +108 -0
- package/template/tests/e2e/global-setup.ts +73 -0
- package/template/tests/e2e/global-teardown.ts +28 -0
- package/template/tests/e2e/notification.spec.ts +458 -0
- package/template/tests/e2e/todo.spec.ts +623 -0
- package/template/tests/e2e/websocket.spec.ts +254 -0
- package/template/tsconfig.json +35 -0
- package/template/tsup.config.ts +95 -0
- package/template/vite-plugins.ts +113 -0
- package/template/vite.config.ts +96 -0
- package/template/vitest.config.ts +53 -0
- package/template/vitest.integration.config.ts +24 -0
- package/template/vitest.integration.setup.ts +22 -0
- package/template/vitest.setup.ts +74 -0
- package/template/wrangler.toml +33 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, beforeAll, afterAll } from 'vitest'
|
|
2
|
+
import { PermissionService } from '../services/permission-service-impl'
|
|
3
|
+
import { roleService } from '../services/role-service'
|
|
4
|
+
import { setupTestDatabase, cleanupTestDatabase } from '@server/db/test-setup'
|
|
5
|
+
|
|
6
|
+
describe('Permission Service', () => {
|
|
7
|
+
let service: PermissionService
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
await setupTestDatabase()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await cleanupTestDatabase()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
service = new PermissionService()
|
|
19
|
+
// Reset role_user permissions to default state
|
|
20
|
+
const currentPermissions = await service.getRolePermissions('role_user')
|
|
21
|
+
for (const perm of currentPermissions) {
|
|
22
|
+
if (perm.id !== 'perm_content_view' && perm.id !== 'perm_order_view') {
|
|
23
|
+
await service.revokePermissionFromRole('role_user', perm.id)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('getAll', () => {
|
|
29
|
+
it('should return all active permissions', async () => {
|
|
30
|
+
const permissions = await service.getAll()
|
|
31
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
32
|
+
expect(permissions.every(p => p.isActive)).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should return permissions with correct structure', async () => {
|
|
36
|
+
const permissions = await service.getAll()
|
|
37
|
+
const permission = permissions[0]
|
|
38
|
+
expect(permission).toHaveProperty('id')
|
|
39
|
+
expect(permission).toHaveProperty('code')
|
|
40
|
+
expect(permission).toHaveProperty('name')
|
|
41
|
+
expect(permission).toHaveProperty('label')
|
|
42
|
+
expect(permission).toHaveProperty('category')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should return permissions with valid categories', async () => {
|
|
46
|
+
const permissions = await service.getAll()
|
|
47
|
+
const validCategories = [
|
|
48
|
+
'user',
|
|
49
|
+
'content',
|
|
50
|
+
'system',
|
|
51
|
+
'data',
|
|
52
|
+
'order',
|
|
53
|
+
'ticket',
|
|
54
|
+
'dispute',
|
|
55
|
+
'role',
|
|
56
|
+
]
|
|
57
|
+
expect(permissions.every(p => validCategories.includes(p.category))).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('getById', () => {
|
|
62
|
+
it('should return permission by id', async () => {
|
|
63
|
+
const permission = await service.getById('perm_user_view')
|
|
64
|
+
expect(permission).toBeDefined()
|
|
65
|
+
expect(permission?.code).toBe('user:view')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should return undefined for non-existent id', async () => {
|
|
69
|
+
const permission = await service.getById('non-existent-id')
|
|
70
|
+
expect(permission).toBeUndefined()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should return permission with all required fields', async () => {
|
|
74
|
+
const permission = await service.getById('perm_user_view')
|
|
75
|
+
expect(permission?.id).toBe('perm_user_view')
|
|
76
|
+
expect(permission?.name).toBe('查看用户')
|
|
77
|
+
expect(permission?.label).toBe('查看用户')
|
|
78
|
+
expect(permission?.category).toBe('user')
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('getByCode', () => {
|
|
83
|
+
it('should return permission by code', async () => {
|
|
84
|
+
const permission = await service.getByCode('user:view')
|
|
85
|
+
expect(permission).toBeDefined()
|
|
86
|
+
expect(permission?.id).toBe('perm_user_view')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should return undefined for non-existent code', async () => {
|
|
90
|
+
const permission = await service.getByCode('non:existent')
|
|
91
|
+
expect(permission).toBeUndefined()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should find permission by exact code match', async () => {
|
|
95
|
+
const permission = await service.getByCode('content:view')
|
|
96
|
+
expect(permission?.id).toBe('perm_content_view')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('getByCategory', () => {
|
|
101
|
+
it('should return permissions by category', async () => {
|
|
102
|
+
const permissions = await service.getByCategory('user')
|
|
103
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
104
|
+
expect(permissions.every(p => p.category === 'user')).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should return empty array for non-existent category', async () => {
|
|
108
|
+
const permissions = await service.getByCategory('non-existent')
|
|
109
|
+
expect(permissions).toEqual([])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should return all permissions for each valid category', async () => {
|
|
113
|
+
const categories = ['user', 'content', 'system', 'data', 'order', 'ticket']
|
|
114
|
+
for (const category of categories) {
|
|
115
|
+
const permissions = await service.getByCategory(category)
|
|
116
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('getRolePermissions', () => {
|
|
122
|
+
it('should return permissions for super admin role', async () => {
|
|
123
|
+
const permissions = await service.getRolePermissions('role_super_admin')
|
|
124
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should return permissions for customer service role', async () => {
|
|
128
|
+
const permissions = await service.getRolePermissions('role_customer_service')
|
|
129
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should return permissions for user role', async () => {
|
|
133
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
134
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should return empty array for non-existent role', async () => {
|
|
138
|
+
const permissions = await service.getRolePermissions('non-existent')
|
|
139
|
+
expect(permissions).toEqual([])
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should return only active permissions', async () => {
|
|
143
|
+
const permissions = await service.getRolePermissions('role_super_admin')
|
|
144
|
+
expect(permissions.every(p => p.isActive)).toBe(true)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should return permissions with correct permission IDs', async () => {
|
|
148
|
+
const permissions = await service.getRolePermissions('role_customer_service')
|
|
149
|
+
const permissionIds = permissions.map(p => p.id)
|
|
150
|
+
expect(permissionIds).toContain('perm_user_view')
|
|
151
|
+
expect(permissionIds).toContain('perm_content_view')
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('getUserPermissions', () => {
|
|
156
|
+
it('should return empty array when roleCode is not provided', async () => {
|
|
157
|
+
const permissions = await service.getUserPermissions('user-123')
|
|
158
|
+
expect(permissions).toEqual([])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should return empty array when roleCode is empty string', async () => {
|
|
162
|
+
const permissions = await service.getUserPermissions('user-123', '')
|
|
163
|
+
expect(permissions).toEqual([])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should return permissions for valid role code', async () => {
|
|
167
|
+
const permissions = await service.getUserPermissions('user-123', 'customer_service')
|
|
168
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should return empty array for non-existent role code', async () => {
|
|
172
|
+
const permissions = await service.getUserPermissions('user-123', 'non-existent')
|
|
173
|
+
expect(permissions).toEqual([])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should return same permissions as getRolePermissions', async () => {
|
|
177
|
+
const role = await roleService.getByCode('customer_service')
|
|
178
|
+
const userPermissions = await service.getUserPermissions('user-123', 'customer_service')
|
|
179
|
+
const rolePermissions = await service.getRolePermissions(role?.id as string)
|
|
180
|
+
expect(userPermissions.length).toBe(rolePermissions.length)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should return all permissions for super admin', async () => {
|
|
184
|
+
const permissions = await service.getUserPermissions('user-123', 'super_admin')
|
|
185
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should return limited permissions for user role', async () => {
|
|
189
|
+
const permissions = await service.getUserPermissions('user-123', 'user')
|
|
190
|
+
expect(permissions.length).toBeGreaterThan(0)
|
|
191
|
+
expect(permissions.length).toBeLessThan(20)
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('hasPermission', () => {
|
|
196
|
+
it('should return true when user has permission', async () => {
|
|
197
|
+
const hasPermission = await service.hasPermission(
|
|
198
|
+
'test-customer-service-1',
|
|
199
|
+
'user:view',
|
|
200
|
+
'customer_service'
|
|
201
|
+
)
|
|
202
|
+
expect(hasPermission).toBe(true)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should return false when user does not have permission', async () => {
|
|
206
|
+
const hasPermission = await service.hasPermission('test-user-1', 'user:view', 'user')
|
|
207
|
+
expect(hasPermission).toBe(false)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('should check permission by code', async () => {
|
|
211
|
+
const hasPermission = await service.hasPermission(
|
|
212
|
+
'test-customer-service-1',
|
|
213
|
+
'content:view',
|
|
214
|
+
'customer_service'
|
|
215
|
+
)
|
|
216
|
+
expect(hasPermission).toBe(true)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
describe('assignPermissionToRole', () => {
|
|
221
|
+
it('should assign permission to role', async () => {
|
|
222
|
+
await service.assignPermissionToRole('role_user', 'perm_user_create')
|
|
223
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
224
|
+
const permissionIds = permissions.map(p => p.id)
|
|
225
|
+
expect(permissionIds).toContain('perm_user_create')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should not duplicate permission assignment', async () => {
|
|
229
|
+
await service.assignPermissionToRole('role_user', 'perm_user_view')
|
|
230
|
+
await service.assignPermissionToRole('role_user', 'perm_user_view')
|
|
231
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
232
|
+
const viewPermissions = permissions.filter(p => p.id === 'perm_user_view')
|
|
233
|
+
expect(viewPermissions.length).toBe(1)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should allow assigning multiple permissions', async () => {
|
|
237
|
+
await service.assignPermissionToRole('role_user', 'perm_user_create')
|
|
238
|
+
await service.assignPermissionToRole('role_user', 'perm_user_update')
|
|
239
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
240
|
+
const permissionIds = permissions.map(p => p.id)
|
|
241
|
+
expect(permissionIds).toContain('perm_user_create')
|
|
242
|
+
expect(permissionIds).toContain('perm_user_update')
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
describe('revokePermissionFromRole', () => {
|
|
247
|
+
it('should revoke permission from role', async () => {
|
|
248
|
+
await service.assignPermissionToRole('role_user', 'perm_user_create')
|
|
249
|
+
await service.revokePermissionFromRole('role_user', 'perm_user_create')
|
|
250
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
251
|
+
const permissionIds = permissions.map(p => p.id)
|
|
252
|
+
expect(permissionIds).not.toContain('perm_user_create')
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('should handle revoking non-existent permission', async () => {
|
|
256
|
+
await service.revokePermissionFromRole('role_user', 'non-existent-permission')
|
|
257
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
258
|
+
expect(permissions).toBeDefined()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('should only revoke specified permission', async () => {
|
|
262
|
+
await service.assignPermissionToRole('role_user', 'perm_user_create')
|
|
263
|
+
await service.assignPermissionToRole('role_user', 'perm_user_update')
|
|
264
|
+
await service.revokePermissionFromRole('role_user', 'perm_user_create')
|
|
265
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
266
|
+
const permissionIds = permissions.map(p => p.id)
|
|
267
|
+
expect(permissionIds).not.toContain('perm_user_create')
|
|
268
|
+
expect(permissionIds).toContain('perm_user_update')
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
describe('Permission Dependencies', () => {
|
|
273
|
+
it('should have user:view permission for customer service', async () => {
|
|
274
|
+
const permissions = await service.getRolePermissions('role_customer_service')
|
|
275
|
+
const permissionCodes = permissions.map(p => p.code)
|
|
276
|
+
expect(permissionCodes).toContain('user:view')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('should have content:view permission for customer service', async () => {
|
|
280
|
+
const permissions = await service.getRolePermissions('role_customer_service')
|
|
281
|
+
const permissionCodes = permissions.map(p => p.code)
|
|
282
|
+
expect(permissionCodes).toContain('content:view')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should have order permissions for customer service', async () => {
|
|
286
|
+
const permissions = await service.getRolePermissions('role_customer_service')
|
|
287
|
+
const permissionCodes = permissions.map(p => p.code)
|
|
288
|
+
expect(permissionCodes).toContain('order:view')
|
|
289
|
+
expect(permissionCodes).toContain('order:process')
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('should have ticket permissions for customer service', async () => {
|
|
293
|
+
const permissions = await service.getRolePermissions('role_customer_service')
|
|
294
|
+
const permissionCodes = permissions.map(p => p.code)
|
|
295
|
+
expect(permissionCodes).toContain('ticket:view')
|
|
296
|
+
expect(permissionCodes).toContain('ticket:reply')
|
|
297
|
+
expect(permissionCodes).toContain('ticket:close')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('should have limited permissions for user role', async () => {
|
|
301
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
302
|
+
const permissionCodes = permissions.map(p => p.code)
|
|
303
|
+
expect(permissionCodes).toContain('content:view')
|
|
304
|
+
expect(permissionCodes).toContain('order:view')
|
|
305
|
+
expect(permissionCodes).not.toContain('user:view')
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should have all permissions for super admin', async () => {
|
|
309
|
+
const permissions = await service.getRolePermissions('role_super_admin')
|
|
310
|
+
const permissionCodes = permissions.map(p => p.code)
|
|
311
|
+
expect(permissionCodes).toContain('user:view')
|
|
312
|
+
expect(permissionCodes).toContain('user:create')
|
|
313
|
+
expect(permissionCodes).toContain('content:view')
|
|
314
|
+
expect(permissionCodes).toContain('system:settings')
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
describe('Permission Categories', () => {
|
|
319
|
+
it('should have correct number of user permissions', async () => {
|
|
320
|
+
const permissions = await service.getByCategory('user')
|
|
321
|
+
expect(permissions.length).toBe(4)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should have correct number of content permissions', async () => {
|
|
325
|
+
const permissions = await service.getByCategory('content')
|
|
326
|
+
expect(permissions.length).toBe(4)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('should have correct number of system permissions', async () => {
|
|
330
|
+
const permissions = await service.getByCategory('system')
|
|
331
|
+
expect(permissions.length).toBe(3)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('should have correct number of order permissions', async () => {
|
|
335
|
+
const permissions = await service.getByCategory('order')
|
|
336
|
+
expect(permissions.length).toBe(5)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('should have correct number of ticket permissions', async () => {
|
|
340
|
+
const permissions = await service.getByCategory('ticket')
|
|
341
|
+
expect(permissions.length).toBe(6)
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('should have correct number of data permissions', async () => {
|
|
345
|
+
const permissions = await service.getByCategory('data')
|
|
346
|
+
expect(permissions.length).toBe(2)
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
describe('Edge Cases', () => {
|
|
351
|
+
it('should handle empty role permissions', async () => {
|
|
352
|
+
const permissions = await service.getRolePermissions('non-existent-role')
|
|
353
|
+
expect(permissions).toEqual([])
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('should handle assigning permission to non-existent role gracefully', async () => {
|
|
357
|
+
// 由于外键约束,无法向不存在的角色分配权限
|
|
358
|
+
// 测试应该验证服务层是否正确处理这种情况
|
|
359
|
+
try {
|
|
360
|
+
await service.assignPermissionToRole('non-existent-role', 'perm_user_view')
|
|
361
|
+
// 如果成功,验证权限是否被分配
|
|
362
|
+
const permissions = await service.getRolePermissions('non-existent-role')
|
|
363
|
+
expect(permissions.length).toBe(0)
|
|
364
|
+
} catch (error) {
|
|
365
|
+
// 如果抛出错误,验证是外键约束错误
|
|
366
|
+
expect(error).toBeDefined()
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('should handle revoking from empty role', async () => {
|
|
371
|
+
await service.revokePermissionFromRole('non-existent-role', 'perm_user_view')
|
|
372
|
+
const permissions = await service.getRolePermissions('non-existent-role')
|
|
373
|
+
expect(permissions).toEqual([])
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('should handle multiple assignments and revocations', async () => {
|
|
377
|
+
await service.assignPermissionToRole('role_user', 'perm_user_view')
|
|
378
|
+
await service.assignPermissionToRole('role_user', 'perm_user_create')
|
|
379
|
+
await service.assignPermissionToRole('role_user', 'perm_user_update')
|
|
380
|
+
await service.revokePermissionFromRole('role_user', 'perm_user_create')
|
|
381
|
+
const permissions = await service.getRolePermissions('role_user')
|
|
382
|
+
const permissionIds = permissions.map(p => p.id)
|
|
383
|
+
expect(permissionIds).toContain('perm_user_view')
|
|
384
|
+
expect(permissionIds).toContain('perm_user_update')
|
|
385
|
+
expect(permissionIds).not.toContain('perm_user_create')
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
|
+
})
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import * as service from '../services/permission-service'
|
|
3
|
+
import { Role } from '@shared/modules/permission'
|
|
4
|
+
import * as fs from 'fs'
|
|
5
|
+
import * as path from 'path'
|
|
6
|
+
|
|
7
|
+
describe('Permission Service', () => {
|
|
8
|
+
describe('getAllRoles', () => {
|
|
9
|
+
it('should return all roles', () => {
|
|
10
|
+
const result = service.getAllRoles()
|
|
11
|
+
expect(Array.isArray(result)).toBe(true)
|
|
12
|
+
expect(result.length).toBe(3)
|
|
13
|
+
expect(result[0]).toHaveProperty('role')
|
|
14
|
+
expect(result[0]).toHaveProperty('label')
|
|
15
|
+
expect(result[0]).toHaveProperty('permissions')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('should contain correct role values', () => {
|
|
19
|
+
const result = service.getAllRoles()
|
|
20
|
+
const roles = result.map(r => r.role)
|
|
21
|
+
expect(roles).toContain(Role.SUPER_ADMIN)
|
|
22
|
+
expect(roles).toContain(Role.CUSTOMER_SERVICE)
|
|
23
|
+
expect(roles).toContain(Role.USER)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('getAllPermissions', () => {
|
|
28
|
+
it('should return all permissions', () => {
|
|
29
|
+
const result = service.getAllPermissions()
|
|
30
|
+
expect(Array.isArray(result)).toBe(true)
|
|
31
|
+
expect(result.length).toBeGreaterThan(0)
|
|
32
|
+
expect(result[0]).toHaveProperty('permission')
|
|
33
|
+
expect(result[0]).toHaveProperty('label')
|
|
34
|
+
expect(result[0]).toHaveProperty('category')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should have valid category values', () => {
|
|
38
|
+
const result = service.getAllPermissions()
|
|
39
|
+
const categories = result.map(r => r.category)
|
|
40
|
+
// 从 PERMISSION_CATEGORIES 中提取的所有有效分类
|
|
41
|
+
const validCategories = [
|
|
42
|
+
'user',
|
|
43
|
+
'content',
|
|
44
|
+
'system',
|
|
45
|
+
'data',
|
|
46
|
+
'order',
|
|
47
|
+
'ticket',
|
|
48
|
+
'role',
|
|
49
|
+
'dispute',
|
|
50
|
+
'notification',
|
|
51
|
+
'todo',
|
|
52
|
+
'chat',
|
|
53
|
+
'other',
|
|
54
|
+
]
|
|
55
|
+
categories.forEach(cat => {
|
|
56
|
+
expect(validCategories).toContain(cat)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('getUserPermissions', () => {
|
|
62
|
+
it('should return user permissions for super admin', () => {
|
|
63
|
+
const result = service.getUserPermissions('user-1', Role.SUPER_ADMIN)
|
|
64
|
+
expect(result).toHaveProperty('userId', 'user-1')
|
|
65
|
+
expect(result).toHaveProperty('role', Role.SUPER_ADMIN)
|
|
66
|
+
expect(result.permissions.length).toBeGreaterThan(0)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should return user permissions for customer service', () => {
|
|
70
|
+
const result = service.getUserPermissions('user-2', Role.CUSTOMER_SERVICE)
|
|
71
|
+
expect(result).toHaveProperty('userId', 'user-2')
|
|
72
|
+
expect(result).toHaveProperty('role', Role.CUSTOMER_SERVICE)
|
|
73
|
+
expect(result.permissions.length).toBeGreaterThan(0)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should return user permissions for regular user', () => {
|
|
77
|
+
const result = service.getUserPermissions('user-3', Role.USER)
|
|
78
|
+
expect(result).toHaveProperty('userId', 'user-3')
|
|
79
|
+
expect(result).toHaveProperty('role', Role.USER)
|
|
80
|
+
expect(result.permissions.length).toBeGreaterThan(0)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should return different permission counts for different roles', () => {
|
|
84
|
+
const superAdminPermissions = service.getUserPermissions('user-1', Role.SUPER_ADMIN)
|
|
85
|
+
const customerServicePermissions = service.getUserPermissions('user-2', Role.CUSTOMER_SERVICE)
|
|
86
|
+
const userPermissions = service.getUserPermissions('user-3', Role.USER)
|
|
87
|
+
|
|
88
|
+
expect(superAdminPermissions.permissions.length).toBeGreaterThan(
|
|
89
|
+
customerServicePermissions.permissions.length
|
|
90
|
+
)
|
|
91
|
+
expect(customerServicePermissions.permissions.length).toBeGreaterThan(
|
|
92
|
+
userPermissions.permissions.length
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('Error Scenarios', () => {
|
|
98
|
+
it('should handle empty userId gracefully', () => {
|
|
99
|
+
const result = service.getUserPermissions('', Role.USER)
|
|
100
|
+
expect(result).not.toBeNull()
|
|
101
|
+
expect(result.userId).toBe('')
|
|
102
|
+
expect(result.permissions.length).toBeGreaterThan(0)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should handle unknown role gracefully', () => {
|
|
106
|
+
const result = service.getUserPermissions('user-unknown', 'unknown_role' as Role)
|
|
107
|
+
expect(result).not.toBeNull()
|
|
108
|
+
expect(result).toHaveProperty('userId')
|
|
109
|
+
expect(result).toHaveProperty('role')
|
|
110
|
+
expect(Array.isArray(result.permissions)).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should return empty permissions for invalid role', () => {
|
|
114
|
+
const result = service.getUserPermissions('user-test', 'invalid' as Role)
|
|
115
|
+
expect(result).toBeDefined()
|
|
116
|
+
expect(result.permissions).toBeDefined()
|
|
117
|
+
expect(Array.isArray(result.permissions)).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should handle null role parameter', () => {
|
|
121
|
+
expect(() => {
|
|
122
|
+
service.getUserPermissions('user-test', null as unknown as Role)
|
|
123
|
+
}).not.toThrow()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('Naming Convention Compliance', () => {
|
|
128
|
+
it('should not use "-new" or "-old" suffix in service file names', () => {
|
|
129
|
+
const servicesDir = path.join(__dirname, '../services')
|
|
130
|
+
const files = fs.readdirSync(servicesDir)
|
|
131
|
+
|
|
132
|
+
const invalidPatterns = ['-new', '-old', 'new-', 'old-', '.new.', '.old.']
|
|
133
|
+
const invalidFiles: string[] = []
|
|
134
|
+
|
|
135
|
+
files.forEach(file => {
|
|
136
|
+
const lowerFile = file.toLowerCase()
|
|
137
|
+
invalidPatterns.forEach(pattern => {
|
|
138
|
+
if (lowerFile.includes(pattern)) {
|
|
139
|
+
invalidFiles.push(file)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(invalidFiles).toEqual([])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should use V1, V2, V3 naming convention instead of new/old', () => {
|
|
148
|
+
const servicesDir = path.join(__dirname, '../services')
|
|
149
|
+
const files = fs.readdirSync(servicesDir)
|
|
150
|
+
|
|
151
|
+
const validVersionPattern = /V[1-9][0-9]?/
|
|
152
|
+
const invalidNamingFiles: string[] = []
|
|
153
|
+
|
|
154
|
+
files.forEach(file => {
|
|
155
|
+
const lowerFile = file.toLowerCase()
|
|
156
|
+
if (lowerFile.includes('new') || lowerFile.includes('old')) {
|
|
157
|
+
if (!validVersionPattern.test(file)) {
|
|
158
|
+
invalidNamingFiles.push(file)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
expect(invalidNamingFiles).toEqual([])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should not contain forbidden naming patterns in any service file content', () => {
|
|
167
|
+
const servicesDir = path.join(__dirname, '../services')
|
|
168
|
+
const files = fs.readdirSync(servicesDir).filter(f => f.endsWith('.ts'))
|
|
169
|
+
|
|
170
|
+
// 使用字符拼接构造检查模式,避免 ESLint 误报
|
|
171
|
+
const pattern1 = '干' + 'new'
|
|
172
|
+
const pattern2 = '干' + 'old'
|
|
173
|
+
const forbiddenPatterns = [pattern1, pattern2]
|
|
174
|
+
const violations: { file: string; pattern: string }[] = []
|
|
175
|
+
|
|
176
|
+
files.forEach(file => {
|
|
177
|
+
const filePath = path.join(servicesDir, file)
|
|
178
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
179
|
+
|
|
180
|
+
forbiddenPatterns.forEach(pattern => {
|
|
181
|
+
if (content.includes(pattern)) {
|
|
182
|
+
violations.push({ file, pattern })
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
expect(violations).toEqual([])
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
})
|