//! Reminder endpoints for the dashboard. //! //! Lists pending reminders for the reminders tab, and lets the operator //! cancel a pending reminder or reset its failure state so the scheduler //! retries it on the next tick. use axum::{ extract::{Path as AxumPath, State}, http::StatusCode, response::{IntoResponse, Response}, }; use problem_details::ProblemDetails; use super::{AppState, error_response}; pub(super) async fn api_reminders(State(state): State) -> Response { match state.coord.broker.list_pending_reminders() { Ok(rows) => axum::Json(rows).into_response(), Err(e) => error_response(&format!("reminders: {e:#}")), } } pub(super) async fn post_cancel_reminder( State(state): State, AxumPath(id): AxumPath, ) -> Response { match state.coord.broker.cancel_reminder(id) { Ok(0) => ProblemDetails::from_status_code(StatusCode::NOT_FOUND) .with_detail(format!("reminder {id} not pending (already delivered?)")) .into_response(), Ok(_) => { tracing::info!(%id, "operator cancelled reminder"); state.coord.emit_reminders_snapshot(); (StatusCode::OK, "ok").into_response() } Err(e) => error_response(&format!("cancel reminder {id} failed: {e:#}")), } } /// Reset a pending reminder's failure state so the scheduler /// retries it on the next tick. Useful when the failure was /// transient (sqlite lock contention, disk full → freed up) and /// the operator wants delivery to resume immediately instead of /// the row sitting in attempt-count-capped purgatory. pub(super) async fn post_retry_reminder( State(state): State, AxumPath(id): AxumPath, ) -> Response { match state.coord.broker.reset_reminder_failure(id) { Ok(0) => ProblemDetails::from_status_code(StatusCode::NOT_FOUND) .with_detail(format!("reminder {id} not pending (already delivered?)")) .into_response(), Ok(_) => { tracing::info!(%id, "operator reset reminder failure for retry"); state.coord.emit_reminders_snapshot(); (StatusCode::OK, "ok").into_response() } Err(e) => error_response(&format!("retry reminder {id} failed: {e:#}")), } }