Files
HIPCTF2/.kilo/plans/893.md
T

7.8 KiB

Implementation Plan: Admin Area General Settings and Categories 1.11

1. Architectural Reconnaissance

  • Codebase style & conventions: Node.js npm-workspace monorepo with strict TypeScript. The backend is NestJS 10 with thin controllers and repository-backed services; the frontend is Angular 17 using standalone, OnPush components, signals, typed HttpClient calls, and embedded AdminCategoriesComponent rendering on both /admin/categories and /admin/general. Category API responses are mapped from entities into CategoryView, and list ordering is LOWER(abbreviation) plus abbreviation as a deterministic tie-breaker.
  • Data Layer: TypeORM 0.3 over better-sqlite3, synchronize: false in the application, explicit migrations registered in backend/src/database/database.module.ts, and startup migration execution through DatabaseInitService.init() before listening. The persistent production database defaults to /data/hipctf/db.sqlite. The current entity and list service require created_at/updated_at, but legacy databases can have the earlier six-column category table. Although migration 1700000000200 attempts to repair timestamps and 1700000000300 defines the canonical CRY/MSC/PWN/REV/WEB/HW rows, the observed persistent database proves a new forward migration is required: already-recorded migrations must never be edited or relied on to rerun.
  • Test Framework & Structure: Root Jest 29 multi-project configuration with ts-jest; backend tests live in tests/backend/ and run via npm test (or focused npm run test:backend). Existing migration tests use an isolated in-memory SQLite database. Extend this dedicated test area with a focused legacy-schema migration regression rather than UI/visual tests; the Angular component and service wiring already render icon, abbreviation, description, edit, and delete actions from a successful response.
  • Required Tools & Dependencies: No new system tools, global CLIs, npm packages, or /data fixtures are required. Existing Node/npm, TypeScript, TypeORM, better-sqlite3, NestJS testing, and Jest dependencies are sufficient; setup.sh should remain unchanged. Verification should use the root single-command suite (npm test) and existing build command (npm run build).

2. Impacted Files

  • To Modify:
    • backend/src/database/database.module.ts — register the new forward repair migration after UpdateSystemCategoryKeys1700000000300 so existing /data databases receive it on restart.
    • tests/backend/migrations.spec.ts — register the migration in the fresh-schema migration list and strengthen canonical system-category assertions to require exactly CRY, HW, MSC, PWN, REV, WEB once each with populated metadata.
    • tests/backend/database-init.spec.ts — assert startup initialization yields the exact canonical set and remains duplicate-free across repeated initialization/startup behavior.
    • backend/src/config/env.schema.ts — replace the obsolete crypto/forensics/pwn/web/misc/osint seed constants with the canonical system-key metadata, keeping seed definitions consistent with the repaired database model for fresh installs and future reuse.
  • To Create:
    • backend/src/database/migrations/1700000000400-RepairCategorySchemaAndSystemCategories.ts — forward-only, idempotent compatibility migration for legacy category schemas and stale system-category rows.
    • tests/backend/category-repair-migration.spec.ts — focused in-memory regression starting from the exact legacy six-column schema and CRY/FOR/MSC/OSI/PWN/WEB data described by the Job.

3. Proposed Changes

  1. Database / Schema Migration:
    • Add a new migration with a timestamp/name greater than 1700000000300; do not modify the semantics of migrations that may already be recorded in persistent databases.
    • Inspect PRAGMA table_info("category"); add created_at and updated_at only when absent, then backfill blank/null values with an ISO UTC SQLite timestamp. This makes TypeORM SELECT c.* valid before any category rows are read through CategoryEntity.
    • Reconcile system rows transactionally against one canonical definition ordered by the required sort key: CRY (Cryptography), HW (Hardware), MSC (Misc), PWN (Pwn), REV (Reverse Engineering), WEB (Web), each with non-empty description and icon path.
    • Preserve user-created rows (system_key IS NULL). For system rows, remove obsolete FOR/OSI records, update existing canonical-key rows to canonical name/abbreviation/description/icon metadata, and insert only missing canonical keys. Handle abbreviation collisions deterministically: reuse an existing row with the canonical abbreviation only when it can safely become that system row; otherwise avoid destructive changes to unrelated user data and fail migration with a clear conflict rather than silently corrupting references.
    • Ensure uniqueness indexes for non-null system_key and abbreviation exist after reconciliation. Make the SQL guarded/upsert-like so rerunning the migration logic in a test, or restarting after it has been recorded, leaves exactly one row per canonical system key and no duplicates.
    • Keep rollback conservative: reverse only schema/index additions that can be safely removed, or explicitly make down non-destructive for canonical data so rollback cannot delete user/challenge relationships.
  2. Backend Logic & APIs:
    • Keep AdminCategoriesService.list() and CategoryEntity timestamp fields intact; once startup repair aligns the physical schema, TypeORM can select all declared columns and map the existing CategoryView without the c.created_at 500.
    • Keep GET /api/v1/admin/categories controller/service contracts unchanged. Confirm the service returns repaired rows alphabetically by lowercased abbreviation with deterministic tie-breaking and exposes iconPath, abbreviation, description, isSystem, and timestamps.
    • Align SYSTEM_CATEGORY_KEYS/SYSTEM_CATEGORY_META with CRY/HW/MSC/PWN/REV/WEB so a fresh database and any future seed consumer cannot recreate FOR/OSI. Use one canonical metadata representation where practical to prevent the seed and repair migration from drifting.
    • Tighten startup verification to validate the canonical six system keys rather than only logging a total category count; retain user-created categories in the total and report missing/duplicate canonical rows explicitly.
  3. Frontend UI Integration:
    • No frontend code change is planned. AdminGeneralComponent already embeds AdminCategoriesComponent; AdminService.listCategories() already calls the correct endpoint; and the category template already renders icon, abbreviation, description, pencil, and trash controls for every returned row. Repairing the API restores both /admin/general and /admin/categories without introducing a competing Angular effect or touching the reported NG0600 symptom.

4. Test Strategy

  • Target Unit Test File: tests/backend/category-repair-migration.spec.ts, plus minimal assertion updates in tests/backend/migrations.spec.ts and tests/backend/database-init.spec.ts.
  • Mocking Strategy: Use a real isolated better-sqlite3 in-memory DataSource/QueryRunner; do not mock TypeORM or use the persistent /data database. Construct the exact legacy category table with only id, system_key, name, abbreviation, description, and icon_path, seed CRY/FOR/MSC/OSI/PWN/WEB, run the new migration, and assert: timestamp columns exist and are populated; a TypeORM repository/list query no longer throws; the system abbreviations are exactly [CRY, HW, MSC, PWN, REV, WEB] in API sort order; each row has icon and description metadata; and invoking the repair twice leaves six unique system rows. Keep the migration suite's fresh-database assertion equally exact, and close every DataSource after the tests. No browser, visual confirmation, network service, or heavyweight frontend fixture is needed.