Skip to content
Merged
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
55 changes: 45 additions & 10 deletions chats/consumers/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.conf import settings
from django.core.cache import cache
from django.db.models import Q
from django.utils import timezone

from chats.exceptions import ChatException
Expand All @@ -25,7 +26,7 @@
from users.models import CustomUser
from chats.consumers.event_types import DirectEvent, ProjectEvent
from chats.utils import get_chat_and_user_ids_from_content
from chats.models import DirectChat
from chats.models import DirectChat, ProjectChat


@database_sync_to_async
Expand All @@ -35,6 +36,19 @@ def get_user_project_ids(user_id: int) -> list[int]:
)


@database_sync_to_async
def can_access_project_chat(user_id, chat_id):
try:
chat_id = int(chat_id)
except (TypeError, ValueError):
return False
return (
ProjectChat.objects.filter(pk=chat_id)
.filter(Q(project__leader_id=user_id) | Q(project__collaborator__user_id=user_id))
.exists()
)


class ChatConsumer(AsyncJsonWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -88,10 +102,7 @@ async def connect(self):
)
# Confirm selected subprotocol so browser clients finish handshake.
subprotocol = None
if (
self.scope.get("subprotocols")
and len(self.scope["subprotocols"]) >= 1
):
if self.scope.get("subprotocols") and len(self.scope["subprotocols"]) >= 1:
subprotocol = self.scope["subprotocols"][0]

await self.accept(subprotocol=subprotocol)
Expand Down Expand Up @@ -138,6 +149,11 @@ async def receive_json(self, content, **kwargs):
if event.content["chat_type"] == ChatType.DIRECT:
self.event = DirectEvent(self.user, self.channel_layer, self.channel_name)
elif event.content["chat_type"] == ChatType.PROJECT:
if not await can_access_project_chat(
self.user.pk, event.content.get("chat_id")
):
await self.send_json({"error": "У вас нет доступа к чату проекта."})
return
self.event = ProjectEvent(
self.user, self.channel_layer, self.channel_name
)
Expand Down Expand Up @@ -188,6 +204,9 @@ async def __process_typing_event(self, event: Event, room_name: str):
},
}

if event.content["chat_type"] == ChatType.PROJECT:
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(
Expand Down Expand Up @@ -217,17 +236,33 @@ async def __process_typing_event(self, event: Event, room_name: str):
event_data,
)

async def _send_chat_event(self, event):
chat_id = event.get("_project_chat_id")
if chat_id is not None:
if not self.user or not await can_access_project_chat(self.user.pk, chat_id):
room_name = f"{EventGroupType.CHATS_RELATED}_{chat_id}"
await self.channel_layer.group_discard(room_name, self.channel_name)
self.joined_rooms.discard(room_name)
return
# Служебный контекст нужен для проверки уже открытых сокетов,
# клиентский формат сообщений остается прежним.
await self.send(
json.dumps(
{key: value for key, value in event.items() if key != "_project_chat_id"}
)
)

async def message_read(self, event: Event):
await self.send(json.dumps(event))
await self._send_chat_event(event)

async def user_typing(self, event: Event):
await self.send(json.dumps(event))
await self._send_chat_event(event)

async def new_message(self, event: Event):
await self.send(json.dumps(event))
await self._send_chat_event(event)

async def delete_message(self, event: Event):
await self.send(json.dumps(event))
await self._send_chat_event(event)

async def set_online(self, event: Event):
await self.send(json.dumps(event))
Expand All @@ -236,7 +271,7 @@ async def set_offline(self, event: Event):
await self.send(json.dumps(event))

async def edit_message(self, event: Event):
await self.send(json.dumps(event))
await self._send_chat_event(event)

async def __process_general_event(self, event: Event, room_name: str):
cache_key = get_user_online_cache_key(self.user)
Expand Down
34 changes: 24 additions & 10 deletions chats/consumers/event_types/ProjectEvent.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ async def process_new_message_event(self, event: Event, room_name: str):
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"
),
ProjectChatMessage.objects.select_related(
"author", "reply_to__author"
).prefetch_related("file_to_message__file"),
pk=msg.pk,
)
message_data = ProjectChatMessageListSerializer(serialized_message).data
Expand All @@ -77,7 +77,11 @@ async def process_new_message_event(self, event: Event, room_name: str):
"message": message_data,
}

event_data = {"type": EventType.NEW_MESSAGE, "content": content}
event_data = {
"type": EventType.NEW_MESSAGE,
"content": content,
"_project_chat_id": chat_id,
}

await self.channel_layer.group_send(room_name, event_data)

Expand Down Expand Up @@ -111,6 +115,7 @@ async def process_read_message_event(self, event: Event, room_name: str):
"user_id": self.user.id,
"message_id": event.content["message_id"],
},
"_project_chat_id": event.content["chat_id"],
},
)

Expand All @@ -130,7 +135,9 @@ async def process_delete_message_event(self, event: Event, room_name: str):
f"User {self.user.id} is not in project chat {chat_id}"
)

message = await orm_get(ProjectChatMessage.objects, pk=event.content["message_id"])
message = await orm_get(
ProjectChatMessage.objects, pk=event.content["message_id"]
)

if self.user.id != message.author_id:
raise UserIsNotAuthor(f"User {self.user.id} is not author {chat_id}")
Expand All @@ -142,6 +149,7 @@ async def process_delete_message_event(self, event: Event, room_name: str):
{
"type": EventType.DELETE_MESSAGE,
"content": {"message_id": event.content["message_id"]},
"_project_chat_id": event.content["chat_id"],
},
)

Expand All @@ -161,7 +169,9 @@ async def process_edit_message_event(self, event, room_name):
f"User {self.user.id} is not in project chat {chat_id}"
)

message = await orm_get(ProjectChatMessage.objects, pk=event.content["message_id"])
message = await orm_get(
ProjectChatMessage.objects, pk=event.content["message_id"]
)

if message.author_id != self.user.id:
raise UserNotMessageAuthorException(
Expand All @@ -172,9 +182,9 @@ async def process_edit_message_event(self, event, room_name):
await orm_save(message, update_fields=["text", "is_edited"])

serialized_message = await orm_get(
ProjectChatMessage.objects.select_related("author", "reply_to__author").prefetch_related(
"file_to_message__file"
),
ProjectChatMessage.objects.select_related(
"author", "reply_to__author"
).prefetch_related("file_to_message__file"),
pk=message.pk,
)
message_data = ProjectChatMessageListSerializer(serialized_message).data
Expand All @@ -183,6 +193,10 @@ async def process_edit_message_event(self, event, room_name):
"message": message_data,
}

event_data = {"type": EventType.EDIT_MESSAGE, "content": content}
event_data = {
"type": EventType.EDIT_MESSAGE,
"content": content,
"_project_chat_id": chat_id,
}

await self.channel_layer.group_send(room_name, event_data)
10 changes: 10 additions & 0 deletions partner_programs/serializers/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ def validate_ids(self, values):

class ManagementRemovalWrite(ManagementIdsWrite):
preview_token = serializers.CharField(max_length=200000)
confirmation_version = serializers.IntegerField(required=False)

def validate(self, attrs):
if attrs.get("confirmation_version") != 2:
raise serializers.ValidationError(
{
"detail": "Обновите страницу и заново подтвердите удаление участников из команд заявок."
}
)
return attrs


BASIC_FIELDS = (
Expand Down
Loading
Loading