mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-23 11:03:43 +00:00
43ebed1c3a
Targets broadcasts via per-tab list and open subs (validated at subscribe time) instead of broadcasting message uuid to all agents. Adds frontend list typing, throttled sound, and many perf improvements to reduce cpu memory & database queries. Other fixes: Enforce message-to-conversation binding in handleGetMessage
295 lines
9.0 KiB
Go
295 lines
9.0 KiB
Go
package main
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
amodels "github.com/abhinavxd/libredesk/internal/auth/models"
|
|
"github.com/abhinavxd/libredesk/internal/envelope"
|
|
"github.com/abhinavxd/libredesk/internal/stringutil"
|
|
"github.com/abhinavxd/libredesk/internal/user/models"
|
|
"github.com/valyala/fasthttp"
|
|
"github.com/volatiletech/null/v9"
|
|
"github.com/zerodha/fastglue"
|
|
)
|
|
|
|
type createContactNoteReq struct {
|
|
Note string `json:"note"`
|
|
}
|
|
|
|
type blockContactReq struct {
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
// handleGetContacts returns a list of contacts from the database.
|
|
func handleGetContacts(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
order = string(r.RequestCtx.QueryArgs().Peek("order"))
|
|
orderBy = string(r.RequestCtx.QueryArgs().Peek("order_by"))
|
|
filters = string(r.RequestCtx.QueryArgs().Peek("filters"))
|
|
total = 0
|
|
)
|
|
page, pageSize := getPagination(r)
|
|
contacts, err := app.user.GetContacts(page, pageSize, order, orderBy, filters)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
if len(contacts) > 0 {
|
|
total = contacts[0].Total
|
|
}
|
|
return r.SendEnvelope(envelope.PageResults{
|
|
Results: contacts,
|
|
Total: total,
|
|
PerPage: pageSize,
|
|
TotalPages: (total + pageSize - 1) / pageSize,
|
|
Page: page,
|
|
})
|
|
}
|
|
|
|
// handleGetTags returns a contact from the database.
|
|
func handleGetContact(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
)
|
|
if id <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
|
|
}
|
|
c, err := app.user.GetContactOrVisitor(id, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(c)
|
|
}
|
|
|
|
// handleUpdateContact updates a contact in the database.
|
|
func handleUpdateContact(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
)
|
|
if id <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
|
|
}
|
|
|
|
contact, err := app.user.GetContactOrVisitor(id, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
form, err := r.RequestCtx.MultipartForm()
|
|
if err != nil {
|
|
app.lo.Error("error parsing form data", "error", err)
|
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, app.i18n.T("errors.parsingRequest"), nil, envelope.GeneralError)
|
|
}
|
|
|
|
// Parse form data
|
|
firstName := ""
|
|
if v, ok := form.Value["first_name"]; ok && len(v) > 0 {
|
|
firstName = string(v[0])
|
|
}
|
|
lastName := ""
|
|
if v, ok := form.Value["last_name"]; ok && len(v) > 0 {
|
|
lastName = string(v[0])
|
|
}
|
|
email := ""
|
|
if v, ok := form.Value["email"]; ok && len(v) > 0 {
|
|
email = strings.TrimSpace(string(v[0]))
|
|
}
|
|
phoneNumber := ""
|
|
if v, ok := form.Value["phone_number"]; ok && len(v) > 0 {
|
|
phoneNumber = string(v[0])
|
|
}
|
|
phoneNumberCountryCode := ""
|
|
if v, ok := form.Value["phone_number_country_code"]; ok && len(v) > 0 {
|
|
phoneNumberCountryCode = string(v[0])
|
|
}
|
|
country := ""
|
|
if v, ok := form.Value["country"]; ok && len(v) > 0 {
|
|
country = string(v[0])
|
|
}
|
|
avatarURL := ""
|
|
if v, ok := form.Value["avatar_url"]; ok && len(v) > 0 {
|
|
avatarURL = string(v[0])
|
|
}
|
|
|
|
// Set nulls to empty strings.
|
|
if avatarURL == "null" {
|
|
avatarURL = ""
|
|
}
|
|
if phoneNumberCountryCode == "null" {
|
|
phoneNumberCountryCode = ""
|
|
}
|
|
if phoneNumber == "null" {
|
|
phoneNumber = ""
|
|
}
|
|
if country == "null" {
|
|
country = ""
|
|
}
|
|
|
|
// Validate mandatory fields.
|
|
if email == "" {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "email"), nil, envelope.InputError)
|
|
}
|
|
if !stringutil.ValidEmail(email) {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("validation.invalidEmail"), nil, envelope.InputError)
|
|
}
|
|
if firstName == "" {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "first_name"), nil, envelope.InputError)
|
|
}
|
|
|
|
contactToUpdate := models.User{
|
|
FirstName: firstName,
|
|
LastName: lastName,
|
|
Email: null.StringFrom(email),
|
|
AvatarURL: null.NewString(avatarURL, avatarURL != ""),
|
|
PhoneNumber: null.NewString(phoneNumber, phoneNumber != ""),
|
|
PhoneNumberCountryCode: null.NewString(phoneNumberCountryCode, phoneNumberCountryCode != ""),
|
|
Country: null.NewString(country, country != ""),
|
|
}
|
|
|
|
if err := app.user.UpdateContact(id, contactToUpdate); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
// Delete avatar?
|
|
if avatarURL == "" && contact.AvatarURL.Valid {
|
|
fileName := filepath.Base(contact.AvatarURL.String)
|
|
app.media.Delete(fileName)
|
|
contact.AvatarURL.Valid = false
|
|
contact.AvatarURL.String = ""
|
|
}
|
|
|
|
// Upload avatar?
|
|
files, ok := form.File["files"]
|
|
if ok && len(files) > 0 {
|
|
if err := uploadUserAvatar(r, contact, files); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
}
|
|
|
|
// Refetch contact and return it
|
|
contact, err = app.user.GetContactOrVisitor(id, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(contact)
|
|
}
|
|
|
|
// handleGetContactNotes returns all notes for a contact.
|
|
func handleGetContactNotes(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
contactID, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
)
|
|
if contactID <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
|
|
}
|
|
notes, err := app.user.GetNotes(contactID)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(notes)
|
|
}
|
|
|
|
// handleCreateContactNote creates a note for a contact.
|
|
func handleCreateContactNote(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
contactID, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
auser = r.RequestCtx.UserValue("user").(amodels.User)
|
|
req = createContactNoteReq{}
|
|
)
|
|
if err := r.Decode(&req, "json"); err != nil {
|
|
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.T("errors.parsingRequest"), nil))
|
|
}
|
|
if len(req.Note) == 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "note"), nil, envelope.InputError)
|
|
}
|
|
n, err := app.user.CreateNote(contactID, auser.ID, req.Note)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
n, err = app.user.GetNote(n.ID)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(n)
|
|
}
|
|
|
|
// handleDeleteContactNote deletes a note for a contact.
|
|
func handleDeleteContactNote(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
contactID, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
noteID, _ = strconv.Atoi(r.RequestCtx.UserValue("note_id").(string))
|
|
auser = r.RequestCtx.UserValue("user").(amodels.User)
|
|
)
|
|
if contactID <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
|
|
}
|
|
if noteID <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
|
|
}
|
|
|
|
agent, err := app.user.GetAgentCachedOrLoad(auser.ID)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
// Allow deletion of only own notes and not those created by others, but also allow `Admin` to delete any note.
|
|
if !agent.HasAdminRole() {
|
|
note, err := app.user.GetNote(noteID)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
if note.UserID != auser.ID {
|
|
return r.SendErrorEnvelope(fasthttp.StatusForbidden, app.i18n.T("errors.canOnlyDeleteOwnNote"), nil, envelope.InputError)
|
|
}
|
|
}
|
|
|
|
app.lo.Info("deleting contact note", "note_id", noteID, "contact_id", contactID, "actor_id", auser.ID)
|
|
|
|
if err := app.user.DeleteNote(noteID, contactID); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(true)
|
|
}
|
|
|
|
// handleBlockContact blocks a contact.
|
|
func handleBlockContact(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
contactID, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
auser = r.RequestCtx.UserValue("user").(amodels.User)
|
|
req = blockContactReq{}
|
|
)
|
|
|
|
if contactID <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
|
|
}
|
|
|
|
if err := r.Decode(&req, "json"); err != nil {
|
|
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.T("errors.parsingRequest"), nil))
|
|
}
|
|
|
|
app.lo.Info("setting contact block status", "contact_id", contactID, "enabled", req.Enabled, "actor_id", auser.ID)
|
|
|
|
contact, err := app.user.GetContactOrVisitor(contactID, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
if err := app.user.ToggleEnabled(contactID, contact.Type, req.Enabled); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
contact, err = app.user.GetContactOrVisitor(contactID, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(contact)
|
|
}
|