"""Response serializers for the Mail plugin ORM models. Extracted from services.py as part of the God-object split (BUG-018 pilot). Re-exported by ``app.plugins.builtins.mail.services``. account_to_response NEVER includes passwords — security-critical contract. """ from __future__ import annotations import json from app.plugins.builtins.mail.models import ( Mail, MailAccount, MailAttachment, MailFolder, MailLabel, MailRule, MailSignature, MailTemplate, ) from app.plugins.builtins.mail.text_utils import ( extract_email_addresses, # noqa: F401 — re-exported for backwards compat ) def attachment_to_response(att: MailAttachment) -> dict: """Convert a MailAttachment ORM object to a response dict.""" return { "id": str(att.id), "mail_id": str(att.mail_id), "filename": att.filename, "mime_type": att.mime_type, "size_bytes": att.size_bytes, "size": att.size_bytes, # alias for frontend compatibility "content_id": att.content_id, "is_inline": bool(att.content_id), "dms_file_id": str(att.dms_file_id) if att.dms_file_id else None, } def account_to_response(account: MailAccount) -> dict: """Convert MailAccount to response dict, NEVER including password.""" return { "id": str(account.id), "email": account.email_address, "email_address": account.email_address, "display_name": account.display_name, "imap_host": account.imap_host, "imap_port": account.imap_port, "imap_ssl": account.imap_ssl, "smtp_host": account.smtp_host, "smtp_port": account.smtp_port, "smtp_tls": account.smtp_tls, "username": account.username, "is_shared": account.is_shared, "is_active": account.is_active, "sent_folder_imap_name": account.sent_folder_imap_name, "drafts_folder_imap_name": account.drafts_folder_imap_name, "spam_folder_imap_name": account.spam_folder_imap_name, "trash_folder_imap_name": account.trash_folder_imap_name, "created_at": account.created_at, "updated_at": account.updated_at, } def mail_to_response( mail: Mail, attachments: list[MailAttachment] | None = None, labels: list[MailLabel] | None = None, ) -> dict: """Convert a Mail ORM object to a response dict.""" resp = { "id": str(mail.id), "account_id": str(mail.account_id), "folder_id": str(mail.folder_id), "message_id": mail.message_id, "thread_id": mail.thread_id, "in_reply_to": mail.in_reply_to, "subject": mail.subject, "from_address": mail.from_address, "from_name": mail.from_address.split("<")[0].strip().strip('"') if "<" in mail.from_address else mail.from_address, "to_addresses": extract_email_addresses(mail.to_addresses) if mail.to_addresses else [], "cc_addresses": extract_email_addresses(mail.cc_addresses) if mail.cc_addresses else [], "bcc_addresses": extract_email_addresses(mail.bcc_addresses) if mail.bcc_addresses else [], "body_text": mail.body_text, "body_html": mail.body_html if mail.body_html else None, "body_html_sanitized": mail.body_html_sanitized, "sanitized_html": mail.body_html_sanitized, "date": mail.received_at.isoformat() if mail.received_at else (mail.sent_at.isoformat() if mail.sent_at else None), "is_seen": mail.is_seen, "is_flagged": mail.is_flagged, "flag_type": getattr(mail, 'flag_type', None), "is_draft": mail.is_draft, "is_answered": mail.is_answered, "is_forwarded": mail.is_forwarded, "has_attachments": mail.has_attachments, "size_bytes": mail.size_bytes, "received_at": mail.received_at, "sent_at": mail.sent_at, "contact_id": str(mail.contact_id) if mail.contact_id else None, "attachments": [], "labels": [], } if attachments: resp["attachments"] = [attachment_to_response(a) for a in attachments] if labels: resp["labels"] = [ {"id": str(lbl.id), "name": lbl.name, "color": lbl.color} for lbl in labels ] return resp def folder_to_response(folder: MailFolder) -> dict: """Convert MailFolder to response dict.""" return { "id": str(folder.id), "account_id": str(folder.account_id), "name": folder.name, "imap_name": folder.imap_name, "parent_id": str(folder.parent_id) if folder.parent_id else None, "is_standard": folder.is_standard, "unread_count": folder.unread_count, "total_count": folder.total_count, } def rule_to_response(rule: MailRule) -> dict: """Convert MailRule to response dict.""" return { "id": str(rule.id), "name": rule.name, "account_id": str(rule.account_id) if rule.account_id else None, "priority": rule.priority, "is_active": rule.is_active, "conditions": json.loads(rule.conditions) if rule.conditions else {}, "actions": json.loads(rule.actions) if rule.actions else {}, } def template_to_response(template: MailTemplate) -> dict: """Convert MailTemplate to response dict.""" return { "id": str(template.id), "name": template.name, "subject": template.subject, "body_html": template.body_html, } def signature_to_response(sig: MailSignature) -> dict: """Convert MailSignature to response dict.""" return { "id": str(sig.id), "name": sig.name, "body_html": sig.body_html, "account_id": str(sig.account_id) if sig.account_id else None, "is_default": sig.is_default, } def label_to_response(label: MailLabel) -> dict: """Convert MailLabel to response dict.""" return { "id": str(label.id), "name": label.name, "color": label.color, }