131 lines
4.0 KiB
Python
131 lines
4.0 KiB
Python
from typing import Annotated, Any
|
|
|
|
import asyncpg
|
|
from dishka.integrations.fastapi import DishkaRoute, FromDishka
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
|
|
from utils.read import watches
|
|
from utils.read.models import AlertPage, WatchItem, WatchView
|
|
from utils.watch_rules import WatchParamsError, clean_name, normalize_params
|
|
|
|
router = APIRouter(prefix="/api", tags=["watches"], route_class=DishkaRoute)
|
|
|
|
|
|
class WatchUpdate(BaseModel):
|
|
kind: str | None = None
|
|
params: dict[str, Any] = {}
|
|
name: str | None = None
|
|
enabled: bool = True
|
|
|
|
|
|
class WatchCreate(WatchUpdate):
|
|
account_id: int
|
|
kind: str
|
|
|
|
|
|
class SeenAll(BaseModel):
|
|
account_id: int
|
|
up_to: int | None = None
|
|
|
|
|
|
def _params(kind: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
try:
|
|
return normalize_params(kind, params)
|
|
except WatchParamsError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
|
|
|
|
async def _item(pool: asyncpg.Pool, watch: WatchView) -> WatchItem:
|
|
return (await watches.describe_watches(pool, watch.account_id, [watch]))[0]
|
|
|
|
|
|
@router.get("/watches")
|
|
async def list_watches(
|
|
pool: FromDishka[asyncpg.Pool], account_id: Annotated[int, Query()]
|
|
) -> list[WatchItem]:
|
|
rows = await watches.list_watches(pool, account_id)
|
|
return await watches.describe_watches(pool, account_id, rows)
|
|
|
|
|
|
@router.post("/watches", status_code=201)
|
|
async def create_watch(pool: FromDishka[asyncpg.Pool], body: WatchCreate) -> WatchItem:
|
|
watch = await watches.create_watch(
|
|
pool,
|
|
body.account_id,
|
|
body.kind,
|
|
_params(body.kind, body.params),
|
|
name=clean_name(body.name),
|
|
enabled=body.enabled,
|
|
)
|
|
return await _item(pool, watch)
|
|
|
|
|
|
@router.get("/watches/{watch_id}")
|
|
async def get_watch(pool: FromDishka[asyncpg.Pool], watch_id: int) -> WatchItem:
|
|
watch = await watches.get_watch(pool, watch_id)
|
|
if watch is None:
|
|
raise HTTPException(status_code=404, detail="watch not found")
|
|
return await _item(pool, watch)
|
|
|
|
|
|
@router.put("/watches/{watch_id}")
|
|
async def update_watch(
|
|
pool: FromDishka[asyncpg.Pool], watch_id: int, body: WatchUpdate
|
|
) -> WatchItem:
|
|
current = await watches.get_watch(pool, watch_id)
|
|
if current is None:
|
|
raise HTTPException(status_code=404, detail="watch not found")
|
|
kind = body.kind or current.kind
|
|
watch = await watches.update_watch(
|
|
pool,
|
|
watch_id,
|
|
kind,
|
|
_params(kind, body.params),
|
|
name=clean_name(body.name),
|
|
enabled=body.enabled,
|
|
)
|
|
if watch is None:
|
|
raise HTTPException(status_code=404, detail="watch not found")
|
|
return await _item(pool, watch)
|
|
|
|
|
|
@router.delete("/watches/{watch_id}", status_code=204)
|
|
async def delete_watch(pool: FromDishka[asyncpg.Pool], watch_id: int) -> None:
|
|
if not await watches.delete_watch(pool, watch_id):
|
|
raise HTTPException(status_code=404, detail="watch not found")
|
|
|
|
|
|
@router.get("/alerts")
|
|
async def list_alerts(
|
|
pool: FromDishka[asyncpg.Pool],
|
|
account_id: Annotated[int, Query()],
|
|
seen: Annotated[bool | None, Query()] = None,
|
|
before: Annotated[int | None, Query()] = None,
|
|
limit: Annotated[int, Query()] = watches.ALERT_PAGE,
|
|
) -> AlertPage:
|
|
return await watches.list_alerts(
|
|
pool, account_id, seen=seen, before=before, limit=limit
|
|
)
|
|
|
|
|
|
@router.get("/alerts/unseen")
|
|
async def unseen_alerts(
|
|
pool: FromDishka[asyncpg.Pool], account_id: Annotated[int, Query()]
|
|
) -> dict[str, int]:
|
|
return {"unseen": await watches.unseen_count(pool, account_id)}
|
|
|
|
|
|
@router.post("/alerts/seen")
|
|
async def mark_all_seen(
|
|
pool: FromDishka[asyncpg.Pool], body: SeenAll
|
|
) -> dict[str, int]:
|
|
marked = await watches.mark_all_seen(pool, body.account_id, body.up_to)
|
|
return {"marked": marked}
|
|
|
|
|
|
@router.post("/alerts/{alert_id}/seen", status_code=204)
|
|
async def mark_alert_seen(pool: FromDishka[asyncpg.Pool], alert_id: int) -> None:
|
|
if not await watches.mark_alert_seen(pool, alert_id):
|
|
raise HTTPException(status_code=404, detail="alert not found")
|