Files
vidconf/backend/api/livekit_webhook.py

64 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Приёмник webhook-событий LiveKit (без JWT — верификация подписью LiveKit)."""
import logging
from typing import Annotated, Any, cast
from fastapi import APIRouter, Depends, Header, HTTPException, Request, status
from livekit import api
from sqlalchemy import CursorResult
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.ext.asyncio import AsyncSession
from core.config import get_settings
from core.db import get_session
from models.webhook_event import LivekitWebhookEvent
from services.webhook_handlers import WebhookDispatcher
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1/livekit", tags=["livekit"])
@router.post("/webhook")
async def receive_webhook(
request: Request,
session: Annotated[AsyncSession, Depends(get_session)],
authorization: Annotated[str | None, Header()] = None,
) -> dict[str, str]:
"""Принять, верифицировать и обработать webhook-событие LiveKit.
Дедупликация по `event.id`: `INSERT ... ON CONFLICT DO NOTHING` в
`livekit_webhook_events` в одной транзакции с эффектами обработчика —
при конфликте (дубль) эффекты пропускаются, но ответ всё равно 200.
"""
settings = get_settings()
raw_body = await request.body()
receiver = api.WebhookReceiver(
api.TokenVerifier(settings.livekit_api_key, settings.livekit_api_secret)
)
try:
event = receiver.receive(raw_body.decode(), authorization or "")
except Exception as exc: # noqa: BLE001 — SDK кидает generic Exception на невалидную подпись
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid_signature"
) from exc
insert_result = cast(
CursorResult[Any],
await session.execute(
pg_insert(LivekitWebhookEvent)
.values(event_id=event.id, event_type=event.event)
.on_conflict_do_nothing(index_elements=["event_id"])
),
)
if insert_result.rowcount == 0:
# Дубль уже обработанного события — пропускаем эффекты, но отвечаем 200.
await session.commit()
return {"status": "duplicate"}
dispatcher = WebhookDispatcher(session)
await dispatcher.dispatch(event)
await session.commit()
return {"status": "ok"}