rush_sync_server/server/handlers/web/
api.rs

1// ===== src/server/handlers/web/api.rs =====
2use super::PROXY_PORT;
3use crate::server::{config, logging::ServerLogger, types::ServerData};
4use actix_web::{web, HttpResponse, Result as ActixResult};
5use serde_json::json;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8pub async fn status_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
9    let uptime = SystemTime::now()
10        .duration_since(UNIX_EPOCH)
11        .unwrap_or_default()
12        .as_secs();
13    let server_dir = format!("www/{}-[{}]", data.name, data.port);
14
15    Ok(HttpResponse::Ok().json(json!({
16        "status": "running",
17        "server_id": data.id,
18        "server_name": data.name,
19        "port": data.port,
20        "proxy_port": PROXY_PORT,
21        "server": config::get_server_name(),
22        "version": config::get_server_version(),
23        "uptime_seconds": uptime,
24        "static_files": true,
25        "template_system": true,
26        "hot_reload": true,
27        "websocket_endpoint": "/ws/hot-reload",
28        "server_directory": server_dir,
29        "log_file": format!(".rss/servers/{}-[{}].log", data.name, data.port),
30        "certificate_file": format!(".rss/certs/{}-{}.cert", data.name, data.port),
31        "private_key_file": format!(".rss/certs/{}-{}.key", data.name, data.port),
32        "urls": {
33            "http": format!("http://127.0.0.1:{}", data.port),
34            "proxy": format!("https://{}.localhost:{}", data.name, PROXY_PORT)
35        }
36    })))
37}
38
39pub async fn info_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
40    let server_dir = format!("www/{}-[{}]", data.name, data.port);
41
42    Ok(HttpResponse::Ok().json(json!({
43        "name": "Rush Sync Server",
44        "version": config::get_server_version(),
45        "server_id": data.id,
46        "server_name": data.name,
47        "port": data.port,
48        "proxy_port": PROXY_PORT,
49        "static_files_enabled": true,
50        "template_system": "enabled",
51        "hot_reload_enabled": true,
52        "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.port),
53        "server_directory": server_dir,
54        "certificate": {
55            "cert_file": format!(".rss/certs/{}-{}.cert", data.name, data.port),
56            "key_file": format!(".rss/certs/{}-{}.key", data.name, data.port),
57            "common_name": format!("{}.localhost", data.name)
58        },
59        "urls": {
60            "http": format!("http://127.0.0.1:{}", data.port),
61            "proxy": format!("https://{}.localhost:{}", data.name, PROXY_PORT),
62            "websocket": format!("ws://127.0.0.1:{}/ws/hot-reload", data.port)
63        },
64        "endpoints": [
65            { "path": "/", "method": "GET", "description": "Static files from server directory", "type": "static" },
66            { "path": "/.rss/favicon.svg", "method": "GET", "description": "SVG favicon", "type": "static" },
67            { "path": "/api/status", "method": "GET", "description": "Server status", "type": "api" },
68            { "path": "/api/info", "method": "GET", "description": "API information", "type": "api" },
69            { "path": "/api/metrics", "method": "GET", "description": "Server metrics", "type": "api" },
70            { "path": "/api/stats", "method": "GET", "description": "Request statistics", "type": "api" },
71            { "path": "/api/logs", "method": "GET", "description": "Live server logs", "type": "api" },
72            { "path": "/api/logs/raw", "method": "GET", "description": "Raw log data (JSON)", "type": "api" },
73            { "path": "/api/health", "method": "GET", "description": "Health check", "type": "api" },
74            { "path": "/ws/hot-reload", "method": "GET", "description": "WebSocket hot reload", "type": "websocket" }
75        ]
76    })))
77}
78
79pub async fn metrics_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
80    let uptime = SystemTime::now()
81        .duration_since(UNIX_EPOCH)
82        .unwrap_or_default()
83        .as_secs();
84    let server_dir = format!("www/{}-[{}]", data.name, data.port);
85    let log_file_size = if let Ok(logger) = ServerLogger::new(&data.name, data.port) {
86        logger.get_log_file_size_bytes().unwrap_or(0)
87    } else {
88        0
89    };
90
91    let file_count = std::fs::read_dir(&server_dir)
92        .map(|entries| entries.count())
93        .unwrap_or(0);
94
95    Ok(HttpResponse::Ok().json(json!({
96        "server_id": data.id,
97        "server_name": data.name,
98        "port": data.port,
99        "uptime_seconds": uptime,
100        "status": "running",
101        "hot_reload": {
102            "enabled": true,
103            "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.port),
104            "watching_directory": server_dir,
105            "file_watcher": "active"
106        },
107        "static_files": {
108            "directory": server_dir,
109            "file_count": file_count,
110            "enabled": true,
111            "template_based": true
112        },
113        "logging": {
114            "file_size_bytes": log_file_size,
115            "enabled": true
116        },
117        "endpoints_count": 10,
118        "last_updated": uptime
119    })))
120}
121
122pub async fn stats_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
123    let server_dir = format!("www/{}-[{}]", data.name, data.port);
124
125    let stats = if let Ok(logger) = ServerLogger::new(&data.name, data.port) {
126        logger.get_request_stats().await.unwrap_or_default()
127    } else {
128        Default::default()
129    };
130
131    Ok(HttpResponse::Ok().json(json!({
132        "server_id": data.id,
133        "server_name": data.name,
134        "server_directory": server_dir,
135        "total_requests": stats.total_requests,
136        "unique_ips": stats.unique_ips,
137        "error_requests": stats.error_requests,
138        "security_alerts": stats.security_alerts,
139        "performance_warnings": stats.performance_warnings,
140        "avg_response_time_ms": stats.avg_response_time,
141        "max_response_time_ms": stats.max_response_time,
142        "total_bytes_sent": stats.total_bytes_sent,
143        "uptime_seconds": SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(),
144        "hot_reload_status": "active"
145    })))
146}
147
148pub async fn health_handler(_data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
149    let timestamp = SystemTime::now()
150        .duration_since(UNIX_EPOCH)
151        .unwrap_or_default()
152        .as_secs();
153
154    Ok(HttpResponse::Ok().json(json!({
155        "status": "healthy",
156        "timestamp": timestamp,
157        "uptime": "running",
158        "logging": "active",
159        "static_files": "enabled",
160        "template_system": "active",
161        "hot_reload": "active",
162        "file_watcher": "monitoring",
163        "config": "loaded from TOML"
164    })))
165}
166
167pub async fn close_browser_handler() -> ActixResult<HttpResponse> {
168    let html = r#"
169<script>
170setTimeout(() => { window.close(); }, 100);
171document.write('<h1>Server stopped - closing...</h1>');
172</script>
173"#;
174    Ok(HttpResponse::Ok().content_type("text/html").body(html))
175}