Spaces:
Running
Running
File size: 2,250 Bytes
7d65af7 c846dab 7d65af7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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
)
|