// ©AngelaMos | 2026 // main.rs mod config; mod db; mod error; mod middleware; mod routes; mod state; use std::sync::Arc; use anyhow::Context; use axum::extract::DefaultBodyLimit; use clap::Parser; use sqlx::postgres::PgPoolOptions; use tower::ServiceBuilder; use tower_http::trace::TraceLayer; use tracing_subscriber::EnvFilter; use config::AppConfig; use state::AppState; const DB_MAX_CONNECTIONS: u32 = 20; #[tokio::main] async fn main() -> anyhow::Result<()> { let config = AppConfig::parse(); tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env() .unwrap_or_else(|_| { EnvFilter::new( "info,tower_http=debug", ) }), ) .init(); let db = PgPoolOptions::new() .max_connections(DB_MAX_CONNECTIONS) .connect(&config.database_url) .await .context("failed to connect to database")?; db::run_migrations(&db) .await .context("failed to run database migrations")?; let engine = axumortem_engine::AnalysisEngine::new() .context( "failed to initialize analysis engine", )?; let config = Arc::new(config); let state = AppState { db, engine: Arc::new(engine), config: Arc::clone(&config), }; let layers = ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(middleware::cors::layer(&config)) .layer(DefaultBodyLimit::max( config.max_upload_size, )); let app = routes::api_router().layer(layers).with_state(state); let bind_address = config.bind_address(); let listener = tokio::net::TcpListener::bind(&bind_address) .await .context("failed to bind TCP listener")?; tracing::info!("listening on {}", bind_address); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await .context("server error")?; Ok(()) } async fn shutdown_signal() { tokio::signal::ctrl_c() .await .expect("failed to install ctrl+c handler"); tracing::info!("shutdown signal received"); }