docs(quotesdb): admin features design doc

main
Elijah Voigt 3 months ago
parent 2ccad33921
commit 95cd0a8183

@ -0,0 +1,135 @@
# Admin Features — Design
**Date:** 2026-03-04
---
## Overview
Adds a set of admin-only controls to the QuotesDB API and UI:
- **Auth code reset** — change the admin passphrase in-place.
- **Submission lock / unlock** — globally prevent new quotes from being created.
- **Public status endpoint** — lets the UI know whether submissions are open.
- **Admin UI page** — a single `/admin` page with an auth code field, a reset
form, and a lock/unlock toggle.
- **Submit page gate** — shows a "submissions are closed" banner instead of the
form when the lock is active.
---
## API
### Public endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/api/status` | None | Returns `{ "submissions_locked": bool }` |
### Admin endpoints
All admin endpoints require the `X-Admin-Code` header matching the value stored
in `admin_config`. A mismatch returns `403 Forbidden`.
| Method | Path | Body | Response |
|--------|------|------|----------|
| `POST` | `/api/admin/reset-auth-code` | `{ "new_code"?: "..." }` | `200 { "auth_code": "new-code" }` |
| `POST` | `/api/admin/lock` | — | `200 { "submissions_locked": true }` |
| `POST` | `/api/admin/unlock` | — | `200 { "submissions_locked": false }` |
`new_code` is optional. If omitted, the server generates a new 4-word
passphrase.
### Modified endpoints
`PUT /api/quotes` — gains a pre-flight lock check:
- If `submissions_locked = true``423 Locked` with
`{ "error": "submissions are closed" }`.
---
## Database
The existing `admin_config` key/value table gains a second row:
| key | value |
|-----|-------|
| `admin_auth_code` | the 4-word passphrase (already exists) |
| `submissions_locked` | `"0"` or `"1"` (new) |
`submissions_locked` is seeded to `"0"` alongside `admin_auth_code` on
startup.
### New `QuoteRepository` trait methods
```rust
/// Replace the admin auth code if `current` matches; generate or use `new_code`.
/// Returns the new auth code string.
async fn update_admin_auth_code(
&self,
current: &str,
new_code: Option<&str>,
) -> Result<String, DbError>;
/// Return whether submissions are currently locked.
async fn get_submissions_locked(&self) -> Result<bool, DbError>;
/// Set the submissions lock state.
async fn set_submissions_locked(&self, locked: bool) -> Result<(), DbError>;
```
---
## Frontend
### New route
| Route | Page |
|-------|------|
| `/admin` | Admin controls page |
### `/admin` page
- A single persistent `Admin auth code` text input at the top.
- **Reset auth code section:** optional `New passphrase` field + "Reset" button.
On success, shows the new code prominently.
- **Submissions section:** shows current lock state (fetched via `GET
/api/status` on mount) and a single "Lock submissions" / "Unlock submissions"
button.
- All actions send `X-Admin-Code: <value-from-field>`.
- `403` responses show an "Wrong auth code" inline error.
### `/submit` page
- On mount, calls `GET /api/status`.
- If `submissions_locked = true`, hides the form entirely and shows a banner:
> "Submissions are currently closed."
- The submit nav link in the top nav remains active (users still reach the page
and see the closed notice).
### New API client functions (`src/bin/ui/api.rs`)
```rust
pub async fn get_status() -> Result<StatusResponse, ApiError>
pub async fn admin_reset_auth_code(current: &str, new_code: Option<&str>, admin_code: &str) -> Result<String, ApiError>
pub async fn admin_lock(admin_code: &str) -> Result<bool, ApiError>
pub async fn admin_unlock(admin_code: &str) -> Result<bool, ApiError>
```
---
## Auth flow
The admin code is a 4-word passphrase printed to stderr on API startup. It is
never exposed over the API (no GET endpoint). The admin enters it manually into
the `/admin` page's persistent field each session.
---
## Error handling
| Scenario | HTTP status | UI message |
|----------|-------------|------------|
| Wrong admin code | `403 Forbidden` | "Wrong auth code." |
| Submissions locked on create | `423 Locked` | Banner: "Submissions are currently closed." |
| DB error | `500 Internal Server Error` | Generic error display |
Loading…
Cancel
Save