Files
web/backend/services/main/routes/livekit.py
T

101 lines
3.0 KiB
Python

"""
Mint LiveKit participant JWTs for DM calls. Requires LIVEKIT_API_KEY, LIVEKIT_API_SECRET,
and LIVEKIT_URL (WebSocket URL for clients, e.g. wss://livekit.example.com or ws://host:7880).
"""
from __future__ import annotations
import logging
import os
import uuid
from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from ..dependencies import get_current_user, get_db
from ..models import User
logger = logging.getLogger("uvicorn.error")
router = APIRouter()
class LiveKitTokenRequest(BaseModel):
peer_user_id: int = Field(..., description="The other participant (DM peer)")
room_name: str | None = Field(
None,
description="Existing room from an invite; omit to create a new room",
)
class LiveKitTokenResponse(BaseModel):
server_url: str
token: str
room_name: str
def _livekit_env() -> tuple[str, str, str]:
api_key = os.getenv("LIVEKIT_API_KEY", "").strip()
api_secret = os.getenv("LIVEKIT_API_SECRET", "").strip()
server_url = os.getenv("LIVEKIT_URL", "").strip()
if not api_key or not api_secret or not server_url:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="LiveKit is not configured (LIVEKIT_API_KEY / LIVEKIT_API_SECRET / LIVEKIT_URL)",
)
return api_key, api_secret, server_url
@router.post("/token", response_model=LiveKitTokenResponse)
async def create_livekit_token(
body: LiveKitTokenRequest,
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""
Issue a short-lived JWT for joining a 1:1 call room with peer_user_id.
"""
if body.peer_user_id == user.id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="peer_user_id must differ from caller")
peer = db.query(User).filter(User.id == body.peer_user_id).first()
if not peer:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Peer user not found")
api_key, api_secret, server_url = _livekit_env()
if body.room_name and body.room_name.strip():
room_name = body.room_name.strip()
else:
room_name = f"call-{uuid.uuid4().hex}"
try:
from livekit.api import AccessToken, VideoGrants
except ImportError as e:
logger.exception("livekit-api not installed")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="LiveKit SDK unavailable on server",
) from e
grants = VideoGrants(
room_join=True,
room=room_name,
can_publish=True,
can_subscribe=True,
can_publish_data=True,
)
token = (
AccessToken(api_key, api_secret)
.with_identity(str(user.id))
.with_name(user.username or str(user.id))
.with_ttl(timedelta(hours=1))
.with_grants(grants)
)
jwt_token = token.to_jwt()
return LiveKitTokenResponse(server_url=server_url, token=jwt_token, room_name=room_name)