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::rcon::RconClient;
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    rcon: Arc<RwLock<Option<RconClient>>>,
34    start_time: std::time::Instant,
35}
36
37impl HttpApi {
38    pub fn new(
39        port: u16,
40        context: Arc<ContextManager>,
41        rcon: Arc<RwLock<Option<RconClient>>>,
42    ) -> Self {
43        Self {
44            port,
45            context,
46            rcon,
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 rcon_clone = self.rcon.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 rcon = rcon_clone.clone();
84                async move {
85                    let mut rcon_guard = rcon.write().await;
86                    if let Some(ref mut rcon_client) = *rcon_guard {
87                        match rcon_client.execute(&req.command).await {
88                            Ok(result) => {
89                                let response = CommandResponse {
90                                    success: true,
91                                    result,
92                                };
93                                Ok::<_, warp::Rejection>(warp::reply::json(&response))
94                            }
95                            Err(e) => {
96                                let response = CommandResponse {
97                                    success: false,
98                                    result: e.to_string(),
99                                };
100                                Ok(warp::reply::json(&response))
101                            }
102                        }
103                    } else {
104                        let response = CommandResponse {
105                            success: false,
106                            result: "RCON not connected. Please check server.properties for RCON settings.".to_string(),
107                        };
108                        Ok(warp::reply::json(&response))
109                    }
110                }
111            });
112
113        let routes = status_route
114            .or(history_route)
115            .or(command_route)
116            .with(warp::cors().allow_any_origin());
117
118        info!("Starting HTTP API server on port {}", port);
119
120        warp::serve(routes)
121            .bind_with_graceful_shutdown(([0, 0, 0, 0], port), shutdown)
122            .1
123            .await;
124
125        Ok(())
126    }
127}