//! Admin-only crawler observability + control endpoints. //! //! Mounted under `/api/v1/admin/crawler*`, cookie-only via `RequireAdmin`. //! All control endpoints return 503 when the crawler daemon is disabled //! (`AppState.crawler == None`). Reads compose the live in-process status //! ([`crate::crawler::status`]) with DB-derived queue counts and the //! session/browser flags. //! //! Split into four siblings for navigability — the surface area grew //! past the point where keeping it in one file made review harder: //! - [`status`] — SSE stream + composed status snapshot //! - [`control`] — run / restart / session endpoints //! - [`dead_jobs`] — dead-letter list + requeue //! - [`backlog`] — pending-chapters and missing-covers backlog reads mod backlog; mod control; mod dead_jobs; mod status; use axum::Router; use crate::app::{AppState, CrawlerControl}; use crate::error::AppError; pub fn routes() -> Router { Router::new() .merge(status::routes()) .merge(control::routes()) .merge(dead_jobs::routes()) .merge(backlog::routes()) } /// Default page size for the backlog list endpoints. pub(super) fn default_limit() -> i64 { 50 } /// Shared 503 helper: the daemon-only control + observability endpoints /// gate on `AppState.crawler == Some(_)`. Returns the same code and body /// across every caller so the frontend can rely on `service_unavailable` /// rather than each handler returning its own variant. pub(super) fn require_crawler( state: &AppState, ) -> Result<&std::sync::Arc, AppError> { state.crawler.as_ref().ok_or_else(|| { AppError::ServiceUnavailable("crawler daemon is disabled".into()) }) }