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:
2026-07-17 23:20:34 +02:00
co-authored by Claude Opus 4.8
commit a5ec0d21cb
174 changed files with 22239 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
"""Shared async Redis client (sessions, rate limits, pub/sub, arq)."""
from __future__ import annotations
from collections.abc import Awaitable
from typing import Any, cast
import redis.asyncio as aioredis
from app.config import get_settings
def aw(result: Any) -> Awaitable[Any]:
"""Normalize a redis-py ``ResponseT`` (``Awaitable | value``) to an awaitable.
Some redis-py async commands (hash ops, getdel, ...) are typed as a
sync/async union; this keeps call sites awaitable under ``mypy --strict``.
"""
return cast("Awaitable[Any]", result)
_client: aioredis.Redis | None = None
def get_redis() -> aioredis.Redis:
"""Return (creating on first use) the process-wide Redis client."""
global _client
if _client is None:
_client = aioredis.Redis.from_url(
get_settings().redis_url,
encoding="utf-8",
decode_responses=True,
)
return _client
async def close_redis() -> None:
"""Close the Redis client (used on app shutdown)."""
global _client
if _client is not None:
await _client.aclose()
_client = None