Refactor multiplayer room endpoints and add logging
Refactored the multiplayer room creation and user management endpoints in lio.py for improved modularity and validation. Added helper functions for user, room, and playlist validation, and enhanced error handling. In auth.py, added logging to output generated JWT tokens for better traceability.
This commit is contained in:
@@ -212,7 +212,7 @@ async def oauth_token(
|
|||||||
geoip: GeoIPHelper = Depends(get_geoip_helper),
|
geoip: GeoIPHelper = Depends(get_geoip_helper),
|
||||||
):
|
):
|
||||||
scopes = scope.split(" ")
|
scopes = scope.split(" ")
|
||||||
|
|
||||||
client = (
|
client = (
|
||||||
await db.exec(
|
await db.exec(
|
||||||
select(OAuthClient).where(
|
select(OAuthClient).where(
|
||||||
@@ -364,7 +364,8 @@ async def oauth_token(
|
|||||||
refresh_token_str,
|
refresh_token_str,
|
||||||
settings.access_token_expire_minutes * 60,
|
settings.access_token_expire_minutes * 60,
|
||||||
)
|
)
|
||||||
|
# 打印jwt
|
||||||
|
logger.info(f"[Auth] Generated JWT for user {user_id}: {access_token}")
|
||||||
return TokenResponse(
|
return TokenResponse(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
token_type="Bearer",
|
token_type="Bearer",
|
||||||
@@ -416,7 +417,8 @@ async def oauth_token(
|
|||||||
new_refresh_token,
|
new_refresh_token,
|
||||||
settings.access_token_expire_minutes * 60,
|
settings.access_token_expire_minutes * 60,
|
||||||
)
|
)
|
||||||
|
# 打印jwt
|
||||||
|
logger.info(f"[Auth] Generated JWT for user {token_record.user_id}: {access_token}")
|
||||||
return TokenResponse(
|
return TokenResponse(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
token_type="Bearer",
|
token_type="Bearer",
|
||||||
@@ -482,6 +484,9 @@ async def oauth_token(
|
|||||||
settings.access_token_expire_minutes * 60,
|
settings.access_token_expire_minutes * 60,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 打印jwt
|
||||||
|
logger.info(f"[Auth] Generated JWT for user {user_id}: {access_token}")
|
||||||
|
|
||||||
return TokenResponse(
|
return TokenResponse(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
token_type="Bearer",
|
token_type="Bearer",
|
||||||
@@ -525,6 +530,9 @@ async def oauth_token(
|
|||||||
settings.access_token_expire_minutes * 60,
|
settings.access_token_expire_minutes * 60,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 打印生成的令牌
|
||||||
|
logger.info(f"[Auth] Generated access token for client {client_id}: {access_token}")
|
||||||
|
|
||||||
return TokenResponse(
|
return TokenResponse(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
token_type="Bearer",
|
token_type="Bearer",
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
"""LIO (Legacy IO) router for osu-server-spectator compatibility."""
|
"""LIO (Legacy IO) router for osu-server-spectator compatibility."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import json
|
import json
|
||||||
import time
|
from typing import Any, Dict, List
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Request, status
|
from fastapi import APIRouter, HTTPException, Request, status
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlmodel import col
|
from sqlmodel import col, select
|
||||||
|
|
||||||
|
from app.database.lazer_user import User
|
||||||
|
from app.database.playlists import Playlist as DBPlaylist
|
||||||
|
from app.database.room import Room
|
||||||
|
from app.database.room_participated_user import RoomParticipatedUser
|
||||||
from app.dependencies.database import Database
|
from app.dependencies.database import Database
|
||||||
|
from app.models.multiplayer_hub import PlaylistItem as HubPlaylistItem
|
||||||
|
from app.models.room import MatchType, QueueMode, RoomStatus
|
||||||
from app.utils import utcnow
|
from app.utils import utcnow
|
||||||
|
|
||||||
router = APIRouter(prefix="/_lio", tags=["LIO"])
|
router = APIRouter(prefix="/_lio", tags=["LIO"])
|
||||||
@@ -24,23 +27,270 @@ class RoomCreateRequest(BaseModel):
|
|||||||
password: str | None = None
|
password: str | None = None
|
||||||
match_type: str = "HeadToHead"
|
match_type: str = "HeadToHead"
|
||||||
queue_mode: str = "HostOnly"
|
queue_mode: str = "HostOnly"
|
||||||
|
initial_playlist: List[Dict[str, Any]] = []
|
||||||
|
playlist: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
|
||||||
def verify_request_signature(request: Request, timestamp: str, body: bytes) -> bool:
|
def verify_request_signature(request: Request, timestamp: str, body: bytes) -> bool:
|
||||||
"""Verify HMAC signature for shared interop requests."""
|
"""
|
||||||
# For now, skip signature verification in development
|
Verify HMAC signature for shared interop requests.
|
||||||
# In production, you should implement proper HMAC verification
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI request object
|
||||||
|
timestamp: Request timestamp
|
||||||
|
body: Request body bytes
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if signature is valid
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Currently skips verification in development.
|
||||||
|
In production, implement proper HMAC verification.
|
||||||
|
"""
|
||||||
|
# TODO: Implement proper HMAC verification for production
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _validate_user_exists(db: Database, user_id: int) -> User:
|
||||||
|
"""Validate that a user exists in the database."""
|
||||||
|
user_result = await db.execute(select(User).where(User.id == user_id))
|
||||||
|
user = user_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"User with ID {user_id} not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_room_enums(match_type: str, queue_mode: str) -> tuple[MatchType, QueueMode]:
|
||||||
|
"""Parse and validate room type enums."""
|
||||||
|
try:
|
||||||
|
match_type_enum = MatchType(match_type.lower())
|
||||||
|
except ValueError:
|
||||||
|
match_type_enum = MatchType.HEAD_TO_HEAD
|
||||||
|
|
||||||
|
try:
|
||||||
|
queue_mode_enum = QueueMode(queue_mode.lower())
|
||||||
|
except ValueError:
|
||||||
|
queue_mode_enum = QueueMode.HOST_ONLY
|
||||||
|
|
||||||
|
return match_type_enum, queue_mode_enum
|
||||||
|
|
||||||
|
|
||||||
|
def _coerce_playlist_item(item_data: Dict[str, Any], default_order: int, host_user_id: int) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Normalize playlist item data with default values.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_data: Raw playlist item data
|
||||||
|
default_order: Default playlist order
|
||||||
|
host_user_id: Host user ID for default owner
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with normalized playlist item data
|
||||||
|
"""
|
||||||
|
# Use host_user_id if owner_id is 0 or not provided
|
||||||
|
owner_id = item_data.get("owner_id", host_user_id)
|
||||||
|
if owner_id == 0:
|
||||||
|
owner_id = host_user_id
|
||||||
|
|
||||||
|
return {
|
||||||
|
"owner_id": owner_id,
|
||||||
|
"ruleset_id": item_data.get("ruleset_id", 0),
|
||||||
|
"beatmap_id": item_data.get("beatmap_id"),
|
||||||
|
"required_mods": item_data.get("required_mods", []),
|
||||||
|
"allowed_mods": item_data.get("allowed_mods", []),
|
||||||
|
"expired": bool(item_data.get("expired", False)),
|
||||||
|
"playlist_order": item_data.get("playlist_order", default_order),
|
||||||
|
"played_at": item_data.get("played_at", None),
|
||||||
|
"freestyle": bool(item_data.get("freestyle", True)),
|
||||||
|
"beatmap_checksum": item_data.get("beatmap_checksum", ""),
|
||||||
|
"star_rating": item_data.get("star_rating", 0.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_playlist_items(items: List[Dict[str, Any]]) -> None:
|
||||||
|
"""Validate playlist items data."""
|
||||||
|
if not items:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="At least one playlist item is required to create a room"
|
||||||
|
)
|
||||||
|
|
||||||
|
for idx, item in enumerate(items):
|
||||||
|
if item["beatmap_id"] is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"Playlist item at index {idx} missing beatmap_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
ruleset_id = item["ruleset_id"]
|
||||||
|
if not isinstance(ruleset_id, int) or not (0 <= ruleset_id <= 3):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"Playlist item at index {idx} has invalid ruleset_id {ruleset_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _create_room(db: Database, room_data: Dict[str, Any]) -> tuple[Room, int]:
|
||||||
|
"""Create a new multiplayer room."""
|
||||||
|
host_user_id = room_data.get("user_id")
|
||||||
|
room_name = room_data.get("name", "Unnamed Room")
|
||||||
|
password = room_data.get("password")
|
||||||
|
match_type = room_data.get("match_type", "HeadToHead")
|
||||||
|
queue_mode = room_data.get("queue_mode", "HostOnly")
|
||||||
|
|
||||||
|
if not host_user_id or not isinstance(host_user_id, int):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Missing or invalid user_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate host user exists
|
||||||
|
await _validate_user_exists(db, host_user_id)
|
||||||
|
|
||||||
|
# Parse room type enums
|
||||||
|
match_type_enum, queue_mode_enum = _parse_room_enums(match_type, queue_mode)
|
||||||
|
|
||||||
|
# Create room
|
||||||
|
room = Room(
|
||||||
|
name=room_name,
|
||||||
|
host_id=host_user_id,
|
||||||
|
password=password if password else None,
|
||||||
|
type=match_type_enum,
|
||||||
|
queue_mode=queue_mode_enum,
|
||||||
|
status=RoomStatus.IDLE,
|
||||||
|
participant_count=1,
|
||||||
|
auto_skip=False,
|
||||||
|
auto_start_duration=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(room)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(room)
|
||||||
|
|
||||||
|
return room, host_user_id
|
||||||
|
|
||||||
|
|
||||||
|
async def _add_playlist_items(db: Database, room_id: int, room_data: Dict[str, Any], host_user_id: int) -> None:
|
||||||
|
"""Add playlist items to the room."""
|
||||||
|
initial_playlist = room_data.get("initial_playlist", [])
|
||||||
|
legacy_playlist = room_data.get("playlist", [])
|
||||||
|
|
||||||
|
items_raw: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
# Process initial playlist
|
||||||
|
for i, item in enumerate(initial_playlist):
|
||||||
|
if hasattr(item, "dict"):
|
||||||
|
item = item.dict()
|
||||||
|
items_raw.append(_coerce_playlist_item(item, i, host_user_id))
|
||||||
|
|
||||||
|
# Process legacy playlist
|
||||||
|
start_index = len(items_raw)
|
||||||
|
for j, item in enumerate(legacy_playlist, start=start_index):
|
||||||
|
items_raw.append(_coerce_playlist_item(item, j, host_user_id))
|
||||||
|
|
||||||
|
# Validate playlist items
|
||||||
|
_validate_playlist_items(items_raw)
|
||||||
|
|
||||||
|
# Insert playlist items
|
||||||
|
for item_data in items_raw:
|
||||||
|
hub_item = HubPlaylistItem(
|
||||||
|
id=-1, # Placeholder, will be assigned by add_to_db
|
||||||
|
owner_id=item_data["owner_id"],
|
||||||
|
ruleset_id=item_data["ruleset_id"],
|
||||||
|
expired=item_data["expired"],
|
||||||
|
playlist_order=item_data["playlist_order"],
|
||||||
|
played_at=item_data["played_at"],
|
||||||
|
allowed_mods=item_data["allowed_mods"],
|
||||||
|
required_mods=item_data["required_mods"],
|
||||||
|
beatmap_id=item_data["beatmap_id"],
|
||||||
|
freestyle=item_data["freestyle"],
|
||||||
|
beatmap_checksum=item_data["beatmap_checksum"],
|
||||||
|
star_rating=item_data["star_rating"],
|
||||||
|
)
|
||||||
|
await DBPlaylist.add_to_db(hub_item, room_id=room_id, session=db)
|
||||||
|
|
||||||
|
|
||||||
|
async def _add_host_as_participant(db: Database, room_id: int, host_user_id: int) -> None:
|
||||||
|
"""Add the host as a room participant and update participant count."""
|
||||||
|
participant = RoomParticipatedUser(room_id=room_id, user_id=host_user_id)
|
||||||
|
db.add(participant)
|
||||||
|
|
||||||
|
await _update_room_participant_count(db, room_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def _update_room_participant_count(db: Database, room_id: int) -> None:
|
||||||
|
"""Update the participant count for a room."""
|
||||||
|
# Count active participants
|
||||||
|
active_participants = await db.execute(
|
||||||
|
select(RoomParticipatedUser).where(
|
||||||
|
RoomParticipatedUser.room_id == room_id,
|
||||||
|
col(RoomParticipatedUser.left_at).is_(None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
count = len(active_participants.all())
|
||||||
|
|
||||||
|
# Update room participant count
|
||||||
|
room_result = await db.execute(select(Room).where(Room.id == room_id))
|
||||||
|
room_obj = room_result.scalar_one_or_none()
|
||||||
|
if room_obj:
|
||||||
|
room_obj.participant_count = count
|
||||||
|
|
||||||
|
|
||||||
|
async def _verify_room_password(db: Database, room_id: int, provided_password: str | None) -> None:
|
||||||
|
"""Verify room password if required."""
|
||||||
|
room_result = await db.execute(
|
||||||
|
select(Room.password).where(Room.id == room_id)
|
||||||
|
)
|
||||||
|
room_password = room_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if room_password is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Room not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
if room_password and provided_password != room_password:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Invalid password"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _add_or_update_participant(db: Database, room_id: int, user_id: int) -> None:
|
||||||
|
"""Add a user as participant or update existing participation."""
|
||||||
|
existing_result = await db.execute(
|
||||||
|
select(RoomParticipatedUser).where(
|
||||||
|
RoomParticipatedUser.room_id == room_id,
|
||||||
|
RoomParticipatedUser.user_id == user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing = existing_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
# Rejoin room
|
||||||
|
existing.left_at = None
|
||||||
|
existing.joined_at = utcnow()
|
||||||
|
else:
|
||||||
|
# New participant
|
||||||
|
participant = RoomParticipatedUser(room_id=room_id, user_id=user_id)
|
||||||
|
db.add(participant)
|
||||||
|
|
||||||
|
|
||||||
|
# ===== API ENDPOINTS =====
|
||||||
|
|
||||||
@router.post("/multiplayer/rooms")
|
@router.post("/multiplayer/rooms")
|
||||||
async def create_multiplayer_room(
|
async def create_multiplayer_room(
|
||||||
request: Request,
|
request: Request,
|
||||||
room_data: dict[str, Any],
|
room_data: Dict[str, Any],
|
||||||
db: Database,
|
db: Database,
|
||||||
timestamp: str = "",
|
timestamp: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> int:
|
||||||
"""Create a new multiplayer room."""
|
"""Create a new multiplayer room with initial playlist."""
|
||||||
try:
|
try:
|
||||||
# Verify request signature
|
# Verify request signature
|
||||||
body = await request.body()
|
body = await request.body()
|
||||||
@@ -50,90 +300,39 @@ async def create_multiplayer_room(
|
|||||||
detail="Invalid request signature"
|
detail="Invalid request signature"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse room data
|
# Parse room data if string
|
||||||
if isinstance(room_data, str):
|
if isinstance(room_data, str):
|
||||||
room_data = json.loads(room_data)
|
room_data = json.loads(room_data)
|
||||||
|
|
||||||
# Extract required fields
|
print(f"Creating room with data: {room_data}")
|
||||||
host_user_id = room_data.get("user_id")
|
|
||||||
room_name = room_data.get("name", "Unnamed Room")
|
|
||||||
password = room_data.get("password")
|
|
||||||
match_type = room_data.get("match_type", "HeadToHead")
|
|
||||||
queue_mode = room_data.get("queue_mode", "HostOnly")
|
|
||||||
|
|
||||||
if not host_user_id:
|
# Create room
|
||||||
raise HTTPException(
|
room, host_user_id = await _create_room(db, room_data)
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail="Missing user_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify that the host user exists
|
|
||||||
from app.database.lazer_user import User
|
|
||||||
from sqlmodel import select
|
|
||||||
|
|
||||||
user_result = await db.execute(
|
|
||||||
select(User).where(User.id == host_user_id)
|
|
||||||
)
|
|
||||||
host_user = user_result.first()
|
|
||||||
|
|
||||||
if not host_user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"User with ID {host_user_id} not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create room in database using SQLModel
|
|
||||||
from app.database.room import Room
|
|
||||||
from app.models.room import MatchType, QueueMode, RoomStatus
|
|
||||||
|
|
||||||
# Convert string values to enums
|
|
||||||
try:
|
|
||||||
match_type_enum = MatchType(match_type.lower())
|
|
||||||
except ValueError:
|
|
||||||
match_type_enum = MatchType.HEAD_TO_HEAD
|
|
||||||
|
|
||||||
try:
|
|
||||||
queue_mode_enum = QueueMode(queue_mode.lower())
|
|
||||||
except ValueError:
|
|
||||||
queue_mode_enum = QueueMode.HOST_ONLY
|
|
||||||
|
|
||||||
# Create new room
|
|
||||||
room = Room(
|
|
||||||
name=room_name,
|
|
||||||
host_id=host_user_id,
|
|
||||||
password=password if password else None,
|
|
||||||
type=match_type_enum,
|
|
||||||
queue_mode=queue_mode_enum,
|
|
||||||
status=RoomStatus.IDLE,
|
|
||||||
participant_count=1,
|
|
||||||
auto_skip=False,
|
|
||||||
auto_start_duration=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
db.add(room)
|
|
||||||
await db.commit()
|
|
||||||
await db.refresh(room)
|
|
||||||
|
|
||||||
room_id = room.id
|
room_id = room.id
|
||||||
|
|
||||||
# Add host as participant
|
try:
|
||||||
from app.database.room_participated_user import RoomParticipatedUser
|
# Add playlist items
|
||||||
|
await _add_playlist_items(db, room_id, room_data, host_user_id)
|
||||||
participant = RoomParticipatedUser(
|
|
||||||
room_id=room_id,
|
# Add host as participant
|
||||||
user_id=host_user_id,
|
await _add_host_as_participant(db, room_id, host_user_id)
|
||||||
)
|
|
||||||
|
await db.commit()
|
||||||
db.add(participant)
|
return room_id
|
||||||
await db.commit()
|
|
||||||
|
except HTTPException:
|
||||||
return {"room_id": str(room_id)}
|
# Clean up room if playlist creation fails
|
||||||
|
await db.delete(room)
|
||||||
|
await db.commit()
|
||||||
|
raise
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=f"Invalid JSON: {str(e)}"
|
detail=f"Invalid JSON: {str(e)}"
|
||||||
)
|
)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
@@ -147,9 +346,9 @@ async def add_user_to_room(
|
|||||||
room_id: int,
|
room_id: int,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
db: Database,
|
db: Database,
|
||||||
user_data: dict[str, Any] | None = None,
|
user_data: Dict[str, Any] | None = None,
|
||||||
timestamp: str = "",
|
timestamp: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Add a user to a multiplayer room."""
|
"""Add a user to a multiplayer room."""
|
||||||
try:
|
try:
|
||||||
# Verify request signature
|
# Verify request signature
|
||||||
@@ -160,78 +359,21 @@ async def add_user_to_room(
|
|||||||
detail="Invalid request signature"
|
detail="Invalid request signature"
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.database.room import Room
|
# Verify room password if provided
|
||||||
from sqlmodel import select
|
provided_password = user_data.get("password") if user_data else None
|
||||||
|
await _verify_room_password(db, room_id, provided_password)
|
||||||
|
|
||||||
# Check if room exists
|
# Add or update participant
|
||||||
result = await db.execute(
|
await _add_or_update_participant(db, room_id, user_id)
|
||||||
select(Room.password, Room.participant_count).where(Room.id == room_id)
|
|
||||||
)
|
|
||||||
room_data = result.first()
|
|
||||||
|
|
||||||
if not room_data:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail="Room not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
password, participant_count = room_data
|
|
||||||
|
|
||||||
# Check password if room is password protected
|
|
||||||
if password and user_data:
|
|
||||||
provided_password = user_data.get("password")
|
|
||||||
if provided_password != password:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail="Invalid password"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add user to room (or update existing participation)
|
|
||||||
from app.database.room_participated_user import RoomParticipatedUser
|
|
||||||
from sqlmodel import select
|
|
||||||
|
|
||||||
# Check if user already participated
|
|
||||||
existing_participation = await db.execute(
|
|
||||||
select(RoomParticipatedUser).where(
|
|
||||||
RoomParticipatedUser.room_id == room_id,
|
|
||||||
RoomParticipatedUser.user_id == user_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
existing = existing_participation.first()
|
|
||||||
|
|
||||||
if existing:
|
|
||||||
# Update existing participation
|
|
||||||
existing.left_at = None
|
|
||||||
existing.joined_at = utcnow()
|
|
||||||
else:
|
|
||||||
# Create new participation
|
|
||||||
participant = RoomParticipatedUser(
|
|
||||||
room_id=room_id,
|
|
||||||
user_id=user_id,
|
|
||||||
)
|
|
||||||
db.add(participant)
|
|
||||||
|
|
||||||
# Update participant count
|
# Update participant count
|
||||||
active_count = await db.execute(
|
await _update_room_participant_count(db, room_id)
|
||||||
select(RoomParticipatedUser).where(
|
|
||||||
RoomParticipatedUser.room_id == room_id,
|
|
||||||
col(RoomParticipatedUser.left_at).is_(None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
count = len(active_count.all())
|
|
||||||
|
|
||||||
# Update room participant count
|
|
||||||
room_update = await db.execute(
|
|
||||||
select(Room).where(Room.id == room_id)
|
|
||||||
)
|
|
||||||
room_obj = room_update.first()
|
|
||||||
if room_obj:
|
|
||||||
room_obj.participant_count = count
|
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
@@ -246,7 +388,7 @@ async def remove_user_from_room(
|
|||||||
user_id: int,
|
user_id: int,
|
||||||
db: Database,
|
db: Database,
|
||||||
timestamp: str = "",
|
timestamp: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Remove a user from a multiplayer room."""
|
"""Remove a user from a multiplayer room."""
|
||||||
try:
|
try:
|
||||||
# Verify request signature
|
# Verify request signature
|
||||||
@@ -257,11 +399,7 @@ async def remove_user_from_room(
|
|||||||
detail="Invalid request signature"
|
detail="Invalid request signature"
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.database.room import Room
|
# Mark user as left
|
||||||
from app.database.room_participated_user import RoomParticipatedUser
|
|
||||||
from sqlmodel import select
|
|
||||||
|
|
||||||
# Remove user from room by setting left_at timestamp
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(RoomParticipatedUser).where(
|
select(RoomParticipatedUser).where(
|
||||||
RoomParticipatedUser.room_id == room_id,
|
RoomParticipatedUser.room_id == room_id,
|
||||||
@@ -269,34 +407,21 @@ async def remove_user_from_room(
|
|||||||
col(RoomParticipatedUser.left_at).is_(None)
|
col(RoomParticipatedUser.left_at).is_(None)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
participation = result.first()
|
participation = result.scalar_one_or_none()
|
||||||
|
|
||||||
if participation:
|
if participation:
|
||||||
participation.left_at = utcnow()
|
participation.left_at = utcnow()
|
||||||
|
|
||||||
# Update participant count
|
# Update participant count
|
||||||
active_count = await db.execute(
|
await _update_room_participant_count(db, room_id)
|
||||||
select(RoomParticipatedUser).where(
|
|
||||||
RoomParticipatedUser.room_id == room_id,
|
|
||||||
col(RoomParticipatedUser.left_at).is_(None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
count = len(active_count.all())
|
|
||||||
|
|
||||||
# Update room participant count
|
|
||||||
room_result = await db.execute(
|
|
||||||
select(Room).where(Room.id == room_id)
|
|
||||||
)
|
|
||||||
room_obj = room_result.first()
|
|
||||||
if room_obj:
|
|
||||||
room_obj.participant_count = count
|
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail=f"Failed to remove user from room: {str(e)}"
|
detail=f"Failed to remove user from room: {str(e)}"
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user