import asyncio import json import logging from fastapi import APIRouter from sse_starlette.sse import EventSourceResponse from app.services.note_store import get_note from app.models.enums import NoteStatus logger = logging.getLogger(__name__) router = APIRouter() @router.get("/notes/{note_id}/events") async def note_events(note_id: str): """ Server-Sent Events endpoint: - Push note.status whenever it changes - Close stream when status == ready or error """ async def event_generator(): last_status = None try: while True: note = get_note(note_id) if not note: # note chưa được tạo, chờ await asyncio.sleep(0.5) continue status = note.get("status") # Push only when status changes if status != last_status: yield { "event": "status", "data": json.dumps({ "note_id": note_id, "status": status, }), } last_status = status # Terminal states → close SSE if status in ( NoteStatus.ready, NoteStatus.error, ): break # Avoid hammering Firestore await asyncio.sleep(1.0) except asyncio.CancelledError: # Client disconnected (normal) logger.info( "[notes_events] SSE client disconnected note_id=%s", note_id, ) except Exception as e: logger.exception( "[notes_events] SSE error note_id=%s: %s", note_id, e, ) yield { "event": "error", "data": json.dumps({ "note_id": note_id, "status": "internal_error", }), } return EventSourceResponse( event_generator(), ping=15, # keep-alive, tránh proxy/nginx kill connection )