Files
PecHub/backend/app/services/label_service.py
T
2026-03-19 14:28:09 +01:00

272 lines
9.1 KiB
Python

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