178 lines
31 KiB
Markdown
178 lines
31 KiB
Markdown
---
|
||
type: architecture
|
||
title: Key Files Index
|
||
description: One-line responsibility for important source and contract-test files, including event-window, challenge, scoreboard, session, notification, and blog flows.
|
||
tags: [architecture, key-files, event-window, validation, default-challenge-ip, sse, bootstrap, migrations, challenges, import, board, notifications, per-user, session, blog]
|
||
timestamp: 2026-07-23T16:10:00Z
|
||
---
|
||
|
||
# Backend
|
||
|
||
| File | Responsibility |
|
||
|---|---|
|
||
| `backend/src/main.ts` | Bootstraps Nest, middleware, OpenAPI, static assets, and SPA fallback. |
|
||
| `backend/src/app.module.ts` | Wires feature modules and global providers. |
|
||
| `backend/src/config/env.schema.ts` | Zod-validated runtime config; canonical defaults for `DATABASE_PATH` (`./data/db.sqlite`) and `UPLOAD_DIR` (`./data/uploads`). |
|
||
| `backend/src/database/database.module.ts` | Configures TypeORM with SQLite and imports the shared `FilesystemTransactionModule` used by pre-TypeORM startup recovery. |
|
||
| `backend/src/database/database-init.service.ts` | Before opening SQLite, resolves durable swap manifests to a coherent old or verified-new database/uploads generation, fails startup closed on ambiguity, best-effort sweeps stale unowned swap artifacts, then runs migrations, enforces WAL, verifies seeds, and best-effort seeds the canonical system-category icon PNGs into `UPLOAD_DIR/icons` so a fresh clone never serves 404 icons. |
|
||
| `backend/src/database/system-category-icons.ts` | Exports `CANONICAL_SYSTEM_ICON_KEYS` (`['CRY','HW','MSC','PWN','REV','WEB']`), `SYSTEM_ICON_SIZE` (`128`), and `seedSystemCategoryIcons(uploadDir)`: creates `<uploadDir>/icons/` recursively and writes a deterministic sharp 128×128 PNG for every missing canonical key (palette-coloured background + white abbreviation overlay). Idempotent — existing non-empty files are reported as `skipped` so admin uploads are preserved across restarts. |
|
||
| `backend/src/modules/admin/system/filesystem-transaction.module.ts` | Exports one shared `FilesystemTransactionService` provider to `DatabaseModule` and `AdminSystemModule`, avoiding a circular dependency. |
|
||
| `backend/src/database/migrations/1700000000400-RepairCategorySchemaAndSystemCategories.ts` | Forward-only repair migration: adds `created_at`/`updated_at` to legacy six-column `category` tables, deletes obsolete `FOR`/`OSI` system rows, rewrites canonical row metadata, inserts any missing canonical key, and re-asserts unique indexes. Exports `CANONICAL_SYSTEM_CATEGORIES`. |
|
||
| `backend/src/common/guards/jwt-auth.guard.ts` | Global JWT authorization guard. |
|
||
| `backend/src/common/middleware/csrf.middleware.ts` | Double-submit CSRF protection. |
|
||
| `backend/src/common/services/event-status.service.ts` | Computes the event-window state machine. |
|
||
| `backend/src/common/services/sse-hub.service.ts` | In-process pub/sub for server-sent event streams. |
|
||
| `backend/src/modules/system/system.controller.ts` | Registers bootstrap, event status, settings, and SSE endpoints. |
|
||
| `backend/src/modules/system/system.service.ts` | Builds the public bootstrap payload from admin existence, settings, the theme loader, and password-policy configuration. |
|
||
| `backend/src/modules/admin/admin-general.controller.ts` | Registers admin general-settings and available-theme endpoints. |
|
||
| `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 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. |
|
||
| `tests/backend/admin-challenges-import-replace.spec.ts` | Contract tests for the confirmed-import full-replace: wipes unrelated pre-existing challenges, unlinks wiped disk files, empty archive, unknown-category skip-then-wipe. |
|
||
| `backend/src/modules/challenges/challenges.module.ts` | Wires `ChallengesController`, `ChallengesEventsController`, `ChallengesService`, and the four TypeORM entities (`ChallengeEntity`, `CategoryEntity`, `SolveEntity`, `UserEntity`). |
|
||
| `backend/src/modules/challenges/challenges.controller.ts` | Registers `/api/v1/challenges/{board,status,:id,:id/solves}` (all JWT-protected). |
|
||
| `backend/src/modules/challenges/events.controller.ts` | Authenticated SSE `/api/v1/events` merging status ticks + hub `general` pushes + `scoreboard$` solve frames. |
|
||
| `backend/src/modules/challenges/challenges.service.ts` | `getBoard` / `getDetail` / `submitFlag` + `dataSource.transaction` for race-safe idempotent solves; publishes `solve` SSE frames on success. |
|
||
| `backend/src/modules/challenges/scoreboard.controller.ts` | Registers the four authenticated scoreboard read-only endpoints under `/api/v1/scoreboard/{ranking,matrix,event-log,graph}`. |
|
||
| `backend/src/modules/challenges/scoreboard.service.ts` | Builds the `RankingRowDto[]`, `MatrixViewDto`, `EventLogRowDto[]`, and `GraphViewDto` projections used by the `/scoreboard` page; consumes `SettingsService` for the event window and `EventStatusService` for `state`. |
|
||
| `backend/src/modules/challenges/dto/scoreboard.dto.ts` | `EventLogQuerySchema` (zod) + TypeScript DTO interfaces (`RankingRowDto`, `MatrixViewDto`, `EventLogRowDto`, `GraphViewDto`, `GraphSeriesDto`, `GraphPointDto`) for the four scoreboard endpoints. |
|
||
| `backend/src/modules/challenges/dto/challenges.dto.ts` | zod contracts (`SolveSubmitBodySchema`, `BoardQuerySchema`, `ChallengeIdParamSchema`) + DTO types for the player-facing board. |
|
||
| `backend/src/modules/challenges/scoring.util.ts` | Pure `computeLivePoints`, `rankBonusForPosition`, `compareDifficulty`, `computeAwardedPoints` (base + rank bonus), `PLAYER_COLOR_PALETTE` + `stablePlayerColorIndex` (FNV-1a → palette index), and constant-time `safeEqualString` for flag comparison. |
|
||
| `backend/src/common/errors/error-codes.ts` | Canonical error code map (now includes `EVENT_NOT_RUNNING`, `FLAG_INCORRECT`, `CHALLENGE_DISABLED`). |
|
||
| `backend/src/modules/blog/blog.module.ts` | Wires the public and admin blog controllers/services to `BlogPostEntity`. |
|
||
| `backend/src/modules/blog/admin-blog.controller.ts` | Registers administrator CRUD routes at `/api/v1/admin/blog/posts`. |
|
||
| `backend/src/modules/blog/admin-blog.service.ts` | Lists, creates, updates, publishes/drafts, and deletes posts while preserving first-publication time. |
|
||
| `backend/src/modules/blog/dto/blog.dto.ts` | Public/admin response contracts and zod validation for title, body, status, and post id. |
|
||
| `tests/backend/blog-admin.spec.ts` | Admin authorization, CRUD, validation, publication-timestamp, deletion, and public draft-hiding contracts. |
|
||
|
||
# Frontend
|
||
|
||
| File | Responsibility |
|
||
|---|---|
|
||
| `frontend/src/main.ts` | Bootstraps Angular and registers HTTP interceptors. |
|
||
| `frontend/proxy.conf.json` | Angular dev-server proxy: forwards `/api` and `/uploads` to `http://localhost:3000`. |
|
||
| `frontend/angular.json` | Angular workspace config; the `serve` target references `proxy.conf.json`. |
|
||
| `package.json` (root) | Root npm scripts; `dev` runs both workspaces via `concurrently`, `dev:backend` / `dev:frontend` run one at a time. |
|
||
| `setup.sh` | Idempotent setup: installs root + workspace deps, builds both packages, creates `./data/uploads`. |
|
||
| `frontend/src/app/app.routes.ts` | Defines public, shell, child, and admin routes. |
|
||
| `frontend/src/app/core/services/bootstrap.service.ts` | Caches and exposes bootstrap state, refreshes it after admin updates, and applies the resolved theme. |
|
||
| `frontend/src/app/core/services/bootstrap.types.ts` | Defines bootstrap/theme payload types and maps theme tokens to CSS custom properties. |
|
||
| `frontend/src/app/core/services/admin.service.ts` | Calls admin settings, theme-list, category, and upload APIs. |
|
||
| `frontend/src/app/features/home/home.component.ts` | Authenticated shell container; starts user and event state services; on logout and on session invalidation also calls `challengesStore.reset()` to flush any per-user board cache before navigating to `/login`. |
|
||
| `frontend/src/app/core/services/auth.service.ts` | Signal-backed access token and current-user state; owns the cross-tab invalidation `BroadcastChannel` + `storage`-event fallback. |
|
||
| `frontend/src/app/core/services/auth-session-events.pure.ts` | Pure cross-tab message encoding/validation and namespaced constants used by the auth broadcast channel. |
|
||
| `frontend/src/app/core/services/authenticated-event-source.service.ts` | Fetch-based authenticated SSE transport with frame parsing, abort support, and a separate `'unauthorized'` event for `401`/`403`. |
|
||
| `frontend/src/app/core/services/bootstrap-event.service.ts` | Root-provided public SSE listener that subscribes to `/api/v1/event/stream`, filters `topic === 'general'` frames, and triggers `BootstrapService.refresh()` so the landing modal stays in sync with admin general-settings updates. |
|
||
| `frontend/src/app/core/services/bootstrap-event.pure.ts` | Pure helpers for the public bootstrap SSE listener: `isBootstrapGeneralFrame` predicate, SSE line parser, and the `makeBootstrapEventSource` factory (fetch + `ReadableStream` opener with frame buffering and idempotent `close()`). |
|
||
| `frontend/src/app/core/services/event-status.store.ts` | Event state signal store, one-second countdown timer, and the optional `onUnauthorized` callback wiring; exposes `start()`/`stop()`/`closeTransport()` for transport lifecycle and `subscribeReloadAtCountdownZero(handler)` (with `reloadAtCountdownZero(handler)` kept as a back-compat wrapper) so the reload handler is owned by its subscriber rather than the transport. |
|
||
| `frontend/src/app/core/services/event-status.pure.ts` | Event payload types, transport interface (including `'unauthorized'` listener), pure countdown helpers (`formatDdHhMm` returns `DD:HH:mm:ss` and `deriveCountdownText`), and the `LED_COLOR_BY_STATE` map used by the shell LED. |
|
||
| `frontend/src/app/features/home/home.component.ts` | Authenticated shell container; starts user and event state services, subscribes to peer invalidation, resets both `UserStore` and `ChallengesStore`, and navigates to `/login` when the session is invalidated elsewhere. |
|
||
| `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/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, 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. |
|
||
| `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/admin-challenges-import-autoclose.spec.ts` | Pure helpers (`summarizeImportResult`, `importErrorMessage`) and modal auto-close branch for the confirmed-import handler. |
|
||
| `frontend/src/app/features/challenges/challenges.page.ts` | `/challenges` smart page: gate logic (countdown/running/stopped/unconfigured), modal lifecycle, SSE wiring on `/api/v1/events`, and the page-owned countdown-zero reload (handler registered in the constructor with the disposer wired into `DestroyRef`). |
|
||
| `frontend/src/app/features/challenges/challenges.store.ts` | Signal store: board, event state, per-card solve listeners, SSE solve-frame mutation, submit response application, stop() lifecycle, public `reset()` for the session-boundary flush, and `setMyUserId(id)` which now returns the previous id and clears the cache whenever the cached state belongs to an unknown or different user. |
|
||
| `frontend/src/app/features/challenges/challenges.service.ts` | HTTP service for `/api/v1/challenges/{board,status,:id,:id/solves}` returning typed `ApiErrorEnvelope`s. `getDetail` always requests `?include=solvers` so the modal can render the solvers list in one round-trip; `getBoard` only attaches `include=solvers` when the caller opts in. |
|
||
| `frontend/src/app/features/challenges/challenges.pure.ts` | Pure types and helpers (`BoardCard`, `SolverRow`, `SolveEventPayload`, `parseSolveEvent`, `mergeSolveEventIntoSolvers`, `messageForSolveError`, `formatDdHhMm`). |
|
||
| `frontend/src/app/features/challenges/category-column.component.ts` | Renders a single category column (header + cards) on the challenges board. |
|
||
| `frontend/src/app/features/challenges/challenge-card.component.ts` | Renders a single challenge card on the board as a semantic `<button>` (difficulty, live points, solve count, `✓` when solved by the player). Exposes `data-testid="challenge-card-<id>"`, `data-solved`, `aria-pressed`, an accessible name (`Challenge <name>, solved|not solved`), and an inner `data-testid="challenge-check-<id>"` for the solved glyph. |
|
||
| `frontend/src/app/features/challenges/challenge-modal.component.ts` | Challenge detail modal: description, flag form, awarded banner, solvers list, live-solve updates. |
|
||
| `frontend/src/app/features/scoreboard/scoreboard.page.ts` | `/scoreboard` smart page: 4 tabs (Ranking / Matrix / Event Log / Score Graph) wired through `ScoreboardStore`, SSE `solve`-frame subscription on `/api/v1/events`, and lifecycle (`loadAll` + `wireSse` on init, `stop()` on destroy). |
|
||
| `frontend/src/app/features/scoreboard/scoreboard.store.ts` | Signal store for the four scoreboard projections + active tab + SSE lifecycle; mutates `ranking`, `matrix`, `event-log`, and `graph` state from each `solve` frame via pure helpers (`parseSolveEventIntoRanking`, `mutateMatrixFromSolve`, `applySolveToGraph`, `dedupEventLogBySolveId`); exposes `wireSse`/`stop`/`reset` and a 1s→30s exponential reconnect loop on transport error. |
|
||
| `frontend/src/app/features/scoreboard/scoreboard.service.ts` | HTTP client for `/api/v1/scoreboard/{ranking,matrix,event-log,graph}` returning typed `ApiErrorEnvelope`s. |
|
||
| `frontend/src/app/features/scoreboard/scoreboard.pure.ts` | Pure types and helpers: `RankingRow`, `MatrixView`, `EventLogRow`, `GraphView`, `SolveLivePayload`, `PLAYER_COLOR_PALETTE`, `stablePlayerColorIndex`, `applyRankingSort`, `parseSolveEventIntoRanking`, `dedupEventLogBySolveId`, `applySolveToGraph`, `mutateMatrixFromSolve`, `formatSolveDateTime`. |
|
||
| `frontend/src/app/features/scoreboard/tabs.component.ts` | Horizontal tab strip (`Ranking` / `Matrix` / `Event Log` / `Score Graph`) bound to `ScoreboardStore.activeTab`. |
|
||
| `frontend/src/app/features/scoreboard/ranking.component.ts` | Ranking tab table (rank, player with color swatch, solved count, points). |
|
||
| `frontend/src/app/features/scoreboard/matrix.component.ts` | Matrix tab — sticky-header, sticky-player grid of cells (`★` gold/silver/bronze, `✓`, blank) with player color swatches. |
|
||
| `frontend/src/app/features/scoreboard/event-log.component.ts` | Event Log tab — newest-first `<ol>` of solves with timestamp, player, challenge, awarded points; position 1–3 use gold/silver/bronze stars, others use a green check. |
|
||
| `frontend/src/app/features/scoreboard/score-graph.component.ts` | Score Graph tab — pure-SVG line chart of top 10 cumulative points over the event window with per-player colored polylines and a legend; renders empty / countdown / unconfigured states. |
|
||
| `frontend/src/app/features/scoreboard/scoreboard.page.ts` | `/scoreboard` smart page (also listed above): gates the four tabs, wires `store.loadAll()` and `store.wireSse(...)` on init, and tears them down via `store.stop()` on destroy. |
|
||
| `frontend/src/app/core/services/notification.service.ts` | Root-provided signal-backed store of `{ id, kind, message, ts }` records; `error()` / `info()` push, `dismiss(id)` / `clear()` remove. |
|
||
| `frontend/src/app/core/interceptors/error-notification.interceptor.ts` | Translates `HttpErrorResponse` into friendly messages and pushes them to `NotificationService` (with suppression for `/api/v1/auth/{login,csrf,register}` and `/api/v1/challenges/status`). |
|
||
| `frontend/src/app/core/services/blog.service.ts` | Typed Promise-based public blog list and admin CRUD HTTP client. |
|
||
| `frontend/src/app/features/blog/blog.page.ts` | `/blog` smart page with loading, error, empty, and published-list states. |
|
||
| `frontend/src/app/features/blog/blog-presenter.component.ts` | Shared sanitized Markdown post renderer used by the Blog and landing pages. |
|
||
| `frontend/src/app/features/blog/blog.pure.ts` | Pure blog-list state derivation. |
|
||
| `frontend/src/app/features/admin/blog/blog.component.ts` | `/admin/blog` post table and create/edit/delete workflow coordinator. |
|
||
| `frontend/src/app/features/admin/blog/blog-form-modal.component.ts` | Reactive title/Markdown editor with live preview and draft/publish actions. |
|
||
| `frontend/src/app/features/admin/blog/blog-delete-modal.component.ts` | Post deletion confirmation and inline failure UI. |
|
||
| `frontend/src/app/features/admin/blog/blog-form.pure.ts` | Admin form validation, form synchronization, request cleanup, and status display helpers. |
|
||
| `tests/frontend/blog-page.spec.ts` | Blog page-state and shared sanitized Markdown rendering contracts. |
|
||
| `tests/frontend/blog-admin-form.spec.ts` | Admin form validation, prefill/reset, payload, and status-label contracts. |
|
||
| `tests/frontend/challenges.pure.spec.ts` | Pure helpers: sorting, parsers, friendly error mapping, `formatDdHhMm` (`DD:HH:mm:ss`). |
|
||
| `tests/frontend/challenges.service.spec.ts` | HTTP-service contract: `getDetail` URL-encodes the id, attaches `?include=solvers`, and forwards `withCredentials: true`. |
|
||
| `tests/frontend/challenges.store.spec.ts` | Signal store: board mutation, live solve merge, listeners, `stop()`. The "marks solvedByMe" and "does not double-count" tests now call `setMyUserId('me-1')` *before* `load()` to exercise the realistic page-mount sequence. |
|
||
| `tests/frontend/challenges.store.reset.spec.ts` | Per-user `solvedByMe` regression suite: `reset()` clears board/detail/myUserId/SSE; switching user id flushes the previous board; **orphan board** (cached with no recorded user id) is cleared when PlayerB's id is set; a non-mine solve never flips `solvedByMe` from `false` to `true`; `applySubmitResponse` takes the server value verbatim. |
|
||
| `tests/frontend/notification-interceptor.spec.ts` | Interceptor suppression rules and friendly message mapping. |
|
||
| `tests/frontend/challenge-card-accessibility.spec.ts` | Challenge-card accessibility contract: keyboard activation, `aria-pressed` toggling, accessible-name composition, and the `data-solved` / `challenge-check-*` test selectors. |
|
||
| `tests/backend/challenges-board.spec.ts` | Board query shape, ordering, `?include=solvers`. |
|
||
| `tests/backend/challenges-status-rest.spec.ts` | `/api/v1/challenges/status` snapshot shape. |
|
||
| `tests/backend/challenges-events-sse.spec.ts` | `/api/v1/events` SSE: status + solve frames, dedup, ordering. |
|
||
| `tests/backend/challenges-submit-flag.spec.ts` | Submit flow: correct/incorrect flag, idempotent re-submit, event-state guard, race handling. |
|
||
| `tests/backend/scoreboard-controller.spec.ts` | Contract tests for the four `/api/v1/scoreboard/*` endpoints: JWT protection, ranking sort + competition rank, matrix cell assignment (1/2/3/solved/null), event-log `limit` clamp, graph boundaries + top-10 trim + empty state. |
|
||
| `tests/backend/scoreboard.service.spec.ts` | Service-level tests covering `computeAwardedPoints` parity with `submitFlag`, color-index stability, matrix ordering, and graph state handling. |
|
||
| `tests/frontend/scoreboard.pure.spec.ts` | Pure helpers: `applyRankingSort` (competition-rank numbering), `parseSolveEventIntoRanking` (add-or-increment + sort), `dedupEventLogBySolveId`, `applySolveToGraph` (anchors + plateau + top-10 trim), `mutateMatrixFromSolve`, `stablePlayerColorIndex`, `formatSolveDateTime`. |
|
||
| `tests/frontend/scoreboard.store.spec.ts` | Store lifecycle + live merge: `loadAll` aggregation + idempotency, SSE `solve` frame applying to all four tabs, `stop()` teardown, exponential reconnect. |
|
||
| `backend/src/modules/admin/system/admin-system.controller.ts` | Mounts `/api/v1/admin/system/*` (backup download, restore validate/commit, confirmations, scores/reset, challenges/wipe); all handlers require `AdminGuard` + `@Roles('admin')` and the destructive ones also consume a confirmation token. |
|
||
| `backend/src/modules/admin/system/admin-system.module.ts` | Wires `AdminSystemController`, `BackupService`, `RestoreService`, `DangerZoneService`, and `ConfirmationTokenService`; imports the shared `FilesystemTransactionModule`, `TypeOrmModule.forFeature([AdminOperationTokenEntity])`, and `AuthModule`. |
|
||
| `backend/src/modules/admin/system/backup.service.ts` | Builds the full backup JSON: every application table (`sqlite_master` discovery, excluding `admin_operation_token`/`migrations`/`sqlite_*`), with base64-encoded uploads walked recursively from `UPLOAD_DIR` (skipping `.staging`). Exposes `discoverTables()`, `getUploadDir()`, and `BackupService.stringify(doc)`. |
|
||
| `backend/src/modules/admin/system/restore.service.ts` | Two-phase restore: validates/stages the archive, rebuilds an offline SQLite candidate while preserving the last-admin triggers, then swaps database + uploads as one durable manifest-backed generation; tracks SQLite `-wal`/`-shm` sidecars and reconnects TypeORM only after verification. |
|
||
| `backend/src/modules/admin/system/danger-zone.service.ts` | `resetScores()` deletes every `solve` row transactionally; `wipeChallenges()` snapshots `<UPLOAD_DIR>/challenges` to a side directory, deletes every challenge inside a transaction, physically removes the live `-challenges` directory post-commit, and either deletes the snapshot or restores from it on failure. |
|
||
| `backend/src/modules/admin/system/filesystem-transaction.service.ts` | Crash-safe multi-path swap primitive. Persists per-transaction phase/path manifests under `SYSTEM_OP_STAGING_DIR`, moves configured sidecars with their generation, commits or rolls back idempotently, restores a complete old generation after an interrupted snapshot, finalizes a complete verified new generation, fails closed on mixed/ambiguous state, and sweeps stale unowned rollback/restore/wipe artifacts. |
|
||
| `backend/src/modules/admin/system/confirmation-token.service.ts` | Issues SHA-256-hashed single-use tokens with `SYSTEM_OP_CONFIRM_TOKEN_TTL_MS` TTL; `consume()` runs in a transaction with conditional `WHERE consumedAt IS NULL` updates so only the first concurrent caller succeeds. `purgeExpired()` deletes expired and >24h-old consumed rows. |
|
||
| `backend/src/modules/admin/system/dto/admin-system.dto.ts` | Re-exports `ADMIN_OPERATION_KINDS` and zod contracts for the re-auth body, danger confirm body, and restore-commit body; declares the backup format identifier + version constants. |
|
||
| `backend/src/database/entities/admin-operation-token.entity.ts` | TypeORM entity and the canonical `ADMIN_OPERATION_KINDS` literal union (`restore-backup`, `reset-scores`, `wipe-challenges`). |
|
||
| `backend/src/database/migrations/1700000000900-AddAdminOperationTokens.ts` | Forward-only migration that creates the `admin_operation_token` table and its three indexes (`uq_admin_op_token_hash`, `idx_admin_op_token_user_op`, `idx_admin_op_token_expiry`). |
|
||
| `backend/src/common/utils/upload.ts` | Adds `resolveSystemStagingDir(config)` (defaults to `<DATA_DIR>/.system-staging`, never nested under `UPLOAD_DIR`) and `getRequestSizeLimit(config)` used by the system endpoints. |
|
||
| `backend/src/config/env.schema.ts` | Adds `SYSTEM_OP_STAGING_DIR`, `SYSTEM_OP_RESTORE_STAGE_TTL_MS` (default 15 min), `SYSTEM_OP_CONFIRM_TOKEN_TTL_MS` (default 5 min). |
|
||
| `backend/src/common/errors/error-codes.ts` | Adds the `SYSTEM_*` error codes used by the system endpoints (`SYSTEM_BACKUP_FAILED`, `SYSTEM_RESTORE_VALIDATION_FAILED`, `SYSTEM_RESTORE_PAYLOAD_TOO_LARGE`, `SYSTEM_RESTORE_STAGE_EXPIRED`, `SYSTEM_RESTORE_FAILED`, `SYSTEM_RESTORE_ROLLED_BACK`, `SYSTEM_OPERATION_IN_PROGRESS`, `SYSTEM_REAUTH_REQUIRED`, `SYSTEM_INVALID_CREDENTIALS`, `SYSTEM_TOKEN_INVALID`, `SYSTEM_TOKEN_EXPIRED`, `SYSTEM_TOKEN_REUSED`, `SYSTEM_TOKEN_MISMATCH`, `SYSTEM_DANGER_FAILED`, `SYSTEM_DANGER_ROLLED_BACK`, `SYSTEM_FILE_READ_FAILED`, `SYSTEM_PAYLOAD_INVALID`). |
|
||
| `backend/src/modules/auth/auth.service.ts` | Adds `reauthenticateAdmin(userId, password)` (Argon2id verify + admin/enabled status check) and `revokeAllRefreshSessions(userId, manager?)` (used after a successful restore to force the SPA back to `/login`). |
|
||
| `tests/backend/admin-system-authorization.spec.ts` | Non-admin JWTs and missing tokens are rejected on every `/api/v1/admin/system/*` endpoint. |
|
||
| `tests/backend/admin-system-backup.spec.ts` | Backup envelope shape, table discovery exclusion, and base64 file inclusion. |
|
||
| `tests/backend/admin-system-confirmation-token.spec.ts` | Issue/consume/expire/reuse/mismatch flow and TTL config. |
|
||
| `tests/backend/admin-system-danger.spec.ts` | Reset-scores and wipe-challenges happy path + DB/file rollback. |
|
||
| `tests/backend/admin-system-restore-validation.spec.ts` | Archive schema validation, base64 size mismatch, sha256 mismatch, safe-path rejection, and duplicate-path rejection. |
|
||
| `tests/backend/admin-system-restore-commit.spec.ts` | Controller forwarding, real-file restore, trigger preservation, admin-less archive rejection, and kill-during-swap regression proving restart recovers one coherent database/uploads generation. |
|
||
| `tests/backend/database-init-recovery.spec.ts` | Startup recovery contracts: restores old generations from interrupted manifests, fails closed without mutation on hybrid state, and removes stale unowned artifacts. |
|
||
| `tests/backend/system-category-icons.spec.ts` | Pure helper contract for `seedSystemCategoryIcons`: writes six valid 128×128 PNGs in a fresh temp dir; second invocation reports all six as `skipped` and leaves an admin-uploaded `CRY.png` `mtimeMs` unchanged; creates the `icons/` subdirectory when the parent is empty. Real `sharp` + `os.tmpdir()`, no mocks. |
|
||
| `tests/backend/filesystem-transaction.spec.ts` | Durable manifest phase, commit/rollback, old/new generation recovery, ambiguous fail-closed, and stale artifact sweep contracts. |
|
||
| `frontend/src/app/features/admin/system/system.component.ts` | `/admin/system` smart page: two panels (Database + Danger Zone), backup download, restore pick + validate, confirm modal trigger, and per-operation success/error surfacing + cross-store invalidation. |
|
||
| `frontend/src/app/features/admin/system/system-confirm-modal.component.ts` | Re-authentication + confirmation-phrase modal with operation-specific copy from `system.pure.ts`. |
|
||
| `frontend/src/app/features/admin/system/system.service.ts` | HTTP client for the six `/api/v1/admin/system/*` endpoints. |
|
||
| `frontend/src/app/features/admin/system/system.pure.ts` | Pure helpers: error → friendly message map, `destructiveCopy(op)` per-operation modal copy, `pickRestoreFile()` (jsdom-testable file picker), `deriveBackupFilename()`. |
|
||
| `frontend/src/app/core/services/system-data-change.service.ts` | Root-provided signal bus (`'scores-reset'`, `'challenges-wiped'`, `'restore-completed'`) consumed by `ChallengesStore` and `ScoreboardStore` to invalidate their caches after a destructive operation. |
|
||
| `frontend/src/app/core/services/auth.service.ts` | Adds `forceServerInvalidation()` so the restore flow can drop the local session and broadcast the cross-tab invalidation without invoking the backend logout. |
|
||
| `frontend/src/app/features/challenges/challenges.store.ts` | Now subscribes to `SystemDataChangeService` and calls `reset()` on every destructive operation kind. |
|
||
| `frontend/src/app/features/scoreboard/scoreboard.store.ts` | Now subscribes to `SystemDataChangeService` and clears ranking/matrix/event-log/graph on every destructive operation kind. |
|
||
| `frontend/src/app/features/admin/admin-shell.component.ts` | Side-nav entry for `system` is now `enabled: true`. |
|
||
| `frontend/src/app/app.routes.ts` | Adds the lazy `system` child route under the admin shell. |
|
||
| `tests/frontend/admin-system.pure.spec.ts` | Pure-helper contracts for the friendly error map, destructive copy, and file picker. |
|
||
|
||
# See also
|
||
|
||
- [Frontend Structure](/architecture/frontend-structure.md)
|
||
- [Authenticated Shell](/guides/authenticated-shell.md)
|
||
- [System Category Icon Seed](/architecture/system-category-icons.md)
|
||
- [System Endpoints](/api/system.md)
|
||
- [Challenges Endpoints](/api/challenges.md)
|
||
- [Scoreboard Page Guide](/guides/scoreboard-page.md)
|
||
- [Challenges Board](/guides/challenges-board.md)
|
||
- [Notifications](/guides/notifications.md)
|
||
- [Blog Publishing and Reading](/guides/blog.md)
|
||
- [Blog API](/api/blog.md)
|
||
- [Admin System API](/api/admin-system.md)
|
||
- [Admin System Page Guide](/guides/admin-system-page.md)
|
||
- [Admin Operation Tokens Table](/database/admin-operation-tokens.md)
|