You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.1 KiB
4.1 KiB
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
/adminpage 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 Lockedwith{ "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
/// 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 codetext input at the top. - Reset auth code section: optional
New passphrasefield + "Reset" button. On success, shows the new code prominently. - Submissions section: shows current lock state (fetched via
GET /api/statuson mount) and a single "Lock submissions" / "Unlock submissions" button. - All actions send
X-Admin-Code: <value-from-field>. 403responses 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)
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 |