decomposer: generate deliverable files for Define the service contract and project architecture for the FastAPI endpoint monitoring service.; Implement the typed monitor CRUD API and concurrency-safe in-memory state according to the service design.; Implement secure on-demand endpoint checks with status updates, latency measurement, robust error handling, and redacted structured logs.; Add operational API endpoints and environment-driven runtime configuration to the monitoring service.; Create automated tests for the monitoring service.; Package the service with Docker and developer documentation.; Validate the complete project.
Some checks failed
ci / validate (push) Has been cancelled

This commit is contained in:
2026-08-09 15:52:27 +00:00
parent 33892321d9
commit 625ad314e5

View File

@@ -0,0 +1,96 @@
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)