from uuid import UUID from fastapi import APIRouter, HTTPException, Request, Response, status from .checker import EndpointChecker, TargetBlockedError from .models import CheckResult, Health, Monitor, MonitorCreate, MonitorUpdate, StatusView from .store import Conflict, MonitorStore, NotFound router = APIRouter() def store(request: Request) -> MonitorStore: return request.app.state.store def checker(request: Request) -> EndpointChecker: return request.app.state.checker @router.get("/healthz", response_model=Health, tags=["operations"]) async def health() -> Health: return Health(status="ok") @router.get("/readyz", response_model=Health, tags=["operations"]) async def ready(request: Request) -> Health: if not hasattr(request.app.state, "store"): raise HTTPException(status_code=503, detail="store unavailable") return Health(status="ready") @router.post("/v1/monitors", response_model=Monitor, status_code=201, tags=["monitors"]) async def create(data: MonitorCreate, request: Request) -> Monitor: try: return await store(request).create(data) except Conflict as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.get("/v1/monitors", response_model=list[Monitor], tags=["monitors"]) async def list_monitors(request: Request) -> list[Monitor]: return await store(request).list() @router.get("/v1/monitors/{monitor_id}", response_model=Monitor, tags=["monitors"]) async def get_monitor(monitor_id: UUID, request: Request) -> Monitor: try: return await store(request).get(monitor_id) except NotFound as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @router.patch("/v1/monitors/{monitor_id}", response_model=Monitor, tags=["monitors"]) async def update_monitor(monitor_id: UUID, data: MonitorUpdate, request: Request) -> Monitor: try: return await store(request).update(monitor_id, data) except NotFound as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except Conflict as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.delete("/v1/monitors/{monitor_id}", status_code=204, tags=["monitors"]) async def delete_monitor(monitor_id: UUID, request: Request) -> Response: try: await store(request).delete(monitor_id) except NotFound as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return Response(status_code=status.HTTP_204_NO_CONTENT) @router.post("/v1/monitors/{monitor_id}/check", response_model=CheckResult, tags=["checks"]) async def check_monitor(monitor_id: UUID, request: Request) -> CheckResult: try: item = await store(request).get(monitor_id) result = await checker(request).check(str(item.url), str(monitor_id)) await store(request).record_check(monitor_id, result) return result except NotFound as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except TargetBlockedError as exc: result = CheckResult(status="error", checked_at=__import__("datetime").datetime.now(__import__("datetime").UTC), latency_ms=0, final_url=None, detail=str(exc)) try: await store(request).record_check(monitor_id, result) except NotFound as missing: raise HTTPException(status_code=404, detail=str(missing)) from missing raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/v1/monitors/{monitor_id}/status", response_model=StatusView, tags=["checks"]) async def monitor_status(monitor_id: UUID, request: Request) -> StatusView: try: item = await store(request).get(monitor_id) except NotFound as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return StatusView(id=item.id, status=item.status, last_check=item.last_check)