""" Service per la gestione delle Label (tag) e la loro assegnazione ai messaggi. """ import uuid from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.exceptions import ConflictError, NotFoundError from app.models.label import Label, MessageLabel from app.models.message import Message from app.schemas.label import LabelCreate, LabelUpdate class LabelService: def __init__(self, db: AsyncSession): self.db = db # ─── CRUD Label ─────────────────────────────────────────────────────────── async def list_labels(self, tenant_id: uuid.UUID) -> list[Label]: result = await self.db.execute( select(Label) .where(Label.tenant_id == tenant_id) .order_by(Label.name) ) return list(result.scalars().all()) async def get_label(self, tenant_id: uuid.UUID, label_id: uuid.UUID) -> Label: result = await self.db.execute( select(Label).where( Label.id == label_id, Label.tenant_id == tenant_id, ) ) label = result.scalar_one_or_none() if not label: raise NotFoundError(f"Tag {label_id} non trovato") return label async def create_label(self, tenant_id: uuid.UUID, data: LabelCreate) -> Label: # Verifica unicità existing = await self.db.execute( select(Label).where( Label.tenant_id == tenant_id, Label.name == data.name, ) ) if existing.scalar_one_or_none(): raise ConflictError(f"Tag '{data.name}' già esistente") label = Label( tenant_id=tenant_id, name=data.name, color=data.color, ) self.db.add(label) await self.db.commit() await self.db.refresh(label) return label async def update_label( self, tenant_id: uuid.UUID, label_id: uuid.UUID, data: LabelUpdate ) -> Label: label = await self.get_label(tenant_id, label_id) if data.name is not None: # Verifica unicità del nuovo nome existing = await self.db.execute( select(Label).where( Label.tenant_id == tenant_id, Label.name == data.name, Label.id != label_id, ) ) if existing.scalar_one_or_none(): raise ConflictError(f"Tag '{data.name}' già esistente") label.name = data.name if data.color is not None: label.color = data.color await self.db.commit() await self.db.refresh(label) return label async def delete_label(self, tenant_id: uuid.UUID, label_id: uuid.UUID) -> None: label = await self.get_label(tenant_id, label_id) await self.db.delete(label) await self.db.commit() # ─── Assegnazione tag a singolo messaggio ───────────────────────────────── async def get_message_labels( self, message_id: uuid.UUID, tenant_id: uuid.UUID ) -> list[Label]: result = await self.db.execute( select(Label) .join(MessageLabel, Label.id == MessageLabel.label_id) .where( MessageLabel.message_id == message_id, Label.tenant_id == tenant_id, ) .order_by(Label.name) ) return list(result.scalars().all()) async def set_message_labels( self, message_id: uuid.UUID, tenant_id: uuid.UUID, label_ids: list[uuid.UUID], ) -> list[Label]: """Sostituisce tutti i tag di un messaggio con quelli indicati.""" # Verifica che i label appartengano al tenant valid_ids: set[uuid.UUID] = set() if label_ids: result = await self.db.execute( select(Label).where( Label.id.in_(label_ids), Label.tenant_id == tenant_id, ) ) valid_ids = {lbl.id for lbl in result.scalars().all()} # Rimuovi tutti i tag esistenti dal messaggio await self.db.execute( delete(MessageLabel).where(MessageLabel.message_id == message_id) ) # Aggiungi i nuovi tag validi for lbl_id in valid_ids: self.db.add(MessageLabel(message_id=message_id, label_id=lbl_id)) await self.db.commit() return await self.get_message_labels(message_id, tenant_id) async def add_message_labels( self, message_id: uuid.UUID, tenant_id: uuid.UUID, label_ids: list[uuid.UUID], ) -> list[Label]: """Aggiunge tag a un messaggio senza rimuovere quelli esistenti.""" if not label_ids: return await self.get_message_labels(message_id, tenant_id) # Verifica appartenenza al tenant result = await self.db.execute( select(Label).where( Label.id.in_(label_ids), Label.tenant_id == tenant_id, ) ) valid_labels = list(result.scalars().all()) # Carica tag esistenti per evitare duplicati existing_result = await self.db.execute( select(MessageLabel.label_id).where( MessageLabel.message_id == message_id ) ) existing_ids = set(existing_result.scalars().all()) for lbl in valid_labels: if lbl.id not in existing_ids: self.db.add(MessageLabel(message_id=message_id, label_id=lbl.id)) await self.db.commit() return await self.get_message_labels(message_id, tenant_id) async def remove_message_labels( self, message_id: uuid.UUID, tenant_id: uuid.UUID, label_ids: list[uuid.UUID], ) -> list[Label]: """Rimuove specifici tag da un messaggio.""" if label_ids: await self.db.execute( delete(MessageLabel).where( MessageLabel.message_id == message_id, MessageLabel.label_id.in_(label_ids), ) ) await self.db.commit() return await self.get_message_labels(message_id, tenant_id) # ─── Azioni bulk ────────────────────────────────────────────────────────── async def bulk_add_labels( self, message_ids: list[uuid.UUID], tenant_id: uuid.UUID, label_ids: list[uuid.UUID], ) -> int: """Aggiunge tag a più messaggi in blocco.""" if not label_ids or not message_ids: return 0 # Verifica label del tenant lbl_result = await self.db.execute( select(Label).where( Label.id.in_(label_ids), Label.tenant_id == tenant_id, ) ) valid_label_ids = [lbl.id for lbl in lbl_result.scalars().all()] # Verifica messaggi del tenant msg_result = await self.db.execute( select(Message.id).where( Message.id.in_(message_ids), Message.tenant_id == tenant_id, ) ) valid_message_ids = list(msg_result.scalars().all()) if not valid_label_ids or not valid_message_ids: return 0 # Carica coppie esistenti per evitare duplicati existing_result = await self.db.execute( select(MessageLabel).where( MessageLabel.message_id.in_(valid_message_ids), MessageLabel.label_id.in_(valid_label_ids), ) ) existing_pairs = { (ml.message_id, ml.label_id) for ml in existing_result.scalars().all() } for msg_id in valid_message_ids: for lbl_id in valid_label_ids: if (msg_id, lbl_id) not in existing_pairs: self.db.add(MessageLabel(message_id=msg_id, label_id=lbl_id)) await self.db.commit() return len(valid_message_ids) async def bulk_remove_labels( self, message_ids: list[uuid.UUID], tenant_id: uuid.UUID, label_ids: list[uuid.UUID], ) -> int: """Rimuove tag da più messaggi in blocco.""" if not label_ids or not message_ids: return 0 # Verifica messaggi del tenant msg_result = await self.db.execute( select(Message.id).where( Message.id.in_(message_ids), Message.tenant_id == tenant_id, ) ) valid_message_ids = list(msg_result.scalars().all()) if not valid_message_ids: return 0 await self.db.execute( delete(MessageLabel).where( MessageLabel.message_id.in_(valid_message_ids), MessageLabel.label_id.in_(label_ids), ) ) await self.db.commit() return len(valid_message_ids)