Files
remnawave-bedolaga-telegram…/app/webapi/routes/backups.py
2025-09-28 14:04:17 +05:00

160 lines
4.5 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Any, Optional
from fastapi import APIRouter, HTTPException, Query, Security, status
from app.services.backup_service import backup_service
from ..background.backup_tasks import backup_task_manager
from ..dependencies import require_api_token
from ..schemas.backups import (
BackupCreateResponse,
BackupInfo,
BackupListResponse,
BackupStatusResponse,
BackupTaskInfo,
BackupTaskListResponse,
)
router = APIRouter()
def _parse_datetime(value: Optional[str]) -> Optional[datetime]:
if not value:
return None
try:
if value.endswith("Z"):
value = value.replace("Z", "+00:00")
return datetime.fromisoformat(value)
except ValueError:
return None
def _to_int(value: Any) -> Optional[int]:
try:
return int(value)
except (TypeError, ValueError):
return None
def _serialize_backup(raw: dict) -> BackupInfo:
timestamp = _parse_datetime(raw.get("timestamp"))
tables_count = _to_int(raw.get("tables_count"))
total_records = _to_int(raw.get("total_records"))
file_size_bytes = _to_int(raw.get("file_size_bytes")) or 0
file_size_mb = raw.get("file_size_mb")
try:
file_size_mb = float(file_size_mb)
except (TypeError, ValueError):
file_size_mb = round(file_size_bytes / 1024 / 1024, 2)
created_by = _to_int(raw.get("created_by"))
return BackupInfo(
filename=str(raw.get("filename")),
filepath=str(raw.get("filepath")),
timestamp=timestamp,
tables_count=tables_count,
total_records=total_records,
compressed=bool(raw.get("compressed", False)),
file_size_bytes=file_size_bytes,
file_size_mb=float(file_size_mb),
created_by=created_by,
database_type=raw.get("database_type"),
version=raw.get("version"),
error=raw.get("error"),
)
@router.post(
"",
response_model=BackupCreateResponse,
status_code=status.HTTP_202_ACCEPTED,
summary="Запустить создание резервной копии",
)
async def create_backup_endpoint(
token: Any = Security(require_api_token),
) -> BackupCreateResponse:
created_by = getattr(token, "id", None)
state = await backup_task_manager.enqueue(created_by=created_by)
return BackupCreateResponse(task_id=state.task_id, status=state.status)
@router.get(
"",
response_model=BackupListResponse,
summary="Список резервных копий",
)
async def list_backups(
_: Any = Security(require_api_token),
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
) -> BackupListResponse:
backups = await backup_service.get_backup_list()
total = len(backups)
slice_backups = backups[offset : offset + limit]
items = [_serialize_backup(raw) for raw in slice_backups]
return BackupListResponse(
items=items,
total=total,
limit=limit,
offset=offset,
)
@router.get(
"/status/{task_id}",
response_model=BackupStatusResponse,
summary="Статус создания резервной копии",
)
async def get_backup_status(
task_id: str,
_: Any = Security(require_api_token),
) -> BackupStatusResponse:
state = await backup_task_manager.get(task_id)
if not state:
raise HTTPException(status.HTTP_404_NOT_FOUND, "Task not found")
return BackupStatusResponse(
task_id=state.task_id,
status=state.status,
message=state.message,
file_path=state.file_path,
created_by=state.created_by,
created_at=state.created_at,
updated_at=state.updated_at,
)
@router.get(
"/tasks",
response_model=BackupTaskListResponse,
summary="Список фоновых задач бекапов",
)
async def list_backup_tasks(
_: Any = Security(require_api_token),
active_only: bool = Query(False, description="Вернуть только активные задачи"),
) -> BackupTaskListResponse:
states = await backup_task_manager.list(active_only=active_only)
items = [
BackupTaskInfo(
task_id=state.task_id,
status=state.status,
message=state.message,
file_path=state.file_path,
created_by=state.created_by,
created_at=state.created_at,
updated_at=state.updated_at,
)
for state in states
]
return BackupTaskListResponse(items=items, total=len(items))