refactor(i-g): BUG-018 Pilot Split Schritt 5 — mail/services.py komplett zur Fassade reduziert (-95%)
services.py: 3087 -> ~170 Zeilen reine Re-Export-Fassade. Alle Implementierung jetzt in 12 Sub-Modulen: accounts/crypto/drafts_sync/imap_ops/imap_sync/pgp/rules_vacation/sanitize/serializers/smtp_send/text_utils/attachments. Fixes waehrend Extraktion: (1) get_account_password async statt sync (brach send/reply/forward), (2) aiosmtplib als Modulattribut fuer Test-Mocks, (3) conftest Mock-Pfad auf imap_sync statt services, (4) test_mail.py SMTP-Mock-Pfade auf smtp_send umgestellt, (5) Fassade fehlende Symbole ergaenzt: MAX_ATTACHMENT_SIZE/_sanitize_filename/imap_create_folder/imap_delete_folder/mail_to_response. Beweis: mail+sig_label_routes 51/51 passed in 106.88s; alle 13 Sub-Module Import-OK; ruff clean; Symbol-Aufloesung MISSING: NONE.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user