import json from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from django.contrib.auth.models import User from django.utils import timezone class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_id = self.scope['url_route']['kwargs']['room_id'] self.room_group_name = f'chat_{self.room_id}' user = self.scope['user'] if not user.is_authenticated: await self.close() return # Verify membership is_member = await self.check_membership(user, self.room_id) if not is_member: await self.close() return await self.channel_layer.group_add(self.room_group_name, self.channel_name) await self.accept() # Broadcast join event await self.channel_layer.group_send( self.room_group_name, {'type': 'user_join', 'username': user.username, 'display_name': await self.get_display_name(user)} ) async def disconnect(self, close_code): user = self.scope.get('user') if user and user.is_authenticated: await self.channel_layer.group_send( self.room_group_name, {'type': 'user_leave', 'username': user.username, 'display_name': await self.get_display_name(user)} ) await self.channel_layer.group_discard(self.room_group_name, self.channel_name) async def receive(self, text_data): data = json.loads(text_data) msg_type = data.get('type', 'chat_message') user = self.scope['user'] if msg_type == 'chat_message': content = data.get('content', '').strip() if not content: return message = await self.save_message(user, content) await self.channel_layer.group_send( self.room_group_name, { 'type': 'chat_message', 'message_id': str(message.id), 'content': content, 'sender': user.username, 'display_name': await self.get_display_name(user), 'avatar_color': await self.get_avatar_color(user), 'initials': await self.get_initials(user), 'timestamp': message.created_at.strftime('%H:%M'), 'full_timestamp': message.created_at.isoformat(), } ) async def chat_message(self, event): await self.send(text_data=json.dumps({'type': 'chat_message', **event})) async def user_join(self, event): await self.send(text_data=json.dumps({'type': 'user_join', **event})) async def user_leave(self, event): await self.send(text_data=json.dumps({'type': 'user_leave', **event})) async def room_update(self, event): """Broadcast when events/expenses are created""" await self.send(text_data=json.dumps({'type': 'room_update', **event})) @database_sync_to_async def check_membership(self, user, room_id): from core.models import Membership return Membership.objects.filter(user=user, room_id=room_id, is_active=True).exists() @database_sync_to_async def save_message(self, user, content): from core.models import Message, ChatRoom room = ChatRoom.objects.get(id=self.room_id) return Message.objects.create(room=room, sender=user, content=content) @database_sync_to_async def get_display_name(self, user): try: return user.profile.name except Exception: return user.username @database_sync_to_async def get_avatar_color(self, user): try: return user.profile.avatar_color except Exception: return '#6366f1' @database_sync_to_async def get_initials(self, user): try: return user.profile.get_avatar_initials() except Exception: return user.username[:2].upper()