Skip to main content

mc_minder/api/
mod.rs

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