diff --git a/app/database/playlists.py b/app/database/playlists.py index 79e5c0d..73755bf 100644 --- a/app/database/playlists.py +++ b/app/database/playlists.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from app.models.model import UTCBaseModel from app.models.mods import APIMod @@ -60,6 +60,17 @@ class Playlist(PlaylistBase, table=True): } ) room: "Room" = Relationship() + created_at: Optional[datetime] = Field( + default=None, + sa_column_kwargs={"server_default": func.now()} + ) + updated_at: Optional[datetime] = Field( + default=None, + sa_column_kwargs={ + "server_default": func.now(), + "onupdate": func.now() + } + ) @classmethod async def get_next_id_for_room(cls, room_id: int, session: AsyncSession) -> int: diff --git a/app/router/lio.py b/app/router/lio.py index 3d38405..b675379 100644 --- a/app/router/lio.py +++ b/app/router/lio.py @@ -438,51 +438,109 @@ async def remove_user_from_room( timestamp: str = "", ) -> Dict[str, Any]: """Remove a user from a multiplayer room.""" - """ try: """ - # Verify request signature - body = await request.body() - # 直接用内部获取的时间戳 - now = utcnow() - if not verify_request_signature(request, timestamp, body): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid request signature" + try: + # Verify request signature + body = await request.body() + now = utcnow() + if not verify_request_signature(request, timestamp, body): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid request signature" + ) + + # 检查房间是否存在 + room_query = await db.fetch_one( + select(Room.owner_id, Room.status, Room.participant_count) + .where(col(Room.id) == room_id) + ) + + if not room_query: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Room not found" + ) + + room_owner_id = room_query['owner_id'] + room_status = room_query['status'] + current_participant_count = room_query['participant_count'] + + # 检查用户是否在房间中 + participant_query = await db.fetch_one( + select(RoomParticipatedUser.id) + .where( + col(RoomParticipatedUser.room_id) == room_id, + col(RoomParticipatedUser.user_id) == user_id, + col(RoomParticipatedUser.left_at).is_(None) + ) + ) + + if not participant_query: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User is not in this room" + ) + + # 标记用户离开房间 + await db.execute( + update(RoomParticipatedUser) + .where( + col(RoomParticipatedUser.room_id) == room_id, + col(RoomParticipatedUser.user_id) == user_id, + col(RoomParticipatedUser.left_at).is_(None) + ) + .values(left_at=now) ) - # 更新房间结束时间 - await db.execute( - update(Room) - .where(col(Room.id) == room_id) - .values(ends_at=now, status=RoomStatus.IDLE) - ) + # 检查是否是房主离开 + if user_id == room_owner_id: + # 房主离开,查找其他参与者来转让房主权限 + remaining_result = await db.execute( + select(RoomParticipatedUser.user_id) + .where( + col(RoomParticipatedUser.room_id) == room_id, + col(RoomParticipatedUser.user_id) != user_id, + col(RoomParticipatedUser.left_at).is_(None) + ) + .order_by(col(RoomParticipatedUser.joined_at)) # 按加入时间排序,选择最早的 + ) + remaining_participants = remaining_result.all() + + if remaining_participants: + # 将房主权限转让给最早加入的用户 + new_owner_id = remaining_participants[0][0] # 获取 user_id + await db.execute( + update(Room) + .where(col(Room.id) == room_id) + .values(owner_id=new_owner_id) + ) + + # 记录房主转让日志(可选) + # logger.info(f"Room {room_id} ownership transferred from {user_id} to {new_owner_id}") + else: + # 没有其他参与者,房间应该被标记为结束 + await db.execute( + update(Room) + .where(col(Room.id) == room_id) + .values( + ends_at=now, + status=RoomStatus.IDLE, + participant_count=0 + ) + ) + await db.commit() + return {"success": True, "room_ended": True} - # 标记所有参与者已离开 - await db.execute( - update(RoomParticipatedUser) - .where(col(RoomParticipatedUser.room_id) == room_id, col(RoomParticipatedUser.left_at).is_(None)) - .values(left_at=now) - ) + # 更新房间参与者数量 + await _update_room_participant_count(db, room_id) + + await db.commit() + return {"success": True, "room_ended": False} - # 人数归零 - await db.execute( - update(Room) - .where(col(Room.id) == room_id) - .values(participant_count=0) - ) - - await db.commit() - - - # Update participant count - await _update_room_participant_count(db, room_id) - - await db.commit() - return {"success": True} - - """ except HTTPException: + except HTTPException: raise except Exception as e: + await db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to remove user from room: {str(e)}" - ) """ \ No newline at end of file + ) \ No newline at end of file diff --git a/app/router/v2/room.py b/app/router/v2/room.py index 0afadb0..d41e640 100644 --- a/app/router/v2/room.py +++ b/app/router/v2/room.py @@ -50,14 +50,26 @@ async def get_all_rooms( current_user: User = Security(get_current_user, scopes=["public"]), ): resp_list: list[RoomResp] = [] - where_clauses: list[ColumnElement[bool]] = [col(Room.category) == category] + if category == RoomCategory.REALTIME: + db_category = RoomCategory.NORMAL # 实际查询 normal + else: + db_category = category + where_clauses: list[ColumnElement[bool]] = [col(Room.category) == db_category] now = utcnow() + if status is not None: where_clauses.append(col(Room.status) == status) + print(mode, category, status, current_user.id) if mode == "open": - where_clauses.append((col(Room.ends_at).is_(None)) | (col(Room.ends_at) > now.replace(tzinfo=UTC))) - if category == RoomCategory.REALTIME: - where_clauses.append(col(Room.id).in_(MultiplayerHubs.rooms.keys())) + # 修改为新的查询逻辑:状态为 idle 或 playing,starts_at 不为空,ends_at 为空 + where_clauses.extend([ + col(Room.status).in_([RoomStatus.IDLE, RoomStatus.PLAYING]), + col(Room.starts_at).is_not(None), + col(Room.ends_at).is_(None) + ]) + #if category == RoomCategory.REALTIME: + # where_clauses.append(col(Room.id).in_(MultiplayerHubs.rooms.keys())) + if mode == "participated": where_clauses.append( exists().where( @@ -65,11 +77,14 @@ async def get_all_rooms( col(RoomParticipatedUser.user_id) == current_user.id, ) ) + if mode == "owned": where_clauses.append(col(Room.host_id) == current_user.id) + if mode == "ended": where_clauses.append((col(Room.ends_at).is_not(None)) & (col(Room.ends_at) < now.replace(tzinfo=UTC))) + # 使用 select 指定需要的字段,对应您的 SQL 语句 db_rooms = ( ( await db.exec( @@ -81,7 +96,7 @@ async def get_all_rooms( .unique() .all() ) - + print("Retrieved rooms:", db_rooms) for room in db_rooms: resp = await RoomResp.from_db(room, db) if category == RoomCategory.REALTIME: @@ -409,4 +424,4 @@ async def get_room_events( playlist_items=playlist_items_resps, room=room_resp, user=user_resps, - ) + ) \ No newline at end of file diff --git a/migrations/versions/20c6df84813f_add_updated_at_column_to_room_playlists.py b/migrations/versions/20c6df84813f_add_updated_at_column_to_room_playlists.py new file mode 100644 index 0000000..84520a0 --- /dev/null +++ b/migrations/versions/20c6df84813f_add_updated_at_column_to_room_playlists.py @@ -0,0 +1,26 @@ +"""add_updated_at_column_to_room_playlists + +Revision ID: 20c6df84813f +Revises: 57bacf936413 +Create Date: 2025-08-24 00:08:14.704724 + +""" +from __future__ import annotations + +from collections.abc import Sequence + +# revision identifiers, used by Alembic. +revision: str = "20c6df84813f" +down_revision: str | Sequence[str] | None = "57bacf936413" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/migrations/versions/7576ca1e056d_add_updated_at_column_to_room_playlists.py b/migrations/versions/7576ca1e056d_add_updated_at_column_to_room_playlists.py new file mode 100644 index 0000000..5131d54 --- /dev/null +++ b/migrations/versions/7576ca1e056d_add_updated_at_column_to_room_playlists.py @@ -0,0 +1,26 @@ +"""add_updated_at_column_to_room_playlists + +Revision ID: 7576ca1e056d +Revises: 20c6df84813f +Create Date: 2025-08-24 00:08:42.419252 + +""" +from __future__ import annotations + +from collections.abc import Sequence + +# revision identifiers, used by Alembic. +revision: str = "7576ca1e056d" +down_revision: str | Sequence[str] | None = "20c6df84813f" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/migrations/versions/8d2af11343b9_add_updated_at_column_to_room_playlists.py b/migrations/versions/8d2af11343b9_add_updated_at_column_to_room_playlists.py new file mode 100644 index 0000000..d0da132 --- /dev/null +++ b/migrations/versions/8d2af11343b9_add_updated_at_column_to_room_playlists.py @@ -0,0 +1,26 @@ +"""add_updated_at_column_to_room_playlists + +Revision ID: 8d2af11343b9 +Revises: 7576ca1e056d +Create Date: 2025-08-24 00:11:05.064099 + +""" +from __future__ import annotations + +from collections.abc import Sequence + +# revision identifiers, used by Alembic. +revision: str = "8d2af11343b9" +down_revision: str | Sequence[str] | None = "7576ca1e056d" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass