//! Stats + loose-ends + todos read endpoints. use axum::extract::State; use axum::response::{IntoResponse, Response}; use serde::{Deserialize, Serialize}; use super::AppState; #[derive(Deserialize)] pub(super) struct StatsQuery { window: Option, } pub(super) async fn api_stats( State(_state): State, axum::extract::Query(q): axum::extract::Query, ) -> axum::Json { let window = crate::stats::Window::parse(q.window.as_deref().unwrap_or("24h")); let mut snapshot = crate::stats::snapshot_default(window); // Pass the window span so the local reminder rollup filters its counts // to the same time range as the chart data. let window_secs = window.span_secs(); let window_secs_u = u64::try_from(window_secs).unwrap_or(0); snapshot.reminder_stats = fetch_reminder_stats(window_secs_u).await; axum::Json(snapshot) } /// Fetch reminder activity stats from the harness-local reminder store over /// `HIVE_AGENT_SOCKET` — was a broker RPC before reminders moved /// in-container. Returns `None` on any transport / decode failure or /// when the socket is unset — the stats are decorative, not authoritative. async fn fetch_reminder_stats(window_secs: u64) -> Option { match crate::todo_server::dial(&hive_agent_sock::Request::ReminderRollup { since_secs: window_secs, }) .await? { hive_agent_sock::Response::ReminderRollup { stats } => Some(stats), _ => None, } } #[derive(Serialize)] struct TodosBody { todos: Vec, } /// `GET /api/todos` — snapshot of this agent's local todos (loose-ends v2). /// /// Connects to the in-agent harness socket (`HIVE_AGENT_SOCKET`) and calls /// `ListTodos`. Returns `{ "todos": [...] }` where each entry is a /// `LooseEnd::Todo` (subsystem, key, summary, source, `age_seconds`). Returns /// an empty array when the socket is unavailable — best-effort, silent failure. pub(super) async fn api_todos() -> Response { let todos = match crate::todo_server::dial(&hive_agent_sock::Request::ListTodos { subsystem: None }) .await { Some(hive_agent_sock::Response::LooseEnds { loose_ends }) => loose_ends, _ => Vec::new(), }; axum::Json(TodosBody { todos }).into_response() }