docs: update documentation to OKF v0.1 format
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
---
|
||||
type: api
|
||||
title: REST API Overview
|
||||
description: Base URL, versioning, auth, CSRF, and the standard error envelope.
|
||||
tags: [api, rest, overview, csrf]
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# Base URL & versioning
|
||||
|
||||
All endpoints live under `/api/v1`. The Angular SPA uses the same
|
||||
prefix; the SPA fallback only kicks in for non-API routes.
|
||||
|
||||
| Path prefix | Purpose |
|
||||
|-----------------|--------------------------------------------------------|
|
||||
| `/api/v1/*` | Versioned JSON REST endpoints. |
|
||||
| `/api/docs` | Swagger UI (OpenAPI 3.1). |
|
||||
| `/api/docs-json`| Raw OpenAPI 3.1 document. |
|
||||
| `/uploads/*` | Static uploads directory (`UPLOAD_DIR`). |
|
||||
| `/*` | SPA fallback (`FRONTEND_DIST/index.html`). |
|
||||
|
||||
# Authentication
|
||||
|
||||
* `JwtAuthGuard` is registered as `APP_GUARD` and runs on every request
|
||||
unless the handler is marked with `@Public()` from
|
||||
`backend/src/common/decorators/public.decorator.ts`.
|
||||
* Successful auth produces an access JWT in the response body and an
|
||||
`HttpOnly` refresh cookie named `rt` (see `backend/src/modules/auth/cookie.ts`).
|
||||
* `Authorization: Bearer <accessToken>` is attached by the
|
||||
`authInterceptor` on the frontend.
|
||||
|
||||
# CSRF
|
||||
|
||||
* `CsrfMiddleware` (registered globally) mints a `csrf` cookie on every
|
||||
response and, for unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`),
|
||||
compares the cookie against the `x-csrf-token` header using
|
||||
`crypto.timingSafeEqual`.
|
||||
* The frontend's `csrfInterceptor` runs *before* `authInterceptor` so
|
||||
the header is attached on every unsafe call.
|
||||
* Skip list (`SKIP_PATH_PREFIXES` in
|
||||
`backend/src/common/middleware/csrf.middleware.ts`):
|
||||
- `/api/v1/auth/register-first-admin`
|
||||
- `/api/v1/auth/login`
|
||||
- `/api/v1/setup/create-admin`
|
||||
|
||||
# Error envelope
|
||||
|
||||
Every error flows through `GlobalExceptionFilter` and is shaped as:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "USERNAME_TAKEN",
|
||||
"message": "Username already exists",
|
||||
"details": null,
|
||||
"path": "/api/v1/setup/create-admin",
|
||||
"timestamp": "2026-07-21T14:43:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------------|---------------------|--------------------------------------------------------|
|
||||
| `code` | string | One of `ERROR_CODES` (`backend/src/common/errors/error-codes.ts`). |
|
||||
| `message` | string | Human-readable summary. |
|
||||
| `details` | unknown \| null | Optional structured details (e.g. zod field errors). |
|
||||
| `path` | string | Original request path. |
|
||||
| `timestamp` | ISO 8601 string | When the filter produced the response. |
|
||||
|
||||
# Canonical error codes
|
||||
|
||||
`USERNAME_TAKEN` is the most recently added code (used by the setup
|
||||
endpoint). Full list lives in `backend/src/common/errors/error-codes.ts`:
|
||||
|
||||
`VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`,
|
||||
`CONFLICT`, `LAST_ADMIN`, `SYSTEM_INITIALIZED`, `RATE_LIMITED`,
|
||||
`CSRF_INVALID`, `WEAK_PASSWORD`, `USERNAME_TAKEN`,
|
||||
`INVALID_CREDENTIALS`, `TOKEN_REVOKED`, `THEME_INVALID`, `INTERNAL`.
|
||||
|
||||
# See also
|
||||
|
||||
- [Auth Endpoints](/api/auth.md)
|
||||
- [Admin Endpoints](/api/admin.md)
|
||||
- [Setup Endpoint](/api/setup.md)
|
||||
- [System Endpoints](/api/system.md)
|
||||
- [Uploads Endpoints](/api/uploads.md)
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
type: api
|
||||
title: Setup Endpoint
|
||||
description: Dedicated POST /api/v1/setup/create-admin endpoint used only while no administrator exists.
|
||||
tags: [api, setup, bootstrap, first-admin]
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# Endpoint
|
||||
|
||||
| Method | Path | Auth | CSRF | Rate limited |
|
||||
|--------|-------------------------------|------|--------|--------------|
|
||||
| `POST` | `/api/v1/setup/create-admin` | `@Public()` | Skipped (path is in `CsrfMiddleware.SKIP_PATH_PREFIXES`) | Yes (per-IP via `RegistrationRateLimitService`) |
|
||||
|
||||
# Request body
|
||||
|
||||
Validated by `backend/src/modules/setup/dto/create-admin.dto.ts` (zod):
|
||||
|
||||
| Field | Type | Constraints |
|
||||
|-------------------|--------|------------------------------------------------------------------------------|
|
||||
| `username` | string | 3–32 chars, regex `^[a-zA-Z0-9_.-]+$` |
|
||||
| `password` | string | 1–256 chars; additionally passes `validatePassword` (Argon2 policy). |
|
||||
| `passwordConfirm` | string | Must equal `password` (zod refine → `VALIDATION_FAILED`, `passwordConfirm`).|
|
||||
|
||||
# Successful response (201)
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "<jwt>",
|
||||
"expiresIn": 900,
|
||||
"user": { "id": "<uuid>", "username": "first.admin", "role": "admin" }
|
||||
}
|
||||
```
|
||||
|
||||
The refresh token is set as an `HttpOnly` cookie via
|
||||
`setRefreshCookie(res, session.refreshToken, config)` so the SPA can keep
|
||||
refreshing without storing the raw token in JS.
|
||||
|
||||
# Error envelope
|
||||
|
||||
| HTTP | `code` | When |
|
||||
|------|---------------------|----------------------------------------------------------------------|
|
||||
| 400 | `VALIDATION_FAILED` | zod body validation failed (length, regex, or `passwordConfirm`). |
|
||||
| 400 | `WEAK_PASSWORD` | Argon2 policy rejects the password (length / mixed-case requirement). |
|
||||
| 404 | `NOT_FOUND` | An admin already exists — the route is hidden once initialized. |
|
||||
| 409 | `USERNAME_TAKEN` | Username collision (reachable only during a race before init). |
|
||||
| 429 | `RATE_LIMITED` | `RegistrationRateLimitService` blocked the IP. |
|
||||
|
||||
# Wiring
|
||||
|
||||
* Registered in `backend/src/app.module.ts` via `SetupModule`.
|
||||
* `SetupController` (`backend/src/modules/setup/setup.controller.ts`) is
|
||||
`@Public()` and uses `ZodValidationPipe(CreateAdminDtoSchema)`.
|
||||
* `SetupService.createAdmin` runs everything inside a single
|
||||
`DataSource.transaction`: admin count check → username uniqueness check
|
||||
→ Argon2id hash → insert `UserEntity(role='admin', status='enabled')` →
|
||||
mint session via the static `AuthService.createSession`.
|
||||
* CSRF is bypassed for `/api/v1/setup/create-admin` so the first caller
|
||||
(before any cookie exists) is not blocked. The middleware still mints
|
||||
a `csrf` cookie on the response so subsequent calls are covered.
|
||||
|
||||
# Frontend consumer
|
||||
|
||||
`frontend/src/app/features/setup/setup-create-admin.service.ts` calls
|
||||
this endpoint and normalizes the response into the discriminated union
|
||||
`CreateAdminResult`:
|
||||
|
||||
```ts
|
||||
type CreateAdminResult = CreateAdminSuccess | CreateAdminFailure;
|
||||
```
|
||||
|
||||
`CreateAdminFailure` exposes `{ ok: false, status, code, message }` so
|
||||
the component can switch on `code === 'USERNAME_TAKEN'` to surface a
|
||||
non-retryable inline error and offer a Retry button for everything else.
|
||||
|
||||
# See also
|
||||
|
||||
- [First-Run Bootstrap Guide](/guides/bootstrap.md)
|
||||
- [Backend Module Map](/architecture/backend-modules.md)
|
||||
- [Bootstrap Service](/architecture/frontend-structure.md)
|
||||
@@ -3,7 +3,7 @@ type: architecture
|
||||
title: Backend Module Map
|
||||
description: NestJS modules, controllers, services, and how they are wired together.
|
||||
tags: [architecture, backend, nestjs, modules]
|
||||
timestamp: 2026-07-21T14:18:00Z
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# Module Map
|
||||
@@ -25,6 +25,7 @@ also registers two global providers:
|
||||
| `SettingsModule` | `backend/src/modules/settings/settings.module.ts` | Exposes `SettingsService` (get/set/getAll over `setting` table). |
|
||||
| `AuthModule` | `backend/src/modules/auth/auth.module.ts` | `AuthController` (`/api/v1/auth/*`) + `AuthService` (login/refresh/logout/register-first-admin).|
|
||||
| `UsersModule` | `backend/src/modules/users/users.module.ts` | `UsersController` (`/api/v1/auth/register-first-admin`) + `UsersService` (last-admin invariant). |
|
||||
| `SetupModule` | `backend/src/modules/setup/setup.module.ts` | `SetupController` (`POST /api/v1/setup/create-admin`) + `SetupService`. Returns 404 once any admin exists (route hidden). |
|
||||
| `SystemModule` | `backend/src/modules/system/system.module.ts` | `SystemController` (`/api/v1/bootstrap`, `/event/status`, SSE streams) + `SystemService`. |
|
||||
| `AdminModule` | `backend/src/modules/admin/admin.module.ts` | `AdminController` (`/api/v1/admin/users*`) + `AdminService`. Gated by `AdminGuard` + `@Roles('admin')`. |
|
||||
| `UploadsModule` | `backend/src/modules/uploads/uploads.module.ts` | `UploadsController` (`/api/v1/uploads/*`). Admin-only multipart. |
|
||||
@@ -36,6 +37,7 @@ also registers two global providers:
|
||||
|------------------------|-------------------------|--------------|--------------------------------------------------------------|
|
||||
| `AuthController` | `/api/v1/auth` | Mostly public (login, refresh, logout, csrf) | `backend/src/modules/auth/auth.controller.ts` |
|
||||
| `UsersController` | `/api/v1/auth/register-first-admin` | Public (bootstrap-only) | `backend/src/modules/users/users.controller.ts` |
|
||||
| `SetupController` | `/api/v1/setup/create-admin` | Public (bootstrap-only; 404 once an admin exists) | `backend/src/modules/setup/setup.controller.ts` |
|
||||
| `AdminController` | `/api/v1/admin` | Admin only | `backend/src/modules/admin/admin.controller.ts` |
|
||||
| `SystemController` | `/api/v1` | Public | `backend/src/modules/system/system.controller.ts` |
|
||||
| `UploadsController` | `/api/v1/uploads` | Admin only | `backend/src/modules/uploads/uploads.controller.ts` |
|
||||
|
||||
@@ -3,7 +3,7 @@ type: architecture
|
||||
title: Frontend Structure
|
||||
description: Angular routes, components, services, guards, and interceptors.
|
||||
tags: [architecture, frontend, angular]
|
||||
timestamp: 2026-07-21T14:18:00Z
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# Routes
|
||||
@@ -12,7 +12,7 @@ Routes live in `frontend/src/app/app.routes.ts`:
|
||||
|
||||
| Path | Component | Guard | Notes |
|
||||
|--------------|----------------------------------|---------------|----------------------------------------|
|
||||
| `/bootstrap` | `CreateAdminComponent` | — | Rendered only when `initialized === false`. |
|
||||
| `/bootstrap` | `SetupCreateAdminComponent` | — | Rendered only when `initialized === false`. Non-dismissible modal overlay. |
|
||||
| `/login` | `LoginComponent` | — | Standard login form. |
|
||||
| `/` | `HomeComponent` | `authGuard` | Requires auth + initialized. |
|
||||
| `**` | Redirect to `/` | — | Wildcard fallback. |
|
||||
@@ -27,7 +27,7 @@ All components are standalone (no NgModules). Each component imports
|
||||
| `AppComponent` | `frontend/src/app/app.component.ts` | Root; renders `<router-outlet>` and calls `BootstrapService.load()`. |
|
||||
| `HomeComponent` | `frontend/src/app/features/home/home.component.ts` | Landing page shown after auth. |
|
||||
| `LoginComponent` | `frontend/src/app/features/auth/login.component.ts` | Username/password form. |
|
||||
| `CreateAdminComponent` | `frontend/src/app/features/bootstrap/create-admin.component.ts`| First-admin bootstrap form. |
|
||||
| `SetupCreateAdminComponent` | `frontend/src/app/features/setup/setup-create-admin.component.ts` | First-admin bootstrap modal (non-dismissible, typed reactive forms). |
|
||||
|
||||
# Services
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ type: architecture
|
||||
title: Key Files Index
|
||||
description: One-line responsibility for every important source file in the repository.
|
||||
tags: [architecture, index, key-files]
|
||||
timestamp: 2026-07-21T14:18:00Z
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# Backend
|
||||
@@ -74,6 +74,10 @@ timestamp: 2026-07-21T14:18:00Z
|
||||
| `backend/src/modules/users/users.controller.ts` | `/api/v1/auth/register-first-admin`. |
|
||||
| `backend/src/modules/users/users.service.ts` | `enforceLastAdminInvariant` helper. |
|
||||
| `backend/src/modules/users/dto/create-first-admin.dto.ts` | Zod schema for first-admin registration. |
|
||||
| `backend/src/modules/setup/setup.module.ts` | Wires `SetupController` + `SetupService` (dedicated first-admin bootstrap flow). |
|
||||
| `backend/src/modules/setup/setup.controller.ts` | `POST /api/v1/setup/create-admin` (returns 404 once an admin exists). |
|
||||
| `backend/src/modules/setup/setup.service.ts` | Hashes password (Argon2id), runs the create inside a transaction, mints session via `AuthService.createSession`. |
|
||||
| `backend/src/modules/setup/dto/create-admin.dto.ts` | Zod schema with `password === passwordConfirm` refine. |
|
||||
| `backend/src/modules/admin/admin.module.ts` | Wires `AdminController` + `AdminService`. |
|
||||
| `backend/src/modules/admin/admin.controller.ts` | `/api/v1/admin/users[/{id}]`. |
|
||||
| `backend/src/modules/admin/admin.service.ts` | list / create / update role / delete user with last-admin guard. |
|
||||
@@ -110,7 +114,9 @@ timestamp: 2026-07-21T14:18:00Z
|
||||
| `frontend/src/app/core/interceptors/auth.interceptor.ts` | Attaches Bearer header. |
|
||||
| `frontend/src/app/core/interceptors/csrf.interceptor.ts` | Attaches `X-CSRF-Token` on unsafe methods. |
|
||||
| `frontend/src/app/features/auth/login.component.ts` | Login form. |
|
||||
| `frontend/src/app/features/bootstrap/create-admin.component.ts` | First-admin creation form. |
|
||||
| `frontend/src/app/features/setup/setup-create-admin.component.ts` | First-admin modal overlay (non-dismissible; typed reactive form). |
|
||||
| `frontend/src/app/features/setup/setup-create-admin.service.ts` | POSTs to `/api/v1/setup/create-admin` and tags the result with the error code. |
|
||||
| `frontend/src/app/features/setup/setup-create-admin.validators.ts` | Standalone `passwordMatchValidator` group-level reactive form validator. |
|
||||
| `frontend/src/app/features/home/home.component.ts` | Landing page after auth. |
|
||||
|
||||
# Tests
|
||||
|
||||
+11
-4
@@ -3,7 +3,7 @@ type: database
|
||||
title: User Table
|
||||
description: The `user` table — accounts, roles, and statuses.
|
||||
tags: [database, user, auth]
|
||||
timestamp: 2026-07-21T14:18:00Z
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# Schema
|
||||
@@ -34,9 +34,16 @@ timestamp: 2026-07-21T14:18:00Z
|
||||
|
||||
# First-admin bootstrap
|
||||
|
||||
When the `user` table is empty of admins, `POST /api/v1/auth/register-first-admin`
|
||||
is the only endpoint that can create one. After that, the endpoint
|
||||
returns `409 SYSTEM_INITIALIZED`.
|
||||
When the `user` table is empty of admins, the dedicated
|
||||
`POST /api/v1/setup/create-admin` endpoint (see
|
||||
[Setup Endpoint](/api/setup.md)) creates the very first admin and
|
||||
auto-issues a session. Once any admin row exists the endpoint becomes
|
||||
inert and returns `404 NOT_FOUND`, so the route is effectively hidden
|
||||
in production.
|
||||
|
||||
The legacy `POST /api/v1/auth/register-first-admin` route is preserved
|
||||
for compatibility but the canonical first-admin flow now lives in the
|
||||
`SetupModule`.
|
||||
|
||||
# See also
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
type: guide
|
||||
title: First-Run Bootstrap
|
||||
description: How a fresh HIPCTF instance is initialized by the very first administrator.
|
||||
tags: [guide, bootstrap, first-admin, onboarding, tester]
|
||||
timestamp: 2026-07-21T14:43:00Z
|
||||
---
|
||||
|
||||
# When this flow runs
|
||||
|
||||
A brand-new deployment shows a non-dismissible modal asking the operator
|
||||
to create the very first admin user. This is gated by the
|
||||
`initialized` flag returned from `GET /api/v1/bootstrap`:
|
||||
|
||||
| `initialized` | User experience |
|
||||
|---------------|---------------------------------------------------------------|
|
||||
| `false` | Every route is redirected to `/bootstrap` until an admin is created. |
|
||||
| `true` | The setup route becomes a 404 and the modal can never appear again. |
|
||||
|
||||
The `authGuard` enforces the redirect on the client side; the backend
|
||||
independently rejects `/api/v1/setup/create-admin` with `404 NOT_FOUND`
|
||||
once any admin row exists, so the flow is safe even if the SPA is
|
||||
bypassed.
|
||||
|
||||
# How to access (tester steps)
|
||||
|
||||
1. Start the stack against an empty database:
|
||||
```
|
||||
npm run db:reset && npm run dev
|
||||
```
|
||||
2. Open the browser at `http://localhost:3000/`.
|
||||
3. The app redirects to `/bootstrap` and shows the
|
||||
**"Welcome to HIPCTF — Create the first administrator"** modal.
|
||||
4. Fill in the three fields and submit:
|
||||
- **Username** — 3–32 chars, letters/digits/`.`/`-`/`_` only. Helper
|
||||
text is shown via `data-testid="setup-username-error"` when the
|
||||
pattern fails.
|
||||
- **Password** — must satisfy the policy shown directly below the
|
||||
field (default: 12 chars with upper, lower, digit, symbol).
|
||||
- **Confirm Password** — must equal the password above (verified by
|
||||
the group-level `passwordMatchValidator`; mismatch shows
|
||||
`data-testid="setup-confirm-error"`).
|
||||
5. Click **Create admin** (`data-testid="setup-submit"`).
|
||||
|
||||
# Expected behavior
|
||||
|
||||
* On success the modal disappears, the app stores the access token via
|
||||
`AuthService.setSession`, calls `BootstrapService.markInitialized()`,
|
||||
and navigates to `/challenges`.
|
||||
* On `USERNAME_TAKEN` the form shows a red alert:
|
||||
`"Username already exists"`. The **Retry** button is intentionally
|
||||
hidden because the username must be changed first.
|
||||
* On any other failure (network, `WEAK_PASSWORD`, `VALIDATION_FAILED`,
|
||||
`RATE_LIMITED`) the alert shows `"Setup failed, please retry"` and a
|
||||
**Retry** link (`data-testid="setup-retry"`) re-submits the same
|
||||
payload.
|
||||
|
||||
The modal **cannot** be dismissed by the user:
|
||||
|
||||
* Clicking the backdrop is a no-op (`swallowBackdrop`).
|
||||
* Pressing **Esc** is swallowed by the `keydown.escape` host listener.
|
||||
|
||||
# Visual elements
|
||||
|
||||
| Element | Selector | Purpose |
|
||||
|--------------------------------|----------------------------------|-----------------------------------------------|
|
||||
| Modal overlay | `.overlay` | Full-screen backdrop. |
|
||||
| Modal card | `.modal[role="dialog"]` | Contains the form; stops click propagation. |
|
||||
| Username input | `[data-testid="setup-username"]` | Reactive control with length/pattern rules. |
|
||||
| Password input | `[data-testid="setup-password"]` | Reactive control. |
|
||||
| Confirm input | `[data-testid="setup-password-confirm"]` | Reactive control. |
|
||||
| Submit button | `[data-testid="setup-submit"]` | Disabled while submitting or invalid. |
|
||||
| Server error alert | `[data-testid="setup-server-error"]` | Displays `serverError()`. |
|
||||
| Retry button | `[data-testid="setup-retry"]` | Re-submits; absent when the error is non-retryable. |
|
||||
| Password policy hint | `.hint` | Live description from `BootstrapService.passwordPolicy()`. |
|
||||
|
||||
# Behind the scenes (architecture map)
|
||||
|
||||
| Step | Where | What happens |
|
||||
|------|--------------------------------------------------------|-------------------------------------------------------------------------|
|
||||
| 1 | `BootstrapService.load()` | Calls `GET /api/v1/bootstrap`, sets `initialized = false`. |
|
||||
| 2 | `authGuard` | Sees `initialized() === false`, redirects to `/bootstrap`. |
|
||||
| 3 | `SetupCreateAdminComponent` | Renders the modal; reactive form enforces username + password policy + passwordMatch. |
|
||||
| 4 | `SetupCreateAdminService.ensureCsrf()` | Preflights `GET /api/v1/auth/csrf` so the response sets the `csrf` cookie. |
|
||||
| 5 | `POST /api/v1/setup/create-admin` | `SetupService.createAdmin` runs in a TypeORM transaction; creates the user with `role='admin'`; mints session via `AuthService.createSession`. |
|
||||
| 6 | `setRefreshCookie` | Sets the `rt` HttpOnly cookie. |
|
||||
| 7 | Component on success | `AuthService.setSession`, `BootstrapService.markInitialized()`, navigate to `/challenges`. |
|
||||
| 8 | Subsequent `authGuard` checks | `initialized === true`; require auth instead. |
|
||||
|
||||
# What the operator sees after init
|
||||
|
||||
Once the first admin exists, `/api/v1/bootstrap` returns
|
||||
`initialized: true`. The app now:
|
||||
|
||||
* Skips `/bootstrap` entirely.
|
||||
* Routes unauthenticated users to `/login`.
|
||||
* Routes authenticated users to `/` (Home) which itself redirects to
|
||||
`/challenges` once admin features are wired.
|
||||
|
||||
# See also
|
||||
|
||||
- [Setup Endpoint](/api/setup.md)
|
||||
- [Frontend Structure](/architecture/frontend-structure.md)
|
||||
- [Backend Module Map](/architecture/backend-modules.md)
|
||||
- [Bootstrap payload source of truth](`/architecture/overview.md`)
|
||||
+3
-1
@@ -40,6 +40,8 @@ they need.
|
||||
CSRF, and error envelope.
|
||||
* [Auth Endpoints](/api/auth.md) - Login, refresh, logout, CSRF, and
|
||||
first-admin registration.
|
||||
* [Setup Endpoint](/api/setup.md) - Dedicated `POST /api/v1/setup/create-admin`
|
||||
used only while no administrator exists.
|
||||
* [Admin Endpoints](/api/admin.md) - User management endpoints
|
||||
(admin role required).
|
||||
* [System Endpoints](/api/system.md) - Bootstrap, event status, and SSE
|
||||
@@ -49,7 +51,7 @@ they need.
|
||||
# Guides
|
||||
|
||||
* [First-Run Bootstrap](/guides/bootstrap.md) - How a fresh instance is
|
||||
initialized by the very first admin.
|
||||
initialized by the very first admin via the non-dismissible modal.
|
||||
* [Theming](/guides/theming.md) - How themes are loaded and applied.
|
||||
* [Event Window](/guides/event-window.md) - How the event window and live
|
||||
countdown work.
|
||||
|
||||
Reference in New Issue
Block a user