"""Mail Account Service for the Mail plugin. Extracted from services.py as part of the God-object split (BUG-018 pilot). Re-exported by ``app.plugins.builtins.mail.services``. """ from __future__ import annotations import logging import uuid from sqlalchemy.ext.asyncio import AsyncSession from app.plugins.builtins.mail.crypto import encrypt_password, generate_salt from app.plugins.builtins.mail.models import ( MailAccount, MailFolder, ) logger = logging.getLogger(__name__) async def create_mail_account( db: AsyncSession, *, tenant_id: uuid.UUID, user_id: uuid.UUID, data: dict ) -> MailAccount: """Create a new mail account with encrypted password.""" salt = generate_salt() account = MailAccount( tenant_id=tenant_id, user_id=user_id, owner_id=user_id, email_address=data["email_address"], display_name=data.get("display_name", ""), imap_host=data["imap_host"], imap_port=data.get("imap_port", 993), imap_ssl=data.get("imap_ssl", True), smtp_host=data["smtp_host"], smtp_port=data.get("smtp_port", 587), smtp_tls=data.get("smtp_tls", True), username=data.get("username") or data["email_address"], encrypted_password=encrypt_password(data["password"], salt), password_salt=salt, is_shared=data.get("is_shared", False), is_active=True, sent_folder_imap_name=data.get("sent_folder_imap_name"), drafts_folder_imap_name=data.get("drafts_folder_imap_name"), spam_folder_imap_name=data.get("spam_folder_imap_name"), trash_folder_imap_name=data.get("trash_folder_imap_name"), ) db.add(account) await db.flush() # Create INBOX first so subfolders can reference it as parent inbox_folder = MailFolder( tenant_id=tenant_id, account_id=account.id, name="Posteingang", imap_name="INBOX", is_standard=True, ) db.add(inbox_folder) await db.flush() # Create standard subfolders under INBOX (IMAP server uses '.' delimiter) for fname, imap_name in [ ("Gesendet", "INBOX.Sent"), ("Entwürfe", "INBOX.Drafts"), ("Papierkorb", "INBOX.Trash"), ("Spam", "INBOX.spam"), ]: folder = MailFolder( tenant_id=tenant_id, account_id=account.id, name=fname, imap_name=imap_name, parent_id=inbox_folder.id, is_standard=True, ) db.add(folder) await db.flush() return account async def update_mail_account(db: AsyncSession, account: MailAccount, data: dict) -> MailAccount: """Update a mail account, encrypting password if changed.""" field_map = { "email": "email_address", "email_address": "email_address", "display_name": "display_name", "imap_host": "imap_host", "imap_port": "imap_port", "imap_ssl": "imap_ssl", "smtp_host": "smtp_host", "smtp_port": "smtp_port", "smtp_tls": "smtp_tls", "username": "username", "is_shared": "is_shared", "is_active": "is_active", "sent_folder_imap_name": "sent_folder_imap_name", "drafts_folder_imap_name": "drafts_folder_imap_name", "spam_folder_imap_name": "spam_folder_imap_name", "trash_folder_imap_name": "trash_folder_imap_name", } for api_field, model_field in field_map.items(): if api_field in data and data[api_field] is not None: setattr(account, model_field, data[api_field]) if "password" in data and data["password"] is not None: new_salt = generate_salt() account.password_salt = new_salt account.encrypted_password = encrypt_password(data["password"], new_salt) await db.flush() await db.refresh(account) return account