SSE endpoint publishing quest-completed transitions (task 4d7bae00)

The panel needs live transitions, so add ONE in-process publisher
(api/events.py, installed on app.state.publisher in the lifespan) and
a GET /api/v1/panel/events SSE route on the panel router — it inherits
the single require_panel_token check. The complete route now builds
the event through the core builder and publishes it only when the
core's .appended flag says an actual transition happened; a no-op
re-complete publishes nothing. Tests stream from a real uvicorn
instance on an ephemeral port because httpx's ASGITransport buffers
endless SSE bodies.
This commit is contained in:
2026-09-22 23:00:42 -04:00
parent 550a8b3e1e
commit 4e79aebc22
5 changed files with 438 additions and 0 deletions
+5
View File
@@ -39,6 +39,7 @@ from fastapi import FastAPI
from api.config import ApiConfig
from api.database import DatabaseState, make_database
from api.events import TransitionPublisher
from api.routes_panel import router as panel_router
@@ -47,12 +48,16 @@ def _build_lifespan(db_path: str):
Captured as a closure so the app factory can hand the configured
path in without threading it through FastAPI's lifespan signature.
The lifespan also installs the app's ONE in-process transition
publisher on ``app.state.publisher`` (see :mod:`api.events`) so
every route and SSE subscriber shares that single instance.
"""
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
state = make_database(db_path)
app.state.db = state
app.state.publisher = TransitionPublisher()
try:
await state.open_and_migrate()
yield