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
2 changes: 1 addition & 1 deletion baseapp-chats/baseapp_chats/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MessageAdmin(admin.ModelAdmin):

@admin.register(ChatRoom)
class ChatRoomAdmin(admin.ModelAdmin):
list_display = ("room",)
list_display = ("room", "title", "is_group")
search_fields = ["room"]
inlines = [ChatRoomParticipantInline]

Expand Down
1 change: 1 addition & 0 deletions baseapp-chats/baseapp_chats/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def description(self):
)
action_object_object_id = models.IntegerField(blank=True, null=True, db_index=True)
action_object = GenericForeignKey("action_object_content_type", "action_object_object_id")
deleted = models.BooleanField(default=False)

extra_data = models.JSONField(blank=True, null=True)

Expand Down
71 changes: 69 additions & 2 deletions baseapp-chats/baseapp_chats/graphql/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from rest_framework import serializers

from baseapp_chats.graphql.subscriptions import (
ChatRoomOnMessage,
ChatRoomOnMessagesCountUpdate,
ChatRoomOnNewMessage,
ChatRoomOnRoomUpdate,
)
from baseapp_chats.utils import (
Expand Down Expand Up @@ -496,7 +496,7 @@ def mutate_and_get_payload(cls, root, info, **input):
message.content = content
message.save(update_fields=["content"])

ChatRoomOnNewMessage.new_message(room_id=message.room.relay_id, message=message)
ChatRoomOnMessage.edit_message(room_id=message.room.relay_id, message=message)

return ChatRoomEditMessage(
message=MessageObjectType._meta.connection.Edge(
Expand All @@ -505,6 +505,72 @@ def mutate_and_get_payload(cls, root, info, **input):
)


class ChatRoomDeleteMessage(RelayMutation):
deleted_message = graphene.Field(MessageObjectType._meta.connection.Edge)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to return the deleted_message, you are required to return only its relay ID to make relay in the frontend remove the message from the store. This follows better the relay/graphql pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we're doing a soft delete, so this is more like a message update than an actual deletion. We're just updating the deleted flag so we can replace the content of the message with the default deletion copy.


class Input:
id = graphene.ID(required=True)

@classmethod
@login_required
def mutate_and_get_payload(cls, root, info, **input):
pk = get_pk_from_relay_id(input.get("id"))
try:
message = Message.objects.get(pk=pk)
except Message.DoesNotExist:
return ChatRoomDeleteMessage(
errors=[
ErrorType(
field="id",
messages=[_("Message does not exist")],
)
]
)

if message.deleted:
return ChatRoomDeleteMessage(
errors=[
ErrorType(
field="deleted",
messages=[_("This message has already been deleted")],
)
]
)

profile = (
info.context.user.current_profile
if hasattr(info.context.user, "current_profile")
else (info.context.user.profile if hasattr(info.context.user, "profile") else None)
)

if not info.context.user.has_perm(
"baseapp_chats.delete_message",
{
"profile": profile,
"message": message,
},
):
return ChatRoomDeleteMessage(
errors=[
ErrorType(
field="id",
messages=[_("You don't have permission to update this message")],
)
]
)

message.deleted = True
message.save(update_fields=["deleted"])

ChatRoomOnMessage.edit_message(room_id=message.room.relay_id, message=message)

return ChatRoomDeleteMessage(
deleted_message=MessageObjectType._meta.connection.Edge(
node=message,
)
)


class ChatRoomReadMessages(RelayMutation):
room = graphene.Field(ChatRoomObjectType)
profile = graphene.Field(ProfileObjectType)
Expand Down Expand Up @@ -683,6 +749,7 @@ class ChatsMutations(object):
chat_room_update = ChatRoomUpdate.Field()
chat_room_send_message = ChatRoomSendMessage.Field()
chat_room_edit_message = ChatRoomEditMessage.Field()
chat_room_delete_message = ChatRoomDeleteMessage.Field()
chat_room_read_messages = ChatRoomReadMessages.Field()
chat_room_unread = ChatRoomUnread.Field()
chat_room_archive = ChatRoomArchive.Field()
5 changes: 5 additions & 0 deletions baseapp-chats/baseapp_chats/graphql/object_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Meta:
"extra_data",
"in_reply_to",
"is_read",
"deleted",
)
filter_fields = ("verb",)

Expand Down Expand Up @@ -109,6 +110,10 @@ def resolve_content(root, info, profile_id=None, **kwargs):
profile_pk = BaseMessageObjectType.get_profile_pk(info, profile_id)
if not profile_pk:
return None
if root.deleted:
if profile_pk == root.profile.pk:
return "You deleted this message"
return "This message was deleted"
if root.message_type == Message.MessageType.USER_MESSAGE:
return root.content

Expand Down
13 changes: 10 additions & 3 deletions baseapp-chats/baseapp_chats/graphql/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def send_updated_chat_count(cls, profile, profile_id):
)


class ChatRoomOnNewMessage(channels_graphql_ws.Subscription):
class ChatRoomOnMessage(channels_graphql_ws.Subscription):
message = graphene.Field(lambda: MessageObjectType._meta.connection.Edge)

class Arguments:
Expand All @@ -110,7 +110,7 @@ def publish(payload, info, room_id):
if not user.is_authenticated:
return None

return ChatRoomOnNewMessage(message=MessageObjectType._meta.connection.Edge(node=message))
return ChatRoomOnMessage(message=MessageObjectType._meta.connection.Edge(node=message))

@classmethod
def new_message(cls, message, room_id):
Expand All @@ -120,8 +120,15 @@ def new_message(cls, message, room_id):
)
ChatRoomOnRoomUpdate.new_message(message=message)

@classmethod
def edit_message(cls, message, room_id):
cls.broadcast(
group=room_id,
payload={"message": message},
)


class ChatsSubscriptions:
chat_room_on_new_message = ChatRoomOnNewMessage.Field()
chat_room_on_message = ChatRoomOnMessage.Field()
chat_room_on_room_update = ChatRoomOnRoomUpdate.Field()
chat_room_on_messages_count_update = ChatRoomOnMessagesCountUpdate.Field()
18 changes: 18 additions & 0 deletions baseapp-chats/baseapp_chats/migrations/0010_message_deleted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.10 on 2025-02-04 17:31

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("baseapp_chats", "0009_remove_message_create_message_status_and_more"),
]

operations = [
migrations.AddField(
model_name="message",
name="deleted",
field=models.BooleanField(default=False),
),
]
4 changes: 3 additions & 1 deletion baseapp-chats/baseapp_chats/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ def has_perm(self, user_obj, perm, obj=None):

return room.participants.filter(profile_id__in=my_profile_ids).exists()

if perm == "baseapp_chats.change_message" and user_obj.is_authenticated:
if (
perm == "baseapp_chats.change_message" or perm == "baseapp_chats.delete_message"
) and user_obj.is_authenticated:
profile = obj.get("profile", None)
message = obj.get("message", None)
if profile and message and isinstance(message, Message):
Expand Down
76 changes: 76 additions & 0 deletions baseapp-chats/baseapp_chats/tests/test_graphql_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,23 @@
}
"""

DELETE_MESSAGE_GRAPHQL = """
mutation DeleteMessageMutation($input: ChatRoomDeleteMessageInput!) {
chatRoomDeleteMessage(input: $input) {
deletedMessage {
node {
id
deleted
}
}
errors {
field
messages
}
}
}
"""


def test_user_can_read_all_messages(graphql_user_client, django_user_client):
room = ChatRoomFactory(created_by=django_user_client.user)
Expand Down Expand Up @@ -1281,3 +1298,62 @@ def test_member_user_cannot_remove_other_members(
content["data"]["chatRoomUpdate"]["errors"][0]["messages"][0]
== "You don't have permission to update this room"
)


def test_user_can_delete_own_message(graphql_user_client, django_user_client):
room = ChatRoomFactory(created_by=django_user_client.user)

my_profile = django_user_client.user.profile

ChatRoomParticipantFactory(room=room, profile=my_profile)
my_messages = MessageFactory.create_batch(
2, room=room, profile=my_profile, user=my_profile.owner
)

# Unread chat and check it is marked unread
response = graphql_user_client(
DELETE_MESSAGE_GRAPHQL,
variables={
"input": {
"id": my_messages[0].relay_id,
},
},
)

my_messages[0].refresh_from_db()
my_messages[1].refresh_from_db()
content = response.json()
assert (
content["data"]["chatRoomDeleteMessage"]["deletedMessage"]["node"]["id"]
== my_messages[0].relay_id
)
assert content["data"]["chatRoomDeleteMessage"]["deletedMessage"]["node"]["deleted"] is True
assert my_messages[0].deleted is True
assert my_messages[1].deleted is False


def test_user_cant_delete_other_users_message(graphql_user_client, django_user_client):
room = ChatRoomFactory(created_by=django_user_client.user)

my_profile = django_user_client.user.profile

ChatRoomParticipantFactory(room=room, profile=my_profile)
other_participant = ChatRoomParticipantFactory(room=room)
other_users_messages = MessageFactory.create_batch(
2, room=room, profile=other_participant.profile, user=other_participant.profile.owner
)

# Unread chat and check it is marked unread
response = graphql_user_client(
DELETE_MESSAGE_GRAPHQL,
variables={
"input": {
"id": other_users_messages[0].relay_id,
},
},
)
content = response.json()
assert (
content["data"]["chatRoomDelete"]["errors"][0]["messages"][0]
== "You don't have permission to update this room"
)
5 changes: 2 additions & 3 deletions baseapp-chats/baseapp_chats/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ def send_message(
action_object=action_object,
extra_data=extra_data,
)

from baseapp_chats.graphql.subscriptions import ChatRoomOnNewMessage
from baseapp_chats.graphql.subscriptions import ChatRoomOnMessage

room.last_message_time = message.created
room.last_message = message
room.save()

ChatRoomOnNewMessage.new_message(room_id=room_id or room.relay_id, message=message)
ChatRoomOnMessage.new_message(room_id=room_id or room.relay_id, message=message)

return message

Expand Down
2 changes: 1 addition & 1 deletion baseapp-chats/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = baseapp_chats
version = 0.0.14
version = 0.0.15
description = BaseApp Chats
long_description = file: README.md
url = https://github.com/silverlogic/baseapp-backend
Expand Down
Loading