Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Резервные копии (только администратор): список, создание, скачивание, загрузка, восстановление, удаление."""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import backup as B, config
|
||||
from ..db import get_db
|
||||
from ..models import User
|
||||
from ..security import require_admin
|
||||
|
||||
router = APIRouter(prefix="/backups", tags=["backups"])
|
||||
MAX_UPLOAD_MB = 2048
|
||||
|
||||
|
||||
class CreateIn(BaseModel):
|
||||
include_media: bool = False
|
||||
note: str = Field(default="", max_length=200)
|
||||
|
||||
|
||||
class RestoreIn(BaseModel):
|
||||
confirm: bool = False
|
||||
|
||||
|
||||
def _err(e: B.BackupError):
|
||||
return HTTPException(409 if "демонстрацион" in str(e) else 422, str(e))
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_(db: Session = Depends(get_db), _: User = Depends(require_admin)):
|
||||
return {"items": B.list_backups(), "folder": str(B.folder().resolve()),
|
||||
"auto_hours": int(B.setting(db, "backup_auto_hours") or 0), "keep": int(B.setting(db, "backup_keep") or 14),
|
||||
"media": B.setting(db, "backup_media") == "1", "next_auto": B.next_auto(db), "now": config.iso(config.now())}
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create(data: CreateIn, db: Session = Depends(get_db), _: User = Depends(require_admin)):
|
||||
try:
|
||||
p = B.create(db, include_media=data.include_media, note=data.note.strip() or "ручная копия")
|
||||
except B.BackupError as e:
|
||||
raise _err(e)
|
||||
return next(x for x in B.list_backups() if x["name"] == p.name)
|
||||
|
||||
|
||||
@router.get("/{name}")
|
||||
def download(name: str, _: User = Depends(require_admin)):
|
||||
try:
|
||||
f = B.path_of(name)
|
||||
except B.BackupError as e:
|
||||
raise HTTPException(404, str(e))
|
||||
return FileResponse(f, media_type="application/zip", filename=f.name)
|
||||
|
||||
|
||||
@router.delete("/{name}", status_code=204)
|
||||
def delete(name: str, _: User = Depends(require_admin)):
|
||||
try:
|
||||
B.path_of(name).unlink()
|
||||
except B.BackupError as e:
|
||||
raise HTTPException(404, str(e))
|
||||
|
||||
|
||||
@router.post("/upload", status_code=201)
|
||||
async def upload(request: Request, _: User = Depends(require_admin)):
|
||||
"""Загрузить файл копии (тело запроса — zip), например с другого сервера, чтобы потом восстановить."""
|
||||
data = b""
|
||||
async for chunk in request.stream():
|
||||
data += chunk
|
||||
if len(data) > MAX_UPLOAD_MB * 1024 * 1024:
|
||||
raise HTTPException(413, "Файл слишком большой")
|
||||
try:
|
||||
p = B.save_upload(data)
|
||||
except B.BackupError as e:
|
||||
raise _err(e)
|
||||
return next(x for x in B.list_backups() if x["name"] == p.name)
|
||||
|
||||
|
||||
@router.post("/{name}/restore")
|
||||
def restore(name: str, data: RestoreIn, db: Session = Depends(get_db), _: User = Depends(require_admin)):
|
||||
"""Заменить ВСЕ данные продуктовой базы содержимым копии. Текущее состояние сначала сохраняется отдельной копией."""
|
||||
if not data.confirm:
|
||||
raise HTTPException(422, "Подтвердите восстановление (confirm: true)")
|
||||
try:
|
||||
return B.restore(db, name)
|
||||
except B.BackupError as e:
|
||||
raise _err(e)
|
||||
Reference in New Issue
Block a user