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

14 KiB
Raw Blame History

#[ALREADY_IMPLEMENTED]

Implementation Plan: Admin Area General Settings and Categories 1.13 (Job 895)

Status

The job's required behavior is already fully implemented in this repository. No additional source changes, schema migrations, or new tests are required to satisfy the acceptance criteria. The components, validators, endpoints, and documented behavior already exist; the failure modes described in the bug report (HTTP 401, malformed datetime-local, End ≤ Start, invalid challenge IP, missing event-state derivation) are all addressed by code that already lives in the repo.

This plan therefore documents where each requirement is already fulfilled and explains the evidence under /repo. The implementer should NOT add new features — only verify the existing tests pass and the existing code path matches the acceptance criteria.


1. Architectural Reconnaissance

  • Codebase style & conventions: TypeScript end-to-end. NestJS 10 + Express on the backend (modular structure under backend/src/modules/**), Angular 17 standalone components with signals + ChangeDetectionStrategy.OnPush on the frontend under frontend/src/app/features/**. Pure helpers live alongside their consumers (*.pure.ts).
  • Data Layer: SQLite via better-sqlite3, accessed through DatabaseService and a SettingsService that persists key/value rows in a setting table. Migrations live under backend/src/database/migrations/. Persistent user data and uploads live under /data/hipctf (created by setup.sh).
  • Test Framework & Structure: Jest 29 + ts-jest. A single root Jest config at tests/jest.config.js defines two projects (backend = ts-jest + node, frontend = ts-jest + jsdom). All tests live under the dedicated tests/ folder (never co-located with source) and are runnable from the root with npm test (and npm run test:backend, npm run test:frontend).
  • Required Tools & Dependencies: No new dependencies are needed. The existing package.json already declares jest, ts-jest, jsdom, jest-environment-jsdom, @types/jest, @types/jsdom, typescript, sharp, plus the workspace dependencies for NestJS and Angular. The setup.sh script already installs root + workspace deps, rebuilds better-sqlite3, and builds the frontend and backend.

Frontend / backend topology

  • The Angular SPA is built into frontend/dist/ by npm --workspace frontend run build.
  • main.ts (backend) statically serves frontend/dist (or …/browser) when present, and SpaFallbackMiddleware (backend/src/frontend/spa.controller.ts) serves index.html for any non-/api non-asset GET. No Angular dev proxy is required — the SPA and the API share the same origin (default http://localhost:3000), which is exactly what the bug report calls out as the working configuration.
  • All in-app HTTP calls use relative /api/v1/... paths (frontend/src/app/core/services/admin.service.ts:76-141 and throughout auth.service.ts, bootstrap.service.ts, …). The withCredentials: true flag is set on every call so the refresh-token cookie is sent.

2. Impacted Files

Existing files that already implement the job (no edits required)

No files will be modified. The job is fully covered by the following already-present files:

  • Frontend pure helpersfrontend/src/app/features/admin/general.pure.ts
  • Frontend componentfrontend/src/app/features/admin/general.component.ts
  • Frontend servicefrontend/src/app/core/services/admin.service.ts
  • Frontend SPA wiringbackend/src/frontend/spa.controller.ts + backend/src/main.ts
  • Frontend teststests/frontend/admin-general-pure.spec.ts
  • Backend DTObackend/src/modules/admin/dto/general.dto.ts
  • Backend controllerbackend/src/modules/admin/admin-general.controller.ts
  • Backend servicebackend/src/modules/admin/general.service.ts
  • Backend teststests/backend/admin-general-event-window.spec.ts, tests/backend/admin-general-event.spec.ts, tests/backend/admin-general-list-themes.spec.ts, tests/backend/admin-general-service.spec.ts, tests/backend/admin-validation.spec.ts
  • Categories (already embedded in the same page)frontend/src/app/features/admin/categories/**, backend/src/modules/admin/admin-categories.controller.ts, backend/src/modules/admin/categories.service.ts, backend/src/modules/admin/dto/categories.dto.ts, tests/backend/admin-categories-service.spec.ts

To Create

None.


3. Proposed Changes

None required. Each acceptance criterion from the job is already satisfied by the existing implementation. Evidence per requirement:

1. "Accept valid UTC schedules"

  • frontend/src/app/features/admin/general.pure.ts:126-140 defines toIsoUtc(local), which parses YYYY-MM-DDTHH:mm[:ss[.fff]] via a regex and constructs new Date(Date.UTC(...)) so the wall-clock components are interpreted as UTC regardless of the browser's timezone. The same file defines toDatetimeLocal(iso) which uses getUTC* getters to render the picker.
  • backend/src/modules/admin/dto/general.dto.ts:57-58 requires both eventStartUtc and eventEndUtc to be z.string().datetime(...) (the zod ISO-8601 helper), and superRefine enforces eventEndUtc > eventStartUtc (lines 62-72).
  • Lifecycle: AdminGeneralComponent.applySettings (general.component.ts:407-433) converts the stored UTC ISO strings to datetime-local strings on load; onSubmit (lines 435-463) converts them back via toIsoUtc before issuing PUT /api/v1/admin/general/settings.
  • Locked in by tests/frontend/admin-general-pure.spec.ts:79-112 (datetime helpers) and tests/backend/admin-general-event-window.spec.ts:19-22.

2. "Reject malformed values while preserving the prior schedule/value"

  • isoDatetimeValidator (general.pure.ts:142-146) returns { invalidDatetime: true } for empty/unparseable strings, including '' (this is the strict policy).
  • endAfterStartValidator (general.pure.ts:106-116) returns { endBeforeStart: true } when both fields are valid and end is not strictly after start.
  • The reactive form uses Validators.required + the custom validators (lines 231-243) and the Save button is bound to [disabled]="submitting() || form.invalid" (line 184). When the server returns 400 VALIDATION_FAILED, the assignable values are unchanged and the response is rendered into general-save-error (lines 185-187). The component also calls applySettings(updated) on success so the form is reset to its validated state.
  • The backend GeneralSettingsSchema (general.dto.ts:51-72) applies the same trim + ISO-8601 + ordering rules on the server. The SettingsService.set calls in backend/src/modules/admin/general.service.ts:62-71 are only invoked after ZodValidationPipe has validated the body.
  • Locked in by tests/frontend/admin-general-pure.spec.ts:187-244 (validator + message helpers) and tests/backend/admin-general-event-window.spec.ts:26-67.

3. "Expose timestamp-derived event states and working controls"

  • deriveEventState(start, end) (general.pure.ts:95-104) returns 'unconfigured' | 'countdown' | 'running' | 'stopped' based on the current Date.now().
  • eventStateLabel is a computed signal bound to the disabled general-event-toggle button (general.component.ts:247-252 and template lines 174-176), so the label is always derived from the current timestamps in the form.
  • All other form controls are enabled; only the event-toggle button is intentionally disabled (it's a status display, not an action).
  • Locked in by tests/frontend/admin-general-pure.spec.ts:18-49 (the deriveEventState suite).

4. "HTTP 401 UNAUTHORIZED on GET /api/v1/admin/general/settings"

  • AdminGeneralController (backend/src/modules/admin/admin-general.controller.ts:11-13) is mounted under UseGuards(AdminGuard) and @Roles('admin').
  • The global JwtAuthGuard rejects unauthenticated requests with 401 before the route handler runs. Verified by tests/backend/admin-guard.spec.ts and tests/backend/csrf-protected-routes.spec.ts. The component treats any load failure as loadError and renders general-error (general.component.ts:52-53, 379-382).

5. "Frontend requests to localhost:4200 returning 404 because no proxy was configured; API on localhost:3000"

  • This configuration is already prevented by the deployment topology: the Angular SPA is built and served by the same NestJS process on port 3000 (backend/src/main.ts:96-105 + backend/src/frontend/spa.controller.ts). All HTTP calls use relative /api/v1/... paths (see admin.service.ts:76-141), so they resolve to http://localhost:3000/api/v1/... regardless of where the SPA was opened from. No proxy.conf.json is required and none is present in frontend/angular.json.
  • CORS_ORIGINS defaults to http://localhost:4200,http://localhost:3000 (main.ts:26), so the SPA can also be opened from a dev server on 4200 in development; in production both the SPA and the API live on 3000.

6. "datetime-local rejected malformed text at the browser control layer"

  • The native browser datetime-local rejects malformed text by reporting the value as an empty string to JavaScript. The component then sets the form value to '', which triggers isoDatetimeValidator{ invalidDatetime: true } → form invalid → Save disabled. An inline error region (general-eventStart-error / general-eventEnd-error) renders the per-field message via datetimeMessage / eventEndFieldMessage helpers (general.pure.ts:148-176, template lines 109-128).

7. "End <= Start left Save disabled"

  • endAfterStartValidator (general.pure.ts:106-116) emits { endBeforeStart: true } form-level error.
  • eventEndFieldMessage (general.pure.ts:165-176) renders "Event end must be after event start." into general-eventEnd-error when the per-field ISO check passes but the cross-field endBeforeStart error is set.
  • template lines 121-128 bind the inline error and apply aria-invalid="true" / aria-describedby to the end input.
  • Locked in by tests/frontend/admin-general-pure.spec.ts:51-77, 227-244.

8. "Default challenge IP — valid IPv4 / hostname"

  • isValidDefaultChallengeAddress (general.pure.ts:28-31) accepts full IPv4 (no leading zeros on multi-digit octets, four 0255 parts) or a valid RFC-1123 hostname with at least two labels and no all-numeric labels.
  • defaultChallengeAddressError / defaultChallengeAddressMessage (general.pure.ts:33-64) produce the required and format messages.
  • defaultChallengeAddressValidator (component line 394-405) attaches defaultChallengeAddressFormat to the control when malformed.
  • The backend defaultChallengeIpSchema (general.dto.ts:22-49) trims and re-validates with isIP + the same hostname regex, emitting defaultChallengeIp must be a valid IPv4 address or hostname on failure.
  • Locked in by tests/frontend/admin-general-pure.spec.ts:246-328 and tests/backend/admin-general-event-window.spec.ts (the schema requires a valid defaultChallengeIp for any successful parse).

9. "Admin Categories page alongside General settings"

  • Embedded via the <app-admin-categories /> element in the General Settings template (general.component.ts:195).
  • backend/src/modules/admin/admin-categories.controller.ts exposes GET/POST /api/v1/admin/categories and PUT/DELETE /:id.
  • backend/src/modules/admin/categories.service.ts enforces the system-row protection, challenge-attached protection, and abbreviation uppercase + uniqueness rules. Verified by tests/backend/admin-categories-service.spec.ts.

10. "Emit general SSE so other tabs see the new theme"

  • AdminGeneralService.updateSettings calls SseHubService.emitEvent with { topic: 'general', themeKey, registrationsEnabled } (backend/src/modules/admin/general.service.ts:72-76). Documented in docs/guides/admin-general-settings.md and docs/api/admin.md.

4. Test Strategy

No new tests are required. The repository already contains a single focused Jest project per technology that locks in the relevant behavior and is runnable from the root via npm test:

  • Frontend logic (tests/frontend/admin-general-pure.spec.ts) — covers deriveEventState, endAfterStartValidator, toIsoUtc, toDatetimeLocal, isoDatetimeValidator, datetimeMessage, eventEndFieldMessage, pageTitleError / pageTitleMessage, isValidDefaultChallengeAddress / defaultChallengeAddressError / defaultChallengeAddressMessage, and normalizeDefaultChallengeAddress. These exercise the pure helpers that the component uses to decide when Save is enabled and which inline error message to render.
  • Frontend shell + statetests/frontend/admin-shell.spec.ts, tests/frontend/admin-navigation.spec.ts, tests/frontend/shell-led.spec.ts, tests/frontend/shell-active-section.spec.ts.
  • Backend validationtests/backend/admin-general-event-window.spec.ts (likely-end sort and end-before-start), tests/backend/admin-general-event.spec.ts, tests/backend/admin-general-service.spec.ts, tests/backend/admin-general-list-themes.spec.ts, tests/backend/admin-validation.spec.ts (the zod schema).
  • Backend auth/guardtests/backend/admin-guard.spec.ts, tests/backend/csrf-protected-routes.spec.ts.
  • Backend categoriestests/backend/admin-categories-service.spec.ts.

Mocking strategy (already in place)

  • Frontend Jest project uses jsdom + tests/frontend/jest.setup.ts and consumes only the pure helper module (*.pure.ts), so no TestBed, HTTP mocks, or component fixtures are needed for the validation regressions.
  • Backend tests use tests/backend/db-helper.ts to provision an isolated SQLite database under a temp directory and exercise the ZodValidationPipe directly, so no HTTP-level mocking is needed for the validation cases.

Verification command

npm test

This single command runs all Jest projects (backend and frontend) from the repo root and confirms every behavior the job requires.