Files
beavergram/backend/src/api/routers/policy.py
T

122 lines
3.6 KiB
Python

from typing import Annotated
import asyncpg
from dishka.integrations.fastapi import DishkaRoute, FromDishka
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from utils.policy import overview, repository
from utils.policy.models import (
CaptureToggles,
ChatKind,
ChatMeta,
PolicyCreate,
PolicyRecord,
PolicyRuleView,
PolicyScopeView,
)
from utils.policy.resolver import resolve
router = APIRouter(prefix="/api/policy", tags=["policy"], route_class=DishkaRoute)
POLICY_CHANGED_CHANNEL = "policy_changed"
class EffectiveQuery(BaseModel):
account_id: int
chat_id: int
kind: ChatKind
is_bot: bool = False
is_contact: bool | None = None
@router.get("")
async def list_policies(
pool: FromDishka[asyncpg.Pool], account_id: Annotated[int, Query()]
) -> list[PolicyRecord]:
return await repository.list_policies(pool, account_id)
@router.get("/effective")
async def effective_policy(
pool: FromDishka[asyncpg.Pool], query: Annotated[EffectiveQuery, Query()]
) -> CaptureToggles:
folders = await repository.list_folders(pool, query.account_id)
policies = await repository.load_policy_set(pool, query.account_id)
chat = ChatMeta(
chat_id=query.chat_id,
kind=query.kind,
is_bot=query.is_bot,
is_contact=query.is_contact,
)
return resolve(chat, folders, policies)
@router.get("/overview")
async def policy_overview(
pool: FromDishka[asyncpg.Pool], account_id: Annotated[int, Query()]
) -> list[PolicyRuleView]:
return await overview.list_rules(pool, account_id)
@router.get("/scope")
async def policy_scope(
pool: FromDishka[asyncpg.Pool],
account_id: Annotated[int, Query()],
chat_id: Annotated[int | None, Query()] = None,
folder_id: Annotated[int | None, Query()] = None,
) -> PolicyScopeView:
if chat_id is not None:
return await overview.chat_scope(pool, account_id, chat_id)
scope = (
await overview.folder_scope(pool, account_id, folder_id)
if folder_id is not None
else None
)
if scope is None:
raise HTTPException(status_code=404, detail="scope not found")
return scope
@router.post("", status_code=201)
async def create_policy(
pool: FromDishka[asyncpg.Pool], body: PolicyCreate
) -> PolicyRecord:
record = await repository.create_policy(
pool, body.account_id, body.scope_type, body.scope_id, body
)
await pool.execute(f"NOTIFY {POLICY_CHANGED_CHANNEL}")
return record
@router.get("/{policy_id}")
async def get_policy(pool: FromDishka[asyncpg.Pool], policy_id: int) -> PolicyRecord:
record = await repository.get_policy(pool, policy_id)
if record is None:
raise HTTPException(status_code=404, detail="policy not found")
return record
@router.put("/{policy_id}")
async def update_policy(
pool: FromDishka[asyncpg.Pool],
policy_id: int,
body: CaptureToggles,
account_id: Annotated[int | None, Query()] = None,
) -> PolicyRecord:
if account_id is None:
record = await repository.update_policy(pool, policy_id, body)
else:
record = await repository.override_policy(pool, policy_id, account_id, body)
if record is None:
raise HTTPException(status_code=404, detail="policy not found")
await pool.execute(f"NOTIFY {POLICY_CHANGED_CHANNEL}")
return record
@router.delete("/{policy_id}", status_code=204)
async def delete_policy(pool: FromDishka[asyncpg.Pool], policy_id: int) -> None:
if not await repository.delete_policy(pool, policy_id):
raise HTTPException(status_code=404, detail="policy not found")
await pool.execute(f"NOTIFY {POLICY_CHANGED_CHANNEL}")