Files
crucible-agent-build-fastap…/src/endpoint_monitor/api.py

97 lines
3.8 KiB
Python

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)