snipt_server/server/
http_server.rs1use crate::api::{
4 add_snippet_handler, delete_snippet_handler, get_daemon_details, get_daemon_status,
5 get_snippet, get_snippets, update_snippet_handler, DeleteSnippetRequest, GetSnippetRequest,
6 SnippetRequest,
7};
8use crate::server::utils::{port_is_available, save_api_port};
9
10use snipt_core::{get_config_dir, is_daemon_running, Result, SniptError};
11use std::fs;
12use std::net::SocketAddr;
13use warp::Filter;
14
15use super::utils::get_api_server_port;
16
17pub async fn start_api_server(port: u16) -> Result<()> {
19 let addr = SocketAddr::from(([127, 0, 0, 1], port));
20
21 save_api_port(port)?;
23
24 println!("┌─────────────────────────────────────────┐");
25 println!("│ snipt API Server │");
26 println!("├─────────────────────────────────────────┤");
27 println!("│ Status: Running │");
28 println!("│ Port: {:<33} │", port);
29 println!("│ URL: http://localhost:{:<21} │", port);
30 println!("└─────────────────────────────────────────┘");
31
32 let cors = warp::cors()
34 .allow_any_origin()
35 .allow_headers(vec!["Content-Type"])
36 .allow_methods(vec!["GET", "POST", "DELETE", "PUT"]);
37
38 let get_snippets_route = warp::path!("api" / "snippets")
40 .and(warp::get())
41 .map(|| warp::reply::json(&get_snippets()));
42
43 let get_snippet_route = warp::path!("api" / "snippet")
44 .and(warp::get())
45 .and(warp::query::<GetSnippetRequest>())
46 .map(|query: GetSnippetRequest| warp::reply::json(&get_snippet(&query.shortcut)));
47
48 let add_snippet_route = warp::path!("api" / "snippets")
49 .and(warp::post())
50 .and(warp::body::json())
51 .map(|body: SnippetRequest| {
52 warp::reply::json(&add_snippet_handler(body.shortcut, body.snippet))
53 });
54
55 let update_snippet_route = warp::path!("api" / "snippets")
56 .and(warp::put())
57 .and(warp::body::json())
58 .map(|body: SnippetRequest| {
59 warp::reply::json(&update_snippet_handler(body.shortcut, body.snippet))
60 });
61
62 let delete_snippet_route = warp::path!("api" / "snippets")
63 .and(warp::delete())
64 .and(warp::query::<DeleteSnippetRequest>())
65 .map(|query: DeleteSnippetRequest| {
66 warp::reply::json(&delete_snippet_handler(query.shortcut))
67 });
68
69 let daemon_status_route = warp::path!("api" / "daemon" / "status")
70 .and(warp::get())
71 .map(|| warp::reply::json(&get_daemon_status()));
72
73 let daemon_details_route = warp::path!("api" / "daemon" / "details")
74 .and(warp::get())
75 .map(move || warp::reply::json(&get_daemon_details(port)));
76
77 let health_route = warp::path!("health").map(|| "snipt API is running");
79
80 let routes = get_snippets_route
82 .or(get_snippet_route)
83 .or(add_snippet_route)
84 .or(update_snippet_route)
85 .or(delete_snippet_route)
86 .or(daemon_status_route)
87 .or(daemon_details_route)
88 .or(health_route)
89 .with(cors);
90
91 let server = warp::serve(routes).try_bind_with_graceful_shutdown(addr, async {
93 tokio::signal::ctrl_c().await.ok();
95 println!("Received shutdown signal, stopping API server...");
96 });
97
98 match server {
99 Ok((addr, server)) => {
100 println!("API server started successfully on {}", addr);
101
102 server.await;
104 Ok(())
105 }
106 Err(e) => Err(SniptError::Other(format!(
107 "Failed to bind to port {}: {}",
108 port, e
109 ))),
110 }
111}
112
113pub fn check_api_server_health() -> Result<()> {
115 match get_api_server_port() {
116 Ok(port) => {
117 println!("Checking API server on port {}...", port);
118
119 match std::net::TcpStream::connect(format!("127.0.0.1:{}", port)) {
121 Ok(_) => {
122 println!("✅ API server is running on port {}", port);
123 println!("Connection successful (TCP port is open)");
124 Ok(())
125 }
126 Err(e) => {
127 println!("❌ Failed to connect to API server on port {}", port);
128 println!("Error: {}", e);
129 Err(SniptError::Other(format!(
130 "Failed to connect to API server: {}",
131 e
132 )))
133 }
134 }
135 }
136 Err(_) => {
137 println!("❌ API server port information not found");
138 println!(
139 "The API server may not be running or was started without saving port information."
140 );
141 Err(SniptError::Other(
142 "API server port information not found".to_string(),
143 ))
144 }
145 }
146}
147
148pub fn stop_api_server() -> Result<()> {
150 if let Ok(port) = get_api_server_port() {
152 println!("Stopping API server on port {}...", port);
153
154 let _ = std::net::TcpStream::connect(format!("127.0.0.1:{}", port));
156
157 let port_file_path = get_config_dir().join("api_port.txt");
159 if port_file_path.exists() {
160 let _ = fs::remove_file(port_file_path);
161 }
162
163 println!("API server port file removed.");
164
165 #[cfg(unix)]
167 {
168 use std::process::Command;
169 let _ = Command::new("bash")
171 .arg("-c")
172 .arg(format!("lsof -ti:{} | xargs kill -9", port))
173 .status();
174 }
175
176 #[cfg(windows)]
177 {
178 use std::process::Command;
179 let _ = Command::new("cmd")
181 .arg("/C")
182 .arg(format!("for /f \"tokens=5\" %a in ('netstat -aon ^| findstr :{} ^| findstr LISTENING') do taskkill /F /PID %a", port))
183 .status();
184 }
185 }
186
187 Ok(())
188}
189
190pub fn diagnose_api_server() -> Result<()> {
192 println!("snipt API Server Diagnostics");
193 println!("============================");
194
195 match is_daemon_running()? {
197 Some(pid) => println!("✅ Daemon is running with PID {}", pid),
198 None => println!("❌ Daemon is not running"),
199 }
200
201 let port_file = get_config_dir().join("api_port.txt");
203 if port_file.exists() {
204 println!("✅ API port file exists at {}", port_file.display());
205
206 match std::fs::read_to_string(&port_file) {
208 Ok(content) => {
209 match content.trim().parse::<u16>() {
210 Ok(port) => {
211 println!("✅ Port file contains valid port: {}", port);
212
213 match std::net::TcpStream::connect(format!("127.0.0.1:{}", port)) {
215 Ok(_) => {
216 println!("✅ API server is responsive on port {}", port);
217 println!("✅ Server URL: http://localhost:{}", port);
218 }
219 Err(_) => {
220 println!(
221 "❌ Port {} is not in use - API server may not be running",
222 port
223 );
224
225 if port_is_available(port) {
227 println!("✅ Port {} is available", port);
228 println!("ℹ️ You can start the API server with: snipt serve --port {}", port);
229 } else {
230 println!(
231 "❌ Port {} is in use but not responding as API server",
232 port
233 );
234 println!(
235 "ℹ️ You may need to free this port or choose another one"
236 );
237 }
238 }
239 }
240 }
241 Err(_) => println!("❌ Port file contains invalid port number"),
242 }
243 }
244 Err(e) => println!("❌ Failed to read port file: {}", e),
245 }
246 } else {
247 println!("❌ API port file does not exist at {}", port_file.display());
248 println!("ℹ️ API server may not have been started, or the file was deleted");
249 }
250
251 let log_file = get_config_dir().join("api_server_log.txt");
253 if log_file.exists() {
254 println!("✅ API server log file exists at {}", log_file.display());
255
256 #[cfg(unix)]
258 {
259 use std::process::Command;
260 println!("\nLast 10 lines of API server log:");
261 let _ = Command::new("sh")
262 .arg("-c")
263 .arg(format!("tail -n 10 \"{}\"", log_file.display()))
264 .status();
265 }
266
267 #[cfg(windows)]
268 {
269 use std::process::Command;
270 println!("\nLast 5 lines of API server log:");
271 let _ = Command::new("cmd")
272 .arg("/C")
273 .arg(format!(
274 "type \"{}\" | findstr /n . | findstr /r \"[1-5]:\"",
275 log_file.display()
276 ))
277 .status();
278 }
279 } else {
280 println!(
281 "❌ API server log file does not exist at {}",
282 log_file.display()
283 );
284 }
285
286 println!("\nTo start the API server, you can run: snipt serve --port <port>");
287 println!("To restart daemon and API: snipt start");
288 println!("To stop all services: snipt stop");
289
290 Ok(())
291}