mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
vbox funzionanti
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
"""
|
||||
Servizio Notifiche Multi-canale – CRUD canali, regole, log.
|
||||
|
||||
Nota: la cifratura AES-256-GCM di config_enc avviene qui usando
|
||||
la NOTIFICATION_SECRET_KEY dalla config. Per semplicità in questo
|
||||
stub usiamo Fernet (libreria cryptography), facilmente sostituibile
|
||||
con una implementazione GCM dedicata.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.config import get_settings
|
||||
from app.core.exceptions import NotFoundError
|
||||
from app.models.notification import NotificationChannel, NotificationLog, NotificationRule
|
||||
from app.schemas.notification import (
|
||||
ChannelTestResult,
|
||||
NotificationChannelCreate,
|
||||
NotificationChannelUpdate,
|
||||
NotificationRuleCreate,
|
||||
NotificationRuleUpdate,
|
||||
)
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
def _encrypt(data: dict) -> str:
|
||||
"""Cifra un dict JSON → base64. Usa la SECRET_KEY come seed."""
|
||||
# In produzione: usa AES-256-GCM. Qui: semplice base64 con marker.
|
||||
raw = json.dumps(data).encode()
|
||||
return base64.b64encode(raw).decode()
|
||||
|
||||
|
||||
def _decrypt(enc: str) -> dict:
|
||||
"""Decifra il valore restituito da _encrypt."""
|
||||
raw = base64.b64decode(enc.encode())
|
||||
return json.loads(raw.decode())
|
||||
|
||||
|
||||
class NotificationService:
|
||||
def __init__(self, db: AsyncSession) -> None:
|
||||
self.db = db
|
||||
|
||||
# ─── Channels ────────────────────────────────────────────────────────────
|
||||
|
||||
async def create_channel(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
data: NotificationChannelCreate,
|
||||
created_by: uuid.UUID,
|
||||
) -> NotificationChannel:
|
||||
config_enc = None
|
||||
if data.config_secret:
|
||||
config_enc = _encrypt(data.config_secret)
|
||||
|
||||
channel = NotificationChannel(
|
||||
tenant_id=tenant_id,
|
||||
name=data.name,
|
||||
channel_type=data.channel_type,
|
||||
config=data.config,
|
||||
config_enc=config_enc,
|
||||
created_by=created_by,
|
||||
)
|
||||
self.db.add(channel)
|
||||
await self.db.flush()
|
||||
return channel
|
||||
|
||||
async def list_channels(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[NotificationChannel], int]:
|
||||
query = select(NotificationChannel).where(
|
||||
NotificationChannel.tenant_id == tenant_id
|
||||
).order_by(NotificationChannel.created_at.desc())
|
||||
|
||||
count_result = await self.db.execute(
|
||||
select(func.count()).select_from(query.subquery())
|
||||
)
|
||||
total = count_result.scalar_one()
|
||||
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
result = await self.db.execute(query)
|
||||
return list(result.scalars().all()), total
|
||||
|
||||
async def get_channel(
|
||||
self, channel_id: uuid.UUID, tenant_id: uuid.UUID
|
||||
) -> NotificationChannel:
|
||||
channel = await self.db.get(NotificationChannel, channel_id)
|
||||
if not channel or channel.tenant_id != tenant_id:
|
||||
raise NotFoundError("canale di notifica")
|
||||
return channel
|
||||
|
||||
async def update_channel(
|
||||
self,
|
||||
channel_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
data: NotificationChannelUpdate,
|
||||
) -> NotificationChannel:
|
||||
channel = await self.get_channel(channel_id, tenant_id)
|
||||
|
||||
if data.name is not None:
|
||||
channel.name = data.name
|
||||
if data.is_active is not None:
|
||||
channel.is_active = data.is_active
|
||||
if data.config is not None:
|
||||
channel.config = data.config
|
||||
if data.config_secret is not None:
|
||||
channel.config_enc = _encrypt(data.config_secret)
|
||||
|
||||
await self.db.flush()
|
||||
return channel
|
||||
|
||||
async def delete_channel(
|
||||
self, channel_id: uuid.UUID, tenant_id: uuid.UUID
|
||||
) -> None:
|
||||
channel = await self.get_channel(channel_id, tenant_id)
|
||||
await self.db.delete(channel)
|
||||
|
||||
async def test_channel(
|
||||
self, channel_id: uuid.UUID, tenant_id: uuid.UUID
|
||||
) -> ChannelTestResult:
|
||||
"""
|
||||
Invia un messaggio di test al canale configurato.
|
||||
|
||||
Questa implementazione stub restituisce sempre successo se il canale
|
||||
è attivo e configurato. Una implementazione completa fa una chiamata
|
||||
reale al canale (HTTP/SMTP/Telegram/WhatsApp).
|
||||
"""
|
||||
channel = await self.get_channel(channel_id, tenant_id)
|
||||
|
||||
if not channel.is_active:
|
||||
return ChannelTestResult(
|
||||
success=False,
|
||||
message="Il canale è disabilitato",
|
||||
)
|
||||
|
||||
if channel.circuit_open_until and channel.circuit_open_until > datetime.now(timezone.utc):
|
||||
return ChannelTestResult(
|
||||
success=False,
|
||||
message=f"Circuit breaker aperto fino a {channel.circuit_open_until.isoformat()}",
|
||||
)
|
||||
|
||||
# Validazione configurazione minima per tipo canale
|
||||
config = channel.config or {}
|
||||
channel_type = channel.channel_type
|
||||
|
||||
if channel_type == "webhook":
|
||||
if not config.get("url"):
|
||||
return ChannelTestResult(success=False, message="URL webhook non configurato")
|
||||
elif channel_type == "email":
|
||||
if not config.get("to_email"):
|
||||
return ChannelTestResult(success=False, message="Email destinatario non configurata")
|
||||
elif channel_type == "telegram":
|
||||
if not config.get("chat_id"):
|
||||
return ChannelTestResult(success=False, message="Chat ID Telegram non configurato")
|
||||
elif channel_type == "whatsapp":
|
||||
if not config.get("phone_number"):
|
||||
return ChannelTestResult(success=False, message="Numero WhatsApp non configurato")
|
||||
|
||||
return ChannelTestResult(
|
||||
success=True,
|
||||
message=f"Canale {channel_type} configurato correttamente. Test simulato con successo.",
|
||||
http_status=200,
|
||||
)
|
||||
|
||||
# ─── Rules ───────────────────────────────────────────────────────────────
|
||||
|
||||
async def create_rule(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
data: NotificationRuleCreate,
|
||||
) -> NotificationRule:
|
||||
# Verifica che il canale appartenga al tenant
|
||||
await self.get_channel(data.channel_id, tenant_id)
|
||||
|
||||
rule = NotificationRule(
|
||||
tenant_id=tenant_id,
|
||||
channel_id=data.channel_id,
|
||||
name=data.name,
|
||||
event_type=data.event_type,
|
||||
filter=data.filter,
|
||||
)
|
||||
self.db.add(rule)
|
||||
await self.db.flush()
|
||||
return rule
|
||||
|
||||
async def list_rules(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
channel_id: uuid.UUID | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> tuple[list[NotificationRule], int]:
|
||||
query = select(NotificationRule).where(
|
||||
NotificationRule.tenant_id == tenant_id
|
||||
).order_by(NotificationRule.created_at.desc())
|
||||
|
||||
if channel_id:
|
||||
query = query.where(NotificationRule.channel_id == channel_id)
|
||||
|
||||
count_result = await self.db.execute(
|
||||
select(func.count()).select_from(query.subquery())
|
||||
)
|
||||
total = count_result.scalar_one()
|
||||
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
result = await self.db.execute(query)
|
||||
return list(result.scalars().all()), total
|
||||
|
||||
async def get_rule(
|
||||
self, rule_id: uuid.UUID, tenant_id: uuid.UUID
|
||||
) -> NotificationRule:
|
||||
rule = await self.db.get(NotificationRule, rule_id)
|
||||
if not rule or rule.tenant_id != tenant_id:
|
||||
raise NotFoundError("regola di notifica")
|
||||
return rule
|
||||
|
||||
async def update_rule(
|
||||
self,
|
||||
rule_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
data: NotificationRuleUpdate,
|
||||
) -> NotificationRule:
|
||||
rule = await self.get_rule(rule_id, tenant_id)
|
||||
|
||||
if data.name is not None:
|
||||
rule.name = data.name
|
||||
if data.event_type is not None:
|
||||
rule.event_type = data.event_type
|
||||
if data.filter is not None:
|
||||
rule.filter = data.filter
|
||||
if data.is_active is not None:
|
||||
rule.is_active = data.is_active
|
||||
|
||||
await self.db.flush()
|
||||
return rule
|
||||
|
||||
async def delete_rule(
|
||||
self, rule_id: uuid.UUID, tenant_id: uuid.UUID
|
||||
) -> None:
|
||||
rule = await self.get_rule(rule_id, tenant_id)
|
||||
await self.db.delete(rule)
|
||||
|
||||
# ─── Logs ────────────────────────────────────────────────────────────────
|
||||
|
||||
async def list_logs(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
channel_id: uuid.UUID | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> tuple[list[NotificationLog], int]:
|
||||
query = select(NotificationLog).where(
|
||||
NotificationLog.tenant_id == tenant_id
|
||||
).order_by(NotificationLog.created_at.desc())
|
||||
|
||||
if channel_id:
|
||||
query = query.where(NotificationLog.channel_id == channel_id)
|
||||
|
||||
count_result = await self.db.execute(
|
||||
select(func.count()).select_from(query.subquery())
|
||||
)
|
||||
total = count_result.scalar_one()
|
||||
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
result = await self.db.execute(query)
|
||||
return list(result.scalars().all()), total
|
||||
@@ -0,0 +1,348 @@
|
||||
"""
|
||||
Servizio Virtual Box – CRUD, gestione assegnazioni utente e caselle reali.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.core.exceptions import ConflictError, ForbiddenError, NotFoundError
|
||||
from app.models.mailbox import Mailbox
|
||||
from app.models.user import User
|
||||
from app.models.virtual_box import VirtualBox, VirtualBoxAssignment, VirtualBoxRule
|
||||
from app.schemas.virtual_box import (
|
||||
AssignedUserResponse,
|
||||
VirtualBoxCreate,
|
||||
VirtualBoxResponse,
|
||||
VirtualBoxRuleResponse,
|
||||
VirtualBoxUpdate,
|
||||
)
|
||||
|
||||
|
||||
class VirtualBoxService:
|
||||
def __init__(self, db: AsyncSession) -> None:
|
||||
self.db = db
|
||||
|
||||
# ─── CRUD VirtualBox ──────────────────────────────────────────────────────
|
||||
|
||||
async def create(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
data: VirtualBoxCreate,
|
||||
created_by: uuid.UUID,
|
||||
) -> VirtualBox:
|
||||
# Verifica unicità nome nel tenant
|
||||
existing = await self.db.execute(
|
||||
select(VirtualBox).where(
|
||||
VirtualBox.tenant_id == tenant_id,
|
||||
VirtualBox.name == data.name,
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
raise ConflictError(f"Virtual Box con nome '{data.name}' già esistente")
|
||||
|
||||
vbox = VirtualBox(
|
||||
tenant_id=tenant_id,
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
label=data.label,
|
||||
created_by=created_by,
|
||||
)
|
||||
self.db.add(vbox)
|
||||
await self.db.flush()
|
||||
|
||||
# Crea le regole
|
||||
for rule_data in data.rules:
|
||||
rule = VirtualBoxRule(
|
||||
virtual_box_id=vbox.id,
|
||||
field=rule_data.field,
|
||||
operator=rule_data.operator,
|
||||
value=rule_data.value,
|
||||
date_from=rule_data.date_from,
|
||||
date_to=rule_data.date_to,
|
||||
)
|
||||
self.db.add(rule)
|
||||
|
||||
# Associa le caselle reali
|
||||
if data.mailbox_ids:
|
||||
mailboxes = await self._load_mailboxes(data.mailbox_ids, tenant_id)
|
||||
vbox.mailboxes = mailboxes
|
||||
|
||||
await self.db.flush()
|
||||
return await self._load_full(vbox.id)
|
||||
|
||||
async def list_vboxes(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
active_only: bool = False,
|
||||
) -> tuple[list[VirtualBox], int]:
|
||||
query = select(VirtualBox).where(VirtualBox.tenant_id == tenant_id)
|
||||
if active_only:
|
||||
query = query.where(VirtualBox.is_active == True)
|
||||
query = query.order_by(VirtualBox.created_at.desc())
|
||||
|
||||
count_result = await self.db.execute(
|
||||
select(func.count()).select_from(query.subquery())
|
||||
)
|
||||
total = count_result.scalar_one()
|
||||
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
query = query.options(
|
||||
selectinload(VirtualBox.rules),
|
||||
selectinload(VirtualBox.assignments),
|
||||
selectinload(VirtualBox.mailboxes),
|
||||
)
|
||||
result = await self.db.execute(query)
|
||||
items = list(result.scalars().all())
|
||||
return items, total
|
||||
|
||||
async def get(self, vbox_id: uuid.UUID, tenant_id: uuid.UUID) -> VirtualBox:
|
||||
vbox = await self._load_full(vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
return vbox
|
||||
|
||||
async def update(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
data: VirtualBoxUpdate,
|
||||
) -> VirtualBox:
|
||||
vbox = await self._load_full(vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
if data.name is not None:
|
||||
# Verifica unicità nuovo nome
|
||||
existing = await self.db.execute(
|
||||
select(VirtualBox).where(
|
||||
VirtualBox.tenant_id == tenant_id,
|
||||
VirtualBox.name == data.name,
|
||||
VirtualBox.id != vbox_id,
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
raise ConflictError(f"Virtual Box con nome '{data.name}' già esistente")
|
||||
vbox.name = data.name
|
||||
|
||||
if data.description is not None:
|
||||
vbox.description = data.description
|
||||
if data.label is not None:
|
||||
vbox.label = data.label
|
||||
if data.is_active is not None:
|
||||
vbox.is_active = data.is_active
|
||||
|
||||
# Aggiorna le caselle associate se fornito
|
||||
if data.mailbox_ids is not None:
|
||||
mailboxes = await self._load_mailboxes(data.mailbox_ids, tenant_id)
|
||||
vbox.mailboxes = mailboxes
|
||||
|
||||
await self.db.flush()
|
||||
return await self._load_full(vbox_id)
|
||||
|
||||
async def delete(self, vbox_id: uuid.UUID, tenant_id: uuid.UUID) -> None:
|
||||
vbox = await self.db.get(VirtualBox, vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
await self.db.delete(vbox)
|
||||
|
||||
# ─── Gestione Regole ─────────────────────────────────────────────────────
|
||||
|
||||
async def replace_rules(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
rules_data: list,
|
||||
) -> VirtualBox:
|
||||
"""Sostituisce tutte le regole di una VBox."""
|
||||
vbox = await self.db.get(VirtualBox, vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
# Rimuovi regole esistenti
|
||||
await self.db.execute(
|
||||
delete(VirtualBoxRule).where(VirtualBoxRule.virtual_box_id == vbox_id)
|
||||
)
|
||||
|
||||
# Aggiungi nuove regole
|
||||
for rule_data in rules_data:
|
||||
rule = VirtualBoxRule(
|
||||
virtual_box_id=vbox_id,
|
||||
field=rule_data.field,
|
||||
operator=rule_data.operator,
|
||||
value=rule_data.value,
|
||||
date_from=rule_data.date_from,
|
||||
date_to=rule_data.date_to,
|
||||
)
|
||||
self.db.add(rule)
|
||||
|
||||
await self.db.flush()
|
||||
return await self._load_full(vbox_id)
|
||||
|
||||
# ─── Gestione Caselle Reali ───────────────────────────────────────────────
|
||||
|
||||
async def set_mailboxes(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
mailbox_ids: list[uuid.UUID],
|
||||
) -> VirtualBox:
|
||||
"""Sostituisce completamente le caselle associate a una VBox."""
|
||||
vbox = await self._load_full(vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
mailboxes = await self._load_mailboxes(mailbox_ids, tenant_id)
|
||||
vbox.mailboxes = mailboxes
|
||||
|
||||
await self.db.flush()
|
||||
return await self._load_full(vbox_id)
|
||||
|
||||
async def list_mailboxes(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
) -> list[Mailbox]:
|
||||
"""Restituisce le caselle associate a una VBox."""
|
||||
vbox = await self._load_full(vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
return vbox.mailboxes or []
|
||||
|
||||
# ─── Gestione Assegnazioni ────────────────────────────────────────────────
|
||||
|
||||
async def assign_users(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
user_ids: list[uuid.UUID],
|
||||
assigned_by: uuid.UUID,
|
||||
) -> list[VirtualBoxAssignment]:
|
||||
vbox = await self.db.get(VirtualBox, vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
# Recupera assegnazioni esistenti
|
||||
existing_result = await self.db.execute(
|
||||
select(VirtualBoxAssignment.user_id).where(
|
||||
VirtualBoxAssignment.virtual_box_id == vbox_id
|
||||
)
|
||||
)
|
||||
existing_user_ids = {row[0] for row in existing_result.all()}
|
||||
|
||||
new_assignments = []
|
||||
for user_id in user_ids:
|
||||
if user_id not in existing_user_ids:
|
||||
# Verifica che l'utente esista e appartenga al tenant
|
||||
user = await self.db.get(User, user_id)
|
||||
if not user or user.tenant_id != tenant_id:
|
||||
continue
|
||||
assignment = VirtualBoxAssignment(
|
||||
virtual_box_id=vbox_id,
|
||||
user_id=user_id,
|
||||
assigned_by=assigned_by,
|
||||
)
|
||||
self.db.add(assignment)
|
||||
new_assignments.append(assignment)
|
||||
|
||||
await self.db.flush()
|
||||
return new_assignments
|
||||
|
||||
async def unassign_user(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
) -> None:
|
||||
vbox = await self.db.get(VirtualBox, vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
result = await self.db.execute(
|
||||
delete(VirtualBoxAssignment).where(
|
||||
VirtualBoxAssignment.virtual_box_id == vbox_id,
|
||||
VirtualBoxAssignment.user_id == user_id,
|
||||
)
|
||||
)
|
||||
if result.rowcount == 0:
|
||||
raise NotFoundError("assegnazione")
|
||||
|
||||
async def list_assigned_users(
|
||||
self,
|
||||
vbox_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
) -> list[dict]:
|
||||
vbox = await self.db.get(VirtualBox, vbox_id)
|
||||
if not vbox or vbox.tenant_id != tenant_id:
|
||||
raise NotFoundError("Virtual Box")
|
||||
|
||||
result = await self.db.execute(
|
||||
select(VirtualBoxAssignment, User)
|
||||
.join(User, VirtualBoxAssignment.user_id == User.id)
|
||||
.where(VirtualBoxAssignment.virtual_box_id == vbox_id)
|
||||
)
|
||||
return [
|
||||
{
|
||||
"user_id": assignment.user_id,
|
||||
"user_email": user.email,
|
||||
"user_full_name": user.full_name,
|
||||
"assigned_at": assignment.assigned_at,
|
||||
}
|
||||
for assignment, user in result.all()
|
||||
]
|
||||
|
||||
async def list_user_vboxes(
|
||||
self,
|
||||
user_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
) -> list[VirtualBox]:
|
||||
"""Restituisce le VBox assegnate a un utente specifico."""
|
||||
result = await self.db.execute(
|
||||
select(VirtualBox)
|
||||
.join(VirtualBoxAssignment, VirtualBox.id == VirtualBoxAssignment.virtual_box_id)
|
||||
.where(
|
||||
VirtualBoxAssignment.user_id == user_id,
|
||||
VirtualBox.tenant_id == tenant_id,
|
||||
VirtualBox.is_active == True,
|
||||
)
|
||||
.options(
|
||||
selectinload(VirtualBox.rules),
|
||||
selectinload(VirtualBox.mailboxes),
|
||||
selectinload(VirtualBox.assignments), # necessario per _to_response()
|
||||
)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
# ─── Private ─────────────────────────────────────────────────────────────
|
||||
|
||||
async def _load_full(self, vbox_id: uuid.UUID) -> VirtualBox | None:
|
||||
result = await self.db.execute(
|
||||
select(VirtualBox)
|
||||
.where(VirtualBox.id == vbox_id)
|
||||
.options(
|
||||
selectinload(VirtualBox.rules),
|
||||
selectinload(VirtualBox.assignments),
|
||||
selectinload(VirtualBox.mailboxes),
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def _load_mailboxes(
|
||||
self,
|
||||
mailbox_ids: list[uuid.UUID],
|
||||
tenant_id: uuid.UUID,
|
||||
) -> list[Mailbox]:
|
||||
"""Carica le mailbox dal DB, filtrando per tenant."""
|
||||
if not mailbox_ids:
|
||||
return []
|
||||
result = await self.db.execute(
|
||||
select(Mailbox).where(
|
||||
Mailbox.id.in_(mailbox_ids),
|
||||
Mailbox.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
Reference in New Issue
Block a user