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