feat: Add health check endpoint
Introduce a new health check endpoint at `/api/health`. This endpoint returns a JSON response indicating the server's status and configuration details, such as the port, data path, and configured URLs for external services. This change also refactors the application setup by: - Creating a `lib.rs` file to house the `create_router` function, making it reusable for both the main application and integration tests. - Moving configuration loading logic into `main.rs` and passing it as a shared state (`Arc<Config>`) to the router. - Adding a unit test (`health_test.rs`) to verify the functionality of the health check endpoint. - Updating `Cargo.toml` and `Cargo.lock` to include the `tower` dependency, which is used for testing.
This commit is contained in:
Generated
+1
@@ -348,6 +348,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower",
|
||||
"tower-http 0.5.2",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
|
||||
@@ -20,3 +20,6 @@ tower-http = { version = "0.5", features = ["limit", "trace"] }
|
||||
rand = "0.8"
|
||||
bcrypt = "0.15"
|
||||
axum-extra = { version = "0.9", features = ["cookie"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tower = { version = "0.5", features = ["util"] }
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::Router;
|
||||
|
||||
use config::Config;
|
||||
|
||||
/// Build the application router. Used by main.rs and integration tests.
|
||||
pub fn create_router(config: Arc<Config>) -> Router {
|
||||
routes::api_router().with_state(config)
|
||||
}
|
||||
+11
-5
@@ -1,12 +1,18 @@
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
use config::Config;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use doctate_server::config::Config;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
let config = Arc::new(Config::from_env());
|
||||
println!("Server starting on port {}", config.server_port);
|
||||
|
||||
let addr = format!("0.0.0.0:{}", config.server_port);
|
||||
println!("Server starting on {addr}");
|
||||
|
||||
let app = doctate_server::create_router(config);
|
||||
let listener = TcpListener::bind(&addr).await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn handle_health(State(config): State<Arc<Config>>) -> Json<Value> {
|
||||
Json(json!({
|
||||
"status": "ok",
|
||||
"port": config.server_port,
|
||||
"data_path": config.data_path.to_string_lossy(),
|
||||
"users": config.users.iter().map(|u| &u.slug).collect::<Vec<_>>(),
|
||||
"whisper_url": config.whisper_url,
|
||||
"ollama_url": config.ollama_url,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod health;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::Router;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub fn api_router() -> Router<Arc<Config>> {
|
||||
Router::new().route("/api/health", axum::routing::get(health::handle_health))
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use doctate_server::config::Config;
|
||||
|
||||
fn test_config() -> Arc<Config> {
|
||||
Arc::new(Config {
|
||||
server_port: 3000,
|
||||
data_path: "/tmp/doctate-test".into(),
|
||||
log_level: "info".into(),
|
||||
log_path: "/tmp/doctate-test/logs".into(),
|
||||
log_max_days: 90,
|
||||
users: vec![],
|
||||
api_keys: HashMap::new(),
|
||||
retention_audio_days: 30,
|
||||
retention_transcript_days: 30,
|
||||
retention_document_days: 0,
|
||||
whisper_url: "http://localhost:10300".into(),
|
||||
whisper_timeout_seconds: 120,
|
||||
ollama_url: "http://localhost:11434".into(),
|
||||
ollama_model: "gemma3:4b".into(),
|
||||
ollama_keep_alive: 0,
|
||||
llm_url: String::new(),
|
||||
llm_api_key: String::new(),
|
||||
llm_model: String::new(),
|
||||
llm_temperature: 0.0,
|
||||
session_timeout_hours: 8,
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_returns_ok() {
|
||||
let app = doctate_server::create_router(test_config());
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/health")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_returns_json_with_status() {
|
||||
let app = doctate_server::create_router(test_config());
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/health")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
assert_eq!(json["status"], "ok");
|
||||
assert_eq!(json["port"], 3000);
|
||||
}
|
||||
Reference in New Issue
Block a user