diff --git a/docs/refactor-v3/api-admin-contract-matrix.md b/docs/refactor-v3/api-admin-contract-matrix.md new file mode 100644 index 0000000..cf0c1b2 --- /dev/null +++ b/docs/refactor-v3/api-admin-contract-matrix.md @@ -0,0 +1,12 @@ +# 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:/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 | +| 4 | `/qqbot/account/*`, `/qqbot/command/*`, `/qqbot/rule/*`, `/qqbot/message/*`, `/qqbot/send/*` | QQBot core pages | `/qqbot/command/test` local request | +| 5 | `/qqbot/plugin-platform/*` | Plugin upload/install/enable/config/health pages | local test plugin install and enable | +| 6 | plugin operations exposed through QQBot command/event routing | Existing plugin pages and operation views | BangDream, FF14, FFLogs, Repeater smoke | +| 7 | `/qqbot/napcat/*`, login SSE events | NapCat device/login progress pages | simulated captcha and new-device session | +| 8 | public deployed URLs | deployed Admin | online smoke bundle | diff --git a/docs/refactor-v3/breaking-changes.md b/docs/refactor-v3/breaking-changes.md new file mode 100644 index 0000000..60f1910 --- /dev/null +++ b/docs/refactor-v3/breaking-changes.md @@ -0,0 +1,20 @@ +# Refactor V3 Breaking Changes + +## 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 | + +## Protected Behavior + +- Admin login succeeds. +- Admin menu loads. +- Vben success/error wrappers remain stable for Admin APIs. +- `/health/runtime` remains plain JSON. +- Blog public list/detail remain available. +- QQBot command test remains available. +- QQBot status keeps OneBot, container, WebUI, and QQ login state separate. +- NapCat cleanup failure blocks success. diff --git a/docs/refactor-v3/rebuild-runbook.md b/docs/refactor-v3/rebuild-runbook.md new file mode 100644 index 0000000..600347d --- /dev/null +++ b/docs/refactor-v3/rebuild-runbook.md @@ -0,0 +1,31 @@ +# Refactor V3 Rebuild Runbook + +## Local Dry Run + +1. Create an empty local database dedicated to refactor V3. +2. Apply `sql/refactor-v3/00-full-schema.sql`. +3. Apply `sql/refactor-v3/01-seed-core.sql`. +4. Run `sql/refactor-v3/99-verify.sql`. +5. Start API against the dry-run database. +6. Run `scripts/refactor-v3/local-smoke.ps1`. + +## Online Backup + +1. Confirm the exact API image tag and current database name. +2. Run `scripts/refactor-v3/db-backup-online.ps1`. +3. Record backup path, timestamp, source database, and restore command. + +## Online Rebuild + +1. Stop or limit API write traffic. +2. Apply full schema and seed scripts. +3. Run verify SQL. +4. Deploy API/Admin image versions bound to this schema. +5. Run online smoke bundle. + +## Rollback + +1. Stop write traffic. +2. Restore the recorded backup or previous schema bundle. +3. Roll back API/Admin images. +4. Re-run smoke for the restored version. diff --git a/docs/refactor-v3/schema-map.md b/docs/refactor-v3/schema-map.md new file mode 100644 index 0000000..a8c652d --- /dev/null +++ b/docs/refactor-v3/schema-map.md @@ -0,0 +1,25 @@ +# Refactor V3 Schema Map + +## Global Rules + +- Primary keys are Snowflake `BIGINT`; API/Admin boundary treats IDs as strings. +- Table names use lower snake case. +- Queryable values are structured columns, not JSON-only fields. +- Event/log tables are append-only and have retention strategy. +- New schema is full initialization SQL, not historical `ALTER TABLE` patches. + +## 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. | +| WordPress Mirror | `wordpress_site`, `wordpress_auth_session`, `wordpress_remote_post`, `wordpress_remote_term`, `wordpress_sync_job`, `wordpress_sync_mapping` | Batch 3 | Remote state separate from local Blog content. | +| 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. | +| 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. | 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 b6183b8..d2e96b2 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 @@ -18,8 +18,8 @@ Every batch must leave the product in a locally verifiable state. Batch 8 is the ## Current State Evidence -- API repo: `D:\MyFiles\KT\Node\kt-template-online-api`, branch `main`, clean at plan start, package manager `pnpm@9.15.9`, no `.node-version`, no `engines`. -- Admin repo: `D:\MyFiles\KT\Vue\kt-template-admin`, branch `main`, package manager `pnpm@10.28.2`, `.node-version=22.22.0`, `engines.node >=20.19.0`, `engines.pnpm >=10.0.0`. +- API repo: `D:\MyFiles\KT\Node\kt-template-online-api`, implementation branch `dev-api-full-refactor-v3`, package manager `pnpm@9.15.9`, no `.node-version`, no `engines`. +- Admin repo: `D:\MyFiles\KT\Vue\kt-template-admin`, implementation branch `dev-admin-full-refactor-v3`, package manager `pnpm@10.28.2`, `.node-version=22.22.0`, `engines.node >=20.19.0`, `engines.pnpm >=10.0.0`. - Admin repo has three old dirty files authorized by the user to discard during implementation preparation: - `README.md` - `apps/web-antdv-next/src/api/qqbot/index.ts` @@ -106,7 +106,7 @@ Every batch must leave the product in a locally verifiable state. Batch 8 is the **Files:** - No file edits in this task. -- [ ] **Step 1: Verify API worktree is clean** +- [x] **Step 1: Verify API worktree is clean** Run: @@ -120,21 +120,21 @@ Expected: ## main...origin/main [ahead 3] ``` -- [ ] **Step 2: Create API implementation branch** +- [x] **Step 2: Create API implementation branch** Run: ```powershell -git -C D:\MyFiles\KT\Node\kt-template-online-api switch -c dev/api-full-refactor-v3 +git -C D:\MyFiles\KT\Node\kt-template-online-api switch -c dev-api-full-refactor-v3 ``` Expected: ```text -Switched to a new branch 'dev/api-full-refactor-v3' +Switched to a new branch 'dev-api-full-refactor-v3' ``` -- [ ] **Step 3: Confirm branch** +- [x] **Step 3: Confirm branch** Run: @@ -145,9 +145,11 @@ git -C D:\MyFiles\KT\Node\kt-template-online-api branch --show-current Expected: ```text -dev/api-full-refactor-v3 +dev-api-full-refactor-v3 ``` +Actual note: `dev/api-full-refactor-v3` could not be created because the API repository already has a local `dev` branch, so Git cannot create a nested ref under `refs/heads/dev`. The implementation branch is `dev-api-full-refactor-v3` to preserve the existing `dev` branch. + ### Task 0.2: Clean authorized Admin old artifacts and prepare branch **Files:** @@ -156,7 +158,7 @@ dev/api-full-refactor-v3 - `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\index.ts` - `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\account\list.tsx` -- [ ] **Step 1: Verify Admin dirty set is exactly authorized** +- [x] **Step 1: Verify Admin dirty set is exactly authorized** Run: @@ -172,11 +174,11 @@ Expected: M apps/web-antdv-next/src/views/qqbot/account/list.tsx ``` -- [ ] **Step 2: Stop if any extra path appears** +- [x] **Step 2: Stop if any extra path appears** If the output contains another path, stop and ask the user before cleanup. -- [ ] **Step 3: Restore authorized old files** +- [x] **Step 3: Restore authorized old files** Run: @@ -186,7 +188,7 @@ git -C D:\MyFiles\KT\Vue\kt-template-admin restore -- README.md apps/web-antdv-n Expected: command exits `0`. -- [ ] **Step 4: Confirm Admin worktree is clean** +- [x] **Step 4: Confirm Admin worktree is clean** Run: @@ -200,18 +202,18 @@ Expected: ## main...origin/main ``` -- [ ] **Step 5: Create Admin implementation branch** +- [x] **Step 5: Create Admin implementation branch** Run: ```powershell -git -C D:\MyFiles\KT\Vue\kt-template-admin switch -c dev/admin-full-refactor-v3 +git -C D:\MyFiles\KT\Vue\kt-template-admin switch -c dev-admin-full-refactor-v3 ``` Expected: ```text -Switched to a new branch 'dev/admin-full-refactor-v3' +Switched to a new branch 'dev-admin-full-refactor-v3' ``` ### Task 0.3: Create migration control documents @@ -222,7 +224,7 @@ Switched to a new branch 'dev/admin-full-refactor-v3' - Create: `D:\MyFiles\KT\Node\kt-template-online-api\docs\refactor-v3\breaking-changes.md` - Create: `D:\MyFiles\KT\Node\kt-template-online-api\docs\refactor-v3\rebuild-runbook.md` -- [ ] **Step 1: Add `schema-map.md`** +- [x] **Step 1: Add `schema-map.md`** Use this content: @@ -254,7 +256,7 @@ Use this content: | Plugin-Owned Data | plugin namespace tables | Batch 6 | Table names start with registered plugin namespace. | ``` -- [ ] **Step 2: Add `api-admin-contract-matrix.md`** +- [x] **Step 2: Add `api-admin-contract-matrix.md`** Use this content: @@ -273,7 +275,7 @@ Use this content: | 8 | public deployed URLs | deployed Admin | online smoke bundle | ``` -- [ ] **Step 3: Add `breaking-changes.md`** +- [x] **Step 3: Add `breaking-changes.md`** Use this content: @@ -300,7 +302,7 @@ Use this content: - NapCat cleanup failure blocks success. ``` -- [ ] **Step 4: Add `rebuild-runbook.md`** +- [x] **Step 4: Add `rebuild-runbook.md`** Use this content: @@ -338,7 +340,7 @@ Use this content: 4. Re-run smoke for the restored version. ``` -- [ ] **Step 5: Verify docs exist** +- [x] **Step 5: Verify docs exist** Run: @@ -366,7 +368,7 @@ True - Create: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\99-verify.sql` - Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\refactor-v3\schema-map.spec.ts` -- [ ] **Step 1: Write failing schema test** +- [x] **Step 1: Write failing schema test** Create `test/refactor-v3/schema-map.spec.ts`: @@ -425,15 +427,17 @@ describe('refactor v3 schema skeleton', () => { ); expect(seed).toContain('INSERT INTO admin_user'); + expect(seed).toContain('INSERT INTO qqbot_command'); expect(seed).toContain('INSERT INTO qqbot_plugin'); expect(verify).toContain('admin_user'); + expect(verify).toContain('qqbot_command'); expect(verify).toContain('qqbot_plugin'); expect(verify).toContain('napcat_device_identity'); }); }); ``` -- [ ] **Step 2: Run RED check** +- [x] **Step 2: Run RED check** Run: @@ -443,11 +447,11 @@ pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --run Expected: FAIL because `sql/refactor-v3/00-full-schema.sql` does not exist. -- [ ] **Step 3: Add minimal SQL skeleton** +- [x] **Step 3: Add minimal SQL skeleton** Create the three SQL files with table declarations, seed markers, and verify markers. Use full table definitions before ending Batch 0; the initial minimal version only makes the RED check meaningful. -- [ ] **Step 4: Run GREEN check** +- [x] **Step 4: Run GREEN check** Run: @@ -465,7 +469,7 @@ Expected: PASS. - Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-restore-online.ps1` - Create: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\local-smoke.ps1` -- [ ] **Step 1: Add dry-run script** +- [x] **Step 1: Add dry-run script** Create `db-dry-run.ps1`: @@ -478,18 +482,31 @@ param( ) $ErrorActionPreference = "Stop" +if ($Database -notmatch '^[A-Za-z0-9_]+$') { + throw "Database must match ^[A-Za-z0-9_]+$" +} + $root = Resolve-Path (Join-Path $PSScriptRoot "..\..") $schema = Join-Path $root "sql\refactor-v3\00-full-schema.sql" $seed = Join-Path $root "sql\refactor-v3\01-seed-core.sql" $verify = Join-Path $root "sql\refactor-v3\99-verify.sql" +function Invoke-MysqlSource { + param( + [Parameter(Mandatory = $true)][string]$Path + ) + + $sourcePath = (Resolve-Path -LiteralPath $Path).Path.Replace("\", "/") + mysql -h $HostName -P $Port -u $User $Database --execute="source $sourcePath" +} + mysql -h $HostName -P $Port -u $User -e "DROP DATABASE IF EXISTS ``$Database``; CREATE DATABASE ``$Database`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" -mysql -h $HostName -P $Port -u $User $Database < $schema -mysql -h $HostName -P $Port -u $User $Database < $seed -mysql -h $HostName -P $Port -u $User $Database < $verify +Invoke-MysqlSource -Path $schema +Invoke-MysqlSource -Path $seed +Invoke-MysqlSource -Path $verify ``` -- [ ] **Step 2: Add online backup script** +- [x] **Step 2: Add online backup script** Create `db-backup-online.ps1`: @@ -500,13 +517,17 @@ param( ) $ErrorActionPreference = "Stop" +if ($Database -notmatch '^[A-Za-z0-9_]+$') { + throw "Database must match ^[A-Za-z0-9_]+$" +} + $stamp = Get-Date -Format "yyyyMMdd-HHmmss" $target = Join-Path $OutputDirectory "$Database-refactor-v3-$stamp.sql" -mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers $Database | Out-File -Encoding utf8 $target +mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers --default-character-set=utf8mb4 "--result-file=$target" $Database Write-Output $target ``` -- [ ] **Step 3: Add restore script** +- [x] **Step 3: Add restore script** Create `db-restore-online.ps1`: @@ -517,11 +538,19 @@ param( ) $ErrorActionPreference = "Stop" +if ($Database -notmatch '^[A-Za-z0-9_]+$') { + throw "Database must match ^[A-Za-z0-9_]+$" +} +if (-not (Test-Path -LiteralPath $BackupFile -PathType Leaf)) { + throw "BackupFile does not exist" +} + +$sourcePath = (Resolve-Path -LiteralPath $BackupFile).Path.Replace("\", "/") mysql -e "DROP DATABASE IF EXISTS ``$Database``; CREATE DATABASE ``$Database`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" -mysql $Database < $BackupFile +mysql --default-character-set=utf8mb4 $Database --execute="source $sourcePath" ``` -- [ ] **Step 4: Add local smoke script** +- [x] **Step 4: Add local smoke script** Create `local-smoke.ps1`: @@ -538,13 +567,15 @@ if (-not $runtime.service) { Write-Output "runtime.service=$($runtime.service)" ``` -- [ ] **Step 5: Validate scripts parse** +- [x] **Step 5: Validate scripts parse** Run: ```powershell -powershell -NoProfile -Command "$null = [scriptblock]::Create((Get-Content -Raw D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-dry-run.ps1)); 'ok'" -powershell -NoProfile -Command "$null = [scriptblock]::Create((Get-Content -Raw D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\local-smoke.ps1)); 'ok'" +powershell -NoProfile -Command '$null = [scriptblock]::Create((Get-Content -Raw -LiteralPath "D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-dry-run.ps1")); "ok"' +powershell -NoProfile -Command '$null = [scriptblock]::Create((Get-Content -Raw -LiteralPath "D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\local-smoke.ps1")); "ok"' +powershell -NoProfile -Command '$null = [scriptblock]::Create((Get-Content -Raw -LiteralPath "D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-backup-online.ps1")); "ok"' +powershell -NoProfile -Command '$null = [scriptblock]::Create((Get-Content -Raw -LiteralPath "D:\MyFiles\KT\Node\kt-template-online-api\scripts\refactor-v3\db-restore-online.ps1")); "ok"' ``` Expected: @@ -552,6 +583,8 @@ Expected: ```text ok ok +ok +ok ``` ### Task 0.6: Batch 0 verification and commits @@ -1373,8 +1406,8 @@ After confirmation, push both repos. Run: ```powershell -git -C D:\MyFiles\KT\Node\kt-template-online-api push -u origin dev/api-full-refactor-v3 -git -C D:\MyFiles\KT\Vue\kt-template-admin push -u origin dev/admin-full-refactor-v3 +git -C D:\MyFiles\KT\Node\kt-template-online-api push -u origin dev-api-full-refactor-v3 +git -C D:\MyFiles\KT\Vue\kt-template-admin push -u origin dev-admin-full-refactor-v3 ``` Then collect Jenkins/K8s evidence: diff --git a/scripts/refactor-v3/db-backup-online.ps1 b/scripts/refactor-v3/db-backup-online.ps1 new file mode 100644 index 0000000..bfd748a --- /dev/null +++ b/scripts/refactor-v3/db-backup-online.ps1 @@ -0,0 +1,14 @@ +param( + [Parameter(Mandatory = $true)][string]$Database, + [Parameter(Mandatory = $true)][string]$OutputDirectory +) + +$ErrorActionPreference = "Stop" +if ($Database -notmatch '^[A-Za-z0-9_]+$') { + throw "Database must match ^[A-Za-z0-9_]+$" +} + +$stamp = Get-Date -Format "yyyyMMdd-HHmmss" +$target = Join-Path $OutputDirectory "$Database-refactor-v3-$stamp.sql" +mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers --default-character-set=utf8mb4 "--result-file=$target" $Database +Write-Output $target diff --git a/scripts/refactor-v3/db-dry-run.ps1 b/scripts/refactor-v3/db-dry-run.ps1 new file mode 100644 index 0000000..2a5e9ec --- /dev/null +++ b/scripts/refactor-v3/db-dry-run.ps1 @@ -0,0 +1,30 @@ +param( + [Parameter(Mandatory = $true)][string]$Database, + [string]$HostName = "127.0.0.1", + [int]$Port = 3306, + [string]$User = "root" +) + +$ErrorActionPreference = "Stop" +if ($Database -notmatch '^[A-Za-z0-9_]+$') { + throw "Database must match ^[A-Za-z0-9_]+$" +} + +$root = Resolve-Path (Join-Path $PSScriptRoot "..\..") +$schema = Join-Path $root "sql\refactor-v3\00-full-schema.sql" +$seed = Join-Path $root "sql\refactor-v3\01-seed-core.sql" +$verify = Join-Path $root "sql\refactor-v3\99-verify.sql" + +function Invoke-MysqlSource { + param( + [Parameter(Mandatory = $true)][string]$Path + ) + + $sourcePath = (Resolve-Path -LiteralPath $Path).Path.Replace("\", "/") + mysql -h $HostName -P $Port -u $User $Database --execute="source $sourcePath" +} + +mysql -h $HostName -P $Port -u $User -e "DROP DATABASE IF EXISTS ``$Database``; CREATE DATABASE ``$Database`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +Invoke-MysqlSource -Path $schema +Invoke-MysqlSource -Path $seed +Invoke-MysqlSource -Path $verify diff --git a/scripts/refactor-v3/db-restore-online.ps1 b/scripts/refactor-v3/db-restore-online.ps1 new file mode 100644 index 0000000..1e2aaf6 --- /dev/null +++ b/scripts/refactor-v3/db-restore-online.ps1 @@ -0,0 +1,16 @@ +param( + [Parameter(Mandatory = $true)][string]$Database, + [Parameter(Mandatory = $true)][string]$BackupFile +) + +$ErrorActionPreference = "Stop" +if ($Database -notmatch '^[A-Za-z0-9_]+$') { + throw "Database must match ^[A-Za-z0-9_]+$" +} +if (-not (Test-Path -LiteralPath $BackupFile -PathType Leaf)) { + throw "BackupFile does not exist" +} + +$sourcePath = (Resolve-Path -LiteralPath $BackupFile).Path.Replace("\", "/") +mysql -e "DROP DATABASE IF EXISTS ``$Database``; CREATE DATABASE ``$Database`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +mysql --default-character-set=utf8mb4 $Database --execute="source $sourcePath" diff --git a/scripts/refactor-v3/local-smoke.ps1 b/scripts/refactor-v3/local-smoke.ps1 new file mode 100644 index 0000000..d4f14c9 --- /dev/null +++ b/scripts/refactor-v3/local-smoke.ps1 @@ -0,0 +1,10 @@ +param( + [string]$BaseUrl = "http://127.0.0.1:5320" +) + +$ErrorActionPreference = "Stop" +$runtime = Invoke-RestMethod -Method Get -Uri "$BaseUrl/health/runtime" -TimeoutSec 10 +if (-not $runtime.service) { + throw "Runtime health response did not include service" +} +Write-Output "runtime.service=$($runtime.service)" diff --git a/sql/refactor-v3/00-full-schema.sql b/sql/refactor-v3/00-full-schema.sql new file mode 100644 index 0000000..d6e6076 --- /dev/null +++ b/sql/refactor-v3/00-full-schema.sql @@ -0,0 +1,677 @@ +CREATE TABLE IF NOT EXISTS admin_user ( + id BIGINT NOT NULL PRIMARY KEY, + username VARCHAR(64) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + nickname VARCHAR(64) NOT NULL, + status VARCHAR(32) 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_admin_user_username (username) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_role ( + id BIGINT NOT NULL PRIMARY KEY, + role_key VARCHAR(64) NOT NULL, + role_name VARCHAR(64) NOT NULL, + status VARCHAR(32) 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_admin_role_key (role_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_permission ( + id BIGINT NOT NULL PRIMARY KEY, + permission_key VARCHAR(128) NOT NULL, + permission_name VARCHAR(128) NOT NULL, + module_key VARCHAR(64) 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_admin_permission_key (permission_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_menu ( + id BIGINT NOT NULL PRIMARY KEY, + parent_id BIGINT NULL, + menu_key VARCHAR(128) NOT NULL, + title VARCHAR(128) NOT NULL, + path VARCHAR(255) NOT NULL, + component VARCHAR(255) NULL, + sort_no INT NOT NULL DEFAULT 0, + status VARCHAR(32) 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_admin_menu_key (menu_key), + KEY idx_admin_menu_parent (parent_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_department ( + id BIGINT NOT NULL PRIMARY KEY, + parent_id BIGINT NULL, + dept_name VARCHAR(128) NOT NULL, + sort_no INT NOT NULL DEFAULT 0, + status VARCHAR(32) NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_admin_department_parent (parent_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_user_role ( + id BIGINT NOT NULL PRIMARY KEY, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_admin_user_role (user_id, role_id), + KEY idx_admin_user_role_role (role_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_role_permission ( + id BIGINT NOT NULL PRIMARY KEY, + role_id BIGINT NOT NULL, + permission_id BIGINT NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_admin_role_permission (role_id, permission_id), + KEY idx_admin_role_permission_permission (permission_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS admin_role_menu ( + id BIGINT NOT NULL PRIMARY KEY, + role_id BIGINT NOT NULL, + menu_id BIGINT NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_admin_role_menu (role_id, menu_id), + KEY idx_admin_role_menu_menu (menu_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS platform_dict_group ( + id BIGINT NOT NULL PRIMARY KEY, + group_key VARCHAR(128) NOT NULL, + group_name VARCHAR(128) NOT NULL, + status VARCHAR(32) 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_platform_dict_group_key (group_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS platform_dict_item ( + id BIGINT NOT NULL PRIMARY KEY, + group_id BIGINT NOT NULL, + item_key VARCHAR(128) NOT NULL, + item_label VARCHAR(128) NOT NULL, + item_value VARCHAR(255) NOT NULL, + sort_no INT NOT NULL DEFAULT 0, + status VARCHAR(32) 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_platform_dict_item (group_id, item_key), + KEY idx_platform_dict_item_group (group_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS platform_component_template ( + id BIGINT NOT NULL PRIMARY KEY, + template_key VARCHAR(128) NOT NULL, + template_name VARCHAR(128) NOT NULL, + schema_json JSON NULL, + status VARCHAR(32) 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_platform_component_template_key (template_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS platform_setting ( + id BIGINT NOT NULL PRIMARY KEY, + setting_key VARCHAR(128) NOT NULL, + setting_value TEXT NULL, + value_type VARCHAR(32) 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_platform_setting_key (setting_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS blog_post ( + id BIGINT NOT NULL PRIMARY KEY, + slug VARCHAR(255) NOT NULL, + title VARCHAR(255) NOT NULL, + summary TEXT NULL, + content_markdown LONGTEXT NULL, + content_html LONGTEXT NULL, + status VARCHAR(32) NOT NULL, + publish_time 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_blog_post_slug (slug), + KEY idx_blog_post_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS blog_taxonomy ( + id BIGINT NOT NULL PRIMARY KEY, + taxonomy_key VARCHAR(64) NOT NULL, + taxonomy_name VARCHAR(128) 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_blog_taxonomy_key (taxonomy_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS blog_term ( + id BIGINT NOT NULL PRIMARY KEY, + taxonomy_id BIGINT NOT NULL, + slug VARCHAR(255) NOT NULL, + term_name VARCHAR(128) 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_blog_term_slug (taxonomy_id, slug), + KEY idx_blog_term_taxonomy (taxonomy_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS blog_post_term ( + id BIGINT NOT NULL PRIMARY KEY, + post_id BIGINT NOT NULL, + term_id BIGINT NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_blog_post_term (post_id, term_id), + KEY idx_blog_post_term_term (term_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS blog_theme_profile ( + id BIGINT NOT NULL PRIMARY KEY, + profile_key VARCHAR(128) NOT NULL, + profile_name VARCHAR(128) NOT NULL, + config_json JSON NOT NULL, + enabled 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_blog_theme_profile_key (profile_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS blog_import_job ( + id BIGINT NOT NULL PRIMARY KEY, + source_key VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + started_at DATETIME NULL, + finished_at DATETIME NULL, + summary_json JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_blog_import_job_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wordpress_site ( + id BIGINT NOT NULL PRIMARY KEY, + site_key VARCHAR(128) NOT NULL, + base_url VARCHAR(512) NOT NULL, + status VARCHAR(32) 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_wordpress_site_key (site_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wordpress_auth_session ( + id BIGINT NOT NULL PRIMARY KEY, + site_id BIGINT NOT NULL, + status VARCHAR(32) NOT NULL, + expires_at DATETIME NULL, + safe_summary JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_wordpress_auth_session_site (site_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wordpress_remote_post ( + id BIGINT NOT NULL PRIMARY KEY, + site_id BIGINT NOT NULL, + remote_id VARCHAR(64) NOT NULL, + slug VARCHAR(255) NOT NULL, + status VARCHAR(32) NOT NULL, + raw_payload JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_wordpress_remote_post (site_id, remote_id), + KEY idx_wordpress_remote_post_slug (slug) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wordpress_remote_term ( + id BIGINT NOT NULL PRIMARY KEY, + site_id BIGINT NOT NULL, + remote_id VARCHAR(64) NOT NULL, + taxonomy_key VARCHAR(64) NOT NULL, + slug VARCHAR(255) NOT NULL, + raw_payload JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_wordpress_remote_term (site_id, taxonomy_key, remote_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wordpress_sync_job ( + id BIGINT NOT NULL PRIMARY KEY, + site_id BIGINT NOT NULL, + job_type VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + started_at DATETIME NULL, + finished_at DATETIME NULL, + summary_json JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_wordpress_sync_job_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wordpress_sync_mapping ( + id BIGINT NOT NULL PRIMARY KEY, + site_id BIGINT NOT NULL, + remote_type VARCHAR(64) NOT NULL, + remote_id VARCHAR(64) NOT NULL, + local_type VARCHAR(64) NOT NULL, + local_id BIGINT 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_wordpress_sync_mapping (site_id, remote_type, remote_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS asset_bucket ( + id BIGINT NOT NULL PRIMARY KEY, + bucket_key VARCHAR(128) NOT NULL, + bucket_name VARCHAR(128) NOT NULL, + provider VARCHAR(64) NOT NULL, + status VARCHAR(32) 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_asset_bucket_key (bucket_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS asset_object ( + id BIGINT NOT NULL PRIMARY KEY, + bucket_id BIGINT NOT NULL, + object_key VARCHAR(512) NOT NULL, + source_module VARCHAR(64) NOT NULL, + mime_type VARCHAR(128) NULL, + size_bytes BIGINT NULL, + status VARCHAR(32) NOT NULL, + metadata_json JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_asset_object_key (bucket_id, object_key), + KEY idx_asset_object_source (source_module) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS asset_reference ( + id BIGINT NOT NULL PRIMARY KEY, + object_id BIGINT NOT NULL, + owner_module VARCHAR(64) NOT NULL, + owner_type VARCHAR(64) NOT NULL, + owner_id BIGINT NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_asset_reference (object_id, owner_module, owner_type, owner_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS asset_access_grant ( + id BIGINT NOT NULL PRIMARY KEY, + object_id BIGINT NOT NULL, + grant_token VARCHAR(128) NOT NULL, + expires_at DATETIME NOT NULL, + status VARCHAR(32) NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_asset_access_grant_token (grant_token), + KEY idx_asset_access_grant_object (object_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS system_notice ( + id BIGINT NOT NULL PRIMARY KEY, + notice_key VARCHAR(128) NOT NULL, + title VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + level VARCHAR(32) NOT NULL, + status VARCHAR(32) NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_system_notice_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS system_event ( + id BIGINT NOT NULL PRIMARY KEY, + event_key VARCHAR(128) NOT NULL, + source_module VARCHAR(64) NOT NULL, + event_type VARCHAR(64) NOT NULL, + payload_json JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_system_event_key (event_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS system_event_dedupe ( + id BIGINT NOT NULL PRIMARY KEY, + dedupe_key VARCHAR(255) NOT NULL, + first_seen_at DATETIME NOT NULL, + last_seen_at DATETIME NOT NULL, + hit_count INT NOT NULL DEFAULT 1, + UNIQUE KEY uk_system_event_dedupe_key (dedupe_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS system_event_delivery ( + id BIGINT NOT NULL PRIMARY KEY, + event_id BIGINT NOT NULL, + delivery_target VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + attempt_count INT NOT NULL DEFAULT 0, + last_error TEXT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_system_event_delivery_event (event_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS runtime_evidence_index ( + id BIGINT NOT NULL PRIMARY KEY, + evidence_key VARCHAR(128) NOT NULL, + evidence_type VARCHAR(64) NOT NULL, + artifact_path VARCHAR(512) NOT NULL, + safe_summary JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_runtime_evidence_key (evidence_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_account ( + id BIGINT NOT NULL PRIMARY KEY, + self_id VARCHAR(64) NOT NULL, + 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, + last_error TEXT 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_account_self_id (self_id) +) 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, + session_key VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + connected_at DATETIME NULL, + disconnected_at DATETIME NULL, + close_reason TEXT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_connection_session_key (session_key), + KEY idx_qqbot_connection_session_account (account_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_capability_binding ( + id BIGINT NOT NULL PRIMARY KEY, + account_id BIGINT NOT NULL, + capability_key VARCHAR(128) NOT NULL, + enabled TINYINT NOT NULL DEFAULT 1, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_capability_binding (account_id, capability_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_permission_policy ( + id BIGINT NOT NULL PRIMARY KEY, + policy_key VARCHAR(128) NOT NULL, + scope_type VARCHAR(64) NOT NULL, + scope_value VARCHAR(128) NOT NULL, + effect VARCHAR(32) 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_permission_policy (policy_key, scope_type, scope_value) +) 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, + plugin_key VARCHAR(128) NULL, + enabled TINYINT NOT NULL DEFAULT 1, + cooldown_seconds INT 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), + KEY idx_qqbot_command_operation (operation_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_command_alias ( + id BIGINT NOT NULL PRIMARY KEY, + command_id BIGINT NOT NULL, + alias_text VARCHAR(128) NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + 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_rule ( + id BIGINT NOT NULL PRIMARY KEY, + rule_key VARCHAR(128) NOT NULL, + account_id BIGINT NULL, + command_id BIGINT NULL, + matcher_json JSON NOT NULL, + action_json JSON NOT NULL, + enabled TINYINT NOT NULL DEFAULT 1, + 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) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +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, + display_name VARCHAR(255) 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_conversation (account_id, conversation_type, conversation_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_message ( + id BIGINT NOT NULL PRIMARY KEY, + account_id BIGINT NOT NULL, + conversation_id BIGINT NULL, + message_id VARCHAR(128) NOT NULL, + direction VARCHAR(32) NOT NULL, + message_type VARCHAR(64) NOT NULL, + summary TEXT NULL, + raw_payload JSON NULL, + create_time DATETIME NOT NULL DEFAULT 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, + conversation_id BIGINT NULL, + task_key VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + payload_json JSON NOT NULL, + reserved_at DATETIME NULL, + sent_at DATETIME NULL, + last_error TEXT 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_send_task_key (task_key), + KEY idx_qqbot_send_task_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_send_log ( + id BIGINT NOT NULL PRIMARY KEY, + task_id BIGINT NULL, + account_id BIGINT NOT NULL, + status VARCHAR(32) NOT NULL, + safe_summary JSON NULL, + error_message TEXT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_qqbot_send_log_task (task_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_dedupe_event ( + id BIGINT NOT NULL PRIMARY KEY, + dedupe_key VARCHAR(255) NOT NULL, + account_id BIGINT NULL, + expires_at DATETIME NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_dedupe_event_key (dedupe_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, + plugin_name VARCHAR(128) NOT NULL, + description TEXT NULL, + status VARCHAR(32) 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_plugin_key (plugin_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_version ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + version VARCHAR(64) NOT NULL, + package_hash VARCHAR(128) NOT NULL, + manifest_json JSON NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_plugin_version (plugin_id, version) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_installation ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + version_id BIGINT NOT NULL, + status VARCHAR(32) NOT NULL, + runtime_status VARCHAR(32) NOT NULL, + installed_path VARCHAR(512) NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_qqbot_plugin_installation_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_operation ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + operation_key VARCHAR(128) NOT NULL, + operation_name VARCHAR(128) NOT NULL, + handler_name VARCHAR(128) NOT NULL, + enabled TINYINT NOT NULL DEFAULT 1, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_plugin_operation (plugin_id, operation_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_event_handler ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + event_key VARCHAR(128) NOT NULL, + handler_name VARCHAR(128) NOT NULL, + enabled TINYINT NOT NULL DEFAULT 1, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_plugin_event_handler (plugin_id, event_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_account_binding ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + account_id BIGINT NOT NULL, + enabled TINYINT NOT NULL DEFAULT 1, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_plugin_account_binding (plugin_id, account_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_config ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + config_key VARCHAR(128) NOT NULL, + config_value JSON 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_plugin_config (plugin_id, config_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_asset ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + asset_key VARCHAR(255) NOT NULL, + asset_path VARCHAR(512) NOT NULL, + content_hash VARCHAR(128) NOT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_qqbot_plugin_asset (plugin_id, asset_key) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS qqbot_plugin_runtime_event ( + id BIGINT NOT NULL PRIMARY KEY, + plugin_id BIGINT NOT NULL, + installation_id BIGINT NULL, + event_type VARCHAR(64) NOT NULL, + level VARCHAR(32) NOT NULL, + safe_summary JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_qqbot_plugin_runtime_event_plugin (plugin_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS napcat_container ( + id BIGINT NOT NULL PRIMARY KEY, + account_id BIGINT NOT NULL, + container_name VARCHAR(128) NOT NULL, + image_name VARCHAR(255) NOT NULL, + status VARCHAR(32) 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_napcat_container_name (container_name) +) 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, + container_id BIGINT NULL, + data_dir VARCHAR(512) NOT NULL, + hostname VARCHAR(128) NOT NULL, + machine_id_path VARCHAR(512) NOT NULL, + mac_address VARCHAR(64) NOT NULL, + verification_status VARCHAR(32) NOT NULL, + last_login_evidence JSON NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_napcat_device_identity_account (account_id), + KEY idx_napcat_device_identity_container (container_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS napcat_account_binding ( + id BIGINT NOT NULL PRIMARY KEY, + account_id BIGINT NOT NULL, + container_id BIGINT NOT NULL, + device_identity_id BIGINT NOT NULL, + status VARCHAR(32) 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_napcat_account_binding_account (account_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS napcat_login_session ( + id BIGINT NOT NULL PRIMARY KEY, + account_id BIGINT NOT NULL, + session_key VARCHAR(128) NOT NULL, + login_stage VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + progress_message VARCHAR(255) 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_napcat_login_session_key (session_key), + KEY idx_napcat_login_session_account (account_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS napcat_login_challenge ( + id BIGINT NOT NULL PRIMARY KEY, + session_id BIGINT NOT NULL, + challenge_type VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + challenge_url TEXT NULL, + challenge_payload JSON NULL, + resolved_at DATETIME NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_napcat_login_challenge_session (session_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS napcat_runtime_cleanup ( + id BIGINT NOT NULL PRIMARY KEY, + session_id BIGINT NOT NULL, + cleanup_type VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + error_message TEXT NULL, + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_napcat_runtime_cleanup_session (session_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/sql/refactor-v3/01-seed-core.sql b/sql/refactor-v3/01-seed-core.sql new file mode 100644 index 0000000..af057fc --- /dev/null +++ b/sql/refactor-v3/01-seed-core.sql @@ -0,0 +1,138 @@ +INSERT INTO admin_user ( + id, + username, + password_hash, + nickname, + status +) VALUES ( + 1000000000000000001, + 'admin', + '!disabled-refactor-v3-password-hash!', + 'Administrator', + 'password_unset' +) ON DUPLICATE KEY UPDATE + nickname = VALUES(nickname), + status = VALUES(status); + +INSERT INTO admin_role ( + id, + role_key, + role_name, + status +) VALUES ( + 1000000000000000002, + 'super_admin', + 'Super Administrator', + 'enabled' +) ON DUPLICATE KEY UPDATE + role_name = VALUES(role_name), + status = VALUES(status); + +INSERT INTO admin_user_role ( + id, + user_id, + role_id +) VALUES ( + 1000000000000000003, + 1000000000000000001, + 1000000000000000002 +) ON DUPLICATE KEY UPDATE + role_id = VALUES(role_id); + +INSERT INTO admin_menu ( + id, + parent_id, + menu_key, + title, + path, + component, + sort_no, + status +) VALUES ( + 1000000000000000004, + NULL, + 'dashboard', + 'Dashboard', + '/dashboard', + 'dashboard/workspace/index', + 10, + 'enabled' +) ON DUPLICATE KEY UPDATE + title = VALUES(title), + path = VALUES(path), + component = VALUES(component), + status = VALUES(status); + +INSERT INTO platform_setting ( + id, + setting_key, + setting_value, + value_type +) VALUES ( + 1000000000000000005, + 'schema.version', + 'refactor-v3', + 'string' +) ON DUPLICATE KEY UPDATE + setting_value = VALUES(setting_value), + value_type = VALUES(value_type); + +INSERT INTO qqbot_plugin ( + id, + plugin_key, + plugin_name, + description, + status +) VALUES + ( + 1000000000000000101, + 'bangdream', + 'BangDream', + 'Built-in BangDream command plugin metadata.', + 'installed' + ), + ( + 1000000000000000102, + 'ff14-market', + 'FF14 Market', + 'Built-in FF14 market command plugin metadata.', + 'installed' + ), + ( + 1000000000000000103, + 'fflogs', + 'FFLogs', + 'Built-in FFLogs command plugin metadata.', + 'installed' + ), + ( + 1000000000000000104, + 'repeater', + 'Repeater', + 'Built-in repeater event plugin metadata.', + 'installed' + ) +ON DUPLICATE KEY UPDATE + plugin_name = VALUES(plugin_name), + description = VALUES(description), + status = VALUES(status); + +INSERT INTO qqbot_command ( + id, + operation_key, + command_key, + plugin_key, + enabled, + cooldown_seconds +) VALUES ( + 1000000000000000201, + 'bangdream.song.search', + 'bangdream_song', + 'bangdream', + 1, + 2 +) ON DUPLICATE KEY UPDATE + operation_key = VALUES(operation_key), + plugin_key = VALUES(plugin_key), + enabled = VALUES(enabled), + cooldown_seconds = VALUES(cooldown_seconds); diff --git a/sql/refactor-v3/99-verify.sql b/sql/refactor-v3/99-verify.sql new file mode 100644 index 0000000..32c6708 --- /dev/null +++ b/sql/refactor-v3/99-verify.sql @@ -0,0 +1,53 @@ +SELECT 'admin_user' AS table_name, COUNT(*) AS row_count FROM admin_user; +SELECT 'admin_role' AS table_name, COUNT(*) AS row_count FROM admin_role; +SELECT 'admin_menu' AS table_name, COUNT(*) AS row_count FROM admin_menu; +SELECT 'platform_setting' AS table_name, COUNT(*) AS row_count FROM platform_setting; +SELECT 'qqbot_command' AS table_name, COUNT(*) AS row_count FROM qqbot_command; +SELECT 'qqbot_plugin' AS table_name, COUNT(*) AS row_count FROM qqbot_plugin; +SELECT 'napcat_device_identity' AS table_name, COUNT(*) AS row_count FROM napcat_device_identity; + +SELECT 'seed_admin_user' AS check_name, COUNT(*) AS matched_rows +FROM admin_user +WHERE username = 'admin' + AND status = 'password_unset'; + +SELECT 'seed_platform_schema_version' AS check_name, COUNT(*) AS matched_rows +FROM platform_setting +WHERE setting_key = 'schema.version' + AND setting_value = 'refactor-v3'; + +SELECT 'seed_qqbot_plugin_bangdream' AS check_name, COUNT(*) AS matched_rows +FROM qqbot_plugin +WHERE plugin_key = 'bangdream' + AND status = 'installed'; + +SELECT 'seed_qqbot_command_bangdream_song' AS check_name, COUNT(*) AS matched_rows +FROM qqbot_command +WHERE command_key = 'bangdream_song' + AND operation_key = 'bangdream.song.search' + AND plugin_key = 'bangdream' + AND enabled = 1; + +SELECT 'index_admin_user_username' AS check_name, COUNT(*) AS matched_rows +FROM information_schema.statistics +WHERE table_schema = DATABASE() + AND table_name = 'admin_user' + AND index_name = 'uk_admin_user_username'; + +SELECT 'index_platform_setting_key' AS check_name, COUNT(*) AS matched_rows +FROM information_schema.statistics +WHERE table_schema = DATABASE() + AND table_name = 'platform_setting' + AND index_name = 'uk_platform_setting_key'; + +SELECT 'index_qqbot_command_key' AS check_name, COUNT(*) AS matched_rows +FROM information_schema.statistics +WHERE table_schema = DATABASE() + AND table_name = 'qqbot_command' + AND index_name = 'uk_qqbot_command_key'; + +SELECT 'index_napcat_device_identity_account' AS check_name, COUNT(*) AS matched_rows +FROM information_schema.statistics +WHERE table_schema = DATABASE() + AND table_name = 'napcat_device_identity' + AND index_name = 'uk_napcat_device_identity_account'; diff --git a/test/refactor-v3/schema-map.spec.ts b/test/refactor-v3/schema-map.spec.ts new file mode 100644 index 0000000..e6b3e88 --- /dev/null +++ b/test/refactor-v3/schema-map.spec.ts @@ -0,0 +1,56 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +const root = join(__dirname, '..', '..'); + +const extractSchemaMapTables = () => { + const schemaMap = readFileSync( + join(root, 'docs/refactor-v3/schema-map.md'), + 'utf8', + ); + + const matches = schemaMap.match(/`[a-z][a-z0-9_]+`/g) || []; + + return Array.from( + new Set( + matches + .map((match) => match.slice(1, -1)) + .filter((tableName) => !tableName.endsWith('_*')), + ), + ).sort(); +}; + +describe('refactor v3 schema skeleton', () => { + it('declares every table listed in the schema map in the full schema file', () => { + const sql = readFileSync( + join(root, 'sql/refactor-v3/00-full-schema.sql'), + 'utf8', + ); + const requiredTables = extractSchemaMapTables(); + + expect(requiredTables.length).toBeGreaterThan(50); + + for (const table of requiredTables) { + expect(sql).toContain(`CREATE TABLE IF NOT EXISTS ${table}`); + } + }); + + it('declares core seed and verification scripts', () => { + const seed = readFileSync( + join(root, 'sql/refactor-v3/01-seed-core.sql'), + 'utf8', + ); + const verify = readFileSync( + join(root, 'sql/refactor-v3/99-verify.sql'), + 'utf8', + ); + + expect(seed).toContain('INSERT INTO admin_user'); + expect(seed).toContain('INSERT INTO qqbot_command'); + expect(seed).toContain('INSERT INTO qqbot_plugin'); + expect(verify).toContain('admin_user'); + expect(verify).toContain('qqbot_command'); + expect(verify).toContain('qqbot_plugin'); + expect(verify).toContain('napcat_device_identity'); + }); +});