Files
employee-tracking-backend/src/main.rs
Gerard d3feeef996 Rename employee-tracking-backend to purenotify_backend and add OFFLINE
mode

- Rename crate and update dependencies to newer versions - Add OFFLINE
runtime mode for loopback-only server without DB - Refactor state
handling with typed Axum routers and state injection - Rename mongodb
module to mongo and fix imports accordingly - Update Cargo.lock with
updated and removed dependencies - Remove no-auth feature and related
code - Simplify health and user routes to generic state parameter Rename
backend to purenotify_backend and add OFFLINE mode

Use OFFLINE env var to run server without DB, binding to loopback only.
Rename mongodb module to mongo and update dependencies. Update
dependencies and fix router state handling.
2025-08-20 20:53:22 -04:00

143 lines
3.9 KiB
Rust

// 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:<port> or [::1]:<port>), 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::<Arc<AppState>>();
let user_router = routes::user::user::user_routes::<Arc<AppState>>();
// Root router typed with state; set state once on the root
let app = Router::<Arc<AppState>>::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");
}