"""Monitors API endpoints.""" from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, Field from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, verify_api_key from app.models.models import Monitor, MonitorResult router = APIRouter() class MonitorCreate(BaseModel): service_id: UUID url: str = Field(..., max_length=500) method: str = Field("GET", pattern=r"^(GET|POST|HEAD)$") expected_status: int = 200 timeout_seconds: int = Field(10, ge=1, le=60) interval_seconds: int = Field(60, ge=30, le=3600) class MonitorUpdate(BaseModel): url: str | None = Field(None, max_length=500) method: str | None = Field(None, pattern=r"^(GET|POST|HEAD)$") expected_status: int | None = None timeout_seconds: int | None = Field(None, ge=1, le=60) interval_seconds: int | None = Field(None, ge=30, le=3600) is_active: bool | None = None def serialize_monitor(m: Monitor) -> dict: return { "id": m.id, "service_id": m.service_id, "url": m.url, "method": m.method, "expected_status": m.expected_status, "timeout_seconds": m.timeout_seconds, "interval_seconds": m.interval_seconds, "is_active": m.is_active, "created_at": m.created_at.isoformat() if m.created_at else None, "updated_at": m.updated_at.isoformat() if m.updated_at else None, } @router.get("/") async def list_monitors(db: AsyncSession = Depends(get_db)): """List all monitors.""" result = await db.execute(select(Monitor)) monitors = result.scalars().all() return [serialize_monitor(m) for m in monitors] @router.post("/", status_code=status.HTTP_201_CREATED) async def create_monitor( data: MonitorCreate, db: AsyncSession = Depends(get_db), api_key: str = Depends(verify_api_key), ): """Create a new monitor.""" monitor = Monitor( service_id=str(data.service_id), url=data.url, method=data.method, expected_status=data.expected_status, timeout_seconds=data.timeout_seconds, interval_seconds=data.interval_seconds, ) db.add(monitor) await db.flush() await db.refresh(monitor) return serialize_monitor(monitor) @router.get("/{monitor_id}") async def get_monitor(monitor_id: UUID, db: AsyncSession = Depends(get_db)): """Get a monitor with recent results.""" result = await db.execute(select(Monitor).where(Monitor.id == str(monitor_id))) monitor = result.scalar_one_or_none() if not monitor: raise HTTPException(status_code=404, detail="Monitor not found") # Query recent results separately results_query = ( select(MonitorResult) .where(MonitorResult.monitor_id == str(monitor_id)) .order_by(MonitorResult.checked_at.desc()) .limit(10) ) results_result = await db.execute(results_query) recent_results = results_result.scalars().all() data = serialize_monitor(monitor) data["recent_results"] = [ { "id": r.id, "status": r.status, "response_time_ms": r.response_time_ms, "status_code": r.status_code, "error_message": r.error_message, "checked_at": r.checked_at.isoformat() if r.checked_at else None, } for r in recent_results ] return data @router.patch("/{monitor_id}") async def update_monitor( monitor_id: UUID, data: MonitorUpdate, db: AsyncSession = Depends(get_db), api_key: str = Depends(verify_api_key), ): """Update a monitor.""" result = await db.execute(select(Monitor).where(Monitor.id == str(monitor_id))) monitor = result.scalar_one_or_none() if not monitor: raise HTTPException(status_code=404, detail="Monitor not found") update_data = data.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(monitor, field, value) await db.flush() await db.refresh(monitor) return serialize_monitor(monitor) @router.delete("/{monitor_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_monitor( monitor_id: UUID, db: AsyncSession = Depends(get_db), api_key: str = Depends(verify_api_key), ): """Delete a monitor.""" result = await db.execute(select(Monitor).where(Monitor.id == str(monitor_id))) monitor = result.scalar_one_or_none() if not monitor: raise HTTPException(status_code=404, detail="Monitor not found") await db.delete(monitor) @router.post("/{monitor_id}/check") async def trigger_check( monitor_id: UUID, db: AsyncSession = Depends(get_db), api_key: str = Depends(verify_api_key), ): """Trigger a manual uptime check for this monitor.""" from app.services.uptime import check_monitor result = await db.execute(select(Monitor).where(Monitor.id == str(monitor_id))) monitor = result.scalar_one_or_none() if not monitor: raise HTTPException(status_code=404, detail="Monitor not found") monitor_result = await check_monitor(monitor, db) return { "status": monitor_result.status, "response_time_ms": monitor_result.response_time_ms, "status_code": monitor_result.status_code, }