1use crate::prelude::*;
14
15use crate::{lib_context::LibContext, service};
16use axum::response::Json;
17use axum::{Router, routing};
18use tower::ServiceBuilder;
19use tower_http::{
20 cors::{AllowOrigin, CorsLayer},
21 trace::TraceLayer,
22};
23
24#[derive(Deserialize, Debug)]
25pub struct ServerSettings {
26 pub address: String,
27 #[serde(default)]
28 pub cors_origins: Vec<String>,
29}
30
31pub async fn init_server(
33 lib_context: Arc<LibContext>,
34 settings: ServerSettings,
35) -> Result<BoxFuture<'static, ()>> {
36 let mut cors = CorsLayer::default();
37 if !settings.cors_origins.is_empty() {
38 let origins: Vec<_> = settings
39 .cors_origins
40 .iter()
41 .map(|origin| origin.parse())
42 .collect::<std::result::Result<_, _>>()?;
43 cors = cors
44 .allow_origin(AllowOrigin::list(origins))
45 .allow_methods([
46 axum::http::Method::GET,
47 axum::http::Method::POST,
48 axum::http::Method::DELETE,
49 ])
50 .allow_headers([axum::http::header::CONTENT_TYPE]);
51 }
52 let app = Router::new()
53 .route("/healthz", routing::get(healthz))
54 .route(
55 "/cocoindex",
56 routing::get(|| async { "CocoIndex is running!" }),
57 )
58 .nest(
59 "/cocoindex/api",
60 Router::new()
61 .route("/flows", routing::get(service::flows::list_flows))
62 .route(
63 "/flows/{flowInstName}",
64 routing::get(service::flows::get_flow),
65 )
66 .route(
67 "/flows/{flowInstName}/schema",
68 routing::get(service::flows::get_flow_schema),
69 )
70 .route(
71 "/flows/{flowInstName}/keys",
72 routing::get(service::flows::get_keys),
73 )
74 .route(
75 "/flows/{flowInstName}/data",
76 routing::get(service::flows::evaluate_data),
77 )
78 .route(
79 "/flows/{flowInstName}/queryHandlers/{queryHandlerName}",
80 routing::get(service::flows::query),
81 )
82 .route(
83 "/flows/{flowInstName}/rowStatus",
84 routing::get(service::flows::get_row_indexing_status),
85 )
86 .route(
87 "/flows/{flowInstName}/update",
88 routing::post(service::flows::update),
89 )
90 .layer(
91 ServiceBuilder::new()
92 .layer(TraceLayer::new_for_http())
93 .layer(cors),
94 )
95 .with_state(lib_context.clone()),
96 );
97
98 let listener = tokio::net::TcpListener::bind(&settings.address)
99 .await
100 .map_err(Error::from)
101 .with_context(|| format!("Failed to bind to address: {}", settings.address))?;
102
103 println!(
104 "Server running at http://{}/cocoindex",
105 listener.local_addr()?
106 );
107 let serve_fut = async { axum::serve(listener, app).await.unwrap() };
108 Ok(serve_fut.boxed())
109}
110
111async fn healthz() -> Json<serde_json::Value> {
112 Json(serde_json::json!({
113 "status": "ok",
114 "version": env!("CARGO_PKG_VERSION"),
115 }))
116}