baseapp-backend 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. baseapp-backend-0.0.1/PKG-INFO +12 -0
  2. baseapp-backend-0.0.1/README.md +1 -0
  3. baseapp-backend-0.0.1/baseapp/__init__.py +0 -0
  4. baseapp-backend-0.0.1/baseapp/activity_log/__init__.py +0 -0
  5. baseapp-backend-0.0.1/baseapp/activity_log/admin.py +35 -0
  6. baseapp-backend-0.0.1/baseapp/activity_log/apps.py +8 -0
  7. baseapp-backend-0.0.1/baseapp/activity_log/base.py +218 -0
  8. baseapp-backend-0.0.1/baseapp/activity_log/graphql/__init__.py +0 -0
  9. baseapp-backend-0.0.1/baseapp/activity_log/graphql/filters.py +26 -0
  10. baseapp-backend-0.0.1/baseapp/activity_log/graphql/interfaces.py +46 -0
  11. baseapp-backend-0.0.1/baseapp/activity_log/graphql/mutations.py +284 -0
  12. baseapp-backend-0.0.1/baseapp/activity_log/graphql/object_types.py +69 -0
  13. baseapp-backend-0.0.1/baseapp/activity_log/graphql/queries.py +8 -0
  14. baseapp-backend-0.0.1/baseapp/activity_log/graphql/subscriptions.py +117 -0
  15. baseapp-backend-0.0.1/baseapp/activity_log/middleware.py +84 -0
  16. baseapp-backend-0.0.1/baseapp/activity_log/migrations/0001_initial.py +290 -0
  17. baseapp-backend-0.0.1/baseapp/activity_log/migrations/0002_message_set_last_message_and_more.py +111 -0
  18. baseapp-backend-0.0.1/baseapp/activity_log/migrations/__init__.py +0 -0
  19. baseapp-backend-0.0.1/baseapp/activity_log/models.py +58 -0
  20. baseapp-backend-0.0.1/baseapp/activity_log/permissions.py +89 -0
  21. baseapp-backend-0.0.1/baseapp/activity_log/tests/__init__.py +0 -0
  22. baseapp-backend-0.0.1/baseapp/activity_log/tests/conftest.py +2 -0
  23. baseapp-backend-0.0.1/baseapp/activity_log/tests/factories.py +32 -0
  24. baseapp-backend-0.0.1/baseapp/activity_log/tests/test_graphql_mutations.py +428 -0
  25. baseapp-backend-0.0.1/baseapp/activity_log/tests/test_graphql_queries.py +271 -0
  26. baseapp-backend-0.0.1/baseapp/activity_log/tests/test_graphql_subscriptions.py +187 -0
  27. baseapp-backend-0.0.1/baseapp/activity_log/tests/test_utils.py +70 -0
  28. baseapp-backend-0.0.1/baseapp/activity_log/triggers.py +111 -0
  29. baseapp-backend-0.0.1/baseapp/activity_log/utils.py +65 -0
  30. baseapp-backend-0.0.1/baseapp_backend.egg-info/PKG-INFO +12 -0
  31. baseapp-backend-0.0.1/baseapp_backend.egg-info/SOURCES.txt +41 -0
  32. baseapp-backend-0.0.1/baseapp_backend.egg-info/dependency_links.txt +1 -0
  33. baseapp-backend-0.0.1/baseapp_backend.egg-info/top_level.txt +2 -0
  34. baseapp-backend-0.0.1/pyproject.toml +10 -0
  35. baseapp-backend-0.0.1/setup.cfg +25 -0
  36. baseapp-backend-0.0.1/setup.py +11 -0
  37. baseapp-backend-0.0.1/testproject/__init__.py +0 -0
  38. baseapp-backend-0.0.1/testproject/graphql.py +25 -0
  39. baseapp-backend-0.0.1/testproject/settings.py +56 -0
  40. baseapp-backend-0.0.1/testproject/setup.py +3 -0
  41. baseapp-backend-0.0.1/testproject/urls.py +10 -0
  42. baseapp-backend-0.0.1/testproject/wsgi.py +16 -0
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: baseapp-backend
3
+ Version: 0.0.1
4
+ Summary: BaseApp
5
+ Home-page: https://github.com/silverlogic/baseapp-backend
6
+ Author: The SilverLogic
7
+ Author-email: dev@tsl.io
8
+ License: BSD-3-Clause # Example license
9
+ Description: # BaseApp
10
+ Platform: UNKNOWN
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
@@ -0,0 +1 @@
1
+ # BaseApp
File without changes
File without changes
@@ -0,0 +1,35 @@
1
+ import swapper
2
+ from django.contrib import admin
3
+
4
+ ChatRoom = swapper.load_model("baseapp_chats", "ChatRoom")
5
+ ChatRoomParticipant = swapper.load_model("baseapp_chats", "ChatRoomParticipant")
6
+ Message = swapper.load_model("baseapp_chats", "Message")
7
+
8
+
9
+ class ChatRoomParticipantInline(admin.TabularInline):
10
+ model = ChatRoomParticipant
11
+ extra = 0
12
+
13
+
14
+ @admin.register(ChatRoomParticipant)
15
+ class ChatRoomParticipantAdmin(admin.ModelAdmin):
16
+ list_display = ("profile", "room", "role")
17
+ list_filter = ["created", "role"]
18
+ search_fields = ["profile", "room"]
19
+
20
+
21
+ @admin.register(Message)
22
+ class MessageAdmin(admin.ModelAdmin):
23
+ list_display = ("profile", "verb", "action_object", "room", "timesince")
24
+ list_filter = ["verb"]
25
+ search_fields = ["content", "profile", "action_object", "room"]
26
+
27
+
28
+ @admin.register(ChatRoom)
29
+ class ChatRoomAdmin(admin.ModelAdmin):
30
+ list_display = ("room",)
31
+ search_fields = ["room"]
32
+ inlines = [ChatRoomParticipantInline]
33
+
34
+ def room(self, obj):
35
+ return obj.__str__()
@@ -0,0 +1,8 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class ChatsConfig(AppConfig):
5
+ default = True
6
+ name = "baseapp_chats"
7
+ verbose_name = "BaseApp Chats"
8
+ default_auto_field = "django.db.models.BigAutoField"
@@ -0,0 +1,218 @@
1
+ import swapper
2
+ from baseapp_core.graphql.models import RelayModel
3
+ from baseapp_core.models import random_name_in
4
+ from django.conf import settings
5
+ from django.contrib.contenttypes.fields import GenericForeignKey
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from django.db import models
8
+ from django.utils import timezone
9
+ from django.utils.timesince import timesince as djtimesince
10
+ from django.utils.translation import gettext_lazy as _
11
+ from model_utils.models import TimeStampedModel
12
+
13
+
14
+ class AbstractBaseChatRoom(TimeStampedModel, RelayModel):
15
+ title = models.CharField(max_length=255, blank=True, null=True)
16
+ image = models.ImageField(
17
+ _("image"), upload_to=random_name_in("chat_room_images"), blank=True, null=True
18
+ )
19
+
20
+ created_by = models.ForeignKey(
21
+ settings.AUTH_USER_MODEL, related_name="created_rooms", on_delete=models.CASCADE
22
+ )
23
+ last_message = models.ForeignKey(
24
+ swapper.get_model_name("baseapp_chats", "Message"),
25
+ null=True,
26
+ blank=True,
27
+ on_delete=models.SET_NULL,
28
+ )
29
+ last_message_time = models.DateTimeField(null=True, blank=True, default=timezone.now)
30
+ participants_count = models.IntegerField(default=0)
31
+ messages_count = models.IntegerField(default=0)
32
+
33
+ class Meta:
34
+ abstract = True
35
+ ordering = ["-last_message_time", "-created"]
36
+
37
+ def __str__(self):
38
+ return str(self.pk)
39
+
40
+ @classmethod
41
+ def get_graphql_object_type(cls):
42
+ from .graphql.object_types import ChatRoomObjectType
43
+
44
+ return ChatRoomObjectType
45
+
46
+
47
+ class AbstractBaseMessage(TimeStampedModel, RelayModel):
48
+ class Verbs(models.IntegerChoices):
49
+ SENT_MESSAGE = 100, _("sent a message")
50
+
51
+ @property
52
+ def description(self):
53
+ return self.label
54
+
55
+ content = models.TextField(null=True, blank=True)
56
+
57
+ user = models.ForeignKey(
58
+ settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True
59
+ )
60
+ profile = models.ForeignKey(
61
+ swapper.get_model_name("baseapp_profiles", "Profile"),
62
+ on_delete=models.CASCADE,
63
+ null=True,
64
+ blank=True,
65
+ )
66
+
67
+ verb = models.IntegerField(choices=Verbs.choices, default=Verbs.SENT_MESSAGE, db_index=True)
68
+
69
+ room = models.ForeignKey(
70
+ swapper.get_model_name("baseapp_chats", "ChatRoom"),
71
+ blank=True,
72
+ null=True,
73
+ related_name="messages",
74
+ on_delete=models.CASCADE,
75
+ db_index=True,
76
+ )
77
+
78
+ in_reply_to = models.ForeignKey(
79
+ swapper.get_model_name("baseapp_chats", "Message"),
80
+ on_delete=models.SET_NULL,
81
+ null=True,
82
+ blank=True,
83
+ related_name="replies",
84
+ )
85
+
86
+ action_object_content_type = models.ForeignKey(
87
+ ContentType,
88
+ blank=True,
89
+ null=True,
90
+ related_name="action_object",
91
+ on_delete=models.CASCADE,
92
+ db_index=True,
93
+ )
94
+ action_object_object_id = models.IntegerField(blank=True, null=True, db_index=True)
95
+ action_object = GenericForeignKey("action_object_content_type", "action_object_object_id")
96
+
97
+ extra_data = models.JSONField(blank=True, null=True)
98
+
99
+ class Meta:
100
+ abstract = True
101
+ ordering = ["-created"]
102
+
103
+ def __str__(self):
104
+ ctx = {
105
+ "actor": self.profile,
106
+ "verb": self.__class__.Verbs(self.verb).label,
107
+ "action_object": self.action_object,
108
+ "target": self.room,
109
+ "timesince": self.timesince(),
110
+ }
111
+ if self.room:
112
+ if self.action_object:
113
+ return (
114
+ _("%(actor)s %(verb)s %(action_object)s on %(target)s %(timesince)s ago") % ctx
115
+ )
116
+ return _("%(actor)s %(verb)s on %(target)s %(timesince)s ago") % ctx
117
+ if self.action_object:
118
+ return _("%(actor)s %(verb)s %(action_object)s %(timesince)s ago") % ctx
119
+ return _("%(actor)s %(verb)s %(timesince)s ago") % ctx
120
+
121
+ def timesince(self, now=None):
122
+ """
123
+ Shortcut for the ``django.utils.timesince.timesince`` function of the
124
+ current timestamp.
125
+ """
126
+ return (
127
+ djtimesince(self.created, now).encode("utf8").replace(b"\xc2\xa0", b" ").decode("utf8")
128
+ )
129
+
130
+ @classmethod
131
+ def get_graphql_object_type(cls):
132
+ from .graphql.object_types import MessageObjectType
133
+
134
+ return MessageObjectType
135
+
136
+ def save(self, *args, **kwargs):
137
+ created = self._state.adding
138
+ super().save(*args, **kwargs)
139
+
140
+ if created:
141
+ from baseapp_chats.graphql.subscriptions import (
142
+ ChatRoomOnMessagesCountUpdate,
143
+ )
144
+
145
+ for participant in self.room.participants.all():
146
+ if participant.profile_id != self.profile_id:
147
+ ChatRoomOnMessagesCountUpdate.send_updated_chat_count(
148
+ profile=participant.profile, profile_id=participant.profile.relay_id
149
+ )
150
+
151
+
152
+ class AbstractChatRoomParticipant(TimeStampedModel, RelayModel):
153
+ class ChatRoomParticipantRoles(models.IntegerChoices):
154
+ MEMBER = 1, _("member")
155
+ ADMIM = 2, _("admin")
156
+
157
+ @property
158
+ def description(self):
159
+ return self.label
160
+
161
+ profile = models.ForeignKey(
162
+ swapper.get_model_name("baseapp_profiles", "Profile"),
163
+ on_delete=models.CASCADE,
164
+ null=True,
165
+ blank=True,
166
+ )
167
+ room = models.ForeignKey(
168
+ swapper.get_model_name("baseapp_chats", "ChatRoom"),
169
+ related_name="participants",
170
+ on_delete=models.CASCADE,
171
+ )
172
+ role = models.IntegerField(
173
+ choices=ChatRoomParticipantRoles.choices, default=ChatRoomParticipantRoles.MEMBER
174
+ )
175
+ accepted_at = models.DateTimeField(null=True, blank=True)
176
+
177
+ class Meta:
178
+ abstract = True
179
+ ordering = ["created"]
180
+
181
+ @classmethod
182
+ def get_graphql_object_type(cls):
183
+ from .graphql.object_types import ChatRoomParticipantObjectType
184
+
185
+ return ChatRoomParticipantObjectType
186
+
187
+
188
+ class AbstractUnreadMessageCount(RelayModel):
189
+ room = models.ForeignKey(
190
+ swapper.get_model_name("baseapp_chats", "ChatRoom"),
191
+ on_delete=models.CASCADE,
192
+ )
193
+ profile = models.ForeignKey(
194
+ swapper.get_model_name("baseapp_profiles", "Profile"), on_delete=models.CASCADE
195
+ )
196
+ count = models.IntegerField(default=0)
197
+
198
+ class Meta:
199
+ abstract = True
200
+ ordering = ["id"]
201
+ unique_together = ["room_id", "profile_id"]
202
+
203
+
204
+ class AbstractMessageStatus(RelayModel):
205
+ message = models.ForeignKey(
206
+ swapper.get_model_name("baseapp_chats", "Message"),
207
+ on_delete=models.CASCADE,
208
+ related_name="statuses",
209
+ )
210
+ profile = models.ForeignKey(
211
+ swapper.get_model_name("baseapp_profiles", "Profile"), on_delete=models.CASCADE
212
+ )
213
+ is_read = models.BooleanField(default=False)
214
+ read_at = models.DateTimeField(null=True, blank=True)
215
+
216
+ class Meta:
217
+ abstract = True
218
+ unique_together = ["message_id", "profile_id"]
@@ -0,0 +1,26 @@
1
+ import django_filters
2
+ import swapper
3
+ from baseapp_core.graphql import get_pk_from_relay_id
4
+ from django.db.models import Q
5
+
6
+ ChatRoom = swapper.load_model("baseapp_chats", "ChatRoom")
7
+
8
+
9
+ class ChatRoomFilter(django_filters.FilterSet):
10
+ q = django_filters.CharFilter(method="filter_q")
11
+ profile_id = django_filters.CharFilter(method="filter_profile_id")
12
+
13
+ order_by = django_filters.OrderingFilter(fields=(("created", "created"),))
14
+
15
+ class Meta:
16
+ model = ChatRoom
17
+ fields = ["q", "order_by"]
18
+
19
+ def filter_q(self, queryset, name, value):
20
+ return queryset.filter(
21
+ Q(title__icontains=value) | Q(participants__profile__name__icontains=value)
22
+ )
23
+
24
+ def filter_profile_id(self, queryset, name, value):
25
+ pk = get_pk_from_relay_id(value)
26
+ return queryset.filter(participants__profile_id=pk)
@@ -0,0 +1,46 @@
1
+ import graphene
2
+ import swapper
3
+ from baseapp_core.graphql import get_object_type_for_model
4
+ from django.db.models import Sum
5
+ from graphene import relay
6
+ from graphene_django.filter import DjangoFilterConnectionField
7
+
8
+ ChatRoom = swapper.load_model("baseapp_chats", "ChatRoom")
9
+ UnreadMessageCount = swapper.load_model("baseapp_chats", "UnreadMessageCount")
10
+ Block = swapper.load_model("baseapp_blocks", "Block")
11
+
12
+
13
+ class ActivityLogInterface(relay.Node):
14
+ activity_logs = DjangoFilterConnectionField(ActivityEventObjectType)
15
+
16
+ def resolve_chat_rooms(self, info, **kwargs):
17
+ if not info.context.user.has_perm("baseapp_chats.list_chatrooms", self):
18
+ return ChatRoom.objects.none()
19
+
20
+ qs = ChatRoom.objects.filter(
21
+ participants__profile_id=self.pk,
22
+ ).order_by("-last_message_time", "-created")
23
+
24
+ # Exclude rooms with any blocked participant
25
+ blocking_profile_ids = Block.objects.filter(actor_id=self.pk).values_list(
26
+ "target_id", flat=True
27
+ )
28
+ qs = qs.exclude(participants__profile_id__in=blocking_profile_ids)
29
+
30
+ # Exclude rooms with any participant that blocks the profile (self)
31
+ blocker_profile_ids = Block.objects.filter(target_id=self.pk).values_list(
32
+ "actor_id", flat=True
33
+ )
34
+ qs = qs.exclude(participants__profile_id__in=blocker_profile_ids)
35
+
36
+ return qs
37
+
38
+ def resolve_unread_messages_count(self, info, **kwargs):
39
+ if not info.context.user.has_perm("baseapp_chats.list_chatrooms", self):
40
+ return None
41
+
42
+ aggregate_result = UnreadMessageCount.objects.filter(
43
+ profile_id=self.pk,
44
+ ).aggregate(total_count=Sum("count"))
45
+
46
+ return aggregate_result["total_count"] or 0
@@ -0,0 +1,284 @@
1
+ import graphene
2
+ import swapper
3
+ from baseapp_core.graphql import (
4
+ RelayMutation,
5
+ get_obj_from_relay_id,
6
+ get_pk_from_relay_id,
7
+ login_required,
8
+ )
9
+ from django.contrib.auth import get_user_model
10
+ from django.db.models import Count, Q
11
+ from django.utils import timezone
12
+ from django.utils.translation import gettext_lazy as _
13
+ from graphene_django.types import ErrorType
14
+
15
+ from baseapp_chats.graphql.subscriptions import ChatRoomOnMessagesCountUpdate
16
+ from baseapp_chats.utils import send_message, send_new_chat_message_notification
17
+
18
+ ChatRoom = swapper.load_model("baseapp_chats", "ChatRoom")
19
+ ChatRoomParticipant = swapper.load_model("baseapp_chats", "ChatRoomParticipant")
20
+ Message = swapper.load_model("baseapp_chats", "Message")
21
+ MessageStatus = swapper.load_model("baseapp_chats", "MessageStatus")
22
+ Block = swapper.load_model("baseapp_blocks", "Block")
23
+ User = get_user_model()
24
+ Profile = swapper.load_model("baseapp_profiles", "Profile")
25
+
26
+
27
+ ChatRoomObjectType = ChatRoom.get_graphql_object_type()
28
+ ProfileObjectType = Profile.get_graphql_object_type()
29
+ MessageObjectType = Message.get_graphql_object_type()
30
+
31
+
32
+ class ChatRoomCreate(RelayMutation):
33
+ room = graphene.Field(ChatRoomObjectType._meta.connection.Edge)
34
+ profile = graphene.Field(ProfileObjectType)
35
+
36
+ class Input:
37
+ profile_id = graphene.ID(required=True)
38
+ participants = graphene.List(graphene.ID, required=True)
39
+
40
+ @classmethod
41
+ @login_required
42
+ def mutate_and_get_payload(cls, root, info, profile_id, participants, **input):
43
+ profile = get_obj_from_relay_id(info, profile_id)
44
+
45
+ if not info.context.user.has_perm("baseapp_profiles.use_profile", profile):
46
+ return ChatRoomCreate(
47
+ errors=[
48
+ ErrorType(
49
+ field="profile_id",
50
+ messages=[_("You don't have permission to send a message as this profile")],
51
+ )
52
+ ]
53
+ )
54
+
55
+ participants = [get_obj_from_relay_id(info, participant) for participant in participants]
56
+ participants = [participant for participant in participants if participant is not None]
57
+ participants_ids = [participant.pk for participant in participants]
58
+
59
+ # Check if participants are blocked
60
+ if Block.objects.filter(
61
+ Q(actor_id=profile.id, target_id__in=participants_ids)
62
+ | Q(actor_id__in=participants_ids, target_id=profile.id)
63
+ ).exists():
64
+ return ChatRoomCreate(
65
+ errors=[
66
+ ErrorType(
67
+ field="participants",
68
+ messages=[_("You can't create a chatroom with those participants")],
69
+ )
70
+ ]
71
+ )
72
+
73
+ participants.append(profile)
74
+
75
+ if len(participants) < 2:
76
+ return ChatRoomCreate(
77
+ errors=[
78
+ ErrorType(
79
+ field="participants",
80
+ messages=[_("You need add at least one participant")],
81
+ )
82
+ ]
83
+ )
84
+
85
+ if not info.context.user.has_perm(
86
+ "baseapp_chats.add_chatroom", {"profile": profile, "participants": participants}
87
+ ):
88
+ return ChatRoomCreate(
89
+ errors=[
90
+ ErrorType(
91
+ field="participants",
92
+ messages=[_("You don't have permission to create a room")],
93
+ )
94
+ ]
95
+ )
96
+
97
+ query_set = ChatRoom.objects.annotate(count=Count("participants")).filter(
98
+ count=len(participants)
99
+ )
100
+ for participant in participants:
101
+ query_set = query_set.filter(
102
+ participants__profile_id=participant.id,
103
+ )
104
+ existent_room = query_set.first()
105
+
106
+ if existent_room:
107
+ return ChatRoomCreate(
108
+ profile=profile,
109
+ room=ChatRoomObjectType._meta.connection.Edge(
110
+ node=existent_room,
111
+ ),
112
+ )
113
+
114
+ room = ChatRoom.objects.create(
115
+ created_by=info.context.user, last_message_time=timezone.now()
116
+ )
117
+ for participant in participants:
118
+ ChatRoomParticipant.objects.create(profile=participant, room=room)
119
+
120
+ return ChatRoomCreate(
121
+ profile=profile,
122
+ room=ChatRoomObjectType._meta.connection.Edge(
123
+ node=room,
124
+ ),
125
+ )
126
+
127
+
128
+ class ChatRoomSendMessage(RelayMutation):
129
+ message = graphene.Field(MessageObjectType._meta.connection.Edge)
130
+
131
+ class Input:
132
+ room_id = graphene.ID(required=True)
133
+ profile_id = graphene.ID(required=True)
134
+ content = graphene.String(required=True)
135
+ in_reply_to_id = graphene.ID(required=False)
136
+
137
+ @classmethod
138
+ @login_required
139
+ def mutate_and_get_payload(
140
+ cls, root, info, room_id, content, profile_id, in_reply_to_id=None, **input
141
+ ):
142
+ room = get_obj_from_relay_id(info, room_id)
143
+ profile = get_obj_from_relay_id(info, profile_id)
144
+
145
+ in_reply_to = None
146
+ if in_reply_to_id:
147
+ in_reply_to_pk = get_pk_from_relay_id(in_reply_to_id)
148
+ in_reply_to = Message.objects.filter(
149
+ pk=in_reply_to_pk,
150
+ room=room,
151
+ ).first()
152
+
153
+ if not info.context.user.has_perm("baseapp_profiles.use_profile", profile):
154
+ return ChatRoomSendMessage(
155
+ errors=[
156
+ ErrorType(
157
+ field="profile_id",
158
+ messages=[_("You don't have permission to send a message as this profile")],
159
+ )
160
+ ]
161
+ )
162
+
163
+ if not room or not info.context.user.has_perm(
164
+ "baseapp_chats.add_message", {"profile": profile, "room": room}
165
+ ):
166
+ return ChatRoomSendMessage(
167
+ errors=[
168
+ ErrorType(
169
+ field="room_id",
170
+ messages=[_("You don't have permission to send a message in this room")],
171
+ )
172
+ ]
173
+ )
174
+
175
+ if len(content) < 1:
176
+ return ChatRoomSendMessage(
177
+ errors=[
178
+ ErrorType(
179
+ field="content",
180
+ messages=[_("You need to write something")],
181
+ )
182
+ ]
183
+ )
184
+
185
+ if len(content) > 1000:
186
+ return ChatRoomSendMessage(
187
+ errors=[
188
+ ErrorType(
189
+ field="content",
190
+ messages=[_("You can't write more than 1000 characters")],
191
+ )
192
+ ]
193
+ )
194
+
195
+ message = send_message(
196
+ profile=profile,
197
+ user=info.context.user,
198
+ content=content,
199
+ room=room,
200
+ verb=Message.Verbs.SENT_MESSAGE,
201
+ room_id=room_id,
202
+ in_reply_to=in_reply_to,
203
+ )
204
+
205
+ send_new_chat_message_notification(room, message, info)
206
+
207
+ return ChatRoomSendMessage(
208
+ message=MessageObjectType._meta.connection.Edge(
209
+ node=message,
210
+ )
211
+ )
212
+
213
+
214
+ class ChatRoomReadMessages(RelayMutation):
215
+ room = graphene.Field(ChatRoomObjectType)
216
+ profile = graphene.Field(ProfileObjectType)
217
+ messages = graphene.List(MessageObjectType)
218
+
219
+ class Input:
220
+ room_id = graphene.ID(required=True)
221
+ profile_id = graphene.ID(required=True)
222
+ message_ids = graphene.List(graphene.ID, required=False)
223
+
224
+ @classmethod
225
+ @login_required
226
+ def mutate_and_get_payload(cls, root, info, room_id, profile_id, message_ids=None, **input):
227
+ room = get_obj_from_relay_id(info, room_id)
228
+ profile = get_obj_from_relay_id(info, profile_id)
229
+
230
+ if not info.context.user.has_perm("baseapp_profiles.use_profile", profile):
231
+ return ChatRoomReadMessages(
232
+ errors=[
233
+ ErrorType(
234
+ field="profile_id",
235
+ messages=[_("You don't have permission to act as this profile")],
236
+ )
237
+ ]
238
+ )
239
+
240
+ if not ChatRoomParticipant.objects.filter(
241
+ profile_id=profile.pk,
242
+ room=room,
243
+ ).exists():
244
+ return ChatRoomReadMessages(
245
+ errors=[
246
+ ErrorType(
247
+ field="participant",
248
+ messages=[_("Participant is not part of the room.")],
249
+ )
250
+ ]
251
+ )
252
+
253
+ messages_status_qs = MessageStatus.objects.filter(
254
+ profile_id=profile.pk,
255
+ is_read=False,
256
+ )
257
+
258
+ if message_ids:
259
+ message_ids = [get_pk_from_relay_id(message_id) for message_id in message_ids]
260
+ messages_status_qs = messages_status_qs.filter(
261
+ message_id__in=message_ids,
262
+ )
263
+ else:
264
+ messages_status_qs = messages_status_qs.filter(
265
+ message__room_id=room.pk,
266
+ )
267
+
268
+ messages = Message.objects.filter(
269
+ pk__in=messages_status_qs.values_list("message_id", flat=True)
270
+ )
271
+
272
+ messages_status_qs.update(is_read=True, read_at=timezone.now())
273
+
274
+ ChatRoomOnMessagesCountUpdate.send_updated_chat_count(
275
+ profile=profile, profile_id=profile.relay_id
276
+ )
277
+
278
+ return ChatRoomReadMessages(room=room, profile=profile, messages=messages)
279
+
280
+
281
+ class ChatsMutations(object):
282
+ chat_room_create = ChatRoomCreate.Field()
283
+ chat_room_send_message = ChatRoomSendMessage.Field()
284
+ chat_room_read_messages = ChatRoomReadMessages.Field()