Initial commit: webmail client (M0/M1)
Three-pane webmail client. - Backend: FastAPI + async SQLAlchemy + arq worker (Postgres/Redis), IMAP/SMTP sync, threading, search, drafts/outbox with undo, attachments, 2FA. - Frontend: React 18 + Vite + TypeScript + TanStack Query + Tailwind; three-pane UI with rich-text composer, light/dark themes, resizable panels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
"""Keyset cursor helpers for thread listings (SPEC §9.1: cursor = last_message_at,id)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import datetime as dt
|
||||
import uuid
|
||||
|
||||
|
||||
def encode_cursor(last_message_at: dt.datetime, thread_id: uuid.UUID) -> str:
|
||||
raw = f"{last_message_at.isoformat()}|{thread_id}"
|
||||
return base64.urlsafe_b64encode(raw.encode()).decode()
|
||||
|
||||
|
||||
def decode_cursor(cursor: str | None) -> tuple[dt.datetime, uuid.UUID] | None:
|
||||
if not cursor:
|
||||
return None
|
||||
try:
|
||||
raw = base64.urlsafe_b64decode(cursor.encode()).decode()
|
||||
date_str, id_str = raw.split("|", 1)
|
||||
return dt.datetime.fromisoformat(date_str), uuid.UUID(id_str)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
Reference in New Issue
Block a user