add chat title #5
6 changed files with 62 additions and 3 deletions
18
apps/chat/migrations/0005_conversation_title.py
Normal file
18
apps/chat/migrations/0005_conversation_title.py
Normal file
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -27,6 +27,7 @@ class Conversation(models.Model):
|
||||||
choices=ConversationStatus.choices,
|
choices=ConversationStatus.choices,
|
||||||
default=ConversationStatus.OPEN,
|
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.
|
# Set only while status is CLOSED; identifies who is allowed to reopen it.
|
||||||
closed_by_uuid = models.UUIDField(null=True, blank=True)
|
closed_by_uuid = models.UUIDField(null=True, blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ from apps.chat.models import Conversation, ConversationParticipant
|
||||||
class CreateConversationSerializer(serializers.Serializer):
|
class CreateConversationSerializer(serializers.Serializer):
|
||||||
user_1_uuid = serializers.UUIDField()
|
user_1_uuid = serializers.UUIDField()
|
||||||
user_2_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):
|
class ConversationUserActionSerializer(serializers.Serializer):
|
||||||
|
|
@ -18,7 +21,15 @@ class ConversationSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Conversation
|
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()))
|
@extend_schema_field(serializers.ListField(child=serializers.UUIDField()))
|
||||||
def get_participants(self, obj):
|
def get_participants(self, obj):
|
||||||
|
|
@ -33,6 +44,7 @@ class ConversationListSerializer(serializers.Serializer):
|
||||||
uuid = serializers.UUIDField()
|
uuid = serializers.UUIDField()
|
||||||
type = serializers.CharField()
|
type = serializers.CharField()
|
||||||
status = serializers.CharField()
|
status = serializers.CharField()
|
||||||
|
title = serializers.CharField(allow_null=True)
|
||||||
closed_by_uuid = serializers.UUIDField(allow_null=True)
|
closed_by_uuid = serializers.UUIDField(allow_null=True)
|
||||||
created_at = serializers.DateTimeField()
|
created_at = serializers.DateTimeField()
|
||||||
participants = serializers.ListField(child=serializers.UUIDField())
|
participants = serializers.ListField(child=serializers.UUIDField())
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ class ConversationService:
|
||||||
self._account = account_service or AccountService()
|
self._account = account_service or AccountService()
|
||||||
self._mm = mattermost_client or MattermostClient()
|
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):
|
if not self._account.validate_user(user_1_uuid):
|
||||||
raise ValueError(f"User {user_1_uuid} is not valid")
|
raise ValueError(f"User {user_1_uuid} is not valid")
|
||||||
if not self._account.validate_user(user_2_uuid):
|
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])
|
channel_id = self._mm.create_private_channel([mm_user_1, mm_user_2])
|
||||||
|
|
||||||
with transaction.atomic():
|
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(
|
ConversationParticipant.objects.create(
|
||||||
conversation=conversation, user_uuid=user_1_uuid
|
conversation=conversation, user_uuid=user_1_uuid
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,28 @@ def test_create_conversation_success():
|
||||||
assert user_2 in participant_uuids
|
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
|
@pytest.mark.django_db
|
||||||
def test_create_conversation_creates_mattermost_mapping():
|
def test_create_conversation_creates_mattermost_mapping():
|
||||||
user_1 = uuid.uuid4()
|
user_1 = uuid.uuid4()
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class ConversationCreateView(APIView):
|
||||||
conversation = ConversationService().create(
|
conversation = ConversationService().create(
|
||||||
serializer.validated_data["user_1_uuid"],
|
serializer.validated_data["user_1_uuid"],
|
||||||
serializer.validated_data["user_2_uuid"],
|
serializer.validated_data["user_2_uuid"],
|
||||||
|
title=serializer.validated_data.get("title"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
|
|
@ -86,6 +87,7 @@ class UserConversationListView(APIView):
|
||||||
"uuid": conv.uuid,
|
"uuid": conv.uuid,
|
||||||
"type": conv.type,
|
"type": conv.type,
|
||||||
"status": conv.status,
|
"status": conv.status,
|
||||||
|
"title": conv.title,
|
||||||
"closed_by_uuid": conv.closed_by_uuid,
|
"closed_by_uuid": conv.closed_by_uuid,
|
||||||
"created_at": conv.created_at,
|
"created_at": conv.created_at,
|
||||||
"participants": participants,
|
"participants": participants,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue