Skip to main content

weft_core/api/
services.rs

1use crate::api::openai_compat::AppState;
2use axum::extract::{Path, State};
3use axum::http::StatusCode;
4use axum::response::IntoResponse;
5use axum::Json;
6
7fn service_request_url(name: &str, health_url: &str) -> String {
8    let base_url = health_url.trim_end_matches("/health");
9    let _ = name;
10    format!("{}/webhook", base_url)
11}
12
13fn service_request_body(name: &str, body: &serde_json::Value) -> serde_json::Value {
14    let _ = name;
15    body.clone()
16}
17
18pub async fn list_services(State(state): State<AppState>) -> Json<serde_json::Value> {
19    state.process_manager.run_health_checks().await;
20    let statuses = state.process_manager.all_statuses().await;
21    let service_names: Vec<String> = statuses.keys().cloned().collect();
22
23    let services: Vec<serde_json::Value> = service_names
24        .iter()
25        .filter_map(|name| {
26            let config = state.process_manager.service_config_sync(name)?;
27            let status = statuses
28                .get(name)
29                .map(|st| st.to_string())
30                .unwrap_or_else(|| "unknown".into());
31            Some(serde_json::json!({
32                "name": config.name,
33                "command": config.command,
34                "auto_start": config.auto_start,
35                "status": status,
36            }))
37        })
38        .collect();
39
40    Json(serde_json::json!({ "services": services }))
41}
42
43/// Proxy a webhook message to a managed weft-claw service.
44/// POST /api/services/{name}/webhook  body: {"message": "..."}
45pub async fn proxy_webhook(
46    State(state): State<AppState>,
47    Path(name): Path<String>,
48    Json(body): Json<serde_json::Value>,
49) -> impl IntoResponse {
50    let health_url = match state
51        .process_manager
52        .service_config_sync(&name)
53        .and_then(|svc| svc.health_url)
54    {
55        Some(u) => u.clone(),
56        None => {
57            return (
58                StatusCode::NOT_FOUND,
59                Json(serde_json::json!({ "error": format!("Service '{}' not found or has no health_url", name) })),
60            )
61                .into_response();
62        }
63    };
64
65    let request_url = service_request_url(&name, &health_url);
66    let request_body = service_request_body(&name, &body);
67
68    // Forward the request
69    let client = reqwest::Client::new();
70    match client
71        .post(&request_url)
72        .json(&request_body)
73        .timeout(std::time::Duration::from_secs(300))
74        .send()
75        .await
76    {
77        Ok(resp) => {
78            let status = resp.status();
79            match resp.text().await {
80                Ok(text) => {
81                    let axum_status = StatusCode::from_u16(status.as_u16())
82                        .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
83                    (axum_status, text).into_response()
84                }
85                Err(e) => (
86                    StatusCode::BAD_GATEWAY,
87                    Json(serde_json::json!({ "error": format!("Failed to read response: {}", e) })),
88                )
89                    .into_response(),
90            }
91        }
92        Err(e) => (
93            StatusCode::BAD_GATEWAY,
94            Json(serde_json::json!({ "error": format!("Failed to proxy webhook: {}", e) })),
95        )
96            .into_response(),
97    }
98}
99
100pub async fn start_service(
101    State(state): State<AppState>,
102    Path(name): Path<String>,
103) -> impl IntoResponse {
104    match state.process_manager.start(&name).await {
105        Ok(()) => Json(serde_json::json!({ "ok": true })).into_response(),
106        Err(e) => (
107            StatusCode::BAD_REQUEST,
108            Json(serde_json::json!({ "error": e.to_string() })),
109        )
110            .into_response(),
111    }
112}
113
114pub async fn stop_service(
115    State(state): State<AppState>,
116    Path(name): Path<String>,
117) -> impl IntoResponse {
118    match state.process_manager.stop(&name).await {
119        Ok(()) => Json(serde_json::json!({ "ok": true })).into_response(),
120        Err(e) => (
121            StatusCode::BAD_REQUEST,
122            Json(serde_json::json!({ "error": e.to_string() })),
123        )
124            .into_response(),
125    }
126}
127
128pub async fn restart_service(
129    State(state): State<AppState>,
130    Path(name): Path<String>,
131) -> impl IntoResponse {
132    match state.process_manager.restart(&name).await {
133        Ok(()) => Json(serde_json::json!({ "ok": true })).into_response(),
134        Err(e) => (
135            StatusCode::BAD_REQUEST,
136            Json(serde_json::json!({ "error": e.to_string() })),
137        )
138            .into_response(),
139    }
140}