"""Backup management API endpoints.""" import logging from typing import Any, Dict, List, Optional from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from src.server.services.backup_service import BackupService, get_backup_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/backup", tags=["backup"]) class BackupCreateRequest(BaseModel): """Request to create a backup.""" backup_type: str # 'config', 'database', 'full' description: Optional[str] = None class BackupResponse(BaseModel): """Response for backup creation.""" success: bool message: str backup_name: Optional[str] = None size_bytes: Optional[int] = None class BackupListResponse(BaseModel): """Response for listing backups.""" backups: List[Dict[str, Any]] total_count: int class RestoreRequest(BaseModel): """Request to restore from backup.""" backup_name: str class RestoreResponse(BaseModel): """Response for restore operation.""" success: bool message: str def get_backup_service_dep() -> BackupService: """Dependency to get backup service.""" return get_backup_service() @router.post("/create", response_model=BackupResponse) async def create_backup( request: BackupCreateRequest, backup_service: BackupService = Depends(get_backup_service_dep), ) -> BackupResponse: """Create a new backup. Args: request: Backup creation request. backup_service: Backup service dependency. Returns: BackupResponse: Result of backup creation. """ try: backup_info = None if request.backup_type == "config": backup_info = backup_service.backup_configuration( request.description or "" ) elif request.backup_type == "database": backup_info = backup_service.backup_database( request.description or "" ) elif request.backup_type == "full": backup_info = backup_service.backup_full( request.description or "" ) else: raise ValueError(f"Invalid backup type: {request.backup_type}") if backup_info is None: return BackupResponse( success=False, message=f"Failed to create {request.backup_type} backup", ) return BackupResponse( success=True, message=( f"{request.backup_type.capitalize()} backup created " "successfully" ), backup_name=backup_info.name, size_bytes=backup_info.size_bytes, ) except Exception as e: logger.error(f"Failed to create backup: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/list", response_model=BackupListResponse) async def list_backups( backup_type: Optional[str] = None, backup_service: BackupService = Depends(get_backup_service_dep), ) -> BackupListResponse: """List available backups. Args: backup_type: Optional filter by backup type. backup_service: Backup service dependency. Returns: BackupListResponse: List of available backups. """ try: backups = backup_service.list_backups(backup_type) return BackupListResponse(backups=backups, total_count=len(backups)) except Exception as e: logger.error(f"Failed to list backups: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/restore", response_model=RestoreResponse) async def restore_backup( request: RestoreRequest, backup_type: Optional[str] = None, backup_service: BackupService = Depends(get_backup_service_dep), ) -> RestoreResponse: """Restore from a backup. Args: request: Restore request. backup_type: Type of backup to restore. backup_service: Backup service dependency. Returns: RestoreResponse: Result of restore operation. """ try: # Determine backup type from filename if not provided if backup_type is None: if "config" in request.backup_name: backup_type = "config" elif "database" in request.backup_name: backup_type = "database" else: backup_type = "full" success = False if backup_type == "config": success = backup_service.restore_configuration( request.backup_name ) elif backup_type == "database": success = backup_service.restore_database(request.backup_name) else: raise ValueError(f"Cannot restore backup type: {backup_type}") if not success: return RestoreResponse( success=False, message=f"Failed to restore {backup_type} backup", ) return RestoreResponse( success=True, message=f"{backup_type.capitalize()} backup restored successfully", ) except Exception as e: logger.error(f"Failed to restore backup: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.delete("/{backup_name}", response_model=Dict[str, Any]) async def delete_backup( backup_name: str, backup_service: BackupService = Depends(get_backup_service_dep), ) -> Dict[str, Any]: """Delete a backup. Args: backup_name: Name of the backup to delete. backup_service: Backup service dependency. Returns: dict: Result of delete operation. """ try: success = backup_service.delete_backup(backup_name) if not success: raise HTTPException(status_code=404, detail="Backup not found") return {"success": True, "message": "Backup deleted successfully"} except HTTPException: raise except Exception as e: logger.error(f"Failed to delete backup: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/cleanup", response_model=Dict[str, Any]) async def cleanup_backups( max_backups: int = 10, backup_type: Optional[str] = None, backup_service: BackupService = Depends(get_backup_service_dep), ) -> Dict[str, Any]: """Clean up old backups. Args: max_backups: Maximum number of backups to keep. backup_type: Optional filter by backup type. backup_service: Backup service dependency. Returns: dict: Number of backups deleted. """ try: deleted_count = backup_service.cleanup_old_backups( max_backups, backup_type ) return { "success": True, "message": "Cleanup completed", "deleted_count": deleted_count, } except Exception as e: logger.error(f"Failed to cleanup backups: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/export/anime", response_model=Dict[str, Any]) async def export_anime_data( backup_service: BackupService = Depends(get_backup_service_dep), ) -> Dict[str, Any]: """Export anime library data. Args: backup_service: Backup service dependency. Returns: dict: Result of export operation. """ try: output_file = "data/backups/anime_export.json" success = backup_service.export_anime_data(output_file) if not success: raise HTTPException( status_code=500, detail="Failed to export anime data" ) return { "success": True, "message": "Anime data exported successfully", "export_file": output_file, } except HTTPException: raise except Exception as e: logger.error(f"Failed to export anime data: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/import/anime", response_model=Dict[str, Any]) async def import_anime_data( import_file: str, backup_service: BackupService = Depends(get_backup_service_dep), ) -> Dict[str, Any]: """Import anime library data. Args: import_file: Path to import file. backup_service: Backup service dependency. Returns: dict: Result of import operation. """ try: success = backup_service.import_anime_data(import_file) if not success: raise HTTPException( status_code=400, detail="Failed to import anime data" ) return { "success": True, "message": "Anime data imported successfully", } except HTTPException: raise except Exception as e: logger.error(f"Failed to import anime data: {e}") raise HTTPException(status_code=500, detail=str(e))