feat(mail): define standard IMAP folder mappings
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
from govoplan_core.mail.config import ImapConfig, SmtpConfig, TransportSecurity
|
||||
from govoplan_core.mail.config import (
|
||||
ImapConfig,
|
||||
ImapFolderMappings,
|
||||
SmtpConfig,
|
||||
TransportSecurity,
|
||||
)
|
||||
|
||||
__all__ = ["ImapConfig", "SmtpConfig", "TransportSecurity"]
|
||||
__all__ = ["ImapConfig", "ImapFolderMappings", "SmtpConfig", "TransportSecurity"]
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
|
||||
class StrictModel(BaseModel):
|
||||
@@ -34,21 +34,61 @@ class SmtpServerConfig(StrictModel):
|
||||
return self
|
||||
|
||||
|
||||
class ImapFolderMappings(StrictModel):
|
||||
"""Profile-level names for the standard IMAP mailbox roles."""
|
||||
|
||||
inbox: str | None = None
|
||||
sent: str | None = None
|
||||
drafts: str | None = None
|
||||
trash: str | None = None
|
||||
archive: str | None = None
|
||||
junk: str | None = None
|
||||
|
||||
@field_validator("*", mode="before")
|
||||
@classmethod
|
||||
def normalize_folder_name(cls, value: Any) -> Any:
|
||||
if value is None:
|
||||
return None
|
||||
normalized = str(value).strip()
|
||||
return normalized or None
|
||||
|
||||
|
||||
class ImapServerConfig(StrictModel):
|
||||
host: str | None = None
|
||||
port: int | None = Field(default=None, ge=1, le=65535)
|
||||
security: TransportSecurity = TransportSecurity.TLS
|
||||
sent_folder: str = "auto"
|
||||
folder_mappings: ImapFolderMappings | None = None
|
||||
timeout_seconds: int = Field(default=30, ge=1)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def discard_legacy_enabled(cls, value: Any) -> Any:
|
||||
if isinstance(value, dict) and "enabled" in value:
|
||||
data = dict(value)
|
||||
data.pop("enabled", None)
|
||||
return data
|
||||
return value
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
data = dict(value)
|
||||
data.pop("enabled", None)
|
||||
mappings_value = data.get("folder_mappings")
|
||||
mappings = (
|
||||
mappings_value.model_dump(exclude_none=True)
|
||||
if isinstance(mappings_value, ImapFolderMappings)
|
||||
else dict(mappings_value)
|
||||
if isinstance(mappings_value, dict)
|
||||
else {}
|
||||
)
|
||||
mapped_sent = str(mappings.get("sent") or "").strip()
|
||||
legacy_sent = str(data.get("sent_folder") or "").strip()
|
||||
if mapped_sent:
|
||||
# The typed mapping is canonical when both new and legacy callers
|
||||
# provide a Sent value. Keep the legacy field synchronized for
|
||||
# existing Campaign append consumers.
|
||||
data["sent_folder"] = mapped_sent
|
||||
elif legacy_sent and legacy_sent != "auto":
|
||||
data["sent_folder"] = legacy_sent
|
||||
mappings["sent"] = legacy_sent
|
||||
if mappings:
|
||||
data["folder_mappings"] = mappings
|
||||
return data
|
||||
|
||||
@model_validator(mode="after")
|
||||
def apply_default_port(self) -> "ImapServerConfig":
|
||||
|
||||
Reference in New Issue
Block a user