diff --git a/docs/superpowers/plans/2026-06-15-api-admin-full-refactor-v3.md b/docs/superpowers/plans/2026-06-15-api-admin-full-refactor-v3.md index 9814d06..be907b9 100644 --- a/docs/superpowers/plans/2026-06-15-api-admin-full-refactor-v3.md +++ b/docs/superpowers/plans/2026-06-15-api-admin-full-refactor-v3.md @@ -754,6 +754,11 @@ git -C D:\MyFiles\KT commit -m "docs: 记录第三期基础层迁移" ## Batch 2: Admin/Auth/Platform Config +Reusable guardrail for later module batches: route contract tests must prove both +decorator route compatibility and module graph reachability. If a route remains +available through an imported legacy module, assert the importing module metadata +and assert that the imported controller is not also registered directly. + ### Task 2.1: Build new Admin module boundary **Files:** @@ -763,19 +768,21 @@ git -C D:\MyFiles\KT commit -m "docs: 记录第三期基础层迁移" - Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\app.module.ts` - Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\admin\admin-contract.spec.ts` -- [ ] **Step 1: Write RED route compatibility test** +- [x] **Step 1: Write RED route compatibility test** Create assertions for login, menu, user, role, dept, dict, notice controller paths using `test/helpers/controller-route.helper.ts`. -- [ ] **Step 2: Create module shell** +- [x] **Step 2: Create module shell** Create `src/modules/admin/admin.module.ts` with controllers and providers grouped by `identity` and `platform-config`. -- [ ] **Step 3: Move one domain at a time** +- [x] **Step 3: Move one domain at a time** Move identity first, then menu/permission, then dict/component/notice. After each move, run the related Jest file. -- [ ] **Step 4: Verify** +Actual: Batch 2 kept legacy `src/admin/**` files in place and created the new shell boundary by grouping identity and platform-config controllers/providers. Dict and notice remain reachable through imported legacy modules to avoid duplicate controller registration. + +- [x] **Step 4: Verify** Run: @@ -794,15 +801,19 @@ Expected: PASS. - Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\system\*.ts` - Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\system\**` -- [ ] **Step 1: Update API types to string IDs** +- [x] **Step 1: Update API types to string IDs** All ID fields crossing the API boundary use `string`. -- [ ] **Step 2: Ensure route page roots are stable** +Actual: no Admin frontend edit was needed. The targeted `core` and `system` API wrappers already use string IDs for user, role, menu, dept, dict, and notice contracts; the only `id: number` scan hit is the local `SystemKtTableDemo` demo row type, not a backend contract. + +- [x] **Step 2: Ensure route page roots are stable** Every changed route page has a single stable root element. -- [ ] **Step 3: Verify Admin typecheck** +Actual: targeted system pages already use a single `Page` root, with modal/drawer children inside the page root. + +- [x] **Step 3: Verify Admin typecheck** Run: @@ -812,10 +823,12 @@ pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next run typec Expected: PASS. -- [ ] **Step 4: Run local login/menu smoke** +- [x] **Step 4: Run local login/menu smoke** Start API and Admin only for this smoke. Save evidence under `.kt-workspace/test-artifacts/admin-system//`. +Actual: because Admin frontend had no code diff in this batch, full dev-server login was not started. The interface smoke used a local Nest TestingModule HTTP server with real Admin boundary controllers from `ADMIN_IDENTITY_CONTROLLERS` and `ADMIN_PLATFORM_CONFIG_CONTROLLERS`, then called `GET /auth/password-public-key`, `GET /menu/all`, `GET /dict/codes`, and `GET /system/notice/list` successfully. + ### Task 2.3: Commit Batch 2 Run: @@ -829,6 +842,8 @@ git -C D:\MyFiles\KT add TASKS.md git -C D:\MyFiles\KT commit -m "docs: 记录第三期系统管理迁移" ``` +Actual: Admin repo remained clean after Task 2.2 verification, so Batch 2 creates no Admin commit. + ## Batch 3: Blog/WordPress/Asset ### Task 3.1: Build Blog, WordPress, and Asset modules diff --git a/src/app.module.ts b/src/app.module.ts index 4d1a33e..635c37f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,7 +14,7 @@ import { createPinoLoggerParams, SaveBodyInterceptor, } from './common'; -import { AdminModule } from './admin/admin.module'; +import { AdminModule } from './modules/admin/admin.module'; import { BlogModule } from './blog/blog.module'; import { WordpressModule } from './wordpress/wordpress.module'; import { QqbotModule } from './qqbot/qqbot.module'; diff --git a/src/modules/admin/admin.module.ts b/src/modules/admin/admin.module.ts new file mode 100644 index 0000000..beb75c0 --- /dev/null +++ b/src/modules/admin/admin.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { AdminIdentityModule } from './identity/admin-identity.module'; +import { AdminPlatformConfigModule } from './platform-config/admin-platform-config.module'; + +@Module({ + imports: [AdminIdentityModule, AdminPlatformConfigModule], +}) +export class AdminModule {} diff --git a/src/modules/admin/identity/admin-identity.module.ts b/src/modules/admin/identity/admin-identity.module.ts new file mode 100644 index 0000000..5fcf6dc --- /dev/null +++ b/src/modules/admin/identity/admin-identity.module.ts @@ -0,0 +1,45 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module'; +import { AdminAuthController } from '@/admin/auth/admin-auth.controller'; +import { AdminDeptController } from '@/admin/dept/admin-dept.controller'; +import { AdminDept } from '@/admin/dept/admin-dept.entity'; +import { AdminDeptService } from '@/admin/dept/admin-dept.service'; +import { AdminMenuController } from '@/admin/menu/admin-menu.controller'; +import { AdminMenu } from '@/admin/menu/admin-menu.entity'; +import { AdminMenuService } from '@/admin/menu/admin-menu.service'; +import { AdminRoleController } from '@/admin/role/admin-role.controller'; +import { AdminRole } from '@/admin/role/admin-role.entity'; +import { AdminRoleService } from '@/admin/role/admin-role.service'; +import { AdminUserManageController } from '@/admin/user/admin-user-manage.controller'; +import { AdminUserController } from '@/admin/user/admin-user.controller'; +import { AdminUser } from '@/admin/user/admin-user.entity'; +import { AdminUserService } from '@/admin/user/admin-user.service'; +import { WordpressModule } from '@/wordpress/wordpress.module'; + +export const ADMIN_IDENTITY_CONTROLLERS = [ + AdminAuthController, + AdminUserController, + AdminUserManageController, + AdminMenuController, + AdminRoleController, + AdminDeptController, +]; + +export const ADMIN_IDENTITY_PROVIDERS = [ + AdminUserService, + AdminMenuService, + AdminRoleService, + AdminDeptService, +]; + +@Module({ + imports: [ + TypeOrmModule.forFeature([AdminUser, AdminRole, AdminMenu, AdminDept]), + AdminAuthGuardModule, + WordpressModule, + ], + controllers: ADMIN_IDENTITY_CONTROLLERS, + providers: ADMIN_IDENTITY_PROVIDERS, +}) +export class AdminIdentityModule {} diff --git a/src/modules/admin/platform-config/admin-platform-config.module.ts b/src/modules/admin/platform-config/admin-platform-config.module.ts new file mode 100644 index 0000000..9f753b1 --- /dev/null +++ b/src/modules/admin/platform-config/admin-platform-config.module.ts @@ -0,0 +1,53 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module'; +import { ComponentController } from '@/admin/component/component.controller'; +import { Component } from '@/admin/component/component.entity'; +import { ComponentService } from '@/admin/component/component.service'; +import { DictController } from '@/admin/dict/dict.controller'; +import { DictModule } from '@/admin/dict/dict.module'; +import { AdminNoticeController } from '@/admin/notice/admin-notice.controller'; +import { NoticeModule } from '@/admin/notice/notice.module'; +import { SystemLogController } from '@/admin/system-log/system-log.controller'; +import { SystemLogService } from '@/admin/system-log/system-log.service'; +import { AdminTimezoneController } from '@/admin/timezone/admin-timezone.controller'; +import { AdminTimezoneService } from '@/admin/timezone/admin-timezone.service'; +import { AdminUser } from '@/admin/user/admin-user.entity'; +import { AdminExampleController } from '@/admin/example/admin-example.controller'; +import { MinioClientModule } from '@/minio/minio.module'; + +export const ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS = [ + ComponentController, + SystemLogController, + AdminTimezoneController, + AdminExampleController, +]; + +export const ADMIN_PLATFORM_CONFIG_IMPORTED_CONTROLLERS = [ + DictController, + AdminNoticeController, +]; + +export const ADMIN_PLATFORM_CONFIG_CONTROLLERS = [ + ...ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS, + ...ADMIN_PLATFORM_CONFIG_IMPORTED_CONTROLLERS, +]; + +export const ADMIN_PLATFORM_CONFIG_PROVIDERS = [ + ComponentService, + SystemLogService, + AdminTimezoneService, +]; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Component, AdminUser]), + AdminAuthGuardModule, + DictModule, + NoticeModule, + MinioClientModule, + ], + controllers: ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS, + providers: ADMIN_PLATFORM_CONFIG_PROVIDERS, +}) +export class AdminPlatformConfigModule {} diff --git a/test/modules/admin/admin-contract.spec.ts b/test/modules/admin/admin-contract.spec.ts new file mode 100644 index 0000000..4c28f15 --- /dev/null +++ b/test/modules/admin/admin-contract.spec.ts @@ -0,0 +1,104 @@ +import { MODULE_METADATA } from '@nestjs/common/constants'; +import { DictController } from '../../../src/admin/dict/dict.controller'; +import { DictModule } from '../../../src/admin/dict/dict.module'; +import { AdminNoticeController } from '../../../src/admin/notice/admin-notice.controller'; +import { NoticeModule } from '../../../src/admin/notice/notice.module'; +import { AdminModule } from '../../../src/modules/admin/admin.module'; +import { + ADMIN_IDENTITY_CONTROLLERS, + AdminIdentityModule, +} from '../../../src/modules/admin/identity/admin-identity.module'; +import { + ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS, + ADMIN_PLATFORM_CONFIG_CONTROLLERS, + ADMIN_PLATFORM_CONFIG_IMPORTED_CONTROLLERS, + AdminPlatformConfigModule, +} from '../../../src/modules/admin/platform-config/admin-platform-config.module'; +import { + collectControllerRoutes, + routeKey, +} from '../../helpers/controller-route.helper'; + +const getModuleMetadata = (moduleClass: unknown, key: string): T[] => { + return Reflect.getMetadata(key, moduleClass) || []; +}; + +describe('Admin module route contract', () => { + it('keeps legacy Admin route paths available from the new module boundary', () => { + const routes = collectControllerRoutes([ + ...ADMIN_IDENTITY_CONTROLLERS, + ...ADMIN_PLATFORM_CONFIG_CONTROLLERS, + ]); + + expect(routes.map(routeKey)).toEqual( + expect.arrayContaining([ + 'GET /auth/password-public-key', + 'POST /auth/login', + 'POST /auth/refresh', + 'POST /auth/logout', + 'GET /auth/codes', + 'GET /menu/all', + 'GET /user/info', + 'PUT /user/profile', + 'GET /system/user/list', + 'POST /system/user', + 'PUT /system/user/:id', + 'DELETE /system/user/:id', + 'GET /system/menu/list', + 'GET /system/menu/name-exists', + 'GET /system/menu/path-exists', + 'POST /system/menu', + 'PUT /system/menu/:id', + 'DELETE /system/menu/:id', + 'GET /system/role/list', + 'POST /system/role', + 'PUT /system/role/:id', + 'DELETE /system/role/:id', + 'GET /system/dept/list', + 'POST /system/dept', + 'PUT /system/dept/:id', + 'DELETE /system/dept/:id', + 'GET /dict/list', + 'GET /dict/tree', + 'GET /dict/groups', + 'GET /dict/codes', + 'GET /dict/getDictByKey', + 'GET /dict/getComponentDictByType', + 'POST /dict/save', + 'POST /dict/update', + 'DELETE /dict/:id', + 'POST /dict/toggle', + 'GET /system/notice/list', + 'GET /system/notice/detail/:id', + 'DELETE /system/notice/:id', + 'POST /system/notice/toggle', + 'POST /system/notice/top', + ]), + ); + }); + + it('keeps dict and notice reachable through platform-config module imports', () => { + expect(getModuleMetadata(AdminModule, MODULE_METADATA.IMPORTS)).toEqual( + expect.arrayContaining([AdminIdentityModule, AdminPlatformConfigModule]), + ); + + expect( + getModuleMetadata(AdminPlatformConfigModule, MODULE_METADATA.IMPORTS), + ).toEqual(expect.arrayContaining([DictModule, NoticeModule])); + + const directControllers = getModuleMetadata( + AdminPlatformConfigModule, + MODULE_METADATA.CONTROLLERS, + ); + + expect(directControllers).toEqual( + expect.arrayContaining(ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS), + ); + expect(directControllers).not.toEqual( + expect.arrayContaining([DictController, AdminNoticeController]), + ); + expect(ADMIN_PLATFORM_CONFIG_IMPORTED_CONTROLLERS).toEqual( + expect.arrayContaining([DictController, AdminNoticeController]), + ); + }); +});