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

6.7 KiB

Implementation Plan: Job 918 — Admin Area System Database Backup/Restore and Danger Zone 1.01

1. Architectural Reconnaissance

  • Codebase style & conventions: Node.js monorepo using strict TypeScript, NestJS controllers/services on the backend, Angular standalone components and signals on the frontend, async/await, dependency injection, Zod request/archive validation, and stable ApiError codes. The requested UI and API flow already exists; the defect is isolated to the backend restore rebuild. RestoreService.cloneAndOverwriteDatabase() clones the live SQLite file and executes DELETE statements while the live schema's trg_user_last_admin_delete trigger remains active. PRAGMA foreign_keys = OFF does not disable triggers, so deleting the sole live admin raises LAST_ADMIN before archived users can be inserted.
  • Data Layer: SQLite through TypeORM's better-sqlite3 driver. Runtime migrations create a database-level invariant with trg_user_last_admin_update and trg_user_last_admin_delete. Backups dynamically include application tables but exclude admin_operation_token, migrations, and SQLite internals. Restore uses a cloned database, direct parameterized better-sqlite3 statements, and a database/uploads filesystem swap with rollback handles.
  • Test Framework & Structure: Jest 29 with ts-jest; backend tests live under /repo/tests/backend and are selected by /repo/tests/jest.config.js. Root npm test runs backend and frontend projects, while npm run test:backend runs the focused backend suite. Tests must remain CLI-only and use isolated temporary SQLite/upload/staging paths rather than shared mutable resources.
  • Required Tools & Dependencies: No new package, system tool, global CLI, or persistent /data asset is required. Existing Node.js/npm, TypeScript, Jest, Nest testing utilities, TypeORM, and better-sqlite3 are sufficient. setup.sh already installs dependencies and rebuilds the native SQLite binding, so it should not be changed.

2. Impacted Files

  • To Modify:
    • backend/src/modules/admin/system/restore.service.ts — make the offline clone rebuild temporarily suspend the last-admin triggers, restore all archived rows, validate invariants, and reinstate the triggers before the file is eligible for swapping.
    • tests/backend/admin-system-restore-commit.spec.ts — add a focused real-file restore regression covering a backup that contains the same sole admin as the live database, restored table content, uploads, and trigger preservation; retain the existing controller/DTO contract coverage.
  • To Create: None.

3. Proposed Changes

  1. Database / Schema Migration:
    • Do not add or alter a migration: the LAST_ADMIN triggers are correct for ordinary runtime user mutations.
    • In the private staged database clone only, read the exact SQL definitions of trg_user_last_admin_update and trg_user_last_admin_delete from sqlite_master, fail closed if the expected trigger definitions cannot be captured, and drop those triggers before clearing application tables.
    • Keep foreign keys disabled during the bulk replacement, clear all known application tables, and insert archived rows in the existing dependency-safe order with parameterized values.
    • Before closing the staged clone, verify the archive produced at least one admin user so a restore cannot bypass the deployment-wide invariant. Recreate both captured triggers exactly, re-enable foreign keys, run PRAGMA foreign_key_check, and treat any violations or trigger-restoration failure as a restore failure. This confines the temporary invariant suspension to an offline candidate file and ensures the candidate has full protections before swap.
  2. Backend Logic & APIs:
    • Preserve the existing POST /api/v1/admin/system/restore/validate, confirmation-token, and POST /restore/commit contracts; stagingId already survives Zod validation and reaches RestoreService.commitRestore().
    • Refactor the staged rebuild cleanup so database closure and pragma/trigger restoration are deterministic and original failures are not masked by cleanup. Any rebuild, integrity, swap, or verification error must continue through the existing rollback path and return SYSTEM_RESTORE_ROLLED_BACK without changing live data/uploads.
    • Keep the existing filesystem transaction: build and validate the candidate first, copy staged uploads beside the live upload root, swap database and uploads together, verify the swapped database, then commit rollback artifacts.
    • Keep post-success session revocation in AdminSystemController.commitRestore(). After the restored database is live, revoke refresh sessions for the authenticated admin ID; the frontend's already-wired forceServerInvalidation() then clears the access session and redirects all tabs to login.
  3. Frontend UI Integration:
    • No frontend changes are planned. The file picker, staging summary, reauthentication modal, confirmation phrase, restore commit request, rollback alert, data-cache invalidation, and forced-login behavior are already wired in frontend/src/app/features/admin/system/system.component.ts, system.service.ts, and the auth/system data-change services.

4. Test Strategy

  • Target Unit Test File: tests/backend/admin-system-restore-commit.spec.ts.
  • Mocking Strategy: Keep existing controller dependencies mocked for request forwarding/token/session-revocation assertions. Add one minimal service-level integration regression using a unique temporary directory, a real file-backed better-sqlite3 database, real RestoreService, and real FilesystemTransactionService; mock only ConfigService and the live DataSource.query verification boundary as needed. Arrange a live database with the migrated last-admin triggers, one sole admin, pre-restore challenge/blog/setting rows, and live uploads; stage a valid archive containing that same admin plus different table values and replacement challenge/icon files; commit; then open the swapped file independently and assert archived counts/settings, exact upload replacement, and both last-admin triggers still exist and reject deleting the sole restored admin. Add one focused invalid-archive invariant case with no admin and assert SYSTEM_RESTORE_ROLLED_BACK plus unchanged live database/uploads. Clean all temporary files in afterEach/afterAll; no UI, network, visual checks, shared /data, or large mock environment.
  • Execution: Follow red-green-refactor: first run the focused backend spec to confirm the LAST_ADMIN failure, implement the smallest restore-service correction, rerun the focused spec, then run npm run test:backend, root npm test, npm --workspace backend run build, and the repository lint/typecheck commands if present (the current package scripts expose build but no dedicated lint/typecheck command).