Specs stay at repo root (cross-VM). Move deploy and code into logical projects with README per domain, updated manifest.yaml, and symlinks at legacy paths for VM122 backward compatibility.
153 lines
4.6 KiB
Python
153 lines
4.6 KiB
Python
"""Rotas Desk — domínios VM112 (Spec 017)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import StreamingResponse
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app import auth, vm112_domains
|
|
from app.permissions import can_manage_vm112_domains
|
|
from app.vm112_purge_stream import purge_sse_generator
|
|
from app.vm112_purge_jobs import get_job_public, list_jobs, recover_job, start_job
|
|
|
|
router = APIRouter(prefix="/api/v1/vm112", tags=["vm112-domains"])
|
|
|
|
|
|
class DomainPurgeRequest(BaseModel):
|
|
confirm_domain: str = Field(..., min_length=3)
|
|
root_password: str = Field(..., min_length=1)
|
|
|
|
|
|
def _require_admin(user: auth.DeskUser = Depends(auth.get_current_user)) -> auth.DeskUser:
|
|
if not can_manage_vm112_domains(user.role):
|
|
raise HTTPException(403, "Apenas perfis Admin (super_admin, ops_lead)")
|
|
return user
|
|
|
|
|
|
def _validate_purge_request(domain: str, body: DomainPurgeRequest) -> str:
|
|
domain = domain.lower().strip()
|
|
if domain in vm112_domains.PURGE_BLOCKLIST:
|
|
raise HTTPException(400, f"Domínio {domain} está protegido contra purge")
|
|
if body.confirm_domain.lower().strip() != domain:
|
|
raise HTTPException(400, "Confirmação do domínio não coincide")
|
|
return domain
|
|
|
|
|
|
@router.get("/domains")
|
|
def list_vm112_domains(
|
|
q: str = "",
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
try:
|
|
return vm112_domains.list_domains(q)
|
|
except Exception as e:
|
|
raise HTTPException(502, f"VM112 indisponível: {e}") from e
|
|
|
|
|
|
@router.get("/domains/{domain}")
|
|
def get_vm112_domain(
|
|
domain: str,
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
try:
|
|
return vm112_domains.get_domain(domain)
|
|
except Exception as e:
|
|
raise HTTPException(502, f"VM112: {e}") from e
|
|
|
|
|
|
@router.post("/domains/{domain}/purge")
|
|
def purge_vm112_domain(
|
|
domain: str,
|
|
body: DomainPurgeRequest,
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
domain = _validate_purge_request(domain, body)
|
|
conn = auth.db()
|
|
try:
|
|
if not vm112_domains.verify_root_password(conn, body.root_password):
|
|
raise HTTPException(403, "Senha Root incorrecta")
|
|
finally:
|
|
conn.close()
|
|
try:
|
|
vm112_result = vm112_domains.purge_vm112(domain)
|
|
except Exception as e:
|
|
raise HTTPException(502, f"Purge VM112 falhou: {e}") from e
|
|
conn = auth.db()
|
|
try:
|
|
desk_counts, desk_timeline = vm112_domains.purge_desk_timeline(conn, domain)
|
|
finally:
|
|
conn.close()
|
|
timeline = vm112_domains.build_purge_timeline(vm112_result, desk_counts, desk_timeline)
|
|
return {
|
|
"ok": True,
|
|
"domain": domain,
|
|
"vm112": vm112_result,
|
|
"desk": desk_counts,
|
|
"timeline": timeline,
|
|
"by": user.username,
|
|
}
|
|
|
|
|
|
@router.post("/domains/{domain}/purge/stream")
|
|
def purge_vm112_domain_stream(
|
|
domain: str,
|
|
body: DomainPurgeRequest,
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
"""SSE — progresso purge em tempo real (Fase 2 Spec 017)."""
|
|
domain = _validate_purge_request(domain, body)
|
|
return StreamingResponse(
|
|
purge_sse_generator(domain, body.root_password, user.username),
|
|
media_type="text/event-stream",
|
|
headers={
|
|
"Cache-Control": "no-cache",
|
|
"Connection": "keep-alive",
|
|
"X-Accel-Buffering": "no",
|
|
},
|
|
)
|
|
|
|
|
|
@router.post("/domains/{domain}/purge/jobs")
|
|
def start_purge_job(
|
|
domain: str,
|
|
body: DomainPurgeRequest,
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
"""Inicia purge em background; consultar GET /purge/jobs/{id} (recomendado via Traefik)."""
|
|
domain = _validate_purge_request(domain, body)
|
|
job_id = start_job(domain, body.root_password, user.username)
|
|
return {"ok": True, "job_id": job_id, "domain": domain, "status": "running"}
|
|
|
|
|
|
@router.get("/purge/jobs")
|
|
def list_purge_jobs(
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
return list_jobs(limit=limit, offset=offset)
|
|
|
|
|
|
@router.get("/purge/jobs/{job_id}")
|
|
def get_purge_job_status(
|
|
job_id: str,
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
job = get_job_public(job_id)
|
|
if not job:
|
|
raise HTTPException(404, "Job purge não encontrado")
|
|
return job
|
|
|
|
@router.post("/purge/jobs/{job_id}/recover")
|
|
def recover_purge_job(
|
|
job_id: str,
|
|
domain: str = "",
|
|
user: auth.DeskUser = Depends(_require_admin),
|
|
):
|
|
"""Recupera purge quando job sumiu da memória mas VM112 já removeu o domínio."""
|
|
job = recover_job(job_id, domain or None)
|
|
if not job:
|
|
raise HTTPException(404, "Não foi possível recuperar o job purge")
|
|
return job
|
|
|