from fastapi import APIRouter, Depends, Query from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession from ..cursors import decode, encode from ..db import get_session from ..models import Check from ..schemas import ID_PATTERN, ChecksPage, CheckState from ..timeutil import to_utc router = APIRouter() @router.get("/checks", response_model=ChecksPage) async def list_checks( agent_id: str | None = Query(default=None, max_length=128, pattern=ID_PATTERN), cursor: str | None = Query(default=None, max_length=512), back: bool = Query(default=False), limit: int = Query(default=100, ge=1, le=500), session: AsyncSession = Depends(get_session), ) -> ChecksPage: # PH-review: bidirectional cursor. Natural sort is ASC (agent_id, check_id). if back: stmt = select(Check).order_by(Check.agent_id.desc(), Check.check_id.desc()) else: stmt = select(Check).order_by(Check.agent_id, Check.check_id) if agent_id is not None: stmt = stmt.where(Check.agent_id == agent_id) if cursor is not None: last_agent, last_check = decode(cursor, 2) if back: stmt = stmt.where( or_( Check.agent_id < last_agent, and_(Check.agent_id == last_agent, Check.check_id < last_check), ) ) else: stmt = stmt.where( or_( Check.agent_id > last_agent, and_(Check.agent_id == last_agent, Check.check_id > last_check), ) ) rows = (await session.execute(stmt.limit(limit + 1))).scalars().all() has_more = len(rows) > limit rows = rows[:limit] if back: rows = list(reversed(rows)) next_cursor: str | None = None prev_cursor: str | None = None if rows: if back: next_cursor = encode(rows[-1].agent_id, rows[-1].check_id) if has_more: prev_cursor = encode(rows[0].agent_id, rows[0].check_id) else: if has_more: next_cursor = encode(rows[-1].agent_id, rows[-1].check_id) if cursor is not None: prev_cursor = encode(rows[0].agent_id, rows[0].check_id) items = [ CheckState( agent_id=r.agent_id, check_id=r.check_id, status=r.status, last_observed_at=to_utc(r.last_observed_at), exit_code=r.exit_code, incident_key=r.incident_key, summary=r.summary, ) for r in rows ] return ChecksPage(items=items, next_cursor=next_cursor, prev_cursor=prev_cursor)