mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-10-23 05:11:57 +00:00
For example, when a user selects the USA, the form also shows Canada, as both share the +1 calling code. Rename column from `phone_number_calling_code` to `phone_number_country_code`. Feat: Show the calling code alongside the country flag in the contact form for the selected country. Previously, only the flag was displayed.
289 lines
9.3 KiB
Go
289 lines
9.3 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"))
|
|
page, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page")))
|
|
pageSize, _ = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("page_size")))
|
|
total = 0
|
|
)
|
|
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.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
|
}
|
|
c, err := app.user.GetContact(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.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
|
}
|
|
|
|
contact, err := app.user.GetContact(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.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), 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])
|
|
}
|
|
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 = ""
|
|
}
|
|
|
|
// 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.Ts("globals.messages.invalid", "name", "email"), nil, envelope.InputError)
|
|
}
|
|
if firstName == "" {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "first_name"), nil, envelope.InputError)
|
|
}
|
|
|
|
// Another contact with same new email?
|
|
existingContact, _ := app.user.GetContact(0, email)
|
|
if existingContact.ID > 0 && existingContact.ID != id {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("contact.alreadyExistsWithEmail"), 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 != ""),
|
|
}
|
|
|
|
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.GetContact(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.Ts("globals.messages.invalid", "name", "`id`"), 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.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), 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.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
|
}
|
|
if noteID <= 0 {
|
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`note_id`"), nil, envelope.InputError)
|
|
}
|
|
|
|
agent, err := app.user.GetAgent(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.Ts("globals.messages.canOnlyDeleteOwn", "name", "{globals.terms.note}"), 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.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
|
|
}
|
|
|
|
if err := r.Decode(&req, "json"); err != nil {
|
|
return sendErrorEnvelope(r, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), nil))
|
|
}
|
|
|
|
app.lo.Info("setting contact block status", "contact_id", contactID, "enabled", req.Enabled, "actor_id", auser.ID)
|
|
|
|
if err := app.user.ToggleEnabled(contactID, models.UserTypeContact, req.Enabled); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
contact, err := app.user.GetContact(contactID, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(contact)
|
|
}
|