Skip to main content

ccs_proxy/api/
routes.rs

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}