""" Router tag (Label). Endpoint: - GET /labels – elenca i tag del tenant - POST /labels – crea un nuovo tag (admin) - PATCH /labels/{id} – modifica un tag (admin) - DELETE /labels/{id} – elimina un tag (admin) - GET /messages/{id}/labels – tag di un messaggio - PUT /messages/{id}/labels – imposta i tag di un messaggio (sostituisce) - POST /messages/{id}/labels/add – aggiunge tag a un messaggio - POST /messages/{id}/labels/remove – rimuove tag da un messaggio - POST /messages/bulk-labels – aggiunge/rimuove tag in blocco Permessi: - GET /labels: tutti gli utenti autenticati - POST/PATCH/DELETE: solo admin - Operazioni su messaggi: utenti con accesso alla casella del messaggio """ import uuid from fastapi import APIRouter, status from sqlalchemy import select from app.core.exceptions import ForbiddenError, NotFoundError from app.dependencies import AdminUser, CurrentUser, DB from app.models.message import Message from app.schemas.label import ( LabelCreate, LabelResponse, LabelUpdate, MessageBulkLabelRequest, MessageBulkLabelResponse, MessageLabelAddRequest, MessageLabelRemoveRequest, MessageLabelSetRequest, ) from app.services.label_service import LabelService router = APIRouter(tags=["Labels"]) # ─── Helpers ────────────────────────────────────────────────────────────────── async def _check_message_access( message_id: uuid.UUID, current_user, db, ) -> Message: """Verifica che il messaggio esista e l'utente vi abbia accesso.""" result = await db.execute( select(Message).where( Message.id == message_id, Message.tenant_id == current_user.tenant_id, ) ) message = result.scalar_one_or_none() if not message: raise NotFoundError(f"Messaggio {message_id} non trovato") if not current_user.is_admin: from app.services.permission_service import PermissionService perm_svc = PermissionService(db) if not await perm_svc.check_can_read(current_user, message.mailbox_id): raise ForbiddenError("Accesso al messaggio non autorizzato") return message # ─── CRUD Label ─────────────────────────────────────────────────────────────── @router.get("/labels", response_model=list[LabelResponse]) async def list_labels( current_user: CurrentUser, db: DB, ) -> list[LabelResponse]: """Elenca tutti i tag del tenant corrente.""" svc = LabelService(db) labels = await svc.list_labels(current_user.tenant_id) return [LabelResponse.model_validate(l) for l in labels] @router.post("/labels", response_model=LabelResponse, status_code=status.HTTP_201_CREATED) async def create_label( data: LabelCreate, current_user: AdminUser, db: DB, ) -> LabelResponse: """Crea un nuovo tag (solo admin).""" svc = LabelService(db) label = await svc.create_label(current_user.tenant_id, data) return LabelResponse.model_validate(label) @router.patch("/labels/{label_id}", response_model=LabelResponse) async def update_label( label_id: uuid.UUID, data: LabelUpdate, current_user: AdminUser, db: DB, ) -> LabelResponse: """Modifica un tag esistente (solo admin).""" svc = LabelService(db) label = await svc.update_label(current_user.tenant_id, label_id, data) return LabelResponse.model_validate(label) @router.delete("/labels/{label_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_label( label_id: uuid.UUID, current_user: AdminUser, db: DB, ) -> None: """Elimina un tag (solo admin). Viene rimosso automaticamente da tutti i messaggi.""" svc = LabelService(db) await svc.delete_label(current_user.tenant_id, label_id) # ─── Tag su singolo messaggio ───────────────────────────────────────────────── @router.get("/messages/{message_id}/labels", response_model=list[LabelResponse]) async def get_message_labels( message_id: uuid.UUID, current_user: CurrentUser, db: DB, ) -> list[LabelResponse]: """Elenca i tag assegnati a un messaggio.""" await _check_message_access(message_id, current_user, db) svc = LabelService(db) labels = await svc.get_message_labels(message_id, current_user.tenant_id) return [LabelResponse.model_validate(l) for l in labels] @router.put("/messages/{message_id}/labels", response_model=list[LabelResponse]) async def set_message_labels( message_id: uuid.UUID, data: MessageLabelSetRequest, current_user: CurrentUser, db: DB, ) -> list[LabelResponse]: """ Sostituisce tutti i tag di un messaggio. Passare una lista vuota per rimuovere tutti i tag. """ await _check_message_access(message_id, current_user, db) svc = LabelService(db) labels = await svc.set_message_labels( message_id, current_user.tenant_id, data.label_ids ) return [LabelResponse.model_validate(l) for l in labels] @router.post("/messages/{message_id}/labels/add", response_model=list[LabelResponse]) async def add_message_labels( message_id: uuid.UUID, data: MessageLabelAddRequest, current_user: CurrentUser, db: DB, ) -> list[LabelResponse]: """Aggiunge tag a un messaggio senza rimuovere quelli esistenti.""" await _check_message_access(message_id, current_user, db) svc = LabelService(db) labels = await svc.add_message_labels( message_id, current_user.tenant_id, data.label_ids ) return [LabelResponse.model_validate(l) for l in labels] @router.post("/messages/{message_id}/labels/remove", response_model=list[LabelResponse]) async def remove_message_labels( message_id: uuid.UUID, data: MessageLabelRemoveRequest, current_user: CurrentUser, db: DB, ) -> list[LabelResponse]: """Rimuove specifici tag da un messaggio.""" await _check_message_access(message_id, current_user, db) svc = LabelService(db) labels = await svc.remove_message_labels( message_id, current_user.tenant_id, data.label_ids ) return [LabelResponse.model_validate(l) for l in labels] # ─── Bulk labels ────────────────────────────────────────────────────────────── @router.post("/messages/bulk-labels", response_model=MessageBulkLabelResponse) async def bulk_labels( data: MessageBulkLabelRequest, current_user: CurrentUser, db: DB, ) -> MessageBulkLabelResponse: """ Aggiunge o rimuove tag da più messaggi in blocco. - action="add": aggiunge i tag ai messaggi indicati - action="remove": rimuove i tag dai messaggi indicati I messaggi non accessibili all'utente vengono silenziosamente ignorati. """ if not data.message_ids or not data.label_ids: return MessageBulkLabelResponse(updated=0) # Per utenti non-admin filtra per caselle accessibili message_ids = [str(mid) for mid in data.message_ids] if not current_user.is_admin: from app.services.permission_service import PermissionService perm_svc = PermissionService(db) visible = await perm_svc.get_visible_mailboxes(current_user) visible_set = set(visible) if visible else set() # Filtra i messaggi per caselle visibili result = await db.execute( select(Message.id).where( Message.id.in_(data.message_ids), Message.tenant_id == current_user.tenant_id, Message.mailbox_id.in_(visible_set), ) ) filtered_ids = list(result.scalars().all()) else: filtered_ids = data.message_ids svc = LabelService(db) if data.action == "add": updated = await svc.bulk_add_labels( filtered_ids, current_user.tenant_id, data.label_ids ) else: updated = await svc.bulk_remove_labels( filtered_ids, current_user.tenant_id, data.label_ids ) return MessageBulkLabelResponse(updated=updated)