feat: implement OpenAI-compatible Realtime API with authentication and management features
- Added support for public Realtime API, including new routes for managing API keys and handling WebRTC connections. - Introduced RealtimeApiKey model and associated CRUD operations for admin management of API keys. - Implemented authentication mechanisms for API keys and client secrets. - Enhanced environment configuration with new secrets for Realtime API. - Created OpenAIRealtime session management and event processing for real-time interactions. - Updated schemas and settings to accommodate new features and ensure compatibility with existing systems.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Admin-only management of public Realtime API credentials."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from db.models import RealtimeApiKey
|
||||
from db.session import get_session
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from schemas import RealtimeApiKeyCreate, RealtimeApiKeyCreated, RealtimeApiKeyOut
|
||||
from services.auth import require_admin
|
||||
from services.openai_realtime.auth import create_api_key_value
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/realtime/api-keys",
|
||||
tags=["realtime-api-keys"],
|
||||
dependencies=[Depends(require_admin)],
|
||||
)
|
||||
|
||||
|
||||
def _status(row: RealtimeApiKey) -> str:
|
||||
if row.revoked_at is not None:
|
||||
return "revoked"
|
||||
if row.expires_at is not None and row.expires_at <= datetime.now(UTC):
|
||||
return "expired"
|
||||
return "active"
|
||||
|
||||
|
||||
def _out(row: RealtimeApiKey) -> RealtimeApiKeyOut:
|
||||
return RealtimeApiKeyOut(
|
||||
id=row.id,
|
||||
name=row.name,
|
||||
key_prefix=row.key_prefix,
|
||||
status=_status(row),
|
||||
expires_at=row.expires_at,
|
||||
last_used_at=row.last_used_at,
|
||||
revoked_at=row.revoked_at,
|
||||
created_at=row.created_at,
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=RealtimeApiKeyCreated, status_code=status.HTTP_201_CREATED)
|
||||
async def create_realtime_api_key(
|
||||
body: RealtimeApiKeyCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
key_id, key, prefix, digest = create_api_key_value()
|
||||
row = RealtimeApiKey(
|
||||
id=key_id,
|
||||
name=body.name,
|
||||
key_prefix=prefix,
|
||||
key_hash=digest,
|
||||
expires_at=body.expires_at,
|
||||
)
|
||||
session.add(row)
|
||||
await session.commit()
|
||||
await session.refresh(row)
|
||||
return RealtimeApiKeyCreated(**_out(row).model_dump(), key=key)
|
||||
|
||||
|
||||
@router.get("", response_model=list[RealtimeApiKeyOut])
|
||||
async def list_realtime_api_keys(session: AsyncSession = Depends(get_session)):
|
||||
rows = (
|
||||
await session.execute(
|
||||
select(RealtimeApiKey).order_by(RealtimeApiKey.created_at.desc())
|
||||
)
|
||||
).scalars().all()
|
||||
return [_out(row) for row in rows]
|
||||
|
||||
|
||||
@router.delete("/{key_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def revoke_realtime_api_key(
|
||||
key_id: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
row = await session.get(RealtimeApiKey, key_id)
|
||||
if row is None:
|
||||
raise HTTPException(404, "Realtime API Key 不存在")
|
||||
if row.revoked_at is None:
|
||||
row.revoked_at = datetime.now(UTC)
|
||||
await session.commit()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
Reference in New Issue
Block a user