use actix_web::{web, HttpResponse}; use sqlx::PgPool; use std::collections::HashMap; use crate::errors::AppError; use crate::models::health::{HealthResponse, ServiceHealth}; use crate::services::cache::CacheService; use crate::services::martin::MartinService; use crate::services::osrm::OsrmService; use crate::services::photon::PhotonService; /// GET /api/health /// /// Checks connectivity to all upstream services and returns aggregated status. /// Always returns HTTP 200; the client should inspect the `status` field. pub async fn health_check( martin: web::Data, photon: web::Data, osrm: web::Data, pool: web::Data, cache: web::Data, start_time: web::Data, ) -> Result { let mut services = HashMap::new(); // Check Martin let martin_health = check_service("martin", martin.health_check()).await; services.insert("martin".to_string(), martin_health); // Check Photon let photon_health = check_service("photon", photon.health_check()).await; services.insert("photon".to_string(), photon_health); // Check OSRM instances let osrm_driving_health = check_service("osrm_driving", osrm.health_check("driving")).await; services.insert("osrm_driving".to_string(), osrm_driving_health); let osrm_walking_health = check_service("osrm_walking", osrm.health_check("walking")).await; services.insert("osrm_walking".to_string(), osrm_walking_health); let osrm_cycling_health = check_service("osrm_cycling", osrm.health_check("cycling")).await; services.insert("osrm_cycling".to_string(), osrm_cycling_health); // Check PostgreSQL let postgres_health = check_postgres(&pool).await; services.insert("postgres".to_string(), postgres_health); // Check Redis let redis_health = check_service("redis", cache.ping()).await; services.insert("redis".to_string(), redis_health); let status = HealthResponse::compute_status(&services); let uptime = start_time.elapsed().as_secs(); let response = HealthResponse { status, version: "1.0.0".to_string(), uptime_seconds: uptime, services, }; Ok(HttpResponse::Ok() .content_type("application/json; charset=utf-8") .insert_header(("Cache-Control", "no-store")) .json(response)) } /// Check a service that returns Result. async fn check_service( _name: &str, future: impl std::future::Future>, ) -> ServiceHealth { match future.await { Ok(latency_ms) => ServiceHealth { status: if latency_ms > 2000 { "degraded".to_string() } else { "ok".to_string() }, latency_ms, }, Err(()) => ServiceHealth { status: "down".to_string(), latency_ms: 0, }, } } /// Check PostgreSQL connectivity. async fn check_postgres(pool: &PgPool) -> ServiceHealth { let start = std::time::Instant::now(); let result = sqlx::query_scalar::<_, i32>("SELECT 1") .fetch_one(pool) .await; let latency_ms = start.elapsed().as_millis() as u64; match result { Ok(_) => ServiceHealth { status: if latency_ms > 2000 { "degraded".to_string() } else { "ok".to_string() }, latency_ms, }, Err(_) => ServiceHealth { status: "down".to_string(), latency_ms: 0, }, } }