// src/main.rs use std::{process::exit, sync::Arc}; use axum::Router; use dotenvy::dotenv; use tokio::signal; use tower_http::trace::TraceLayer; use tracing::{error, info}; use tracing_subscriber::{EnvFilter, fmt, prelude::*}; mod config; mod handlers; mod mongo; // local module wrapping the Mongo client mod routes; use ::mongodb::Database; // external crate (absolute path avoids name clash) use config::Config; use mongo::MongoDb; // your wrapper // Shared application state for online mode pub struct AppState { pub db: Database, pub config: Config, } #[tokio::main] async fn main() { // Load .env early dotenv().ok(); // Tracing with a safe fallback if RUST_LOG is unset let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::registry() .with(fmt::layer()) .with(env_filter) .init(); // Load config let config = match Config::from_env() { Ok(c) => c, Err(e) => { error!("Failed to load config: {e}"); exit(1); } }; // Runtime OFFLINE switch: true if OFFLINE is 1/true/yes/on (case-insensitive) let offline = std::env::var("OFFLINE") .ok() .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) .unwrap_or(false); if offline { // Enforce loopback binding while offline if !config.bind_address.ip().is_loopback() { error!( "OFFLINE=true requires binding to a loopback address (e.g., 127.0.0.1: or [::1]:), got {}", config.bind_address ); exit(1); } info!("OFFLINE mode enabled — not connecting to MongoDB"); info!("Server starting on {}", config.bind_address); // Health-only, no state. Subrouter is typed to `()`. let app = Router::new() .nest("/health", routes::health::health::health_routes::<()>()) .layer(TraceLayer::new_for_http()); let listener = tokio::net::TcpListener::bind(config.bind_address) .await .unwrap(); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await .unwrap(); return; } // --- Online (DB-enabled) path --- let mongo = match MongoDb::connect(&config).await { Ok(db) => db, Err(e) => { error!("Failed to connect to MongoDB: {e}"); exit(1); } }; let shared_state = Arc::new(AppState { db: mongo.database, config: config.clone(), }); info!("Server starting on {}", config.bind_address); // Build subrouters typed with the same state as the root let health_router = routes::health::health::health_routes::>(); let user_router = routes::user::user::user_routes::>(); // Root router typed with state; set state once on the root let app = Router::>::new() .nest("/health", health_router) .nest("/user", user_router) .with_state(shared_state) .layer(TraceLayer::new_for_http()); let listener = tokio::net::TcpListener::bind(config.bind_address) .await .unwrap(); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await .unwrap(); } async fn shutdown_signal() { let ctrl_c = async { signal::ctrl_c() .await .expect("Failed to install Ctrl+C handler"); }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("Failed to install signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => {}, _ = terminate => {}, } info!("Signal received, shutting down server gracefully"); }