Add user state updates and bidding error handling in draft consumers

- Implement user state tracking and broadcasting on connect/disconnect and phase changes
- Add bid start and place rejection handling with error messages to frontend and backend
- Enhance movie serializer with TMDB integration and update relevant frontend components
This commit is contained in:
2025-08-24 17:16:22 -05:00
parent baddca8d50
commit 5e08fdc9a2
15 changed files with 385 additions and 242 deletions

View File

@@ -22,9 +22,11 @@ class DraftCache:
bids: str
bid_timer_start: str
bid_timer_end: str
connected_participants: str
_cached_properties = {
"participants",
"connected_participants",
"phase",
"draft_order",
"draft_index",
@@ -32,7 +34,6 @@ class DraftCache:
"bids",
"bid_timer_start",
"bid_timer_end",
}
def __init__(self, draft_id: str, cache: BaseCache = cache):
@@ -71,8 +72,7 @@ class DraftStateManager:
self.session_id: str = session.hashid
self.cache: DraftCache = DraftCache(self.session_id, cache)
self.settings: DraftSessionSettings = session.settings
self.participants: set[User] = set(session.participants.all())
self.connected_participants: set[User] = set()
self._participants = list(session.participants.all())
# === Phase Management ===
@property
@@ -85,12 +85,21 @@ class DraftStateManager:
# === Connected Users ===
@property
def connected_participants(self):
return set(json.loads(self.cache.connected_participants or "[]"))
def connect_participant(self, username: str):
self.connected_participants.add(username)
return self.connected_participants
connected_participants = self.connected_participants
connected_participants.add(username)
self.cache.connected_participants = json.dumps(list(connected_participants))
return connected_participants
def disconnect_participant(self, username: str):
self.connected_participants.discard(username)
connected_participants = self.connected_participants
connected_participants.discard(username)
self.cache.connected_participants = json.dumps(list(connected_participants))
return connected_participants
# === Draft Order ===
@property
@@ -107,7 +116,7 @@ class DraftStateManager:
self.phase = DraftPhase.DETERMINE_ORDER
self.draft_index = 0
draft_order = random.sample(
list(self.participants), len(self.participants)
list(self._participants), len(self._participants)
)
self.draft_order = [user.username for user in draft_order]
return self.draft_order
@@ -169,18 +178,29 @@ class DraftStateManager:
if isinstance(amount, str):
amount = int(amount)
bids = self.get_bids()
bids.append({"user":user.username, "amount":amount})
user_state = self.user_state(user)
timestamp = int(time.time() * 1000)
if not user_state['can_bid']:
raise DraftStateException('Cannot bid')
if not user_state['remaining_budget'] > amount:
raise DraftStateException('No Budget Remaining')
if not self.get_timer_end() or not timestamp < self.get_timer_end() * 1000:
raise DraftStateException("Timer Error")
bids.append({"user":user.username, "amount":amount, 'timestamp': timestamp})
self.cache.bids = json.dumps(bids)
def get_bids(self) -> dict:
return json.loads(self.cache.bids or "[]")
def current_movie(self) -> Movie | None:
movie_id = self.current_movie
return Movie.objects.filter(pk=movie_id).first() if movie_id else None
movie_id = self.cache.current_movie
return movie_id if movie_id else None
def start_bidding(self):
if not self.phase == DraftPhase.BIDDING:
raise DraftStateException('Not the right phase for that')
if not self.current_movie():
raise DraftStateException('No movie nominated')
seconds = self.settings.bidding_duration
start_time = time.time()
end_time = start_time + seconds
@@ -202,6 +222,7 @@ class DraftStateManager:
"draft_index": self.draft_index,
"connected_participants": list(self.connected_participants),
"current_movie": self.cache.current_movie,
"awards": [],
"bids": self.get_bids(),
"bidding_timer_end": self.get_timer_end(),
"bidding_timer_start": self.get_timer_start(),
@@ -209,6 +230,17 @@ class DraftStateManager:
"next_picks": picks[1:] if picks else []
}
def user_state(self, user: User) -> dict:
picks = self.next_picks(include_current=True)
return {
"is_admin": user.is_staff,
"user": user.username,
"can_bid": self.phase == DraftPhase.BIDDING,
"can_nominate": self.phase == DraftPhase.NOMINATING and picks[0].get('participant') == user.username,
"movies":[],
"remaining_budget":100,
}
# def __dict__(self):
# return self.get_summary()