refactor: 迁移QQBot核心模块
This commit is contained in:
parent
679aabb40d
commit
7c32cda506
@ -11,7 +11,7 @@
|
||||
## Domains
|
||||
|
||||
| Domain | Tables | Owner Batch | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Admin Identity | `admin_user`, `admin_role`, `admin_permission`, `admin_menu`, `admin_department`, `admin_user_role`, `admin_role_permission`, `admin_role_menu` | Batch 2 | Login, menu, role, permission, department. |
|
||||
| Platform Config | `platform_dict_group`, `platform_dict_item`, `platform_component_template`, `platform_setting` | Batch 2 | Dict and component template split from legacy Admin misc. |
|
||||
| Blog Content | `blog_post`, `blog_taxonomy`, `blog_term`, `blog_post_term`, `blog_theme_profile`, `blog_import_job` | Batch 3 | Categories and tags use relation table. |
|
||||
@ -19,7 +19,7 @@
|
||||
| Asset | `asset_bucket`, `asset_object`, `asset_reference`, `asset_access_grant` | Batch 3 | MinIO object ownership and access grant. |
|
||||
| System Event | `system_notice`, `system_event`, `system_event_dedupe`, `system_event_delivery` | Batch 2 | MySQL stores actionable events; Loki remains log query source. |
|
||||
| Runtime Evidence | `runtime_evidence_index` | Batch 1 | Safe index only, no large logs or secrets. |
|
||||
| QQBot Core | `qqbot_account`, `qqbot_connection_session`, `qqbot_capability_binding`, `qqbot_permission_policy`, `qqbot_command`, `qqbot_command_alias`, `qqbot_rule`, `qqbot_conversation`, `qqbot_message`, `qqbot_send_task`, `qqbot_send_log`, `qqbot_dedupe_event` | Batch 4 | Account, connection, permission, command, message, send queue. |
|
||||
| QQBot Core | `qqbot_account`, `qqbot_account_ability`, `qqbot_connection_session`, `qqbot_capability_binding`, `qqbot_permission_policy`, `qqbot_allowlist`, `qqbot_blocklist`, `qqbot_command`, `qqbot_command_alias`, `qqbot_command_log`, `qqbot_config`, `qqbot_rule`, `qqbot_conversation`, `qqbot_message`, `qqbot_send_task`, `qqbot_send_log`, `qqbot_dedupe_event`, `qqbot_dedupe`, `qqbot_account_napcat`, `qqbot_napcat_container` | Batch 4 | Account, connection, permission, command, message, send queue, and transitional registered-entity compatibility tables until Batches 5-7 replace plugin/NapCat internals. |
|
||||
| NapCat Runtime | `napcat_container`, `napcat_device_identity`, `napcat_account_binding`, `napcat_login_session`, `napcat_login_challenge`, `napcat_runtime_cleanup` | Batch 7 | Device identity and login challenge state. |
|
||||
| QQBot Plugin Platform | `qqbot_plugin`, `qqbot_plugin_version`, `qqbot_plugin_installation`, `qqbot_plugin_operation`, `qqbot_plugin_event_handler`, `qqbot_plugin_account_binding`, `qqbot_plugin_config`, `qqbot_plugin_asset`, `qqbot_plugin_runtime_event` | Batch 5 | Manifest, install lifecycle, runtime health, bindings. |
|
||||
| Plugin-Owned Data | plugin namespace tables | Batch 6 | Table names start with registered plugin namespace. |
|
||||
|
||||
@ -44,7 +44,7 @@ Every batch must leave the product in a locally verifiable state. Batch 8 is the
|
||||
### API Files To Create
|
||||
|
||||
| Path | Responsibility |
|
||||
| --- | --- |
|
||||
| ----------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| `docs/refactor-v3/schema-map.md` | Batch 0 full schema ownership map. |
|
||||
| `docs/refactor-v3/api-admin-contract-matrix.md` | API route, DTO, SSE, and Admin caller/page matrix. |
|
||||
| `docs/refactor-v3/breaking-changes.md` | Approved route, DTO, schema, seed, and Admin contract breaks. |
|
||||
@ -70,7 +70,7 @@ Every batch must leave the product in a locally verifiable state. Batch 8 is the
|
||||
### API Files To Modify
|
||||
|
||||
| Path | Responsibility |
|
||||
| --- | --- |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `src/app.module.ts` | Replace legacy module imports with new `src/modules/**` modules when each batch is ready. |
|
||||
| `src/runtime/**` | Keep `/health/runtime` stable, add runtime clients/adapters needed by modules. |
|
||||
| `src/common/**` | Keep only shared response, error, time, Snowflake, logger, decorator helpers. |
|
||||
@ -85,7 +85,7 @@ Every batch must leave the product in a locally verifiable state. Batch 8 is the
|
||||
### Admin Files To Create Or Modify
|
||||
|
||||
| Path | Responsibility |
|
||||
| --- | --- |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `apps/web-antdv-next/src/api/system/*.ts` | Sync Admin/Auth/Platform Config API contracts. |
|
||||
| `apps/web-antdv-next/src/api/blog/*.ts` | Sync Blog/WordPress/Asset API contracts. |
|
||||
| `apps/web-antdv-next/src/api/qqbot/index.ts` | Replace old QQBot API types and callers. |
|
||||
@ -104,6 +104,7 @@ Every batch must leave the product in a locally verifiable state. Batch 8 is the
|
||||
### Task 0.1: Prepare API branch
|
||||
|
||||
**Files:**
|
||||
|
||||
- No file edits in this task.
|
||||
|
||||
- [x] **Step 1: Verify API worktree is clean**
|
||||
@ -153,6 +154,7 @@ Actual note: `dev/api-full-refactor-v3` could not be created because the API rep
|
||||
### Task 0.2: Clean authorized Admin old artifacts and prepare branch
|
||||
|
||||
**Files:**
|
||||
|
||||
- Restore only:
|
||||
- `D:\MyFiles\KT\Vue\kt-template-admin\README.md`
|
||||
- `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\index.ts`
|
||||
@ -219,6 +221,7 @@ Switched to a new branch 'dev-admin-full-refactor-v3'
|
||||
### Task 0.3: Create migration control documents
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\docs\refactor-v3\schema-map.md`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\docs\refactor-v3\api-admin-contract-matrix.md`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\docs\refactor-v3\breaking-changes.md`
|
||||
@ -242,7 +245,7 @@ Use this content:
|
||||
## Domains
|
||||
|
||||
| Domain | Tables | Owner Batch | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------------- |
|
||||
| Admin Identity | `admin_user`, `admin_role`, `admin_permission`, `admin_menu`, `admin_department`, `admin_user_role`, `admin_role_permission`, `admin_role_menu` | Batch 2 | Login, menu, role, permission, department. |
|
||||
| Platform Config | `platform_dict_group`, `platform_dict_item`, `platform_component_template`, `platform_setting` | Batch 2 | Dict and component template split from legacy Admin misc. |
|
||||
| Blog Content | `blog_post`, `blog_taxonomy`, `blog_term`, `blog_post_term`, `blog_theme_profile`, `blog_import_job` | Batch 3 | Categories and tags use relation table. |
|
||||
@ -264,7 +267,7 @@ Use this content:
|
||||
# API/Admin Contract Matrix
|
||||
|
||||
| Batch | API Contract | Admin Surface | Smoke Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| ----- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------- |
|
||||
| 1 | `GET /health/runtime` plain JSON, runtime adapter internals | Runtime status remains available | `curl http://localhost:<port>/health/runtime` |
|
||||
| 2 | `/auth/*`, `/admin/user/*`, `/admin/menu/*`, `/admin/role/*`, `/admin/dept/*`, `/admin/dict/*`, `/admin/notice/*` | Login, menu, system pages | Login request, menu load, route render |
|
||||
| 3 | `/blog/*`, `/wordpress/*`, `/asset/*` | Blog, WordPress, Asset pages | Public blog request, Admin list request, asset upload smoke |
|
||||
@ -285,7 +288,7 @@ Use this content:
|
||||
## Approved
|
||||
|
||||
| Area | Change | Reason | First Batch |
|
||||
| --- | --- | --- | --- |
|
||||
| --------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------- | ----------- |
|
||||
| Database | Rebuild all API-owned tables from new full schema | Current data is not important and old schema blocks clean module ownership | Batch 0 |
|
||||
| SQL init | Replace historical patch scripts with `sql/refactor-v3/*` | Avoid accumulated `ALTER TABLE` history | Batch 0 |
|
||||
| Admin QQBot API types | Replace old QQBot caller types with new contract | New QQBot Core, Plugin Platform, and NapCat state model | Batch 4 |
|
||||
@ -363,6 +366,7 @@ True
|
||||
### Task 0.4: Add schema skeleton and RED schema test
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\00-full-schema.sql`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\01-seed-core.sql`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\99-verify.sql`
|
||||
@ -464,6 +468,7 @@ Expected: PASS.
|
||||
### Task 0.5: Add DB dry-run wrappers
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-dry-run.ps1`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-backup-online.ps1`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-restore-online.ps1`
|
||||
@ -590,6 +595,7 @@ ok
|
||||
### Task 0.6: Batch 0 verification and commits
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `D:\MyFiles\KT\TASKS.md`
|
||||
- API commit includes only Batch 0 API files.
|
||||
- Root commit includes only `TASKS.md`.
|
||||
@ -648,6 +654,7 @@ git -C D:\MyFiles\KT commit -m "docs: 记录第三期迁移准备"
|
||||
### Task 1.1: Add runtime adapter contracts
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\runtime\client\runtime-http-client.types.ts`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\runtime\client\runtime-process-client.types.ts`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\runtime\client\runtime-docker-client.types.ts`
|
||||
@ -695,6 +702,7 @@ Expected: PASS.
|
||||
### Task 1.2: Keep common narrow and stable
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\common\index.ts`
|
||||
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\common\common.module.ts`
|
||||
- Test: existing `test/common/*.spec.ts`
|
||||
@ -727,6 +735,7 @@ Expected: PASS.
|
||||
### Task 1.3: Commit Batch 1
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `D:\MyFiles\KT\TASKS.md`
|
||||
- API and root commits.
|
||||
|
||||
@ -762,6 +771,7 @@ and assert that the imported controller is not also registered directly.
|
||||
### Task 2.1: Build new Admin module boundary
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\admin.module.ts`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\identity\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\admin\platform-config\**`
|
||||
@ -796,6 +806,7 @@ Expected: PASS.
|
||||
### Task 2.2: Sync Admin frontend system callers and pages
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\core\auth.ts`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\core\menu.ts`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\system\*.ts`
|
||||
@ -849,6 +860,7 @@ Actual: Admin repo remained clean after Task 2.2 verification, so Batch 2 create
|
||||
### Task 3.1: Build Blog, WordPress, and Asset modules
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\blog\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\wordpress\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\asset\**`
|
||||
@ -889,6 +901,7 @@ Actual note: Batch 3 moved API ownership to transitional `BlogContentModule`, `W
|
||||
### Task 3.2: Sync Admin Blog/WordPress/Asset pages
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\blog\index.ts`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\blog\wordpress.ts`
|
||||
- Create: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\blog\asset.ts`
|
||||
@ -931,27 +944,28 @@ git -C D:\MyFiles\KT\Vue\kt-template-admin commit -m "refactor: 同步博客与
|
||||
### Task 4.1: Build QQBot core module
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\core\**`
|
||||
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\app.module.ts`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\core\**`
|
||||
|
||||
- [ ] **Step 1: Write RED status-separation tests**
|
||||
- [x] **Step 1: Write RED status-separation tests**
|
||||
|
||||
Cover OneBot connection, container status, WebUI status, and QQ login status as separate fields.
|
||||
|
||||
- [ ] **Step 2: Write RED command tests**
|
||||
- [x] **Step 2: Write RED command tests**
|
||||
|
||||
Cover operation key lookup, command ID usage, aliases, cooldown, permission policy, and parser validation.
|
||||
|
||||
- [ ] **Step 3: Write RED send queue tests**
|
||||
- [x] **Step 3: Write RED send queue tests**
|
||||
|
||||
Cover queue reservation, rate limit, send log, and dedupe event behavior.
|
||||
|
||||
- [ ] **Step 4: Implement core**
|
||||
- [x] **Step 4: Implement core**
|
||||
|
||||
Move account, connection, command, rule, message, permission, send, dedupe, and dashboard logic into `src/modules/qqbot/core`.
|
||||
|
||||
- [ ] **Step 5: Verify**
|
||||
- [x] **Step 5: Verify**
|
||||
|
||||
Run:
|
||||
|
||||
@ -962,9 +976,12 @@ pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Actual note: `QqbotCoreModule` now owns the QQBot Nest boundary directly with Config/AdminAuthGuard/Dict/TypeORM imports, full controller/provider/entity/export lists, and `src/qqbot/qqbot.module.ts` kept only as a compatibility shim. The RED contract tests failed first on missing `QQBOT_CORE_ENTITIES` / `QQBOT_CORE_EXPORTS` and missing `qqbot-core.contract`; after implementation, Turing review found that registered legacy TypeORM entities were not fully represented in `sql/refactor-v3/00-full-schema.sql`. The follow-up RED/GREEN guard now checks every registered QQBot Core entity table/column against the real v3 SQL, checks nullable entity columns, and rejects SQL-only required insert columns. The schema map and full SQL include transitional compatibility tables/columns for currently registered legacy QQBot, plugin-adjacent, and NapCat-adjacent entities; Batches 5-7 will replace those internals rather than treating this as the final plugin/NapCat design.
|
||||
|
||||
### Task 4.2: Sync Admin QQBot core pages
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\index.ts`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\account\**`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\command\list.tsx`
|
||||
@ -972,15 +989,15 @@ Expected: PASS.
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\message\list.tsx`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\sendLog\list.tsx`
|
||||
|
||||
- [ ] **Step 1: Split status fields in the API types**
|
||||
- [x] **Step 1: Split status fields in the API types**
|
||||
|
||||
Define `oneBotStatus`, `containerStatus`, `webuiStatus`, and `qqLoginStatus` separately.
|
||||
|
||||
- [ ] **Step 2: Keep account page single-root**
|
||||
- [x] **Step 2: Keep account page single-root**
|
||||
|
||||
If modal/drawer components are siblings, wrap the route in one root container.
|
||||
|
||||
- [ ] **Step 3: Verify**
|
||||
- [x] **Step 3: Verify**
|
||||
|
||||
Run:
|
||||
|
||||
@ -990,6 +1007,8 @@ pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next run typec
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Actual note: QQBot account API types now expose `oneBotStatus`, `containerStatus`, `webuiStatus`, and `qqLoginStatus` as top-level split fields while preserving nested `napcat` fallbacks. The account page keeps its existing single route root and derives display/actions from the split status helpers, including disabling disconnect by OneBot status instead of the old collapsed connection status. Command/rule/message/sendLog pages did not need visible changes in this slice because their contracts did not consume the status fields. Admin typecheck passed.
|
||||
|
||||
### Task 4.3: Run local QQBot command smoke
|
||||
|
||||
Run API locally, query enabled command by `operationKey`, pass `commandId`, and include full command text.
|
||||
@ -1002,6 +1021,8 @@ Expected evidence:
|
||||
- response status
|
||||
- sanitized response summary without `replyText`
|
||||
|
||||
Actual evidence: local Nest TestingModule HTTP smoke calls `GET /qqbot/command/list?operationKey=bangdream.song.search&enabled=true&pageNo=1&pageSize=10`, obtains command ID `cmd-bangdream-song-search`, then calls `POST /qqbot/command/test` with `commandId` and full text `/查歌 FIRE BIRD`. Response status is 200 and the asserted summary strips `replyText` while proving real `QqbotCommandService.page`, `QqbotCommandEngineService.preview`, parser, reply template, and plugin execution boundaries were exercised with mocked repository/plugin edges. Main-thread verification passed `pnpm exec jest --runInBand --runTestsByPath test/refactor-v3/schema-map.spec.ts ... test/modules/qqbot/core/qqbot-core-command-smoke.spec.ts` with 13 suites / 28 tests, API `pnpm run typecheck`, and Admin `pnpm -F @vben/web-antdv-next run typecheck`.
|
||||
|
||||
### Task 4.4: Commit Batch 4
|
||||
|
||||
Run:
|
||||
@ -1018,6 +1039,7 @@ git -C D:\MyFiles\KT\Vue\kt-template-admin commit -m "refactor: 同步QQBot核
|
||||
### Task 5.1: Build plugin platform schema and manifest contracts
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\manifest\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\persistence\**`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugin-platform\manifest.spec.ts`
|
||||
@ -1043,6 +1065,7 @@ Expected: PASS.
|
||||
### Task 5.2: Build plugin CLI
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\qqbot-plugin\cli.ts`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\qqbot-plugin\templates\basic\plugin.json`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\qqbot-plugin\templates\basic\src\index.ts`
|
||||
@ -1089,6 +1112,7 @@ Expected: commands exit `0`, package includes content hash.
|
||||
### Task 5.3: Build worker runtime and lifecycle
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\runtime\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\sdk\**`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugin-platform\worker-runtime.spec.ts`
|
||||
@ -1119,6 +1143,7 @@ Expected: PASS.
|
||||
### Task 5.4: Sync Admin plugin platform page
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\plugin.ts`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\plugin\list.tsx`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\router\routes\modules\qqbot.ts`
|
||||
@ -1157,6 +1182,7 @@ git -C D:\MyFiles\KT\Vue\kt-template-admin commit -m "feat: 增加QQBot插件管
|
||||
### Task 6.1: Rewrite BangDream as platform plugin
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangDream\plugin.json`
|
||||
- Create/modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangDream\**`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\bangDream\**`
|
||||
@ -1183,6 +1209,7 @@ Expected: PASS. Event stage smoke keeps `imageCount=5`.
|
||||
### Task 6.2: Rewrite FF14 Market, FFLogs, and Repeater
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\ff14Market\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\fflogs\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\repeater\**`
|
||||
@ -1227,6 +1254,7 @@ git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "refactor: 重写现
|
||||
### Task 7.1: Build NapCat device persistence model
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\device\**`
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\container\**`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\napcat\device-identity.spec.ts`
|
||||
@ -1252,6 +1280,7 @@ Expected: PASS.
|
||||
### Task 7.2: Build NapCat login state machine
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\login\**`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\napcat\login-state-machine.spec.ts`
|
||||
|
||||
@ -1282,6 +1311,7 @@ Expected: PASS.
|
||||
### Task 7.3: Implement new-device NapCat API flow
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\integration\napcat-login-api.client.ts`
|
||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\napcat\new-device-flow.spec.ts`
|
||||
|
||||
@ -1322,6 +1352,7 @@ Expected: PASS.
|
||||
### Task 7.4: Sync Admin NapCat login page and SSE Chinese progress
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\napcat.ts`
|
||||
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\account\**`
|
||||
- Create: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\napcat\**`
|
||||
@ -1504,7 +1535,7 @@ If review finds a real Important issue, fix it and re-review before commit.
|
||||
### Spec Coverage
|
||||
|
||||
| Spec Requirement | Plan Coverage |
|
||||
| --- | --- |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| API/Admin dual-repo refactor | Tasks 0.1, 0.2, 2.2, 3.2, 4.2, 5.4, 7.4, 8.1-8.7 |
|
||||
| Clean Admin authorized old artifacts during implementation preparation | Task 0.2 |
|
||||
| Full database redesign and destructive rebuild with rollback | Tasks 0.3-0.5, 8.5 |
|
||||
|
||||
@ -368,17 +368,41 @@ CREATE TABLE IF NOT EXISTS runtime_evidence_index (
|
||||
CREATE TABLE IF NOT EXISTS qqbot_account (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
connection_mode VARCHAR(32) NOT NULL DEFAULT 'reverse-ws',
|
||||
name VARCHAR(120) NOT NULL DEFAULT '',
|
||||
display_name VARCHAR(128) NULL,
|
||||
onebot_status VARCHAR(32) NOT NULL,
|
||||
container_status VARCHAR(32) NOT NULL,
|
||||
webui_status VARCHAR(32) NOT NULL,
|
||||
qq_login_status VARCHAR(32) NOT NULL,
|
||||
access_token VARCHAR(255) NULL,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
connect_status VARCHAR(32) NOT NULL DEFAULT 'offline',
|
||||
onebot_status VARCHAR(32) NOT NULL DEFAULT 'offline',
|
||||
container_status VARCHAR(32) NOT NULL DEFAULT 'unknown',
|
||||
webui_status VARCHAR(32) NOT NULL DEFAULT 'unknown',
|
||||
qq_login_status VARCHAR(32) NOT NULL DEFAULT 'unknown',
|
||||
client_role VARCHAR(32) NULL,
|
||||
last_connected_at DATETIME NULL,
|
||||
last_heartbeat_at DATETIME NULL,
|
||||
last_error TEXT NULL,
|
||||
napcat_login_password_secret VARCHAR(1024) NULL,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_account_self_id (self_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_account_ability (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
ability_type VARCHAR(32) NOT NULL,
|
||||
ability_key VARCHAR(128) NOT NULL,
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_account_ability (account_id, ability_type, ability_key),
|
||||
KEY idx_qqbot_account_ability_self (self_id, ability_type, is_deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_connection_session (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
@ -412,13 +436,55 @@ CREATE TABLE IF NOT EXISTS qqbot_permission_policy (
|
||||
UNIQUE KEY uk_qqbot_permission_policy (policy_key, scope_type, scope_value)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_allowlist (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
self_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
target_type VARCHAR(32) NOT NULL DEFAULT 'qq',
|
||||
target_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
user_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
precise_user TINYINT NOT NULL DEFAULT 0,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_blocklist (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
self_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
target_type VARCHAR(32) NOT NULL DEFAULT 'qq',
|
||||
target_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
user_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
precise_user TINYINT NOT NULL DEFAULT 0,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_command (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
operation_key VARCHAR(128) NOT NULL,
|
||||
command_key VARCHAR(128) NOT NULL,
|
||||
command_key VARCHAR(128) NULL,
|
||||
code VARCHAR(80) NOT NULL,
|
||||
name VARCHAR(120) NOT NULL DEFAULT '',
|
||||
aliases TEXT NOT NULL,
|
||||
prefixes VARCHAR(120) NOT NULL DEFAULT '/,!,!',
|
||||
plugin_key VARCHAR(128) NULL,
|
||||
parser_key VARCHAR(40) NOT NULL DEFAULT 'plain',
|
||||
target_type VARCHAR(32) NOT NULL DEFAULT 'all',
|
||||
default_params TEXT NULL,
|
||||
reply_template TEXT NULL,
|
||||
error_template TEXT NULL,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
priority INT NOT NULL DEFAULT 0,
|
||||
cooldown_ms INT NOT NULL DEFAULT 1500,
|
||||
cooldown_seconds INT NOT NULL DEFAULT 0,
|
||||
last_hit_at DATETIME NULL,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_command_key (command_key),
|
||||
@ -433,14 +499,53 @@ CREATE TABLE IF NOT EXISTS qqbot_command_alias (
|
||||
UNIQUE KEY uk_qqbot_command_alias (command_id, alias_text)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_command_log (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
command_id VARCHAR(64) NOT NULL,
|
||||
command_code VARCHAR(80) NOT NULL DEFAULT '',
|
||||
plugin_key VARCHAR(80) NOT NULL,
|
||||
operation_key VARCHAR(120) NOT NULL,
|
||||
self_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
target_type VARCHAR(32) NOT NULL DEFAULT 'private',
|
||||
target_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
user_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
raw_message TEXT NOT NULL,
|
||||
input TEXT NULL,
|
||||
output TEXT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'success',
|
||||
error_message TEXT NULL,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_config (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
config_key VARCHAR(120) NOT NULL,
|
||||
config_value TEXT NOT NULL,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_config_key (config_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_rule (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
rule_key VARCHAR(128) NOT NULL,
|
||||
rule_key VARCHAR(128) NULL,
|
||||
name VARCHAR(120) NOT NULL DEFAULT '',
|
||||
match_type VARCHAR(32) NOT NULL DEFAULT 'keyword',
|
||||
keyword VARCHAR(500) NOT NULL,
|
||||
target_type VARCHAR(32) NOT NULL DEFAULT 'all',
|
||||
reply_content TEXT NOT NULL,
|
||||
account_id BIGINT NULL,
|
||||
command_id BIGINT NULL,
|
||||
matcher_json JSON NOT NULL,
|
||||
action_json JSON NOT NULL,
|
||||
matcher_json JSON NULL,
|
||||
action_json JSON NULL,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
priority INT NOT NULL DEFAULT 0,
|
||||
cooldown_ms INT NOT NULL DEFAULT 1500,
|
||||
last_hit_at DATETIME NULL,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_rule_key (rule_key)
|
||||
@ -448,10 +553,19 @@ CREATE TABLE IF NOT EXISTS qqbot_rule (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_conversation (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
conversation_type VARCHAR(64) NOT NULL,
|
||||
conversation_key VARCHAR(128) NOT NULL,
|
||||
account_id BIGINT NULL,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
conversation_type VARCHAR(64) NULL,
|
||||
conversation_key VARCHAR(128) NULL,
|
||||
target_type VARCHAR(32) NOT NULL,
|
||||
target_id VARCHAR(64) NOT NULL,
|
||||
display_name VARCHAR(255) NULL,
|
||||
target_name VARCHAR(120) NOT NULL DEFAULT '',
|
||||
last_message_id VARCHAR(64) NULL,
|
||||
last_message_text TEXT NOT NULL,
|
||||
last_message_time DATETIME NULL,
|
||||
message_count INT NOT NULL DEFAULT 0,
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_conversation (account_id, conversation_type, conversation_key)
|
||||
@ -459,21 +573,31 @@ CREATE TABLE IF NOT EXISTS qqbot_conversation (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
account_id BIGINT NULL,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
conversation_id BIGINT NULL,
|
||||
message_id VARCHAR(128) NOT NULL,
|
||||
message_id VARCHAR(128) NULL,
|
||||
direction VARCHAR(32) NOT NULL,
|
||||
message_type VARCHAR(64) NOT NULL,
|
||||
target_id VARCHAR(64) NOT NULL,
|
||||
group_id VARCHAR(64) NULL,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
sender_nickname VARCHAR(120) NOT NULL DEFAULT '',
|
||||
raw_message TEXT NOT NULL,
|
||||
message_text TEXT NOT NULL,
|
||||
summary TEXT NULL,
|
||||
raw_payload JSON NULL,
|
||||
raw_event JSON NULL,
|
||||
event_time DATETIME NOT NULL,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_message (account_id, message_id),
|
||||
KEY idx_qqbot_message_conversation (conversation_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_send_task (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
account_id BIGINT NULL,
|
||||
conversation_id BIGINT NULL,
|
||||
task_key VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
@ -490,11 +614,21 @@ CREATE TABLE IF NOT EXISTS qqbot_send_task (
|
||||
CREATE TABLE IF NOT EXISTS qqbot_send_log (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
task_id BIGINT NULL,
|
||||
account_id BIGINT NOT NULL,
|
||||
account_id BIGINT NULL,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
target_type VARCHAR(32) NOT NULL,
|
||||
target_id VARCHAR(64) NOT NULL,
|
||||
action VARCHAR(64) NOT NULL,
|
||||
message_text TEXT NOT NULL,
|
||||
params JSON NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
echo VARCHAR(80) NULL,
|
||||
message_id VARCHAR(64) NULL,
|
||||
safe_summary JSON NULL,
|
||||
error_message TEXT NULL,
|
||||
response JSON NULL,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_qqbot_send_log_task (task_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@ -507,6 +641,15 @@ CREATE TABLE IF NOT EXISTS qqbot_dedupe_event (
|
||||
UNIQUE KEY uk_qqbot_dedupe_event_key (dedupe_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_dedupe (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
event_key VARCHAR(255) NOT NULL,
|
||||
expire_at DATETIME NULL,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_dedupe_event_key (event_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_plugin (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
plugin_key VARCHAR(128) NOT NULL,
|
||||
@ -612,6 +755,42 @@ CREATE TABLE IF NOT EXISTS napcat_container (
|
||||
UNIQUE KEY uk_napcat_container_name (container_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_account_napcat (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
container_id BIGINT NOT NULL,
|
||||
bind_status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||
is_primary TINYINT NOT NULL DEFAULT 1,
|
||||
last_login_at DATETIME NULL,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_qqbot_account_napcat_account (account_id, is_deleted),
|
||||
KEY idx_qqbot_account_napcat_container (container_id, is_deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_napcat_container (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
base_url VARCHAR(255) NOT NULL,
|
||||
webui_port INT NULL,
|
||||
webui_token VARCHAR(255) NULL,
|
||||
image VARCHAR(255) NOT NULL DEFAULT '',
|
||||
data_dir VARCHAR(500) NOT NULL DEFAULT '',
|
||||
reverse_ws_url VARCHAR(500) NOT NULL DEFAULT '',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'creating',
|
||||
last_started_at DATETIME NULL,
|
||||
last_checked_at DATETIME NULL,
|
||||
last_error VARCHAR(500) NULL,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
is_deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_qqbot_napcat_container_name (name),
|
||||
KEY idx_qqbot_napcat_container_status (status, is_deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS napcat_device_identity (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
|
||||
@ -16,8 +16,8 @@ import {
|
||||
import { AdminModule } from './modules/admin/admin.module';
|
||||
import { AssetModule } from './modules/asset/asset.module';
|
||||
import { BlogContentModule } from './modules/blog/blog-content.module';
|
||||
import { QqbotCoreModule } from './modules/qqbot/core/qqbot-core.module';
|
||||
import { WordpressMirrorModule } from './modules/wordpress/wordpress-mirror.module';
|
||||
import { QqbotModule } from './qqbot/qqbot.module';
|
||||
import { RuntimeModule } from './runtime';
|
||||
|
||||
@Module({
|
||||
@ -69,7 +69,7 @@ import { RuntimeModule } from './runtime';
|
||||
BlogContentModule,
|
||||
WordpressMirrorModule,
|
||||
AssetModule,
|
||||
QqbotModule,
|
||||
QqbotCoreModule,
|
||||
],
|
||||
providers: [
|
||||
AppService,
|
||||
|
||||
74
src/modules/qqbot/core/qqbot-core.contract.ts
Normal file
74
src/modules/qqbot/core/qqbot-core.contract.ts
Normal file
@ -0,0 +1,74 @@
|
||||
export const QQBOT_CORE_DOMAIN_CONTRACT = {
|
||||
tables: [
|
||||
'qqbot_account',
|
||||
'qqbot_connection_session',
|
||||
'qqbot_capability_binding',
|
||||
'qqbot_permission_policy',
|
||||
'qqbot_command',
|
||||
'qqbot_command_alias',
|
||||
'qqbot_rule',
|
||||
'qqbot_conversation',
|
||||
'qqbot_message',
|
||||
'qqbot_send_task',
|
||||
'qqbot_send_log',
|
||||
'qqbot_dedupe_event',
|
||||
],
|
||||
status: {
|
||||
accountTable: 'qqbot_account',
|
||||
oneBotField: 'onebot_status',
|
||||
containerField: 'container_status',
|
||||
webuiField: 'webui_status',
|
||||
qqLoginField: 'qq_login_status',
|
||||
lastErrorField: 'last_error',
|
||||
connectionSessionTable: 'qqbot_connection_session',
|
||||
sessionStatusField: 'status',
|
||||
closeReasonField: 'close_reason',
|
||||
},
|
||||
command: {
|
||||
commandTable: 'qqbot_command',
|
||||
commandIdField: 'id',
|
||||
commandKeyField: 'command_key',
|
||||
operationKeyField: 'operation_key',
|
||||
pluginKeyField: 'plugin_key',
|
||||
enabledField: 'enabled',
|
||||
cooldownField: 'cooldown_seconds',
|
||||
aliasTable: 'qqbot_command_alias',
|
||||
aliasCommandField: 'command_id',
|
||||
aliasTextField: 'alias_text',
|
||||
accountBindingTable: 'qqbot_capability_binding',
|
||||
accountBindingCommandField: 'capability_key',
|
||||
parserValidation: {
|
||||
route: 'POST /qqbot/command/test',
|
||||
commandIdField: 'commandId',
|
||||
fullCommandTextField: 'text',
|
||||
},
|
||||
},
|
||||
permission: {
|
||||
policyTable: 'qqbot_permission_policy',
|
||||
policyKeyField: 'policy_key',
|
||||
scopeFields: ['scope_type', 'scope_value'],
|
||||
effectField: 'effect',
|
||||
ruleTable: 'qqbot_rule',
|
||||
ruleCommandField: 'command_id',
|
||||
matcherField: 'matcher_json',
|
||||
actionField: 'action_json',
|
||||
enabledField: 'enabled',
|
||||
},
|
||||
messageSend: {
|
||||
conversationTable: 'qqbot_conversation',
|
||||
messageTable: 'qqbot_message',
|
||||
sendQueueTable: 'qqbot_send_task',
|
||||
queueTaskKeyField: 'task_key',
|
||||
queueStatusField: 'status',
|
||||
queuePayloadField: 'payload_json',
|
||||
queueReservedAtField: 'reserved_at',
|
||||
queueSentAtField: 'sent_at',
|
||||
sendLogTable: 'qqbot_send_log',
|
||||
sendLogStatusField: 'status',
|
||||
sendLogSafeSummaryField: 'safe_summary',
|
||||
dedupeTable: 'qqbot_dedupe_event',
|
||||
dedupeKeyField: 'dedupe_key',
|
||||
dedupeExpiresAtField: 'expires_at',
|
||||
rateLimitProvider: 'QqbotRateLimitService',
|
||||
},
|
||||
} as const;
|
||||
142
src/modules/qqbot/core/qqbot-core.module.ts
Normal file
142
src/modules/qqbot/core/qqbot-core.module.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
||||
import { DictModule } from '@/admin/dict/dict.module';
|
||||
import { QqbotAccountAbility } from '@/qqbot/account/qqbot-account-ability.entity';
|
||||
import { QqbotAccountController } from '@/qqbot/account/qqbot-account.controller';
|
||||
import { QqbotAccount } from '@/qqbot/account/qqbot-account.entity';
|
||||
import { QqbotAccountService } from '@/qqbot/account/qqbot-account.service';
|
||||
import { QqbotNapcatLoginService } from '@/qqbot/account/qqbot-napcat-login.service';
|
||||
import { QqbotNapcatWatchdogService } from '@/qqbot/account/qqbot-napcat-watchdog.service';
|
||||
import { QqbotCommandController } from '@/qqbot/command/qqbot-command.controller';
|
||||
import { QqbotCommand } from '@/qqbot/command/qqbot-command.entity';
|
||||
import { QqbotCommandEngineService } from '@/qqbot/command/qqbot-command-engine.service';
|
||||
import { QqbotCommandLog } from '@/qqbot/command/qqbot-command-log.entity';
|
||||
import { QqbotCommandParserService } from '@/qqbot/command/qqbot-command-parser.service';
|
||||
import { QqbotCommandService } from '@/qqbot/command/qqbot-command.service';
|
||||
import { QqbotReplyTemplateService } from '@/qqbot/command/qqbot-reply-template.service';
|
||||
import { QqbotReverseWsService } from '@/qqbot/connection/qqbot-reverse-ws.service';
|
||||
import { QqbotConfig } from '@/qqbot/config/qqbot-config.entity';
|
||||
import { QqbotConfigService } from '@/qqbot/config/qqbot-config.service';
|
||||
import { QqbotDashboardController } from '@/qqbot/dashboard/qqbot-dashboard.controller';
|
||||
import { QqbotDashboardService } from '@/qqbot/dashboard/qqbot-dashboard.service';
|
||||
import { QqbotDedupe } from '@/qqbot/dedupe/qqbot-dedupe.entity';
|
||||
import { QqbotDedupeService } from '@/qqbot/dedupe/qqbot-dedupe.service';
|
||||
import { QqbotEventService } from '@/qqbot/event/qqbot-event.service';
|
||||
import { QqbotConversation } from '@/qqbot/message/qqbot-conversation.entity';
|
||||
import { QqbotMessageController } from '@/qqbot/message/qqbot-message.controller';
|
||||
import { QqbotMessage } from '@/qqbot/message/qqbot-message.entity';
|
||||
import { QqbotMessageService } from '@/qqbot/message/qqbot-message.service';
|
||||
import { QqbotBusService } from '@/qqbot/mqtt/qqbot-bus.service';
|
||||
import { QqbotAccountNapcat } from '@/qqbot/napcat/qqbot-account-napcat.entity';
|
||||
import { QqbotNapcatContainer } from '@/qqbot/napcat/qqbot-napcat-container.entity';
|
||||
import { QqbotNapcatContainerService } from '@/qqbot/napcat/qqbot-napcat-container.service';
|
||||
import { QqbotAllowlist } from '@/qqbot/permission/qqbot-allowlist.entity';
|
||||
import { QqbotBlocklist } from '@/qqbot/permission/qqbot-blocklist.entity';
|
||||
import { QqbotPermissionController } from '@/qqbot/permission/qqbot-permission.controller';
|
||||
import { QqbotPermissionService } from '@/qqbot/permission/qqbot-permission.service';
|
||||
import { QqbotEventPluginRegistryService } from '@/qqbot/plugin/qqbot-event-plugin-registry.service';
|
||||
import { QqbotPluginController } from '@/qqbot/plugin/qqbot-plugin.controller';
|
||||
import { QqbotPluginRegistryService } from '@/qqbot/plugin/qqbot-plugin-registry.service';
|
||||
import { QqbotBangDreamClientService } from '@/qqbot/plugins/bangDream/application/bangdream-client.service';
|
||||
import { TsuguApplicationService } from '@/qqbot/plugins/bangDream/application/bangdream-application.service';
|
||||
import { QqbotBangDreamRendererService } from '@/qqbot/plugins/bangDream/application/bangdream-renderer.facade';
|
||||
import { QqbotBangDreamPluginService } from '@/qqbot/plugins/bangDream/qqbot-bangdream.plugin';
|
||||
import { QqbotFf14ClientService } from '@/qqbot/plugins/ff14Market/qqbot-ff14-client.service';
|
||||
import { QqbotFf14MarketPluginService } from '@/qqbot/plugins/ff14Market/qqbot-ff14-market.plugin';
|
||||
import { QqbotFflogsClientService } from '@/qqbot/plugins/fflogs/qqbot-fflogs-client.service';
|
||||
import { QqbotFflogsPluginService } from '@/qqbot/plugins/fflogs/qqbot-fflogs.plugin';
|
||||
import { QqbotRepeaterPluginService } from '@/qqbot/plugins/repeater/qqbot-repeater.plugin';
|
||||
import { QqbotRuleController } from '@/qqbot/rule/qqbot-rule.controller';
|
||||
import { QqbotRule } from '@/qqbot/rule/qqbot-rule.entity';
|
||||
import { QqbotRuleEngineService } from '@/qqbot/rule/qqbot-rule-engine.service';
|
||||
import { QqbotRuleService } from '@/qqbot/rule/qqbot-rule.service';
|
||||
import { QqbotRateLimitService } from '@/qqbot/send/qqbot-rate-limit.service';
|
||||
import { QqbotSendController } from '@/qqbot/send/qqbot-send.controller';
|
||||
import { QqbotSendLog } from '@/qqbot/send/qqbot-send-log.entity';
|
||||
import { QqbotSendService } from '@/qqbot/send/qqbot-send.service';
|
||||
|
||||
export { QQBOT_CORE_DOMAIN_CONTRACT } from './qqbot-core.contract';
|
||||
|
||||
export const QQBOT_CORE_ENTITIES = [
|
||||
QqbotAccount,
|
||||
QqbotAccountAbility,
|
||||
QqbotAllowlist,
|
||||
QqbotBlocklist,
|
||||
QqbotCommand,
|
||||
QqbotCommandLog,
|
||||
QqbotConfig,
|
||||
QqbotConversation,
|
||||
QqbotDedupe,
|
||||
QqbotMessage,
|
||||
QqbotAccountNapcat,
|
||||
QqbotNapcatContainer,
|
||||
QqbotRule,
|
||||
QqbotSendLog,
|
||||
];
|
||||
|
||||
export const QQBOT_CORE_CONTROLLERS = [
|
||||
QqbotAccountController,
|
||||
QqbotCommandController,
|
||||
QqbotDashboardController,
|
||||
QqbotMessageController,
|
||||
QqbotPermissionController,
|
||||
QqbotPluginController,
|
||||
QqbotRuleController,
|
||||
QqbotSendController,
|
||||
];
|
||||
|
||||
export const QQBOT_CORE_PROVIDERS = [
|
||||
QqbotAccountService,
|
||||
QqbotBusService,
|
||||
QqbotCommandEngineService,
|
||||
QqbotCommandParserService,
|
||||
QqbotCommandService,
|
||||
QqbotConfigService,
|
||||
QqbotDashboardService,
|
||||
QqbotDedupeService,
|
||||
QqbotEventService,
|
||||
QqbotBangDreamClientService,
|
||||
QqbotBangDreamPluginService,
|
||||
QqbotBangDreamRendererService,
|
||||
TsuguApplicationService,
|
||||
QqbotFf14ClientService,
|
||||
QqbotFf14MarketPluginService,
|
||||
QqbotFflogsClientService,
|
||||
QqbotFflogsPluginService,
|
||||
QqbotMessageService,
|
||||
QqbotNapcatLoginService,
|
||||
QqbotNapcatWatchdogService,
|
||||
QqbotNapcatContainerService,
|
||||
QqbotPermissionService,
|
||||
QqbotEventPluginRegistryService,
|
||||
QqbotPluginRegistryService,
|
||||
QqbotRepeaterPluginService,
|
||||
QqbotRateLimitService,
|
||||
QqbotReplyTemplateService,
|
||||
QqbotReverseWsService,
|
||||
QqbotRuleEngineService,
|
||||
QqbotRuleService,
|
||||
QqbotSendService,
|
||||
];
|
||||
|
||||
export const QQBOT_CORE_EXPORTS = [
|
||||
QqbotAccountService,
|
||||
QqbotNapcatLoginService,
|
||||
QqbotNapcatContainerService,
|
||||
QqbotReverseWsService,
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
AdminAuthGuardModule,
|
||||
DictModule,
|
||||
TypeOrmModule.forFeature(QQBOT_CORE_ENTITIES),
|
||||
],
|
||||
controllers: QQBOT_CORE_CONTROLLERS,
|
||||
providers: QQBOT_CORE_PROVIDERS,
|
||||
exports: QQBOT_CORE_EXPORTS,
|
||||
})
|
||||
export class QqbotCoreModule {}
|
||||
@ -1,132 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
||||
import { DictModule } from '@/admin/dict/dict.module';
|
||||
import { QqbotAccountController } from './account/qqbot-account.controller';
|
||||
import { QqbotAccountAbility } from './account/qqbot-account-ability.entity';
|
||||
import { QqbotAccount } from './account/qqbot-account.entity';
|
||||
import { QqbotAccountService } from './account/qqbot-account.service';
|
||||
import { QqbotNapcatLoginService } from './account/qqbot-napcat-login.service';
|
||||
import { QqbotNapcatWatchdogService } from './account/qqbot-napcat-watchdog.service';
|
||||
import { QqbotCommandController } from './command/qqbot-command.controller';
|
||||
import { QqbotCommand } from './command/qqbot-command.entity';
|
||||
import { QqbotCommandEngineService } from './command/qqbot-command-engine.service';
|
||||
import { QqbotCommandLog } from './command/qqbot-command-log.entity';
|
||||
import { QqbotCommandParserService } from './command/qqbot-command-parser.service';
|
||||
import { QqbotCommandService } from './command/qqbot-command.service';
|
||||
import { QqbotReplyTemplateService } from './command/qqbot-reply-template.service';
|
||||
import { QqbotReverseWsService } from './connection/qqbot-reverse-ws.service';
|
||||
import { QqbotConfig } from './config/qqbot-config.entity';
|
||||
import { QqbotConfigService } from './config/qqbot-config.service';
|
||||
import { QqbotDashboardController } from './dashboard/qqbot-dashboard.controller';
|
||||
import { QqbotDashboardService } from './dashboard/qqbot-dashboard.service';
|
||||
import { QqbotDedupe } from './dedupe/qqbot-dedupe.entity';
|
||||
import { QqbotDedupeService } from './dedupe/qqbot-dedupe.service';
|
||||
import { QqbotEventService } from './event/qqbot-event.service';
|
||||
import { QqbotConversation } from './message/qqbot-conversation.entity';
|
||||
import { QqbotMessageController } from './message/qqbot-message.controller';
|
||||
import { QqbotMessage } from './message/qqbot-message.entity';
|
||||
import { QqbotMessageService } from './message/qqbot-message.service';
|
||||
import { QqbotBusService } from './mqtt/qqbot-bus.service';
|
||||
import { QqbotAccountNapcat } from './napcat/qqbot-account-napcat.entity';
|
||||
import { QqbotNapcatContainer } from './napcat/qqbot-napcat-container.entity';
|
||||
import { QqbotNapcatContainerService } from './napcat/qqbot-napcat-container.service';
|
||||
import { QqbotAllowlist } from './permission/qqbot-allowlist.entity';
|
||||
import { QqbotBlocklist } from './permission/qqbot-blocklist.entity';
|
||||
import { QqbotPermissionController } from './permission/qqbot-permission.controller';
|
||||
import { QqbotPermissionService } from './permission/qqbot-permission.service';
|
||||
import { QqbotEventPluginRegistryService } from './plugin/qqbot-event-plugin-registry.service';
|
||||
import { QqbotPluginController } from './plugin/qqbot-plugin.controller';
|
||||
import { QqbotPluginRegistryService } from './plugin/qqbot-plugin-registry.service';
|
||||
import { QqbotBangDreamClientService } from './plugins/bangDream/application/bangdream-client.service';
|
||||
import { QqbotBangDreamRendererService } from './plugins/bangDream/application/bangdream-renderer.facade';
|
||||
import { TsuguApplicationService } from './plugins/bangDream/application/bangdream-application.service';
|
||||
import { QqbotBangDreamPluginService } from './plugins/bangDream/qqbot-bangdream.plugin';
|
||||
import { QqbotFf14ClientService } from './plugins/ff14Market/qqbot-ff14-client.service';
|
||||
import { QqbotFf14MarketPluginService } from './plugins/ff14Market/qqbot-ff14-market.plugin';
|
||||
import { QqbotFflogsClientService } from './plugins/fflogs/qqbot-fflogs-client.service';
|
||||
import { QqbotFflogsPluginService } from './plugins/fflogs/qqbot-fflogs.plugin';
|
||||
import { QqbotRepeaterPluginService } from './plugins/repeater/qqbot-repeater.plugin';
|
||||
import { QqbotRuleController } from './rule/qqbot-rule.controller';
|
||||
import { QqbotRule } from './rule/qqbot-rule.entity';
|
||||
import { QqbotRuleEngineService } from './rule/qqbot-rule-engine.service';
|
||||
import { QqbotRuleService } from './rule/qqbot-rule.service';
|
||||
import { QqbotRateLimitService } from './send/qqbot-rate-limit.service';
|
||||
import { QqbotSendController } from './send/qqbot-send.controller';
|
||||
import { QqbotSendLog } from './send/qqbot-send-log.entity';
|
||||
import { QqbotSendService } from './send/qqbot-send.service';
|
||||
import { QqbotCoreModule } from '@/modules/qqbot/core/qqbot-core.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
AdminAuthGuardModule,
|
||||
DictModule,
|
||||
TypeOrmModule.forFeature([
|
||||
QqbotAccount,
|
||||
QqbotAccountAbility,
|
||||
QqbotAllowlist,
|
||||
QqbotBlocklist,
|
||||
QqbotCommand,
|
||||
QqbotCommandLog,
|
||||
QqbotConfig,
|
||||
QqbotConversation,
|
||||
QqbotDedupe,
|
||||
QqbotMessage,
|
||||
QqbotAccountNapcat,
|
||||
QqbotNapcatContainer,
|
||||
QqbotRule,
|
||||
QqbotSendLog,
|
||||
]),
|
||||
],
|
||||
controllers: [
|
||||
QqbotAccountController,
|
||||
QqbotCommandController,
|
||||
QqbotDashboardController,
|
||||
QqbotMessageController,
|
||||
QqbotPermissionController,
|
||||
QqbotPluginController,
|
||||
QqbotRuleController,
|
||||
QqbotSendController,
|
||||
],
|
||||
providers: [
|
||||
QqbotAccountService,
|
||||
QqbotBusService,
|
||||
QqbotCommandEngineService,
|
||||
QqbotCommandParserService,
|
||||
QqbotCommandService,
|
||||
QqbotConfigService,
|
||||
QqbotDashboardService,
|
||||
QqbotDedupeService,
|
||||
QqbotEventService,
|
||||
QqbotBangDreamClientService,
|
||||
QqbotBangDreamPluginService,
|
||||
QqbotBangDreamRendererService,
|
||||
TsuguApplicationService,
|
||||
QqbotFf14ClientService,
|
||||
QqbotFf14MarketPluginService,
|
||||
QqbotFflogsClientService,
|
||||
QqbotFflogsPluginService,
|
||||
QqbotMessageService,
|
||||
QqbotNapcatLoginService,
|
||||
QqbotNapcatWatchdogService,
|
||||
QqbotNapcatContainerService,
|
||||
QqbotPermissionService,
|
||||
QqbotEventPluginRegistryService,
|
||||
QqbotPluginRegistryService,
|
||||
QqbotRepeaterPluginService,
|
||||
QqbotRateLimitService,
|
||||
QqbotReplyTemplateService,
|
||||
QqbotReverseWsService,
|
||||
QqbotRuleEngineService,
|
||||
QqbotRuleService,
|
||||
QqbotSendService,
|
||||
],
|
||||
exports: [
|
||||
QqbotAccountService,
|
||||
QqbotNapcatLoginService,
|
||||
QqbotNapcatContainerService,
|
||||
QqbotReverseWsService,
|
||||
],
|
||||
imports: [QqbotCoreModule],
|
||||
exports: [QqbotCoreModule],
|
||||
})
|
||||
export class QqbotModule {}
|
||||
|
||||
@ -14,10 +14,16 @@ const nonColumnTokens = new Set([
|
||||
|
||||
export type SqlSchemaContract = {
|
||||
hasTable(tableName: string): boolean;
|
||||
getTableColumns(tableName: string): SqlSchemaColumn[];
|
||||
expectTableColumns(tableName: string, columns: readonly string[]): void;
|
||||
};
|
||||
|
||||
const parseColumnNames = (tableBlock: string) => {
|
||||
export type SqlSchemaColumn = {
|
||||
definition: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const parseColumns = (tableBlock: string): SqlSchemaColumn[] => {
|
||||
return tableBlock
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim().replace(/,$/, ''))
|
||||
@ -28,16 +34,21 @@ const parseColumnNames = (tableBlock: string) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [match[1]];
|
||||
return [
|
||||
{
|
||||
definition: line,
|
||||
name: match[1],
|
||||
},
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
const parseSchema = (sql: string) => {
|
||||
const tables = new Map<string, Set<string>>();
|
||||
const tables = new Map<string, SqlSchemaColumn[]>();
|
||||
|
||||
for (const match of sql.matchAll(createTableRegex)) {
|
||||
const [, tableName, tableBlock] = match;
|
||||
tables.set(tableName, new Set(parseColumnNames(tableBlock)));
|
||||
tables.set(tableName, parseColumns(tableBlock));
|
||||
}
|
||||
|
||||
return tables;
|
||||
@ -55,12 +66,13 @@ export const readRefactorV3SqlSchema = (): SqlSchemaContract => {
|
||||
const tables = parseSchema(fs.readFileSync(schemaPath, 'utf8'));
|
||||
|
||||
return {
|
||||
getTableColumns: (tableName) => tables.get(tableName) || [],
|
||||
hasTable: (tableName) => tables.has(tableName),
|
||||
expectTableColumns: (tableName, columns) => {
|
||||
const tableColumns = tables.get(tableName);
|
||||
|
||||
expect(tableColumns).toBeDefined();
|
||||
expect(Array.from(tableColumns || [])).toEqual(
|
||||
expect((tableColumns || []).map((column) => column.name)).toEqual(
|
||||
expect.arrayContaining([...columns]),
|
||||
);
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
jest.mock('../../../src/qqbot/qqbot.module', () => ({
|
||||
QqbotModule: class QqbotModule {},
|
||||
jest.mock('../../../src/modules/qqbot/core/qqbot-core.module', () => ({
|
||||
QqbotCoreModule: class QqbotCoreModule {},
|
||||
}));
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
jest.mock('../../../src/qqbot/qqbot.module', () => ({
|
||||
QqbotModule: class QqbotModule {},
|
||||
jest.mock('../../../src/modules/qqbot/core/qqbot-core.module', () => ({
|
||||
QqbotCoreModule: class QqbotCoreModule {},
|
||||
}));
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
|
||||
94
test/modules/qqbot/core/qqbot-core-command-contract.spec.ts
Normal file
94
test/modules/qqbot/core/qqbot-core-command-contract.spec.ts
Normal file
@ -0,0 +1,94 @@
|
||||
jest.mock('@/qqbot/qqbot.module', () => ({
|
||||
QqbotModule: class QqbotModule {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugin/qqbot-event-plugin-registry.service', () => ({
|
||||
QqbotEventPluginRegistryService: class QqbotEventPluginRegistryService {},
|
||||
}));
|
||||
jest.mock(
|
||||
'../../../../src/qqbot/plugin/qqbot-event-plugin-registry.service',
|
||||
() => ({
|
||||
QqbotEventPluginRegistryService: class QqbotEventPluginRegistryService {},
|
||||
}),
|
||||
);
|
||||
jest.mock('@/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
jest.mock('../../../../src/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
|
||||
import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/qqbot-core.contract';
|
||||
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
|
||||
|
||||
describe('QQBot core command contract', () => {
|
||||
const schema = readRefactorV3SqlSchema();
|
||||
|
||||
it('keeps operation lookup, command ID binding, aliases, cooldown and parser validation explicit', () => {
|
||||
expect(QQBOT_CORE_DOMAIN_CONTRACT.command).toEqual({
|
||||
commandTable: 'qqbot_command',
|
||||
commandIdField: 'id',
|
||||
commandKeyField: 'command_key',
|
||||
operationKeyField: 'operation_key',
|
||||
pluginKeyField: 'plugin_key',
|
||||
enabledField: 'enabled',
|
||||
cooldownField: 'cooldown_seconds',
|
||||
aliasTable: 'qqbot_command_alias',
|
||||
aliasCommandField: 'command_id',
|
||||
aliasTextField: 'alias_text',
|
||||
accountBindingTable: 'qqbot_capability_binding',
|
||||
accountBindingCommandField: 'capability_key',
|
||||
parserValidation: {
|
||||
route: 'POST /qqbot/command/test',
|
||||
commandIdField: 'commandId',
|
||||
fullCommandTextField: 'text',
|
||||
},
|
||||
});
|
||||
expect(QQBOT_CORE_DOMAIN_CONTRACT.permission).toEqual({
|
||||
policyTable: 'qqbot_permission_policy',
|
||||
policyKeyField: 'policy_key',
|
||||
scopeFields: ['scope_type', 'scope_value'],
|
||||
effectField: 'effect',
|
||||
ruleTable: 'qqbot_rule',
|
||||
ruleCommandField: 'command_id',
|
||||
matcherField: 'matcher_json',
|
||||
actionField: 'action_json',
|
||||
enabledField: 'enabled',
|
||||
});
|
||||
|
||||
schema.expectTableColumns('qqbot_command', [
|
||||
'id',
|
||||
'operation_key',
|
||||
'command_key',
|
||||
'plugin_key',
|
||||
'enabled',
|
||||
'cooldown_seconds',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_command_alias', [
|
||||
'id',
|
||||
'command_id',
|
||||
'alias_text',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_capability_binding', [
|
||||
'id',
|
||||
'account_id',
|
||||
'capability_key',
|
||||
'enabled',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_permission_policy', [
|
||||
'id',
|
||||
'policy_key',
|
||||
'scope_type',
|
||||
'scope_value',
|
||||
'effect',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_rule', [
|
||||
'id',
|
||||
'rule_key',
|
||||
'account_id',
|
||||
'command_id',
|
||||
'matcher_json',
|
||||
'action_json',
|
||||
'enabled',
|
||||
]);
|
||||
});
|
||||
});
|
||||
205
test/modules/qqbot/core/qqbot-core-command-smoke.spec.ts
Normal file
205
test/modules/qqbot/core/qqbot-core-command-smoke.spec.ts
Normal file
@ -0,0 +1,205 @@
|
||||
jest.mock('@/admin/auth/jwt-auth.guard', () => ({
|
||||
JwtAuthGuard: class {
|
||||
canActivate() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
jest.mock('../../../../src/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { DictService } from '../../../../src/admin/dict/dict.service';
|
||||
import { ToolsService } from '../../../../src/common';
|
||||
import { QqbotAccountService } from '../../../../src/qqbot/account/qqbot-account.service';
|
||||
import { QqbotCommandController } from '../../../../src/qqbot/command/qqbot-command.controller';
|
||||
import { QqbotCommand } from '../../../../src/qqbot/command/qqbot-command.entity';
|
||||
import { QqbotCommandEngineService } from '../../../../src/qqbot/command/qqbot-command-engine.service';
|
||||
import { QqbotCommandLog } from '../../../../src/qqbot/command/qqbot-command-log.entity';
|
||||
import { QqbotCommandParserService } from '../../../../src/qqbot/command/qqbot-command-parser.service';
|
||||
import { QqbotCommandService } from '../../../../src/qqbot/command/qqbot-command.service';
|
||||
import { QqbotReplyTemplateService } from '../../../../src/qqbot/command/qqbot-reply-template.service';
|
||||
import { QqbotPluginRegistryService } from '../../../../src/qqbot/plugin/qqbot-plugin-registry.service';
|
||||
import { QqbotSendService } from '../../../../src/qqbot/send/qqbot-send.service';
|
||||
|
||||
describe('QQBot core command local smoke', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
const command = {
|
||||
aliases: '["查歌"]',
|
||||
code: 'bangdream-song-search',
|
||||
cooldownMs: 5000,
|
||||
defaultParams: null,
|
||||
enabled: true,
|
||||
errorTemplate: null,
|
||||
id: 'cmd-bangdream-song-search',
|
||||
isDeleted: false,
|
||||
lastHitAt: null,
|
||||
name: 'BangDream 查歌',
|
||||
operationKey: 'bangdream.song.search',
|
||||
parserKey: 'plain',
|
||||
pluginKey: 'bangdream',
|
||||
prefixes: '["/"]',
|
||||
priority: 100,
|
||||
replyTemplate: '找到歌曲:{{output.title}}',
|
||||
targetType: 'all',
|
||||
} as QqbotCommand;
|
||||
|
||||
const queryBuilder = {
|
||||
addOrderBy: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
getManyAndCount: jest.fn().mockResolvedValue([[command], 1]),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
skip: jest.fn().mockReturnThis(),
|
||||
take: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
};
|
||||
|
||||
const commandRepository = {
|
||||
createQueryBuilder: jest.fn(() => queryBuilder),
|
||||
findOne: jest.fn().mockResolvedValue(command),
|
||||
};
|
||||
|
||||
const commandLogRepository = {
|
||||
create: jest.fn((payload) => payload),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const pluginRegistry = {
|
||||
execute: jest.fn().mockResolvedValue({
|
||||
title: 'FIRE BIRD',
|
||||
type: 'text',
|
||||
}),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [QqbotCommandController],
|
||||
providers: [
|
||||
ToolsService,
|
||||
QqbotCommandEngineService,
|
||||
QqbotCommandParserService,
|
||||
QqbotCommandService,
|
||||
QqbotReplyTemplateService,
|
||||
{
|
||||
provide: getRepositoryToken(QqbotCommand),
|
||||
useValue: commandRepository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(QqbotCommandLog),
|
||||
useValue: commandLogRepository,
|
||||
},
|
||||
{
|
||||
provide: QqbotAccountService,
|
||||
useValue: {
|
||||
getBoundCommandIds: jest.fn().mockResolvedValue([command.id]),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: QqbotPluginRegistryService,
|
||||
useValue: pluginRegistry,
|
||||
},
|
||||
{
|
||||
provide: QqbotSendService,
|
||||
useValue: {
|
||||
sendText: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
get: jest.fn().mockReturnValue('5000'),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DictService,
|
||||
useValue: {
|
||||
getDictItemsByKey: jest.fn().mockResolvedValue([]),
|
||||
relationTree: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('queries enabled command by operationKey before previewing a full command text with commandId', async () => {
|
||||
const listResponse = await request(app.getHttpServer())
|
||||
.get('/qqbot/command/list')
|
||||
.query({
|
||||
enabled: true,
|
||||
operationKey: command.operationKey,
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const commandId = listResponse.body.data.list[0].id;
|
||||
|
||||
const previewResponse = await request(app.getHttpServer())
|
||||
.post('/qqbot/command/test')
|
||||
.send({
|
||||
commandId,
|
||||
selfId: 'preview',
|
||||
targetId: '10000',
|
||||
targetType: 'group',
|
||||
text: '/查歌 FIRE BIRD',
|
||||
userId: '10000',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const sanitizedPreview = { ...previewResponse.body.data };
|
||||
delete sanitizedPreview.replyText;
|
||||
|
||||
expect(sanitizedPreview).toMatchObject({
|
||||
command: {
|
||||
id: command.id,
|
||||
operationKey: command.operationKey,
|
||||
},
|
||||
input: {
|
||||
text: 'FIRE BIRD',
|
||||
},
|
||||
matched: true,
|
||||
status: 'success',
|
||||
});
|
||||
expect(queryBuilder.andWhere).toHaveBeenCalledWith(
|
||||
'command.operationKey = :operationKey',
|
||||
{ operationKey: command.operationKey },
|
||||
);
|
||||
expect(queryBuilder.andWhere).toHaveBeenCalledWith(
|
||||
'command.enabled = :enabled',
|
||||
{ enabled: true },
|
||||
);
|
||||
expect(commandRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: command.id, isDeleted: false },
|
||||
});
|
||||
expect(pluginRegistry.execute).toHaveBeenCalledWith(
|
||||
command.pluginKey,
|
||||
command.operationKey,
|
||||
expect.objectContaining({
|
||||
text: 'FIRE BIRD',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
command,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
280
test/modules/qqbot/core/qqbot-core-module-contract.spec.ts
Normal file
280
test/modules/qqbot/core/qqbot-core-module-contract.spec.ts
Normal file
@ -0,0 +1,280 @@
|
||||
const mockQqbotEventPluginRegistryService = class QqbotEventPluginRegistryService {};
|
||||
const mockQqbotPluginRegistryService = class QqbotPluginRegistryService {};
|
||||
|
||||
jest.mock('@/qqbot/plugin/qqbot-event-plugin-registry.service', () => ({
|
||||
QqbotEventPluginRegistryService: mockQqbotEventPluginRegistryService,
|
||||
}));
|
||||
jest.mock(
|
||||
'../../../../src/qqbot/plugin/qqbot-event-plugin-registry.service',
|
||||
() => ({
|
||||
QqbotEventPluginRegistryService: mockQqbotEventPluginRegistryService,
|
||||
}),
|
||||
);
|
||||
jest.mock('@/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: mockQqbotPluginRegistryService,
|
||||
}));
|
||||
jest.mock('../../../../src/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: mockQqbotPluginRegistryService,
|
||||
}));
|
||||
jest.mock(
|
||||
'@/qqbot/plugins/bangDream/application/bangdream-client.service',
|
||||
() => ({
|
||||
QqbotBangDreamClientService: class QqbotBangDreamClientService {},
|
||||
}),
|
||||
);
|
||||
jest.mock(
|
||||
'@/qqbot/plugins/bangDream/application/bangdream-renderer.facade',
|
||||
() => ({
|
||||
QqbotBangDreamRendererService: class QqbotBangDreamRendererService {},
|
||||
}),
|
||||
);
|
||||
jest.mock(
|
||||
'@/qqbot/plugins/bangDream/application/bangdream-application.service',
|
||||
() => ({
|
||||
TsuguApplicationService: class TsuguApplicationService {},
|
||||
}),
|
||||
);
|
||||
jest.mock('@/qqbot/plugins/bangDream/qqbot-bangdream.plugin', () => ({
|
||||
QqbotBangDreamPluginService: class QqbotBangDreamPluginService {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugins/ff14Market/qqbot-ff14-client.service', () => ({
|
||||
QqbotFf14ClientService: class QqbotFf14ClientService {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugins/ff14Market/qqbot-ff14-market.plugin', () => ({
|
||||
QqbotFf14MarketPluginService: class QqbotFf14MarketPluginService {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugins/fflogs/qqbot-fflogs-client.service', () => ({
|
||||
QqbotFflogsClientService: class QqbotFflogsClientService {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugins/fflogs/qqbot-fflogs.plugin', () => ({
|
||||
QqbotFflogsPluginService: class QqbotFflogsPluginService {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugins/repeater/qqbot-repeater.plugin', () => ({
|
||||
QqbotRepeaterPluginService: class QqbotRepeaterPluginService {},
|
||||
}));
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { getMetadataArgsStorage } from 'typeorm';
|
||||
import { AdminAuthGuardModule } from '../../../../src/admin/auth/admin-auth-guard.module';
|
||||
import { DictModule } from '../../../../src/admin/dict/dict.module';
|
||||
import { AppModule } from '../../../../src/app.module';
|
||||
import { QqbotAccountController } from '../../../../src/qqbot/account/qqbot-account.controller';
|
||||
import { QqbotCommandController } from '../../../../src/qqbot/command/qqbot-command.controller';
|
||||
import { QqbotDashboardController } from '../../../../src/qqbot/dashboard/qqbot-dashboard.controller';
|
||||
import { QqbotMessageController } from '../../../../src/qqbot/message/qqbot-message.controller';
|
||||
import { QqbotPermissionController } from '../../../../src/qqbot/permission/qqbot-permission.controller';
|
||||
import { QqbotPluginController } from '../../../../src/qqbot/plugin/qqbot-plugin.controller';
|
||||
import { QqbotModule } from '../../../../src/qqbot/qqbot.module';
|
||||
import { QqbotRuleController } from '../../../../src/qqbot/rule/qqbot-rule.controller';
|
||||
import { QqbotSendController } from '../../../../src/qqbot/send/qqbot-send.controller';
|
||||
import {
|
||||
QQBOT_CORE_CONTROLLERS,
|
||||
QQBOT_CORE_ENTITIES,
|
||||
QQBOT_CORE_EXPORTS,
|
||||
QQBOT_CORE_PROVIDERS,
|
||||
QqbotCoreModule,
|
||||
} from '../../../../src/modules/qqbot/core/qqbot-core.module';
|
||||
import {
|
||||
collectControllerRoutes,
|
||||
routeKey,
|
||||
} from '../../../helpers/controller-route.helper';
|
||||
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
|
||||
|
||||
const getModuleMetadata = <T>(moduleClass: unknown, key: string): T[] => {
|
||||
return Reflect.getMetadata(key, moduleClass) || [];
|
||||
};
|
||||
|
||||
const getNames = (items: unknown[]) =>
|
||||
items.map((item) => (item as { name?: string }).name || `${item}`);
|
||||
|
||||
type EntityClass = new (...args: never[]) => unknown;
|
||||
|
||||
const getEntityTableName = (entity: EntityClass) => {
|
||||
return getMetadataArgsStorage().tables.find(
|
||||
(table) => table.target === entity,
|
||||
)?.name;
|
||||
};
|
||||
|
||||
const getEntityColumnNames = (entity: EntityClass) => {
|
||||
return getMetadataArgsStorage()
|
||||
.columns.filter((column) => column.target === entity)
|
||||
.map((column) => `${column.options.name || column.propertyName}`);
|
||||
};
|
||||
|
||||
const getEntityNullableColumnNames = (entity: EntityClass) => {
|
||||
return getMetadataArgsStorage()
|
||||
.columns.filter((column) => column.target === entity)
|
||||
.filter((column) => column.options.nullable === true)
|
||||
.map((column) => `${column.options.name || column.propertyName}`);
|
||||
};
|
||||
|
||||
const isOptionalSqlColumnForEntityInsert = (definition: string) => {
|
||||
return (
|
||||
!/\bNOT\s+NULL\b/i.test(definition) ||
|
||||
/\bDEFAULT\b/i.test(definition) ||
|
||||
/\bAUTO_INCREMENT\b/i.test(definition)
|
||||
);
|
||||
};
|
||||
|
||||
describe('QQBot core module contract', () => {
|
||||
const schema = readRefactorV3SqlSchema();
|
||||
|
||||
it('keeps QQBot Admin and runtime routes compatible through the core boundary', () => {
|
||||
const routes = collectControllerRoutes(QQBOT_CORE_CONTROLLERS);
|
||||
|
||||
expect(routes.map(routeKey)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'GET /qqbot/account/list',
|
||||
'GET /qqbot/account/enabled',
|
||||
'POST /qqbot/account/scan/create',
|
||||
'GET /qqbot/account/scan/status',
|
||||
'POST /qqbot/account/bind/command',
|
||||
'POST /qqbot/account/unbind/command',
|
||||
'POST /qqbot/account/kick',
|
||||
'GET /qqbot/command/list',
|
||||
'POST /qqbot/command/test',
|
||||
'GET /qqbot/conversation/list',
|
||||
'GET /qqbot/message/list',
|
||||
'GET /qqbot/permission/config',
|
||||
'GET /qqbot/permission/allowlist',
|
||||
'GET /qqbot/permission/blocklist',
|
||||
'GET /qqbot/plugin/list',
|
||||
'GET /qqbot/rule/list',
|
||||
'GET /qqbot/send/log/list',
|
||||
'POST /qqbot/send/private',
|
||||
'POST /qqbot/send/group',
|
||||
'GET /qqbot/dashboard/summary',
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('routes QQBot through the core module as the owning Nest boundary', () => {
|
||||
expect(getModuleMetadata(AppModule, MODULE_METADATA.IMPORTS)).toEqual(
|
||||
expect.arrayContaining([QqbotCoreModule]),
|
||||
);
|
||||
expect(getModuleMetadata(AppModule, MODULE_METADATA.IMPORTS)).not.toEqual(
|
||||
expect.arrayContaining([QqbotModule]),
|
||||
);
|
||||
|
||||
const coreImports = getModuleMetadata(
|
||||
QqbotCoreModule,
|
||||
MODULE_METADATA.IMPORTS,
|
||||
);
|
||||
expect(coreImports).toEqual(
|
||||
expect.arrayContaining([ConfigModule, AdminAuthGuardModule, DictModule]),
|
||||
);
|
||||
expect(
|
||||
coreImports.some(
|
||||
(item) => (item as { module?: unknown }).module === TypeOrmModule,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(coreImports).not.toEqual(expect.arrayContaining([QqbotModule]));
|
||||
|
||||
expect(
|
||||
getModuleMetadata(QqbotCoreModule, MODULE_METADATA.CONTROLLERS),
|
||||
).toEqual(expect.arrayContaining(QQBOT_CORE_CONTROLLERS));
|
||||
expect(
|
||||
getModuleMetadata(QqbotCoreModule, MODULE_METADATA.PROVIDERS),
|
||||
).toEqual(expect.arrayContaining(QQBOT_CORE_PROVIDERS));
|
||||
expect(getModuleMetadata(QqbotCoreModule, MODULE_METADATA.EXPORTS)).toEqual(
|
||||
expect.arrayContaining(QQBOT_CORE_EXPORTS),
|
||||
);
|
||||
expect(getModuleMetadata(QqbotModule, MODULE_METADATA.IMPORTS)).toEqual(
|
||||
expect.arrayContaining([QqbotCoreModule]),
|
||||
);
|
||||
expect(getModuleMetadata(QqbotModule, MODULE_METADATA.EXPORTS)).toEqual(
|
||||
expect.arrayContaining([QqbotCoreModule]),
|
||||
);
|
||||
});
|
||||
|
||||
it('makes the legacy QQBot controllers, providers and entities explicit', () => {
|
||||
expect(QQBOT_CORE_CONTROLLERS).toEqual(
|
||||
expect.arrayContaining([
|
||||
QqbotAccountController,
|
||||
QqbotCommandController,
|
||||
QqbotDashboardController,
|
||||
QqbotMessageController,
|
||||
QqbotPermissionController,
|
||||
QqbotPluginController,
|
||||
QqbotRuleController,
|
||||
QqbotSendController,
|
||||
]),
|
||||
);
|
||||
expect(getNames(QQBOT_CORE_PROVIDERS)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'QqbotAccountService',
|
||||
'QqbotBusService',
|
||||
'QqbotCommandEngineService',
|
||||
'QqbotCommandParserService',
|
||||
'QqbotCommandService',
|
||||
'QqbotConfigService',
|
||||
'QqbotDashboardService',
|
||||
'QqbotDedupeService',
|
||||
'QqbotEventPluginRegistryService',
|
||||
'QqbotEventService',
|
||||
'QqbotMessageService',
|
||||
'QqbotNapcatContainerService',
|
||||
'QqbotNapcatLoginService',
|
||||
'QqbotNapcatWatchdogService',
|
||||
'QqbotPermissionService',
|
||||
'QqbotPluginRegistryService',
|
||||
'QqbotRateLimitService',
|
||||
'QqbotReplyTemplateService',
|
||||
'QqbotReverseWsService',
|
||||
'QqbotRuleEngineService',
|
||||
'QqbotRuleService',
|
||||
'QqbotSendService',
|
||||
]),
|
||||
);
|
||||
expect(getNames(QQBOT_CORE_ENTITIES)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'QqbotAccount',
|
||||
'QqbotAccountAbility',
|
||||
'QqbotAccountNapcat',
|
||||
'QqbotAllowlist',
|
||||
'QqbotBlocklist',
|
||||
'QqbotCommand',
|
||||
'QqbotCommandLog',
|
||||
'QqbotConfig',
|
||||
'QqbotConversation',
|
||||
'QqbotDedupe',
|
||||
'QqbotMessage',
|
||||
'QqbotNapcatContainer',
|
||||
'QqbotRule',
|
||||
'QqbotSendLog',
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps every registered QQBot core entity mapped to the refactor-v3 schema', () => {
|
||||
for (const entity of QQBOT_CORE_ENTITIES) {
|
||||
const tableName = getEntityTableName(entity);
|
||||
const columns = getEntityColumnNames(entity);
|
||||
const nullableColumns = getEntityNullableColumnNames(entity);
|
||||
|
||||
expect(tableName).toBeTruthy();
|
||||
expect(schema.hasTable(tableName || '')).toBe(true);
|
||||
schema.expectTableColumns(tableName || '', columns);
|
||||
|
||||
const sqlColumns = schema.getTableColumns(tableName || '');
|
||||
const entityColumnNames = new Set(columns);
|
||||
const requiredSqlOnlyColumns = sqlColumns
|
||||
.filter((column) => !entityColumnNames.has(column.name))
|
||||
.filter(
|
||||
(column) => !isOptionalSqlColumnForEntityInsert(column.definition),
|
||||
)
|
||||
.map((column) => `${tableName}.${column.name}`);
|
||||
|
||||
expect(requiredSqlOnlyColumns).toEqual([]);
|
||||
|
||||
for (const nullableColumn of nullableColumns) {
|
||||
const sqlColumn = sqlColumns.find(
|
||||
(column) => column.name === nullableColumn,
|
||||
);
|
||||
expect(sqlColumn?.definition).not.toMatch(/\bNOT\s+NULL\b/i);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
86
test/modules/qqbot/core/qqbot-core-send-contract.spec.ts
Normal file
86
test/modules/qqbot/core/qqbot-core-send-contract.spec.ts
Normal file
@ -0,0 +1,86 @@
|
||||
jest.mock('@/qqbot/qqbot.module', () => ({
|
||||
QqbotModule: class QqbotModule {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugin/qqbot-event-plugin-registry.service', () => ({
|
||||
QqbotEventPluginRegistryService: class QqbotEventPluginRegistryService {},
|
||||
}));
|
||||
jest.mock(
|
||||
'../../../../src/qqbot/plugin/qqbot-event-plugin-registry.service',
|
||||
() => ({
|
||||
QqbotEventPluginRegistryService: class QqbotEventPluginRegistryService {},
|
||||
}),
|
||||
);
|
||||
jest.mock('@/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
jest.mock('../../../../src/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
|
||||
import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/qqbot-core.contract';
|
||||
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
|
||||
|
||||
describe('QQBot core send contract', () => {
|
||||
const schema = readRefactorV3SqlSchema();
|
||||
|
||||
it('keeps send queue reservation, rate limit, send log and dedupe event contracts explicit', () => {
|
||||
expect(QQBOT_CORE_DOMAIN_CONTRACT.messageSend).toEqual({
|
||||
conversationTable: 'qqbot_conversation',
|
||||
messageTable: 'qqbot_message',
|
||||
sendQueueTable: 'qqbot_send_task',
|
||||
queueTaskKeyField: 'task_key',
|
||||
queueStatusField: 'status',
|
||||
queuePayloadField: 'payload_json',
|
||||
queueReservedAtField: 'reserved_at',
|
||||
queueSentAtField: 'sent_at',
|
||||
sendLogTable: 'qqbot_send_log',
|
||||
sendLogStatusField: 'status',
|
||||
sendLogSafeSummaryField: 'safe_summary',
|
||||
dedupeTable: 'qqbot_dedupe_event',
|
||||
dedupeKeyField: 'dedupe_key',
|
||||
dedupeExpiresAtField: 'expires_at',
|
||||
rateLimitProvider: 'QqbotRateLimitService',
|
||||
});
|
||||
|
||||
schema.expectTableColumns('qqbot_conversation', [
|
||||
'id',
|
||||
'account_id',
|
||||
'conversation_type',
|
||||
'conversation_key',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_message', [
|
||||
'id',
|
||||
'account_id',
|
||||
'conversation_id',
|
||||
'message_id',
|
||||
'direction',
|
||||
'message_type',
|
||||
'raw_payload',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_send_task', [
|
||||
'id',
|
||||
'account_id',
|
||||
'conversation_id',
|
||||
'task_key',
|
||||
'status',
|
||||
'payload_json',
|
||||
'reserved_at',
|
||||
'sent_at',
|
||||
'last_error',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_send_log', [
|
||||
'id',
|
||||
'task_id',
|
||||
'account_id',
|
||||
'status',
|
||||
'safe_summary',
|
||||
'error_message',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_dedupe_event', [
|
||||
'id',
|
||||
'dedupe_key',
|
||||
'account_id',
|
||||
'expires_at',
|
||||
]);
|
||||
});
|
||||
});
|
||||
58
test/modules/qqbot/core/qqbot-core-status-contract.spec.ts
Normal file
58
test/modules/qqbot/core/qqbot-core-status-contract.spec.ts
Normal file
@ -0,0 +1,58 @@
|
||||
jest.mock('@/qqbot/qqbot.module', () => ({
|
||||
QqbotModule: class QqbotModule {},
|
||||
}));
|
||||
jest.mock('@/qqbot/plugin/qqbot-event-plugin-registry.service', () => ({
|
||||
QqbotEventPluginRegistryService: class QqbotEventPluginRegistryService {},
|
||||
}));
|
||||
jest.mock(
|
||||
'../../../../src/qqbot/plugin/qqbot-event-plugin-registry.service',
|
||||
() => ({
|
||||
QqbotEventPluginRegistryService: class QqbotEventPluginRegistryService {},
|
||||
}),
|
||||
);
|
||||
jest.mock('@/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
jest.mock('../../../../src/qqbot/plugin/qqbot-plugin-registry.service', () => ({
|
||||
QqbotPluginRegistryService: class QqbotPluginRegistryService {},
|
||||
}));
|
||||
|
||||
import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/qqbot-core.contract';
|
||||
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
|
||||
|
||||
describe('QQBot core status contract', () => {
|
||||
const schema = readRefactorV3SqlSchema();
|
||||
|
||||
it('keeps OneBot connection, container, WebUI and QQ login status as separate account fields', () => {
|
||||
expect(QQBOT_CORE_DOMAIN_CONTRACT.status).toEqual({
|
||||
accountTable: 'qqbot_account',
|
||||
oneBotField: 'onebot_status',
|
||||
containerField: 'container_status',
|
||||
webuiField: 'webui_status',
|
||||
qqLoginField: 'qq_login_status',
|
||||
lastErrorField: 'last_error',
|
||||
connectionSessionTable: 'qqbot_connection_session',
|
||||
sessionStatusField: 'status',
|
||||
closeReasonField: 'close_reason',
|
||||
});
|
||||
|
||||
schema.expectTableColumns('qqbot_account', [
|
||||
'id',
|
||||
'self_id',
|
||||
'onebot_status',
|
||||
'container_status',
|
||||
'webui_status',
|
||||
'qq_login_status',
|
||||
'last_error',
|
||||
]);
|
||||
schema.expectTableColumns('qqbot_connection_session', [
|
||||
'id',
|
||||
'account_id',
|
||||
'session_key',
|
||||
'status',
|
||||
'connected_at',
|
||||
'disconnected_at',
|
||||
'close_reason',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
jest.mock('../../../src/qqbot/qqbot.module', () => ({
|
||||
QqbotModule: class QqbotModule {},
|
||||
jest.mock('../../../src/modules/qqbot/core/qqbot-core.module', () => ({
|
||||
QqbotCoreModule: class QqbotCoreModule {},
|
||||
}));
|
||||
|
||||
import { MODULE_METADATA } from '@nestjs/common/constants';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user