return created message in message fetch API

This commit is contained in:
Abhinav Raut
2025-07-06 19:51:44 +05:30
parent 4203b82e90
commit ccc5940dd9
5 changed files with 67 additions and 37 deletions

View File

@@ -744,7 +744,7 @@ func handleCreateConversation(r *fastglue.Request) error {
} }
// Send reply to the created conversation. // Send reply to the created conversation.
if err := app.conversation.SendReply(media, req.InboxID, auser.ID /**sender_id**/, conversationUUID, req.Content, to, nil /**cc**/, nil /**bcc**/, map[string]any{} /**meta**/); err != nil { if _, err := app.conversation.SendReply(media, req.InboxID, auser.ID /**sender_id**/, conversationUUID, req.Content, to, nil /**cc**/, nil /**bcc**/, map[string]any{} /**meta**/); err != nil {
// Delete the conversation if reply fails. // Delete the conversation if reply fails.
if err := app.conversation.DeleteConversation(conversationUUID); err != nil { if err := app.conversation.DeleteConversation(conversationUUID); err != nil {
app.lo.Error("error deleting conversation", "error", err) app.lo.Error("error deleting conversation", "error", err)

View File

@@ -162,13 +162,15 @@ func handleSendMessage(r *fastglue.Request) error {
} }
if req.Private { if req.Private {
if err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message); err != nil { message, err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message)
return sendErrorEnvelope(r, err) if err != nil {
}
} else {
if err := app.conversation.SendReply(media, conv.InboxID, user.ID, cuuid, req.Message, req.To, req.CC, req.BCC, map[string]any{} /**meta**/); err != nil {
return sendErrorEnvelope(r, err) return sendErrorEnvelope(r, err)
} }
return r.SendEnvelope(message)
} }
return r.SendEnvelope(true) message, err := app.conversation.SendReply(media, conv.InboxID, user.ID, cuuid, req.Message, req.To, req.CC, req.BCC, map[string]any{} /**meta**/)
if err != nil {
return sendErrorEnvelope(r, err)
}
return r.SendEnvelope(message)
} }

View File

@@ -920,14 +920,17 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
} }
return m.UpdateConversationStatus(conv.UUID, statusID, "", "", user) return m.UpdateConversationStatus(conv.UUID, statusID, "", "", user)
case amodels.ActionSendPrivateNote: case amodels.ActionSendPrivateNote:
return m.SendPrivateNote([]mmodels.Media{}, user.ID, conv.UUID, action.Value[0]) _, err := m.SendPrivateNote([]mmodels.Media{}, user.ID, conv.UUID, action.Value[0])
if err != nil {
return fmt.Errorf("sending private note: %w", err)
}
case amodels.ActionReply: case amodels.ActionReply:
// Make recipient list. // Make recipient list.
to, cc, bcc, err := m.makeRecipients(conv.ID, conv.Contact.Email.String, conv.InboxMail) to, cc, bcc, err := m.makeRecipients(conv.ID, conv.Contact.Email.String, conv.InboxMail)
if err != nil { if err != nil {
return fmt.Errorf("making recipients for reply action: %w", err) return fmt.Errorf("making recipients for reply action: %w", err)
} }
return m.SendReply( _, err = m.SendReply(
[]mmodels.Media{}, []mmodels.Media{},
conv.InboxID, conv.InboxID,
user.ID, user.ID,
@@ -938,6 +941,9 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
bcc, bcc,
map[string]any{}, /**meta**/ map[string]any{}, /**meta**/
) )
if err != nil {
return fmt.Errorf("sending reply: %w", err)
}
case amodels.ActionSetSLA: case amodels.ActionSetSLA:
slaID, err := strconv.Atoi(action.Value[0]) slaID, err := strconv.Atoi(action.Value[0])
if err != nil { if err != nil {
@@ -951,6 +957,7 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
default: default:
return fmt.Errorf("unknown action: %s", action.Type) return fmt.Errorf("unknown action: %s", action.Type)
} }
return nil
} }
// RemoveConversationAssignee removes the assignee from the conversation. // RemoveConversationAssignee removes the assignee from the conversation.
@@ -991,10 +998,16 @@ func (m *Manager) SendCSATReply(actorUserID int, conversation models.Conversatio
// Make recipient list. // Make recipient list.
to, cc, bcc, err := m.makeRecipients(conversation.ID, conversation.Contact.Email.String, conversation.InboxMail) to, cc, bcc, err := m.makeRecipients(conversation.ID, conversation.Contact.Email.String, conversation.InboxMail)
if err != nil { if err != nil {
return fmt.Errorf("making recipients for CSAT reply: %w", err) return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.csat}"), nil)
} }
return m.SendReply(nil /**media**/, conversation.InboxID, actorUserID, conversation.UUID, message, to, cc, bcc, meta) // Send CSAT reply.
_, err = m.SendReply(nil /**media**/, conversation.InboxID, actorUserID, conversation.UUID, message, to, cc, bcc, meta)
if err != nil {
m.lo.Error("error sending CSAT reply", "conversation_uuid", conversation.UUID, "error", err)
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.csat}"), nil)
}
return nil
} }
// DeleteConversation deletes a conversation. // DeleteConversation deletes a conversation.

View File

@@ -356,8 +356,7 @@ func (m *Manager) MarkMessageAsPending(uuid string) error {
} }
// SendPrivateNote inserts a private message in a conversation. // SendPrivateNote inserts a private message in a conversation.
func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversationUUID, content string) error { func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversationUUID, content string) (models.Message, error) {
// Insert Message.
message := models.Message{ message := models.Message{
ConversationUUID: conversationUUID, ConversationUUID: conversationUUID,
SenderID: senderID, SenderID: senderID,
@@ -369,18 +368,25 @@ func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversat
Private: true, Private: true,
Media: media, Media: media,
} }
return m.InsertMessage(&message) if err := m.InsertMessage(&message); err != nil {
return models.Message{}, err
}
return message, nil
} }
// SendReply inserts a reply message in a conversation. // SendReply inserts a reply message in a conversation.
func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conversationUUID, content string, to, cc, bcc []string, meta map[string]interface{}) error { func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conversationUUID, content string, to, cc, bcc []string, meta map[string]interface{}) (models.Message, error) {
// Save to, cc and bcc in meta. var (
message = models.Message{}
)
// Clear empty fields in to, cc, bcc.
to = stringutil.RemoveEmpty(to) to = stringutil.RemoveEmpty(to)
cc = stringutil.RemoveEmpty(cc) cc = stringutil.RemoveEmpty(cc)
bcc = stringutil.RemoveEmpty(bcc) bcc = stringutil.RemoveEmpty(bcc)
if len(to) == 0 { if len(to) == 0 {
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.empty", "name", "`to`"), nil) return message, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.empty", "name", "`to`"), nil)
} }
meta["to"] = to meta["to"] = to
@@ -393,22 +399,22 @@ func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conver
metaJSON, err := json.Marshal(meta) metaJSON, err := json.Marshal(meta)
if err != nil { if err != nil {
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorMarshalling", "name", "{globals.terms.meta}"), nil) return message, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorMarshalling", "name", "{globals.terms.meta}"), nil)
} }
// Generage unique source ID i.e. message-id for email. // Generage unique source ID i.e. message-id for email.
inbox, err := m.inboxStore.GetDBRecord(inboxID) inbox, err := m.inboxStore.GetDBRecord(inboxID)
if err != nil { if err != nil {
return err return message, err
} }
sourceID, err := stringutil.GenerateEmailMessageID(conversationUUID, inbox.From) sourceID, err := stringutil.GenerateEmailMessageID(conversationUUID, inbox.From)
if err != nil { if err != nil {
m.lo.Error("error generating source message id", "error", err) m.lo.Error("error generating source message id", "error", err)
return envelope.NewError(envelope.GeneralError, m.i18n.T("conversation.errorGeneratingMessageID"), nil) return message, envelope.NewError(envelope.GeneralError, m.i18n.T("conversation.errorGeneratingMessageID"), nil)
} }
// Insert Message. // Insert Message.
message := models.Message{ message = models.Message{
ConversationUUID: conversationUUID, ConversationUUID: conversationUUID,
SenderID: senderID, SenderID: senderID,
Type: models.MessageOutgoing, Type: models.MessageOutgoing,
@@ -421,16 +427,17 @@ func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conver
Meta: metaJSON, Meta: metaJSON,
SourceID: null.StringFrom(sourceID), SourceID: null.StringFrom(sourceID),
} }
return m.InsertMessage(&message) if err := m.InsertMessage(&message); err != nil {
return models.Message{}, err
}
return message, nil
} }
// InsertMessage inserts a message and attaches the media to the message. // InsertMessage inserts a message and attaches the media to the message.
func (m *Manager) InsertMessage(message *models.Message) error { func (m *Manager) InsertMessage(message *models.Message) error {
// Private message is always sent.
if message.Private { if message.Private {
message.Status = models.MessageStatusSent message.Status = models.MessageStatusSent
} }
if len(message.Meta) == 0 || string(message.Meta) == "null" { if len(message.Meta) == 0 || string(message.Meta) == "null" {
message.Meta = json.RawMessage(`{}`) message.Meta = json.RawMessage(`{}`)
} }
@@ -438,14 +445,16 @@ func (m *Manager) InsertMessage(message *models.Message) error {
// Convert HTML content to text for search. // Convert HTML content to text for search.
message.TextContent = stringutil.HTML2Text(message.Content) message.TextContent = stringutil.HTML2Text(message.Content)
// Insert Message. // Insert and scan the message into the struct.
if err := m.q.InsertMessage.QueryRow(message.Type, message.Status, message.ConversationID, message.ConversationUUID, message.Content, message.TextContent, message.SenderID, message.SenderType, if err := m.q.InsertMessage.Get(message,
message.Private, message.ContentType, message.SourceID, message.Meta).Scan(&message.ID, &message.UUID, &message.CreatedAt); err != nil { message.Type, message.Status, message.ConversationID, message.ConversationUUID,
message.Content, message.TextContent, message.SenderID, message.SenderType,
message.Private, message.ContentType, message.SourceID, message.Meta); err != nil {
m.lo.Error("error inserting message in db", "error", err) m.lo.Error("error inserting message in db", "error", err)
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorInserting", "name", "{globals.terms.message}"), nil) return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorInserting", "name", "{globals.terms.message}"), nil)
} }
// Attach message to the media. // Attach just inserted message to the media.
for _, media := range message.Media { for _, media := range message.Media {
m.mediaStore.Attach(media.ID, mmodels.ModelMessages, message.ID) m.mediaStore.Attach(media.ID, mmodels.ModelMessages, message.ID)
} }
@@ -465,14 +474,20 @@ func (m *Manager) InsertMessage(message *models.Message) error {
// Broadcast new message. // Broadcast new message.
m.BroadcastNewMessage(message) m.BroadcastNewMessage(message)
// Refetch message and send webhook event for message created. // Refetch message if this message has media attachments, as media gets linked after inserting the message.
updatedMessage, err := m.GetMessage(message.UUID) if len(message.Media) > 0 {
if err != nil { refetchedMessage, err := m.GetMessage(message.UUID)
m.lo.Error("error fetching updated message for webhook event", "uuid", message.UUID, "error", err) if err != nil {
} else { m.lo.Error("error fetching message after insert", "error", err)
m.webhookStore.TriggerEvent(wmodels.EventMessageCreated, updatedMessage) } else {
// Replace the message in the struct with the refetched message.
*message = refetchedMessage
}
} }
// Trigger webhook for new message created.
m.webhookStore.TriggerEvent(wmodels.EventMessageCreated, message)
return nil return nil
} }
@@ -530,7 +545,7 @@ func (m *Manager) InsertConversationActivity(activityType, conversationUUID, new
content, err := m.getMessageActivityContent(activityType, newValue, actor.FullName()) content, err := m.getMessageActivityContent(activityType, newValue, actor.FullName())
if err != nil { if err != nil {
m.lo.Error("error could not generate activity content", "error", err) m.lo.Error("error could not generate activity content", "error", err)
return err return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorGenerating", "name", "{globals.terms.activityMessage}"), nil)
} }
message := models.Message{ message := models.Message{

View File

@@ -509,9 +509,9 @@ inserted_msg AS (
$1, $2, (SELECT id FROM conversation_id), $1, $2, (SELECT id FROM conversation_id),
$5, $6, $7, $8, $9, $10, $11, $12 $5, $6, $7, $8, $9, $10, $11, $12
) )
RETURNING id, uuid, created_at, conversation_id RETURNING *
) )
SELECT id, uuid, created_at FROM inserted_msg; SELECT * FROM inserted_msg;
-- name: message-exists-by-source-id -- name: message-exists-by-source-id
SELECT conversation_id SELECT conversation_id