bloom_web_core/
application.rs1use actix_web::{web, App, HttpResponse, HttpServer, Responder};
2use anyhow::Result;
3use sqlx::mysql::MySqlPoolOptions;
4use sqlx::MySqlPool;
5use utoipa_swagger_ui::SwaggerUi;
6use crate::config::Settings;
7use crate::{logger, entity_registry, swagger_registry};
8
9pub struct ServerRun;
10
11impl ServerRun {
12 async fn start(enable_swagger: bool) -> Result<()> {
23 let settings = Settings::load().map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
24
25 let pool: MySqlPool = MySqlPoolOptions::new()
26 .max_connections(5)
27 .connect(&settings.database_url)
28 .await
29 .map_err(|e| anyhow::anyhow!("Failed to connect to DB: {}", e))?;
30
31 entity_registry::run_all(&pool).await.map_err(|e| anyhow::anyhow!("Failed to create tables: {}", e))?;
32
33 crate::scheduler_registry::start_all(&pool);
34
35 let cors_cfg = settings.cors.clone().unwrap_or_default();
36 let enable_swagger_ui = enable_swagger;
37
38 let server = HttpServer::new(move || {
39 use actix_cors::Cors;
40 use actix_web::http::{header, Method};
41 use actix_web::middleware::Condition;
42
43 let mut cors = Cors::default();
44
45 if let Some(origins) = &cors_cfg.allowed_origins {
46 for o in origins { cors = cors.allowed_origin(o); }
47 } else {
48 cors = cors.allow_any_origin();
49 }
50
51 if let Some(methods) = &cors_cfg.allowed_methods {
52 let parsed: Vec<Method> = methods.iter().filter_map(|m| m.parse::<Method>().ok()).collect();
53 if !parsed.is_empty() { cors = cors.allowed_methods(parsed); } else { cors = cors.allow_any_method(); }
54 } else {
55 cors = cors.allow_any_method();
56 }
57
58 if let Some(headers) = &cors_cfg.allowed_headers {
59 for h in headers {
60 if let Ok(hh) = h.parse::<header::HeaderName>() { cors = cors.allowed_header(hh); }
61 }
62 } else {
63 cors = cors.allow_any_header();
64 }
65
66 if cors_cfg.allow_credentials.unwrap_or(false) { cors = cors.supports_credentials(); }
67 if let Some(max_age) = cors_cfg.max_age { cors = cors.max_age(max_age as usize); }
68
69 let mut app = App::new()
70 .app_data(web::Data::new(pool.clone()))
71 .wrap(Condition::new(cors_cfg.enabled, cors))
72 .configure(|cfg| crate::controller_registry::configure_all(cfg))
73 .route("/", web::get().to(health_check));
74
75 if enable_swagger_ui {
76 let openapi = swagger_registry::generate_openapi();
77
78 app = app.service(
79 SwaggerUi::new("/swagger-ui/{_:.*}")
80 .url("/api-docs/openapi.json", openapi),
81 );
82 }
83
84 app
85 })
86 .bind(("127.0.0.1", settings.port))?
87 .run();
88 logger!(INFO, "Server running on http://127.0.0.1:{}", settings.port);
89 if enable_swagger_ui {
90 logger!(INFO, "Swagger UI available at http://127.0.0.1:{}/swagger-ui/", settings.port);
91 }
92 server.await.map_err(|e| anyhow::anyhow!("Server failed: {}", e))
93 }
94
95 pub async fn enable_swagger(self) -> Result<()> {
102 Self::start(true).await
103 }
104}
105
106impl std::future::IntoFuture for ServerRun {
107 type Output = Result<()>;
108 type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send>>;
109
110 fn into_future(self) -> Self::IntoFuture {
112 Box::pin(async move { ServerRun::start(false).await })
113 }
114}
115
116pub fn run() -> ServerRun { ServerRun }
123
124async fn health_check() -> impl Responder {
131 HttpResponse::Ok().body("")
132}