bloom_web_core/
application.rs

1use 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    /// Starts the HTTP server with optional Swagger UI support.
13    ///
14    /// This function initializes the database connection pool, creates necessary tables,
15    /// starts scheduled jobs, configures CORS settings, and starts the HTTP server.
16    ///
17    /// # Arguments
18    /// * `enable_swagger` - Whether to enable Swagger UI documentation
19    ///
20    /// # Returns
21    /// * `Result<()>` - Ok if server starts successfully, Err otherwise
22    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    /// Enables Swagger UI and starts the server.
96    ///
97    /// This is a convenience method that starts the server with Swagger UI enabled.
98    ///
99    /// # Returns
100    /// * `Result<()>` - Ok if server starts successfully, Err otherwise
101    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    /// Converts ServerRun into a Future that starts the server without Swagger UI.
111    fn into_future(self) -> Self::IntoFuture {
112        Box::pin(async move { ServerRun::start(false).await })
113    }
114}
115
116/// Creates a new ServerRun instance.
117///
118/// This function returns a ServerRun struct that can be used to start the HTTP server.
119///
120/// # Returns
121/// * `ServerRun` - A new ServerRun instance
122pub fn run() -> ServerRun { ServerRun }
123
124/// Health check endpoint handler.
125///
126/// This function handles health check requests and returns an empty HTTP 200 response.
127///
128/// # Returns
129/// * `impl Responder` - An HTTP response indicating the server is healthy
130async fn health_check() -> impl Responder {
131    HttpResponse::Ok().body("")
132}