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) contact, err := app.user.GetContact(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.GetContact(contactID, "") if err != nil { return sendErrorEnvelope(r, err) } return r.SendEnvelope(contact) }