Fix V2 race conditions, auth gaps, serialization bugs, and async stats
Phase 1 - Critical Fixes: - Add game_lock (asyncio.Lock) to Room class for serializing mutations - Wrap all game action handlers in lock to prevent race conditions - Split Card.to_dict into to_dict (full data) and to_client_dict (hidden) - Fix CardState.from_dict to handle missing rank/suit gracefully - Fix GameOptions reconstruction in recovery_service (dict -> object) - Extend state cache TTL from 4h to 24h, add touch_game method Phase 2 - Security: - Add optional WebSocket authentication via token query param - Use authenticated user ID/name when available - Add auth support to spectator WebSocket endpoint Phase 3 - Performance: - Make stats processing async (fire-and-forget) to avoid blocking game completion notifications Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,12 +52,38 @@ class CardState:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict) -> "CardState":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
rank=d["rank"],
|
||||
suit=d["suit"],
|
||||
face_up=d.get("face_up", False),
|
||||
)
|
||||
"""
|
||||
Create from dictionary.
|
||||
|
||||
Handles both full card data and minimal face-down data gracefully.
|
||||
|
||||
Args:
|
||||
d: Dictionary with card data. May contain:
|
||||
- Full data: {rank, suit, face_up}
|
||||
- Minimal face-down: {face_up: False}
|
||||
|
||||
Returns:
|
||||
CardState instance.
|
||||
|
||||
Raises:
|
||||
ValueError: If face_up is True but rank/suit are missing.
|
||||
"""
|
||||
face_up = d.get("face_up", False)
|
||||
rank = d.get("rank")
|
||||
suit = d.get("suit")
|
||||
|
||||
# If card is face-up, we must have rank and suit
|
||||
if face_up and (rank is None or suit is None):
|
||||
raise ValueError("Face-up card must have rank and suit")
|
||||
|
||||
# For face-down cards with missing data, use placeholder values
|
||||
# This handles improperly serialized data from older versions
|
||||
if rank is None:
|
||||
rank = "?" # Placeholder for unknown
|
||||
if suit is None:
|
||||
suit = "?" # Placeholder for unknown
|
||||
|
||||
return cls(rank=rank, suit=suit, face_up=face_up)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
Reference in New Issue
Block a user