use crate::api::{
add_snippet_handler, delete_snippet_handler, get_daemon_details, get_daemon_status,
get_snippet, get_snippets, update_snippet_handler, DeleteSnippetRequest, GetSnippetRequest,
SnippetRequest,
};
use crate::server::utils::{port_is_available, save_api_port};
use snipt_core::{get_config_dir, is_daemon_running, Result, SniptError};
use std::fs;
use std::net::SocketAddr;
use warp::Filter;
use super::utils::get_api_server_port;
pub async fn start_api_server(port: u16) -> Result<()> {
let addr = SocketAddr::from(([127, 0, 0, 1], port));
save_api_port(port)?;
println!("┌─────────────────────────────────────────┐");
println!("│ snipt API Server │");
println!("├─────────────────────────────────────────┤");
println!("│ Status: Running │");
println!("│ Port: {:<33} │", port);
println!("│ URL: http://localhost:{:<21} │", port);
println!("└─────────────────────────────────────────┘");
let cors = warp::cors()
.allow_any_origin()
.allow_headers(vec!["Content-Type"])
.allow_methods(vec!["GET", "POST", "DELETE", "PUT"]);
let get_snippets_route = warp::path!("api" / "snippets")
.and(warp::get())
.map(|| warp::reply::json(&get_snippets()));
let get_snippet_route = warp::path!("api" / "snippet")
.and(warp::get())
.and(warp::query::<GetSnippetRequest>())
.map(|query: GetSnippetRequest| warp::reply::json(&get_snippet(&query.shortcut)));
let add_snippet_route = warp::path!("api" / "snippets")
.and(warp::post())
.and(warp::body::json())
.map(|body: SnippetRequest| {
warp::reply::json(&add_snippet_handler(body.shortcut, body.snippet))
});
let update_snippet_route = warp::path!("api" / "snippets")
.and(warp::put())
.and(warp::body::json())
.map(|body: SnippetRequest| {
warp::reply::json(&update_snippet_handler(body.shortcut, body.snippet))
});
let delete_snippet_route = warp::path!("api" / "snippets")
.and(warp::delete())
.and(warp::query::<DeleteSnippetRequest>())
.map(|query: DeleteSnippetRequest| {
warp::reply::json(&delete_snippet_handler(query.shortcut))
});
let daemon_status_route = warp::path!("api" / "daemon" / "status")
.and(warp::get())
.map(|| warp::reply::json(&get_daemon_status()));
let daemon_details_route = warp::path!("api" / "daemon" / "details")
.and(warp::get())
.map(move || warp::reply::json(&get_daemon_details(port)));
let health_route = warp::path!("health").map(|| "snipt API is running");
let routes = get_snippets_route
.or(get_snippet_route)
.or(add_snippet_route)
.or(update_snippet_route)
.or(delete_snippet_route)
.or(daemon_status_route)
.or(daemon_details_route)
.or(health_route)
.with(cors);
let server = warp::serve(routes).try_bind_with_graceful_shutdown(addr, async {
tokio::signal::ctrl_c().await.ok();
println!("Received shutdown signal, stopping API server...");
});
match server {
Ok((addr, server)) => {
println!("API server started successfully on {}", addr);
server.await;
Ok(())
}
Err(e) => Err(SniptError::Other(format!(
"Failed to bind to port {}: {}",
port, e
))),
}
}
pub fn check_api_server_health() -> Result<()> {
match get_api_server_port() {
Ok(port) => {
println!("Checking API server on port {}...", port);
match std::net::TcpStream::connect(format!("127.0.0.1:{}", port)) {
Ok(_) => {
println!("✅ API server is running on port {}", port);
println!("Connection successful (TCP port is open)");
Ok(())
}
Err(e) => {
println!("❌ Failed to connect to API server on port {}", port);
println!("Error: {}", e);
Err(SniptError::Other(format!(
"Failed to connect to API server: {}",
e
)))
}
}
}
Err(_) => {
println!("❌ API server port information not found");
println!(
"The API server may not be running or was started without saving port information."
);
Err(SniptError::Other(
"API server port information not found".to_string(),
))
}
}
}
pub fn stop_api_server() -> Result<()> {
if let Ok(port) = get_api_server_port() {
println!("Stopping API server on port {}...", port);
let _ = std::net::TcpStream::connect(format!("127.0.0.1:{}", port));
let port_file_path = get_config_dir().join("api_port.txt");
if port_file_path.exists() {
let _ = fs::remove_file(port_file_path);
}
println!("API server port file removed.");
#[cfg(unix)]
{
use std::process::Command;
let _ = Command::new("bash")
.arg("-c")
.arg(format!("lsof -ti:{} | xargs kill -9", port))
.status();
}
#[cfg(windows)]
{
use std::process::Command;
let _ = Command::new("cmd")
.arg("/C")
.arg(format!("for /f \"tokens=5\" %a in ('netstat -aon ^| findstr :{} ^| findstr LISTENING') do taskkill /F /PID %a", port))
.status();
}
}
Ok(())
}
pub fn diagnose_api_server() -> Result<()> {
println!("snipt API Server Diagnostics");
println!("============================");
match is_daemon_running()? {
Some(pid) => println!("✅ Daemon is running with PID {}", pid),
None => println!("❌ Daemon is not running"),
}
let port_file = get_config_dir().join("api_port.txt");
if port_file.exists() {
println!("✅ API port file exists at {}", port_file.display());
match std::fs::read_to_string(&port_file) {
Ok(content) => {
match content.trim().parse::<u16>() {
Ok(port) => {
println!("✅ Port file contains valid port: {}", port);
match std::net::TcpStream::connect(format!("127.0.0.1:{}", port)) {
Ok(_) => {
println!("✅ API server is responsive on port {}", port);
println!("✅ Server URL: http://localhost:{}", port);
}
Err(_) => {
println!(
"❌ Port {} is not in use - API server may not be running",
port
);
if port_is_available(port) {
println!("✅ Port {} is available", port);
println!("ℹ️ You can start the API server with: snipt serve --port {}", port);
} else {
println!(
"❌ Port {} is in use but not responding as API server",
port
);
println!(
"ℹ️ You may need to free this port or choose another one"
);
}
}
}
}
Err(_) => println!("❌ Port file contains invalid port number"),
}
}
Err(e) => println!("❌ Failed to read port file: {}", e),
}
} else {
println!("❌ API port file does not exist at {}", port_file.display());
println!("ℹ️ API server may not have been started, or the file was deleted");
}
let log_file = get_config_dir().join("api_server_log.txt");
if log_file.exists() {
println!("✅ API server log file exists at {}", log_file.display());
#[cfg(unix)]
{
use std::process::Command;
println!("\nLast 10 lines of API server log:");
let _ = Command::new("sh")
.arg("-c")
.arg(format!("tail -n 10 \"{}\"", log_file.display()))
.status();
}
#[cfg(windows)]
{
use std::process::Command;
println!("\nLast 5 lines of API server log:");
let _ = Command::new("cmd")
.arg("/C")
.arg(format!(
"type \"{}\" | findstr /n . | findstr /r \"[1-5]:\"",
log_file.display()
))
.status();
}
} else {
println!(
"❌ API server log file does not exist at {}",
log_file.display()
);
}
println!("\nTo start the API server, you can run: snipt serve --port <port>");
println!("To restart daemon and API: snipt start");
println!("To stop all services: snipt stop");
Ok(())
}