Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions chats/consumers/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from json import JSONDecodeError
from typing import Optional

from asgiref.sync import sync_to_async
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.conf import settings
Expand Down Expand Up @@ -208,22 +207,11 @@ async def __process_typing_event(self, event: Event, room_name: str):
event_data["_project_chat_id"] = event.content["chat_id"]

if event.content["chat_type"] == ChatType.DIRECT:
# fixme: need to move this to func
chat_id, other_user = await get_chat_and_user_ids_from_content(
_, other_user = await get_chat_and_user_ids_from_content(
event.content, self.user
)

# if chat_id == 17_7, then chat_id will be == 7_17
chat_id = DirectChat.get_chat_id_from_users(self.user, other_user)

# check if chat exists
try:
await sync_to_async(DirectChat.objects.get)(pk=chat_id)
except DirectChat.DoesNotExist:
# if not, create such chat
await sync_to_async(DirectChat.create_from_two_users)(
self.user, other_user
)
await database_sync_to_async(DirectChat.get_chat)(self.user, other_user)

# send message to user's channel
other_user_channel = cache.get(get_user_channel_cache_key(other_user), None)
Expand Down
63 changes: 15 additions & 48 deletions chats/consumers/event_types/DirectEvent.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from channels.db import database_sync_to_async

from chats.models import DirectChatMessage, DirectChat
from chats.websockets_settings import Event, EventType
from chats.exceptions import (
Expand All @@ -8,16 +10,12 @@
from django.core.cache import cache
from chats.utils import (
get_user_channel_cache_key,
create_message,
get_chat_and_user_ids_from_content,
match_files_and_messages,
orm_create,
orm_exists,
orm_get,
orm_save,
orm_set,
)
from chats.serializers import DirectChatMessageListSerializer
from chats.services import create_direct_message


class DirectEvent:
Expand All @@ -27,53 +25,22 @@ def __init__(self, user, channel_layer, channel_name):
self.channel_name = channel_name

async def process_new_message_event(self, event: Event, room_name: str):
chat_id, other_user = await get_chat_and_user_ids_from_content(
event.content, self.user
)

# if chat_id == 17_7, then chat_id will be == 7_17
_, other_user = await get_chat_and_user_ids_from_content(event.content, self.user)
chat_id = DirectChat.get_chat_id_from_users(self.user, other_user)

# check if chat exists
if not await orm_exists(DirectChat.objects.filter(pk=chat_id)):
# if not, create such chat
chat = await orm_create(DirectChat.objects, pk=chat_id)
await orm_set(chat.users, [self.user, other_user])

reply_to_message = None
if event.content["reply_to"] is not None:
try:
reply_to_message = await orm_get(
DirectChatMessage.objects, pk=event.content["reply_to"]
)
except DirectChatMessage.DoesNotExist:
reply_to_message = None

msg = await create_message(
chat_id=chat_id,
chat_model=DirectChatMessage,
message_data, created = await database_sync_to_async(create_direct_message)(
author=self.user,
text=event.content["text"],
reply_to=reply_to_message,
)

messages = {
"direct_message": msg,
"project_message": None,
}
await match_files_and_messages(event.content["file_urls"], messages)

serialized_message = await orm_get(
DirectChatMessage.objects.select_related("author", "reply_to__author").prefetch_related(
"file_to_message__file"
),
pk=msg.pk,
other_user=other_user,
text=event.content.get("text", ""),
reply_to_id=event.content.get("reply_to"),
file_urls=event.content.get("file_urls", []),
client_message_id=event.content.get("client_message_id"),
)
message_data = DirectChatMessageListSerializer(serialized_message).data

content = {
"chat_id": chat_id,
"message": message_data,
"is_replay": not created,
}

# send message to user's channel
Expand All @@ -83,7 +50,7 @@ async def process_new_message_event(self, event: Event, room_name: str):

await self.channel_layer.send(self.channel_name, event_data)

if other_user_channel:
if created and other_user_channel and other_user_channel != self.channel_name:
await self.channel_layer.send(other_user_channel, event_data)

async def process_read_message_event(self, event: Event, room_name: str):
Expand Down Expand Up @@ -164,9 +131,9 @@ async def process_edit_message_event(self, event, room_name):
await orm_save(msg, update_fields=["text", "is_edited"])

serialized_message = await orm_get(
DirectChatMessage.objects.select_related("author", "reply_to__author").prefetch_related(
"file_to_message__file"
),
DirectChatMessage.objects.select_related(
"author", "reply_to__author"
).prefetch_related("file_to_message__file"),
pk=msg.pk,
)
message_data = DirectChatMessageListSerializer(serialized_message).data
Expand Down
57 changes: 13 additions & 44 deletions chats/consumers/event_types/ProjectEvent.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from channels.db import database_sync_to_async

from chats.models import ProjectChat, ProjectChatMessage
from chats.utils import (
create_message,
match_files_and_messages,
orm_exists,
orm_get,
orm_save,
)
from chats.services import create_project_message
from chats.websockets_settings import Event, EventType
from chats.exceptions import (
WrongChatIdException,
Expand All @@ -28,53 +29,18 @@ def __init__(self, user, channel_layer, channel_name):

async def process_new_message_event(self, event: Event, room_name: str):
chat_id = event.content["chat_id"]
chat = await orm_get(
ProjectChat.objects.select_related("project__leader"),
pk=chat_id,
)

# check that user is in this chat
is_member = chat.project.leader_id == self.user.id or await orm_exists(
Collaborator.objects.filter(project_id=chat.project_id, user_id=self.user.id)
)
if not is_member:
raise UserNotInChatException(
f"User {self.user.id} is not in project chat {chat_id}"
)

reply_to_message = None
if event.content["reply_to"] is not None:
try:
reply_to_message = await orm_get(
ProjectChatMessage.objects, pk=event.content["reply_to"]
)
except ProjectChatMessage.DoesNotExist:
reply_to_message = None

msg = await create_message(
message_data, created = await database_sync_to_async(create_project_message)(
chat_id=chat_id,
chat_model=ProjectChatMessage,
author=self.user,
text=event.content["text"],
reply_to=reply_to_message,
text=event.content.get("text", ""),
reply_to_id=event.content.get("reply_to"),
file_urls=event.content.get("file_urls", []),
client_message_id=event.content.get("client_message_id"),
)

messages = {
"direct_message": None,
"project_message": msg,
}
await match_files_and_messages(event.content["file_urls"], messages)

serialized_message = await orm_get(
ProjectChatMessage.objects.select_related(
"author", "reply_to__author"
).prefetch_related("file_to_message__file"),
pk=msg.pk,
)
message_data = ProjectChatMessageListSerializer(serialized_message).data
content = {
"chat_id": chat_id,
"message": message_data,
"is_replay": not created,
}

event_data = {
Expand All @@ -83,7 +49,10 @@ async def process_new_message_event(self, event: Event, room_name: str):
"_project_chat_id": chat_id,
}

await self.channel_layer.group_send(room_name, event_data)
if created:
await self.channel_layer.group_send(room_name, event_data)
else:
await self.channel_layer.send(self.channel_name, event_data)

async def process_read_message_event(self, event: Event, room_name: str):
msg = await orm_get(ProjectChatMessage.objects, pk=event.content["message_id"])
Expand Down
2 changes: 1 addition & 1 deletion chats/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ class UserNotMessageAuthorException(ChatException):
pass


class NonMatchingReplyChatIdException(ChatException):
class MessageValidationException(ChatException):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 5.2.18 on 2026-11-13 16:33

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("chats", "0011_alter_filetomessage_direct_message_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AddField(
model_name="directchatmessage",
name="client_message_id",
field=models.UUIDField(blank=True, editable=False, null=True),
),
migrations.AddField(
model_name="projectchatmessage",
name="client_message_id",
field=models.UUIDField(blank=True, editable=False, null=True),
),
migrations.AddConstraint(
model_name="directchatmessage",
constraint=models.UniqueConstraint(
condition=models.Q(("client_message_id__isnull", False)),
fields=("chat", "author", "client_message_id"),
name="unique_direct_chat_client_message",
),
),
migrations.AddConstraint(
model_name="projectchatmessage",
constraint=models.UniqueConstraint(
condition=models.Q(("client_message_id__isnull", False)),
fields=("chat", "author", "client_message_id"),
name="unique_project_chat_client_message",
),
),
]
32 changes: 20 additions & 12 deletions chats/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models
from django.db import models, transaction

from files.models import UserFile
from projects.models import Project
Expand Down Expand Up @@ -137,6 +137,7 @@ def get_avatar(self, user):
return other_user.avatar

@classmethod
@transaction.atomic
def get_chat(cls, user1, user2) -> "DirectChat":
"""
Returns chat between two users.
Expand All @@ -150,25 +151,17 @@ def get_chat(cls, user1, user2) -> "DirectChat":
"""

pk = cls.get_chat_id_from_users(user1, user2)
try:
return cls.objects.get(pk=pk)
except cls.DoesNotExist:
chat = cls.objects.create(pk=pk)
chat, created = cls.objects.get_or_create(pk=pk)
if created:
chat.users.set([user1, user2])
return chat
return chat

def get_last_messages(self, message_count):
return self.messages.order_by("-created_at")[:message_count]

def get_other_user(self, user) -> User:
return self.users.exclude(pk=user.pk).first()

@classmethod
def create_from_two_users(cls, user1, user2):
chat = cls.objects.create(pk=cls.get_chat_id_from_users(user1, user2))
chat.users.set([user1, user2])
return chat

@classmethod
def get_chat_id_from_users(cls, user1, user2):
first_user = user1 if user1.pk < user2.pk else user2
Expand Down Expand Up @@ -198,6 +191,7 @@ class BaseMessage(models.Model):
is_read = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False)
is_edited = models.BooleanField(default=False)
client_message_id = models.UUIDField(null=True, blank=True, editable=False)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
Expand Down Expand Up @@ -246,6 +240,13 @@ def __str__(self):
class Meta:
verbose_name = "Сообщение в чате проекта"
verbose_name_plural = "Сообщения в чатах проектов"
constraints = [
models.UniqueConstraint(
fields=["chat", "author", "client_message_id"],
condition=models.Q(client_message_id__isnull=False),
name="unique_project_chat_client_message",
)
]


class DirectChatMessage(BaseMessage):
Expand Down Expand Up @@ -284,6 +285,13 @@ def __str__(self):
class Meta:
verbose_name = "Сообщение в личном чате"
verbose_name_plural = "Сообщения в личных чатах"
constraints = [
models.UniqueConstraint(
fields=["chat", "author", "client_message_id"],
condition=models.Q(client_message_id__isnull=False),
name="unique_direct_chat_client_message",
)
]


class FileToMessage(models.Model):
Expand Down
2 changes: 2 additions & 0 deletions chats/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class Meta:
"id",
"author",
"text",
"client_message_id",
"reply_to",
"files",
"is_edited",
Expand Down Expand Up @@ -196,6 +197,7 @@ class Meta:
"id",
"author",
"text",
"client_message_id",
"files",
"reply_to",
"is_edited",
Expand Down
6 changes: 6 additions & 0 deletions chats/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from chats.services.messages import (
create_direct_message,
create_project_message,
)

__all__ = ["create_direct_message", "create_project_message"]
Loading
Loading