ligbox-ops-platform/api/app/carbonio_release_routes.py
Ligbox Spec Hub 3a2c64834b Initial import: ligbox-ops-platform + specs + LAPTOP + obsidian merge (CT130)
Source: VM122 /opt + obsidian-infra + LAPTOP
Hub: CT130 spec-hub 10.10.10.130
2026-06-19 17:26:41 +00:00

120 lines
3.9 KiB
Python

"""Rotas libertação ACCOUNT_EXISTS — Spec 022."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from app import auth, carbonio_release_store, vm112_domains
from app.permissions import can_read_tickets
router = APIRouter(prefix="/api/v1/carbonio-blocks", tags=["carbonio-release"])
class ResolveBlockBody(BaseModel):
confirm_email: str = Field(..., min_length=5)
password: str = Field(..., min_length=1)
def _require_ticket_reader(user: auth.DeskUser = Depends(auth.get_current_user)) -> auth.DeskUser:
if not can_read_tickets(user.role):
raise HTTPException(403, "permissão insuficiente")
return user
def _verify_user_password(conn, username: str, password: str) -> bool:
row = conn.execute(
"SELECT password_hash FROM desk_users WHERE username = ? AND active = 1",
(username,),
).fetchone()
if not row or not row["password_hash"]:
return False
return auth.verify_password(password, row["password_hash"])
@router.get("")
def list_carbonio_blocks(
status: str = Query("pending"),
session_id: str = "",
ticket_id: int | None = None,
limit: int = Query(100, ge=1, le=500),
user: auth.DeskUser = Depends(_require_ticket_reader),
):
conn = auth.db()
try:
return carbonio_release_store.list_blocks(
conn,
status=status or "all",
session_id=session_id.strip() or None,
ticket_id=ticket_id,
limit=limit,
)
finally:
conn.close()
@router.get("/{block_id}")
def get_carbonio_block(
block_id: int,
user: auth.DeskUser = Depends(_require_ticket_reader),
):
conn = auth.db()
try:
block = carbonio_release_store.get_block(conn, block_id)
finally:
conn.close()
if not block:
raise HTTPException(404, "bloqueio não encontrado")
return block
@router.post("/{block_id}/resolve")
def resolve_carbonio_block(
block_id: int,
body: ResolveBlockBody,
user: auth.DeskUser = Depends(_require_ticket_reader),
):
conn = auth.db()
try:
block = carbonio_release_store.get_block(conn, block_id)
if not block:
raise HTTPException(404, "bloqueio não encontrado")
if block["status"] == "resolved":
raise HTTPException(409, f"Já resolvido por {block.get('resolved_by') or 'outro técnico'}")
if body.confirm_email.lower().strip() != block["email"].lower():
raise HTTPException(400, "E-mail de confirmação não coincide")
if not _verify_user_password(conn, user.username, body.password):
raise HTTPException(403, "Senha incorrecta")
try:
vm_result = vm112_domains.delete_carbonio_account(block["email"])
except Exception as e:
raise HTTPException(502, f"VM112/Carbonio: {e}") from e
try:
resolved = carbonio_release_store.resolve_block(
conn,
block_id,
resolved_by=user.username,
note=vm_result.get("message", "zmprov da"),
)
except ValueError as e:
if str(e) == "already_resolved":
raise HTTPException(409, "Outro técnico já resolveu este bloqueio") from e
raise HTTPException(404, "Bloqueio indisponível") from e
if block.get("ticket_id"):
carbonio_release_store.append_ticket_resolution_note(
conn,
int(block["ticket_id"]),
email=block["email"],
by=user.username,
)
return {
"ok": True,
"block": resolved,
"vm112": vm_result,
"message": f"Conta {block['email']} removida do Carbonio. Peça ao cliente para repetir «Criar conta» no wizard.",
}
finally:
conn.close()