feat: add mail source import history
This commit is contained in:
@@ -207,6 +207,7 @@ class GmailClient:
|
||||
"success": True,
|
||||
"processed": 0,
|
||||
"reports_found": 0,
|
||||
"duplicate_reports": 0,
|
||||
"new_domains": [],
|
||||
"errors": [],
|
||||
"new_ingested_ids": [],
|
||||
@@ -365,6 +366,8 @@ class GmailClient:
|
||||
if self._store_report_if_new(report):
|
||||
stats["reports_found"] += 1
|
||||
reports_found += 1
|
||||
else:
|
||||
stats["duplicate_reports"] = stats.get("duplicate_reports", 0) + 1
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
logger.error("Failed to parse DMARC attachment %s: %s", filename, exc)
|
||||
stats["errors"].append(f"Failed to parse {filename}: {exc}")
|
||||
|
||||
@@ -146,7 +146,7 @@ class IMAPClient:
|
||||
msg = email.message_from_bytes(raw_email)
|
||||
|
||||
if self._is_dmarc_report_email(msg):
|
||||
reports_found = self._process_attachments(msg)
|
||||
reports_found = self._process_attachments(msg, stats)
|
||||
stats["reports_found"] += reports_found
|
||||
|
||||
# Mark email as read (and optionally delete)
|
||||
@@ -178,6 +178,7 @@ class IMAPClient:
|
||||
"success": True,
|
||||
"processed": 0,
|
||||
"reports_found": 0,
|
||||
"duplicate_reports": 0,
|
||||
"new_domains": [],
|
||||
"errors": [],
|
||||
}
|
||||
@@ -350,7 +351,7 @@ class IMAPClient:
|
||||
|
||||
return False
|
||||
|
||||
def _process_attachments(self, msg: email.message.Message) -> int:
|
||||
def _process_attachments(self, msg: email.message.Message, stats: dict | None = None) -> int:
|
||||
"""
|
||||
Process email attachments that might be DMARC reports
|
||||
|
||||
@@ -394,6 +395,10 @@ class IMAPClient:
|
||||
report_id,
|
||||
domain,
|
||||
)
|
||||
if stats is not None:
|
||||
stats["duplicate_reports"] = (
|
||||
stats.get("duplicate_reports", 0) + 1
|
||||
)
|
||||
continue
|
||||
|
||||
# Add the report to the store
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.mail_source import MailSource
|
||||
from app.models.mail_source_import import MailSourceImport
|
||||
|
||||
MAX_STORED_ERRORS = 10
|
||||
MAX_ERROR_LENGTH = 500
|
||||
|
||||
|
||||
def _sanitize_error(value: object) -> str:
|
||||
"""Return a compact, log-safe error string for storage and UI display."""
|
||||
text = str(value).replace("\r", "").replace("\n", " ").strip()
|
||||
if len(text) > MAX_ERROR_LENGTH:
|
||||
return text[: MAX_ERROR_LENGTH - 1] + "..."
|
||||
return text
|
||||
|
||||
|
||||
def _json_list(values: Optional[Iterable[Any]]) -> str:
|
||||
return json.dumps([str(value) for value in values or []])
|
||||
|
||||
|
||||
def record_import_attempt(
|
||||
db: Session,
|
||||
source: MailSource,
|
||||
results: Dict[str, Any],
|
||||
*,
|
||||
started_at: datetime,
|
||||
trigger: str,
|
||||
) -> MailSourceImport:
|
||||
"""Persist a sanitized summary of a mail source import attempt."""
|
||||
result_errors = list(results.get("errors") or [])
|
||||
errors = [_sanitize_error(error) for error in result_errors[:MAX_STORED_ERRORS]]
|
||||
success = bool(results.get("success", False))
|
||||
status = "success" if success and not errors else "warning" if success else "failed"
|
||||
|
||||
attempt = MailSourceImport(
|
||||
mail_source_id=source.id,
|
||||
trigger=trigger,
|
||||
status=status,
|
||||
processed=int(results.get("processed", 0) or 0),
|
||||
reports_found=int(results.get("reports_found", 0) or 0),
|
||||
duplicate_reports=int(results.get("duplicate_reports", 0) or 0),
|
||||
error_count=len(result_errors),
|
||||
new_domains=_json_list(results.get("new_domains", [])),
|
||||
errors=json.dumps(errors),
|
||||
started_at=started_at,
|
||||
finished_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(attempt)
|
||||
return attempt
|
||||
Reference in New Issue
Block a user