mod config; mod db; mod handlers; mod state; use axum::Router; use config::AppConfig; use erp_core::events::EventBus; use erp_core::module::ModuleRegistry; use erp_server_migration::MigratorTrait; use state::AppState; use tracing_subscriber::EnvFilter; #[tokio::main] async fn main() -> anyhow::Result<()> { // Load config let config = AppConfig::load()?; // Initialize tracing tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new(&config.log.level)), ) .json() .init(); tracing::info!(version = env!("CARGO_PKG_VERSION"), "ERP Server starting..."); // Connect to database let db = db::connect(&config.database).await?; // Run migrations erp_server_migration::Migrator::up(&db, None).await?; tracing::info!("Database migrations applied"); // Connect to Redis let _redis_client = redis::Client::open(&config.redis.url[..])?; tracing::info!("Redis client created"); // Initialize event bus (capacity 1024 events) let event_bus = EventBus::new(1024); // Initialize module registry let registry = ModuleRegistry::new(); // Phase 2+ will register modules here: // let registry = registry.register(Box::new(erp_auth::AuthModule::new(db.clone()))); tracing::info!(module_count = registry.modules().len(), "Modules registered"); // Register event handlers registry.register_handlers(&event_bus); let host = config.server.host.clone(); let port = config.server.port; // Build shared state let state = AppState { db, config, event_bus, module_registry: registry, }; // Build API router with versioning let api_v1 = Router::new().merge(handlers::health::health_check_router()); // Build application router let app = Router::new().merge(api_v1).with_state(state); let addr = format!("{}:{}", host, port); let listener = tokio::net::TcpListener::bind(&addr).await?; tracing::info!(addr = %addr, "Server listening"); // Graceful shutdown on CTRL+C axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await?; tracing::info!("Server shutdown complete"); Ok(()) } async fn shutdown_signal() { let ctrl_c = async { tokio::signal::ctrl_c() .await .expect("failed to install CTRL+C handler"); }; #[cfg(unix)] let terminate = async { tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) .expect("failed to install signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => { tracing::info!("Received CTRL+C, shutting down gracefully..."); }, _ = terminate => { tracing::info!("Received SIGTERM, shutting down gracefully..."); }, } }