feat(mail): add governed POP3 legacy import
Module Package Release / publish-packages (push) Successful in 11s
Module Package Release / publish-packages (push) Successful in 11s
This commit is contained in:
@@ -16,6 +16,7 @@ from govoplan_mail.backend.db.models import (
|
||||
MailDeliveryCommand,
|
||||
MailMailboxFolderIndex,
|
||||
MailMailboxMessageIndex,
|
||||
MailPop3Import,
|
||||
MailServerEndpoint,
|
||||
MailServerProfile,
|
||||
)
|
||||
@@ -23,6 +24,7 @@ from govoplan_mail.backend.db.models import (
|
||||
|
||||
SMTP_PROVIDER_ID = "mail.smtp_delivery"
|
||||
IMAP_PROVIDER_ID = "mail.imap_mailbox"
|
||||
POP3_PROVIDER_ID = "mail.pop3_legacy_import"
|
||||
_CURRENT_INDEX_WINDOW = timedelta(minutes=30)
|
||||
|
||||
|
||||
@@ -38,6 +40,41 @@ def imap_provider_states(
|
||||
return _mail_provider_states(context, protocol="imap")
|
||||
|
||||
|
||||
def pop3_provider_states(
|
||||
context: ExternalProviderStateContext,
|
||||
) -> tuple[ExternalProviderRuntimeState, ...]:
|
||||
if not isinstance(context.session, Session):
|
||||
raise RuntimeError("Mail provider state requires a database session.")
|
||||
profiles = _profiles(context)
|
||||
if not profiles:
|
||||
return ()
|
||||
profile_ids = tuple(item.id for item in profiles)
|
||||
endpoints = _endpoints(
|
||||
context.session,
|
||||
profile_ids=profile_ids,
|
||||
protocol="pop3",
|
||||
)
|
||||
endpoints_by_profile: dict[str, list[MailServerEndpoint]] = defaultdict(list)
|
||||
for endpoint in endpoints:
|
||||
endpoints_by_profile[endpoint.profile_id].append(endpoint)
|
||||
metrics = _pop3_metrics(
|
||||
context.session,
|
||||
profile_ids=profile_ids,
|
||||
tenant_id=context.tenant_id,
|
||||
)
|
||||
observed_at = datetime.now(UTC)
|
||||
return tuple(
|
||||
_pop3_state(
|
||||
profile,
|
||||
endpoints=endpoints_by_profile.get(profile.id, []),
|
||||
metrics=metrics.get(profile.id, {}),
|
||||
observed_at=observed_at,
|
||||
)
|
||||
for profile in profiles
|
||||
if endpoints_by_profile.get(profile.id)
|
||||
)
|
||||
|
||||
|
||||
def _mail_provider_states(
|
||||
context: ExternalProviderStateContext,
|
||||
*,
|
||||
@@ -194,6 +231,37 @@ def _imap_metrics(
|
||||
return result
|
||||
|
||||
|
||||
def _pop3_metrics(
|
||||
session: Session,
|
||||
*,
|
||||
profile_ids: tuple[str, ...],
|
||||
tenant_id: str | None,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
result: dict[str, dict[str, Any]] = defaultdict(dict)
|
||||
statement = select(
|
||||
MailPop3Import.profile_id,
|
||||
MailPop3Import.deletion_status,
|
||||
func.count(MailPop3Import.id),
|
||||
func.max(MailPop3Import.imported_at),
|
||||
).where(MailPop3Import.profile_id.in_(profile_ids))
|
||||
if tenant_id is not None:
|
||||
statement = statement.where(MailPop3Import.tenant_id == tenant_id)
|
||||
rows = session.execute(
|
||||
statement.group_by(
|
||||
MailPop3Import.profile_id,
|
||||
MailPop3Import.deletion_status,
|
||||
)
|
||||
)
|
||||
for profile_id, deletion_status, count, last_imported_at in rows:
|
||||
item = result[str(profile_id)]
|
||||
item[f"deletion_{deletion_status}"] = int(count)
|
||||
current = _aware(item.get("last_imported_at"))
|
||||
candidate = _aware(last_imported_at)
|
||||
if candidate is not None and (current is None or candidate > current):
|
||||
item["last_imported_at"] = candidate
|
||||
return result
|
||||
|
||||
|
||||
def _smtp_state(
|
||||
profile: MailServerProfile,
|
||||
*,
|
||||
@@ -321,6 +389,71 @@ def _imap_state(
|
||||
)
|
||||
|
||||
|
||||
def _pop3_state(
|
||||
profile: MailServerProfile,
|
||||
*,
|
||||
endpoints: list[MailServerEndpoint],
|
||||
metrics: dict[str, Any],
|
||||
observed_at: datetime,
|
||||
) -> ExternalProviderRuntimeState:
|
||||
enabled_endpoints = [
|
||||
item
|
||||
for item in endpoints
|
||||
if item.is_active and bool((item.config or {}).get("legacy_import_enabled"))
|
||||
]
|
||||
active = bool(profile.is_active) and bool(enabled_endpoints)
|
||||
failed_deletions = int(metrics.get("deletion_failed", 0))
|
||||
unknown_deletions = int(metrics.get("deletion_outcome_unknown", 0))
|
||||
last_imported_at = _aware(metrics.get("last_imported_at"))
|
||||
health = (
|
||||
"inactive"
|
||||
if not active
|
||||
else "warning"
|
||||
if failed_deletions or unknown_deletions
|
||||
else "healthy"
|
||||
if last_imported_at is not None
|
||||
else "unknown"
|
||||
)
|
||||
return ExternalProviderRuntimeState(
|
||||
provider_id=POP3_PROVIDER_ID,
|
||||
binding_ref=f"mail:profile:{profile.id}:pop3",
|
||||
authority_mode="governance_overlay",
|
||||
observed_at=observed_at,
|
||||
configured=True,
|
||||
active=active,
|
||||
health=health,
|
||||
freshness="not_applicable",
|
||||
conflict="pending" if unknown_deletions else "clear",
|
||||
recovery=(
|
||||
"not_applicable"
|
||||
if not active
|
||||
else "attention"
|
||||
if failed_deletions or unknown_deletions
|
||||
else "ready"
|
||||
),
|
||||
last_success_at=last_imported_at,
|
||||
detail=(
|
||||
"POP3 legacy import is disabled."
|
||||
if not active
|
||||
else "POP3 source deletion evidence requires attention."
|
||||
if failed_deletions or unknown_deletions
|
||||
else "POP3 legacy import is enabled but has no retained import yet."
|
||||
if last_imported_at is None
|
||||
else "POP3 governed import evidence is available."
|
||||
),
|
||||
metrics={
|
||||
"active_endpoints": len(enabled_endpoints),
|
||||
"imports": sum(
|
||||
int(value)
|
||||
for key, value in metrics.items()
|
||||
if key.startswith("deletion_")
|
||||
),
|
||||
"failed_deletions": failed_deletions,
|
||||
"outcome_unknown_deletions": unknown_deletions,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _legacy_configured(profile: MailServerProfile, protocol: str) -> bool:
|
||||
value = profile.smtp_config if protocol == "smtp" else profile.imap_config
|
||||
return isinstance(value, dict) and bool(value)
|
||||
@@ -336,7 +469,9 @@ def _aware(value: object | None) -> datetime | None:
|
||||
|
||||
__all__ = [
|
||||
"IMAP_PROVIDER_ID",
|
||||
"POP3_PROVIDER_ID",
|
||||
"SMTP_PROVIDER_ID",
|
||||
"imap_provider_states",
|
||||
"pop3_provider_states",
|
||||
"smtp_provider_states",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user