backup
This commit is contained in:
304
src/server/api/backup.py
Normal file
304
src/server/api/backup.py
Normal file
@@ -0,0 +1,304 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user