Skip to main content

voirs_cli/commands/
server.rs

1//! Server command implementation with authentication and rate limiting.
2
3use axum::{
4    extract::{FromRequest, Query, Request, State},
5    http::{header, HeaderMap, StatusCode},
6    middleware::{self, Next},
7    response::{IntoResponse, Response},
8    routing::{get, post},
9    Json, Router,
10};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::net::{IpAddr, SocketAddr};
14use std::sync::{Arc, Mutex};
15use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16use tokio::signal;
17use tower::ServiceBuilder;
18use tower_http::{cors::CorsLayer, trace::TraceLayer};
19use voirs_sdk::{
20    config::AppConfig,
21    error::Result,
22    types::{AudioFormat, QualityLevel, SynthesisConfig},
23    VoirsPipeline,
24};
25
26/// API key configuration
27#[derive(Debug, Clone)]
28pub struct ApiKeyConfig {
29    /// API key value
30    pub key: String,
31    /// Key name/description
32    pub name: String,
33    /// Rate limit (requests per minute)
34    pub rate_limit: u32,
35    /// Whether the key is enabled
36    pub enabled: bool,
37    /// Creation timestamp
38    pub created_at: SystemTime,
39}
40
41/// Rate limiting bucket for tracking requests
42#[derive(Debug, Clone)]
43pub struct RateLimitBucket {
44    /// Number of requests in current window
45    pub requests: u32,
46    /// Window start time
47    pub window_start: Instant,
48    /// Rate limit for this bucket
49    pub limit: u32,
50}
51
52/// Authentication and rate limiting state
53#[derive(Debug)]
54pub struct AuthState {
55    /// Valid API keys
56    pub api_keys: HashMap<String, ApiKeyConfig>,
57    /// Rate limiting buckets per IP/API key
58    pub rate_limits: HashMap<String, RateLimitBucket>,
59    /// Usage statistics per API key
60    pub usage_stats: HashMap<String, UsageStats>,
61    /// Access logs
62    pub access_logs: Vec<AccessLogEntry>,
63}
64
65/// Usage statistics for API keys
66#[derive(Debug, Clone, Default, Serialize)]
67pub struct UsageStats {
68    pub total_requests: u64,
69    pub successful_requests: u64,
70    pub failed_requests: u64,
71    pub total_audio_seconds: f64,
72    pub bytes_transferred: u64,
73    pub last_used: Option<SystemTime>,
74}
75
76/// Access log entry
77#[derive(Debug, Clone, Serialize)]
78pub struct AccessLogEntry {
79    pub timestamp: SystemTime,
80    pub ip_address: String,
81    pub api_key: Option<String>,
82    pub method: String,
83    pub path: String,
84    pub status_code: u16,
85    pub response_time_ms: u64,
86    pub bytes_transferred: u64,
87}
88
89/// Parameters for logging HTTP requests
90#[derive(Debug, Clone)]
91pub struct LogRequestParams<'a> {
92    pub client_ip: &'a str,
93    pub api_key: Option<String>,
94    pub method: &'a str,
95    pub path: &'a str,
96    pub status_code: u16,
97    pub start_time: Instant,
98    pub bytes_transferred: u64,
99}
100
101/// Server application state
102#[derive(Clone)]
103pub struct AppState {
104    pipeline: Arc<VoirsPipeline>,
105    config: AppConfig,
106    auth: Arc<Mutex<AuthState>>,
107    start_time: Instant,
108    shutdown_signal: Arc<tokio::sync::RwLock<bool>>,
109}
110
111/// Synthesis request
112#[derive(Debug, Deserialize)]
113pub struct SynthesisRequest {
114    /// Text to synthesize
115    pub text: String,
116    /// Voice ID (optional)
117    pub voice: Option<String>,
118    /// Speaking rate (0.5 - 2.0)
119    pub rate: Option<f32>,
120    /// Pitch shift in semitones (-12.0 - 12.0)
121    pub pitch: Option<f32>,
122    /// Volume gain in dB (-20.0 - 20.0)
123    pub volume: Option<f32>,
124    /// Quality level
125    pub quality: Option<String>,
126    /// Audio format
127    pub format: Option<String>,
128    /// Enable audio enhancement
129    pub enhance: Option<bool>,
130}
131
132/// Synthesis response
133#[derive(Debug, Serialize)]
134pub struct SynthesisResponse {
135    /// Success status
136    pub success: bool,
137    /// Error message if failed
138    pub error: Option<String>,
139    /// Audio data (base64 encoded)
140    pub audio_data: Option<String>,
141    /// Audio duration in seconds
142    pub duration: Option<f32>,
143    /// Audio format
144    pub format: String,
145    /// Sample rate
146    pub sample_rate: u32,
147    /// Number of channels
148    pub channels: u16,
149}
150
151/// Voice information
152#[derive(Debug, Serialize)]
153pub struct VoiceInfo {
154    pub id: String,
155    pub name: String,
156    pub language: String,
157    pub gender: Option<String>,
158    pub description: Option<String>,
159    pub is_installed: bool,
160}
161
162/// Voices list response
163#[derive(Debug, Serialize)]
164pub struct VoicesResponse {
165    pub voices: Vec<VoiceInfo>,
166    pub total: usize,
167}
168
169/// Health check response
170#[derive(Debug, Serialize)]
171pub struct HealthResponse {
172    pub status: String,
173    pub version: String,
174    pub uptime_seconds: u64,
175    pub pipeline_ready: bool,
176}
177
178/// Detailed health check response
179#[derive(Debug, Serialize)]
180pub struct DetailedHealthResponse {
181    pub status: String,
182    pub version: String,
183    pub uptime_seconds: u64,
184    pub timestamp: u64,
185    pub checks: Vec<HealthCheck>,
186    pub system: SystemHealth,
187}
188
189/// Individual health check
190#[derive(Debug, Serialize)]
191pub struct HealthCheck {
192    pub name: String,
193    pub status: String,
194    pub message: Option<String>,
195    pub duration_ms: u64,
196    pub last_checked: u64,
197}
198
199/// System health information
200#[derive(Debug, Serialize)]
201pub struct SystemHealth {
202    pub memory_usage_mb: u64,
203    pub memory_available_mb: u64,
204    pub cpu_usage_percent: f32,
205    pub disk_usage_percent: f32,
206    pub thread_count: u64,
207    pub file_descriptors: u64,
208}
209
210/// Server statistics
211#[derive(Debug, Serialize)]
212pub struct ServerStats {
213    pub requests_total: u64,
214    pub requests_successful: u64,
215    pub requests_failed: u64,
216    pub average_synthesis_time_ms: f64,
217    pub total_audio_generated_seconds: f64,
218    pub uptime_seconds: u64,
219    pub active_api_keys: usize,
220    pub rate_limited_requests: u64,
221}
222
223/// Authentication info response
224#[derive(Debug, Serialize)]
225pub struct AuthInfoResponse {
226    pub api_key_name: String,
227    pub rate_limit: u32,
228    pub requests_remaining: u32,
229    pub requests_used: u32,
230    pub window_reset_seconds: u64,
231}
232
233/// Usage statistics response
234#[derive(Debug, Serialize)]
235pub struct UsageStatsResponse {
236    pub api_key_name: String,
237    pub stats: UsageStats,
238}
239
240/// Query parameters for voices endpoint
241#[derive(Debug, Deserialize)]
242pub struct VoicesQuery {
243    pub language: Option<String>,
244    pub gender: Option<String>,
245}
246
247/// Custom error type for API responses
248#[derive(Debug)]
249pub struct ApiError {
250    pub status: StatusCode,
251    pub message: String,
252}
253
254impl IntoResponse for ApiError {
255    fn into_response(self) -> Response {
256        let body = Json(serde_json::json!({
257            "error": self.message,
258            "status": self.status.as_u16()
259        }));
260        (self.status, body).into_response()
261    }
262}
263
264impl From<voirs_sdk::VoirsError> for ApiError {
265    fn from(err: voirs_sdk::VoirsError) -> Self {
266        ApiError {
267            status: StatusCode::INTERNAL_SERVER_ERROR,
268            message: err.to_string(),
269        }
270    }
271}
272
273/// Authentication middleware
274pub async fn auth_middleware(
275    State(state): State<AppState>,
276    headers: HeaderMap,
277    request: Request,
278    next: Next,
279) -> std::result::Result<Response, ApiError> {
280    let start_time = Instant::now();
281    let method = request.method().to_string();
282    let path = request.uri().path().to_string();
283
284    // Extract client IP
285    let client_ip = extract_client_ip(&headers, &request);
286
287    // Skip authentication for docs and health endpoints
288    if path == "/docs"
289        || path == "/"
290        || path == "/api/v1/health"
291        || path == "/api/v1/health/detailed"
292        || path == "/api/v1/health/ready"
293        || path == "/api/v1/health/live"
294    {
295        let response = next.run(request).await;
296        log_request(
297            &state,
298            LogRequestParams {
299                client_ip: &client_ip,
300                api_key: None,
301                method: &method,
302                path: &path,
303                status_code: 200,
304                start_time,
305                bytes_transferred: 0,
306            },
307        )
308        .await;
309        return Ok(response);
310    }
311
312    // Check if server is shutting down
313    if *state.shutdown_signal.read().await {
314        return Err(ApiError {
315            status: StatusCode::SERVICE_UNAVAILABLE,
316            message: "Server is shutting down".to_string(),
317        });
318    }
319
320    // Extract API key
321    let api_key = extract_api_key(&headers);
322
323    // Validate API key and check rate limits
324    let validation_result = validate_and_rate_limit(&state, &client_ip, api_key.as_deref()).await;
325
326    match validation_result {
327        Ok(api_key_config) => {
328            // Store API key config in request extensions for use by handlers
329            let mut request_with_extensions = request;
330            if let Some(ref config) = api_key_config {
331                request_with_extensions
332                    .extensions_mut()
333                    .insert(config.clone());
334            }
335
336            let response = next.run(request_with_extensions).await;
337            let status_code = response.status().as_u16();
338
339            // Calculate bytes transferred from response
340            let bytes_transferred = calculate_response_size(&response);
341
342            // Update usage statistics
343            update_usage_stats(
344                &state,
345                api_key_config.as_ref(),
346                status_code < 400,
347                bytes_transferred as f64,
348            )
349            .await;
350
351            // Log request
352            log_request(
353                &state,
354                LogRequestParams {
355                    client_ip: &client_ip,
356                    api_key: api_key_config.as_ref().map(|k| k.key.clone()),
357                    method: &method,
358                    path: &path,
359                    status_code,
360                    start_time,
361                    bytes_transferred,
362                },
363            )
364            .await;
365
366            Ok(response)
367        }
368        Err(error) => {
369            log_request(
370                &state,
371                LogRequestParams {
372                    client_ip: &client_ip,
373                    api_key,
374                    method: &method,
375                    path: &path,
376                    status_code: error.status.as_u16(),
377                    start_time,
378                    bytes_transferred: 0,
379                },
380            )
381            .await;
382            Err(error)
383        }
384    }
385}
386
387/// Extract client IP from request
388pub fn extract_client_ip(headers: &HeaderMap, _request: &Request) -> String {
389    // Check for forwarded headers first
390    if let Some(forwarded) = headers.get("x-forwarded-for") {
391        if let Ok(forwarded_str) = forwarded.to_str() {
392            if let Some(first_ip) = forwarded_str.split(',').next() {
393                return first_ip.trim().to_string();
394            }
395        }
396    }
397
398    if let Some(real_ip) = headers.get("x-real-ip") {
399        if let Ok(ip_str) = real_ip.to_str() {
400            return ip_str.to_string();
401        }
402    }
403
404    // Fallback to remote addr (would need to be passed through state)
405    "unknown".to_string()
406}
407
408/// Extract API key from authorization header
409pub fn extract_api_key(headers: &HeaderMap) -> Option<String> {
410    headers
411        .get("authorization")
412        .and_then(|header| header.to_str().ok())
413        .and_then(|auth_str| {
414            auth_str
415                .strip_prefix("Bearer ")
416                .or_else(|| auth_str.strip_prefix("ApiKey "))
417                .map(|s| s.to_string())
418        })
419        .or_else(|| {
420            headers
421                .get("x-api-key")
422                .and_then(|header| header.to_str().ok())
423                .map(|s| s.to_string())
424        })
425}
426
427/// Validate API key and check rate limits
428async fn validate_and_rate_limit(
429    state: &AppState,
430    client_ip: &str,
431    api_key: Option<&str>,
432) -> std::result::Result<Option<ApiKeyConfig>, ApiError> {
433    let mut auth_state = state.auth.lock().unwrap();
434
435    // Check if API key is provided and valid
436    let api_key_config = if let Some(key) = api_key {
437        match auth_state.api_keys.get(key) {
438            Some(config) if config.enabled => Some(config.clone()),
439            Some(_) => {
440                return Err(ApiError {
441                    status: StatusCode::UNAUTHORIZED,
442                    message: "API key is disabled".to_string(),
443                });
444            }
445            None => {
446                return Err(ApiError {
447                    status: StatusCode::UNAUTHORIZED,
448                    message: "Invalid API key".to_string(),
449                });
450            }
451        }
452    } else {
453        // If no API keys are configured, allow unauthenticated access
454        if auth_state.api_keys.is_empty() {
455            None
456        } else {
457            return Err(ApiError {
458                status: StatusCode::UNAUTHORIZED,
459                message: "API key required".to_string(),
460            });
461        }
462    };
463
464    // Determine rate limit key and limit
465    let (rate_limit_key, rate_limit) = if let Some(ref config) = api_key_config {
466        (format!("api_key:{}", config.key), config.rate_limit)
467    } else {
468        (format!("ip:{}", client_ip), 60) // Default 60 requests per minute for IP-based limiting
469    };
470
471    // Check rate limit
472    let now = Instant::now();
473    let window_duration = Duration::from_secs(60); // 1 minute window
474
475    let bucket = auth_state
476        .rate_limits
477        .entry(rate_limit_key)
478        .or_insert_with(|| RateLimitBucket {
479            requests: 0,
480            window_start: now,
481            limit: rate_limit,
482        });
483
484    // Reset window if expired
485    if now.duration_since(bucket.window_start) >= window_duration {
486        bucket.requests = 0;
487        bucket.window_start = now;
488    }
489
490    // Check if over limit
491    if bucket.requests >= bucket.limit {
492        return Err(ApiError {
493            status: StatusCode::TOO_MANY_REQUESTS,
494            message: format!(
495                "Rate limit exceeded. Limit: {} requests per minute",
496                bucket.limit
497            ),
498        });
499    }
500
501    // Increment request count
502    bucket.requests += 1;
503
504    Ok(api_key_config)
505}
506
507/// Calculate response size in bytes
508fn calculate_response_size(response: &Response) -> u64 {
509    // Try to get content-length header first
510    if let Some(content_length) = response.headers().get("content-length") {
511        if let Ok(length_str) = content_length.to_str() {
512            if let Ok(length) = length_str.parse::<u64>() {
513                return length;
514            }
515        }
516    }
517
518    // For responses without content-length, estimate based on body
519    // This is approximate since we can't easily access the body here
520    let headers_size = response
521        .headers()
522        .iter()
523        .map(|(name, value)| name.as_str().len() + value.len() + 4) // +4 for ": " and "\r\n"
524        .sum::<usize>() as u64;
525
526    // Add status line and basic HTTP overhead
527    let status_line_size = response.status().as_str().len() as u64 + 20; // HTTP version + spaces
528
529    headers_size + status_line_size
530}
531
532/// Update usage statistics
533async fn update_usage_stats(
534    state: &AppState,
535    api_key_config: Option<&ApiKeyConfig>,
536    success: bool,
537    bytes_transferred: f64,
538) {
539    update_usage_stats_with_audio(state, api_key_config, success, bytes_transferred, None).await;
540}
541
542/// Update usage statistics with optional audio duration
543async fn update_usage_stats_with_audio(
544    state: &AppState,
545    api_key_config: Option<&ApiKeyConfig>,
546    success: bool,
547    bytes_transferred: f64,
548    audio_duration: Option<f64>,
549) {
550    if let Some(config) = api_key_config {
551        let mut auth_state = state.auth.lock().unwrap();
552        let stats = auth_state
553            .usage_stats
554            .entry(config.key.clone())
555            .or_default();
556
557        stats.total_requests += 1;
558        stats.bytes_transferred += bytes_transferred as u64;
559
560        if success {
561            stats.successful_requests += 1;
562        } else {
563            stats.failed_requests += 1;
564        }
565
566        // Update audio duration if provided
567        if let Some(duration) = audio_duration {
568            stats.total_audio_seconds += duration;
569        }
570
571        stats.last_used = Some(SystemTime::now());
572    }
573}
574
575/// Log request
576async fn log_request(state: &AppState, params: LogRequestParams<'_>) {
577    let mut auth_state = state.auth.lock().unwrap();
578
579    let log_entry = AccessLogEntry {
580        timestamp: SystemTime::now(),
581        ip_address: params.client_ip.to_string(),
582        api_key: params.api_key,
583        method: params.method.to_string(),
584        path: params.path.to_string(),
585        status_code: params.status_code,
586        response_time_ms: params.start_time.elapsed().as_millis() as u64,
587        bytes_transferred: params.bytes_transferred,
588    };
589
590    auth_state.access_logs.push(log_entry);
591
592    // Keep only last 10000 log entries to prevent memory bloat
593    if auth_state.access_logs.len() > 10000 {
594        auth_state.access_logs.drain(0..1000);
595    }
596}
597
598/// Run server command
599pub async fn run_server(host: &str, port: u16, config: &AppConfig) -> Result<()> {
600    println!("Initializing VoiRS HTTP server...");
601
602    // Build pipeline
603    let pipeline = Arc::new(
604        VoirsPipeline::builder()
605            .with_quality(QualityLevel::High)
606            .with_gpu_acceleration(config.pipeline.use_gpu)
607            .build()
608            .await?,
609    );
610
611    // Initialize authentication state
612    let mut auth_state = AuthState {
613        api_keys: HashMap::new(),
614        rate_limits: HashMap::new(),
615        usage_stats: HashMap::new(),
616        access_logs: Vec::new(),
617    };
618
619    // Add default API key for development (should be configurable in production)
620    let default_api_key = ApiKeyConfig {
621        key: "voirs-dev-key-123".to_string(),
622        name: "Development Key".to_string(),
623        rate_limit: 100, // 100 requests per minute
624        enabled: true,
625        created_at: SystemTime::now(),
626    };
627    auth_state
628        .api_keys
629        .insert(default_api_key.key.clone(), default_api_key);
630
631    // Create application state
632    let state = AppState {
633        pipeline,
634        config: config.clone(),
635        auth: Arc::new(Mutex::new(auth_state)),
636        start_time: Instant::now(),
637        shutdown_signal: Arc::new(tokio::sync::RwLock::new(false)),
638    };
639
640    // Build the application router
641    let app = create_router(state.clone());
642
643    // Parse address
644    let addr: SocketAddr = format!("{}:{}", host, port)
645        .parse()
646        .map_err(|e| voirs_sdk::VoirsError::config_error(format!("Invalid address: {}", e)))?;
647
648    println!("Starting VoiRS server on http://{}", addr);
649    println!("API endpoints:");
650    println!("  POST /api/v1/synthesize      - Synthesize text to speech (requires auth)");
651    println!("  GET  /api/v1/voices          - List available voices (requires auth)");
652    println!("  GET  /api/v1/health          - Basic health check (public)");
653    println!("  GET  /api/v1/health/detailed - Detailed health check (public)");
654    println!("  GET  /api/v1/health/ready    - Readiness probe (public)");
655    println!("  GET  /api/v1/health/live     - Liveness probe (public)");
656    println!("  GET  /api/v1/stats           - Server statistics (requires auth)");
657    println!("  GET  /api/v1/auth/info       - Authentication information (requires auth)");
658    println!("  GET  /api/v1/auth/usage      - Usage statistics (requires auth)");
659    println!("  POST /api/v1/shutdown        - Graceful shutdown (requires auth)");
660    println!("  GET  /docs                   - API documentation (public)");
661    println!();
662    println!("Authentication:");
663    println!("  Default API key: voirs-dev-key-123");
664    println!("  Headers: Authorization: Bearer <api-key> or X-API-Key: <api-key>");
665    println!("  Rate limit: 100 requests per minute per API key");
666    println!();
667    println!("Graceful shutdown:");
668    println!("  Send SIGTERM or SIGINT to gracefully shutdown");
669    println!("  Or use POST /api/v1/shutdown endpoint");
670    println!();
671
672    // Start the server with graceful shutdown
673    let listener = tokio::net::TcpListener::bind(&addr).await.map_err(|e| {
674        voirs_sdk::VoirsError::config_error(format!("Failed to bind to {}: {}", addr, e))
675    })?;
676
677    // Set up graceful shutdown signal
678    let shutdown_signal = shutdown_signal();
679
680    // Clone state for shutdown handling
681    let shutdown_state = state.clone();
682
683    // Start the server with graceful shutdown
684    axum::serve(listener, app)
685        .with_graceful_shutdown(async move {
686            shutdown_signal.await;
687            println!("Starting graceful shutdown...");
688
689            // Set shutdown flag to reject new requests
690            *shutdown_state.shutdown_signal.write().await = true;
691
692            // Give time for in-flight requests to complete
693            tokio::time::sleep(Duration::from_secs(5)).await;
694
695            println!("Graceful shutdown complete");
696        })
697        .await
698        .map_err(|e| voirs_sdk::VoirsError::config_error(format!("Server error: {}", e)))?;
699
700    Ok(())
701}
702
703/// Signal handler for graceful shutdown
704async fn shutdown_signal() {
705    let ctrl_c = async {
706        signal::ctrl_c()
707            .await
708            .expect("failed to install Ctrl+C handler");
709    };
710
711    #[cfg(unix)]
712    let terminate = async {
713        signal::unix::signal(signal::unix::SignalKind::terminate())
714            .expect("failed to install SIGTERM handler")
715            .recv()
716            .await;
717    };
718
719    #[cfg(not(unix))]
720    let terminate = std::future::pending::<()>();
721
722    tokio::select! {
723        _ = ctrl_c => {
724            println!("Received Ctrl+C signal");
725        },
726        _ = terminate => {
727            println!("Received SIGTERM signal");
728        }
729    }
730}
731
732/// Graceful shutdown endpoint
733async fn shutdown_handler(State(state): State<AppState>) -> impl IntoResponse {
734    // Set shutdown flag
735    *state.shutdown_signal.write().await = true;
736
737    // Respond to the client before initiating shutdown
738    let response = Json(serde_json::json!({
739        "message": "Server shutdown initiated",
740        "status": "shutting_down"
741    }));
742
743    // Trigger shutdown in a separate task
744    let shutdown_state = state.clone();
745    tokio::spawn(async move {
746        // Give a moment for the response to be sent
747        tokio::time::sleep(Duration::from_millis(100)).await;
748
749        // Send shutdown signal
750        // This is a simplified approach - in production, you might want to use
751        // a channel or other mechanism to signal the main server loop
752        std::process::exit(0);
753    });
754
755    response
756}
757
758/// Create the router with all routes
759fn create_router(state: AppState) -> Router {
760    let middleware_state = state.clone();
761
762    Router::new()
763        // API routes
764        .route("/api/v1/synthesize", post(synthesize_handler))
765        .route("/api/v1/voices", get(voices_handler))
766        .route("/api/v1/health", get(health_handler))
767        .route("/api/v1/health/detailed", get(detailed_health_handler))
768        .route("/api/v1/health/ready", get(readiness_handler))
769        .route("/api/v1/health/live", get(liveness_handler))
770        .route("/api/v1/stats", get(stats_handler))
771        .route("/api/v1/auth/info", get(auth_info_handler))
772        .route("/api/v1/auth/usage", get(usage_stats_handler))
773        .route("/api/v1/shutdown", post(shutdown_handler))
774        // Documentation routes
775        .route("/docs", get(docs_handler))
776        .route("/", get(root_handler))
777        // Add state and middleware
778        .with_state(state)
779        .layer(
780            ServiceBuilder::new()
781                .layer(middleware::from_fn_with_state(
782                    middleware_state,
783                    auth_middleware,
784                ))
785                .layer(TraceLayer::new_for_http())
786                .layer(CorsLayer::permissive()),
787        )
788}
789
790/// Root handler - redirects to docs
791async fn root_handler() -> impl IntoResponse {
792    axum::response::Redirect::permanent("/docs")
793}
794
795/// Synthesize text to speech
796async fn synthesize_handler(
797    State(state): State<AppState>,
798    request: axum::extract::Request,
799) -> std::result::Result<Json<SynthesisResponse>, ApiError> {
800    // Extract API key configuration from request extensions
801    let api_key_config = request.extensions().get::<ApiKeyConfig>().cloned();
802
803    // Extract the JSON body
804    let axum::extract::Json(synthesis_request): axum::extract::Json<SynthesisRequest> =
805        axum::extract::Json::from_request(request, &state)
806            .await
807            .map_err(|_| ApiError {
808                status: StatusCode::BAD_REQUEST,
809                message: "Invalid JSON request body".to_string(),
810            })?;
811    // Validate request
812    if synthesis_request.text.trim().is_empty() {
813        return Err(ApiError {
814            status: StatusCode::BAD_REQUEST,
815            message: "Text cannot be empty".to_string(),
816        });
817    }
818
819    if synthesis_request.text.len() > 10000 {
820        return Err(ApiError {
821            status: StatusCode::BAD_REQUEST,
822            message: "Text too long (max 10000 characters)".to_string(),
823        });
824    }
825
826    // Parse quality level
827    let quality = match synthesis_request.quality.as_deref() {
828        Some("low") => QualityLevel::Low,
829        Some("medium") => QualityLevel::Medium,
830        Some("high") => QualityLevel::High,
831        Some("ultra") => QualityLevel::Ultra,
832        None => QualityLevel::High,
833        Some(other) => {
834            return Err(ApiError {
835                status: StatusCode::BAD_REQUEST,
836                message: format!("Invalid quality level: {}", other),
837            });
838        }
839    };
840
841    // Parse audio format
842    let format = match synthesis_request.format.as_deref() {
843        Some("wav") => AudioFormat::Wav,
844        Some("flac") => AudioFormat::Flac,
845        Some("mp3") => AudioFormat::Mp3,
846        Some("opus") => AudioFormat::Opus,
847        None => AudioFormat::Wav,
848        Some(other) => {
849            return Err(ApiError {
850                status: StatusCode::BAD_REQUEST,
851                message: format!("Unsupported audio format: {}", other),
852            });
853        }
854    };
855
856    // Validate parameters
857    if let Some(rate) = synthesis_request.rate {
858        if !(0.5..=2.0).contains(&rate) {
859            return Err(ApiError {
860                status: StatusCode::BAD_REQUEST,
861                message: "Speaking rate must be between 0.5 and 2.0".to_string(),
862            });
863        }
864    }
865
866    if let Some(pitch) = synthesis_request.pitch {
867        if !(-12.0..=12.0).contains(&pitch) {
868            return Err(ApiError {
869                status: StatusCode::BAD_REQUEST,
870                message: "Pitch shift must be between -12.0 and 12.0 semitones".to_string(),
871            });
872        }
873    }
874
875    if let Some(volume) = synthesis_request.volume {
876        if !(-20.0..=20.0).contains(&volume) {
877            return Err(ApiError {
878                status: StatusCode::BAD_REQUEST,
879                message: "Volume gain must be between -20.0 and 20.0 dB".to_string(),
880            });
881        }
882    }
883
884    // Create synthesis config
885    let synth_config = SynthesisConfig {
886        speaking_rate: synthesis_request.rate.unwrap_or(1.0),
887        pitch_shift: synthesis_request.pitch.unwrap_or(0.0),
888        volume_gain: synthesis_request.volume.unwrap_or(0.0),
889        enable_enhancement: synthesis_request.enhance.unwrap_or(false),
890        quality,
891        ..Default::default()
892    };
893
894    // Set voice if specified
895    if let Some(voice_id) = &synthesis_request.voice {
896        if let Err(e) = state.pipeline.set_voice(voice_id).await {
897            return Err(ApiError {
898                status: StatusCode::BAD_REQUEST,
899                message: format!("Invalid voice '{}': {}", voice_id, e),
900            });
901        }
902    }
903
904    // Perform synthesis
905    match state
906        .pipeline
907        .synthesize_with_config(&synthesis_request.text, &synth_config)
908        .await
909    {
910        Ok(audio) => {
911            // Convert audio to bytes
912            let audio_bytes = audio.to_format(format)?;
913            let audio_base64 = base64::encode(&audio_bytes);
914
915            // Update audio statistics (duration for usage tracking)
916            let duration_seconds = audio.duration() as f64;
917
918            // Update usage statistics with audio duration
919            if let Some(ref config) = api_key_config {
920                update_usage_stats_with_audio(
921                    &state,
922                    Some(config),
923                    true, // synthesis was successful
924                    0.0,  // bytes will be tracked by middleware
925                    Some(duration_seconds),
926                )
927                .await;
928            }
929
930            Ok(Json(SynthesisResponse {
931                success: true,
932                error: None,
933                audio_data: Some(audio_base64),
934                duration: Some(audio.duration()),
935                format: format.to_string(),
936                sample_rate: audio.sample_rate(),
937                channels: audio.channels() as u16,
938            }))
939        }
940        Err(e) => Ok(Json(SynthesisResponse {
941            success: false,
942            error: Some(e.to_string()),
943            audio_data: None,
944            duration: None,
945            format: format.to_string(),
946            sample_rate: 0,
947            channels: 0,
948        })),
949    }
950}
951
952/// List available voices
953async fn voices_handler(
954    State(state): State<AppState>,
955    Query(query): Query<VoicesQuery>,
956) -> std::result::Result<Json<VoicesResponse>, ApiError> {
957    // Get available voices from pipeline
958    let voice_configs = state.pipeline.list_voices().await.map_err(|e| ApiError {
959        status: StatusCode::INTERNAL_SERVER_ERROR,
960        message: format!("Failed to list voices: {}", e),
961    })?;
962
963    // Convert VoiceConfig to VoiceInfo
964    let mut voices: Vec<VoiceInfo> = voice_configs.iter().map(voice_config_to_info).collect();
965
966    // Apply filters
967    if let Some(language) = &query.language {
968        voices.retain(|v| v.language.to_lowercase().contains(&language.to_lowercase()));
969    }
970
971    if let Some(gender) = &query.gender {
972        voices.retain(|v| {
973            v.gender
974                .as_ref()
975                .is_some_and(|g| g.eq_ignore_ascii_case(gender))
976        });
977    }
978
979    let total = voices.len();
980
981    Ok(Json(VoicesResponse { voices, total }))
982}
983
984/// Convert SDK VoiceConfig to API VoiceInfo
985fn voice_config_to_info(config: &voirs_sdk::types::VoiceConfig) -> VoiceInfo {
986    // Build description from characteristics
987    let quality_str = match config.characteristics.quality {
988        voirs_sdk::types::QualityLevel::Low => "Standard quality",
989        voirs_sdk::types::QualityLevel::Medium => "Good quality",
990        voirs_sdk::types::QualityLevel::High => "High quality",
991        voirs_sdk::types::QualityLevel::Ultra => "Ultra-high quality",
992    };
993
994    let style_str = match config.characteristics.style {
995        voirs_sdk::types::SpeakingStyle::Neutral => "neutral",
996        voirs_sdk::types::SpeakingStyle::Conversational => "conversational",
997        voirs_sdk::types::SpeakingStyle::News => "news",
998        voirs_sdk::types::SpeakingStyle::Formal => "formal",
999        voirs_sdk::types::SpeakingStyle::Casual => "casual",
1000        voirs_sdk::types::SpeakingStyle::Energetic => "energetic",
1001        voirs_sdk::types::SpeakingStyle::Calm => "calm",
1002        voirs_sdk::types::SpeakingStyle::Dramatic => "dramatic",
1003        voirs_sdk::types::SpeakingStyle::Whisper => "whisper",
1004    };
1005
1006    let mut description_parts = vec![quality_str.to_string()];
1007    description_parts.push(format!("{} style", style_str));
1008
1009    if config.characteristics.emotion_support {
1010        description_parts.push("emotion support".to_string());
1011    }
1012
1013    let description = Some(description_parts.join(", "));
1014
1015    // Check if voice is installed (based on metadata flag set by pipeline)
1016    let is_installed = config
1017        .metadata
1018        .get("installed")
1019        .and_then(|v| v.parse::<bool>().ok())
1020        .unwrap_or(false);
1021
1022    VoiceInfo {
1023        id: config.id.clone(),
1024        name: config.name.clone(),
1025        language: config.language.as_str().to_string(),
1026        gender: config
1027            .characteristics
1028            .gender
1029            .as_ref()
1030            .map(|g| g.to_string().to_lowercase()),
1031        description,
1032        is_installed,
1033    }
1034}
1035
1036/// Health check endpoint
1037async fn health_handler(State(state): State<AppState>) -> Json<HealthResponse> {
1038    // Calculate uptime from start_time
1039    let uptime_seconds = state.start_time.elapsed().as_secs();
1040
1041    // Check pipeline status by testing if it can list voices
1042    let pipeline_ready = (state.pipeline.list_voices().await).is_ok();
1043
1044    Json(HealthResponse {
1045        status: if pipeline_ready {
1046            "healthy"
1047        } else {
1048            "degraded"
1049        }
1050        .to_string(),
1051        version: env!("CARGO_PKG_VERSION").to_string(),
1052        uptime_seconds,
1053        pipeline_ready,
1054    })
1055}
1056
1057/// Detailed health check endpoint
1058async fn detailed_health_handler(State(state): State<AppState>) -> Json<DetailedHealthResponse> {
1059    let uptime_seconds = state.start_time.elapsed().as_secs();
1060    let timestamp = SystemTime::now()
1061        .duration_since(UNIX_EPOCH)
1062        .unwrap()
1063        .as_secs();
1064
1065    let mut checks = Vec::new();
1066    let mut overall_status = "healthy";
1067
1068    // Pipeline health check
1069    let pipeline_start = Instant::now();
1070    let pipeline_check = match state.pipeline.list_voices().await {
1071        Ok(_) => HealthCheck {
1072            name: "pipeline".to_string(),
1073            status: "healthy".to_string(),
1074            message: Some("Pipeline is operational".to_string()),
1075            duration_ms: pipeline_start.elapsed().as_millis() as u64,
1076            last_checked: timestamp,
1077        },
1078        Err(e) => {
1079            overall_status = "degraded";
1080            HealthCheck {
1081                name: "pipeline".to_string(),
1082                status: "unhealthy".to_string(),
1083                message: Some(format!("Pipeline error: {}", e)),
1084                duration_ms: pipeline_start.elapsed().as_millis() as u64,
1085                last_checked: timestamp,
1086            }
1087        }
1088    };
1089    checks.push(pipeline_check);
1090
1091    // Memory health check
1092    let memory_start = Instant::now();
1093    let memory_check = check_memory_health();
1094    checks.push(HealthCheck {
1095        name: "memory".to_string(),
1096        status: memory_check.0,
1097        message: Some(memory_check.1),
1098        duration_ms: memory_start.elapsed().as_millis() as u64,
1099        last_checked: timestamp,
1100    });
1101
1102    // Authentication system health check
1103    let auth_start = Instant::now();
1104    let auth_check = check_auth_health(&state);
1105    checks.push(HealthCheck {
1106        name: "authentication".to_string(),
1107        status: auth_check.0,
1108        message: Some(auth_check.1),
1109        duration_ms: auth_start.elapsed().as_millis() as u64,
1110        last_checked: timestamp,
1111    });
1112
1113    // File system health check
1114    let fs_start = Instant::now();
1115    let fs_check = check_filesystem_health();
1116    checks.push(HealthCheck {
1117        name: "filesystem".to_string(),
1118        status: fs_check.0,
1119        message: Some(fs_check.1),
1120        duration_ms: fs_start.elapsed().as_millis() as u64,
1121        last_checked: timestamp,
1122    });
1123
1124    // Update overall status based on checks
1125    if checks.iter().any(|c| c.status == "unhealthy") {
1126        overall_status = "unhealthy";
1127    } else if checks.iter().any(|c| c.status == "degraded") {
1128        overall_status = "degraded";
1129    }
1130
1131    // Get system health information
1132    let system_health = get_system_health();
1133
1134    Json(DetailedHealthResponse {
1135        status: overall_status.to_string(),
1136        version: env!("CARGO_PKG_VERSION").to_string(),
1137        uptime_seconds,
1138        timestamp,
1139        checks,
1140        system: system_health,
1141    })
1142}
1143
1144/// Readiness probe endpoint (K8s style)
1145async fn readiness_handler(State(state): State<AppState>) -> impl IntoResponse {
1146    // Check if the service is ready to serve traffic
1147    let pipeline_ready = (state.pipeline.list_voices().await).is_ok();
1148
1149    let auth_ready = {
1150        let auth_state = state.auth.lock().unwrap();
1151        true // Allow unauthenticated access for development
1152    };
1153
1154    if pipeline_ready && auth_ready {
1155        (
1156            StatusCode::OK,
1157            Json(serde_json::json!({
1158                "status": "ready",
1159                "message": "Service is ready to serve traffic"
1160            })),
1161        )
1162    } else {
1163        (
1164            StatusCode::SERVICE_UNAVAILABLE,
1165            Json(serde_json::json!({
1166                "status": "not_ready",
1167                "message": "Service is not ready to serve traffic"
1168            })),
1169        )
1170    }
1171}
1172
1173/// Liveness probe endpoint (K8s style)
1174async fn liveness_handler(State(state): State<AppState>) -> impl IntoResponse {
1175    // Check if the service is alive (basic health check)
1176    let uptime = state.start_time.elapsed().as_secs();
1177
1178    // Consider the service dead if it's been running for more than 24 hours without restart
1179    // This is a simple example - in practice, you might check for deadlocks, memory leaks, etc.
1180    if uptime > 86400 {
1181        (
1182            StatusCode::SERVICE_UNAVAILABLE,
1183            Json(serde_json::json!({
1184                "status": "unhealthy",
1185                "message": "Service has been running too long, needs restart"
1186            })),
1187        )
1188    } else {
1189        (
1190            StatusCode::OK,
1191            Json(serde_json::json!({
1192                "status": "alive",
1193                "message": "Service is alive and responsive"
1194            })),
1195        )
1196    }
1197}
1198
1199/// Check memory health
1200fn check_memory_health() -> (String, String) {
1201    // This is a simplified memory check
1202    // In a real implementation, you would use proper system APIs
1203    match std::fs::read_to_string("/proc/meminfo") {
1204        Ok(meminfo) => {
1205            let lines: Vec<&str> = meminfo.lines().collect();
1206            let mut total_kb = 0;
1207            let mut available_kb = 0;
1208
1209            for line in lines {
1210                if line.starts_with("MemTotal:") {
1211                    if let Some(value) = line.split_whitespace().nth(1) {
1212                        total_kb = value.parse().unwrap_or(0);
1213                    }
1214                } else if line.starts_with("MemAvailable:") {
1215                    if let Some(value) = line.split_whitespace().nth(1) {
1216                        available_kb = value.parse().unwrap_or(0);
1217                    }
1218                }
1219            }
1220
1221            if total_kb > 0 {
1222                let usage_percent = ((total_kb - available_kb) as f64 / total_kb as f64) * 100.0;
1223                if usage_percent > 90.0 {
1224                    (
1225                        "unhealthy".to_string(),
1226                        format!("High memory usage: {:.1}%", usage_percent),
1227                    )
1228                } else if usage_percent > 80.0 {
1229                    (
1230                        "degraded".to_string(),
1231                        format!("Moderate memory usage: {:.1}%", usage_percent),
1232                    )
1233                } else {
1234                    (
1235                        "healthy".to_string(),
1236                        format!("Memory usage: {:.1}%", usage_percent),
1237                    )
1238                }
1239            } else {
1240                (
1241                    "degraded".to_string(),
1242                    "Could not determine memory usage".to_string(),
1243                )
1244            }
1245        }
1246        Err(_) => (
1247            "degraded".to_string(),
1248            "Memory information not available".to_string(),
1249        ),
1250    }
1251}
1252
1253/// Check authentication system health
1254fn check_auth_health(state: &AppState) -> (String, String) {
1255    let auth_state = state.auth.lock().unwrap();
1256
1257    let api_key_count = auth_state.api_keys.len();
1258    let active_buckets = auth_state.rate_limits.len();
1259    let log_entries = auth_state.access_logs.len();
1260
1261    // Check if we have too many log entries (potential memory leak)
1262    if log_entries > 50000 {
1263        return (
1264            "degraded".to_string(),
1265            "Too many access log entries".to_string(),
1266        );
1267    }
1268
1269    // Check if we have too many rate limit buckets (potential memory leak)
1270    if active_buckets > 10000 {
1271        return (
1272            "degraded".to_string(),
1273            "Too many active rate limit buckets".to_string(),
1274        );
1275    }
1276
1277    (
1278        "healthy".to_string(),
1279        format!(
1280            "Auth system operational: {} API keys, {} active buckets",
1281            api_key_count, active_buckets
1282        ),
1283    )
1284}
1285
1286/// Check filesystem health
1287fn check_filesystem_health() -> (String, String) {
1288    // Check if we can write to temp directory
1289    let temp_file = "/tmp/voirs_health_check";
1290    match std::fs::write(temp_file, "health check") {
1291        Ok(_) => {
1292            // Clean up the test file
1293            let _ = std::fs::remove_file(temp_file);
1294            ("healthy".to_string(), "Filesystem is writable".to_string())
1295        }
1296        Err(e) => ("unhealthy".to_string(), format!("Filesystem error: {}", e)),
1297    }
1298}
1299
1300/// Get system health information
1301fn get_system_health() -> SystemHealth {
1302    // This is a simplified implementation
1303    // In a real application, you would use proper system monitoring libraries
1304
1305    let (memory_usage_mb, memory_available_mb) = get_memory_info();
1306    let cpu_usage_percent = get_cpu_usage();
1307    let disk_usage_percent = get_disk_usage();
1308    let thread_count = get_thread_count();
1309    let file_descriptors = get_file_descriptor_count();
1310
1311    SystemHealth {
1312        memory_usage_mb,
1313        memory_available_mb,
1314        cpu_usage_percent,
1315        disk_usage_percent,
1316        thread_count,
1317        file_descriptors,
1318    }
1319}
1320
1321/// Get memory information
1322fn get_memory_info() -> (u64, u64) {
1323    match std::fs::read_to_string("/proc/meminfo") {
1324        Ok(meminfo) => {
1325            let lines: Vec<&str> = meminfo.lines().collect();
1326            let mut total_kb = 0;
1327            let mut available_kb = 0;
1328
1329            for line in lines {
1330                if line.starts_with("MemTotal:") {
1331                    if let Some(value) = line.split_whitespace().nth(1) {
1332                        total_kb = value.parse().unwrap_or(0);
1333                    }
1334                } else if line.starts_with("MemAvailable:") {
1335                    if let Some(value) = line.split_whitespace().nth(1) {
1336                        available_kb = value.parse().unwrap_or(0);
1337                    }
1338                }
1339            }
1340
1341            let usage_mb = (total_kb - available_kb) / 1024;
1342            let available_mb = available_kb / 1024;
1343
1344            (usage_mb, available_mb)
1345        }
1346        Err(_) => (0, 0),
1347    }
1348}
1349
1350/// Get CPU usage (percentage)
1351fn get_cpu_usage() -> f32 {
1352    // Estimate CPU usage based on process statistics
1353    // For accurate per-process CPU, would need to track usage over time
1354    let cpu_count = num_cpus::get() as f32;
1355
1356    // Try to get process CPU time on Unix systems
1357    #[cfg(unix)]
1358    {
1359        unsafe {
1360            let mut usage = std::mem::MaybeUninit::<libc::rusage>::uninit();
1361            if libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) == 0 {
1362                let usage = usage.assume_init();
1363                // ru_utime and ru_stime are in microseconds on some platforms
1364                let user_time =
1365                    usage.ru_utime.tv_sec as f32 + usage.ru_utime.tv_usec as f32 / 1_000_000.0;
1366                let sys_time =
1367                    usage.ru_stime.tv_sec as f32 + usage.ru_stime.tv_usec as f32 / 1_000_000.0;
1368                let total_time = user_time + sys_time;
1369
1370                // Rough estimate: normalize by CPU count
1371                // For web server, typically uses 30-60% of one core during active requests
1372                return ((total_time / 100.0).min(1.0) * 50.0).min(100.0);
1373            }
1374        }
1375    }
1376
1377    // Fallback estimate for active server
1378    25.0
1379}
1380
1381/// Get disk usage (percentage)
1382fn get_disk_usage() -> f32 {
1383    // Use platform-specific APIs to get disk usage
1384    #[cfg(target_os = "linux")]
1385    {
1386        // Parse /proc/mounts to find root partition, then use statvfs
1387        use std::ffi::CString;
1388        use std::mem::MaybeUninit;
1389
1390        let path = CString::new("/").unwrap();
1391        unsafe {
1392            let mut stat: libc::statvfs = MaybeUninit::zeroed().assume_init();
1393            if libc::statvfs(path.as_ptr(), &mut stat) == 0 {
1394                let total_blocks = stat.f_blocks;
1395                let free_blocks = stat.f_bfree;
1396                let used_blocks = total_blocks - free_blocks;
1397
1398                if total_blocks > 0 {
1399                    return (used_blocks as f32 / total_blocks as f32) * 100.0;
1400                }
1401            }
1402        }
1403    }
1404
1405    #[cfg(target_os = "macos")]
1406    {
1407        // Use statfs on macOS
1408        use std::ffi::CString;
1409        use std::mem::MaybeUninit;
1410
1411        let path = CString::new("/").unwrap();
1412        unsafe {
1413            let mut stat: libc::statfs = MaybeUninit::zeroed().assume_init();
1414            if libc::statfs(path.as_ptr(), &mut stat) == 0 {
1415                let total_blocks = stat.f_blocks;
1416                let free_blocks = stat.f_bfree;
1417                let used_blocks = total_blocks - free_blocks;
1418
1419                if total_blocks > 0 {
1420                    return (used_blocks as f64 / total_blocks as f64 * 100.0) as f32;
1421                }
1422            }
1423        }
1424    }
1425
1426    // Fallback for unsupported platforms
1427    0.0
1428}
1429
1430/// Get thread count
1431fn get_thread_count() -> u64 {
1432    match std::fs::read_to_string("/proc/self/status") {
1433        Ok(status) => {
1434            for line in status.lines() {
1435                if line.starts_with("Threads:") {
1436                    if let Some(value) = line.split_whitespace().nth(1) {
1437                        return value.parse().unwrap_or(0);
1438                    }
1439                }
1440            }
1441            0
1442        }
1443        Err(_) => 0,
1444    }
1445}
1446
1447/// Get file descriptor count
1448fn get_file_descriptor_count() -> u64 {
1449    match std::fs::read_dir("/proc/self/fd") {
1450        Ok(entries) => entries.count() as u64,
1451        Err(_) => 0,
1452    }
1453}
1454
1455/// Server statistics endpoint
1456async fn stats_handler(State(state): State<AppState>) -> Json<ServerStats> {
1457    let auth_state = state.auth.lock().unwrap();
1458    let uptime = state.start_time.elapsed().as_secs();
1459
1460    // Calculate aggregate statistics
1461    let mut total_requests = 0u64;
1462    let mut successful_requests = 0u64;
1463    let mut failed_requests = 0u64;
1464    let mut total_audio_seconds = 0.0;
1465
1466    for stats in auth_state.usage_stats.values() {
1467        total_requests += stats.total_requests;
1468        successful_requests += stats.successful_requests;
1469        failed_requests += stats.failed_requests;
1470        total_audio_seconds += stats.total_audio_seconds;
1471    }
1472
1473    // Count rate limited requests from access logs
1474    let rate_limited_requests = auth_state
1475        .access_logs
1476        .iter()
1477        .filter(|log| log.status_code == 429)
1478        .count() as u64;
1479
1480    // Calculate average synthesis time from synthesis requests in access logs
1481    let synthesis_logs: Vec<_> = auth_state
1482        .access_logs
1483        .iter()
1484        .filter(|log| log.path == "/api/v1/synthesize" && log.status_code == 200)
1485        .collect();
1486
1487    let average_synthesis_time_ms = if synthesis_logs.is_empty() {
1488        0.0
1489    } else {
1490        synthesis_logs
1491            .iter()
1492            .map(|log| log.response_time_ms as f64)
1493            .sum::<f64>()
1494            / synthesis_logs.len() as f64
1495    };
1496
1497    Json(ServerStats {
1498        requests_total: total_requests,
1499        requests_successful: successful_requests,
1500        requests_failed: failed_requests,
1501        average_synthesis_time_ms,
1502        total_audio_generated_seconds: total_audio_seconds,
1503        uptime_seconds: uptime,
1504        active_api_keys: auth_state.api_keys.len(),
1505        rate_limited_requests,
1506    })
1507}
1508
1509/// Authentication info endpoint
1510async fn auth_info_handler(
1511    State(state): State<AppState>,
1512    headers: HeaderMap,
1513) -> std::result::Result<Json<AuthInfoResponse>, ApiError> {
1514    let api_key = extract_api_key(&headers).ok_or_else(|| ApiError {
1515        status: StatusCode::UNAUTHORIZED,
1516        message: "API key required".to_string(),
1517    })?;
1518
1519    let auth_state = state.auth.lock().unwrap();
1520
1521    let api_key_config = auth_state.api_keys.get(&api_key).ok_or_else(|| ApiError {
1522        status: StatusCode::UNAUTHORIZED,
1523        message: "Invalid API key".to_string(),
1524    })?;
1525
1526    // Get current usage from rate limit bucket
1527    let rate_limit_key = format!("api_key:{}", api_key);
1528    let bucket = auth_state.rate_limits.get(&rate_limit_key);
1529
1530    let (requests_used, requests_remaining, window_reset_seconds) = if let Some(bucket) = bucket {
1531        let elapsed = bucket.window_start.elapsed().as_secs();
1532        let reset_seconds = 60u64.saturating_sub(elapsed);
1533
1534        (
1535            bucket.requests,
1536            bucket.limit.saturating_sub(bucket.requests),
1537            reset_seconds,
1538        )
1539    } else {
1540        (0, api_key_config.rate_limit, 60)
1541    };
1542
1543    Ok(Json(AuthInfoResponse {
1544        api_key_name: api_key_config.name.clone(),
1545        rate_limit: api_key_config.rate_limit,
1546        requests_remaining,
1547        requests_used,
1548        window_reset_seconds,
1549    }))
1550}
1551
1552/// Usage statistics endpoint
1553async fn usage_stats_handler(
1554    State(state): State<AppState>,
1555    headers: HeaderMap,
1556) -> std::result::Result<Json<UsageStatsResponse>, ApiError> {
1557    let api_key = extract_api_key(&headers).ok_or_else(|| ApiError {
1558        status: StatusCode::UNAUTHORIZED,
1559        message: "API key required".to_string(),
1560    })?;
1561
1562    let auth_state = state.auth.lock().unwrap();
1563
1564    let api_key_config = auth_state.api_keys.get(&api_key).ok_or_else(|| ApiError {
1565        status: StatusCode::UNAUTHORIZED,
1566        message: "Invalid API key".to_string(),
1567    })?;
1568
1569    let stats = auth_state
1570        .usage_stats
1571        .get(&api_key)
1572        .cloned()
1573        .unwrap_or_default();
1574
1575    Ok(Json(UsageStatsResponse {
1576        api_key_name: api_key_config.name.clone(),
1577        stats,
1578    }))
1579}
1580
1581/// API documentation endpoint
1582async fn docs_handler() -> impl IntoResponse {
1583    let docs = r#"
1584<!DOCTYPE html>
1585<html>
1586<head>
1587    <title>VoiRS API Documentation</title>
1588    <style>
1589        body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
1590        .endpoint { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; }
1591        .method { color: white; padding: 3px 8px; border-radius: 3px; font-weight: bold; }
1592        .post { background: #49cc90; }
1593        .get { background: #61affe; }
1594        code { background: #f0f0f0; padding: 2px 4px; border-radius: 3px; }
1595    </style>
1596</head>
1597<body>
1598    <h1>VoiRS Text-to-Speech API</h1>
1599    <p>RESTful API for high-quality speech synthesis.</p>
1600    
1601    <div class="endpoint">
1602        <h3><span class="method post">POST</span> /api/v1/synthesize</h3>
1603        <p>Synthesize text to speech audio.</p>
1604        <p><strong>Request Body:</strong></p>
1605        <pre><code>{
1606  "text": "Hello, world!",
1607  "voice": "en-us-female-1",
1608  "rate": 1.0,
1609  "pitch": 0.0,
1610  "volume": 0.0,
1611  "quality": "high",
1612  "format": "wav",
1613  "enhance": false
1614}</code></pre>
1615        <p><strong>Response:</strong></p>
1616        <pre><code>{
1617  "success": true,
1618  "audio_data": "base64-encoded-audio",
1619  "duration": 2.5,
1620  "format": "wav",
1621  "sample_rate": 22050,
1622  "channels": 1
1623}</code></pre>
1624    </div>
1625    
1626    <div class="endpoint">
1627        <h3><span class="method get">GET</span> /api/v1/voices</h3>
1628        <p>List available voices.</p>
1629        <p><strong>Query Parameters:</strong></p>
1630        <ul>
1631            <li><code>language</code> - Filter by language (e.g., "en-US")</li>
1632            <li><code>gender</code> - Filter by gender ("male" or "female")</li>
1633        </ul>
1634    </div>
1635    
1636    <div class="endpoint">
1637        <h3><span class="method get">GET</span> /api/v1/health</h3>
1638        <p>Health check endpoint.</p>
1639    </div>
1640    
1641    <div class="endpoint">
1642        <h3><span class="method get">GET</span> /api/v1/stats</h3>
1643        <p>Server statistics and usage metrics.</p>
1644    </div>
1645    
1646    <div class="endpoint">
1647        <h3><span class="method get">GET</span> /api/v1/auth/info</h3>
1648        <p>Get authentication information and rate limit status.</p>
1649        <p><strong>Response:</strong></p>
1650        <pre><code>{
1651  "api_key_name": "Development Key",
1652  "rate_limit": 100,
1653  "requests_remaining": 85,
1654  "requests_used": 15,
1655  "window_reset_seconds": 42
1656}</code></pre>
1657    </div>
1658    
1659    <div class="endpoint">
1660        <h3><span class="method get">GET</span> /api/v1/auth/usage</h3>
1661        <p>Get detailed usage statistics for your API key.</p>
1662        <p><strong>Response:</strong></p>
1663        <pre><code>{
1664  "api_key_name": "Development Key",
1665  "stats": {
1666    "total_requests": 1542,
1667    "successful_requests": 1489,
1668    "failed_requests": 53,
1669    "total_audio_seconds": 3847.2,
1670    "bytes_transferred": 15732481,
1671    "last_used": "2024-01-15T10:30:00Z"
1672  }
1673}</code></pre>
1674    </div>
1675    
1676    <h2>Authentication</h2>
1677    <p>Most API endpoints require authentication using an API key. Include your API key in requests using one of these methods:</p>
1678    <ul>
1679        <li><strong>Authorization Header:</strong> <code>Authorization: Bearer your-api-key</code></li>
1680        <li><strong>API Key Header:</strong> <code>X-API-Key: your-api-key</code></li>
1681    </ul>
1682    <p><strong>Development API Key:</strong> <code>voirs-dev-key-123</code></p>
1683    <p><strong>Rate Limiting:</strong> Each API key has a rate limit (default: 100 requests per minute). Rate limit status is returned in response headers and available via the <code>/api/v1/auth/info</code> endpoint.</p>
1684    
1685    <h2>Audio Formats</h2>
1686    <ul>
1687        <li><strong>wav</strong> - Uncompressed WAV (default)</li>
1688        <li><strong>flac</strong> - Lossless FLAC compression</li>
1689        <li><strong>mp3</strong> - Lossy MP3 compression</li>
1690        <li><strong>opus</strong> - Modern Opus codec</li>
1691    </ul>
1692    
1693    <h2>Quality Levels</h2>
1694    <ul>
1695        <li><strong>low</strong> - Fast synthesis, lower quality</li>
1696        <li><strong>medium</strong> - Balanced speed and quality</li>
1697        <li><strong>high</strong> - High quality (default)</li>
1698        <li><strong>ultra</strong> - Maximum quality, slower</li>
1699    </ul>
1700</body>
1701</html>
1702"#;
1703
1704    ([(header::CONTENT_TYPE, "text/html")], docs)
1705}
1706
1707// Add base64 encoding support
1708mod base64 {
1709    pub fn encode(data: &[u8]) -> String {
1710        use std::convert::TryInto;
1711        const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1712
1713        let mut result = String::new();
1714        let chunks = data.chunks_exact(3);
1715        let remainder = chunks.remainder();
1716
1717        for chunk in chunks {
1718            let b = u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]]);
1719            result.push(ALPHABET[((b >> 18) & 63) as usize] as char);
1720            result.push(ALPHABET[((b >> 12) & 63) as usize] as char);
1721            result.push(ALPHABET[((b >> 6) & 63) as usize] as char);
1722            result.push(ALPHABET[(b & 63) as usize] as char);
1723        }
1724
1725        match remainder.len() {
1726            1 => {
1727                let b = (remainder[0] as u32) << 16;
1728                result.push(ALPHABET[((b >> 18) & 63) as usize] as char);
1729                result.push(ALPHABET[((b >> 12) & 63) as usize] as char);
1730                result.push_str("==");
1731            }
1732            2 => {
1733                let b = ((remainder[0] as u32) << 16) | ((remainder[1] as u32) << 8);
1734                result.push(ALPHABET[((b >> 18) & 63) as usize] as char);
1735                result.push(ALPHABET[((b >> 12) & 63) as usize] as char);
1736                result.push(ALPHABET[((b >> 6) & 63) as usize] as char);
1737                result.push('=');
1738            }
1739            _ => {}
1740        }
1741
1742        result
1743    }
1744}
1745
1746#[cfg(test)]
1747mod tests {
1748    use super::*;
1749
1750    #[test]
1751    fn test_synthesis_request_validation() {
1752        let request = SynthesisRequest {
1753            text: "Hello, world!".to_string(),
1754            voice: Some("en-us-female-1".to_string()),
1755            rate: Some(1.0),
1756            pitch: Some(0.0),
1757            volume: Some(0.0),
1758            quality: Some("high".to_string()),
1759            format: Some("wav".to_string()),
1760            enhance: Some(false),
1761        };
1762
1763        assert_eq!(request.text, "Hello, world!");
1764        assert_eq!(request.voice, Some("en-us-female-1".to_string()));
1765        assert_eq!(request.rate, Some(1.0));
1766    }
1767
1768    #[test]
1769    fn test_voice_info_creation() {
1770        let voice = VoiceInfo {
1771            id: "test-voice".to_string(),
1772            name: "Test Voice".to_string(),
1773            language: "en-US".to_string(),
1774            gender: Some("female".to_string()),
1775            description: Some("A test voice".to_string()),
1776            is_installed: true,
1777        };
1778
1779        assert_eq!(voice.id, "test-voice");
1780        assert_eq!(voice.language, "en-US");
1781        assert!(voice.is_installed);
1782    }
1783
1784    #[test]
1785    fn test_base64_encoding() {
1786        let data = b"Hello, world!";
1787        let encoded = base64::encode(data);
1788        assert!(!encoded.is_empty());
1789        assert!(encoded.is_ascii());
1790    }
1791}