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

8.5 KiB

Implementation Plan: Admin Area General Settings and Categories 1.09

1. Architectural Reconnaissance

  • Codebase style & conventions: Node.js TypeScript monorepo with a NestJS 10 REST backend and Angular 17 standalone SPA. The General page is an OnPush standalone component using a non-nullable reactive form, Angular signals/computed state, @if template control flow, and takeUntilDestroyed subscriptions. Reusable validation and message logic is kept in frontend/src/app/features/admin/general.pure.ts; the API validates request bodies at the controller boundary with Zod through ZodValidationPipe.
  • Data Layer: TypeORM with better-sqlite3; general settings are key/value rows accessed through SettingsService. No schema migration is required because defaultChallengeIp already exists and only its accepted value contract changes. AdminGeneralService.updateSettings persists the Zod-parsed payload, so server-side normalization must happen in GeneralSettingsSchema before the service receives it.
  • Test Framework & Structure: Root Jest 29 configuration with separate backend and jsdom frontend projects. Tests live under dedicated tests/backend and tests/frontend folders and are run together with npm test from /repo; targeted commands are npm run test:backend and npm run test:frontend. Existing General Settings schema tests are in tests/backend/admin-general-service.spec.ts, while pure Angular form-validation helpers are tested in tests/frontend/admin-general-pure.spec.ts without rendering a UI.
  • Required Tools & Dependencies: No new system tools, global CLIs, package dependencies, persistent /data assets, or setup.sh changes are required. Use the existing Angular Forms APIs, Zod, Jest, TypeScript, and current npm workspace setup. Verification should run npm test and npm run build; no separate lint/typecheck scripts are defined, and both workspace builds perform the available TypeScript/Angular compilation checks.

2. Impacted Files

  • To Modify:
    • backend/src/modules/admin/dto/general.dto.ts — trim and strictly validate the default challenge address at the API boundary, with a field-specific validation message.
    • frontend/src/app/features/admin/general.pure.ts — add a reusable pure default-address validator, normalization helper, and canonical inline-message mapper.
    • frontend/src/app/features/admin/general.component.ts — attach the new validator to the reactive-form control, expose touched/dirty invalid state to the OnPush template, render accessible inline feedback, and submit the normalized value.
    • tests/backend/admin-general-service.spec.ts — add focused Zod schema regression cases for accepted, normalized, and rejected default challenge addresses.
    • tests/frontend/admin-general-pure.spec.ts — add focused tests for default-address validation, messaging, and normalization without browser/UI infrastructure.
    • docs/guides/admin-general-settings.md — update the documented field contract, inline error selector, accessibility behavior, normalization, and test coverage.
    • docs/api/admin.md — document server-side trimming and IP/hostname validation for defaultChallengeIp.
  • To Create: None.

3. Proposed Changes

  1. Database / Schema Migration:

    • Do not alter the SQLite schema or settings keys. Keep valid persisted values unchanged, including the demonstrated IPv4 value 10.66.77.88.
    • Strengthen GeneralSettingsSchema.defaultChallengeIp so Zod trims surrounding whitespace first, rejects an empty result, and accepts only a complete IPv4 address or a valid hostname. Implement the format predicate with built-in/runtime-safe logic (for example Node's net.isIP(value) === 4 plus a hostname-label check) rather than adding a dependency. Reject incomplete dotted addresses such as 10.0.0. and 10.0.0, whitespace-only strings, IPv4 octets outside 0..255, and malformed hostnames. Preserve the existing 255-character ceiling and return a clear field-specific message on defaultChallengeIp.
    • Because Zod transforms before AdminGeneralService.updateSettings, a valid padded address such as 10.20.30.40 is persisted canonically as 10.20.30.40, while invalid values never reach SettingsService. This makes the backend authoritative even if the frontend is bypassed.
  2. Backend Logic & APIs:

    • Keep PUT /api/v1/admin/general/settings, its controller, service, response shape, settings key, and SSE behavior unchanged. The existing ZodValidationPipe will emit the standard 400 VALIDATION_FAILED envelope with an issue path of defaultChallengeIp for bad input.
    • Keep GET /api/v1/admin/general/settings and public bootstrap reads unchanged; successful updates continue to round-trip the normalized value through AdminGeneralService.getSettings and bootstrap refresh.
    • Avoid service-level duplicate validation: the parsed GeneralSettingsPayload is the trusted normalized contract at the service boundary.
  3. Frontend UI Integration:

    • In general.pure.ts, add a strict pure validator compatible with Angular's validator signature. It should trim only for checking, return a dedicated error key for empty/whitespace input and another for malformed IP/hostname input, and return null only for a complete IPv4 address or valid hostname. Add a normalization helper that trims the submitted value and a message helper that maps raw values/control errors to stable text, including a required message for empty/whitespace-only input and a format message for incomplete or malformed addresses.
    • Replace the defaultChallengeIp control's Validators.required-only configuration with the extracted validation logic (retaining Validators.maxLength(255) if message handling distinguishes length). Do not use permissive browser URL parsing or Date.parse-style heuristics.
    • Mirror the component's established Page Title/Event field signal pattern: track the default-IP raw value, validity, and touched-or-dirty state from valueChanges and statusChanges using takeUntilDestroyed(this.destroyRef), then derive showDefaultChallengeIpError and the message with computed signals. Reset the control's touched/pristine state and tracking signal in applySettings so valid loaded or freshly saved data does not display stale errors.
    • Enhance [data-testid=general-defaultIp] with conditional aria-invalid="true" and aria-describedby="general-defaultIp-error". Render a .field-error element with both id and data-testid="general-defaultIp-error" whenever the field has been touched or dirtied and is invalid. This gives the empty case the required visible explanation and keeps Save disabled through form.invalid for incomplete/malformed values.
    • Normalize defaultChallengeIp with the shared helper in onSubmit before calling AdminService.updateGeneralSettings. Valid surrounding whitespace is therefore removed rather than persisted, aligning client behavior with the authoritative backend schema. Do not mutate the control during typing, so the user can see and correct the original entry.

4. Test Strategy

  • Target Unit Test File:
    • tests/backend/admin-general-service.spec.ts: extend the existing GeneralSettingsSchema - validation rules suite with minimal cases proving a valid IPv4 survives exactly, a valid hostname is accepted, surrounding whitespace is trimmed in parsed output, and the key negatives (''/whitespace-only plus representative incomplete dotted forms 10.0.0. and 10.0.0) fail with an issue on defaultChallengeIp and a clear format/required message.
    • tests/frontend/admin-general-pure.spec.ts: test the pure validator/message/normalizer directly: valid IPv4 and hostname return no error/message; empty and whitespace-only return the required error/message; incomplete dotted addresses return the format error/message; padded valid input normalizes to its trimmed value. Keep tests logic-only and independent of Angular TestBed or visual confirmation.
  • Mocking Strategy: No external services, HTTP, database, browser automation, or persistent data are needed. Parse plain objects directly with the Zod schema and invoke pure frontend helpers with small control-shaped objects, following the existing fast Jest suites. Avoid adding a component harness or full Nest application because the behavior is fully covered at the validation boundaries and the existing form wiring uses those helpers directly. Run all suites from the root with npm test, then run npm run build to verify Angular template bindings and backend TypeScript compilation.