This commit is contained in:
2026-03-18 18:16:44 +01:00
parent c89c08c397
commit b3c8b77f12
20 changed files with 1934 additions and 36 deletions
+157
View File
@@ -0,0 +1,157 @@
"""
Router API Invio PEC (Fase 4).
Endpoint:
POST /send invia una nuova PEC (crea Message + SendJob, accoda job)
GET /send/jobs lista job di invio del tenant (paginata)
GET /send/jobs/{id} dettaglio di un singolo job
DELETE /send/jobs/{id} annulla job se ancora pending/retrying
"""
import uuid
from typing import Annotated
from fastapi import APIRouter, Query, status
from app.core.exceptions import ForbiddenError
from app.dependencies import AdminUser, CurrentUser, DB
from app.schemas.send import SendJobListResponse, SendJobResponse, SendPecRequest
from app.services.permission_service import PermissionService
from app.services.send_service import SendService
router = APIRouter(prefix="/send", tags=["Invio PEC"])
# ─── Helpers ──────────────────────────────────────────────────────────────────
def _svc(db) -> SendService:
return SendService(db)
def _job_response(job) -> SendJobResponse:
return SendJobResponse.model_validate(job)
# ─── Endpoints ────────────────────────────────────────────────────────────────
@router.post(
"",
response_model=SendJobResponse,
status_code=status.HTTP_201_CREATED,
summary="Invia una PEC",
description=(
"Crea un messaggio PEC in uscita e accoda il job di invio SMTP. "
"Il job viene eseguito in background con retry automatico. "
"Richiede permesso **can_send** sulla casella (gli admin possono inviare da qualsiasi casella del tenant)."
),
)
async def create_send_job(
data: SendPecRequest,
current_user: CurrentUser,
db: DB,
) -> SendJobResponse:
svc = _svc(db)
job = await svc.create_send_job(current_user=current_user, data=data)
await db.commit()
# Refresh per ottenere tutti i valori default dal DB
await db.refresh(job)
return _job_response(job)
@router.get(
"/jobs",
response_model=SendJobListResponse,
summary="Lista job di invio",
)
async def list_send_jobs(
current_user: CurrentUser,
db: DB,
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=200),
mailbox_id: uuid.UUID | None = Query(None),
status_filter: str | None = Query(
None,
alias="status",
description="Filtra per stato: pending | sending | sent | failed | retrying",
),
) -> SendJobListResponse:
"""
Elenca i job di invio del tenant.
Gli admin vedono tutti i job; gli operatori vedono solo i job
delle caselle su cui hanno permesso can_read.
"""
svc = _svc(db)
# Filtro opzionale per casella: verifica accesso se non admin
if mailbox_id and not current_user.is_admin:
perm_svc = PermissionService(db)
if not await perm_svc.check_can_read(current_user, mailbox_id):
raise ForbiddenError("Accesso alla casella non autorizzato")
items, total = await svc.list_send_jobs(
tenant_id=current_user.tenant_id,
page=page,
page_size=page_size,
mailbox_id=mailbox_id,
status_filter=status_filter,
)
return SendJobListResponse(
items=[_job_response(j) for j in items],
total=total,
page=page,
page_size=page_size,
)
@router.get(
"/jobs/{job_id}",
response_model=SendJobResponse,
summary="Dettaglio job di invio",
)
async def get_send_job(
job_id: uuid.UUID,
current_user: CurrentUser,
db: DB,
) -> SendJobResponse:
"""Recupera lo stato di un singolo job di invio."""
svc = _svc(db)
job = await svc.get_send_job(job_id, current_user.tenant_id)
# Verifica accesso alla casella se non admin
if not current_user.is_admin:
perm_svc = PermissionService(db)
if not await perm_svc.check_can_read(current_user, job.mailbox_id):
raise ForbiddenError("Accesso non autorizzato")
return _job_response(job)
@router.delete(
"/jobs/{job_id}",
status_code=status.HTTP_204_NO_CONTENT,
summary="Annulla job di invio",
)
async def cancel_send_job(
job_id: uuid.UUID,
current_user: CurrentUser,
db: DB,
) -> None:
"""
Annulla un job di invio se è ancora in stato **pending** o **retrying**.
Non è possibile annullare un invio già partito (stato sending) o
completato (sent).
"""
svc = _svc(db)
# Verifica che l'utente possa agire su questo job
job = await svc.get_send_job(job_id, current_user.tenant_id)
if not current_user.is_admin:
perm_svc = PermissionService(db)
if not await perm_svc.check_can_send(current_user, job.mailbox_id):
raise ForbiddenError("Autorizzazione insufficiente per annullare questo invio")
await svc.cancel_send_job(job_id, current_user.tenant_id)
await db.commit()