from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession from ..cursors import decode, decode_datetime, encode from ..db import get_session from ..models import Incident as IncidentModel from ..schemas import Incident, IncidentsPage router = APIRouter() def _to_schema(i: IncidentModel) -> Incident: return Incident( id=str(i.id), incident_key=i.incident_key, state=i.state, severity=i.severity, agent_id=i.agent_id, check_id=i.check_id, opened_at=i.opened_at, resolved_at=i.resolved_at, summary=i.summary, ) @router.get("/incidents", response_model=IncidentsPage) async def list_incidents( state: str | None = Query(default=None), cursor: str | None = Query(default=None, max_length=512), limit: int = Query(default=100, ge=1, le=500), session: AsyncSession = Depends(get_session), ) -> IncidentsPage: if state is not None and state not in ("open", "resolved"): raise HTTPException(status_code=400, detail="invalid state") stmt = select(IncidentModel).order_by(IncidentModel.opened_at.desc(), IncidentModel.id.desc()) if state is not None: stmt = stmt.where(IncidentModel.state == state) if cursor is not None: ts_str, last_id = decode(cursor, 2) ts = decode_datetime(ts_str) stmt = stmt.where( or_( IncidentModel.opened_at < ts, and_(IncidentModel.opened_at == ts, IncidentModel.id < last_id), ) ) rows = (await session.execute(stmt.limit(limit + 1))).scalars().all() next_cursor = None if len(rows) > limit: rows = rows[:limit] next_cursor = encode(rows[-1].opened_at.isoformat(), rows[-1].id) return IncidentsPage(items=[_to_schema(r) for r in rows], next_cursor=next_cursor)