From 6e2f7728e88ea466c112ab606e6d5b3192b0a8f2 Mon Sep 17 00:00:00 2001 From: Akinator31 <99099121+Akinator31@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:39:28 +0200 Subject: [PATCH] feat(api): add health route in axum api --- crates/rustmail/src/api/handler/health.rs | 54 +++++++++++++++++++++++ crates/rustmail/src/api/handler/mod.rs | 2 + crates/rustmail/src/api/router.rs | 1 + 3 files changed, 57 insertions(+) create mode 100644 crates/rustmail/src/api/handler/health.rs diff --git a/crates/rustmail/src/api/handler/health.rs b/crates/rustmail/src/api/handler/health.rs new file mode 100644 index 0000000..89c6d4b --- /dev/null +++ b/crates/rustmail/src/api/handler/health.rs @@ -0,0 +1,54 @@ +use crate::bot::ShardManagerKey; +use crate::prelude::types::*; +use axum::Json; +use axum::extract::State; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use serenity::all::ShardId; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub async fn handle_health(State(bot_state): State>>) -> impl IntoResponse { + let state_lock = bot_state.lock().await; + + let mut db_ok = false; + if let Some(pool) = &state_lock.db_pool { + if sqlx::query("SELECT 1").execute(pool).await.is_ok() { + db_ok = true; + } + } + + let bot_running = matches!(state_lock.status, BotStatus::Running { .. }); + + let ctx_guard = state_lock.bot_context.read().await; + let bot_connected = ctx_guard.is_some(); + + let mut bot_latency = None; + if let Some(ctx) = ctx_guard.as_ref() { + let data = ctx.data.read().await; + if let Some(shard_manager) = data.get::() { + let runners = shard_manager.runners.lock().await; + if let Some(runner) = runners.get(&ShardId(0)) { + bot_latency = runner.latency.map(|d| d.as_millis()); + } + } + } + + let mut status_code = StatusCode::OK; + if !db_ok || (bot_running && !bot_connected) { + status_code = StatusCode::SERVICE_UNAVAILABLE; + } + + ( + status_code, + Json(serde_json::json!({ + "status": if db_ok && (!bot_running || bot_connected) { "healthy" } else { "unhealthy" }, + "database": if db_ok { "connected" } else { "disconnected" }, + "bot": { + "running": bot_running, + "connected": bot_connected, + "latency_ms": bot_latency, + } + })), + ) +} diff --git a/crates/rustmail/src/api/handler/mod.rs b/crates/rustmail/src/api/handler/mod.rs index 2afc135..7832b9f 100644 --- a/crates/rustmail/src/api/handler/mod.rs +++ b/crates/rustmail/src/api/handler/mod.rs @@ -4,6 +4,7 @@ pub mod auth; pub mod bot; pub mod categories; pub mod externals; +pub mod health; pub mod panel; pub mod user; @@ -13,5 +14,6 @@ pub use auth::*; pub use bot::*; pub use categories::*; pub use externals::*; +pub use health::*; pub use panel::*; pub use user::*; diff --git a/crates/rustmail/src/api/router.rs b/crates/rustmail/src/api/router.rs index 61286b6..6f83739 100644 --- a/crates/rustmail/src/api/router.rs +++ b/crates/rustmail/src/api/router.rs @@ -15,6 +15,7 @@ pub fn create_api_router(bot_state: Arc>) -> Router { let external_router = create_external_router(bot_state.clone()); let app = Router::new() + .route("/api/health", axum::routing::get(handle_health)) .nest("/api/admin", admin_router) .nest("/api/apikeys", apikeys_router) .nest("/api/categories", categories_router)