rush_sync_server/server/handlers/web/
api.rs

1use super::ServerDataWithConfig;
2use crate::server::{config, logging::ServerLogger};
3use actix_web::{web, HttpResponse, Result as ActixResult};
4use serde::{Deserialize, Serialize};
5use serde_json::json;
6use std::collections::VecDeque;
7use std::sync::{Arc, Mutex};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11struct Message {
12    message: String,
13    from: String,
14    timestamp: String,
15    id: u32,
16}
17
18pub async fn status_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
19    let uptime = SystemTime::now()
20        .duration_since(UNIX_EPOCH)
21        .unwrap_or_default()
22        .as_secs();
23    let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
24
25    Ok(HttpResponse::Ok().json(json!({
26        "status": "running",
27        "server_id": data.server.id,
28        "server_name": data.server.name,
29        "port": data.server.port,
30        "proxy_port": data.proxy_https_port, // Verwende https proxy port
31        "server": config::get_server_name(),
32        "version": config::get_server_version(),
33        "uptime_seconds": uptime,
34        "static_files": true,
35        "template_system": true,
36        "hot_reload": true,
37        "websocket_endpoint": "/ws/hot-reload",
38        "server_directory": server_dir,
39        "log_file": format!(".rss/servers/{}-[{}].log", data.server.name, data.server.port),
40        "certificate_file": format!(".rss/certs/{}-{}.cert", data.server.name, data.server.port),
41        "private_key_file": format!(".rss/certs/{}-{}.key", data.server.name, data.server.port),
42        "urls": {
43            "http": format!("http://127.0.0.1:{}", data.server.port),
44            "proxy": format!("https://{}.localhost:{}", data.server.name, data.proxy_https_port)
45        }
46    })))
47}
48
49pub async fn info_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
50    let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
51
52    Ok(HttpResponse::Ok().json(json!({
53        "name": "Rush Sync Server",
54        "version": config::get_server_version(),
55        "server_id": data.server.id,
56        "server_name": data.server.name,
57        "port": data.server.port,
58        "proxy_port": data.proxy_https_port, // Verwende https proxy port
59        "static_files_enabled": true,
60        "template_system": "enabled",
61        "hot_reload_enabled": true,
62        "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.server.port),
63        "server_directory": server_dir,
64        "certificate": {
65            "cert_file": format!(".rss/certs/{}-{}.cert", data.server.name, data.server.port),
66            "key_file": format!(".rss/certs/{}-{}.key", data.server.name, data.server.port),
67            "common_name": format!("{}.localhost", data.server.name)
68        },
69        "urls": {
70            "http": format!("http://127.0.0.1:{}", data.server.port),
71            "proxy": format!("https://{}.localhost:{}", data.server.name, data.proxy_https_port),
72            "websocket": format!("ws://127.0.0.1:{}/ws/hot-reload", data.server.port)
73        },
74        "endpoints": [
75            { "path": "/", "method": "GET", "description": "Static files from server directory", "type": "static" },
76            { "path": "/.rss/favicon.svg", "method": "GET", "description": "SVG favicon", "type": "static" },
77            { "path": "/api/status", "method": "GET", "description": "Server status", "type": "api" },
78            { "path": "/api/info", "method": "GET", "description": "API information", "type": "api" },
79            { "path": "/api/metrics", "method": "GET", "description": "Server metrics", "type": "api" },
80            { "path": "/api/stats", "method": "GET", "description": "Request statistics", "type": "api" },
81            { "path": "/api/logs", "method": "GET", "description": "Live server logs", "type": "api" },
82            { "path": "/api/logs/raw", "method": "GET", "description": "Raw log data (JSON)", "type": "api" },
83            { "path": "/api/health", "method": "GET", "description": "Health check", "type": "api" },
84            { "path": "/ws/hot-reload", "method": "GET", "description": "WebSocket hot reload", "type": "websocket" }
85        ]
86    })))
87}
88
89pub async fn metrics_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
90    let uptime = SystemTime::now()
91        .duration_since(UNIX_EPOCH)
92        .unwrap_or_default()
93        .as_secs();
94    let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
95    let log_file_size = if let Ok(logger) = ServerLogger::new(&data.server.name, data.server.port) {
96        logger.get_log_file_size_bytes().unwrap_or(0)
97    } else {
98        0
99    };
100
101    let file_count = std::fs::read_dir(&server_dir)
102        .map(|entries| entries.count())
103        .unwrap_or(0);
104
105    Ok(HttpResponse::Ok().json(json!({
106        "server_id": data.server.id,
107        "server_name": data.server.name,
108        "port": data.server.port,
109        "uptime_seconds": uptime,
110        "status": "running",
111        "hot_reload": {
112            "enabled": true,
113            "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.server.port),
114            "watching_directory": server_dir,
115            "file_watcher": "active"
116        },
117        "static_files": {
118            "directory": server_dir,
119            "file_count": file_count,
120            "enabled": true,
121            "template_based": true
122        },
123        "logging": {
124            "file_size_bytes": log_file_size,
125            "enabled": true
126        },
127        "endpoints_count": 10,
128        "last_updated": uptime
129    })))
130}
131
132pub async fn stats_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
133    let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
134
135    let stats = if let Ok(logger) = ServerLogger::new(&data.server.name, data.server.port) {
136        logger.get_request_stats().await.unwrap_or_default()
137    } else {
138        Default::default()
139    };
140
141    Ok(HttpResponse::Ok().json(json!({
142        "server_id": data.server.id,
143        "server_name": data.server.name,
144        "server_directory": server_dir,
145        "total_requests": stats.total_requests,
146        "unique_ips": stats.unique_ips,
147        "error_requests": stats.error_requests,
148        "security_alerts": stats.security_alerts,
149        "performance_warnings": stats.performance_warnings,
150        "avg_response_time_ms": stats.avg_response_time,
151        "max_response_time_ms": stats.max_response_time,
152        "total_bytes_sent": stats.total_bytes_sent,
153        "uptime_seconds": SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(),
154        "hot_reload_status": "active"
155    })))
156}
157
158pub async fn health_handler(_data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
159    let timestamp = SystemTime::now()
160        .duration_since(UNIX_EPOCH)
161        .unwrap_or_default()
162        .as_secs();
163
164    Ok(HttpResponse::Ok().json(json!({
165        "status": "healthy",
166        "timestamp": timestamp,
167        "uptime": "running",
168        "logging": "active",
169        "static_files": "enabled",
170        "template_system": "active",
171        "hot_reload": "active",
172        "file_watcher": "monitoring",
173        "config": "loaded from TOML"
174    })))
175}
176
177pub async fn ping_handler() -> ActixResult<HttpResponse> {
178    Ok(HttpResponse::Ok().json(json!({
179        "status": "pong",
180        "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
181        "server": "rush-sync-server",
182        "message": "Ping received successfully"
183    })))
184}
185
186// Static Message Store (In-Memory für Demo)
187lazy_static::lazy_static! {
188    static ref MESSAGES: Arc<Mutex<VecDeque<Message>>> = Arc::new(Mutex::new(VecDeque::new()));
189    static ref MESSAGE_COUNTER: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
190}
191
192// POST /api/message - Nachricht empfangen
193pub async fn message_handler(body: web::Json<serde_json::Value>) -> ActixResult<HttpResponse> {
194    let message_text = body
195        .get("message")
196        .and_then(|v| v.as_str())
197        .unwrap_or("No message");
198
199    let from = body
200        .get("from")
201        .and_then(|v| v.as_str())
202        .unwrap_or("Unknown");
203
204    let timestamp = body
205        .get("timestamp")
206        .and_then(|v| v.as_str())
207        .map(|s| s.to_string())
208        .unwrap_or_else(|| chrono::Local::now().to_rfc3339());
209
210    // Message speichern
211    {
212        let mut messages = MESSAGES.lock().unwrap();
213        let mut counter = MESSAGE_COUNTER.lock().unwrap();
214        *counter += 1;
215
216        let message = Message {
217            message: message_text.to_string(),
218            from: from.to_string(),
219            timestamp: timestamp.to_string(),
220            id: *counter,
221        };
222
223        messages.push_back(message);
224
225        // Max 100 Messages behalten
226        if messages.len() > 100 {
227            messages.pop_front();
228        }
229    }
230
231    // Message ID für Response merken
232    let message_id = {
233        let counter = MESSAGE_COUNTER.lock().unwrap();
234        *counter
235    };
236
237    log::info!("Message received from {}: {}", from, message_text);
238
239    Ok(HttpResponse::Ok().json(json!({
240        "status": "received",
241        "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
242        "message_id": message_id
243    })))
244}
245
246// GET /api/messages - Alle Nachrichten abrufen
247pub async fn messages_handler() -> ActixResult<HttpResponse> {
248    let messages = {
249        let messages_lock = MESSAGES.lock().unwrap();
250        messages_lock.iter().cloned().collect::<Vec<_>>()
251    };
252
253    Ok(HttpResponse::Ok().json(json!({
254        "messages": messages,
255        "count": messages.len(),
256        "status": "success"
257    })))
258}
259
260pub async fn close_browser_handler() -> ActixResult<HttpResponse> {
261    let html = r#"
262<script>
263setTimeout(() => { window.close(); }, 100);
264document.write('<h1>Server stopped - closing...</h1>');
265</script>
266"#;
267    Ok(HttpResponse::Ok().content_type("text/html").body(html))
268}