diff --git a/chat/consumers.py b/chat/consumers.py index 47b66ce..f120ee0 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -37,6 +37,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): channels = await Channel.get_accessible_channels(user, 'read') async for channel in channels.all(): await self.channel_layer.group_add(f"chat-{channel.id}", self.channel_name) + await self.channel_layer.group_add(f"user-{user.id}", self.channel_name) async def disconnect(self, close_code) -> None: """ @@ -50,6 +51,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): channels = await Channel.get_accessible_channels(self.scope['user'], 'read') async for channel in channels.all(): await self.channel_layer.group_discard(f"chat-{channel.id}", self.channel_name) + await self.channel_layer.group_discard(f"user-{self.scope['user'].id}", self.channel_name) async def receive_json(self, content, **kwargs): """ @@ -63,6 +65,8 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): await self.receive_message(content) case 'fetch_messages': await self.fetch_messages(**content) + case 'start_private_chat': + await self.start_private_chat(content['user_id']) case unknown: print("Unknown message type:", unknown) @@ -76,12 +80,12 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): 'channels': [ { 'id': channel.id, - 'name': channel.name, + 'name': channel.get_visible_name(user), 'category': channel.category, 'read_access': True, 'write_access': await write_channels.acontains(channel), } - async for channel in read_channels.all() + async for channel in read_channels.prefetch_related('invited').all() ] } await self.send_json(message) @@ -105,6 +109,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): 'id': message.id, 'channel_id': channel.id, 'timestamp': message.created_at.isoformat(), + 'author_id': message.author_id, 'author': await message.aget_author_name(), 'content': message.content, }) @@ -125,6 +130,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): { 'id': message.id, 'timestamp': message.created_at.isoformat(), + 'author_id': message.author_id, 'author': await message.aget_author_name(), 'content': message.content, } @@ -132,7 +138,50 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): ])) }) + async def start_private_chat(self, user_id: int) -> None: + user = self.scope['user'] + other_user = await User.objects.aget(id=user_id) + channel_qs = Channel.objects.filter(private=True).filter(invited=user).filter(invited=other_user) + if not await channel_qs.aexists(): + channel = await Channel.objects.acreate( + name=f"{user.first_name} {user.last_name}, {other_user.first_name} {other_user.last_name}", + category=Channel.ChannelCategory.PRIVATE, + private=True, + ) + await channel.invited.aset([user, other_user]) + await channel.asave() + + await self.channel_layer.group_add(f"chat-{channel.id}", self.channel_name) + else: + channel = await channel_qs.afirst() + + await self.channel_layer.group_send(f"user-{user.id}", { + 'type': 'chat.start_private_chat', + 'channel': { + 'id': channel.id, + 'name': f"{other_user.first_name} {other_user.last_name}", + 'category': channel.category, + 'read_access': True, + 'write_access': True, + } + }) + if user != other_user: + await self.channel_layer.group_send(f"user-{other_user.id}", { + 'type': 'chat.start_private_chat', + 'channel': { + 'id': channel.id, + 'name': f"{user.first_name} {user.last_name}", + 'category': channel.category, + 'read_access': True, + 'write_access': True, + } + }) + async def chat_send_message(self, message) -> None: await self.send_json({'type': 'send_message', 'id': message['id'], 'channel_id': message['channel_id'], 'timestamp': message['timestamp'], 'author': message['author'], 'content': message['content']}) + + async def chat_start_private_chat(self, message) -> None: + await self.channel_layer.group_add(f"chat-{message['channel']['id']}", self.channel_name) + await self.send_json({'type': 'start_private_chat', 'channel': message['channel']}) diff --git a/chat/models.py b/chat/models.py index 9b91ec0..706060d 100644 --- a/chat/models.py +++ b/chat/models.py @@ -91,6 +91,13 @@ class Channel(models.Model): "in addition to the permitted group of the channel."), ) + def get_visible_name(self, user: User) -> str: + if self.private: + users = [f"{u.first_name} {u.last_name}" for u in self.invited.all() if u != user] \ + or [f"{user.first_name} {user.last_name}"] + return ", ".join(users) + return self.name + def __str__(self): return str(format_lazy(_("Channel {name}"), name=self.name)) @@ -106,7 +113,7 @@ class Channel(models.Model): registration = await Registration.objects.prefetch_related('user').aget(user_id=user.id) if registration.is_admin: - return Channel.objects.all() + return Channel.objects.prefetch_related('invited').exclude(~Q(invited=user) & Q(private=True)).all() if registration.is_volunteer: registration = await VolunteerRegistration.objects \ @@ -153,7 +160,7 @@ class Channel(models.Model): qs |= Channel.objects.filter(Q(team=team), **{permission_type: PermissionType.TEAM_MEMBER}) - qs |= Channel.objects.filter(invited=user) + qs |= Channel.objects.filter(invited=user).prefetch_related('invited') return qs diff --git a/chat/static/chat.js b/chat/static/chat.js index b1b7997..9295de2 100644 --- a/chat/static/chat.js +++ b/chat/static/chat.js @@ -95,8 +95,13 @@ function setChannels(new_channels) { } if (new_channels && (!selected_channel_id || !channels[selected_channel_id])) { - if (window.location.hash) - selectChannel(window.location.hash.substring(9)) + if (window.location.hash) { + let channel_id = parseInt(window.location.hash.substring(9)) + if (channels[channel_id]) + selectChannel(channel_id) + else + selectChannel(Object.keys(channels)[0]) + } else selectChannel(Object.keys(channels)[0]) }