feat(backend): alerts come enriched and paged with an unseen count and a live alert frame
This commit is contained in:
1 parent
4469938f47
commit
2bb8ca45fb
7 files changed
+682
-65
No files matched your search
@@ -6,55 +6,88 @@ from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from utils.read import watches
|
||||
from utils.read.models import DEFAULT_LIMIT, AlertView, Page, WatchView
|
||||
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 WatchCreate(BaseModel):
|
||||
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
|
||||
params: dict[str, Any] = {}
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class WatchUpdate(BaseModel):
|
||||
params: dict[str, Any] = {}
|
||||
enabled: bool = True
|
||||
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[WatchView]:
|
||||
return await watches.list_watches(pool, account_id)
|
||||
) -> 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) -> WatchView:
|
||||
return await watches.create_watch(
|
||||
pool, body.account_id, body.kind, body.params, enabled=body.enabled
|
||||
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) -> WatchView:
|
||||
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 watch
|
||||
return await _item(pool, watch)
|
||||
|
||||
|
||||
@router.put("/watches/{watch_id}")
|
||||
async def update_watch(
|
||||
pool: FromDishka[asyncpg.Pool], watch_id: int, body: WatchUpdate
|
||||
) -> WatchView:
|
||||
) -> 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, body.params, enabled=body.enabled
|
||||
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 watch
|
||||
return await _item(pool, watch)
|
||||
|
||||
|
||||
@router.delete("/watches/{watch_id}", status_code=204)
|
||||
@@ -68,14 +101,29 @@ async def list_alerts(
|
||||
pool: FromDishka[asyncpg.Pool],
|
||||
account_id: Annotated[int, Query()],
|
||||
seen: Annotated[bool | None, Query()] = None,
|
||||
limit: Annotated[int, Query()] = DEFAULT_LIMIT,
|
||||
offset: Annotated[int, Query()] = 0,
|
||||
) -> list[AlertView]:
|
||||
before: Annotated[int | None, Query()] = None,
|
||||
limit: Annotated[int, Query()] = watches.ALERT_PAGE,
|
||||
) -> AlertPage:
|
||||
return await watches.list_alerts(
|
||||
pool, account_id, Page(limit=limit, offset=offset), seen=seen
|
||||
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):
|
||||
|
||||
Reference in new issue
Block a user