127 lines
4.1 KiB
Rust
127 lines
4.1 KiB
Rust
//! 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<AppState>,
|
|
/// Server host address
|
|
host: String,
|
|
/// Server port
|
|
port: u16,
|
|
}
|
|
|
|
impl Server {
|
|
/// Create a new server instance
|
|
pub fn new(state: Arc<AppState>) -> 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::rule_routes())
|
|
.merge(routes::execution_routes())
|
|
.merge(routes::trigger_routes())
|
|
.merge(routes::inquiry_routes())
|
|
.merge(routes::event_routes())
|
|
.merge(routes::key_routes())
|
|
.merge(routes::workflow_routes())
|
|
.merge(routes::webhook_routes())
|
|
.merge(routes::history_routes())
|
|
.merge(routes::analytics_routes())
|
|
.merge(routes::artifact_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);
|
|
}
|
|
}
|