docs: update documentation to OKF v0.1 format
This commit is contained in:
+5
-3
@@ -46,9 +46,11 @@ removes the row.
|
|||||||
* `logo` up to 2048 chars (a public URL — uploaded separately)
|
* `logo` up to 2048 chars (a public URL — uploaded separately)
|
||||||
* `welcomeMarkdown` up to 64 000 chars
|
* `welcomeMarkdown` up to 64 000 chars
|
||||||
* `themeKey` is one of `THEME_IDS`
|
* `themeKey` is one of `THEME_IDS`
|
||||||
* `eventStartUtc` / `eventEndUtc` parseable dates; `superRefine`
|
* `eventStartUtc` / `eventEndUtc` are validated as ISO-8601 datetimes
|
||||||
enforces `eventEndUtc > eventStartUtc` and reports the issue on
|
(zod `string().datetime(...)`) — empty strings are explicitly allowed
|
||||||
`eventEndUtc`
|
to keep the event window unconfigured. `superRefine` then enforces
|
||||||
|
`eventEndUtc > eventStartUtc` and reports the issue on `eventEndUtc`
|
||||||
|
when both fields are non-empty.
|
||||||
* `defaultChallengeIp` 1–255 chars
|
* `defaultChallengeIp` 1–255 chars
|
||||||
* `registrationsEnabled` boolean
|
* `registrationsEnabled` boolean
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ timestamp: 2026-07-22T13:40:00Z
|
|||||||
| `backend/src/modules/auth/auth.controller.ts` | Registers authentication and account endpoints. |
|
| `backend/src/modules/auth/auth.controller.ts` | Registers authentication and account endpoints. |
|
||||||
| `backend/src/modules/auth/auth.service.ts` | Handles sessions, authentication, registration, and password changes. |
|
| `backend/src/modules/auth/auth.service.ts` | Handles sessions, authentication, registration, and password changes. |
|
||||||
| `backend/src/modules/uploads/uploads.controller.ts` | Registers admin-only multipart uploads, including Sharp-backed site-logo format validation. |
|
| `backend/src/modules/uploads/uploads.controller.ts` | Registers admin-only multipart uploads, including Sharp-backed site-logo format validation. |
|
||||||
|
| `backend/src/modules/admin/dto/general.dto.ts` | Zod schema for `PUT /api/v1/admin/general/settings` — string-length rules, theme-key enum, ISO-8601 datetime check on the event-window fields (empty string allowed), and the `endAfterStart` super-refine. |
|
||||||
|
|
||||||
# Frontend
|
# Frontend
|
||||||
|
|
||||||
@@ -45,6 +46,8 @@ timestamp: 2026-07-22T13:40:00Z
|
|||||||
| `frontend/src/app/features/shell/header/shell-header.component.ts` | Shell title, status LED (size/shape only — color comes from the pure map via `[style.background-color]`), countdown, and user menu. |
|
| `frontend/src/app/features/shell/header/shell-header.component.ts` | Shell title, status LED (size/shape only — color comes from the pure map via `[style.background-color]`), countdown, and user menu. |
|
||||||
| `frontend/src/app/features/shell/tabs/quick-tabs.component.ts` | Main shell navigation tabs. |
|
| `frontend/src/app/features/shell/tabs/quick-tabs.component.ts` | Main shell navigation tabs. |
|
||||||
| `frontend/src/app/features/shell/change-password/change-password-modal.component.ts` | Change-password form modal. |
|
| `frontend/src/app/features/shell/change-password/change-password-modal.component.ts` | Change-password form modal. |
|
||||||
|
| `frontend/src/app/features/admin/general.component.ts` | `AdminGeneralComponent` reactive form for `/admin/general` — per-field inline error rendering (page-title + event-start + event-end), logo upload wiring, welcome Markdown preview, event-state derivation, and SSE `general` event handling. |
|
||||||
|
| `frontend/src/app/features/admin/general.pure.ts` | Pure helpers used by `AdminGeneralComponent` — `deriveEventState`, `endAfterStartValidator`, `normalizePageTitle`, `toIsoUtc`, `toDatetimeLocal`, `pageTitleError` / `pageTitleMessage`, `isoDatetimeValidator`, and `datetimeMessage`. |
|
||||||
| `tests/frontend/authenticated-event-source.spec.ts` | Tests SSE authorization, frame transport behavior, and the `401`/`403` unauthorized path. |
|
| `tests/frontend/authenticated-event-source.spec.ts` | Tests SSE authorization, frame transport behavior, and the `401`/`403` unauthorized path. |
|
||||||
| `tests/frontend/auth-session-events.spec.ts` | Pure tests for cross-tab invalidation message encoding, payload validation, and `storage`-event filtering. |
|
| `tests/frontend/auth-session-events.spec.ts` | Pure tests for cross-tab invalidation message encoding, payload validation, and `storage`-event filtering. |
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
type: guide
|
type: guide
|
||||||
title: Admin — General Settings
|
title: Admin — General Settings
|
||||||
description: How an admin edits global platform settings (page title, logo, theme, event window, default challenge IP, registrations, welcome Markdown) from the /admin/general page.
|
description: How an admin edits global platform settings (page title, logo, theme, event window, default challenge IP, registrations, welcome Markdown) from the /admin/general page.
|
||||||
tags: [guide, admin, settings, general, tester]
|
tags: [guide, admin, settings, general, tester, datetime, validation]
|
||||||
timestamp: 2026-07-22T12:44:45Z
|
timestamp: 2026-07-22T14:07:58Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# When this view is available
|
# When this view is available
|
||||||
@@ -42,8 +42,8 @@ The page is a single reactive form with these controls (every
|
|||||||
| Logo (file picker) | `general-logo-file` | `logo` (public URL) | Uploads via `POST /api/v1/uploads/logo`; the returned `publicUrl` is bound to a hidden input `general-logo`. |
|
| Logo (file picker) | `general-logo-file` | `logo` (public URL) | Uploads via `POST /api/v1/uploads/logo`; the returned `publicUrl` is bound to a hidden input `general-logo`. |
|
||||||
| Logo upload status | `general-logo-uploading` / `general-logo-error` / `general-logo-current` | — | Inline status text under the file picker. |
|
| Logo upload status | `general-logo-uploading` / `general-logo-error` / `general-logo-current` | — | Inline status text under the file picker. |
|
||||||
| Global theme | `general-themeKey` | `themeKey` | `<select>` populated from `/themes`. Value is one of `THEME_IDS`. |
|
| Global theme | `general-themeKey` | `themeKey` | `<select>` populated from `/themes`. Value is one of `THEME_IDS`. |
|
||||||
| Event start (UTC) | `general-eventStart` | `eventStartUtc` | `datetime-local` input converted to ISO UTC on save. |
|
| Event start (UTC) | `general-eventStart` | `eventStartUtc` | `datetime-local` input. Empty string is allowed; any non-empty value must be a valid ISO-8601 datetime — invalid input renders `general-eventStart-error`. |
|
||||||
| Event end (UTC) | `general-eventEnd` | `eventEndUtc` | Must be strictly after Event start. Invalid pair renders `general-endBeforeStart`. |
|
| Event end (UTC) | `general-eventEnd` | `eventEndUtc` | `datetime-local` input. Empty string is allowed; any non-empty value must be a valid ISO-8601 datetime — invalid input renders `general-eventEnd-error`. Must also be strictly after Event start (`general-endBeforeStart`). |
|
||||||
| Default challenge IP | `general-defaultIp` | `defaultChallengeIp` | Required, max 255 chars. |
|
| Default challenge IP | `general-defaultIp` | `defaultChallengeIp` | Required, max 255 chars. |
|
||||||
| Enable registrations | `general-registrations` | `registrationsEnabled` | Boolean checkbox. When `false`, the public register endpoint returns `REGISTRATIONS_DISABLED`. |
|
| Enable registrations | `general-registrations` | `registrationsEnabled` | Boolean checkbox. When `false`, the public register endpoint returns `REGISTRATIONS_DISABLED`. |
|
||||||
| Welcome description | `general-welcome` | `welcomeMarkdown` | Multi-line textarea; a live preview is rendered into `general-welcome-preview`. |
|
| Welcome description | `general-welcome` | `welcomeMarkdown` | Multi-line textarea; a live preview is rendered into `general-welcome-preview`. |
|
||||||
@@ -74,6 +74,15 @@ The page is a single reactive form with these controls (every
|
|||||||
strictly after the start, the form becomes invalid and
|
strictly after the start, the form becomes invalid and
|
||||||
`general-endBeforeStart` appears under the end input. Save remains
|
`general-endBeforeStart` appears under the end input. Save remains
|
||||||
disabled.
|
disabled.
|
||||||
|
* **Per-field datetime validation:** if the user types (or leaves) a value
|
||||||
|
that is not parseable as an ISO-8601 datetime in either the Event start
|
||||||
|
or Event end input, the affected input flips `aria-invalid="true"` and
|
||||||
|
its inline error element (`general-eventStart-error` or
|
||||||
|
`general-eventEnd-error`) renders the message
|
||||||
|
`"<Field> must be a valid ISO-8601 datetime."`. Empty strings are
|
||||||
|
considered valid (the event window may be unconfigured), so clearing
|
||||||
|
both inputs removes the error. Save remains disabled while either input
|
||||||
|
is invalid.
|
||||||
* **Save:** clicking Save sends `PUT /api/v1/admin/general/settings`
|
* **Save:** clicking Save sends `PUT /api/v1/admin/general/settings`
|
||||||
with all fields. The Page title input is validated client-side for
|
with all fields. The Page title input is validated client-side for
|
||||||
non-blank content; an empty or whitespace-only Page title disables the
|
non-blank content; an empty or whitespace-only Page title disables the
|
||||||
@@ -117,6 +126,50 @@ Save, the control is reset to untouched/pristine and the
|
|||||||
`pageTitleTouchedOrDirty` signal is cleared, so reloading valid
|
`pageTitleTouchedOrDirty` signal is cleared, so reloading valid
|
||||||
settings does not flash a stale error.
|
settings does not flash a stale error.
|
||||||
|
|
||||||
|
# Event start / end inline error messages
|
||||||
|
|
||||||
|
The inline error elements under the Event start and Event end inputs
|
||||||
|
(`general-eventStart-error` and `general-eventEnd-error`) are driven by
|
||||||
|
two pure helpers exported from
|
||||||
|
`frontend/src/app/features/admin/general.pure.ts`:
|
||||||
|
|
||||||
|
* `isoDatetimeValidator(ctrl)` — an Angular reactive-forms validator
|
||||||
|
that returns `null` for an empty value and `{ invalidDatetime: true }`
|
||||||
|
for any non-empty value that `Date.parse` cannot parse. Empty is
|
||||||
|
intentionally valid so the event window can be cleared.
|
||||||
|
* `datetimeMessage(fieldLabel, value, controlErrors)` — renders the
|
||||||
|
human-readable message `"<Field> must be a valid ISO-8601 datetime."`
|
||||||
|
either when `controlErrors.invalidDatetime` is set, or as a fallback
|
||||||
|
when the raw value itself is non-empty and unparseable.
|
||||||
|
|
||||||
|
The component wires both controls (`eventStartUtc`, `eventEndUtc`) with
|
||||||
|
this validator and keeps three local `signal`s per field — `Value`,
|
||||||
|
`Invalid`, `TouchedOrDirty` — exactly mirroring the Page-title pattern.
|
||||||
|
The `computed` signals `showEventStartError` / `showEventEndError` and
|
||||||
|
`eventStartMessage` / `eventEndMessage` are then bound to:
|
||||||
|
|
||||||
|
* `[attr.aria-invalid]` and `[attr.aria-describedby]` on each input so
|
||||||
|
assistive tech is informed the instant the field goes invalid.
|
||||||
|
* An `@if` block that renders the inline `<div class="field-error">`
|
||||||
|
with `data-testid="general-eventStart-error"` /
|
||||||
|
`data-testid="general-eventEnd-error"`.
|
||||||
|
|
||||||
|
| Condition | Message |
|
||||||
|
|------------------------------------------------------------|--------------------------------------------------------|
|
||||||
|
| Empty value (`''`) | `null` (no error; the event window may be unconfigured). |
|
||||||
|
| Non-empty value that `Date.parse` accepts | `null` (no error). |
|
||||||
|
| Non-empty value that `Date.parse` rejects | `"<Field> must be a valid ISO-8601 datetime."` |
|
||||||
|
| Control carries `invalidDatetime` error | Same message (covered by the validator path). |
|
||||||
|
|
||||||
|
The error is shown only when the control is both `touched || dirty`
|
||||||
|
AND `invalid`, so the inputs do not flash errors on initial load. After
|
||||||
|
a successful Save, both controls are reset to untouched/pristine via
|
||||||
|
`resetTouchedState()` and their `TouchedOrDirty` signals are cleared.
|
||||||
|
The cross-field `endBeforeStart` error (`general-endBeforeStart`) is
|
||||||
|
still emitted from the form-level `superRefine`/validator chain and is
|
||||||
|
unchanged — it now sits next to the new per-field messages when the
|
||||||
|
user supplies two non-empty values that are out of order.
|
||||||
|
|
||||||
# Visual elements
|
# Visual elements
|
||||||
|
|
||||||
| Element | Selector |
|
| Element | Selector |
|
||||||
@@ -127,6 +180,8 @@ settings does not flash a stale error.
|
|||||||
| Form | `[data-testid="general-form"]` |
|
| Form | `[data-testid="general-form"]` |
|
||||||
| Welcome preview | `[data-testid="general-welcome-preview"]` |
|
| Welcome preview | `[data-testid="general-welcome-preview"]` |
|
||||||
| End-before-start message | `[data-testid="general-endBeforeStart"]` |
|
| End-before-start message | `[data-testid="general-endBeforeStart"]` |
|
||||||
|
| Event start inline error | `[data-testid="general-eventStart-error"]` |
|
||||||
|
| Event end inline error | `[data-testid="general-eventEnd-error"]` |
|
||||||
| Page-title inline error | `[data-testid="general-pageTitle-error"]` |
|
| Page-title inline error | `[data-testid="general-pageTitle-error"]` |
|
||||||
|
|
||||||
# Architecture map
|
# Architecture map
|
||||||
@@ -138,7 +193,7 @@ settings does not flash a stale error.
|
|||||||
| 3 | `frontend/src/app/core/services/admin.service.ts` | `getGeneralSettings()` → `GET /api/v1/admin/general/settings`; `listAdminThemes()` → `GET /api/v1/admin/general/themes`; `updateGeneralSettings()` → `PUT .../settings`; `uploadLogo()` → `POST /api/v1/uploads/logo`. |
|
| 3 | `frontend/src/app/core/services/admin.service.ts` | `getGeneralSettings()` → `GET /api/v1/admin/general/settings`; `listAdminThemes()` → `GET /api/v1/admin/general/themes`; `updateGeneralSettings()` → `PUT .../settings`; `uploadLogo()` → `POST /api/v1/uploads/logo`. |
|
||||||
| 4 | `backend/src/modules/admin/admin-general.controller.ts` | `AdminGuard` + `@Roles('admin')` on every handler. |
|
| 4 | `backend/src/modules/admin/admin-general.controller.ts` | `AdminGuard` + `@Roles('admin')` on every handler. |
|
||||||
| 5 | `backend/src/modules/admin/general.service.ts` | `getSettings` reads 8 keys via `SettingsService`; `updateSettings` writes all 8, emits `{ topic: 'general', themeKey }` via `SseHubService`. |
|
| 5 | `backend/src/modules/admin/general.service.ts` | `getSettings` reads 8 keys via `SettingsService`; `updateSettings` writes all 8, emits `{ topic: 'general', themeKey }` via `SseHubService`. |
|
||||||
| 6 | `backend/src/modules/admin/dto/general.dto.ts` | `GeneralSettingsSchema` enforces string lengths, `themeKey` enum, and `eventEndUtc > eventStartUtc` via `superRefine`. |
|
| 6 | `backend/src/modules/admin/dto/general.dto.ts` | `GeneralSettingsSchema` enforces string lengths, `themeKey` enum, per-field ISO-8601 datetime check on `eventStartUtc`/`eventEndUtc` (empty string allowed), and `eventEndUtc > eventStartUtc` via `superRefine`. |
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user