use axum::{extract::Path, http::StatusCode, response::{IntoResponse, Response}, routing::get, Router}; use std::net::SocketAddr; use std::sync::Arc; use tracing::{info, warn}; use uuid::Uuid; use crate::persist::Persist; #[derive(Clone)] pub struct HttpState { persist: Arc, } pub fn build_router(persist: Arc) -> Router { let state = HttpState { persist }; Router::new() .route("/jobs/{request_id}/logs", get(get_logs)) .with_state(state) } async fn get_logs( Path(request_id): Path, axum::extract::State(state): axum::extract::State, ) -> Response { let Ok(id) = Uuid::parse_str(&request_id) else { return StatusCode::BAD_REQUEST.into_response(); }; if !state.persist.is_enabled() { return (StatusCode::SERVICE_UNAVAILABLE, "persistence disabled").into_response(); } match state.persist.get_logs_text(id).await { Ok(Some(text)) => ( StatusCode::OK, [(axum::http::header::CONTENT_TYPE, "text/plain; charset=utf-8")], text, ) .into_response(), Ok(None) => StatusCode::NOT_FOUND.into_response(), Err(e) => { warn!(error = %e, request_id = %id, "failed to read logs"); StatusCode::INTERNAL_SERVER_ERROR.into_response() } } } pub async fn serve(addr: SocketAddr, persist: Arc, shutdown: impl std::future::Future) { let app = build_router(persist); info!(%addr, "http server starting"); let listener = tokio::net::TcpListener::bind(addr).await.expect("bind http"); let server = axum::serve(listener, app); let _ = tokio::select! { _ = server => {}, _ = shutdown => {}, }; }