mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-10 14:15:42 +00:00
365 lines
11 KiB
Go
365 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"mime/multipart"
|
|
"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"
|
|
realip "github.com/ferluci/fast-realip"
|
|
"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"`
|
|
}
|
|
|
|
func handleCreateContact(r *fastglue.Request) error {
|
|
var app = r.Context.(*App)
|
|
|
|
contact, _, err := contactFromForm(r)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
if err := app.user.CreateContact(&contact); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
created, err := app.user.GetContactOrVisitor(contact.ID, "")
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
return r.SendEnvelope(created)
|
|
}
|
|
|
|
// 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, app.setting.GetAppTimezone())
|
|
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)
|
|
}
|
|
|
|
contactToUpdate, form, err := contactFromForm(r)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
if err := app.user.UpdateContact(id, contactToUpdate); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
// Delete avatar?
|
|
if !contactToUpdate.AvatarURL.Valid && contact.AvatarURL.Valid {
|
|
fileName := filepath.Base(contact.AvatarURL.String)
|
|
app.media.Delete(fileName)
|
|
contact.AvatarURL.Valid = false
|
|
contact.AvatarURL.String = ""
|
|
}
|
|
|
|
// Upload avatar?
|
|
if files := form.File["files"]; 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)
|
|
}
|
|
|
|
// handleDeleteContact permanently deletes a contact along with their conversations, messages, and notes.
|
|
func handleDeleteContact(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
auser = r.RequestCtx.UserValue("user").(amodels.User)
|
|
)
|
|
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)
|
|
}
|
|
|
|
app.lo.Info("deleting contact", "contact_id", id, "actor_id", auser.ID)
|
|
|
|
if err := app.user.DeleteContact(id); err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
if contact.AvatarURL.Valid {
|
|
fileName := filepath.Base(contact.AvatarURL.String)
|
|
if err := app.media.Delete(fileName); err != nil {
|
|
app.lo.Error("error deleting contact avatar", "contact_id", id, "file", fileName, "error", err)
|
|
}
|
|
}
|
|
|
|
if err := app.activityLog.ContactDeleted(auser.ID, auser.Email, realip.FromRequest(r.RequestCtx), id, contact.Email.String); err != nil {
|
|
app.lo.Error("error creating contact deleted activity log", "error", err)
|
|
}
|
|
|
|
return r.SendEnvelope(true)
|
|
}
|
|
|
|
// handleExportContact sends all stored data for a contact as a JSON file download.
|
|
func handleExportContact(r *fastglue.Request) error {
|
|
var (
|
|
app = r.Context.(*App)
|
|
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
|
auser = r.RequestCtx.UserValue("user").(amodels.User)
|
|
)
|
|
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)
|
|
}
|
|
|
|
data, err := app.user.ExportContactData(id)
|
|
if err != nil {
|
|
return sendErrorEnvelope(r, err)
|
|
}
|
|
|
|
if err := app.activityLog.ContactDataExported(auser.ID, auser.Email, realip.FromRequest(r.RequestCtx), id, contact.Email.String); err != nil {
|
|
app.lo.Error("error creating contact data exported activity log", "error", err)
|
|
}
|
|
|
|
filename := fmt.Sprintf("contact-%d-data.json", id)
|
|
r.RequestCtx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
|
r.RequestCtx.Response.Header.Set("X-Content-Type-Options", "nosniff")
|
|
r.RequestCtx.SetContentType("application/json; charset=utf-8")
|
|
r.RequestCtx.SetBody(data)
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
func contactFromForm(r *fastglue.Request) (models.User, *multipart.Form, error) {
|
|
var app = r.Context.(*App)
|
|
|
|
form, err := r.RequestCtx.MultipartForm()
|
|
if err != nil {
|
|
app.lo.Error("error parsing form data", "error", err)
|
|
return models.User{}, nil, envelope.NewError(envelope.GeneralError, app.i18n.T("errors.parsingRequest"), nil)
|
|
}
|
|
|
|
value := func(key string) string {
|
|
v := ""
|
|
if vals := form.Value[key]; len(vals) > 0 {
|
|
v = strings.TrimSpace(vals[0])
|
|
}
|
|
// The edit page sends cleared fields as the string "null".
|
|
if v == "null" {
|
|
return ""
|
|
}
|
|
return v
|
|
}
|
|
optional := func(key string) null.String {
|
|
v := value(key)
|
|
return null.NewString(v, v != "")
|
|
}
|
|
|
|
email := value("email")
|
|
if email == "" {
|
|
return models.User{}, nil, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "email"), nil)
|
|
}
|
|
if !stringutil.ValidEmail(email) {
|
|
return models.User{}, nil, envelope.NewError(envelope.InputError, app.i18n.T("validation.invalidEmail"), nil)
|
|
}
|
|
firstName := value("first_name")
|
|
if firstName == "" {
|
|
return models.User{}, nil, envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "first_name"), nil)
|
|
}
|
|
|
|
return models.User{
|
|
FirstName: firstName,
|
|
LastName: value("last_name"),
|
|
Email: null.StringFrom(email),
|
|
AvatarURL: optional("avatar_url"),
|
|
PhoneNumber: optional("phone_number"),
|
|
PhoneNumberCountryCode: optional("phone_number_country_code"),
|
|
Country: optional("country"),
|
|
}, form, nil
|
|
}
|