use axum::{ Router, extract::{Json, Path, Query, State}, http::{HeaderMap, StatusCode, header}, response::IntoResponse, routing::{delete, get, post, put}, }; use crate::application::dtos::settings_dto::{ AdminCreateUserDto, AdminResetPasswordDto, DashboardStatsDto, ListUsersQueryDto, MigrationStateDto, SaveOidcSettingsDto, SaveStorageSettingsDto, StartMigrationDto, TestOidcConnectionDto, TestStorageConnectionDto, UpdateUserActiveDto, UpdateUserQuotaDto, UpdateUserRoleDto, VerifyMigrationDto, }; use crate::application::ports::auth_ports::TokenServicePort; use crate::common::di::AppState; use crate::interfaces::errors::AppError; use std::sync::Arc; use uuid::Uuid; /// Admin API routes — all require admin role. pub fn admin_routes() -> Router> { Router::new() // OIDC settings .route("/settings/oidc", get(get_oidc_settings)) .route("/settings/oidc", put(save_oidc_settings)) .route("/settings/oidc/test", post(test_oidc_connection)) // Storage settings .route("/settings/storage", get(get_storage_settings)) .route("/settings/storage", put(save_storage_settings)) .route("/settings/storage/test", post(test_storage_connection)) // Storage migration .route("/storage/migration", get(get_migration_status)) .route("/storage/migration/start", post(start_migration)) .route("/storage/migration/pause", post(pause_migration)) .route("/storage/migration/resume", post(resume_migration)) .route("/storage/migration/complete", post(complete_migration)) .route("/storage/migration/verify", post(verify_migration)) // Encryption key generation .route( "/settings/storage/generate-key", post(generate_encryption_key), ) .route("/settings/general", get(get_general_settings)) // Dashboard / stats .route("/dashboard", get(get_dashboard_stats)) // User management .route("/users", get(list_users)) .route("/users", post(create_user)) .route("/users/{id}", get(get_user)) .route("/users/{id}", delete(delete_user)) .route("/users/{id}/role", put(update_user_role)) .route("/users/{id}/active", put(update_user_active)) .route("/users/{id}/quota", put(update_user_quota)) .route("/users/{id}/password", put(reset_user_password)) // Registration control .route("/settings/registration", get(get_registration_setting)) .route("/settings/registration", put(set_registration_setting)) // Audio metadata .route("/audio/metadata/reextract", post(reextract_audio_metadata)) } /// Validate JWT and require admin role. Returns (user_id, role). async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(Uuid, String), AppError> { let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let token = headers .get(header::AUTHORIZATION) .and_then(|v| v.to_str().ok()) .and_then(|v| v.strip_prefix("Bearer ").map(|s| s.to_string())) .or_else(|| { crate::interfaces::api::cookie_auth::extract_cookie_value( headers, crate::interfaces::api::cookie_auth::ACCESS_COOKIE, ) }) .ok_or_else(|| AppError::unauthorized("Authorization token required"))?; let claims = auth .token_service .validate_token(&token) .map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?; if claims.role != "admin" { return Err(AppError::new( StatusCode::FORBIDDEN, "Admin access required", "Forbidden", )); } Ok(( Uuid::parse_str(&claims.sub) .map_err(|_| AppError::internal_error("Invalid user ID in token"))?, claims.role, )) } /// GET /api/admin/settings/oidc — get OIDC settings for the admin panel async fn get_oidc_settings( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let svc = state .admin_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; let settings = svc .get_oidc_settings() .await .map_err(|e| AppError::internal_error(format!("Failed to load settings: {}", e)))?; Ok(Json(settings)) } /// PUT /api/admin/settings/oidc — save OIDC settings + hot-reload async fn save_oidc_settings( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { let (user_id, _) = admin_guard(&state, &headers).await?; let svc = state .admin_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; svc.save_oidc_settings(dto, user_id) .await .map_err(|e| AppError::internal_error(format!("Failed to save settings: {}", e)))?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "OIDC settings saved and applied successfully" })), )) } /// POST /api/admin/settings/oidc/test — test OIDC discovery async fn test_oidc_connection( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { admin_guard(&state, &headers).await?; let svc = state .admin_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; let result = svc .test_oidc_connection(dto) .await .map_err(|e| AppError::internal_error(format!("Connection test failed: {}", e)))?; Ok(Json(result)) } // ───────────────────────────────────────────────────── // Storage settings handlers // ───────────────────────────────────────────────────── /// GET /api/admin/settings/storage — get storage backend settings async fn get_storage_settings( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let svc = state .storage_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Storage settings service not available"))?; let settings = svc .get_storage_settings() .await .map_err(|e| AppError::internal_error(format!("Failed to load storage settings: {}", e)))?; Ok(Json(settings)) } /// PUT /api/admin/settings/storage — save storage backend settings async fn save_storage_settings( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { let (user_id, _) = admin_guard(&state, &headers).await?; let svc = state .storage_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Storage settings service not available"))?; svc.save_storage_settings(dto, user_id) .await .map_err(|e| AppError::internal_error(format!("Failed to save storage settings: {}", e)))?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "Storage settings saved successfully" })), )) } /// POST /api/admin/settings/storage/test — test storage backend connection async fn test_storage_connection( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { admin_guard(&state, &headers).await?; let svc = state .storage_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Storage settings service not available"))?; let result = svc .test_storage_connection(dto) .await .map_err(|e| AppError::internal_error(format!("Storage connection test failed: {}", e)))?; Ok(Json(result)) } // ───────────────────────────────────────────────────── // Storage migration handlers // ───────────────────────────────────────────────────── /// GET /api/admin/storage/migration — current migration progress async fn get_migration_status( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let s = state.migration_state.read().await; Ok(Json(migration_state_to_dto(&s))) } /// POST /api/admin/storage/migration/start — begin background migration async fn start_migration( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { use crate::infrastructure::services::migration_blob_backend::MigrationStatus; admin_guard(&state, &headers).await?; // Check not already running. { let s = state.migration_state.read().await; if s.status == MigrationStatus::Running { return Err(AppError::bad_request("A migration is already running")); } } let pool = state .db_pool .clone() .ok_or_else(|| AppError::internal_error("Database not available"))?; let source = state.core.dedup_service.backend().clone(); let svc = state .storage_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Storage settings service not available"))?; // Build target backend from saved settings. let effective = svc .load_effective_storage_config() .await .map_err(|e| AppError::internal_error(format!("Failed to load storage config: {}", e)))?; let target = build_backend_from_config(&effective) .map_err(|e| AppError::internal_error(format!("Failed to build target backend: {}", e)))?; target .initialize() .await .map_err(|e| AppError::internal_error(format!("Target backend init failed: {}", e)))?; let concurrency = dto.concurrency.unwrap_or(4).clamp(1, 16); let migration_state = state.migration_state.clone(); // Spawn the background migration job. tokio::spawn(async move { if let Err(e) = crate::infrastructure::services::migration_job::run_migration( source, target, pool, migration_state, concurrency, ) .await { tracing::error!("Migration job error: {}", e); } }); Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "Migration started" })), )) } /// POST /api/admin/storage/migration/pause — pause running migration async fn pause_migration( State(state): State>, headers: HeaderMap, ) -> Result { use crate::infrastructure::services::migration_blob_backend::MigrationStatus; admin_guard(&state, &headers).await?; let mut s = state.migration_state.write().await; if s.status != MigrationStatus::Running { return Err(AppError::bad_request("No running migration to pause")); } s.status = MigrationStatus::Paused; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "Migration paused" })), )) } /// POST /api/admin/storage/migration/resume — resume paused migration async fn resume_migration( State(state): State>, headers: HeaderMap, ) -> Result { use crate::infrastructure::services::migration_blob_backend::MigrationStatus; admin_guard(&state, &headers).await?; // Set status back to Running — the background task checks on each blob. let mut s = state.migration_state.write().await; if s.status != MigrationStatus::Paused { return Err(AppError::bad_request("No paused migration to resume")); } s.status = MigrationStatus::Running; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "Migration resumed" })), )) } /// POST /api/admin/storage/migration/complete — finalize migration async fn complete_migration( State(state): State>, headers: HeaderMap, ) -> Result { use crate::infrastructure::services::migration_blob_backend::MigrationStatus; admin_guard(&state, &headers).await?; let s = state.migration_state.read().await; if s.status != MigrationStatus::Completed { return Err(AppError::bad_request( "Migration must be completed (100%) before finalizing", )); } drop(s); // Mark as idle — the admin has acknowledged completion. let mut s = state.migration_state.write().await; s.status = MigrationStatus::Idle; Ok(( StatusCode::OK, Json( serde_json::json!({ "message": "Migration finalized. Restart the server to use the new backend." }), ), )) } /// POST /api/admin/storage/migration/verify — run integrity check async fn verify_migration( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { admin_guard(&state, &headers).await?; let pool = state .db_pool .clone() .ok_or_else(|| AppError::internal_error("Database not available"))?; let svc = state .storage_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Storage settings service not available"))?; let effective = svc .load_effective_storage_config() .await .map_err(|e| AppError::internal_error(format!("Failed to load storage config: {}", e)))?; let target = build_backend_from_config(&effective) .map_err(|e| AppError::internal_error(format!("Failed to build target backend: {}", e)))?; target .initialize() .await .map_err(|e| AppError::internal_error(format!("Target backend init failed: {}", e)))?; let sample_size = dto.sample_size.unwrap_or(100).clamp(1, 1000); let result = crate::infrastructure::services::migration_job::verify_migration(target, pool, sample_size) .await .map_err(|e| AppError::internal_error(format!("Verification failed: {}", e)))?; Ok(Json(result)) } /// Helper: convert MigrationState to DTO for JSON serialization. fn migration_state_to_dto( s: &crate::infrastructure::services::migration_blob_backend::MigrationState, ) -> MigrationStateDto { let throughput = match (s.started_at, s.migrated_bytes) { (Some(start), bytes) if bytes > 0 => { let elapsed = chrono::Utc::now() .signed_duration_since(start) .num_seconds() .max(1) as f64; Some(bytes as f64 / elapsed) } _ => None, }; MigrationStateDto { status: format!("{:?}", s.status).to_lowercase(), total_blobs: s.total_blobs, migrated_blobs: s.migrated_blobs, migrated_bytes: s.migrated_bytes, failed_blobs: s.failed_blobs.clone(), started_at: s.started_at.map(|d| d.to_rfc3339()), completed_at: s.completed_at.map(|d| d.to_rfc3339()), throughput_bytes_per_sec: throughput, } } /// POST /api/admin/settings/storage/generate-key — generate a random AES-256 key. async fn generate_encryption_key( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let key = crate::infrastructure::services::encrypted_blob_backend::EncryptedBlobBackend::generate_key( ); let key_b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, key); Ok(Json(serde_json::json!({ "key": key_b64, "warning": "Store this key securely. If lost, encrypted data is IRRECOVERABLY LOST." }))) } /// Helper: build a BlobStorageBackend from StorageConfig. fn build_backend_from_config( config: &crate::common::config::StorageConfig, ) -> Result< std::sync::Arc, String, > { match config.backend { crate::common::config::StorageBackendType::Local => Ok(std::sync::Arc::new( crate::infrastructure::services::local_blob_backend::LocalBlobBackend::new( std::path::Path::new(&config.root_dir), ), )), crate::common::config::StorageBackendType::S3 => { let s3 = config.s3.as_ref().ok_or("S3 config missing")?; Ok(std::sync::Arc::new( crate::infrastructure::services::s3_blob_backend::S3BlobBackend::new(s3), )) } crate::common::config::StorageBackendType::Azure => { let az = config.azure.as_ref().ok_or("Azure config missing")?; Ok(std::sync::Arc::new( crate::infrastructure::services::azure_blob_backend::AzureBlobBackend::new(az), )) } } } /// GET /api/admin/settings/general — system overview (backward compat) async fn get_general_settings( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let user_count = auth .auth_application_service .count_users_efficient() .await .unwrap_or(0); let oidc_configured = auth.auth_application_service.oidc_enabled(); Ok(Json(serde_json::json!({ "server_version": env!("CARGO_PKG_VERSION"), "auth_enabled": true, "total_users": user_count, "oidc_configured": oidc_configured, }))) } // ============================================================================ // Dashboard / Stats // ============================================================================ /// GET /api/admin/dashboard — full dashboard statistics async fn get_dashboard_stats( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let auth_app = &auth.auth_application_service; // Get storage stats from repository (single efficient query) let db_pool = state .db_pool .as_ref() .ok_or_else(|| AppError::internal_error("Database not available"))?; // Use direct SQL for aggregated stats — more efficient than loading all users let stats_row = sqlx::query( r#" SELECT COUNT(*)::INT8 as total_users, COUNT(*) FILTER (WHERE active = true)::INT8 as active_users, COUNT(*) FILTER (WHERE role::text = 'admin')::INT8 as admin_users, COALESCE(SUM(storage_quota_bytes)::INT8, 0) as total_quota_bytes, COALESCE(SUM(storage_used_bytes)::INT8, 0) as total_used_bytes, COUNT(*) FILTER (WHERE storage_quota_bytes > 0 AND storage_used_bytes > storage_quota_bytes * 0.8)::INT8 as users_over_80, COUNT(*) FILTER (WHERE storage_quota_bytes > 0 AND storage_used_bytes > storage_quota_bytes)::INT8 as users_over_quota FROM auth.users "# ) .fetch_one(db_pool.as_ref()) .await .map_err(|e| AppError::internal_error(format!("Database query failed: {}", e)))?; use sqlx::Row; let total_quota: i64 = stats_row.get("total_quota_bytes"); let total_used: i64 = stats_row.get("total_used_bytes"); let usage_percent = if total_quota > 0 { (total_used as f64 / total_quota as f64) * 100.0 } else { 0.0 }; let stats = DashboardStatsDto { server_version: env!("CARGO_PKG_VERSION").to_string(), auth_enabled: true, oidc_configured: auth_app.oidc_enabled(), quotas_enabled: true, // Feature flag could be checked here total_users: stats_row.get("total_users"), active_users: stats_row.get("active_users"), admin_users: stats_row.get("admin_users"), total_quota_bytes: total_quota, total_used_bytes: total_used, storage_usage_percent: (usage_percent * 100.0).round() / 100.0, users_over_80_percent: stats_row.get("users_over_80"), users_over_quota: stats_row.get("users_over_quota"), registration_enabled: { if let Some(svc) = state.admin_settings_service.as_ref() { svc.get_registration_enabled().await } else { true // default: enabled } }, }; Ok(Json(stats)) } // ============================================================================ // User Management // ============================================================================ /// GET /api/admin/users?limit=50&offset=0 — list all users async fn list_users( State(state): State>, headers: HeaderMap, Query(query): Query, ) -> Result { admin_guard(&state, &headers).await?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let limit = query.limit.unwrap_or(100).min(500); let offset = query.offset.unwrap_or(0); let users = auth .auth_application_service .list_users(limit, offset) .await .map_err(|e| AppError::internal_error(format!("Failed to list users: {}", e)))?; let total = auth .auth_application_service .count_users_efficient() .await .unwrap_or(0); Ok(Json(serde_json::json!({ "users": users, "total": total, "limit": limit, "offset": offset, }))) } /// GET /api/admin/users/:id — get single user async fn get_user( State(state): State>, headers: HeaderMap, Path(id): Path, ) -> Result { admin_guard(&state, &headers).await?; let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let user = auth .auth_application_service .get_user_admin(id) .await .map_err(|e| AppError::not_found(format!("User not found: {}", e)))?; Ok(Json(user)) } /// DELETE /api/admin/users/:id — delete a user async fn delete_user( State(state): State>, headers: HeaderMap, Path(id): Path, ) -> Result { let (admin_id, _) = admin_guard(&state, &headers).await?; let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?; // Prevent self-deletion if admin_id == id { return Err(AppError::new( StatusCode::BAD_REQUEST, "Cannot delete your own account", "SelfDeletion", )); } let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service .delete_user_admin(id) .await .map_err(|e| AppError::internal_error(format!("Failed to delete user: {}", e)))?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "User deleted successfully" })), )) } /// PUT /api/admin/users/:id/role — change user role async fn update_user_role( State(state): State>, headers: HeaderMap, Path(id): Path, Json(dto): Json, ) -> Result { let (admin_id, _) = admin_guard(&state, &headers).await?; let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?; // Prevent changing own role if admin_id == id { return Err(AppError::new( StatusCode::BAD_REQUEST, "Cannot change your own role", "SelfRoleChange", )); } let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service .change_user_role(id, &dto.role) .await .map_err(|e| AppError::internal_error(format!("Failed to change role: {}", e)))?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": format!("User role updated to '{}'", dto.role) })), )) } /// PUT /api/admin/users/:id/active — activate/deactivate user async fn update_user_active( State(state): State>, headers: HeaderMap, Path(id): Path, Json(dto): Json, ) -> Result { let (admin_id, _) = admin_guard(&state, &headers).await?; let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?; // Prevent deactivating yourself if admin_id == id && !dto.active { return Err(AppError::new( StatusCode::BAD_REQUEST, "Cannot deactivate your own account", "SelfDeactivation", )); } let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service .set_user_active(id, dto.active) .await .map_err(|e| AppError::internal_error(format!("Failed to update user status: {}", e)))?; let status = if dto.active { "activated" } else { "deactivated" }; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": format!("User {}", status) })), )) } /// PUT /api/admin/users/:id/quota — update user storage quota async fn update_user_quota( State(state): State>, headers: HeaderMap, Path(id): Path, Json(dto): Json, ) -> Result { admin_guard(&state, &headers).await?; let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service .update_user_quota(id, dto.quota_bytes) .await .map_err(|e| AppError::internal_error(format!("Failed to update quota: {}", e)))?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "User quota updated", "quota_bytes": dto.quota_bytes, })), )) } // ============================================================================ // Admin User Creation & Password Reset // ============================================================================ /// POST /api/admin/users — create a new user (admin only) async fn create_user( State(state): State>, headers: HeaderMap, Json(dto): Json, ) -> Result { admin_guard(&state, &headers).await?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; let user = auth .auth_application_service .admin_create_user(dto) .await .map_err(|e| { AppError::new( StatusCode::BAD_REQUEST, format!("Failed to create user: {}", e), "CreateUserFailed", ) })?; Ok((StatusCode::CREATED, Json(user))) } /// PUT /api/admin/users/:id/password — reset a user's password (admin only) async fn reset_user_password( State(state): State>, headers: HeaderMap, Path(id): Path, Json(dto): Json, ) -> Result { admin_guard(&state, &headers).await?; let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?; let auth = state .auth_service .as_ref() .ok_or_else(|| AppError::internal_error("Auth service not configured"))?; auth.auth_application_service .admin_reset_password(id, &dto.new_password) .await .map_err(|e| { AppError::new( StatusCode::BAD_REQUEST, format!("Failed to reset password: {}", e), "ResetPasswordFailed", ) })?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": "Password reset successfully" })), )) } // ============================================================================ // Registration Control // ============================================================================ /// GET /api/admin/settings/registration — check if public registration is enabled async fn get_registration_setting( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let svc = state .admin_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; let val = svc.get_registration_enabled().await; Ok(Json(serde_json::json!({ "registration_enabled": val, }))) } /// PUT /api/admin/settings/registration — enable/disable public registration async fn set_registration_setting( State(state): State>, headers: HeaderMap, Json(body): Json, ) -> Result { let (admin_id, _) = admin_guard(&state, &headers).await?; let enabled = body .get("registration_enabled") .and_then(|v| v.as_bool()) .ok_or_else(|| { AppError::new( StatusCode::BAD_REQUEST, "Missing boolean field 'registration_enabled'", "InvalidInput", ) })?; let svc = state .admin_settings_service .as_ref() .ok_or_else(|| AppError::internal_error("Admin settings service not available"))?; svc.set_registration_enabled(enabled, admin_id) .await .map_err(|e| AppError::internal_error(format!("Failed to save setting: {}", e)))?; Ok(( StatusCode::OK, Json(serde_json::json!({ "message": format!("Public registration {}", if enabled { "enabled" } else { "disabled" }), "registration_enabled": enabled, })), )) } async fn reextract_audio_metadata( State(state): State>, headers: HeaderMap, ) -> Result { admin_guard(&state, &headers).await?; let audio_service = state .applications .audio_metadata_service .as_ref() .ok_or_else(|| AppError::internal_error("Audio metadata service not available"))?; let result = audio_service .reextract_all_audio_metadata() .await .map_err(|e| { AppError::internal_error(format!("Failed to re-extract audio metadata: {}", e)) })?; Ok(Json(serde_json::json!({ "message": "Audio metadata extraction complete", "total": result.total, "processed": result.processed, "failed": result.failed, }))) }