8.5 KiB
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
OnPushstandalone component using a non-nullable reactive form, Angular signals/computed state,@iftemplate control flow, andtakeUntilDestroyedsubscriptions. Reusable validation and message logic is kept infrontend/src/app/features/admin/general.pure.ts; the API validates request bodies at the controller boundary with Zod throughZodValidationPipe. - Data Layer: TypeORM with
better-sqlite3; general settings are key/value rows accessed throughSettingsService. No schema migration is required becausedefaultChallengeIpalready exists and only its accepted value contract changes.AdminGeneralService.updateSettingspersists the Zod-parsed payload, so server-side normalization must happen inGeneralSettingsSchemabefore the service receives it. - Test Framework & Structure: Root Jest 29 configuration with separate backend and jsdom frontend projects. Tests live under dedicated
tests/backendandtests/frontendfolders and are run together withnpm testfrom/repo; targeted commands arenpm run test:backendandnpm run test:frontend. Existing General Settings schema tests are intests/backend/admin-general-service.spec.ts, while pure Angular form-validation helpers are tested intests/frontend/admin-general-pure.spec.tswithout rendering a UI. - Required Tools & Dependencies: No new system tools, global CLIs, package dependencies, persistent
/dataassets, orsetup.shchanges are required. Use the existing Angular Forms APIs, Zod, Jest, TypeScript, and current npm workspace setup. Verification should runnpm testandnpm 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 theOnPushtemplate, 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 fordefaultChallengeIp.
- To Create: None.
3. Proposed Changes
-
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.defaultChallengeIpso 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'snet.isIP(value) === 4plus a hostname-label check) rather than adding a dependency. Reject incomplete dotted addresses such as10.0.0.and10.0.0, whitespace-only strings, IPv4 octets outside0..255, and malformed hostnames. Preserve the existing 255-character ceiling and return a clear field-specific message ondefaultChallengeIp. - Because Zod transforms before
AdminGeneralService.updateSettings, a valid padded address such as10.20.30.40is persisted canonically as10.20.30.40, while invalid values never reachSettingsService. This makes the backend authoritative even if the frontend is bypassed.
- Do not alter the SQLite schema or settings keys. Keep valid persisted values unchanged, including the demonstrated IPv4 value
-
Backend Logic & APIs:
- Keep
PUT /api/v1/admin/general/settings, its controller, service, response shape, settings key, and SSE behavior unchanged. The existingZodValidationPipewill emit the standard400 VALIDATION_FAILEDenvelope with an issue path ofdefaultChallengeIpfor bad input. - Keep
GET /api/v1/admin/general/settingsand public bootstrap reads unchanged; successful updates continue to round-trip the normalized value throughAdminGeneralService.getSettingsand bootstrap refresh. - Avoid service-level duplicate validation: the parsed
GeneralSettingsPayloadis the trusted normalized contract at the service boundary.
- Keep
-
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 returnnullonly 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
defaultChallengeIpcontrol'sValidators.required-only configuration with the extracted validation logic (retainingValidators.maxLength(255)if message handling distinguishes length). Do not use permissive browser URL parsing orDate.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
valueChangesandstatusChangesusingtakeUntilDestroyed(this.destroyRef), then deriveshowDefaultChallengeIpErrorand the message withcomputedsignals. Reset the control's touched/pristine state and tracking signal inapplySettingsso valid loaded or freshly saved data does not display stale errors. - Enhance
[data-testid=general-defaultIp]with conditionalaria-invalid="true"andaria-describedby="general-defaultIp-error". Render a.field-errorelement with bothidanddata-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 throughform.invalidfor incomplete/malformed values. - Normalize
defaultChallengeIpwith the shared helper inonSubmitbefore callingAdminService.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.
- In
4. Test Strategy
- Target Unit Test File:
tests/backend/admin-general-service.spec.ts: extend the existingGeneralSettingsSchema - validation rulessuite 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 forms10.0.0.and10.0.0) fail with an issue ondefaultChallengeIpand 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 runnpm run buildto verify Angular template bindings and backend TypeScript compilation.