Files
webmail-client/backend/app/accounts/schemas.py
T
lukasandClaude Opus 5.5 b88855931e M6 hosted mailboxes (off by default); dark message mode; fixes
Hosted mailboxes (SPEC App. D), behind HOSTED_MAILBOXES (off: unchanged):
- Stalwart mail server, provisioned by `python -m app.hosted.provision`;
  directory read from a Postgres view, app passwords only for mail apps
- native account per user, the worker signs in as master for the user
- raw messages of hosted mailboxes only cached, fetched back on demand
- migrations 0025 (accounts.kind, directory view), 0026 (IMAP ids bigint)

Mail:
- dark message mode switch in the reading pane (Thunderbird's algorithm)
- moves and folder creation quote the folder name ("Deleted Items"):
  swipe-to-delete / Trash no longer bounce back
- thread unread counters: lock before recount, sorted lock order,
  UIDVALIDITY re-import recounts, nightly repair_threads
- sidebar accounts sorted alphabetically, more space above headings

Settings: Security headings renamed; antispam accounts are explicit
(none ticked = none filtered), input widths, Activity hidden.
Plugin bundles served with Cache-Control: no-cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:25:10 +02:00

106 lines
2.4 KiB
Python

"""Request/response models for the accounts API (SPEC §9.1)."""
from __future__ import annotations
import uuid
from typing import Literal
from pydantic import BaseModel, Field
# How much history to import at account setup (SPEC §12 M2).
SyncWindow = Literal["all", "3months", "new"]
class ServerConfigIn(BaseModel):
host: str
port: int = Field(ge=1, le=65535)
tls: str = "ssl" # ssl | starttls
auth: str = "password" # password | oauth2
class DiscoverRequest(BaseModel):
email: str
class HelpBlockOut(BaseModel):
help_id: str
deep_link: str
app_name_hint: str = ""
steps: list[str] = []
docs_url: str = ""
setup_link: str = ""
button_after_step: int = 0
class CandidateOut(BaseModel):
imap: ServerConfigIn
smtp: ServerConfigIn
source: str
oauth_provider: str | None = None
auth_strategy: list[str] = ["password"]
help: HelpBlockOut | None = None
class DiscoveryStepOut(BaseModel):
method: str
status: str
detail: str = ""
class DiscoverResponse(BaseModel):
candidates: list[CandidateOut]
steps: list[DiscoveryStepOut]
# Whether the wizard may offer the Google OAuth fallback (SPEC §6.2.1).
google_oauth_enabled: bool = False
class TestRequest(BaseModel):
imap: ServerConfigIn
smtp: ServerConfigIn
email: str
username: str | None = None # defaults to the email address
password: str = Field(min_length=1)
class TestResponse(BaseModel):
ok: bool = True
class CreateAccountRequest(BaseModel):
email: str
display_name: str = ""
color: str = "#888888"
imap: ServerConfigIn
smtp: ServerConfigIn
username: str | None = None
password: str = Field(min_length=1)
sync_window: SyncWindow = "all"
sync_spam: bool = False
class UpdateAccountRequest(BaseModel):
display_name: str | None = None
color: str | None = None
signature: str | None = None
sync_window: SyncWindow | None = None
# Connection/credential changes require the password (re-validated + re-encrypted).
imap: ServerConfigIn | None = None
smtp: ServerConfigIn | None = None
password: str | None = None
class AccountDetailOut(BaseModel):
id: uuid.UUID
email: str
display_name: str
color: str
state: str
state_detail: str
signature: str
sync_window: str
backfill_total: int
backfill_done: int
imap_config: dict[str, object]
smtp_config: dict[str, object]
kind: str = "external"