from typing import Annotated from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Request, Response, status from app.checker import EndpointChecker from app.models import CurrentStatus, Monitor, MonitorCreate, MonitorUpdate from app.store import MonitorStore router = APIRouter() def store(request: Request) -> MonitorStore: return request.app.state.store def checker(request: Request) -> EndpointChecker: return request.app.state.checker Store = Annotated[MonitorStore, Depends(store)] Checker = Annotated[EndpointChecker, Depends(checker)] def missing() -> HTTPException: return HTTPException(status_code=404, detail=("monitor_not_found", "Monitor not found")) @router.post("/v1/monitors", response_model=Monitor, status_code=status.HTTP_201_CREATED) async def create_monitor(data: MonitorCreate, state: Store) -> Monitor: return await state.create(data) @router.get("/v1/monitors", response_model=list[Monitor]) async def list_monitors(state: Store) -> list[Monitor]: return await state.list() @router.get("/v1/monitors/{monitor_id}", response_model=Monitor) async def get_monitor(monitor_id: UUID, state: Store) -> Monitor: item = await state.get(monitor_id) if item is None: raise missing() return item @router.patch("/v1/monitors/{monitor_id}", response_model=Monitor) async def update_monitor(monitor_id: UUID, data: MonitorUpdate, state: Store) -> Monitor: item = await state.update(monitor_id, data) if item is None: raise missing() return item @router.delete("/v1/monitors/{monitor_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_monitor(monitor_id: UUID, state: Store) -> Response: if not await state.delete(monitor_id): raise missing() return Response(status_code=status.HTTP_204_NO_CONTENT) @router.post("/v1/monitors/{monitor_id}/check", response_model=CurrentStatus) async def check_monitor(monitor_id: UUID, state: Store, outbound: Checker) -> CurrentStatus: snapshot = await state.snapshot(monitor_id) if snapshot is None: raise missing() item, revision = snapshot result = await outbound.check(str(item.url)) if await state.record_status(monitor_id, revision, result) is None: raise HTTPException(status_code=409, detail=("monitor_changed", "Monitor changed while check was running")) return result @router.get("/v1/monitors/{monitor_id}/status", response_model=CurrentStatus) async def get_status(monitor_id: UUID, state: Store) -> CurrentStatus: item = await state.get(monitor_id) if item is None: raise missing() return item.current_status @router.get("/healthz") async def health() -> dict[str, str]: return {"status": "ok"} @router.get("/readyz") async def readiness(request: Request) -> dict[str, str]: if not hasattr(request.app.state, "store"): raise HTTPException(status_code=503, detail=("not_ready", "Service is not ready")) return {"status": "ready", "storage": "process-local-memory"}