use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use anyhow::Result;
use sqlx::mysql::MySqlPoolOptions;
use sqlx::MySqlPool;
use utoipa_swagger_ui::SwaggerUi;
use crate::config::Settings;
use crate::{logger, entity_registry, swagger_registry};
pub struct ServerRun;
impl ServerRun {
async fn start(enable_swagger: bool) -> Result<()> {
let settings = Settings::load().map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
let pool: MySqlPool = MySqlPoolOptions::new()
.max_connections(5)
.connect(&settings.database_url)
.await
.map_err(|e| anyhow::anyhow!("Failed to connect to DB: {}", e))?;
entity_registry::run_all(&pool).await.map_err(|e| anyhow::anyhow!("Failed to create tables: {}", e))?;
crate::scheduler_registry::start_all(&pool);
let cors_cfg = settings.cors.clone().unwrap_or_default();
let enable_swagger_ui = enable_swagger;
let server = HttpServer::new(move || {
use actix_cors::Cors;
use actix_web::http::{header, Method};
use actix_web::middleware::Condition;
let mut cors = Cors::default();
if let Some(origins) = &cors_cfg.allowed_origins {
for o in origins { cors = cors.allowed_origin(o); }
} else {
cors = cors.allow_any_origin();
}
if let Some(methods) = &cors_cfg.allowed_methods {
let parsed: Vec<Method> = methods.iter().filter_map(|m| m.parse::<Method>().ok()).collect();
if !parsed.is_empty() { cors = cors.allowed_methods(parsed); } else { cors = cors.allow_any_method(); }
} else {
cors = cors.allow_any_method();
}
if let Some(headers) = &cors_cfg.allowed_headers {
for h in headers {
if let Ok(hh) = h.parse::<header::HeaderName>() { cors = cors.allowed_header(hh); }
}
} else {
cors = cors.allow_any_header();
}
if cors_cfg.allow_credentials.unwrap_or(false) { cors = cors.supports_credentials(); }
if let Some(max_age) = cors_cfg.max_age { cors = cors.max_age(max_age as usize); }
let mut app = App::new()
.app_data(web::Data::new(pool.clone()))
.wrap(Condition::new(cors_cfg.enabled, cors))
.configure(|cfg| crate::controller_registry::configure_all(cfg))
.route("/", web::get().to(health_check));
if enable_swagger_ui {
let openapi = swagger_registry::generate_openapi();
app = app.service(
SwaggerUi::new("/swagger-ui/{_:.*}")
.url("/api-docs/openapi.json", openapi),
);
}
app
})
.bind(("127.0.0.1", settings.port))?
.run();
logger!(INFO, "Server running on http://127.0.0.1:{}", settings.port);
if enable_swagger_ui {
logger!(INFO, "Swagger UI available at http://127.0.0.1:{}/swagger-ui/", settings.port);
}
server.await.map_err(|e| anyhow::anyhow!("Server failed: {}", e))
}
pub async fn enable_swagger(self) -> Result<()> {
Self::start(true).await
}
}
impl std::future::IntoFuture for ServerRun {
type Output = Result<()>;
type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { ServerRun::start(false).await })
}
}
pub fn run() -> ServerRun { ServerRun }
async fn health_check() -> impl Responder {
HttpResponse::Ok().body("")
}