14 KiB
#[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.OnPushon the frontend underfrontend/src/app/features/**. Pure helpers live alongside their consumers (*.pure.ts). - Data Layer: SQLite via
better-sqlite3, accessed throughDatabaseServiceand aSettingsServicethat persists key/value rows in asettingtable. Migrations live underbackend/src/database/migrations/. Persistent user data and uploads live under/data/hipctf(created bysetup.sh). - Test Framework & Structure: Jest 29 +
ts-jest. A single root Jest config attests/jest.config.jsdefines two projects (backend=ts-jest+node,frontend=ts-jest+jsdom). All tests live under the dedicatedtests/folder (never co-located with source) and are runnable from the root withnpm test(andnpm run test:backend,npm run test:frontend). - Required Tools & Dependencies: No new dependencies are needed. The
existing
package.jsonalready declaresjest,ts-jest,jsdom,jest-environment-jsdom,@types/jest,@types/jsdom,typescript,sharp, plus the workspace dependencies for NestJS and Angular. Thesetup.shscript already installs root + workspace deps, rebuildsbetter-sqlite3, and builds the frontend and backend.
Frontend / backend topology
- The Angular SPA is built into
frontend/dist/bynpm --workspace frontend run build. main.ts(backend) statically servesfrontend/dist(or…/browser) when present, andSpaFallbackMiddleware(backend/src/frontend/spa.controller.ts) servesindex.htmlfor any non-/apinon-assetGET. No Angular dev proxy is required — the SPA and the API share the same origin (defaulthttp://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-141and throughoutauth.service.ts,bootstrap.service.ts, …). ThewithCredentials: trueflag 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 helpers —
frontend/src/app/features/admin/general.pure.ts - Frontend component —
frontend/src/app/features/admin/general.component.ts - Frontend service —
frontend/src/app/core/services/admin.service.ts - Frontend SPA wiring —
backend/src/frontend/spa.controller.ts+backend/src/main.ts - Frontend tests —
tests/frontend/admin-general-pure.spec.ts - Backend DTO —
backend/src/modules/admin/dto/general.dto.ts - Backend controller —
backend/src/modules/admin/admin-general.controller.ts - Backend service —
backend/src/modules/admin/general.service.ts - Backend tests —
tests/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-140definestoIsoUtc(local), which parsesYYYY-MM-DDTHH:mm[:ss[.fff]]via a regex and constructsnew Date(Date.UTC(...))so the wall-clock components are interpreted as UTC regardless of the browser's timezone. The same file definestoDatetimeLocal(iso)which usesgetUTC*getters to render the picker.backend/src/modules/admin/dto/general.dto.ts:57-58requires botheventStartUtcandeventEndUtcto bez.string().datetime(...)(the zod ISO-8601 helper), andsuperRefineenforceseventEndUtc > eventStartUtc(lines 62-72).- Lifecycle:
AdminGeneralComponent.applySettings(general.component.ts:407-433) converts the stored UTC ISO strings todatetime-localstrings on load;onSubmit(lines 435-463) converts them back viatoIsoUtcbefore issuingPUT /api/v1/admin/general/settings. - Locked in by
tests/frontend/admin-general-pure.spec.ts:79-112(datetime helpers) andtests/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 returns400 VALIDATION_FAILED, the assignable values are unchanged and the response is rendered intogeneral-save-error(lines 185-187). The component also callsapplySettings(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. TheSettingsService.setcalls inbackend/src/modules/admin/general.service.ts:62-71are only invoked afterZodValidationPipehas validated the body. - Locked in by
tests/frontend/admin-general-pure.spec.ts:187-244(validator + message helpers) andtests/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 currentDate.now().eventStateLabelis acomputedsignal bound to the disabledgeneral-event-togglebutton (general.component.ts:247-252and 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(thederiveEventStatesuite).
4. "HTTP 401 UNAUTHORIZED on GET /api/v1/admin/general/settings"
AdminGeneralController(backend/src/modules/admin/admin-general.controller.ts:11-13) is mounted underUseGuards(AdminGuard)and@Roles('admin').- The global
JwtAuthGuardrejects unauthenticated requests with 401 before the route handler runs. Verified bytests/backend/admin-guard.spec.tsandtests/backend/csrf-protected-routes.spec.ts. The component treats any load failure asloadErrorand rendersgeneral-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 (seeadmin.service.ts:76-141), so they resolve tohttp://localhost:3000/api/v1/...regardless of where the SPA was opened from. Noproxy.conf.jsonis required and none is present infrontend/angular.json. CORS_ORIGINSdefaults tohttp://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-localrejects malformed text by reporting the value as an empty string to JavaScript. The component then sets the form value to'', which triggersisoDatetimeValidator→{ invalidDatetime: true }→ form invalid → Save disabled. An inline error region (general-eventStart-error/general-eventEnd-error) renders the per-field message viadatetimeMessage/eventEndFieldMessagehelpers (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."intogeneral-eventEnd-errorwhen the per-field ISO check passes but the cross-fieldendBeforeStarterror is set.templatelines 121-128 bind the inline error and applyaria-invalid="true"/aria-describedbyto 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 0–255 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) attachesdefaultChallengeAddressFormatto the control when malformed.- The backend
defaultChallengeIpSchema(general.dto.ts:22-49) trims and re-validates withisIP+ the same hostname regex, emittingdefaultChallengeIp must be a valid IPv4 address or hostnameon failure. - Locked in by
tests/frontend/admin-general-pure.spec.ts:246-328andtests/backend/admin-general-event-window.spec.ts(the schema requires a validdefaultChallengeIpfor 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.tsexposesGET/POST /api/v1/admin/categoriesandPUT/DELETE /:id.backend/src/modules/admin/categories.service.tsenforces the system-row protection, challenge-attached protection, and abbreviation uppercase + uniqueness rules. Verified bytests/backend/admin-categories-service.spec.ts.
10. "Emit general SSE so other tabs see the new theme"
AdminGeneralService.updateSettingscallsSseHubService.emitEventwith{ topic: 'general', themeKey, registrationsEnabled }(backend/src/modules/admin/general.service.ts:72-76). Documented indocs/guides/admin-general-settings.mdanddocs/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) — coversderiveEventState,endAfterStartValidator,toIsoUtc,toDatetimeLocal,isoDatetimeValidator,datetimeMessage,eventEndFieldMessage,pageTitleError/pageTitleMessage,isValidDefaultChallengeAddress/defaultChallengeAddressError/defaultChallengeAddressMessage, andnormalizeDefaultChallengeAddress. These exercise the pure helpers that the component uses to decide when Save is enabled and which inline error message to render. - Frontend shell + state —
tests/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 validation —
tests/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/guard —
tests/backend/admin-guard.spec.ts,tests/backend/csrf-protected-routes.spec.ts. - Backend categories —
tests/backend/admin-categories-service.spec.ts.
Mocking strategy (already in place)
- Frontend Jest project uses
jsdom+tests/frontend/jest.setup.tsand consumes only the pure helper module (*.pure.ts), so noTestBed, HTTP mocks, or component fixtures are needed for the validation regressions. - Backend tests use
tests/backend/db-helper.tsto provision an isolated SQLite database under a temp directory and exercise theZodValidationPipedirectly, 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.