pub mod extractors; mod routes; use axum::Router; use domain::{ AuthPort, BroadcastPort, ClientRegistry, ConfigRepository, EventPublisher, PasswordHashPort, WidgetStateReader, }; use std::sync::Arc; use tower_http::cors::CorsLayer; use tower_http::services::{ServeDir, ServeFile}; pub struct AppState { pub config: Arc, pub events: Arc, pub widget_states: Arc, pub broadcaster: Arc, pub clients: Arc, pub auth: Arc, pub hasher: Arc, pub spa_dir: Option, } impl Clone for AppState { fn clone(&self) -> Self { Self { config: self.config.clone(), events: self.events.clone(), widget_states: self.widget_states.clone(), broadcaster: self.broadcaster.clone(), clients: self.clients.clone(), auth: self.auth.clone(), hasher: self.hasher.clone(), spa_dir: self.spa_dir.clone(), } } } pub fn router(state: AppState) -> Router where C: ConfigRepository + Send + Sync + 'static, C::Error: std::fmt::Debug + Send, E: EventPublisher + Send + Sync + 'static, E::Error: std::fmt::Debug + Send, W: WidgetStateReader + Send + Sync + 'static, B: BroadcastPort + Send + Sync + 'static, B::Error: std::fmt::Debug + Send, R: ClientRegistry + Send + Sync + 'static, A: AuthPort + Send + Sync + 'static, H: PasswordHashPort + Send + Sync + 'static, { let spa_dir = state.spa_dir.clone(); let app = Router::new() .nest("/api", routes::api_routes()) .layer(CorsLayer::permissive()) .with_state(state); if let Some(dir) = spa_dir { let index = format!("{dir}/index.html"); app.fallback_service(ServeDir::new(&dir).fallback(ServeFile::new(index))) } else { app } } pub async fn serve( addr: &str, state: AppState, ) -> Result<(), std::io::Error> where C: ConfigRepository + Send + Sync + 'static, C::Error: std::fmt::Debug + Send, E: EventPublisher + Send + Sync + 'static, E::Error: std::fmt::Debug + Send, W: WidgetStateReader + Send + Sync + 'static, B: BroadcastPort + Send + Sync + 'static, B::Error: std::fmt::Debug + Send, R: ClientRegistry + Send + Sync + 'static, A: AuthPort + Send + Sync + 'static, H: PasswordHashPort + Send + Sync + 'static, { let app = router(state); let listener = tokio::net::TcpListener::bind(addr).await?; axum::serve(listener, app).await }