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}