AI Implementation feature(891): Admin Area General Settings and Categories 1.09 (#31)
This commit was merged in pull request #31.
This commit is contained in:
+8
-3
@@ -2,8 +2,8 @@
|
||||
type: api
|
||||
title: Admin Endpoints
|
||||
description: Admin-only endpoints for user management, required general settings, categories, and supporting uploads.
|
||||
tags: [api, admin, users, general, categories]
|
||||
timestamp: 2026-07-22T14:24:08Z
|
||||
tags: [api, admin, users, general, categories, default-challenge-ip, ip-hostname, validation]
|
||||
timestamp: 2026-07-22T15:46:18Z
|
||||
---
|
||||
|
||||
# Endpoints
|
||||
@@ -53,7 +53,12 @@ removes the row.
|
||||
`superRefine` additionally requires `eventEndUtc > eventStartUtc` and
|
||||
reports `eventEndUtc must be strictly after eventStartUtc` on the end
|
||||
field when both values parse but are out of order.
|
||||
* `defaultChallengeIp` 1–255 chars
|
||||
* `defaultChallengeIp` is trimmed server-side; empty/whitespace-only values
|
||||
are rejected (`defaultChallengeIp is required and cannot contain only
|
||||
whitespace`), and the trimmed value must be a complete IPv4 address or a
|
||||
valid hostname, otherwise `400 VALIDATION_FAILED` with a per-field
|
||||
`defaultChallengeIp` issue (`defaultChallengeIp must be a valid IPv4
|
||||
address or hostname`). Maximum length is 255 characters.
|
||||
* `registrationsEnabled` boolean
|
||||
|
||||
On success the handler persists every field via `SettingsService`,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
type: architecture
|
||||
title: Key Files Index
|
||||
description: One-line responsibility for important source and contract-test files, including strict event-window validation.
|
||||
tags: [architecture, key-files, event-window, validation]
|
||||
timestamp: 2026-07-22T14:24:08Z
|
||||
tags: [architecture, key-files, event-window, validation, default-challenge-ip]
|
||||
timestamp: 2026-07-22T15:46:18Z
|
||||
---
|
||||
|
||||
# Backend
|
||||
@@ -49,8 +49,8 @@ timestamp: 2026-07-22T14:24:08Z
|
||||
| `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/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 General Settings helpers, including required datetime validation, field messages, UTC conversion, and end-after-start validation. |
|
||||
| `tests/frontend/admin-general-pure.spec.ts` | Pure client-contract tests for required event timestamps, datetime messaging, UTC conversion, and event-window ordering. |
|
||||
| `frontend/src/app/features/admin/general.pure.ts` | Pure General Settings helpers, including required datetime validation, field messages, UTC conversion, end-after-start validation, and the default-challenge-address IPv4/hostname validator/normalizer/message trio. |
|
||||
| `tests/frontend/admin-general-pure.spec.ts` | Pure client-contract tests for required event timestamps, datetime messaging, UTC conversion, event-window ordering, and default-challenge-address validation, error mapping, and normalization. |
|
||||
| `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. |
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
type: guide
|
||||
title: Admin — General Settings
|
||||
description: How an admin edits required platform-wide settings, including the validated event window, from the /admin/general page.
|
||||
tags: [guide, admin, settings, general, tester, datetime, validation, utc, timezone]
|
||||
timestamp: 2026-07-22T15:09:39Z
|
||||
tags: [guide, admin, settings, general, tester, datetime, validation, utc, timezone, default-challenge-ip, ip-hostname]
|
||||
timestamp: 2026-07-22T15:46:18Z
|
||||
---
|
||||
|
||||
# When this view is available
|
||||
@@ -44,7 +44,7 @@ The page is a single reactive form with these controls (every
|
||||
| Global theme | `general-themeKey` | `themeKey` | `<select>` populated from `/themes`. Value is one of `THEME_IDS`. |
|
||||
| Event start (UTC) | `general-eventStart` | `eventStartUtc` | Required valid ISO-8601 datetime. Empty or malformed input renders `general-eventStart-error` and disables Save. |
|
||||
| Event end (UTC) | `general-eventEnd` | `eventEndUtc` | Required valid ISO-8601 datetime. Empty or malformed input renders `general-eventEnd-error`; it must also be strictly after Event start — when it is not, the same `general-eventEnd-error` region displays "Event end must be after event start." |
|
||||
| Default challenge IP | `general-defaultIp` | `defaultChallengeIp` | Required, max 255 chars. |
|
||||
| Default challenge IP | `general-defaultIp` | `defaultChallengeIp` | Required, non-blank, max 255 chars after trimming; must be a complete IPv4 address or a valid hostname; trimmed server-side. Renders `general-defaultIp-error` for empty/whitespace or malformed input. |
|
||||
| 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`. |
|
||||
| Event controls | `general-event-toggle` | (derived) | Disabled button whose text is the derived event state (`UNCONFIGURED` / `COUNTDOWN` / `RUNNING` / `STOPPED`). |
|
||||
@@ -124,6 +124,51 @@ Save, the control is reset to untouched/pristine and the
|
||||
`pageTitleTouchedOrDirty` signal is cleared, so reloading valid
|
||||
settings does not flash a stale error.
|
||||
|
||||
# Default challenge IP inline error message
|
||||
|
||||
The inline error element under the Default challenge IP field
|
||||
(`general-defaultIp-error`) is driven by the pure helpers exported
|
||||
from `frontend/src/app/features/admin/general.pure.ts`:
|
||||
|
||||
* `isValidDefaultChallengeAddress(value)` — returns `true` only for a
|
||||
complete IPv4 address (four 0–255 octets, no leading zeros on a
|
||||
multi-digit octet) or a valid RFC-1123 hostname.
|
||||
* `defaultChallengeAddressError(value)` — returns `'required'` for
|
||||
empty/whitespace-only input and `'format'` for malformed addresses,
|
||||
otherwise `null`.
|
||||
* `defaultChallengeAddressMessage(value, controlErrors)` — renders the
|
||||
human-readable text used by the inline region.
|
||||
* `normalizeDefaultChallengeAddress(value)` — trims surrounding
|
||||
whitespace from the value submitted to the backend.
|
||||
|
||||
The component keeps three local `signal`s — `defaultChallengeIpValue`,
|
||||
`defaultChallengeIpInvalid`, and `defaultChallengeIpTouchedOrDirty` —
|
||||
mirroring the Page-title pattern. The reactive-form control uses a
|
||||
custom validator that returns `{ defaultChallengeAddressFormat: true }`
|
||||
for malformed addresses (alongside `Validators.required` for the empty
|
||||
case), and the `computed` signals `showDefaultChallengeIpError` and
|
||||
`defaultChallengeIpMessage` drive:
|
||||
|
||||
* `[attr.aria-invalid]` and `[attr.aria-describedby]` on the 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-defaultIp-error"`.
|
||||
|
||||
| Condition | Message |
|
||||
|------------------------------------------------------------|--------------------------------------------------------|
|
||||
| Empty or whitespace-only value | `Default challenge IP is required and cannot contain only whitespace.` |
|
||||
| Incomplete IPv4 (`10.0.0.`, `10.0.0`), out-of-range octets, or malformed hostname | `Default challenge IP must be a valid IPv4 address or hostname.` |
|
||||
| Valid IPv4 or hostname | `null` (no error rendered; Save enabled). |
|
||||
|
||||
After a successful Save the component trims the value through
|
||||
`normalizeDefaultChallengeAddress`, persists the canonical form via
|
||||
`PUT /api/v1/admin/general/settings`, and resets the control to
|
||||
untouched/pristine, so a freshly-loaded valid value does not flash a
|
||||
stale error. The server `GeneralSettingsSchema` performs the same
|
||||
trim and format check, so invalid values return `400 VALIDATION_FAILED`
|
||||
with a per-field `defaultChallengeIp` issue and the stored value is
|
||||
unchanged.
|
||||
|
||||
# Event start / end inline error messages
|
||||
|
||||
The inline error elements under the Event start and Event end inputs
|
||||
@@ -248,6 +293,7 @@ suite, with cases that lock in:
|
||||
| Event start inline error | `[data-testid="general-eventStart-error"]` |
|
||||
| Event end inline error | `[data-testid="general-eventEnd-error"]` (renders both the per-field `invalidDatetime` message and the cross-field `endBeforeStart` "Event end must be after event start." message) |
|
||||
| Page-title inline error | `[data-testid="general-pageTitle-error"]` |
|
||||
| Default challenge IP inline error | `[data-testid="general-defaultIp-error"]` |
|
||||
|
||||
# Architecture map
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ scoreboard, an event window with a public countdown, theming, and admin
|
||||
controls.
|
||||
|
||||
The docs below are organized by purpose so agents can pull just the slice
|
||||
they need. Last regenerated 2026-07-22T15:09:39Z.
|
||||
they need. Last regenerated 2026-07-22T15:46:18Z.
|
||||
|
||||
# Architecture
|
||||
|
||||
|
||||
Reference in New Issue
Block a user