1use std::net::SocketAddr;
4use std::path::Path;
5
6use axum::Router;
7use axum::http::header;
8use tower_http::cors::CorsLayer;
9use tower_http::services::ServeDir;
10use tracing::info;
11
12use crate::routes;
13use crate::state::AppState;
14use crate::websocket::ws_handler;
15
16pub fn build_app(state: AppState, static_dir: Option<&Path>) -> Router {
18 let cors = CorsLayer::new()
19 .allow_origin([
20 "http://localhost:5173".parse().unwrap(),
21 "http://localhost:3000".parse().unwrap(),
22 ])
23 .allow_methods([
24 axum::http::Method::GET,
25 axum::http::Method::POST,
26 axum::http::Method::PUT,
27 axum::http::Method::DELETE,
28 axum::http::Method::OPTIONS,
29 ])
30 .allow_headers([
31 header::CONTENT_TYPE,
32 header::AUTHORIZATION,
33 header::ACCEPT,
34 header::COOKIE,
35 ])
36 .allow_credentials(true);
37
38 let mut app = Router::new()
39 .merge(routes::auth::router())
41 .merge(routes::config::router())
42 .merge(routes::sessions::router())
43 .merge(routes::chat::router())
44 .merge(routes::mcp::router())
45 .merge(routes::commands::router())
46 .route("/api/health", axum::routing::get(health_check))
48 .route("/ws", axum::routing::get(ws_handler))
50 .layer(cors)
51 .with_state(state);
52
53 if let Some(dir) = static_dir
55 && dir.exists()
56 {
57 let assets_dir = dir.join("assets");
58 if assets_dir.exists() {
59 app = app.nest_service("/assets", ServeDir::new(assets_dir));
60 }
61 app = app.fallback_service(ServeDir::new(dir));
63 }
64
65 app
66}
67
68async fn health_check() -> axum::Json<serde_json::Value> {
70 axum::Json(serde_json::json!({
71 "status": "ok",
72 "service": "opendev-web-ui",
73 }))
74}
75
76pub async fn start_server(
78 state: AppState,
79 host: &str,
80 port: u16,
81 static_dir: Option<&Path>,
82) -> std::io::Result<()> {
83 let app = build_app(state, static_dir);
84
85 let addr: SocketAddr = format!("{host}:{port}")
86 .parse()
87 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
88
89 info!("Starting web server on {}", addr);
90
91 let listener = tokio::net::TcpListener::bind(addr).await?;
92 axum::serve(listener, app)
93 .await
94 .map_err(std::io::Error::other)
95}
96
97#[cfg(test)]
98#[path = "server_tests.rs"]
99mod tests;