AI Implementation feature(889): Admin Area General Settings and Categories 1.07 (#29)
This commit was merged in pull request #29.
This commit is contained in:
@@ -3,7 +3,7 @@ type: database
|
||||
title: Challenge Tables
|
||||
description: category, challenge, challenge_file, and solve tables — how CTF challenges and scoring are stored.
|
||||
tags: [database, challenge, category, solve]
|
||||
timestamp: 2026-07-21T14:18:00Z
|
||||
timestamp: 2026-07-22T14:50:25Z
|
||||
---
|
||||
|
||||
# Tables
|
||||
@@ -18,8 +18,8 @@ timestamp: 2026-07-21T14:18:00Z
|
||||
| `abbreviation` | TEXT | Short label (e.g. `CRY`), 2–6 chars, server-uppercased. Globally unique — enforced by `uq_category_abbreviation`. |
|
||||
| `description` | TEXT | Optional description. |
|
||||
| `icon_path` | TEXT | Default icon URL (e.g. `/uploads/icons/CRY.png`). |
|
||||
| `created_at` | TEXT | ISO 8601 timestamp the row was created (added by `AddCategoryTimestampsAndUniqueAbbrev1700000000200`). |
|
||||
| `updated_at` | TEXT | ISO 8601 timestamp the row was last updated (added by the same migration; bumped on `PUT /api/v1/admin/categories/:id`). |
|
||||
| `created_at` | TEXT | ISO 8601 timestamp the row was created. Declared in the initial `InitSchema1700000000000` migration (defaulting to `strftime('%Y-%m-%dT%H:%M:%fZ','now')`); the `AddCategoryTimestampsAndUniqueAbbrev1700000000200` migration adds it as a no-op for older pre-existing databases. |
|
||||
| `updated_at` | TEXT | ISO 8601 timestamp the row was last updated (bumped on `PUT /api/v1/admin/categories/:id`). Same provenance as `created_at`. |
|
||||
|
||||
System rows are seeded by the
|
||||
`UpdateSystemCategoryKeys1700000000300` migration so the canonical
|
||||
|
||||
@@ -3,7 +3,7 @@ type: database
|
||||
title: Database Schema Overview
|
||||
description: SQLite (better-sqlite3) schema for HIPCTF: tables, relationships, indexes, and WAL journal mode.
|
||||
tags: [database, sqlite, typeorm, schema]
|
||||
timestamp: 2026-07-21T14:18:00Z
|
||||
timestamp: 2026-07-22T14:50:25Z
|
||||
---
|
||||
|
||||
# Overview
|
||||
@@ -17,6 +17,12 @@ The schema is created by a single migration
|
||||
(`backend/src/database/migrations/1700000000000-InitSchema.ts`) and seeded
|
||||
by a second migration
|
||||
(`backend/src/database/migrations/1700000000100-SeedSystemData.ts`).
|
||||
The initial migration declares every column used by the entities
|
||||
(including `category.created_at` and `category.updated_at`), so the
|
||||
later `AddCategoryTimestampsAndUniqueAbbrev1700000000200` and
|
||||
`UpdateSystemCategoryKeys1700000000300` migrations only patch legacy
|
||||
databases that pre-date those columns and reseed the canonical system
|
||||
rows.
|
||||
|
||||
# Tables
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ 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]
|
||||
timestamp: 2026-07-22T14:24:08Z
|
||||
timestamp: 2026-07-22T14:50:25Z
|
||||
---
|
||||
|
||||
# When this view is available
|
||||
@@ -17,7 +17,7 @@ by navigating directly to the URL.
|
||||
|------------------|----------------------------------------------------------------------------|------------------------------------------------|
|
||||
| Client route | `frontend/src/app/app.routes.ts` | `/admin/general` child of `adminGuard`. |
|
||||
| Client component | `frontend/src/app/features/admin/general.component.ts` | `AdminGeneralComponent.ngOnInit` fetches settings + themes. |
|
||||
| Client predicate | `frontend/src/app/features/admin/general.pure.ts` (`deriveEventState`, `pageTitleError`, `pageTitleMessage`) | Pure helpers for event-state derivation and Page-title error/message mapping. |
|
||||
| Client predicate | `frontend/src/app/features/admin/general.pure.ts` (`deriveEventState`, `pageTitleError`, `pageTitleMessage`, `eventEndFieldMessage`) | Pure helpers for event-state derivation, Page-title error/message mapping, and the merged Event End field message (per-field `invalidDatetime` + cross-field `endBeforeStart`). |
|
||||
| Server route | `backend/src/modules/admin/admin-general.controller.ts` | `GET/PUT /api/v1/admin/general/settings` + `GET /api/v1/admin/general/themes`. |
|
||||
|
||||
# How to access (tester steps)
|
||||
@@ -43,7 +43,7 @@ The page is a single reactive form with these controls (every
|
||||
| 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`. |
|
||||
| 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 (`general-endBeforeStart`). |
|
||||
| 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. |
|
||||
| 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`. |
|
||||
@@ -71,9 +71,9 @@ The page is a single reactive form with these controls (every
|
||||
* `start <= now < end` → `RUNNING`
|
||||
* `now >= end` → `STOPPED`
|
||||
* **End-before-start validation:** if the user picks an end that is not
|
||||
strictly after the start, the form becomes invalid and
|
||||
`general-endBeforeStart` appears under the end input. Save remains
|
||||
disabled.
|
||||
strictly after the start, the form becomes invalid and the
|
||||
`general-eventEnd-error` region under the end input renders
|
||||
"Event end must be after event start." Save remains disabled.
|
||||
* **Per-field datetime validation:** clearing a field or supplying a value
|
||||
that cannot be parsed as an ISO-8601 datetime makes that control invalid.
|
||||
The affected input receives `aria-invalid="true"`, references its inline
|
||||
@@ -136,6 +136,16 @@ two pure helpers exported from
|
||||
* `datetimeMessage(fieldLabel, value, controlErrors)` — renders the
|
||||
human-readable message `"<Field> must be a valid ISO-8601 datetime."`
|
||||
when `invalidDatetime` is set or when the raw value is unparseable.
|
||||
* `eventEndFieldMessage(controlErrors, crossFieldErrors)` — returns the
|
||||
canonical message for the Event End field's own error region. It
|
||||
prefers the per-field ISO message
|
||||
(`"Event end must be a valid ISO-8601 datetime."`) when
|
||||
`controlErrors?.['invalidDatetime']` is set, otherwise it surfaces the
|
||||
cross-field `endBeforeStart` message
|
||||
(`"Event end must be after event start."`), otherwise `null`. The
|
||||
component reads this helper through the `eventEndFieldMessageText`
|
||||
computed signal so both error kinds render inside the single
|
||||
`general-eventEnd-error` element.
|
||||
|
||||
The component wires both controls (`eventStartUtc`, `eventEndUtc`) with
|
||||
this validator and keeps three local `signal`s per field — `Value`,
|
||||
@@ -160,11 +170,14 @@ 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
|
||||
emitted by the form-level validator whenever both valid timestamps are
|
||||
present and end is not strictly after start. The Event end input also
|
||||
receives `aria-invalid="true"`, references both possible error elements,
|
||||
and exposes `Event end must be after event start.` as its title.
|
||||
The cross-field `endBeforeStart` error is emitted by the form-level
|
||||
validator whenever both valid timestamps are present and end is not
|
||||
strictly after start. The Event end input receives `aria-invalid="true"`,
|
||||
references `general-eventEnd-error` via `aria-describedby`, and exposes
|
||||
`Event end must be after event start.` as its title — the message itself
|
||||
is rendered inside the per-field error region
|
||||
(`data-testid="general-eventEnd-error"`) by the
|
||||
`eventEndFieldMessage` helper.
|
||||
|
||||
# Visual elements
|
||||
|
||||
@@ -175,9 +188,8 @@ and exposes `Event end must be after event start.` as its title.
|
||||
| Load error | `[data-testid="general-error"]` |
|
||||
| Form | `[data-testid="general-form"]` |
|
||||
| Welcome preview | `[data-testid="general-welcome-preview"]` |
|
||||
| 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"]` |
|
||||
| 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"]` |
|
||||
|
||||
# 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-22T14:24:08Z.
|
||||
they need. Last regenerated 2026-07-22T14:50:25Z.
|
||||
|
||||
# Architecture
|
||||
|
||||
|
||||
Reference in New Issue
Block a user