AI Implementation feature(882): Admin Area General Settings and Categories 1.00 (#22)
This commit was merged in pull request #22.
This commit is contained in:
+115
-17
@@ -1,19 +1,120 @@
|
||||
---
|
||||
type: api
|
||||
title: Admin Endpoints
|
||||
description: User management endpoints mounted under /api/v1/admin (admin role required).
|
||||
tags: [api, admin, users]
|
||||
timestamp: 2026-07-21T18:28:00Z
|
||||
description: Admin-only endpoints for user management, general settings, categories, and supporting uploads.
|
||||
tags: [api, admin, users, general, categories]
|
||||
timestamp: 2026-07-22T12:00:00Z
|
||||
---
|
||||
|
||||
# Endpoints
|
||||
|
||||
| Method | Path | Auth | Source |
|
||||
|---------|-----------------------|---------|-----------------------------------------------------|
|
||||
| `GET` | `/api/v1/admin/users` | Admin | `backend/src/modules/admin/admin.controller.ts` |
|
||||
| `POST` | `/api/v1/admin/users` | Admin | Same. |
|
||||
| `PATCH` | `/api/v1/admin/users/:id` | Admin | Same. |
|
||||
| `DELETE`| `/api/v1/admin/users/:id` | Admin | Same. |
|
||||
All handlers below are mounted behind `JwtAuthGuard` (global) +
|
||||
`AdminGuard` + `@Roles('admin')`, so they require a valid admin JWT.
|
||||
CSRF is enforced on every mutating call (standard cookie + header
|
||||
pattern; nothing is added to the skip list).
|
||||
|
||||
## User management — `/api/v1/admin`
|
||||
|
||||
| Method | Path | Source |
|
||||
|----------|-------------------------------|---------------------------------------------------|
|
||||
| `GET` | `/api/v1/admin/users` | `backend/src/modules/admin/admin.controller.ts` |
|
||||
| `POST` | `/api/v1/admin/users` | Same. |
|
||||
| `PATCH` | `/api/v1/admin/users/:id` | Same. |
|
||||
| `DELETE` | `/api/v1/admin/users/:id` | Same. |
|
||||
|
||||
`AdminService` enforces the **last-admin invariant** (`LAST_ADMIN` /
|
||||
`409`) — the final admin row cannot be demoted or deleted. `POST`
|
||||
creates a new user; `PATCH .../:id` changes role / status; `DELETE .../:id`
|
||||
removes the row.
|
||||
|
||||
## General settings — `/api/v1/admin/general`
|
||||
|
||||
| Method | Path | Source |
|
||||
|--------|---------------------------------------|-------------------------------------------------------------|
|
||||
| `GET` | `/api/v1/admin/general/settings` | `backend/src/modules/admin/admin-general.controller.ts` |
|
||||
| `PUT` | `/api/v1/admin/general/settings` | Same. |
|
||||
| `GET` | `/api/v1/admin/general/themes` | Same. |
|
||||
|
||||
`GET /settings` returns the full `GeneralSettingsView`
|
||||
(`pageTitle`, `logo`, `welcomeMarkdown`, `themeKey`, `eventStartUtc`,
|
||||
`eventEndUtc`, `defaultChallengeIp`, `registrationsEnabled`).
|
||||
|
||||
`PUT /settings` is validated by `GeneralSettingsSchema` (zod):
|
||||
|
||||
* `pageTitle` 1–120 chars
|
||||
* `logo` up to 2048 chars (a public URL — uploaded separately)
|
||||
* `welcomeMarkdown` up to 64 000 chars
|
||||
* `themeKey` is one of `THEME_IDS`
|
||||
* `eventStartUtc` / `eventEndUtc` parseable dates; `superRefine`
|
||||
enforces `eventEndUtc > eventStartUtc` and reports the issue on
|
||||
`eventEndUtc`
|
||||
* `defaultChallengeIp` 1–255 chars
|
||||
* `registrationsEnabled` boolean
|
||||
|
||||
On success the handler persists every field via `SettingsService`,
|
||||
emits an SSE `general` event (`{ topic: 'general', themeKey }`) via
|
||||
`SseHubService`, and returns the updated view.
|
||||
|
||||
`GET /themes` returns the `ThemeView[]` for which a corresponding
|
||||
JSON file exists under `THEMES_DIR`. Each item is `{ id, key, name }`.
|
||||
|
||||
See [Admin — General Settings](/guides/admin-general-settings.md).
|
||||
|
||||
## Categories — `/api/v1/admin/categories`
|
||||
|
||||
| Method | Path | Source |
|
||||
|----------|-------------------------------------|-----------------------------------------------------------------|
|
||||
| `GET` | `/api/v1/admin/categories` | `backend/src/modules/admin/admin-categories.controller.ts` |
|
||||
| `POST` | `/api/v1/admin/categories` | Same. |
|
||||
| `PUT` | `/api/v1/admin/categories/:id` | Same. |
|
||||
| `DELETE` | `/api/v1/admin/categories/:id` | Same. |
|
||||
|
||||
Behavior:
|
||||
|
||||
* `GET` returns all categories sorted by `LOWER(abbreviation)` ascending.
|
||||
* `POST` uppercases the abbreviation, rejects duplicates with `409 CONFLICT`,
|
||||
and persists the row. Body validated by `CreateCategorySchema`
|
||||
(`name` 1–120 chars; `abbreviation` 2–6 chars; `description` up to
|
||||
2000 chars; `iconPath` optional, up to 2048 chars).
|
||||
* `PUT` validates the body with `UpdateCategorySchema` (every field
|
||||
optional). System rows (`system_key IS NOT NULL`) cannot change their
|
||||
abbreviation — server returns `409 SYSTEM_PROTECTED`. Duplicate
|
||||
abbreviations on user rows return `409 CONFLICT`. Updates the
|
||||
`updated_at` timestamp on success.
|
||||
* `DELETE` is blocked for system rows (`403 SYSTEM_PROTECTED`) and for
|
||||
rows with at least one attached challenge (`409 CATEGORY_HAS_CHALLENGES`
|
||||
with `{ count }` in `details`). Returns `404 NOT_FOUND` for unknown ids.
|
||||
|
||||
Response shape (`CategoryView`):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Cryptography",
|
||||
"abbreviation": "CRY",
|
||||
"description": "Cryptographic challenges",
|
||||
"iconPath": "/uploads/icons/CRY.png",
|
||||
"isSystem": true,
|
||||
"systemKey": "CRY",
|
||||
"createdAt": "2026-07-21T18:00:00.000Z",
|
||||
"updatedAt": "2026-07-21T18:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
See [Admin — Categories](/guides/admin-categories.md).
|
||||
|
||||
## Supporting uploads
|
||||
|
||||
Logo and category-icon uploads live on the uploads module and are
|
||||
documented in [Uploads Endpoints](/api/uploads.md):
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|-----------------------------------|----------------------------------|
|
||||
| `POST` | `/api/v1/uploads/logo` | Logo used by General settings. |
|
||||
| `POST` | `/api/v1/uploads/category-icon` | Icon used by Categories. |
|
||||
|
||||
Both endpoints are admin-only and use `FileInterceptor('file')` for a
|
||||
single multipart part named `file`.
|
||||
|
||||
# Guard chain
|
||||
|
||||
@@ -21,16 +122,13 @@ timestamp: 2026-07-21T18:28:00Z
|
||||
is `@Public()`. Admin handlers are not.
|
||||
2. `AdminGuard` (`backend/src/common/guards/admin.guard.ts`) requires
|
||||
`req.user.role === 'admin'`.
|
||||
|
||||
# Behavior
|
||||
|
||||
* `AdminService` enforces the **last-admin invariant** (`LAST_ADMIN` /
|
||||
`409`) — the final admin row cannot be demoted or deleted.
|
||||
* `POST /api/v1/admin/users` creates a new user; `PATCH /api/v1/admin/users/:id`
|
||||
changes role / status; `DELETE /api/v1/admin/users/:id` removes the row.
|
||||
3. `RolesGuard` enforces `@Roles('admin')` metadata on the controller.
|
||||
|
||||
# See also
|
||||
|
||||
- [Backend Module Map](/architecture/backend-modules.md)
|
||||
- [Admin Shell & User Management](/guides/admin-shell.md)
|
||||
- [Admin Shell](/guides/admin-shell.md)
|
||||
- [Admin — General Settings](/guides/admin-general-settings.md)
|
||||
- [Admin — Categories](/guides/admin-categories.md)
|
||||
- [Uploads Endpoints](/api/uploads.md)
|
||||
- [REST API Overview](/api/rest-overview.md)
|
||||
|
||||
Reference in New Issue
Block a user