Files
personal-ai-assistant/backend/app/services/ws_manager.py
dolgolyov.alexei ada7e82961 Phase 5: Notifications — WebSocket, APScheduler, AI tool, health review
Backend:
- Notification model + Alembic migration
- Notification service: CRUD, mark read, unread count, pending scheduled
- WebSocket manager singleton for real-time push
- WebSocket endpoint /ws/notifications with JWT auth via query param
- APScheduler integration: periodic notification sender (every 60s),
  daily proactive health review job (8 AM)
- AI tool: schedule_notification (immediate or scheduled)
- Health review worker: analyzes user memory via Claude, creates
  ai_generated notifications with WebSocket push

Frontend:
- Notification API client + Zustand store
- WebSocket hook with auto-reconnect (exponential backoff)
- Notification bell in header with unread count badge + dropdown
- Notifications page with type badges, mark read, mark all read
- WebSocket initialized in AppLayout for app-wide real-time updates
- Enabled notifications nav in sidebar
- English + Russian translations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:57:25 +03:00

38 lines
1.2 KiB
Python

import json
import uuid
from fastapi import WebSocket
class ConnectionManager:
def __init__(self):
self.active_connections: dict[uuid.UUID, list[WebSocket]] = {}
async def connect(self, user_id: uuid.UUID, websocket: WebSocket):
await websocket.accept()
if user_id not in self.active_connections:
self.active_connections[user_id] = []
self.active_connections[user_id].append(websocket)
def disconnect(self, user_id: uuid.UUID, websocket: WebSocket):
if user_id in self.active_connections:
self.active_connections[user_id] = [
ws for ws in self.active_connections[user_id] if ws != websocket
]
if not self.active_connections[user_id]:
del self.active_connections[user_id]
async def send_to_user(self, user_id: uuid.UUID, data: dict):
connections = self.active_connections.get(user_id, [])
dead = []
for ws in connections:
try:
await ws.send_text(json.dumps(data))
except Exception:
dead.append(ws)
for ws in dead:
self.disconnect(user_id, ws)
manager = ConnectionManager()