Files
crucible-agent-build-fastap…/app/main.py

97 lines
3.2 KiB
Python

from contextlib import asynccontextmanager
from uuid import UUID
import httpx
from fastapi import FastAPI, HTTPException, Response, status
from .checker import EndpointChecker
from .config import get_settings
from .logging_config import configure_logging
from .models import Monitor, MonitorCreate, MonitorUpdate, ProbeResponse, StatusResponse
from .store import MonitorStore
settings = get_settings()
configure_logging(settings.log_level)
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.store = MonitorStore()
app.state.ready = True
async with httpx.AsyncClient() as client:
app.state.checker = EndpointChecker(client, settings.request_timeout_seconds, settings.max_redirects, settings.max_response_bytes)
yield
app.state.ready = False
app = FastAPI(title=settings.app_name, version="1.0.0", lifespan=lifespan)
def missing() -> HTTPException:
return HTTPException(status_code=404, detail="monitor not found")
@app.post("/api/v1/monitors", response_model=Monitor, status_code=status.HTTP_201_CREATED)
async def create_monitor(data: MonitorCreate) -> Monitor:
return await app.state.store.create(data)
@app.get("/api/v1/monitors", response_model=list[Monitor])
async def list_monitors() -> list[Monitor]:
return await app.state.store.list()
@app.get("/api/v1/monitors/{monitor_id}", response_model=Monitor)
async def get_monitor(monitor_id: UUID) -> Monitor:
item = await app.state.store.get(monitor_id)
if item is None:
raise missing()
return item
@app.put("/api/v1/monitors/{monitor_id}", response_model=Monitor)
async def update_monitor(monitor_id: UUID, data: MonitorUpdate) -> Monitor:
item = await app.state.store.update(monitor_id, data)
if item is None:
raise missing()
return item
@app.delete("/api/v1/monitors/{monitor_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_monitor(monitor_id: UUID) -> Response:
if not await app.state.store.delete(monitor_id):
raise missing()
return Response(status_code=status.HTTP_204_NO_CONTENT)
@app.post("/api/v1/monitors/{monitor_id}/check", response_model=Monitor)
async def check_monitor(monitor_id: UUID) -> Monitor:
item = await app.state.store.get(monitor_id)
if item is None:
raise missing()
result = await app.state.checker.check(str(monitor_id), str(item.url))
updated = await app.state.store.record(monitor_id, result)
if updated is None:
raise missing()
return updated
@app.get("/api/v1/monitors/{monitor_id}/status", response_model=StatusResponse)
async def monitor_status(monitor_id: UUID) -> StatusResponse:
item = await app.state.store.get(monitor_id)
if item is None:
raise missing()
return StatusResponse(id=item.id, status=item.status, checked_at=item.checked_at or item.created_at, latency_ms=item.latency_ms or 0, http_status=item.http_status, error=item.error)
@app.get("/healthz", response_model=ProbeResponse)
async def health() -> ProbeResponse:
return ProbeResponse(status="ok")
@app.get("/readyz", response_model=ProbeResponse)
async def ready() -> ProbeResponse:
if not getattr(app.state, "ready", False):
raise HTTPException(status_code=503, detail="not ready")
return ProbeResponse(status="ready")