mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
vbox funzionanti
This commit is contained in:
@@ -31,6 +31,8 @@ from app.dependencies import CurrentUser, DB
|
||||
from app.models.message import Attachment, Message
|
||||
from app.schemas.message import (
|
||||
AttachmentResponse,
|
||||
MessageBulkUpdateRequest,
|
||||
MessageBulkUpdateResponse,
|
||||
MessageListResponse,
|
||||
MessageResponse,
|
||||
MessageUpdateRequest,
|
||||
@@ -42,6 +44,49 @@ settings = get_settings()
|
||||
|
||||
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def _apply_vbox_rule(q, field: str, operator: str, value: str):
|
||||
"""
|
||||
Applica una singola regola di Virtual Box alla query SQLAlchemy.
|
||||
|
||||
field : subject | from_address | to_address | imap_folder
|
||||
operator : contains | equals | starts_with | ends_with | regex
|
||||
"""
|
||||
if field == "subject":
|
||||
col = Message.subject
|
||||
elif field == "from_address":
|
||||
col = Message.from_address
|
||||
elif field == "to_address":
|
||||
# to_addresses è ARRAY(Text) – converte in stringa per il confronto
|
||||
arr_text = func.array_to_string(Message.to_addresses, ",")
|
||||
if operator == "contains":
|
||||
return q.where(arr_text.ilike(f"%{value}%"))
|
||||
elif operator == "equals":
|
||||
return q.where(arr_text.ilike(value))
|
||||
elif operator == "starts_with":
|
||||
return q.where(arr_text.ilike(f"{value}%"))
|
||||
elif operator == "ends_with":
|
||||
return q.where(arr_text.ilike(f"%{value}"))
|
||||
elif operator == "regex":
|
||||
return q.where(arr_text.op("~*")(value))
|
||||
return q
|
||||
elif field == "imap_folder":
|
||||
col = Message.imap_folder
|
||||
else:
|
||||
return q # campo non supportato – ignorato
|
||||
|
||||
if operator == "contains":
|
||||
return q.where(col.ilike(f"%{value}%"))
|
||||
elif operator == "equals":
|
||||
return q.where(func.lower(col) == value.lower())
|
||||
elif operator == "starts_with":
|
||||
return q.where(col.ilike(f"{value}%"))
|
||||
elif operator == "ends_with":
|
||||
return q.where(col.ilike(f"%{value}"))
|
||||
elif operator == "regex":
|
||||
return q.where(col.op("~*")(value))
|
||||
return q
|
||||
|
||||
|
||||
async def _get_visible_mailbox_ids(
|
||||
user, db: AsyncSession
|
||||
) -> Optional[list[uuid.UUID]]:
|
||||
@@ -89,6 +134,7 @@ async def list_messages(
|
||||
current_user: CurrentUser,
|
||||
db: DB,
|
||||
# Filtri
|
||||
vbox_id: Optional[uuid.UUID] = Query(None, description="Filtra per Virtual Box assegnata"),
|
||||
mailbox_id: Optional[uuid.UUID] = Query(None),
|
||||
direction: Optional[str] = Query(None, pattern="^(inbound|outbound)$"),
|
||||
state: Optional[str] = Query(None),
|
||||
@@ -106,17 +152,61 @@ async def list_messages(
|
||||
|
||||
- `is_archived=False` (default) esclude i messaggi archiviati.
|
||||
- `search` cerca su subject, from_address, to_addresses.
|
||||
- `vbox_id` filtra per Virtual Box assegnata all'utente corrente.
|
||||
"""
|
||||
# Determinare le caselle visibili
|
||||
# Determinare le caselle visibili (normale check permessi)
|
||||
visible_mailbox_ids = await _get_visible_mailbox_ids(current_user, db)
|
||||
|
||||
# ── Filtro Virtual Box ────────────────────────────────────────────────────
|
||||
vbox_rules: list = []
|
||||
if vbox_id is not None:
|
||||
from sqlalchemy.orm import selectinload
|
||||
from app.models.virtual_box import VirtualBox, VirtualBoxAssignment
|
||||
|
||||
vbox_result = await db.execute(
|
||||
select(VirtualBox)
|
||||
.where(
|
||||
VirtualBox.id == vbox_id,
|
||||
VirtualBox.tenant_id == current_user.tenant_id,
|
||||
VirtualBox.is_active == True,
|
||||
)
|
||||
.options(
|
||||
selectinload(VirtualBox.rules),
|
||||
selectinload(VirtualBox.mailboxes),
|
||||
)
|
||||
)
|
||||
vbox = vbox_result.scalar_one_or_none()
|
||||
if not vbox:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
# Non-admin: verifica che l'utente sia assegnato alla VBox
|
||||
if not current_user.is_admin:
|
||||
assign_result = await db.execute(
|
||||
select(VirtualBoxAssignment).where(
|
||||
VirtualBoxAssignment.virtual_box_id == vbox_id,
|
||||
VirtualBoxAssignment.user_id == current_user.id,
|
||||
)
|
||||
)
|
||||
if not assign_result.scalar_one_or_none():
|
||||
raise ForbiddenError("Virtual Box non accessibile")
|
||||
|
||||
# L'assegnazione alla VBox garantisce accesso alle sue caselle:
|
||||
# sovrascrive il filtro permessi normali per questa query.
|
||||
if vbox.mailboxes:
|
||||
visible_mailbox_ids = [m.id for m in vbox.mailboxes]
|
||||
# Se la VBox non ha caselle esplicitamente associate,
|
||||
# si mantiene il filtro permessi normale (visible_mailbox_ids invariato).
|
||||
|
||||
vbox_rules = vbox.rules or []
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Query base
|
||||
q = select(Message).where(
|
||||
Message.tenant_id == current_user.tenant_id,
|
||||
Message.parent_message_id.is_(None), # escludi ricevute (messaggi figlio)
|
||||
)
|
||||
|
||||
# Filtro caselle visibili per non-admin
|
||||
# Filtro caselle visibili per non-admin (o dopo override VBox)
|
||||
if visible_mailbox_ids is not None:
|
||||
if not visible_mailbox_ids:
|
||||
# Nessuna casella accessibile → lista vuota
|
||||
@@ -158,6 +248,10 @@ async def list_messages(
|
||||
)
|
||||
)
|
||||
|
||||
# Applica le regole della Virtual Box (AND tra le regole)
|
||||
for rule in vbox_rules:
|
||||
q = _apply_vbox_rule(q, rule.field, rule.operator, rule.value)
|
||||
|
||||
# Conteggio totale
|
||||
count_q = select(func.count()).select_from(q.subquery())
|
||||
total = (await db.execute(count_q)).scalar_one()
|
||||
@@ -183,6 +277,59 @@ async def list_messages(
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/bulk", response_model=MessageBulkUpdateResponse)
|
||||
async def bulk_update_messages(
|
||||
data: MessageBulkUpdateRequest,
|
||||
current_user: CurrentUser,
|
||||
db: DB,
|
||||
) -> MessageBulkUpdateResponse:
|
||||
"""
|
||||
Aggiorna in blocco i flag operativi (is_starred, is_archived) di più messaggi.
|
||||
|
||||
Restituisce il numero di messaggi aggiornati e la lista aggiornata.
|
||||
I messaggi non trovati o non accessibili vengono silenziosamente ignorati.
|
||||
"""
|
||||
if not data.ids:
|
||||
return MessageBulkUpdateResponse(updated=0, items=[])
|
||||
|
||||
# Carica tutti i messaggi del tenant
|
||||
result = await db.execute(
|
||||
select(Message).where(
|
||||
Message.id.in_(data.ids),
|
||||
Message.tenant_id == current_user.tenant_id,
|
||||
)
|
||||
)
|
||||
messages = list(result.scalars().all())
|
||||
|
||||
# Filtra per permessi se non admin
|
||||
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()
|
||||
messages = [m for m in messages if m.mailbox_id in visible_set]
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
for message in messages:
|
||||
if data.is_starred is not None:
|
||||
message.is_starred = data.is_starred
|
||||
if data.is_archived is not None:
|
||||
message.is_archived = data.is_archived
|
||||
if data.is_archived and not message.archived_at:
|
||||
message.archived_at = now
|
||||
elif not data.is_archived:
|
||||
message.archived_at = None
|
||||
|
||||
await db.commit()
|
||||
for message in messages:
|
||||
await db.refresh(message)
|
||||
|
||||
return MessageBulkUpdateResponse(
|
||||
updated=len(messages),
|
||||
items=[MessageResponse.model_validate(m) for m in messages],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{message_id}", response_model=MessageResponse)
|
||||
async def get_message(
|
||||
message_id: uuid.UUID,
|
||||
|
||||
Reference in New Issue
Block a user