import uuid from fastapi import APIRouter, WebSocket, WebSocketDisconnect from core.websocket import get_ws_current_user from db.session import AsyncSessionLocal from domains.groups.repo import get_group_member from domains.realtime.ws_manager import manager from domains.realtime.presence_service import ( user_join_group, user_leave_group, list_online_users ) from domains.realtime.speaker_service import ( request_speak, stop_speaking, current_speaker ) from integrations.livekit.token_service import generate_join_token router = APIRouter() @router.websocket("/ws/groups/{group_id}") async def group_ws(websocket: WebSocket, group_id: str): user = await get_ws_current_user(websocket) try: group_id_uuid = uuid.UUID(group_id) except ValueError: await websocket.close(code=1008) return async with AsyncSessionLocal() as db: membership = await get_group_member(db, group_id_uuid, user.id) if not membership: await websocket.close(code=1008) return user_id = str(user.id) # connect websocket await manager.connect(group_id, websocket) # add presence await user_join_group(group_id, user_id) # give listener token listener_token = generate_join_token( user_id=user_id, group_id=group_id, can_publish=False ) await websocket.send_json({ "type": "livekit_token", "token": listener_token }) # broadcast updated presence await manager.broadcast( group_id, { "type": "presence", "users": await list_online_users(group_id) } ) try: async with AsyncSessionLocal() as db: while True: data = await websocket.receive_json() event = data.get("type") # user wants to speak if event == "request_speak": token = await request_speak( db, group_id, user_id ) if token: await manager.broadcast( group_id, { "type": "speaker", "user_id": user_id } ) await websocket.send_json({ "type": "speaker_granted", "token": token }) else: speaker = await current_speaker(db, group_id) await websocket.send_json({ "type": "speaker_busy", "speaker": speaker }) # user stops speaking elif event == "stop_speak": await stop_speaking(db, group_id, user_id) await manager.broadcast( group_id, { "type": "speaker_released" } ) except WebSocketDisconnect: manager.disconnect(group_id, websocket) await user_leave_group(group_id, user_id) await manager.broadcast( group_id, { "type": "presence", "users": await list_online_users(group_id) } )