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::command_sender::MultiCommandSender;
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 sender: Arc<RwLock<MultiCommandSender>>,
34 start_time: std::time::Instant,
35}
36
37impl HttpApi {
38 pub fn new(
39 port: u16,
40 context: Arc<ContextManager>,
41 sender: Arc<RwLock<MultiCommandSender>>,
42 ) -> Self {
43 Self {
44 port,
45 context,
46 sender,
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 sender = self.sender.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 sender = sender.clone();
84 async move {
85 let mut sender_guard = sender.write().await;
86 match sender_guard.send_command(&req.command).await {
87 Ok(response_text) => {
88 let response = CommandResponse {
89 success: true,
90 result: response_text.trim().to_string(),
91 };
92 Ok::<_, warp::Rejection>(warp::reply::json(&response))
93 }
94 Err(e) => {
95 let response = CommandResponse {
96 success: false,
97 result: e.to_string(),
98 };
99 Ok(warp::reply::json(&response))
100 }
101 }
102 }
103 });
104
105 let routes = status_route
106 .or(history_route)
107 .or(command_route)
108 .with(warp::cors().allow_any_origin());
109
110 info!("Starting HTTP API server on port {}", port);
111
112 warp::serve(routes)
113 .bind_with_graceful_shutdown(([0, 0, 0, 0], port), shutdown)
114 .1
115 .await;
116
117 Ok(())
118 }
119}