feat: add support for sending messages as contact

- introduce new permission `messages:write_as_contact` that needs to be set to allow this.
This commit is contained in:
Abhinav Raut
2025-09-13 20:55:04 +05:30
parent a0e1ccf117
commit 210b8bb53b
7 changed files with 50 additions and 2 deletions

View File

@@ -734,7 +734,7 @@ func handleCreateConversation(r *fastglue.Request) error {
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.Ts("globals.messages.errorSending", "name", "{globals.terms.message}"), nil))
}
case umodels.UserTypeContact:
// Create message on behalf of contact.
// Create contact message.
if _, err := app.conversation.CreateContactMessage(media, contact.ID, conversationUUID, req.Content, cmodels.ContentTypeHTML); err != nil {
// Delete the conversation if message creation fails.
if err := app.conversation.DeleteConversation(conversationUUID); err != nil {

View File

@@ -2,10 +2,14 @@ package main
import (
"strconv"
"strings"
amodels "github.com/abhinavxd/libredesk/internal/auth/models"
authzModels "github.com/abhinavxd/libredesk/internal/authz/models"
cmodels "github.com/abhinavxd/libredesk/internal/conversation/models"
"github.com/abhinavxd/libredesk/internal/envelope"
medModels "github.com/abhinavxd/libredesk/internal/media/models"
umodels "github.com/abhinavxd/libredesk/internal/user/models"
"github.com/valyala/fasthttp"
"github.com/zerodha/fastglue"
)
@@ -17,6 +21,7 @@ type messageReq struct {
To []string `json:"to"`
CC []string `json:"cc"`
BCC []string `json:"bcc"`
SenderType string `json:"sender_type"`
}
// handleGetMessages returns messages for a conversation.
@@ -150,7 +155,31 @@ func handleSendMessage(r *fastglue.Request) error {
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil, envelope.InputError)
}
// Prepare attachments.
if req.SenderType != umodels.UserTypeAgent && req.SenderType != umodels.UserTypeContact {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`sender_type`"), nil, envelope.InputError)
}
// Contacts cannot send private messages
if req.SenderType == umodels.UserTypeContact && req.Private {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.badRequest"), nil, envelope.InputError)
}
// Check if user has permission to send messages as contact
if req.SenderType == umodels.UserTypeContact {
parts := strings.Split(authzModels.PermMessagesWriteAsContact, ":")
if len(parts) != 2 {
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorChecking", "name", "{globals.terms.permission}"), nil))
}
ok, err := app.authz.Enforce(user, parts[0], parts[1])
if err != nil {
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorChecking", "name", "{globals.terms.permission}"), nil))
}
if !ok {
return r.SendErrorEnvelope(fasthttp.StatusForbidden, app.i18n.Ts("globals.messages.denied", "name", "{globals.terms.permission}"), nil, envelope.PermissionError)
}
}
// Get media for all attachments.
var media = make([]medModels.Media, 0, len(req.Attachments))
for _, id := range req.Attachments {
m, err := app.media.Get(id, "")
@@ -161,6 +190,16 @@ func handleSendMessage(r *fastglue.Request) error {
media = append(media, m)
}
// Create contact message.
if req.SenderType == umodels.UserTypeContact {
message, err := app.conversation.CreateContactMessage(media, int(conv.ContactID), cuuid, req.Message, cmodels.ContentTypeHTML)
if err != nil {
return sendErrorEnvelope(r, err)
}
return r.SendEnvelope(message)
}
// Send private note.
if req.Private {
message, err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message)
if err != nil {
@@ -168,6 +207,8 @@ func handleSendMessage(r *fastglue.Request) error {
}
return r.SendEnvelope(message)
}
// Queue reply.
message, err := app.conversation.QueueReply(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)

View File

@@ -12,6 +12,7 @@ export const permissions = {
CONVERSATIONS_UPDATE_TAGS: 'conversations:update_tags',
MESSAGES_READ: 'messages:read',
MESSAGES_WRITE: 'messages:write',
MESSAGES_WRITE_AS_CONTACT: 'messages:write_as_contact',
VIEW_MANAGE: 'view:manage',
GENERAL_SETTINGS_MANAGE: 'general_settings:manage',
NOTIFICATION_SETTINGS_MANAGE: 'notification_settings:manage',

View File

@@ -140,6 +140,7 @@ const permissions = ref([
{ name: perms.CONVERSATIONS_UPDATE_TAGS, label: t('admin.role.conversations.updateTags') },
{ name: perms.MESSAGES_READ, label: t('admin.role.messages.read') },
{ name: perms.MESSAGES_WRITE, label: t('admin.role.messages.write') },
{ name: perms.MESSAGES_WRITE_AS_CONTACT, label: t('admin.role.messages.writeAsContact') },
{ name: perms.VIEW_MANAGE, label: t('admin.role.view.manage') }
]
},

View File

@@ -122,6 +122,7 @@ import { Input } from '@/components/ui/input'
import { useEmitter } from '@/composables/useEmitter'
import { useFileUpload } from '@/composables/useFileUpload'
import ReplyBoxContent from '@/features/conversation/ReplyBoxContent.vue'
import { UserTypeAgent } from '@/constants/user'
import {
Form,
FormField,
@@ -252,6 +253,7 @@ const processSend = async () => {
if (hasTextContent.value > 0 || mediaFiles.value.length > 0) {
const message = htmlContent.value
await api.sendMessage(conversationStore.current.uuid, {
sender_type: UserTypeAgent,
private: messageType.value === 'private_note',
message: message,
attachments: mediaFiles.value.map((file) => file.id),

View File

@@ -500,6 +500,7 @@
"admin.role.conversations.updateTags": "Add or remove conversation tags",
"admin.role.messages.read": "View conversation messages",
"admin.role.messages.write": "Send messages in conversations",
"admin.role.messages.writeAsContact": "Send messages as contact",
"admin.role.view.manage": "Create and manage conversation views",
"admin.role.generalSettings.manage": "Manage General Settings",
"admin.role.notificationSettings.manage": "Manage Notification Settings",

View File

@@ -15,6 +15,7 @@ const (
PermConversationWrite = "conversations:write"
PermMessagesRead = "messages:read"
PermMessagesWrite = "messages:write"
PermMessagesWriteAsContact = "messages:write_as_contact"
// View
PermViewManage = "view:manage"
@@ -102,6 +103,7 @@ var validPermissions = map[string]struct{}{
PermConversationWrite: {},
PermMessagesRead: {},
PermMessagesWrite: {},
PermMessagesWriteAsContact: {},
PermViewManage: {},
PermStatusManage: {},
PermTagsManage: {},