2.9 KiB
+++ title = "quotesdb/api: POST /api/admin/lock and /api/admin/unlock endpoints" priority = 6 status = "done" ticket_type = "feature" dependencies = ["69a2c5"] +++
POST /api/admin/lock and /api/admin/unlock endpoints
Add the two admin-protected endpoints that toggle the global submissions lock. Both require X-Admin-Code and return the current lock state after the operation.
Note: This ticket depends on ticket 35685a (GET /api/status) because that ticket adds get_submissions_locked and set_submissions_locked to the QuoteRepository trait and seeds the submissions_locked row in the database. Complete 35685a first.
Files to modify
src/bin/api/handlers/mod.rs(orsrc/bin/api/handlers/admin.rs) — addlock_submissionsandunlock_submissionshandlerssrc/bin/api/main.rs— register the two new routes
No new DB trait methods are needed; both handlers reuse set_submissions_locked(bool) introduced in 35685a.
Handlers
/// POST /api/admin/lock
/// Requires X-Admin-Code header. Sets submissions_locked = true.
/// Response: 200 { "submissions_locked": true } or 403 on bad code.
pub async fn lock_submissions(
State(repo): State<Arc<dyn QuoteRepository>>,
headers: HeaderMap,
) -> impl IntoResponse {
let admin_code = extract_admin_code(&headers);
if !verify_admin_code(&repo, admin_code).await { ... }
match repo.set_submissions_locked(true).await {
Ok(()) => Json(json!({ "submissions_locked": true })).into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
/// POST /api/admin/unlock
/// Requires X-Admin-Code header. Sets submissions_locked = false.
/// Response: 200 { "submissions_locked": false } or 403 on bad code.
pub async fn unlock_submissions(
State(repo): State<Arc<dyn QuoteRepository>>,
headers: HeaderMap,
) -> impl IntoResponse {
// same pattern, locked = false
}
Implement a shared helper verify_admin_code(repo, code) -> bool (or extract inline) that fetches the stored admin code from admin_config and compares it. Use constant-time comparison if possible.
Route registration (src/bin/api/main.rs)
.route("/api/admin/lock", post(handlers::lock_submissions))
.route("/api/admin/unlock", post(handlers::unlock_submissions))
Tests
POST /api/admin/lockwith correctX-Admin-Code→200 { "submissions_locked": true }POST /api/admin/unlockwith correctX-Admin-Code→200 { "submissions_locked": false }POST /api/admin/lockwith wrong code →403POST /api/admin/unlockwith missing header →403- Lock/unlock idempotent: locking when already locked still returns
200 { "submissions_locked": true }
Validation
cargo fmt && cargo check && cargo clippy && cargo test
Commit
feat(quotesdb): POST /api/admin/lock and /api/admin/unlock endpoints