klausur-service (11 files): - cv_gutter_repair, ocr_pipeline_regression, upload_api - ocr_pipeline_sessions, smart_spell, nru_worksheet_generator - ocr_pipeline_overlays, mail/aggregator, zeugnis_api - cv_syllable_detect, self_rag backend-lehrer (17 files): - classroom_engine/suggestions, generators/quiz_generator - worksheets_api, llm_gateway/comparison, state_engine_api - classroom/models (→ 4 submodules), services/file_processor - alerts_agent/api/wizard+digests+routes, content_generators/pdf - classroom/routes/sessions, llm_gateway/inference - classroom_engine/analytics, auth/keycloak_auth - alerts_agent/processing/rule_engine, ai_processor/print_versions agent-core (5 files): - brain/memory_store, brain/knowledge_graph, brain/context_manager - orchestrator/supervisor, sessions/session_manager admin-lehrer (5 components): - GridOverlay, StepGridReview, DevOpsPipelineSidebar - DataFlowDiagram, sbom/wizard/page website (2 files): - DependencyMap, lehrer/abitur-archiv Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
156 lines
4.4 KiB
Python
156 lines
4.4 KiB
Python
"""
|
|
Mail Aggregator Service — barrel re-export.
|
|
|
|
All implementation split into:
|
|
aggregator_imap — IMAP connection, sync, email parsing
|
|
aggregator_smtp — SMTP connection, email sending
|
|
|
|
Multi-account IMAP aggregation with async support.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
from .credentials import get_credentials_service
|
|
from .mail_db import get_email_accounts, get_unified_inbox
|
|
from .models import AccountTestResult
|
|
from .aggregator_imap import IMAPMixin, IMAPConnectionError
|
|
from .aggregator_smtp import SMTPMixin, SMTPConnectionError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MailAggregator(IMAPMixin, SMTPMixin):
|
|
"""
|
|
Aggregates emails from multiple IMAP accounts into a unified inbox.
|
|
|
|
Features:
|
|
- Connect to multiple IMAP accounts
|
|
- Fetch and cache emails in PostgreSQL
|
|
- Send emails via SMTP
|
|
- Handle connection pooling
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._credentials_service = get_credentials_service()
|
|
self._imap_connections: Dict[str, Any] = {}
|
|
self._sync_lock = asyncio.Lock()
|
|
|
|
async def test_account_connection(
|
|
self,
|
|
imap_host: str,
|
|
imap_port: int,
|
|
imap_ssl: bool,
|
|
smtp_host: str,
|
|
smtp_port: int,
|
|
smtp_ssl: bool,
|
|
email_address: str,
|
|
password: str,
|
|
) -> AccountTestResult:
|
|
"""
|
|
Test IMAP and SMTP connection with provided credentials.
|
|
|
|
Returns:
|
|
AccountTestResult with connection status
|
|
"""
|
|
result = AccountTestResult(
|
|
success=False,
|
|
imap_connected=False,
|
|
smtp_connected=False,
|
|
)
|
|
|
|
# Test IMAP
|
|
imap_ok, imap_err, folders = await self.test_imap_connection(
|
|
imap_host, imap_port, imap_ssl, email_address, password
|
|
)
|
|
result.imap_connected = imap_ok
|
|
if folders:
|
|
result.folders_found = folders
|
|
if imap_err:
|
|
result.error_message = imap_err
|
|
|
|
# Test SMTP
|
|
smtp_ok, smtp_err = await self.test_smtp_connection(
|
|
smtp_host, smtp_port, smtp_ssl, email_address, password
|
|
)
|
|
result.smtp_connected = smtp_ok
|
|
if smtp_err:
|
|
if result.error_message:
|
|
result.error_message += f"; {smtp_err}"
|
|
else:
|
|
result.error_message = smtp_err
|
|
|
|
result.success = result.imap_connected and result.smtp_connected
|
|
return result
|
|
|
|
async def sync_all_accounts(self, user_id: str, tenant_id: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Sync all accounts for a user.
|
|
|
|
Returns:
|
|
Dict with sync results per account
|
|
"""
|
|
async with self._sync_lock:
|
|
accounts = await get_email_accounts(user_id, tenant_id)
|
|
results = {}
|
|
|
|
for account in accounts:
|
|
account_id = account["id"]
|
|
try:
|
|
new_count, total_count = await self.sync_account(
|
|
account_id, user_id, max_emails=50
|
|
)
|
|
results[account_id] = {
|
|
"status": "success",
|
|
"new_emails": new_count,
|
|
"total_emails": total_count,
|
|
}
|
|
except Exception as e:
|
|
results[account_id] = {
|
|
"status": "error",
|
|
"error": str(e),
|
|
}
|
|
|
|
return results
|
|
|
|
async def get_unified_inbox_emails(
|
|
self,
|
|
user_id: str,
|
|
account_ids: Optional[List[str]] = None,
|
|
categories: Optional[List[str]] = None,
|
|
is_read: Optional[bool] = None,
|
|
is_starred: Optional[bool] = None,
|
|
limit: int = 50,
|
|
offset: int = 0,
|
|
) -> List[Dict]:
|
|
"""
|
|
Get unified inbox with all filters.
|
|
|
|
Returns:
|
|
List of email dictionaries
|
|
"""
|
|
return await get_unified_inbox(
|
|
user_id=user_id,
|
|
account_ids=account_ids,
|
|
categories=categories,
|
|
is_read=is_read,
|
|
is_starred=is_starred,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
|
|
|
|
# Global instance
|
|
_aggregator: Optional[MailAggregator] = None
|
|
|
|
|
|
def get_mail_aggregator() -> MailAggregator:
|
|
"""Get or create the global MailAggregator instance."""
|
|
global _aggregator
|
|
|
|
if _aggregator is None:
|
|
_aggregator = MailAggregator()
|
|
|
|
return _aggregator
|