Index authorized mailbox cache changes in Search

This commit is contained in:
2026-08-04 03:03:26 +02:00
parent 7844d9cab4
commit 72d6f8432c
5 changed files with 559 additions and 2 deletions
+88 -1
View File
@@ -7,11 +7,17 @@ from threading import Lock
from sqlalchemy import func
from sqlalchemy.orm import Session
from govoplan_core.core.change_sequence import record_change
from govoplan_core.security.time import ensure_aware_utc, utc_now
from govoplan_mail.backend.db.models import MailMailboxFolderIndex, MailMailboxMessageIndex
from govoplan_mail.backend.db.models import (
MailMailboxFolderIndex,
MailMailboxMessageIndex,
new_uuid,
)
from govoplan_mail.backend.sending.imap import ImapFolderListResult, ImapMailboxInfo, ImapMailboxMessageListResult, ImapMailboxMessageSummary
MAILBOX_INDEX_TTL_SECONDS = 30
MAILBOX_MESSAGES_COLLECTION = "mail.mailbox_messages"
_refresh_lock = Lock()
_refreshing_keys: set[tuple[str, str, str]] = set()
@@ -44,6 +50,13 @@ def clear_mailbox_index(session: Session, *, profile_id: str) -> tuple[int, int]
transport change. Message rows are removed before their folder metadata.
"""
messages = (
session.query(MailMailboxMessageIndex)
.filter(MailMailboxMessageIndex.profile_id == profile_id)
.all()
)
for message in messages:
_record_message_change(session, message, operation="deleted")
deleted_messages = (
session.query(MailMailboxMessageIndex)
.filter(MailMailboxMessageIndex.profile_id == profile_id)
@@ -89,6 +102,17 @@ def cache_mailbox_folders(
expected_names = {folder.name for folder in result.folders}
removed_names = set(existing) - expected_names
if removed_names:
removed_messages = (
session.query(MailMailboxMessageIndex)
.filter(
MailMailboxMessageIndex.tenant_id == tenant_id,
MailMailboxMessageIndex.profile_id == profile_id,
MailMailboxMessageIndex.folder.in_(removed_names),
)
.all()
)
for message in removed_messages:
_record_message_change(session, message, operation="deleted")
(
session.query(MailMailboxMessageIndex)
.filter(
@@ -139,6 +163,17 @@ def cache_mailbox_messages(
uids = [message.uid for message in result.messages]
if result.total_count <= 0:
removed_messages = (
session.query(MailMailboxMessageIndex)
.filter(
MailMailboxMessageIndex.tenant_id == tenant_id,
MailMailboxMessageIndex.profile_id == profile_id,
MailMailboxMessageIndex.folder == result.folder,
)
.all()
)
for message in removed_messages:
_record_message_change(session, message, operation="deleted")
(
session.query(MailMailboxMessageIndex)
.filter(
@@ -161,6 +196,7 @@ def cache_mailbox_messages(
if uids:
stale_query = stale_query.filter(MailMailboxMessageIndex.uid.notin_(uids))
for row in stale_query.all():
_record_message_change(session, row, operation="deleted")
session.delete(row)
existing = {}
if uids:
@@ -177,13 +213,16 @@ def cache_mailbox_messages(
}
for index, message in enumerate(result.messages):
row = existing.get(message.uid)
created = row is None
if row is None:
row = MailMailboxMessageIndex(
id=new_uuid(),
tenant_id=tenant_id,
profile_id=profile_id,
folder=result.folder,
uid=message.uid,
)
previous = None if created else _message_state(row)
row.uid_int = _uid_int(message.uid)
row.sort_position = result.offset + index
row.subject = message.subject
@@ -198,6 +237,12 @@ def cache_mailbox_messages(
row.attachment_count = message.attachment_count
row.indexed_at = indexed_at
session.add(row)
if created or previous != _message_state(row):
_record_message_change(
session,
row,
operation="created" if created else "updated",
)
def cached_mailbox_folders(
@@ -308,6 +353,48 @@ def _message_from_index(row: MailMailboxMessageIndex) -> ImapMailboxMessageSumma
)
def _message_state(row: MailMailboxMessageIndex) -> tuple[object, ...]:
return (
row.folder,
row.uid,
row.sort_position,
row.subject,
row.from_header,
row.to_header,
row.cc_header,
row.date,
row.message_id,
tuple(row.flags or ()),
row.size_bytes,
row.body_preview,
row.attachment_count,
)
def _record_message_change(
session: Session,
row: MailMailboxMessageIndex,
*,
operation: str,
) -> None:
record_change(
session,
module_id="mail",
collection=MAILBOX_MESSAGES_COLLECTION,
resource_type="mailbox_message",
resource_id=row.id,
operation=operation,
tenant_id=row.tenant_id,
actor_type="system",
payload={
"profile_id": row.profile_id,
"folder": row.folder,
"uid": row.uid,
"message_id": row.message_id,
},
)
def _uid_int(uid: str) -> int:
try:
return int(str(uid))