""" 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()