from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from fastapi.responses import FileResponse from app.core.deps import get_superuser from app.models.user import User from app.schemas import BackupInfoOut from app.services import backup as backup_service router = APIRouter(prefix="/admin/backups", tags=["admin-backups"]) @router.get("", response_model=list[BackupInfoOut]) def list_backups(_: User = Depends(get_superuser)): return backup_service.list_backups() @router.post("/run", response_model=BackupInfoOut) def run_backup(_: User = Depends(get_superuser)): try: path = backup_service.create_backup() except FileNotFoundError as exc: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc except Exception as exc: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"备份失败: {exc}", ) from exc stat = path.stat() from datetime import datetime, timezone return BackupInfoOut( filename=path.name, size_bytes=stat.st_size, created_at=datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc), ) @router.get("/{filename}/download") def download_backup(filename: str, _: User = Depends(get_superuser)): try: path = backup_service.resolve_backup_file(filename) except ValueError as exc: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc except FileNotFoundError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="备份不存在") from exc return FileResponse( path, media_type="application/gzip", filename=filename, headers={"Content-Disposition": f'attachment; filename="{filename}"'}, ) @router.post("/restore") async def restore_backup(file: UploadFile = File(...), _: User = Depends(get_superuser)): if not file.filename or not file.filename.endswith(".tar.gz"): raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请上传 .tar.gz 备份包") content = await file.read() if len(content) > 512 * 1024 * 1024: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="备份文件过大(最大 512MB)") import tempfile from pathlib import Path tmp = Path(tempfile.mkdtemp(prefix="grade-archive-upload-")) archive = tmp / "restore.tar.gz" try: archive.write_bytes(content) backup_service.restore_backup(archive) except Exception as exc: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"恢复失败: {exc}", ) from exc finally: import shutil shutil.rmtree(tmp, ignore_errors=True) return {"ok": True, "message": "数据已恢复,建议重启服务以确保缓存刷新"}