Files

117 lines
3.9 KiB
Python

import os
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc
from app.core.database import get_db
from app.models.models import User, BackupFile, Client
from app.schemas.schemas import BackupFileResponse
from app.api.deps import get_current_user, require_admin
from app.storage.local import storage_provider
from app.services.event_service import log_event
router = APIRouter(prefix="/backups", tags=["Backups & Restore"])
@router.get("", response_model=List[BackupFileResponse])
async def list_backups(
client_id: Optional[int] = None,
job_id: Optional[int] = None,
limit: int = 100,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
query = select(BackupFile).where(BackupFile.is_active == True)
if client_id:
query = query.where(BackupFile.client_id == client_id)
if job_id:
query = query.where(BackupFile.job_id == job_id)
query = query.order_by(desc(BackupFile.created_at)).limit(limit)
result = await db.execute(query)
return result.scalars().all()
@router.get("/{backup_id}", response_model=BackupFileResponse)
async def get_backup(
backup_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
res = await db.execute(select(BackupFile).where(BackupFile.id == backup_id))
bf = res.scalar_one_or_none()
if not bf:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Backup file not found")
return bf
@router.get("/{backup_id}/download")
async def download_backup(
backup_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Allows downloading a backup file directly from the Web interface."""
res = await db.execute(select(BackupFile).where(BackupFile.id == backup_id))
bf = res.scalar_one_or_none()
if not bf or not bf.is_active:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Backup file not found")
try:
abs_path = await storage_provider.get_file_path(bf.relative_path)
except Exception as ex:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(ex))
await log_event(
db=db,
event_type="RESTORE_STARTED",
message=f"Backup download initiated for '{bf.filename}' ({bf.file_size / (1024*1024):.2f} MB).",
severity="INFO",
client_id=bf.client_id,
job_id=bf.job_id,
user_email=current_user.email
)
return FileResponse(
path=abs_path,
filename=bf.filename,
media_type="application/octet-stream"
)
@router.delete("/{backup_id}")
async def delete_backup(
backup_id: int,
db: AsyncSession = Depends(get_db),
admin_user: User = Depends(require_admin)
):
res = await db.execute(select(BackupFile).where(BackupFile.id == backup_id))
bf = res.scalar_one_or_none()
if not bf:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Backup file not found")
try:
await storage_provider.delete_backup_file(bf.relative_path)
except Exception:
pass
bf.is_active = False
# Update client storage counter
client_res = await db.execute(select(Client).where(Client.id == bf.client_id))
client = client_res.scalar_one_or_none()
if client:
client.storage_used_bytes = max(0, client.storage_used_bytes - bf.file_size)
await db.commit()
await log_event(
db=db,
event_type="FILE_DELETED",
message=f"Manual deletion of backup '{bf.filename}'.",
severity="WARNING",
client_id=bf.client_id,
job_id=bf.job_id,
user_email=admin_user.email
)
return {"message": "Backup file deleted"}