1use std::sync::Arc;
4
5use axum::Router;
6use tower_http::cors::{Any, CorsLayer};
7use tower_http::trace::TraceLayer;
8use uls_query::QueryEngine;
9
10use crate::handlers::{self, AppState};
11
12#[derive(Debug, Clone)]
14pub struct ServerConfig {
15 pub bind: String,
17 pub port: u16,
19 pub cors_origins: Vec<String>,
21}
22
23impl Default for ServerConfig {
24 fn default() -> Self {
25 Self {
26 bind: "127.0.0.1".to_string(),
27 port: 3000,
28 cors_origins: Vec::new(),
29 }
30 }
31}
32
33pub fn build_router(engine: QueryEngine, config: &ServerConfig) -> Router {
35 let state: AppState = Arc::new(engine);
36
37 let mut app = Router::new()
38 .route("/health", axum::routing::get(handlers::health))
39 .route("/stats", axum::routing::get(handlers::stats))
40 .route("/licenses/{callsign}", axum::routing::get(handlers::lookup))
41 .route("/licenses", axum::routing::get(handlers::search))
42 .route("/frn/{frn}", axum::routing::get(handlers::frn_lookup))
43 .with_state(state)
44 .layer(TraceLayer::new_for_http());
45
46 if !config.cors_origins.is_empty() {
47 let cors = if config.cors_origins.iter().any(|o| o == "*") {
48 CorsLayer::new().allow_origin(Any)
49 } else {
50 let origins: Vec<_> = config
51 .cors_origins
52 .iter()
53 .filter_map(|o| o.parse().ok())
54 .collect();
55 CorsLayer::new().allow_origin(origins)
56 };
57 app = app.layer(cors);
58 }
59
60 app
61}
62
63pub async fn run(engine: QueryEngine, config: ServerConfig) -> std::io::Result<()> {
65 let app = build_router(engine, &config);
66 let addr = format!("{}:{}", config.bind, config.port);
67
68 tracing::info!("Starting ULS API server on {}", addr);
69
70 let listener = tokio::net::TcpListener::bind(&addr).await?;
71 axum::serve(listener, app)
72 .await
73 .expect("server should not fail");
74 Ok(())
75}