This commit is contained in:
2026-04-07 11:32:05 +02:00
103 changed files with 14789 additions and 555 deletions
+55 -5
View File
@@ -35,6 +35,8 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import get_settings
from app.jobs.dispatch_notification import evaluate_and_enqueue_notifications
from app.jobs.index_message import index_message
from app.models import Attachment, Mailbox, Message
from app.parsers.eml_parser import parse_eml
from app.parsers.pec_parser import apply_outbound_transition, classify_pec_message
@@ -539,11 +541,15 @@ async def _save_message(
logger.debug(f"[{mailbox.email_address}] UID {uid} in {imap_folder!r} già in DB, skip")
return False
# ── Parsing completo EML ──────────────────────────────────────────────────
parsed = parse_eml(raw_eml)
pec_class = classify_pec_message(
parsed.raw_message or email.message_from_bytes(raw_eml)
)
# ── Classificazione PEC da header (veloce, senza body) ───────────────────
# La classificazione avviene PRIMA del parsing completo perche' il parser
# deve sapere se il messaggio e' una ricevuta per evitare di sovrascrivere
# il body_text (testo della ricevuta) con il contenuto di postacert.eml.
quick_msg = email.message_from_bytes(raw_eml)
pec_class = classify_pec_message(quick_msg)
# ── Parsing completo EML (con is_receipt per proteggere il body) ──────────
parsed = parse_eml(raw_eml, is_receipt=pec_class.is_receipt)
received_at = datetime.now(UTC)
# ── State machine: trova e aggiorna messaggio outbound ────────────────────
@@ -635,6 +641,50 @@ async def _save_message(
f"direction={direction!r} pec_type={pec_class.pec_type!r} "
f"subject={message.subject!r} allegati={len(parsed.attachments)}"
)
# ── Indicizzazione full-text (non bloccante, non interrompe la sync) ─────
# Chiamata dopo il flush degli allegati: index_message puo' leggere
# sia il messaggio che gli allegati dalla sessione corrente.
await index_message(message.id, db)
# ── Valutazione e accodamento notifiche (non bloccante) ───────────────────
# Solo per messaggi inbound: le ricevute PEC e la posta in arrivo
# possono triggerare regole di notifica configurate dal tenant.
# I messaggi outbound (Sent) non generano notifiche automatiche.
if direction == "inbound":
await evaluate_and_enqueue_notifications(
message=message,
mailbox=mailbox,
db=db,
redis_client=redis_client,
)
# ── Regole di smistamento automatico (Feature 2) ──────────────────────────
# Solo per messaggi inbound posta_certificata (non ricevute di sistema).
if direction == "inbound" and pec_class.pec_type == "posta_certificata":
try:
await redis_client.enqueue_job("apply_routing_rules", str(message.id))
except Exception as e:
logger.warning(f"[{mailbox.email_address}] Impossibile enqueue apply_routing_rules: {e}")
# ── Auto-save mittente nella rubrica (Feature 6) ──────────────────────────
# Per messaggi inbound di tipo posta_certificata, salva automaticamente
# il mittente nella rubrica pec_contacts del tenant (upsert idempotente).
if direction == "inbound" and pec_class.pec_type == "posta_certificata" and message.from_address:
try:
from sqlalchemy import text as _text
await db.execute(
_text("""
INSERT INTO pec_contacts (id, tenant_id, email, auto_saved, created_at, updated_at)
VALUES (gen_random_uuid(), :tenant_id, :email, true, now(), now())
ON CONFLICT (tenant_id, email) DO NOTHING
"""),
{"tenant_id": str(mailbox.tenant_id), "email": message.from_address.lower().strip()},
)
await db.flush()
except Exception as e:
logger.debug(f"[{mailbox.email_address}] Auto-save contatto fallito (non critico): {e}")
return True