feat: store last message sender in conversation as this will be used to show the Reply icon in the conversations list.

- Introduces new column `last_message_sender` in conversations table.
- Changes to propogate this new column in websocket updates.
This commit is contained in:
Abhinav Raut
2025-02-21 21:40:57 +05:30
parent aba849d344
commit d2a79d9a10
9 changed files with 30 additions and 18 deletions

View File

@@ -39,10 +39,14 @@
<!-- Message preview and unread count --> <!-- Message preview and unread count -->
<div class="flex items-start justify-between gap-2"> <div class="flex items-start justify-between gap-2">
<p class="text-sm text-gray-600 line-clamp-2 flex-1"> <div class="text-sm text-gray-600 flex items-center gap-1.5 flex-1">
<Reply class="inline-block w-4 h-4 mr-1.5 text-green-600 flex-shrink-0" /> <Reply
class="text-green-600 flex-shrink-0"
size="15"
v-if="conversation.last_message_sender === 'agent'"
/>
{{ trimmedLastMessage }} {{ trimmedLastMessage }}
</p> </div>
<div <div
v-if="conversation.unread_message_count > 0" v-if="conversation.unread_message_count > 0"
class="flex items-center justify-center w-6 h-6 bg-green-600 text-white text-xs font-medium rounded-full" class="flex items-center justify-center w-6 h-6 bg-green-600 text-white text-xs font-medium rounded-full"

View File

@@ -545,10 +545,12 @@ export const useConversationStore = defineStore('conversation', () => {
if (listConversation) { if (listConversation) {
listConversation.last_message = message.content listConversation.last_message = message.content
listConversation.last_message_at = message.created_at listConversation.last_message_at = message.created_at
listConversation.last_message_sender = message.sender_type
if (listConversation.uuid !== conversation?.data?.uuid) { if (listConversation.uuid !== conversation?.data?.uuid) {
listConversation.unread_message_count += 1 listConversation.unread_message_count += 1
} }
} else { } else {
// Conversation is not in the list, fetch the first page of the conversations list as this updated conversation might be at the top.
fetchFirstPageConversations() fetchFirstPageConversations()
} }
} }

View File

@@ -55,6 +55,7 @@ export class WebSocketClient {
const data = JSON.parse(event.data) const data = JSON.parse(event.data)
const handlers = { const handlers = {
// On new message, update the message in the conversation list and in the currently opened conversation.
[WS_EVENT.NEW_MESSAGE]: () => { [WS_EVENT.NEW_MESSAGE]: () => {
this.convStore.updateConversationList(data.data) this.convStore.updateConversationList(data.data)
this.convStore.updateConversationMessage(data.data) this.convStore.updateConversationMessage(data.data)

View File

@@ -399,8 +399,8 @@ func (c *Manager) ActiveUserConversationsCount(userID int) (int, error) {
} }
// UpdateConversationLastMessage updates the last message details for a conversation. // UpdateConversationLastMessage updates the last message details for a conversation.
func (c *Manager) UpdateConversationLastMessage(convesationID int, conversationUUID, lastMessage string, lastMessageAt time.Time) error { func (c *Manager) UpdateConversationLastMessage(conversation int, conversationUUID, lastMessage, lastMessageSenderType string, lastMessageAt time.Time) error {
if _, err := c.q.UpdateConversationLastMessage.Exec(convesationID, conversationUUID, lastMessage, lastMessageAt); err != nil { if _, err := c.q.UpdateConversationLastMessage.Exec(conversation, conversationUUID, lastMessage, lastMessageSenderType, lastMessageAt); err != nil {
c.lo.Error("error updating conversation last message", "error", err) c.lo.Error("error updating conversation last message", "error", err)
return err return err
} }

View File

@@ -356,10 +356,10 @@ func (m *Manager) InsertMessage(message *models.Message) error {
} }
// Update conversation last message details in conversation metadata. // Update conversation last message details in conversation metadata.
m.UpdateConversationLastMessage(message.ConversationID, message.ConversationUUID, message.TextContent, message.CreatedAt) m.UpdateConversationLastMessage(message.ConversationID, message.ConversationUUID, message.TextContent, message.SenderType, message.CreatedAt)
// Broadcast new message. // Broadcast new message.
m.BroadcastNewMessage(message.ConversationUUID, message.TextContent, message.UUID, message.CreatedAt.Format(time.RFC3339), message.Type, message.Private) m.BroadcastNewMessage(message)
return nil return nil
} }

View File

@@ -56,6 +56,7 @@ type Conversation struct {
CustomAttributes pq.StringArray `db:"custom_attributes" json:"custom_attributes"` CustomAttributes pq.StringArray `db:"custom_attributes" json:"custom_attributes"`
LastMessageAt null.Time `db:"last_message_at" json:"last_message_at"` LastMessageAt null.Time `db:"last_message_at" json:"last_message_at"`
LastMessage null.String `db:"last_message" json:"last_message"` LastMessage null.String `db:"last_message" json:"last_message"`
LastMessageSender null.String `db:"last_message_sender" json:"last_message_sender"`
Contact umodels.User `db:"contact" json:"contact"` Contact umodels.User `db:"contact" json:"contact"`
SLAPolicyID null.Int `db:"sla_policy_id" json:"sla_policy_id"` SLAPolicyID null.Int `db:"sla_policy_id" json:"sla_policy_id"`
SlaPolicyName null.String `db:"sla_policy_name" json:"sla_policy_name"` SlaPolicyName null.String `db:"sla_policy_name" json:"sla_policy_name"`

View File

@@ -47,6 +47,7 @@ SELECT
conversations.subject, conversations.subject,
conversations.last_message, conversations.last_message,
conversations.last_message_at, conversations.last_message_at,
conversations.last_message_sender,
conversations.next_sla_deadline_at, conversations.next_sla_deadline_at,
conversations.priority_id, conversations.priority_id,
( (
@@ -197,7 +198,7 @@ SET assignee_last_seen_at = now(),
WHERE uuid = $1; WHERE uuid = $1;
-- name: update-conversation-last-message -- name: update-conversation-last-message
UPDATE conversations SET last_message = $3, last_message_at = $4 WHERE CASE UPDATE conversations SET last_message = $3, last_message_sender = $4, last_message_at = $5, updated_at = NOW() WHERE CASE
WHEN $1 > 0 THEN id = $1 WHEN $1 > 0 THEN id = $1
ELSE uuid = $2 ELSE uuid = $2
END END

View File

@@ -2,24 +2,26 @@ package conversation
import ( import (
"encoding/json" "encoding/json"
"time"
cmodels "github.com/abhinavxd/libredesk/internal/conversation/models"
wsmodels "github.com/abhinavxd/libredesk/internal/ws/models" wsmodels "github.com/abhinavxd/libredesk/internal/ws/models"
) )
// BroadcastNewMessage broadcasts a new message to all users. // BroadcastNewMessage broadcasts a new message to all users.
func (m *Manager) BroadcastNewMessage(conversationUUID, content, messageUUID, lastMessageAt, typ string, private bool) { func (m *Manager) BroadcastNewMessage(message *cmodels.Message) {
message := wsmodels.Message{ m.broadcastToUsers([]int{}, wsmodels.Message{
Type: wsmodels.MessageTypeNewMessage, Type: wsmodels.MessageTypeNewMessage,
Data: map[string]interface{}{ Data: map[string]interface{}{
"conversation_uuid": conversationUUID, "conversation_uuid": message.ConversationUUID,
"content": content, "content": message.TextContent,
"created_at": lastMessageAt, "created_at": message.CreatedAt.Format(time.RFC3339),
"uuid": messageUUID, "uuid": message.UUID,
"private": private, "private": message.Private,
"type": typ, "type": message.Type,
"sender_type": message.SenderType,
}, },
} })
m.broadcastToUsers([]int{}, message)
} }
// BroadcastMessageUpdate broadcasts a message update to all users. // BroadcastMessageUpdate broadcasts a message update to all users.

View File

@@ -209,6 +209,7 @@ CREATE TABLE conversations (
waiting_since TIMESTAMPTZ NULL, waiting_since TIMESTAMPTZ NULL,
last_message_at TIMESTAMPTZ NULL, last_message_at TIMESTAMPTZ NULL,
last_message TEXT NULL, last_message TEXT NULL,
last_message_sender message_sender_type NULL,
next_sla_deadline_at TIMESTAMPTZ NULL, next_sla_deadline_at TIMESTAMPTZ NULL,
snoozed_until TIMESTAMPTZ NULL snoozed_until TIMESTAMPTZ NULL
); );