Files
libredesk/cmd/settings.go
Abhinav Raut 494bc15b0a feat: Enable agents to create conversations from the UI
Before this feature the only way to create a conversation was by adding inbox and sending an email.

Agents first search contacts by email, see a dropdown select an existing contact or fill a new email for new contact.

The backend creates contact if it does not exist, creates a conversation, sends a reply to the conversation.
Optinally assigns conversation to a user / team.

fix: Replies to emails create a new conversation instead of attaching to the previous one.

Was not happening in gmail, as gmail was sending the references headers in all replies and I missed this completely. So when libredesk searches a conversation by references headers it worked!

Instead the right way is to generate the outgoing email message id and saving it in DB. This commit fixes that.

There could be more backup strategies like putting reference number in the subject but that can be explored later.

chore: new role `conversatons:write` that enables the create conversations feature for an agent.

chore: migrations for v0.4.0.
2025-03-05 01:17:42 +05:30

120 lines
3.8 KiB
Go

package main
import (
"encoding/json"
"net/mail"
"strings"
"github.com/abhinavxd/libredesk/internal/envelope"
"github.com/abhinavxd/libredesk/internal/setting/models"
"github.com/abhinavxd/libredesk/internal/stringutil"
"github.com/valyala/fasthttp"
"github.com/zerodha/fastglue"
)
// handleGetGeneralSettings fetches general settings.
func handleGetGeneralSettings(r *fastglue.Request) error {
var (
app = r.Context.(*App)
)
out, err := app.setting.GetByPrefix("app")
if err != nil {
return sendErrorEnvelope(r, err)
}
// Unmarshal to set the app.update to the settings, so the frontend can show that an update is available.
var settings map[string]interface{}
if err := json.Unmarshal(out, &settings); err != nil {
app.lo.Error("error unmarshalling settings", "err", err)
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, "Error fetching settings", nil))
}
// Set the app.update to the settings, adding `app` prefix to the key to match the settings structure in db.
settings["app.update"] = app.update
// Set app version.
settings["app.version"] = versionString
return r.SendEnvelope(settings)
}
// handleUpdateGeneralSettings updates general settings.
func handleUpdateGeneralSettings(r *fastglue.Request) error {
var (
app = r.Context.(*App)
req = models.General{}
)
if err := r.Decode(&req, "json"); err != nil {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Bad request", nil, "")
}
if err := app.setting.Update(req); err != nil {
return sendErrorEnvelope(r, err)
}
// Reload the settings and templates.
if err := reloadSettings(app); err != nil {
return envelope.NewError(envelope.GeneralError, "Could not reload settings, Please restart the app.", nil)
}
if err := reloadTemplates(app); err != nil {
return envelope.NewError(envelope.GeneralError, "Could not reload settings, Please restart the app.", nil)
}
return r.SendEnvelope("Settings updated successfully")
}
// handleGetEmailNotificationSettings fetches email notification settings.
func handleGetEmailNotificationSettings(r *fastglue.Request) error {
var (
app = r.Context.(*App)
notif = models.EmailNotification{}
)
out, err := app.setting.GetByPrefix("notification.email")
if err != nil {
return sendErrorEnvelope(r, err)
}
// Unmarshal and filter out password.
if err := json.Unmarshal(out, &notif); err != nil {
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, "Error fetching settings", nil))
}
if notif.Password != "" {
notif.Password = strings.Repeat(stringutil.PasswordDummy, 10)
}
return r.SendEnvelope(notif)
}
// handleUpdateEmailNotificationSettings updates email notification settings.
func handleUpdateEmailNotificationSettings(r *fastglue.Request) error {
var (
app = r.Context.(*App)
req = models.EmailNotification{}
cur = models.EmailNotification{}
)
if err := r.Decode(&req, "json"); err != nil {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Bad request", nil, envelope.InputError)
}
out, err := app.setting.GetByPrefix("notification.email")
if err != nil {
return sendErrorEnvelope(r, err)
}
if err := json.Unmarshal(out, &cur); err != nil {
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, "Error updating settings", nil))
}
// Make sure it's a valid from email address.
if _, err := mail.ParseAddress(req.EmailAddress); err != nil {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Invalid from email address format", nil, envelope.InputError)
}
if req.Password == "" {
req.Password = cur.Password
}
if err := app.setting.Update(req); err != nil {
return sendErrorEnvelope(r, err)
}
// No reload implemented, so user has to restart the app.
return r.SendEnvelope("Settings updated successfully, Please restart the app for changes to take effect.")
}