Private
Public Access
Splits the WebSocket layer into three pieces so one app instance and many
behave identically: ConnectionManager stays a purely local socket registry;
RoomBroadcaster publishes chat messages to a per-room Redis channel and
every instance (including the publisher) forwards received messages to its
own local sockets via a single psubscribe("room:*") listener started in
main.py's lifespan; Presence is a Redis-backed refcounted hash per room
tracking who's connected across all instances.
Presence replaces the old process-local connected_user_ids check that
Phase 4's offline-push logic used -- without it, a user connected on a
different instance would look offline and get a redundant push. Fixing
this was scoped in beyond the issue's literal ask (message fan-out only)
since it's a real correctness gap in a phase specifically about running
more than one instance; a known limitation (no heartbeat/TTL, so a hard
crash leaks a presence increment) is documented in the README instead of
solved here.
New tests/test_broadcast.py spins up two independent app instances sharing
one Postgres + Redis to prove delivery and presence both actually cross
the Redis boundary, not just work in-process. Manually verified the same
thing against two real uvicorn processes on different ports.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
25 lines
876 B
Python
25 lines
876 B
Python
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
|
|
|
database_url: str
|
|
session_secret: str
|
|
session_https_only: bool = True
|
|
session_max_age_seconds: int = 60 * 60 * 24 * 14
|
|
|
|
# Optional: push notifications are skipped (logged, not an error) if
|
|
# unset, so existing deployments don't have to configure this to keep
|
|
# running. Generate a pair with `python -m app.cli generate-vapid-keys`.
|
|
vapid_public_key: str | None = None
|
|
vapid_private_key: str | None = None
|
|
vapid_subject: str = "mailto:admin@example.com"
|
|
|
|
# Cross-instance WebSocket fan-out + presence (ARCHITECTURE.md phase 5).
|
|
# No credentials in a local-dev default, unlike database_url.
|
|
redis_url: str = "redis://localhost:6379/0"
|
|
|
|
|
|
settings = Settings()
|