"""Cloud provider integration utilities. Adapted for Batch Agent Service: import from shared.config instead of app.config. Provides: * Shared message dataclasses (EmailMessage, ChatMessage) * get_provider() — factory for Gmail/MS Graph clients * encrypt_token() / decrypt_token() — Fernet-based OAuth token encryption """ from __future__ import annotations import json import logging from dataclasses import dataclass, field from datetime import datetime from typing import TYPE_CHECKING from cryptography.fernet import Fernet, InvalidToken from shared.config import settings if TYPE_CHECKING: from app.integrations.gmail import GmailClient from app.integrations.ms_graph import MSGraphClient logger = logging.getLogger(__name__) @dataclass class EmailMessage: id: str subject: str sender: str body_text: str date: datetime labels: list[str] = field(default_factory=list) @property def as_text(self) -> str: date_str = self.date.strftime("%Y-%m-%d %H:%M") labels_str = f" [{', '.join(self.labels)}]" if self.labels else "" return ( f"From: {self.sender}\n" f"Date: {date_str}{labels_str}\n" f"Subject: {self.subject}\n\n" f"{self.body_text}" ) @dataclass class ChatMessage: id: str content: str sender: str channel: str | None date: datetime @property def as_text(self) -> str: date_str = self.date.strftime("%Y-%m-%d %H:%M") channel_str = f" [channel: {self.channel}]" if self.channel else "" return ( f"From: {self.sender}\n" f"Date: {date_str}{channel_str}\n\n" f"{self.content}" ) def _get_fernet() -> Fernet: key = settings.OAUTH_ENCRYPTION_KEY if not key: raise RuntimeError( "OAUTH_ENCRYPTION_KEY is not set. " "Generate one with: python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"" ) return Fernet(key.encode() if isinstance(key, str) else key) def encrypt_token(token_info: dict) -> str: if not isinstance(token_info, dict) or not token_info: raise ValueError("token_info must be a non-empty dict") plaintext = json.dumps(token_info).encode("utf-8") return _get_fernet().encrypt(plaintext).decode("utf-8") def decrypt_token(encrypted: str) -> dict: try: plaintext = _get_fernet().decrypt(encrypted.encode("utf-8")) return json.loads(plaintext) except (InvalidToken, json.JSONDecodeError) as exc: raise ValueError(f"Failed to decrypt OAuth token: {exc}") from exc def get_provider( provider: str, credentials_info: dict, ) -> "GmailClient | MSGraphClient": if provider == "gmail": from app.integrations.gmail import GmailClient return GmailClient(credentials_info) if provider in {"outlook", "teams"}: from app.integrations.ms_graph import MSGraphClient return MSGraphClient(credentials_info) raise ValueError( f"Unknown cloud provider {provider!r}. " "Supported: 'gmail', 'outlook', 'teams'." )