pub mod analyze; pub mod auth; pub mod case_id; pub mod config; pub mod error; pub mod events; pub mod gazetteer; pub mod magic_link; pub mod models; pub mod paths; pub mod retention; pub mod routes; pub mod transcribe; pub mod web_session; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use axum::Router; use axum::extract::FromRef; use analyze::AnalyzeSender; use config::Config; use gazetteer::Gazetteer; use magic_link::MagicLinkStore; use transcribe::TranscribeSender; use web_session::SessionStore; /// Live "is this worker currently processing a job?" flag. /// Set true before each job, false after. UI uses it to distinguish a real /// in-flight job from a stale on-disk marker (orphan input, missing /// transcript) left over from a crash. pub type WorkerBusy = Arc; /// Newtype wrappers so `FromRef` can target the two busy-flags /// separately even though both are `Arc` underneath. #[derive(Clone)] pub struct AnalyzeBusy(pub WorkerBusy); #[derive(Clone)] pub struct TranscribeBusy(pub WorkerBusy); /// RAII guard that flips a [`WorkerBusy`] true on construction and false on /// drop — panic-safe. Shared across analyze + transcribe workers. pub struct BusyGuard(WorkerBusy); impl BusyGuard { pub fn new(busy: WorkerBusy) -> Self { busy.store(true, Ordering::Release); Self(busy) } } impl Drop for BusyGuard { fn drop(&mut self) { self.0.store(false, Ordering::Release); } } /// Bundle of the four worker-pipeline handles (analyze + transcribe, /// each with a busy-flag and a job sender). Extracted so handlers that /// need the whole pipeline (self-heal, status probes) can take a single /// `State` instead of four separate `State(...)` params. /// The individual `FromRef` impls below remain valid, so /// handlers that only need one sender keep their narrower signature. #[derive(Clone)] pub struct PipelineState { pub analyze_busy: AnalyzeBusy, pub analyze_tx: AnalyzeSender, pub transcribe_busy: TranscribeBusy, pub transcribe_tx: TranscribeSender, } /// Shared application state. Clonable — all fields are cheap to clone /// (`Arc` and `mpsc::Sender` / `broadcast::Sender`). #[derive(Clone)] pub struct AppState { pub config: Arc, pub transcribe_tx: TranscribeSender, pub analyze_tx: AnalyzeSender, pub session_store: SessionStore, pub magic_link_store: MagicLinkStore, pub analyze_busy: AnalyzeBusy, pub transcribe_busy: TranscribeBusy, pub events_tx: events::EventSender, pub http_client: reqwest::Client, pub vocab: Arc, } impl FromRef for Arc { fn from_ref(state: &AppState) -> Self { state.config.clone() } } impl FromRef for TranscribeSender { fn from_ref(state: &AppState) -> Self { state.transcribe_tx.clone() } } impl FromRef for AnalyzeSender { fn from_ref(state: &AppState) -> Self { state.analyze_tx.clone() } } impl FromRef for SessionStore { fn from_ref(state: &AppState) -> Self { state.session_store.clone() } } impl FromRef for MagicLinkStore { fn from_ref(state: &AppState) -> Self { state.magic_link_store.clone() } } impl FromRef for AnalyzeBusy { fn from_ref(state: &AppState) -> Self { state.analyze_busy.clone() } } impl FromRef for TranscribeBusy { fn from_ref(state: &AppState) -> Self { state.transcribe_busy.clone() } } impl FromRef for PipelineState { fn from_ref(state: &AppState) -> Self { PipelineState { analyze_busy: state.analyze_busy.clone(), analyze_tx: state.analyze_tx.clone(), transcribe_busy: state.transcribe_busy.clone(), transcribe_tx: state.transcribe_tx.clone(), } } } impl FromRef for events::EventSender { fn from_ref(state: &AppState) -> Self { state.events_tx.clone() } } impl FromRef for reqwest::Client { fn from_ref(state: &AppState) -> Self { state.http_client.clone() } } impl FromRef for Arc { fn from_ref(state: &AppState) -> Self { state.vocab.clone() } } /// Test/simple entrypoint: jobs pushed into either channel are dropped /// because the receivers are not retained. Use [`create_router_with_state`] /// from `main.rs` where real workers own the receivers. pub fn create_router(config: Arc) -> Router { let (transcribe_tx, _transcribe_rx) = transcribe::channel(); let (analyze_tx, _analyze_rx) = analyze::channel(); create_router_with_state(AppState { config, transcribe_tx, analyze_tx, session_store: web_session::new_store(), magic_link_store: magic_link::new_store(), analyze_busy: AnalyzeBusy(Arc::new(AtomicBool::new(false))), transcribe_busy: TranscribeBusy(Arc::new(AtomicBool::new(false))), events_tx: events::channel(), http_client: reqwest::Client::new(), vocab: Arc::new(Gazetteer::empty()), }) } pub fn create_router_with_state(state: AppState) -> Router { routes::api_router().with_state(state) }