from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from sqlalchemy.orm import selectinload from ..database import get_db from ..models.user import User from ..models.room import Room, RoomParticipant from ..models.track import RoomQueue from ..schemas.room import RoomCreate, RoomResponse, RoomDetailResponse, QueueAdd from ..schemas.track import TrackResponse from ..schemas.user import UserResponse from ..services.auth import get_current_user from ..services.sync import manager from ..config import get_settings settings = get_settings() router = APIRouter(prefix="/api/rooms", tags=["rooms"]) @router.get("", response_model=list[RoomResponse]) async def get_rooms(db: AsyncSession = Depends(get_db)): result = await db.execute( select(Room, func.count(RoomParticipant.user_id).label("participants_count")) .outerjoin(RoomParticipant) .group_by(Room.id) .order_by(Room.created_at.desc()) ) rooms = [] for room, count in result.all(): room_dict = { "id": room.id, "name": room.name, "owner_id": room.owner_id, "current_track_id": room.current_track_id, "playback_position": room.playback_position, "is_playing": room.is_playing, "created_at": room.created_at, "participants_count": count, } rooms.append(RoomResponse(**room_dict)) return rooms @router.post("", response_model=RoomResponse) async def create_room( room_data: RoomCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): room = Room(name=room_data.name, owner_id=current_user.id) db.add(room) await db.flush() return RoomResponse( id=room.id, name=room.name, owner_id=room.owner_id, current_track_id=room.current_track_id, playback_position=room.playback_position, is_playing=room.is_playing, created_at=room.created_at, participants_count=0, ) @router.get("/{room_id}", response_model=RoomDetailResponse) async def get_room(room_id: UUID, db: AsyncSession = Depends(get_db)): result = await db.execute( select(Room) .options( selectinload(Room.owner), selectinload(Room.current_track), selectinload(Room.participants).selectinload(RoomParticipant.user), ) .where(Room.id == room_id) ) room = result.scalar_one_or_none() if not room: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Room not found") return RoomDetailResponse( id=room.id, name=room.name, owner=UserResponse.model_validate(room.owner), current_track=TrackResponse.model_validate(room.current_track) if room.current_track else None, playback_position=room.playback_position, is_playing=room.is_playing, created_at=room.created_at, participants=[UserResponse.model_validate(p.user) for p in room.participants], ) @router.delete("/{room_id}") async def delete_room( room_id: UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute(select(Room).where(Room.id == room_id)) room = result.scalar_one_or_none() if not room: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Room not found") if room.owner_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not room owner") await db.delete(room) return {"status": "deleted"} @router.post("/{room_id}/join") async def join_room( room_id: UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute(select(Room).where(Room.id == room_id)) room = result.scalar_one_or_none() if not room: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Room not found") # Check participant limit result = await db.execute( select(func.count(RoomParticipant.user_id)).where(RoomParticipant.room_id == room_id) ) count = result.scalar() if count >= settings.max_room_participants: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Room is full") # Check if already joined result = await db.execute( select(RoomParticipant).where( RoomParticipant.room_id == room_id, RoomParticipant.user_id == current_user.id, ) ) if result.scalar_one_or_none(): return {"status": "already joined"} participant = RoomParticipant(room_id=room_id, user_id=current_user.id) db.add(participant) # Notify others await manager.broadcast_to_room( room_id, {"type": "user_joined", "user": {"id": str(current_user.id), "username": current_user.username}}, ) return {"status": "joined"} @router.post("/{room_id}/leave") async def leave_room( room_id: UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(RoomParticipant).where( RoomParticipant.room_id == room_id, RoomParticipant.user_id == current_user.id, ) ) participant = result.scalar_one_or_none() if participant: await db.delete(participant) # Notify others await manager.broadcast_to_room( room_id, {"type": "user_left", "user_id": str(current_user.id)}, ) return {"status": "left"} @router.get("/{room_id}/queue", response_model=list[TrackResponse]) async def get_queue(room_id: UUID, db: AsyncSession = Depends(get_db)): result = await db.execute( select(RoomQueue) .options(selectinload(RoomQueue.track)) .where(RoomQueue.room_id == room_id) .order_by(RoomQueue.position) ) queue_items = result.scalars().all() return [TrackResponse.model_validate(item.track) for item in queue_items] @router.post("/{room_id}/queue") async def add_to_queue( room_id: UUID, data: QueueAdd, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): # Get max position result = await db.execute( select(func.max(RoomQueue.position)).where(RoomQueue.room_id == room_id) ) max_pos = result.scalar() or 0 queue_item = RoomQueue( room_id=room_id, track_id=data.track_id, position=max_pos + 1, added_by=current_user.id, ) db.add(queue_item) await db.flush() # Notify others await manager.broadcast_to_room( room_id, {"type": "queue_updated"}, ) return {"status": "added"} @router.delete("/{room_id}/queue/{track_id}") async def remove_from_queue( room_id: UUID, track_id: UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(RoomQueue).where( RoomQueue.room_id == room_id, RoomQueue.track_id == track_id, ) ) queue_item = result.scalar_one_or_none() if queue_item: await db.delete(queue_item) # Notify others await manager.broadcast_to_room( room_id, {"type": "queue_updated"}, ) return {"status": "removed"}