AI Implementation feature(898): Admin Area General Settings and Categories 1.16 (#38)

This commit was merged in pull request #38.
This commit is contained in:
2026-07-22 19:19:46 +00:00
parent f62c77cf41
commit 113dd47371
11 changed files with 224 additions and 120 deletions
+14 -1
View File
@@ -3,7 +3,7 @@ type: api
title: Uploads Endpoints
description: Admin-only multipart upload endpoints for site logos, category icons, and challenge files.
tags: [api, uploads, multipart, admin]
timestamp: 2026-07-22T13:05:30Z
timestamp: 2026-07-22T19:18:00Z
---
# Endpoints
@@ -37,6 +37,19 @@ valid admin session and the standard CSRF cookie/header pair.
* An accepted logo keeps its sanitized filename, is written directly under
`UPLOAD_DIR`, and returns `{ publicUrl, originalFilename }`, with `publicUrl`
in the form `/uploads/{safeFilename}`.
* Category icons are decoded with Sharp and normalized to a 128-by-128 PNG.
When `categoryId` is supplied, the file is written to
`UPLOAD_DIR/icons/{categoryId}.png` (overwriting any previous icon for that
category) and the response carries `{ id: "{categoryId}.png", publicUrl,
width: 128, height: 128, mimeType: "image/png", storedPath, size,
originalFilename }`.
* Category-icon validation is strict: a missing `file`, an oversized upload,
a corrupt/non-image payload, or a buffer that Sharp cannot decode returns
`400 Bad Request`. Invalid category-icon payloads use the message
`Category icon must be a valid PNG, JPEG, GIF, or WebP image.` and **are
not persisted** — the existing icon file (if any) on disk is left untouched.
The multipart MIME type, the filename extension, and any browser `accept`
metadata are not trusted as proof of validity.
* A missing `file` part, an oversized upload, a corrupt image, or a decoded logo
in another format returns `400 Bad Request`. Invalid logo payloads use the
message `Logo must be a valid PNG, JPEG, GIF, or WebP image.` and are not
+3 -3
View File
@@ -3,7 +3,7 @@ type: architecture
title: Key Files Index
description: One-line responsibility for important source and contract-test files, including strict event-window validation and the public bootstrap SSE listener.
tags: [architecture, key-files, event-window, validation, default-challenge-ip, sse, bootstrap, migrations]
timestamp: 2026-07-22T18:37:00Z
timestamp: 2026-07-22T19:18:00Z
---
# Backend
@@ -25,7 +25,7 @@ timestamp: 2026-07-22T18:37:00Z
| `backend/src/modules/admin/general.service.ts` | Reads/writes global settings, filters available theme files, and publishes the `general` SSE notification after updates. |
| `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/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 and strict category-icon decode (rejects undecodable / non-image buffers with HTTP 400 before any filesystem write so existing icons are preserved). |
| `backend/src/modules/admin/dto/general.dto.ts` | Zod contract for `PUT /api/v1/admin/general/settings`; both event timestamps are required ISO-8601 values and end must be strictly after start. |
| `tests/backend/admin-general-event-window.spec.ts` | Focused contract tests for valid, empty, malformed, equal, and reversed event-window timestamps. |
| `tests/backend/admin-general-service.spec.ts` | General-settings schema and service tests, including per-field empty datetime failures and settings-event emission. |
@@ -52,7 +52,7 @@ timestamp: 2026-07-22T18:37:00Z
| `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/categories/category-form-modal.component.ts` | Standalone OnPush modal for create + edit; owns the `CategoryFormGroup`, exposes the pure `syncCategoryForm` helper, and reacts to `open` / `mode` / `category` signal inputs via a `markForCheck` effect so edit prefill reaches the DOM. |
| `frontend/src/app/features/admin/categories/category-form-modal.component.ts` | Standalone OnPush modal for create + edit; owns the `CategoryFormGroup`, exposes the pure `syncCategoryForm` helper, binds `[formGroup]` on its template `<form>`, accepts `errorMessage` + `saving` parent inputs (renders `cf-error` and disables OK while saving), and reacts to `open` / `mode` / `category` signal inputs via a `markForCheck` effect so edit prefill reaches the DOM. |
| `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/admin-categories-form-modal.spec.ts` | Tests the pure `syncCategoryForm` helper that drives the edit/create prefill in `CategoryFormModalComponent`: system-row abbreviation lock, user-row unlock, re-population on second invocation, clearing on create, and iconPreview passthrough. |
+43 -6
View File
@@ -3,7 +3,7 @@ type: guide
title: Admin — Categories
description: How an admin lists, creates, edits, and deletes challenge categories from the /admin/categories page, including system-row protection and challenge-attached protection.
tags: [guide, admin, categories, tester]
timestamp: 2026-07-22T18:37:00Z
timestamp: 2026-07-22T19:18:00Z
---
# When this view is available
@@ -45,6 +45,7 @@ side-nav (categories are also embedded inside the General settings page).
| Form modal | `[data-testid="cat-form-modal"]` | Create/edit dialog (`cat-form-backdrop` is the click-outside dismiss layer). |
| Delete modal | `[data-testid="cat-delete-modal"]` | Confirmation dialog (`cat-delete-backdrop` is the dismiss layer). |
| Delete error | `[data-testid="cat-delete-error"]` | Inline error message after a failed delete. |
| Form error | `[data-testid="cf-error"]` | Inline error rendered inside the form modal after a failed create/update/upload. |
# Form modal fields
@@ -54,7 +55,7 @@ side-nav (categories are also embedded inside the General settings page).
| Abbreviation (uppercase) | `cf-abbr` | Required, 26 chars; server upper-cases on save. `readonly` when editing a system row. |
| Description | `cf-desc` | Optional, max 2000 chars. |
| Icon (file picker) | `cf-icon` | Optional image; uploaded to `POST /api/v1/uploads/category-icon` and resized/normalized server-side. |
| Cancel / OK | `cf-cancel`, `cf-ok` | OK disabled while form invalid or already submitting. |
| Cancel / OK | `cf-cancel`, `cf-ok` | OK disabled while form invalid, already submitting, or while the parent is performing the async save (bound `saving` input). |
# Expected behavior
@@ -84,10 +85,23 @@ side-nav (categories are also embedded inside the General settings page).
1. Click `cat-edit-{ABBR}` → modal opens in edit mode, pre-filled with
the row's values.
2. For system rows, the abbreviation field is `readonly`.
3. On save the component issues `updateCategory(id, {...})`. If an icon
file is selected, the new icon is uploaded first and the returned
`publicUrl` replaces `iconPath` in the same update.
2. For system rows, the abbreviation field is `readonly`. For user
(non-system) rows the abbreviation is editable and is uppercased
server-side on save (the existing category row is re-sorted
alphabetically by the defensive client sort after a successful
update).
3. On save the component issues `updateCategory(id, {...})` with
`name`, `abbreviation`, `description`, and (if an icon file was
chosen) `iconPath`. When an icon file is selected, the new icon is
uploaded first via `POST /api/v1/uploads/category-icon` and the
returned `publicUrl` replaces `iconPath` in the same update.
4. On either an upload rejection (HTTP 400) or an update rejection
(HTTP 400/404/409), the modal stays open, the OK button is disabled
while the request is in flight via the bound `saving` flag, and the
server message is rendered inside the modal at
`data-testid="cf-error"`. The list is not refreshed and any
previously-valid icon on disk is preserved (the upload endpoint
does not overwrite when Sharp decode fails).
### Edit prefill mechanics
@@ -112,6 +126,29 @@ input. `syncCategoryForm` is exported from the modal file so it can be
exercised in isolation by
`tests/frontend/admin-categories-form-modal.spec.ts`.
### Save error and loading state
`CategoryFormModalComponent` is purely presentational and does not own
HTTP state. It exposes two additional signal inputs:
* `errorMessage: string | null` — when truthy, an inline error paragraph
is rendered at `data-testid="cf-error"` showing the supplied message.
* `saving: boolean` — when `true`, the OK button is disabled in addition
to the existing `form.invalid` and `submitting` guards.
The parent `AdminCategoriesComponent` owns both signals:
* `formError` — set from the server's `error.message` (or a generic
fallback) when create/update/upload fails, and cleared every time the
modal is opened or a submission begins.
* `saving` — set to `true` at the start of `onFormSubmit` and reset to
`false` in `finally`, so the modal can never get stuck in a
"submitting" state if the request rejects.
This separation matters because `deleteError` (rendered by the delete
modal at `data-testid="cat-delete-error"`) is a different state machine
and must not be reused for create/update failures.
## Delete behavior
* **System rows:** the delete modal renders
+1 -1
View File
@@ -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-22T18:37:00Z.
they need. Last regenerated 2026-07-22T19:18:00Z.
# Architecture