1use axum::{Router, middleware};
8use std::net::SocketAddr;
9use std::path::PathBuf;
10use std::sync::Arc;
11use tower_http::cors::CorsLayer;
12
13use crate::api;
14use crate::auth::{self, AuthState};
15use crate::state::AppState;
16
17#[derive(Debug, Clone)]
19pub struct BackendConfig {
20 pub project_root: PathBuf,
22 pub ito_path: Option<PathBuf>,
24 pub bind: String,
26 pub port: u16,
28 pub token: Option<String>,
30 pub cors_origins: Option<Vec<String>>,
32}
33
34impl Default for BackendConfig {
35 fn default() -> Self {
36 Self {
37 project_root: PathBuf::from("."),
38 ito_path: None,
39 bind: "127.0.0.1".to_string(),
40 port: 9010,
41 token: None,
42 cors_origins: None,
43 }
44 }
45}
46
47pub async fn serve(config: BackendConfig) -> miette::Result<()> {
52 let root = config
53 .project_root
54 .canonicalize()
55 .unwrap_or(config.project_root.clone());
56
57 let token = config.token.unwrap_or_else(|| auth::generate_token(&root));
59
60 let app_state = Arc::new(match config.ito_path {
61 Some(ito_path) => AppState::with_ito_path(root.clone(), ito_path),
62 None => AppState::new(root.clone()),
63 });
64
65 let auth_state = Arc::new(AuthState {
66 token: token.clone(),
67 });
68
69 let cors = match config.cors_origins {
71 Some(origins) => {
72 let mut layer = CorsLayer::new();
73 for origin in origins {
74 let Ok(origin): Result<axum::http::HeaderValue, _> = origin.parse() else {
75 eprintln!("warning: invalid CORS origin skipped: {origin}");
76 continue;
77 };
78 layer = layer.allow_origin(origin);
79 }
80 layer
81 }
82 None => CorsLayer::permissive(),
83 };
84
85 let app = Router::new()
86 .nest("/api/v1", api::v1_router())
87 .with_state(app_state)
88 .layer(middleware::from_fn_with_state(
89 auth_state,
90 auth::auth_middleware,
91 ))
92 .layer(cors);
93
94 let addr: SocketAddr = format!("{}:{}", config.bind, config.port)
95 .parse()
96 .map_err(|e| miette::miette!("Invalid address: {e}"))?;
97
98 let listener = tokio::net::TcpListener::bind(addr)
99 .await
100 .map_err(|e| miette::miette!("Failed to bind to {addr}: {e}"))?;
101
102 eprintln!("ito-backend serving {} at http://{addr}/", root.display());
103 eprintln!("Auth token: {token}");
104
105 axum::serve(listener, app)
106 .await
107 .map_err(|e| miette::miette!("Server error: {e}"))?;
108
109 Ok(())
110}