Restore reactions, add constraint, handle errors

Restore soft-deleted message reactions and enforce uniqueness only for active reactions; add WebSocket error handling and minor UI/Docker tweaks.

- backend/social/chat/models.py: Toggle reaction now restores a stale soft-deleted MessageReaction (avoids unique conflicts) and creates new reactions as needed. Replaced unique_together with a conditional UniqueConstraint that applies only to non-deleted records.
- backend/social/chat/consumers.py: Wrap reaction toggle in try/except to return a WS error message on failure instead of allowing exceptions to bubble up.
- frontend/src/components/social/chat/Message.tsx: Adjusted Tailwind max-width class for the reaction menu (max-w-32).
- docker-compose.yml: Added commented example configuration for an optional Janus media server (documentational/commented service).

These changes prevent unique constraint errors when restoring reactions, improve robustness of the WebSocket reaction flow, and include small UI and deployment notes.
This commit is contained in:
David Bruno Vontor
2026-06-04 16:45:16 +02:00
parent 3859659b13
commit b1f88ca501
4 changed files with 45 additions and 17 deletions

View File

@@ -74,11 +74,16 @@ class ChatConsumer(AsyncWebsocketConsumer):
})
elif msg_type == "reaction":
action, reaction = await _toggle_reaction(
message_id=data["message_id"],
user=user,
emoji=data["emoji"],
)
try:
action, _reaction = await _toggle_reaction(
message_id=data["message_id"],
user=user,
emoji=data["emoji"],
)
except Exception:
await self.send(text_data=json.dumps({"error": "Reaction failed."}))
return
await self.channel_layer.group_send(self.chat_name, {
"type": "message.reaction",
"message_id": data["message_id"],

View File

@@ -142,24 +142,26 @@ class Message(SoftDeleteModel):
"""
try:
reaction = MessageReaction.objects.get(message=self, user=user)
if reaction.emoji == emoji:
# Same emoji -> Remove it (Toggle)
reaction.delete()
return 'removed', None
else:
# Different emoji -> Switch it
reaction.emoji = emoji
reaction.save()
return 'switched', reaction
except MessageReaction.DoesNotExist:
# New reaction -> Create it
reaction = MessageReaction.objects.create(
message=self,
user=user,
emoji=emoji
)
# Restore a stale soft-deleted record if one exists (avoids unique_together violation).
stale = MessageReaction.all_objects.filter(message=self, user=user).first()
if stale:
stale.emoji = emoji
stale.is_deleted = False
stale.deleted_at = None
stale.save()
return 'added', stale
reaction = MessageReaction.objects.create(message=self, user=user, emoji=emoji)
return 'added', reaction
def __str__(self):
@@ -190,7 +192,13 @@ class MessageReaction(SoftDeleteModel):
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('message', 'user')
constraints = [
models.UniqueConstraint(
fields=["message", "user"],
condition=models.Q(is_deleted=False),
name="unique_active_reaction_per_user_message",
)
]
def __str__(self):
return f"{self.user} reacted {self.emoji}"