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.
vibed/quotesdb/.beans/quotesdb-njv2--quotesdbapi-...

2.9 KiB

title status type priority created_at updated_at blocked_by
quotesdb/api: POST /api/admin/lock and /api/admin/unlock endpoints completed feature normal 2026-03-10T23:32:05Z 2026-03-10T23:32:12Z
quotesdb-04cw

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 (or src/bin/api/handlers/admin.rs) — add lock_submissions and unlock_submissions handlers
  • src/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/lock with correct X-Admin-Code200 { "submissions_locked": true }
  • POST /api/admin/unlock with correct X-Admin-Code200 { "submissions_locked": false }
  • POST /api/admin/lock with wrong code → 403
  • POST /api/admin/unlock with 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