"""Public contract for the mail plugin. Exposes only the symbols that other builtins plugins need. Currently the only cross-plugin consumer is ai_proactive, which imports the ``Mail`` model for querying recent emails by contact. Importers should use:: from app.plugins.builtins.contracts import get_contract mail = get_contract("mail") if mail: result = await db.execute(select(mail.Mail).where(...)) instead of importing from internal modules directly. """ from __future__ import annotations from typing import Any from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.plugins.builtins.contracts import get_contract_registry from app.plugins.builtins.mail.models import Mail, MailAccount class MailContract: """Public API surface for the mail plugin. Exposes ORM models so that other plugins can query the mail tables without importing from ``mail.models`` directly. """ contract_name = "mail" # ─── models ─── Mail = Mail MailAccount = MailAccount @staticmethod async def dsar_collect( db: AsyncSession, tenant_id: Any, user_id: Any ) -> dict[str, Any]: """GDPR Art. 15: collect mail account data owned by the user.""" mail_accounts = ( await db.execute( select(MailAccount).where( MailAccount.tenant_id == tenant_id, MailAccount.user_id == user_id, ).limit(500) ) ).scalars().all() return { "mail_accounts": [ { "id": str(a.id), "email_address": a.email_address, "display_name": a.display_name, "is_shared": a.is_shared, "is_active": a.is_active, } for a in mail_accounts ] } # ─── Workspace Scopes contribution (Phase N1, #359 pattern) ─── @staticmethod def workspace_scopes() -> list[dict]: """Scope-Dimensionen des mail-Moduls für den Workspace-Editor (N1).""" return [ { "module_key": "mail", "dimensions": [ { "key": "account_ids", "label": "Postfächer", "control": "multiselect", "value_source": { "endpoint": "/api/v1/mail/accounts", "items_path": "", "value_key": "id", "label_key": "email", }, }, ], } ] @classmethod def get_function(cls, name: str): """Return a callable exposed by this contract, or None if absent.""" return getattr(cls, name, None) # ─── self-registration ─── _contract = MailContract() get_contract_registry().register("mail", _contract) __all__ = [ "MailContract", "Mail", ]