from channels.generic.websocket import AsyncJsonWebsocketConsumer from channels.db import database_sync_to_async from django.core.exceptions import PermissionDenied from boxofficefantasy.models import League, Season from boxofficefantasy.views import parse_season_slug from draft.models import DraftSession, DraftPick, DraftMoviePool, DraftParticipant from django.core.cache import cache from draft.constants import DraftMessage, DraftPhase, DraftGroupChannelNames import random class DraftConsumerBase(AsyncJsonWebsocketConsumer): async def connect(self): draft_session_id_hashed = self.scope["url_route"]["kwargs"].get( "draft_session_id_hashed" ) league_slug = self.scope["url_route"]["kwargs"].get("league_slug") season_slug = self.scope["url_route"]["kwargs"].get("season_slug") self.draft_session = await self.get_draft_session( draft_session_id_hashed=draft_session_id_hashed, ) self.draft_group_names = f"draft_admin_{self.draft_session.hashed_id}" self.draft_participant_group_channels = DraftGroupChannelNames(draft_session_id_hashed) self.user = self.scope["user"] if not self.user.is_authenticated: await self.close() return else: await self.accept() async def receive_json(self, content): event_type = content.get("type") user = self.scope["user"] async def user_joined(self, event): await self.send_json( { "type": "user.joined", "user": event["user"].username, "user_type": event["user_type"], "users": event["users"], } ) async def send_draft_summary(self): state = cache.get(self.draft_status_cache_key, {}) await self.send_json( { "type": "draft_summary", "phase": state.get("phase", "not started"), "movie": state.get("movie"), "current_bid": state.get("current_bid"), "time_remaining": state.get("time_remaining"), "you_are_next": state.get("you_are_next", False), } ) # === Broadcast handlers === async def draft_status(self, event): await self.send_json( { "type": "draft.status", "status": event["status"], } ) # === DB Access === @database_sync_to_async def get_draft_session( self, draft_session_id_hashed, league_slug, season_slug ) -> DraftSession: draft_session_id = DraftSession.decode_id(draft_session_id_hashed) if draft_session_id: draft_session = DraftSession.objects.select_related( "season", "season__league" ).get(pk=draft_session_id) elif league_slug and season_slug: label, year = parse_season_slug(season_slug) season = Season.objects.filter(label=label, year=year).first() draft_session = ( DraftSession.objects.select_related("season", "season__league") .filter(season=season) .first() ) else: raise Exception() return draft_session @database_sync_to_async def get_draft_participants(self) -> list[DraftParticipant]: # Replace this with real queryset to fetch users in draft participants = DraftParticipant.objects.select_related("user").filter( draft=self.draft_session ) connected_ids = cache.get(self.draft_connected_participants_cache_key, set()) for p in participants: p.is_connected = p in connected_ids return list(participants.all()) class DraftAdminConsumer(DraftConsumerBase): async def connect(self): await super().connect() if not self.user.is_staff: await self.close() return await self.channel_layer.group_add( self.draft_admin_group_name, self.channel_name ) # await self.channel_layer.group_send( # self.draft_participant_group_name, # {"type": "user.joined", "user": self.user, "user_type": "admin"}, # ) await self.channel_layer.group_send( self.draft_admin_group_name, {"type": "user.joined", "user": self.user, "user_type": "admin"}, ) async def receive_json(self, content): await super().receive_json(content) event_type = content.get("type") user = self.scope["user"] if event_type == "start.draft": await self.start_draft() elif event_type == "user.joined": pass elif event_type == "nominate": await self.nominate(content.get("movie")) elif event_type == "bid": await self.place_bid(content.get("amount"), self.scope["user"].username) elif event_type == "message": await self.channel_layer.group_send( self.draft_participant_group_name, { "type": "chat.message", "user": self.scope["user"].username, "message": content.get("message"), }, ) # === Draft logic (stubbed for now) === async def start_draft(self): # Example: shuffle draft order participants = await self.get_draft_participants() draft_order = random.sample(participants, len(participants)) connected_participants = cache.get( self.draft_connected_participants_cache_key, () ) initial_state = { "phase": "nominating", "current_nominee": None, "current_bid": None, "participants": [ {"user": p.user.username, "is_connected": p in connected_participants} for p in await self.get_draft_participants() ], "draft_order": [p.user.username for p in draft_order], "current_turn_index": 0, "picks": [], } cache.set(self.draft_status_cache_key, initial_state) for group_name in [ self.draft_admin_group_name, self.draft_participant_group_name, ]: # await self.channel_layer.group_send( # group_name, # { # "type": "draft.start" # } # ) await self.channel_layer.group_send( group_name, { "type": "draft.status", "status": cache.get(self.draft_status_cache_key), }, ) class DraftParticipantConsumer(DraftConsumerBase): async def connect(self): await super().connect() await self.channel_layer.group_add( self.draft_participant_group_name, self.channel_name ) try: await self.add_draft_participant() except Exception as e: await self.close() return await self.send_json( { "type": "connection.accepted", "user": self.user.username, "is_staff": self.user.is_staff, } ) await self.channel_layer.group_send( self.draft_participant_group_name, { "type": "user.joined", "user": self.user, "user_type": "participant", "participants": [], }, ) await self.channel_layer.group_send( self.draft_admin_group_name, {"type": "user.joined", "user": self.user, "user_type": "participant"}, ) async def disconnect(self, close_code): await self.channel_layer.group_discard( self.draft_participant_group_name, self.channel_name ) async def receive_json(self, content): event_type = content.get("type") user = self.scope["user"] if event_type == "user.joined": pass elif event_type == "nominate": await self.nominate(content.get("movie")) elif event_type == "bid": await self.place_bid(content.get("amount"), self.scope["user"].username) elif event_type == "message": await self.channel_layer.group_send( self.draft_participant_group_name, { "type": "chat.message", "user": self.scope["user"].username, "message": content.get("message"), }, ) # === Broadcast handlers === async def chat_message(self, event): await self.send_json( { "type": "chat.message", "user": event["user"], } ) async def draft_update(self, event): await self.send_json( { "type": "draft.update", "state": event["state"], } ) # === Draft logic (stubbed for now) === async def nominate(self, movie_title): await self.channel_layer.group_send( self.draft_participant_group_name, { "type": "draft.update", "state": { "status": "nominating", "movie": movie_title, }, }, ) async def place_bid(self, amount, user): await self.channel_layer.group_send( self.draft_participant_group_name, { "type": "draft.update", "state": {"status": "bidding", "bid": {"amount": amount, "user": user}}, }, ) # === Example DB Access === @database_sync_to_async def add_draft_participant(self): self.participant, _ = DraftParticipant.objects.get_or_create( user=self.user, draft=self.draft_session, defaults={"budget": self.draft_session.settings.starting_budget}, )