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}