- Add AppState with DB, Config, EventBus, ModuleRegistry via Axum State - ModuleRegistry now uses Arc for Clone support, builder-pattern register() - Add /api/v1/health endpoint returning status, version, registered modules - Add graceful shutdown on CTRL+C / SIGTERM - erp-common utils: ID generation, timestamp helpers, code generator with tests - Config structs now derive Clone for state sharing - Update wiki to reflect Phase 1 completion
110 lines
3.0 KiB
Rust
110 lines
3.0 KiB
Rust
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...");
|
|
},
|
|
}
|
|
}
|