1use crate::AppState;
2use axum::response::IntoResponse;
3use axum::{Json, Router, extract::State, routing::get};
4use serde_json::{Value, json};
5
6type RequestPathParams = axum::extract::Path<(String, u64)>;
7
8pub fn router() -> Router<AppState> {
9 Router::new()
10 .route("/api/health", get(health))
11 .route("/api/sessions", get(list_sessions))
12 .route("/api/sessions/{sid}", get(get_session))
13 .route("/api/requests/{sid}/{seq}", get(get_request))
14 .route("/api/stream", get(crate::api::stream::stream))
15}
16
17async fn health(State(state): State<AppState>) -> Json<Value> {
18 let request_count = state
19 .store
20 .list_requests(state.session_id.as_str())
21 .await
22 .map(|requests| requests.len())
23 .unwrap_or(0);
24 let store_status =
25 if let Some(fs) = state.store.as_any().downcast_ref::<crate::store::FsStore>() {
26 if fs.consecutive_write_failures() >= 10 {
27 "degraded"
28 } else {
29 "ok"
30 }
31 } else {
32 "ok"
33 };
34 Json(json!({
35 "status": "ok",
36 "provider": state.provider.as_str(),
37 "upstream": state.upstream.to_string(),
38 "session_id": state.session_id.as_str(),
39 "started_at": state.started_at,
40 "request_count": request_count,
41 "store": store_status,
42 }))
43}
44
45async fn list_sessions(State(state): State<AppState>) -> Json<Value> {
46 let metas = state.store.list_sessions().await.unwrap_or_default();
47 Json(serde_json::to_value(metas).unwrap_or(Value::Null))
48}
49
50async fn get_session(
51 State(state): State<AppState>,
52 axum::extract::Path(sid): axum::extract::Path<String>,
53) -> axum::response::Response {
54 let metas = state.store.list_sessions().await.unwrap_or_default();
55 let meta = metas.into_iter().find(|item| item.session_id == sid);
56 let requests = state.store.list_requests(&sid).await.unwrap_or_default();
57 match meta {
58 Some(found) => Json(json!({"meta": found, "requests": requests})).into_response(),
59 None => (axum::http::StatusCode::NOT_FOUND, "session not found").into_response(),
60 }
61}
62
63async fn get_request(
64 State(state): State<AppState>,
65 axum::extract::Path((sid, seq)): RequestPathParams,
66) -> axum::response::Response {
67 match state.store.get_request(&sid, seq).await {
68 Ok(Some(rec)) => match serde_json::to_value(&rec) {
69 Ok(value) => Json(value).into_response(),
70 Err(err) => {
71 tracing::warn!(?err, "failed to serialize capture record");
72 (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "store error").into_response()
73 }
74 },
75 Ok(None) => (axum::http::StatusCode::NOT_FOUND, "request not found").into_response(),
76 Err(err) => {
77 tracing::warn!(?err, "store error");
78 (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "store error").into_response()
79 }
80 }
81}