Skip to main content

miyabi_a2a/http/
server.rs

1//! HTTP server implementation using Axum
2
3use axum::{
4    http::{header, HeaderValue, Method},
5    routing::{get, post},
6    Router,
7};
8use std::net::SocketAddr;
9use std::sync::Arc;
10use tower_http::cors::CorsLayer;
11
12use super::routes::{
13    cancel_task, get_agents, get_events, get_system_status, get_workflow_dag, health_check,
14    retry_task,
15};
16use super::websocket::{broadcast_updates, ws_handler, WsState};
17use crate::storage::TaskStorage;
18
19/// Application state shared across all handlers
20#[derive(Clone)]
21pub struct AppState {
22    /// WebSocket state for real-time updates
23    pub ws_state: Arc<WsState>,
24    /// Task storage backend (using trait object for dynamic dispatch)
25    pub storage: Arc<dyn TaskStorage>,
26}
27
28/// HTTP server configuration
29#[derive(Debug, Clone)]
30pub struct HttpServerConfig {
31    /// Host
32    pub host: String,
33    pub port: u16,
34}
35
36impl Default for HttpServerConfig {
37    fn default() -> Self {
38        Self {
39            host: "127.0.0.1".to_string(),
40            port: 3001,
41        }
42    }
43}
44
45/// Start the HTTP REST API server with task storage
46pub async fn start_http_server(
47    config: HttpServerConfig,
48    storage: Arc<dyn TaskStorage>,
49) -> anyhow::Result<()> {
50    // Create WebSocket state
51    let ws_state = Arc::new(WsState::new());
52
53    // Create application state
54    let app_state = AppState {
55        ws_state: ws_state.clone(),
56        storage,
57    };
58
59    // Spawn background task to broadcast updates
60    let ws_state_clone = ws_state.clone();
61    tokio::spawn(async move {
62        broadcast_updates(ws_state_clone).await;
63    });
64
65    // CORS configuration for frontend
66    let cors = CorsLayer::new()
67        .allow_origin("http://localhost:5173".parse::<HeaderValue>()?)
68        .allow_methods([Method::GET, Method::POST])
69        .allow_headers([header::CONTENT_TYPE]);
70
71    // Build router with routes
72    let app = Router::new()
73        .route("/health", get(health_check))
74        .route("/api/agents", get(get_agents))
75        .route("/api/system", get(get_system_status))
76        .route("/api/events", get(get_events))
77        .route("/api/workflow/dag", get(get_workflow_dag))
78        // Task recovery endpoints
79        .route("/api/tasks/:id/retry", post(retry_task))
80        .route("/api/tasks/:id/cancel", post(cancel_task))
81        // WebSocket endpoint
82        .route("/ws", get(ws_handler))
83        .with_state(app_state)
84        .layer(cors);
85
86    // Bind to address
87    let addr = format!("{}:{}", config.host, config.port).parse::<SocketAddr>()?;
88
89    tracing::info!("🚀 Miyabi Dashboard API server listening on http://{}", addr);
90    tracing::info!("📡 WebSocket endpoint available at ws://{}/ws", addr);
91
92    // Start server
93    let listener = tokio::net::TcpListener::bind(addr).await?;
94    axum::serve(listener, app).await?;
95
96    Ok(())
97}