Skip to main content

mc_minder/api/
mod.rs

1use anyhow::Result;
2use log::info;
3use std::future::Future;
4use std::sync::Arc;
5use tokio::sync::RwLock;
6use warp::Filter;
7use serde::{Serialize, Deserialize};
8
9use crate::context::ContextManager;
10use crate::command_sender::MultiCommandSender;
11
12#[derive(Debug, Serialize, Deserialize)]
13pub struct StatusResponse {
14    pub status: String,
15    pub uptime: u64,
16    pub context_messages: usize,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct CommandRequest {
21    pub command: String,
22}
23
24#[derive(Debug, Serialize, Deserialize)]
25pub struct CommandResponse {
26    pub success: bool,
27    pub result: String,
28}
29
30pub struct HttpApi {
31    port: u16,
32    context: Arc<ContextManager>,
33    sender: Arc<RwLock<MultiCommandSender>>,
34    start_time: std::time::Instant,
35}
36
37impl HttpApi {
38    pub fn new(
39        port: u16,
40        context: Arc<ContextManager>,
41        sender: Arc<RwLock<MultiCommandSender>>,
42    ) -> Self {
43        Self {
44            port,
45            context,
46            sender,
47            start_time: std::time::Instant::now(),
48        }
49    }
50
51    pub async fn start<S>(self: Arc<Self>, shutdown: S) -> Result<()>
52    where
53        S: Future<Output = ()> + Send + 'static,
54    {
55        let context = self.context.clone();
56        let start_time = self.start_time;
57        let port = self.port;
58
59        let status_route = warp::path("status")
60            .and(warp::get())
61            .map(move || {
62                let response = StatusResponse {
63                    status: "running".to_string(),
64                    uptime: start_time.elapsed().as_secs(),
65                    context_messages: context.len(),
66                };
67                warp::reply::json(&response)
68            });
69
70        let context = self.context.clone();
71        let history_route = warp::path("history")
72            .and(warp::get())
73            .map(move || {
74                let messages = context.get_messages();
75                warp::reply::json(&messages)
76            });
77
78        let sender = self.sender.clone();
79        let command_route = warp::path("command")
80            .and(warp::post())
81            .and(warp::body::json())
82            .and_then(move |req: CommandRequest| {
83                let sender = sender.clone();
84                async move {
85                    let mut sender_guard = sender.write().await;
86                    match sender_guard.send_command(&req.command).await {
87                        Ok(response_text) => {
88                            let response = CommandResponse {
89                                success: true,
90                                result: response_text.trim().to_string(),
91                            };
92                            Ok::<_, warp::Rejection>(warp::reply::json(&response))
93                        }
94                        Err(e) => {
95                            let response = CommandResponse {
96                                success: false,
97                                result: e.to_string(),
98                            };
99                            Ok(warp::reply::json(&response))
100                        }
101                    }
102                }
103            });
104
105        let routes = status_route
106            .or(history_route)
107            .or(command_route)
108            .with(warp::cors().allow_any_origin());
109
110        info!("Starting HTTP API server on port {}", port);
111
112        warp::serve(routes)
113            .bind_with_graceful_shutdown(([0, 0, 0, 0], port), shutdown)
114            .1
115            .await;
116
117        Ok(())
118    }
119}