Skip to content

Commit 544aa69

Browse files
authored
Merge pull request #104 from PROCOLLAB-github/dev
Dev
2 parents 7f028bc + ceeffe7 commit 544aa69

9 files changed

Lines changed: 186 additions & 49 deletions

File tree

chats/consumers.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ async def receive_json(self, content, **kwargs):
9090
EventType.TYPING,
9191
EventType.READ_MESSAGE,
9292
EventType.DELETE_MESSAGE,
93+
EventType.EDIT_MESSAGE,
9394
]:
9495
room_name = f"{EventGroupType.CHATS_RELATED}_{event.content.get('chat_id')}"
9596
try:
@@ -112,6 +113,8 @@ async def __process_chat_related_event(self, event, room_name):
112113
await self.__process_read_message_event(event, room_name)
113114
elif event.type == EventType.DELETE_MESSAGE:
114115
await self.__process_delete_message_event(event, room_name)
116+
elif event.type == EventType.EDIT_MESSAGE:
117+
await self.__process_edit_message_event(event, room_name)
115118

116119
async def __process_new_message_event(self, event, room_name):
117120
if event.content["chat_type"] == ChatType.DIRECT:
@@ -149,9 +152,11 @@ async def __process_new_direct_message_event(self, event: Event):
149152
"content": {
150153
"message_id": msg.id,
151154
"chat_id": msg.chat_id,
155+
"chat_type": ChatType.PROJECT,
152156
"author_id": msg.author.pk,
153157
"text": msg.text,
154158
"created_at": msg.created_at.timestamp(),
159+
"is_edited": msg.is_edited,
155160
},
156161
}
157162
# send message to user's channel
@@ -193,6 +198,7 @@ async def __process_new_project_message_event(self, event: Event, room_name: str
193198
"author_id": msg.author.pk,
194199
"text": msg.text,
195200
"created_at": msg.created_at.timestamp(),
201+
"is_edited": msg.is_edited,
196202
},
197203
},
198204
)
@@ -280,6 +286,12 @@ async def __process_delete_message_event(self, event: Event, room_name: str):
280286
elif event.content["chat_type"] == ChatType.PROJECT:
281287
await self.__process_delete_project_message_event(event, room_name)
282288

289+
async def __process_edit_message_event(self, event, room_name):
290+
if event.content["chat_type"] == ChatType.DIRECT:
291+
await self.__process_edit_direct_message_event(event)
292+
elif event.content["chat_type"] == ChatType.PROJECT:
293+
await self.__process_edit_project_message_event(event, room_name)
294+
283295
async def __process_delete_direct_message_event(self, event):
284296
message_id = event.content["message_id"]
285297

@@ -349,6 +361,9 @@ async def set_online(self, event: Event):
349361
async def set_offline(self, event: Event):
350362
await self.send(json.dumps(event))
351363

364+
async def edit_message(self, event: Event):
365+
await self.send(json.dumps(event))
366+
352367
async def __process_general_event(self, event: Event, room_name: str):
353368
cache_key = get_user_online_cache_key(self.user)
354369
if event.type == EventType.SET_ONLINE:
@@ -371,6 +386,72 @@ async def __process_general_event(self, event: Event, room_name: str):
371386
else:
372387
raise ValueError("Unknown event type")
373388

389+
async def __process_edit_direct_message_event(self, event):
390+
chat_id, other_user = await get_chat_and_user_ids_from_content(
391+
event.content, self.user
392+
)
393+
# if chat_id == 17_7, then chat_id will be == 7_17
394+
chat_id = DirectChat.get_chat_id_from_users(self.user, other_user)
395+
396+
# check if chat exists ( this raises exception if not )
397+
await sync_to_async(DirectChat.objects.get)(pk=chat_id)
398+
399+
msg = await sync_to_async(DirectChatMessage.objects.get)(
400+
pk=event.content["message_id"]
401+
)
402+
msg.text = event.content["message"]
403+
msg.is_edited = True
404+
await sync_to_async(msg.save)()
405+
content = {
406+
"type": EventType.EDIT_MESSAGE,
407+
"content": {
408+
"message_id": msg.id,
409+
"chat_id": msg.chat_id,
410+
"author_id": msg.author_id,
411+
"text": msg.text,
412+
"created_at": msg.created_at.timestamp(),
413+
"is_edited": msg.is_edited,
414+
},
415+
}
416+
# send message to user's channel
417+
other_user_channel = cache.get(get_user_channel_cache_key(other_user), None)
418+
await self.channel_layer.send(self.channel_name, content)
419+
420+
if other_user_channel is None:
421+
return
422+
423+
await self.channel_layer.send(other_user_channel, content)
424+
425+
async def __process_edit_project_message_event(self, event, room_name):
426+
chat_id = event.content["chat_id"]
427+
chat = await sync_to_async(ProjectChat.objects.get)(pk=chat_id)
428+
429+
# check that user is in this chat
430+
users = await sync_to_async(chat.get_users)()
431+
if self.user not in users:
432+
raise UserNotInChatException(
433+
f"User {self.user.id} is not in project chat {chat_id}"
434+
)
435+
436+
message = await sync_to_async(ProjectChatMessage.objects.get)(
437+
pk=event.content["message_id"]
438+
)
439+
message.text = event.content["text"]
440+
message.is_edited = True
441+
await sync_to_async(message.save)()
442+
content = {
443+
"message_id": message.id,
444+
"chat_id": message.chat_id,
445+
"author_id": message.author.pk,
446+
"text": message.text,
447+
"created_at": message.created_at.timestamp(),
448+
"is_edited": message.is_edited,
449+
}
450+
await self.channel_layer.group_send(
451+
room_name,
452+
{"type": EventType.EDIT_MESSAGE, "content": content},
453+
)
454+
374455

375456
class NotificationConsumer(AsyncJsonWebsocketConsumer):
376457
# TODO: implement this
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.1.3 on 2023-03-12 19:12
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("chats", "0003_reply_to_constaint"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="directchatmessage",
15+
name="is_edited",
16+
field=models.BooleanField(default=False),
17+
),
18+
migrations.AddField(
19+
model_name="projectchatmessage",
20+
name="is_edited",
21+
field=models.BooleanField(default=False),
22+
),
23+
]

chats/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ class BaseMessage(models.Model):
196196
text = models.TextField(max_length=8192)
197197
is_read = models.BooleanField(default=False)
198198
is_deleted = models.BooleanField(default=False)
199+
is_edited = models.BooleanField(default=False)
199200
created_at = models.DateTimeField(auto_now_add=True)
200201

201202
def __str__(self):

chats/pagination.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from rest_framework import pagination
2+
3+
4+
class MessageListPagination(pagination.LimitOffsetPagination):
5+
default_limit = 15
6+
limit_query_param = "next"
7+
offset_query_param = "previous"

chats/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from rest_framework.response import Response
77

88
from chats.models import ProjectChat, DirectChat
9+
from chats.pagination import MessageListPagination
910
from chats.serializers import (
1011
DirectChatListSerializer,
1112
DirectChatMessageListSerializer,
@@ -94,6 +95,7 @@ def get(self, request, *args, **kwargs) -> Response:
9495
class DirectChatMessageList(ListCreateAPIView):
9596
serializer_class = DirectChatMessageListSerializer
9697
permission_classes = [IsAuthenticated]
98+
pagination_class = MessageListPagination
9799

98100
def get_queryset(self):
99101
return (
@@ -106,6 +108,7 @@ def get_queryset(self):
106108
class ProjectChatMessageList(ListCreateAPIView):
107109
serializer_class = ProjectChatMessageListSerializer
108110
permission_classes = [IsAuthenticated]
111+
pagination_class = MessageListPagination
109112

110113
def get_queryset(self):
111114
return (

chats/websockets_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class EventType(str, Enum):
1313
DELETE_MESSAGE = "delete_message"
1414
READ_MESSAGE = "message_read"
1515
TYPING = "user_typing"
16+
EDIT_MESSAGE = "edit_message"
1617

1718
# GENERAL EVENTS
1819
SET_ONLINE = "set_online"

core/permissions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,14 @@ def has_object_permission(self, request, view, obj) -> bool:
4343
if request.user in obj.chat.users.all():
4444
return True
4545
return False
46+
47+
48+
class IsAuthenticatedOrWriteOnly(BasePermission):
49+
"""
50+
Access if you Authenticated or for POST request
51+
"""
52+
53+
def has_object_permission(self, request, view, obj) -> bool:
54+
if request.user and request.user.is_authenticated or request.method == "POST":
55+
return True
56+
return False

users/helpers.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
from rest_framework_simplejwt.tokens import RefreshToken
2+
from django.contrib.sites.shortcuts import get_current_site
3+
from django.urls import reverse
4+
5+
from core.utils import Email
6+
7+
18
ADMIN = 0
29
MEMBER = 1
310
MENTOR = 2
@@ -16,3 +23,43 @@
1623
(EXPERT, "Эксперт"),
1724
(INVESTOR, "Инвестор"),
1825
)
26+
27+
REDIRECT_URL = "https://app.procollab.ru/auth/verification/"
28+
29+
30+
def reset_email(user, request):
31+
access_token = RefreshToken.for_user(user).access_token
32+
refresh_token = RefreshToken.for_user(user)
33+
34+
relative_link = reverse("users:password_reset_sent")
35+
36+
current_site = get_current_site(request).domain
37+
absolute_url = f"https://{current_site}{relative_link}?access_token={access_token}&refresh_token={refresh_token}"
38+
39+
email_body = f"Hi, {user.first_name} {user.last_name}! Use link below for reset password {absolute_url}"
40+
41+
data = {
42+
"email_body": email_body,
43+
"email_subject": "Reset password",
44+
"to_email": user.email,
45+
}
46+
47+
Email.send_email(data)
48+
49+
50+
def verify_email(user, request):
51+
token = RefreshToken.for_user(user).access_token
52+
53+
relative_link = reverse("users:account_email_verification_sent")
54+
current_site = get_current_site(request).domain
55+
absolute_url = f"https://{current_site}{relative_link}?token={token}"
56+
57+
email_body = f"Hi, {user.first_name} {user.last_name}! Use link below verify your email {absolute_url}"
58+
59+
data = {
60+
"email_body": email_body,
61+
"email_subject": "Verify your email",
62+
"to_email": user.email,
63+
}
64+
65+
Email.send_email(data)

users/views.py

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
import jwt
44
from django.apps import apps
55
from django.conf import settings
6-
from django.contrib.auth import get_user_model
7-
from django.contrib.sites.shortcuts import get_current_site
86
from django.db import transaction
97
from django.db.models import Q
108
from django.shortcuts import redirect
11-
from django.urls import reverse
9+
from django.contrib.auth import get_user_model
1210
from django_filters import rest_framework as filters
1311
from rest_framework import status
1412
from rest_framework.generics import (
@@ -23,10 +21,15 @@
2321
from rest_framework.views import APIView
2422
from rest_framework_simplejwt.tokens import RefreshToken, TokenError
2523

26-
from core.permissions import IsOwnerOrReadOnly
27-
from core.utils import Email
24+
from core.permissions import IsOwnerOrReadOnly, IsAuthenticatedOrWriteOnly
2825
from projects.serializers import ProjectListSerializer
29-
from users.helpers import VERBOSE_ROLE_TYPES, VERBOSE_USER_TYPES
26+
from users.helpers import (
27+
REDIRECT_URL,
28+
VERBOSE_ROLE_TYPES,
29+
VERBOSE_USER_TYPES,
30+
reset_email,
31+
verify_email,
32+
)
3033
from users.models import UserAchievement, LikesOnProject
3134
from users.permissions import IsAchievementOwnerOrReadOnly
3235
from users.serializers import (
@@ -46,7 +49,7 @@
4649

4750
class UserList(ListCreateAPIView):
4851
queryset = User.objects.get_active()
49-
permission_classes = [AllowAny] # FIXME: change to IsAuthorized
52+
permission_classes = [IsAuthenticatedOrWriteOnly]
5053
serializer_class = UserListSerializer
5154
filter_backends = (filters.DjangoFilterBackend,)
5255
filterset_class = UserFilter
@@ -59,23 +62,7 @@ def post(self, request, *args, **kwargs):
5962

6063
user = User.objects.get(email=serializer.data["email"])
6164

62-
token = RefreshToken.for_user(user).access_token
63-
64-
relative_link = reverse("users:account_email_verification_sent")
65-
current_site = get_current_site(request).domain
66-
absolute_url = "http://" + current_site + relative_link + "?token=" + str(token)
67-
68-
email_body = "Hi, {} {}! Use link below verify your email {}".format(
69-
user.first_name, user.last_name, absolute_url
70-
)
71-
72-
data = {
73-
"email_body": email_body,
74-
"email_subject": "Verify your email",
75-
"to_email": user.email,
76-
}
77-
78-
Email.send_email(data)
65+
verify_email(user, request)
7966

8067
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
8168

@@ -171,7 +158,6 @@ class VerifyEmail(GenericAPIView):
171158

172159
def get(self, request):
173160
token = request.GET.get("token")
174-
REDIRECT_URL = "https://app.procollab.ru/auth/verification/"
175161
try:
176162
payload = jwt.decode(jwt=token, key=settings.SECRET_KEY, algorithms=["HS256"])
177163
user = User.objects.get(id=payload["user_id"])
@@ -212,30 +198,7 @@ def post(self, request, *args, **kwargs):
212198

213199
user = User.objects.get(email=serializer.data["email"])
214200

215-
access_token = RefreshToken.for_user(user).access_token
216-
refresh_token = RefreshToken.for_user(user)
217-
218-
relative_link = reverse("users:password_reset_sent")
219-
220-
current_site = get_current_site(request).domain
221-
absolute_url = (
222-
"http://"
223-
+ current_site
224-
+ relative_link
225-
+ f"?access_token={access_token}&refresh_token={refresh_token}"
226-
)
227-
228-
email_body = "Hi, {} {}! Use link below for reset password {}".format(
229-
user.first_name, user.last_name, absolute_url
230-
)
231-
232-
data = {
233-
"email_body": email_body,
234-
"email_subject": "Reset password",
235-
"to_email": user.email,
236-
}
237-
238-
Email.send_email(data)
201+
reset_email(user, request)
239202

240203
return Response(serializer.data, status=status.HTTP_201_CREATED)
241204

0 commit comments

Comments
 (0)