at protocol indexer with flexible filtering, xrpc queries, and a cursor-backed event stream, built on fjall
at-protocol
atproto
indexer
rust
fjall
1use crate::state::AppState;
2use axum::{Router, routing::get};
3use jacquard::xrpc::GenericXrpcError;
4use jacquard_axum::XrpcErrorResponse;
5use std::{net::SocketAddr, sync::Arc};
6use tower_http::cors::CorsLayer;
7use tower_http::trace::TraceLayer;
8
9mod debug;
10pub mod repo;
11pub mod stats;
12mod stream;
13pub mod xrpc;
14
15pub type XrpcResult<T> = Result<T, XrpcErrorResponse<GenericXrpcError>>;
16
17pub async fn serve(state: Arc<AppState>, port: u16) -> miette::Result<()> {
18 let app = Router::new()
19 .route("/health", get(|| async { "OK" }))
20 .route("/stats", get(stats::get_stats))
21 .route("/stream", get(stream::handle_stream))
22 .merge(xrpc::router())
23 .merge(repo::router())
24 .with_state(state)
25 .layer(TraceLayer::new_for_http())
26 .layer(CorsLayer::permissive());
27
28 let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
29 .await
30 .map_err(|e| miette::miette!("failed to bind to port {port}: {e}"))?;
31
32 tracing::info!("API server listening on {}", listener.local_addr().unwrap());
33
34 axum::serve(
35 listener,
36 app.into_make_service_with_connect_info::<SocketAddr>(),
37 )
38 .await
39 .map_err(|e| miette::miette!("axum server error: {e}"))?;
40
41 Ok(())
42}
43
44pub async fn serve_debug(state: Arc<AppState>, port: u16) -> miette::Result<()> {
45 let app = debug::router()
46 .with_state(state)
47 .layer(TraceLayer::new_for_http());
48
49 let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
50 .await
51 .map_err(|e| miette::miette!("failed to bind debug server to port {port}: {e}"))?;
52
53 tracing::info!(
54 "debug server listening on {}",
55 listener.local_addr().unwrap()
56 );
57
58 axum::serve(
59 listener,
60 app.into_make_service_with_connect_info::<SocketAddr>(),
61 )
62 .await
63 .map_err(|e| miette::miette!("debug server error: {e}"))?;
64
65 Ok(())
66}