Files

129 lines
3.3 KiB
Python
Raw Permalink Normal View History

2026-07-17 23:20:34 +02:00
"""Shared API response models (SPEC §9.1)."""
from __future__ import annotations
import datetime as dt
import uuid
from pydantic import BaseModel
class Address(BaseModel):
name: str = ""
email: str = ""
class ThreadSummary(BaseModel):
id: uuid.UUID
subject: str
snippet: str
participants: list[Address]
# Distinct From addresses of the thread's visible messages, oldest first —
# what the list's sender column shows (recipients excluded).
senders: list[Address] = []
2026-07-17 23:20:34 +02:00
account_ids: list[uuid.UUID]
message_count: int
unread_count: int
has_attachments: bool
pinned: bool
last_message_at: dt.datetime
# Set while the thread is snoozed: when it comes back to the Inbox.
snoozed_until: dt.datetime | None = None
2026-07-17 23:20:34 +02:00
class ThreadListOut(BaseModel):
threads: list[ThreadSummary]
cursor: str | None = None
total: int = 0
2026-07-17 23:20:34 +02:00
class AttachmentOut(BaseModel):
id: uuid.UUID
filename: str
mime_type: str
size_bytes: int
class MessageEnvelope(BaseModel):
id: uuid.UUID
account_id: uuid.UUID
mailbox_id: uuid.UUID
thread_id: uuid.UUID | None
subject: str
from_addr: list[Address]
to_addr: list[Address]
cc_addr: list[Address]
bcc_addr: list[Address]
2026-07-17 23:20:34 +02:00
date: dt.datetime
flags: list[str]
is_unread: bool
is_starred: bool
has_attachments: bool
has_remote_images: bool
# The sender is on the user's trusted list, so remote images load without a prompt.
images_allowed: bool = False
2026-07-19 18:58:01 +02:00
size_bytes: int
2026-07-17 23:20:34 +02:00
snippet: str
# Other accounts this same delivery also landed in (collapsed duplicates).
also_in_account_ids: list[uuid.UUID] = []
# Bulk mail: the Subscriptions group it belongs to, how it can be left
# (one_click | mailto | web), and whether the user already did.
subscription_key: str | None = None
unsubscribe_via: str | None = None
unsubscribed: bool = False
# Its language, once found (the first time it is shown): "" unknown.
content_language: str = ""
2026-07-17 23:20:34 +02:00
class ThreadDetail(BaseModel):
id: uuid.UUID
subject: str
order: str # "asc" | "desc"
pinned: bool
messages: list[MessageEnvelope] # always ASC (SPEC §9.4)
snoozed_until: dt.datetime | None = None
2026-07-17 23:20:34 +02:00
class FlagChangeRequest(BaseModel):
add: list[str] = []
remove: list[str] = []
class MarkReadRequest(BaseModel):
view: str = "unified" # unified | folder:<special> | account:<uuid>
2026-07-17 23:20:34 +02:00
class MoveRequest(BaseModel):
mailbox_id: uuid.UUID
class AccountOut(BaseModel):
id: uuid.UUID
email: str
display_name: str
color: str
state: str
signature: str = ""
sync_window: str = "all"
backfill_total: int = 0
backfill_done: int = 0
# "unreachable" while the mail server has not answered for a while.
sync_error: str | None = None
sync_error_since: str | None = None
# Why the state is what it is (e.g. the server's reply to a refused sign-in).
state_detail: str = ""
# "native": the user's own hosted mailbox — cannot be removed (App. D §D.4).
kind: str = "external"
2026-07-17 23:20:34 +02:00
class SearchThreadHit(BaseModel):
thread: ThreadSummary
highlights: dict[str, str]
class SearchOut(BaseModel):
threads: list[SearchThreadHit]
cursor: str | None = None
# Conversations matching in all (first page only; None where not counted).
total: int | None = None