//! Server setup and lifecycle management use anyhow::Result; use axum::{middleware, Router}; use std::sync::Arc; use tokio::net::TcpListener; use tower::ServiceBuilder; use tower_http::trace::TraceLayer; use tracing::info; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; use crate::{ middleware::{create_cors_layer, log_request}, openapi::ApiDoc, routes, state::AppState, }; /// Server configuration and lifecycle manager pub struct Server { /// Application state state: Arc, /// Server host address host: String, /// Server port port: u16, } impl Server { /// Create a new server instance pub fn new(state: Arc) -> Self { let host = state.config.server.host.clone(); let port = state.config.server.port; Self { state, host, port } } /// Get the router for testing purposes pub fn router(&self) -> Router { self.build_router() } /// Build the application router with all routes and middleware fn build_router(&self) -> Router { // API v1 routes (versioned endpoints) let api_v1 = Router::new() .merge(routes::pack_routes()) .merge(routes::action_routes()) .merge(routes::runtime_routes()) .merge(routes::rule_routes()) .merge(routes::execution_routes()) .merge(routes::trigger_routes()) .merge(routes::inquiry_routes()) .merge(routes::event_routes()) .merge(routes::key_routes()) .merge(routes::permission_routes()) .merge(routes::workflow_routes()) .merge(routes::webhook_routes()) .merge(routes::history_routes()) .merge(routes::analytics_routes()) .merge(routes::artifact_routes()) .merge(routes::agent_routes()) .with_state(self.state.clone()); // Auth routes at root level (not versioned for frontend compatibility) let auth_routes = routes::auth_routes().with_state(self.state.clone()); // Health endpoint at root level (operational endpoint, not versioned) let health_routes = routes::health_routes().with_state(self.state.clone()); // Root router with versioning and documentation Router::new() .merge(SwaggerUi::new("/docs").url("/api-spec/openapi.json", ApiDoc::openapi())) .merge(health_routes) .nest("/auth", auth_routes) .nest("/api/v1", api_v1) .layer( ServiceBuilder::new() // Add tracing for all requests .layer(TraceLayer::new_for_http()) // Add CORS support with configured origins .layer(create_cors_layer(self.state.cors_origins.clone())) // Add custom request logging .layer(middleware::from_fn(log_request)), ) } /// Start the server and listen for requests pub async fn run(self) -> Result<()> { let router = self.build_router(); let addr = format!("{}:{}", self.host, self.port); info!("Starting server on {}", addr); info!("API documentation available at http://{}/docs", addr); let listener = TcpListener::bind(&addr).await?; info!("Server listening on {}", addr); axum::serve(listener, router).await?; Ok(()) } /// Graceful shutdown handler pub async fn shutdown(&self) { info!("Shutting down server..."); // Perform any cleanup here // - Close database connections // - Flush logs // - Wait for in-flight requests info!("Server shutdown complete"); } } #[cfg(test)] mod tests { #[tokio::test] #[ignore] // Ignore until we have test database setup async fn test_server_creation() { // This test is ignored because it requires a test database pool // When implemented, create a test pool and verify server creation // let pool = PgPool::connect(&test_db_url).await.unwrap(); // let state = AppState::new(pool); // let server = Server::new(state, "127.0.0.1".to_string(), 8080); // assert_eq!(server.host, "127.0.0.1"); // assert_eq!(server.port, 8080); } }