Release v0.1.2
This commit is contained in:
@@ -3,9 +3,9 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from govoplan_mail.backend.config import ImapConfig, SmtpConfig
|
||||
from govoplan_mail.backend.config import ImapConfig, ImapServerConfig, SmtpConfig, SmtpServerConfig, TransportCredentials
|
||||
|
||||
|
||||
class MailSmtpTestRequest(SmtpConfig):
|
||||
@@ -15,7 +15,50 @@ class MailSmtpTestRequest(SmtpConfig):
|
||||
class MailImapTestRequest(ImapConfig):
|
||||
"""IMAP settings supplied directly from the WebUI mail settings form."""
|
||||
|
||||
enabled: bool = True
|
||||
|
||||
class MailTransportCredentialsPayload(TransportCredentials):
|
||||
"""Credentials supplied separately from saved server settings."""
|
||||
|
||||
|
||||
class MailServerProfileCredentialsPayload(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
smtp: MailTransportCredentialsPayload = Field(default_factory=MailTransportCredentialsPayload)
|
||||
imap: MailTransportCredentialsPayload = Field(default_factory=MailTransportCredentialsPayload)
|
||||
|
||||
|
||||
def _normalize_profile_transport_payload(value: object) -> object:
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
data = dict(value)
|
||||
credentials = data.get("credentials") if isinstance(data.get("credentials"), dict) else {}
|
||||
credentials = {key: dict(item) for key, item in credentials.items() if isinstance(item, dict)}
|
||||
for protocol in ("smtp", "imap"):
|
||||
transport = data.get(protocol)
|
||||
if not isinstance(transport, dict):
|
||||
continue
|
||||
next_transport = dict(transport)
|
||||
next_credentials = dict(credentials.get(protocol) or {})
|
||||
for field in ("username", "password"):
|
||||
if field in next_transport and field not in next_credentials:
|
||||
next_credentials[field] = next_transport[field]
|
||||
next_transport.pop(field, None)
|
||||
next_transport.pop("enabled", None)
|
||||
data[protocol] = next_transport
|
||||
if next_credentials:
|
||||
credentials[protocol] = next_credentials
|
||||
if credentials:
|
||||
data["credentials"] = credentials
|
||||
return data
|
||||
|
||||
|
||||
def _merge_transport_credentials(server: SmtpServerConfig | ImapServerConfig, credentials: MailTransportCredentialsPayload) -> dict[str, object]:
|
||||
payload = server.model_dump(mode="json")
|
||||
if "username" in credentials.model_fields_set:
|
||||
payload["username"] = credentials.username
|
||||
if "password" in credentials.model_fields_set:
|
||||
payload["password"] = credentials.password
|
||||
return payload
|
||||
|
||||
|
||||
MailProfileScope = Literal["system", "tenant", "user", "group", "campaign"]
|
||||
@@ -25,7 +68,6 @@ class MailCredentialPolicyPayload(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
inherit: bool | None = None
|
||||
allow_override: bool | None = None
|
||||
|
||||
|
||||
class MailProfilePolicyPayload(BaseModel):
|
||||
@@ -39,6 +81,7 @@ class MailProfilePolicyPayload(BaseModel):
|
||||
imap_credentials: MailCredentialPolicyPayload = Field(default_factory=MailCredentialPolicyPayload)
|
||||
whitelist: dict[str, list[str]] = Field(default_factory=dict)
|
||||
blacklist: dict[str, list[str]] = Field(default_factory=dict)
|
||||
allow_lower_level_limits: dict[str, bool] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class MailProfilePolicyUpdateRequest(BaseModel):
|
||||
@@ -52,6 +95,7 @@ class PolicySourceStepResponse(BaseModel):
|
||||
scope_id: str | None = None
|
||||
label: str
|
||||
applied_fields: list[str] = Field(default_factory=list)
|
||||
policy: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class MailProfilePolicyResponse(BaseModel):
|
||||
@@ -73,8 +117,29 @@ class MailServerProfileCreateRequest(BaseModel):
|
||||
is_active: bool = True
|
||||
scope_type: MailProfileScope = "tenant"
|
||||
scope_id: str | None = None
|
||||
smtp: SmtpConfig
|
||||
imap: ImapConfig | None = None
|
||||
smtp: SmtpServerConfig
|
||||
imap: ImapServerConfig | None = None
|
||||
credentials: MailServerProfileCredentialsPayload = Field(default_factory=MailServerProfileCredentialsPayload)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def normalize_legacy_transport_payload(cls, value: object) -> object:
|
||||
return _normalize_profile_transport_payload(value)
|
||||
|
||||
def smtp_config(self) -> SmtpConfig:
|
||||
return SmtpConfig.model_validate(_merge_transport_credentials(self.smtp, self.credentials.smtp))
|
||||
|
||||
def imap_config(self) -> ImapConfig | None:
|
||||
if self.imap is None:
|
||||
return None
|
||||
return ImapConfig.model_validate(_merge_transport_credentials(self.imap, self.credentials.imap))
|
||||
|
||||
def credentials_supplied(self) -> bool:
|
||||
return any(
|
||||
field in credentials.model_fields_set
|
||||
for credentials in (self.credentials.smtp, self.credentials.imap)
|
||||
for field in ("username", "password")
|
||||
)
|
||||
|
||||
|
||||
class MailServerProfileUpdateRequest(BaseModel):
|
||||
@@ -84,10 +149,35 @@ class MailServerProfileUpdateRequest(BaseModel):
|
||||
slug: str | None = Field(default=None, max_length=100)
|
||||
description: str | None = None
|
||||
is_active: bool | None = None
|
||||
smtp: SmtpConfig | None = None
|
||||
imap: ImapConfig | None = None
|
||||
smtp: SmtpServerConfig | None = None
|
||||
imap: ImapServerConfig | None = None
|
||||
credentials: MailServerProfileCredentialsPayload = Field(default_factory=MailServerProfileCredentialsPayload)
|
||||
clear_imap: bool = False
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def normalize_legacy_transport_payload(cls, value: object) -> object:
|
||||
return _normalize_profile_transport_payload(value)
|
||||
|
||||
def smtp_config(self) -> SmtpConfig | None:
|
||||
if self.smtp is None and "smtp" not in self.credentials.model_fields_set:
|
||||
return None
|
||||
server = self.smtp or SmtpServerConfig()
|
||||
return SmtpConfig.model_validate(_merge_transport_credentials(server, self.credentials.smtp))
|
||||
|
||||
def imap_config(self) -> ImapConfig | None:
|
||||
if self.imap is None and "imap" not in self.credentials.model_fields_set:
|
||||
return None
|
||||
server = self.imap or ImapServerConfig()
|
||||
return ImapConfig.model_validate(_merge_transport_credentials(server, self.credentials.imap))
|
||||
|
||||
def credentials_supplied(self) -> bool:
|
||||
return any(
|
||||
field in credentials.model_fields_set
|
||||
for credentials in (self.credentials.smtp, self.credentials.imap)
|
||||
for field in ("username", "password")
|
||||
)
|
||||
|
||||
|
||||
class MailServerProfileResponse(BaseModel):
|
||||
id: str
|
||||
@@ -100,6 +190,7 @@ class MailServerProfileResponse(BaseModel):
|
||||
is_active: bool
|
||||
smtp: dict[str, Any]
|
||||
imap: dict[str, Any] | None = None
|
||||
credentials: dict[str, Any] = Field(default_factory=dict)
|
||||
smtp_password_configured: bool = False
|
||||
imap_password_configured: bool = False
|
||||
created_at: datetime
|
||||
@@ -123,6 +214,8 @@ class MailConnectionTestResponse(BaseModel):
|
||||
class MailImapFolderResponse(BaseModel):
|
||||
name: str
|
||||
flags: list[str] = Field(default_factory=list)
|
||||
message_count: int | None = None
|
||||
unseen_count: int | None = None
|
||||
|
||||
|
||||
class MailImapFolderListResponse(BaseModel):
|
||||
@@ -171,6 +264,8 @@ class MailMailboxMessageListResponse(BaseModel):
|
||||
port: int | None = None
|
||||
security: str | None = None
|
||||
total_count: int = 0
|
||||
offset: int = 0
|
||||
limit: int = 50
|
||||
messages: list[MailMailboxMessageSummaryResponse] = Field(default_factory=list)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user