diff --git a/apps/chat/migrations/0005_conversation_title.py b/apps/chat/migrations/0005_conversation_title.py new file mode 100644 index 0000000..9503a7a --- /dev/null +++ b/apps/chat/migrations/0005_conversation_title.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.13 on 2026-07-25 09:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('chat', '0004_conversationreport'), + ] + + operations = [ + migrations.AddField( + model_name='conversation', + name='title', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/apps/chat/models/conversation.py b/apps/chat/models/conversation.py index 2f67bc1..6f6ec7d 100644 --- a/apps/chat/models/conversation.py +++ b/apps/chat/models/conversation.py @@ -27,6 +27,7 @@ class Conversation(models.Model): choices=ConversationStatus.choices, default=ConversationStatus.OPEN, ) + title = models.CharField(max_length=255, null=True, blank=True) # Set only while status is CLOSED; identifies who is allowed to reopen it. closed_by_uuid = models.UUIDField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) diff --git a/apps/chat/serializers/conversations.py b/apps/chat/serializers/conversations.py index d757ecf..05dd10a 100644 --- a/apps/chat/serializers/conversations.py +++ b/apps/chat/serializers/conversations.py @@ -7,6 +7,9 @@ from apps.chat.models import Conversation, ConversationParticipant class CreateConversationSerializer(serializers.Serializer): user_1_uuid = serializers.UUIDField() user_2_uuid = serializers.UUIDField() + title = serializers.CharField( + max_length=255, required=False, allow_null=True, allow_blank=True + ) class ConversationUserActionSerializer(serializers.Serializer): @@ -18,7 +21,15 @@ class ConversationSerializer(serializers.ModelSerializer): class Meta: model = Conversation - fields = ["uuid", "type", "status", "closed_by_uuid", "created_at", "participants"] + fields = [ + "uuid", + "type", + "status", + "title", + "closed_by_uuid", + "created_at", + "participants", + ] @extend_schema_field(serializers.ListField(child=serializers.UUIDField())) def get_participants(self, obj): @@ -33,6 +44,7 @@ class ConversationListSerializer(serializers.Serializer): uuid = serializers.UUIDField() type = serializers.CharField() status = serializers.CharField() + title = serializers.CharField(allow_null=True) closed_by_uuid = serializers.UUIDField(allow_null=True) created_at = serializers.DateTimeField() participants = serializers.ListField(child=serializers.UUIDField()) diff --git a/apps/chat/services/conversation.py b/apps/chat/services/conversation.py index b11fe9b..6c142b2 100644 --- a/apps/chat/services/conversation.py +++ b/apps/chat/services/conversation.py @@ -21,7 +21,9 @@ class ConversationService: self._account = account_service or AccountService() self._mm = mattermost_client or MattermostClient() - def create(self, user_1_uuid: UUID, user_2_uuid: UUID) -> Conversation: + def create( + self, user_1_uuid: UUID, user_2_uuid: UUID, title: str | None = None + ) -> Conversation: if not self._account.validate_user(user_1_uuid): raise ValueError(f"User {user_1_uuid} is not valid") if not self._account.validate_user(user_2_uuid): @@ -42,7 +44,9 @@ class ConversationService: channel_id = self._mm.create_private_channel([mm_user_1, mm_user_2]) with transaction.atomic(): - conversation = Conversation.objects.create(mattermost_channel_id=channel_id) + conversation = Conversation.objects.create( + mattermost_channel_id=channel_id, title=title + ) ConversationParticipant.objects.create( conversation=conversation, user_uuid=user_1_uuid ) diff --git a/apps/chat/tests/test_conversations.py b/apps/chat/tests/test_conversations.py index 278a14e..53dfdb1 100644 --- a/apps/chat/tests/test_conversations.py +++ b/apps/chat/tests/test_conversations.py @@ -35,6 +35,28 @@ def test_create_conversation_success(): assert user_2 in participant_uuids +@pytest.mark.django_db +def test_create_conversation_stores_title(): + user_1 = uuid.uuid4() + user_2 = uuid.uuid4() + + svc = ConversationService(mattermost_client=_make_mm_client()) + conv = svc.create(user_1, user_2, title="Support request") + + assert Conversation.objects.get(uuid=conv.uuid).title == "Support request" + + +@pytest.mark.django_db +def test_create_conversation_without_title_defaults_to_none(): + user_1 = uuid.uuid4() + user_2 = uuid.uuid4() + + svc = ConversationService(mattermost_client=_make_mm_client()) + conv = svc.create(user_1, user_2) + + assert Conversation.objects.get(uuid=conv.uuid).title is None + + @pytest.mark.django_db def test_create_conversation_creates_mattermost_mapping(): user_1 = uuid.uuid4() diff --git a/apps/chat/views/conversations.py b/apps/chat/views/conversations.py index f1dc8e1..a2aef93 100644 --- a/apps/chat/views/conversations.py +++ b/apps/chat/views/conversations.py @@ -32,6 +32,7 @@ class ConversationCreateView(APIView): conversation = ConversationService().create( serializer.validated_data["user_1_uuid"], serializer.validated_data["user_2_uuid"], + title=serializer.validated_data.get("title"), ) return Response( @@ -86,6 +87,7 @@ class UserConversationListView(APIView): "uuid": conv.uuid, "type": conv.type, "status": conv.status, + "title": conv.title, "closed_by_uuid": conv.closed_by_uuid, "created_at": conv.created_at, "participants": participants,