feat(crm): align contact company payloads
This commit is contained in:
@@ -43,7 +43,7 @@ func (h *CompanyHandler) List(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, companies, pg.Page, pg.PerPage, total)
|
||||
c.JSON(http.StatusOK, companyListResponse(c.Request.Context(), h.svc.DB(), companies, total, pg.Page))
|
||||
}
|
||||
|
||||
// Search searches companies by query.
|
||||
@@ -57,6 +57,10 @@ func (h *CompanyHandler) Search(c *gin.Context) {
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
query := c.DefaultQuery("q", "")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Specify search string with parameter q"})
|
||||
return
|
||||
}
|
||||
sort := c.DefaultQuery("sort", "")
|
||||
searchMode := search.SearchMode(c.DefaultQuery("search_mode", "ilike"))
|
||||
|
||||
@@ -67,7 +71,7 @@ func (h *CompanyHandler) Search(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, companies, pg.Page, pg.PerPage, total)
|
||||
c.JSON(http.StatusOK, companyListResponse(c.Request.Context(), h.svc.DB(), companies, total, pg.Page))
|
||||
}
|
||||
|
||||
// Get retrieves a single company by ID.
|
||||
@@ -91,7 +95,7 @@ func (h *CompanyHandler) Get(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, company)
|
||||
c.JSON(http.StatusOK, companyPayloadResponse(c.Request.Context(), h.svc.DB(), company))
|
||||
}
|
||||
|
||||
// Create creates a new company.
|
||||
@@ -115,7 +119,7 @@ func (h *CompanyHandler) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, company)
|
||||
c.JSON(http.StatusOK, companyPayloadResponse(c.Request.Context(), h.svc.DB(), company))
|
||||
}
|
||||
|
||||
// Update updates an existing company.
|
||||
@@ -145,7 +149,7 @@ func (h *CompanyHandler) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, company)
|
||||
c.JSON(http.StatusOK, companyPayloadResponse(c.Request.Context(), h.svc.DB(), company))
|
||||
}
|
||||
|
||||
// Delete deletes a company.
|
||||
@@ -168,7 +172,7 @@ func (h *CompanyHandler) Delete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// ListContacts retrieves contacts associated with a company.
|
||||
@@ -194,7 +198,7 @@ func (h *CompanyHandler) ListContacts(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, contacts, pg.Page, pg.PerPage, total)
|
||||
c.JSON(http.StatusOK, companyContactsResponse(c.Request.Context(), h.svc.DB(), uint(companyID), contacts, total, pg.Page))
|
||||
}
|
||||
|
||||
// ListConversations retrieves conversations for contacts of a company.
|
||||
@@ -220,7 +224,35 @@ func (h *CompanyHandler) ListConversations(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, conversations, pg.Page, pg.PerPage, total)
|
||||
_ = total
|
||||
c.JSON(http.StatusOK, gin.H{"payload": conversations})
|
||||
}
|
||||
|
||||
func (h *CompanyHandler) SearchContacts(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
companyID, err := strconv.ParseUint(c.Param("company_id"), 10, 32)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid company id")
|
||||
return
|
||||
}
|
||||
|
||||
query := c.Query("q")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Specify search string with parameter q"})
|
||||
return
|
||||
}
|
||||
pg := pagination.Parse(c)
|
||||
contacts, total, svcErr := h.svc.SearchContacts(c.Request.Context(), uint(companyID), accountID, query, pg.Offset, pg.PerPage)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, companyContactsResponse(c.Request.Context(), h.svc.DB(), uint(companyID), contacts, total, pg.Page))
|
||||
}
|
||||
|
||||
// ListNotes retrieves notes for a company.
|
||||
@@ -246,7 +278,8 @@ func (h *CompanyHandler) ListNotes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, notes, pg.Page, pg.PerPage, total)
|
||||
_ = total
|
||||
c.JSON(http.StatusOK, companyNotesResponse(c.Request.Context(), h.svc.DB(), notes))
|
||||
}
|
||||
|
||||
// CreateNote creates a note for a company.
|
||||
@@ -282,7 +315,7 @@ func (h *CompanyHandler) CreateNote(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, note)
|
||||
c.JSON(http.StatusOK, gin.H{"payload": note})
|
||||
}
|
||||
|
||||
// DeleteNote deletes a note from a company.
|
||||
@@ -311,7 +344,7 @@ func (h *CompanyHandler) DeleteNote(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, gin.H{"message": "note deleted"})
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// AddContact adds a contact to a company.
|
||||
@@ -329,7 +362,7 @@ func (h *CompanyHandler) AddContact(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
contactID, err := strconv.ParseUint(c.Param("contact_id"), 10, 32)
|
||||
contactID, err := parseCompanyContactID(c)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid contact id")
|
||||
return
|
||||
@@ -340,7 +373,12 @@ func (h *CompanyHandler) AddContact(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, gin.H{"message": "contact added"})
|
||||
contact, svcErr := h.svc.GetContact(c.Request.Context(), uint(companyID), accountID, contactID)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"payload": serializeCompanyContact(c.Request.Context(), h.svc.DB(), contact, uint(companyID))})
|
||||
}
|
||||
|
||||
// RemoveContact removes a contact from a company.
|
||||
@@ -369,5 +407,21 @@ func (h *CompanyHandler) RemoveContact(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, gin.H{"message": "contact removed"})
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func parseCompanyContactID(c *gin.Context) (uint, error) {
|
||||
if c.Param("contact_id") != "" {
|
||||
return parseUintParam(c, "contact_id")
|
||||
}
|
||||
var req struct {
|
||||
ContactID uint `json:"contact_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if req.ContactID == 0 {
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
return req.ContactID, nil
|
||||
}
|
||||
|
||||
@@ -101,8 +101,11 @@ func (s *CompanyHandlerTestSuite) SetupSuite() {
|
||||
companies.GET("/search", s.handler.Search)
|
||||
companies.GET("/:company_id", s.handler.Get)
|
||||
companies.PUT("/:company_id", s.handler.Update)
|
||||
companies.PATCH("/:company_id", s.handler.Update)
|
||||
companies.DELETE("/:company_id", s.handler.Delete)
|
||||
companies.GET("/:company_id/contacts", s.handler.ListContacts)
|
||||
companies.GET("/:company_id/contacts/search", s.handler.SearchContacts)
|
||||
companies.POST("/:company_id/contacts", s.handler.AddContact)
|
||||
companies.POST("/:company_id/contacts/:contact_id", s.handler.AddContact)
|
||||
companies.DELETE("/:company_id/contacts/:contact_id", s.handler.RemoveContact)
|
||||
companies.GET("/:company_id/conversations", s.handler.ListConversations)
|
||||
@@ -147,8 +150,9 @@ func (s *CompanyHandlerTestSuite) TestList_Empty() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
assert.Contains(s.T(), resp, "payload")
|
||||
assert.Equal(s.T(), float64(0), resp["meta"].(map[string]interface{})["total_count"])
|
||||
}
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestList_WithCompanies() {
|
||||
@@ -163,29 +167,30 @@ func (s *CompanyHandlerTestSuite) TestList_WithCompanies() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
data := resp["data"].([]interface{})
|
||||
assert.Len(s.T(), data, 2)
|
||||
payload := resp["payload"].([]interface{})
|
||||
assert.Len(s.T(), payload, 2)
|
||||
assert.Equal(s.T(), float64(2), resp["meta"].(map[string]interface{})["total_count"])
|
||||
}
|
||||
|
||||
// ========== Create ==========
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestCreate_Success() {
|
||||
body := map[string]interface{}{
|
||||
"name": "NewCorp",
|
||||
"description": "A new company",
|
||||
"domain": "newcorp.com",
|
||||
"company": map[string]interface{}{
|
||||
"name": "NewCorp",
|
||||
"description": "A new company",
|
||||
"domain": "newcorp.com",
|
||||
},
|
||||
}
|
||||
w := s.makeRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/companies/", s.accountID), body)
|
||||
assert.Equal(s.T(), http.StatusCreated, w.Code)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
companyData := resp["data"].(map[string]interface{})
|
||||
companyData := resp["payload"].(map[string]interface{})
|
||||
assert.Equal(s.T(), "NewCorp", companyData["name"])
|
||||
}
|
||||
|
||||
@@ -204,9 +209,9 @@ func (s *CompanyHandlerTestSuite) TestSearch_NoResult() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
data := resp["data"].([]interface{})
|
||||
assert.Len(s.T(), data, 0)
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].([]interface{})
|
||||
assert.Len(s.T(), payload, 0)
|
||||
}
|
||||
|
||||
// ========== Get ==========
|
||||
@@ -220,10 +225,9 @@ func (s *CompanyHandlerTestSuite) TestGet_Success() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
companyData := resp["data"].(map[string]interface{})
|
||||
companyData := resp["payload"].(map[string]interface{})
|
||||
assert.Equal(s.T(), "GetCorp", companyData["name"])
|
||||
}
|
||||
|
||||
@@ -247,17 +251,18 @@ func (s *CompanyHandlerTestSuite) TestUpdate_Success() {
|
||||
s.Require().NoError(companyRepo.Create(context.Background(), company))
|
||||
|
||||
body := map[string]interface{}{
|
||||
"name": "UpdateCorpUpdated",
|
||||
"domain": "updated.com",
|
||||
"company": map[string]interface{}{
|
||||
"name": "UpdateCorpUpdated",
|
||||
"domain": "updated.com",
|
||||
},
|
||||
}
|
||||
w := s.makeRequest("PUT", fmt.Sprintf("/api/v1/accounts/%d/companies/%d", s.accountID, company.ID), body)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
companyData := resp["data"].(map[string]interface{})
|
||||
companyData := resp["payload"].(map[string]interface{})
|
||||
assert.Equal(s.T(), "UpdateCorpUpdated", companyData["name"])
|
||||
}
|
||||
|
||||
@@ -277,7 +282,8 @@ func (s *CompanyHandlerTestSuite) TestDelete_Success() {
|
||||
s.Require().NoError(companyRepo.Create(context.Background(), company))
|
||||
|
||||
w := s.makeRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/companies/%d", s.accountID, company.ID), nil)
|
||||
assert.Equal(s.T(), http.StatusNoContent, w.Code)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
assert.Empty(s.T(), w.Body.String())
|
||||
}
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestDelete_InvalidID() {
|
||||
@@ -294,19 +300,19 @@ func (s *CompanyHandlerTestSuite) TestListContacts_Success() {
|
||||
s.Require().NoError(companyRepo.Create(context.Background(), company))
|
||||
|
||||
// Create contacts and link them
|
||||
contact1 := &model.Contact{AccountID: s.accountID, Name: "Contact1"}
|
||||
contact2 := &model.Contact{AccountID: s.accountID, Name: "Contact2"}
|
||||
contact1 := &model.Contact{AccountID: s.accountID, Name: "Contact1", CompanyID: &company.ID}
|
||||
contact2 := &model.Contact{AccountID: s.accountID, Name: "Contact2", CompanyID: &company.ID}
|
||||
s.Require().NoError(s.db.Create(contact1).Error)
|
||||
s.Require().NoError(s.db.Create(contact2).Error)
|
||||
s.Require().NoError(s.db.Exec("INSERT INTO company_contacts (company_id, contact_id) VALUES (?, ?)", company.ID, contact1.ID).Error)
|
||||
s.Require().NoError(s.db.Exec("INSERT INTO company_contacts (company_id, contact_id) VALUES (?, ?)", company.ID, contact2.ID).Error)
|
||||
|
||||
w := s.makeRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/companies/%d/contacts", s.accountID, company.ID), nil)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].([]interface{})
|
||||
assert.Len(s.T(), payload, 2)
|
||||
assert.Equal(s.T(), float64(2), resp["meta"].(map[string]interface{})["total_count"])
|
||||
}
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestListContacts_InvalidID() {
|
||||
@@ -343,11 +349,10 @@ func (s *CompanyHandlerTestSuite) TestListNotes_Success() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
data := resp["data"].([]interface{})
|
||||
assert.Len(s.T(), data, 2)
|
||||
payload := resp["payload"].([]interface{})
|
||||
assert.Len(s.T(), payload, 2)
|
||||
}
|
||||
|
||||
// ========== CreateNote ==========
|
||||
@@ -361,13 +366,12 @@ func (s *CompanyHandlerTestSuite) TestCreateNote_Success() {
|
||||
"content": "This is a new note",
|
||||
}
|
||||
w := s.makeRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/companies/%d/notes", s.accountID, company.ID), body)
|
||||
assert.Equal(s.T(), http.StatusCreated, w.Code)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
noteData := resp["data"].(map[string]interface{})
|
||||
noteData := resp["payload"].(map[string]interface{})
|
||||
assert.Equal(s.T(), "This is a new note", noteData["content"])
|
||||
}
|
||||
|
||||
@@ -405,10 +409,7 @@ func (s *CompanyHandlerTestSuite) TestDeleteNote_Success() {
|
||||
|
||||
w := s.makeRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/companies/%d/notes/%d", s.accountID, company.ID, note.ID), nil)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
assert.Empty(s.T(), w.Body.String())
|
||||
}
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestDeleteNote_InvalidCompanyID() {
|
||||
@@ -441,8 +442,15 @@ func (s *CompanyHandlerTestSuite) TestAddContact_Success() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].(map[string]interface{})
|
||||
assert.Equal(s.T(), "Test Contact", payload["name"])
|
||||
assert.True(s.T(), payload["linked_to_current_company"].(bool))
|
||||
|
||||
var updated model.Contact
|
||||
s.Require().NoError(s.db.First(&updated, contact.ID).Error)
|
||||
s.Require().NotNil(updated.CompanyID)
|
||||
assert.Equal(s.T(), company.ID, *updated.CompanyID)
|
||||
}
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestAddContact_InvalidCompanyID() {
|
||||
@@ -471,18 +479,16 @@ func (s *CompanyHandlerTestSuite) TestRemoveContact_Success() {
|
||||
company := &model.Company{AccountID: s.accountID, Name: "RemoveContactCorp"}
|
||||
s.Require().NoError(companyRepo.Create(context.Background(), company))
|
||||
|
||||
contact := &model.Contact{AccountID: s.accountID, Name: "Remove Contact", Email: "removecontact@example.com"}
|
||||
contact := &model.Contact{AccountID: s.accountID, Name: "Remove Contact", Email: "removecontact@example.com", CompanyID: &company.ID}
|
||||
s.Require().NoError(s.db.Create(contact).Error)
|
||||
|
||||
// Add association first
|
||||
s.Require().NoError(s.db.Model(&company).Association("Contacts").Append(contact))
|
||||
|
||||
w := s.makeRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/companies/%d/contacts/%d", s.accountID, company.ID, contact.ID), nil)
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
assert.Empty(s.T(), w.Body.String())
|
||||
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(s.T(), resp["success"].(bool))
|
||||
var updated model.Contact
|
||||
s.Require().NoError(s.db.First(&updated, contact.ID).Error)
|
||||
assert.Nil(s.T(), updated.CompanyID)
|
||||
}
|
||||
|
||||
func (s *CompanyHandlerTestSuite) TestRemoveContact_InvalidCompanyID() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/repository"
|
||||
"github.com/gochat/gochat/internal/search"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
@@ -68,10 +69,7 @@ func (h *ContactHandler) List(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"contacts": contacts,
|
||||
"meta": gin.H{"count": total, "page": page, "page_size": perPage},
|
||||
})
|
||||
c.JSON(http.StatusOK, contactListResponse(c.Request.Context(), h.svc.DB(), contacts, total, page, includeContactInboxes(c), nil))
|
||||
}
|
||||
|
||||
// @Summary Search contacts
|
||||
@@ -102,6 +100,10 @@ func (h *ContactHandler) Search(c *gin.Context) {
|
||||
}
|
||||
|
||||
query := c.Query("q")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Specify search string with parameter q"})
|
||||
return
|
||||
}
|
||||
page := getPage(c)
|
||||
perPage := getPageSize(c)
|
||||
offset := (page - 1) * perPage
|
||||
@@ -114,10 +116,8 @@ func (h *ContactHandler) Search(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"contacts": contacts,
|
||||
"meta": gin.H{"count": total, "page": page, "page_size": perPage},
|
||||
})
|
||||
hasMore := int64(len(contacts)) < total
|
||||
c.JSON(http.StatusOK, contactListResponse(c.Request.Context(), h.svc.DB(), contacts, int64(len(contacts)), page, includeContactInboxes(c), &hasMore))
|
||||
}
|
||||
|
||||
// @Summary Get a single contact
|
||||
@@ -155,7 +155,7 @@ func (h *ContactHandler) Get(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, contact)
|
||||
c.JSON(http.StatusOK, contactPayloadResponse(c.Request.Context(), h.svc.DB(), contact, includeContactInboxes(c)))
|
||||
}
|
||||
|
||||
// @Summary Create a new contact
|
||||
@@ -194,7 +194,14 @@ func (h *ContactHandler) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, contact)
|
||||
var contactInbox *model.ContactInbox
|
||||
if h.svc.DB() != nil {
|
||||
var ci model.ContactInbox
|
||||
if err := h.svc.DB().WithContext(c.Request.Context()).Preload("Inbox").Where("contact_id = ?", contact.ID).Order("id DESC").First(&ci).Error; err == nil {
|
||||
contactInbox = &ci
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, contactCreateResponse(c.Request.Context(), h.svc.DB(), contact, contactInbox))
|
||||
}
|
||||
|
||||
// @Summary Update a contact
|
||||
@@ -239,7 +246,7 @@ func (h *ContactHandler) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, contact)
|
||||
c.JSON(http.StatusOK, contactPayloadResponse(c.Request.Context(), h.svc.DB(), contact, includeContactInboxes(c)))
|
||||
}
|
||||
|
||||
// @Summary Delete a contact
|
||||
@@ -276,7 +283,7 @@ func (h *ContactHandler) Delete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "contact deleted"})
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// ListContactInboxes retrieves all contact_inboxes for a contact.
|
||||
@@ -295,10 +302,11 @@ func (h *ContactHandler) ListContactInboxes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"contact_inboxes": contactInboxes,
|
||||
"meta": gin.H{"count": len(contactInboxes)},
|
||||
})
|
||||
payload := make([]any, 0, len(contactInboxes))
|
||||
for i := range contactInboxes {
|
||||
payload = append(payload, serializeContactInbox(&contactInboxes[i]))
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"payload": payload, "meta": gin.H{"count": len(payload)}})
|
||||
}
|
||||
|
||||
// ListConversations retrieves recent conversations for a contact.
|
||||
@@ -368,9 +376,7 @@ func (h *ContactHandler) ListNotes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"notes": notes,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{"payload": notes})
|
||||
}
|
||||
|
||||
// CreateNote creates a note for a contact.
|
||||
@@ -490,6 +496,13 @@ func (h *ContactHandler) DestroyNote(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func includeContactInboxes(c *gin.Context) bool {
|
||||
if raw := c.Query("include_contact_inboxes"); raw != "" {
|
||||
return raw == "true"
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// parseIntOrDefault parses an integer query parameter with a default value.
|
||||
func parseIntOrDefault(c *gin.Context, key string, defaultVal int) int {
|
||||
val := c.Query(key)
|
||||
@@ -636,7 +649,10 @@ func (h *ContactHandler) Import(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
file, _, fileErr := c.Request.FormFile("file")
|
||||
file, _, fileErr := c.Request.FormFile("import_file")
|
||||
if fileErr != nil {
|
||||
file, _, fileErr = c.Request.FormFile("file")
|
||||
}
|
||||
if fileErr != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "csv file required"})
|
||||
return
|
||||
@@ -674,7 +690,11 @@ func (h *ContactHandler) ContactableInboxes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"payload": inboxes})
|
||||
payload := make([]any, 0, len(inboxes))
|
||||
for _, item := range inboxes {
|
||||
payload = append(payload, map[string]any{"inbox": serializeInboxSlim(&item.Inbox), "source_id": item.SourceID})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"payload": payload})
|
||||
}
|
||||
|
||||
// DeleteCustomAttributes removes all custom attributes from a contact.
|
||||
@@ -698,7 +718,12 @@ func (h *ContactHandler) DeleteCustomAttributes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "custom attributes deleted"})
|
||||
contact, svcErr := h.svc.GetByAccountAndID(c.Request.Context(), accountID, contactID)
|
||||
if svcErr != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to load contact"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, contactPayloadResponse(c.Request.Context(), h.svc.DB(), contact, true))
|
||||
}
|
||||
|
||||
// Merge two contacts into one. The base contact survives, mergee is deleted.
|
||||
@@ -749,7 +774,7 @@ func (h *ContactHandler) Filter(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, contacts, p.Page, p.PerPage, total)
|
||||
c.JSON(http.StatusOK, contactListResponse(c.Request.Context(), h.svc.DB(), contacts, total, p.Page, includeContactInboxes(c), nil))
|
||||
}
|
||||
|
||||
// DestroyCustomAttributes removes all custom attributes from a contact.
|
||||
@@ -769,10 +794,18 @@ func (h *ContactHandler) DestroyCustomAttributes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if svcErr := h.svc.DeleteCustomAttributes(c.Request.Context(), accountID, contactID); svcErr != nil {
|
||||
var req struct {
|
||||
CustomAttributes []string `json:"custom_attributes"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
contact, svcErr := h.svc.DestroyCustomAttributes(c.Request.Context(), accountID, contactID, req.CustomAttributes)
|
||||
if svcErr != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to destroy custom attributes"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "custom attributes destroyed"})
|
||||
c.JSON(http.StatusOK, contactPayloadResponse(c.Request.Context(), h.svc.DB(), contact, true))
|
||||
}
|
||||
|
||||
@@ -183,12 +183,13 @@ func (s *ContactHandlerCRUDTestSuite) TestList_Success() {
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
s.Contains(resp, "contacts")
|
||||
s.Contains(resp, "payload")
|
||||
|
||||
meta, ok := resp["meta"]
|
||||
s.True(ok, "response should contain 'meta' key")
|
||||
metaMap := meta.(map[string]interface{})
|
||||
s.Equal(float64(1), metaMap["count"])
|
||||
s.Equal(float64(1), metaMap["current_page"])
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestList_InvalidAccountID() {
|
||||
@@ -227,6 +228,8 @@ func (s *ContactHandlerCRUDTestSuite) TestList_Pagination() {
|
||||
s.True(ok)
|
||||
metaMap := meta.(map[string]interface{})
|
||||
s.Equal(float64(4), metaMap["count"]) // 1 original + 3 new
|
||||
payload := resp["payload"].([]interface{})
|
||||
s.Len(payload, 2)
|
||||
}
|
||||
|
||||
// ===========================
|
||||
@@ -234,7 +237,6 @@ func (s *ContactHandlerCRUDTestSuite) TestList_Pagination() {
|
||||
// ===========================
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestSearch_Success() {
|
||||
skipIfSQLiteForHandler(s.T()) // Search uses ILIKE which SQLite doesn't support
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET",
|
||||
fmt.Sprintf("/api/v1/accounts/%d/contacts/search?q=Jane&page=1&page_size=25", s.account.ID), nil)
|
||||
@@ -244,7 +246,7 @@ func (s *ContactHandlerCRUDTestSuite) TestSearch_Success() {
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.Contains(resp, "contacts")
|
||||
s.Contains(resp, "payload")
|
||||
s.Contains(resp, "meta")
|
||||
}
|
||||
|
||||
@@ -254,12 +256,11 @@ func (s *ContactHandlerCRUDTestSuite) TestSearch_EmptyQuery() {
|
||||
fmt.Sprintf("/api/v1/accounts/%d/contacts/search?q=&page=1&page_size=25", s.account.ID), nil)
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
// Empty query falls back to ListByAccount
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
s.Equal(http.StatusUnprocessableEntity, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.Contains(resp, "contacts")
|
||||
s.Contains(resp, "error")
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestSearch_InvalidAccountID() {
|
||||
@@ -286,10 +287,11 @@ func (s *ContactHandlerCRUDTestSuite) TestGet_Success() {
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var contact model.Contact
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &contact))
|
||||
s.Equal(s.contact.ID, contact.ID)
|
||||
s.Equal("Jane Doe", contact.Name)
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].(map[string]interface{})
|
||||
s.Equal(float64(s.contact.ID), payload["id"])
|
||||
s.Equal("Jane Doe", payload["name"])
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestGet_InvalidAccountID() {
|
||||
@@ -351,13 +353,16 @@ func (s *ContactHandlerCRUDTestSuite) TestCreate_Success() {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusCreated, w.Code)
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var contact model.Contact
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &contact))
|
||||
s.Equal("New Contact", contact.Name)
|
||||
s.Equal("new@example.com", contact.Email)
|
||||
s.NotZero(contact.ID)
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].(map[string]interface{})
|
||||
contact := payload["contact"].(map[string]interface{})
|
||||
s.Equal("New Contact", contact["name"])
|
||||
s.Equal("new@example.com", contact["email"])
|
||||
s.NotZero(contact["id"])
|
||||
s.Contains(payload, "contact_inbox")
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestCreate_InvalidAccountID() {
|
||||
@@ -413,11 +418,15 @@ func (s *ContactHandlerCRUDTestSuite) TestCreate_WithCustomAttributes() {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusCreated, w.Code)
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var contact model.Contact
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &contact))
|
||||
s.Equal("Custom Contact", contact.Name)
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].(map[string]interface{})
|
||||
contact := payload["contact"].(map[string]interface{})
|
||||
s.Equal("Custom Contact", contact["name"])
|
||||
customAttrs := contact["custom_attributes"].(map[string]interface{})
|
||||
s.Equal("gold", customAttrs["tier"])
|
||||
}
|
||||
|
||||
// ===========================
|
||||
@@ -441,10 +450,11 @@ func (s *ContactHandlerCRUDTestSuite) TestUpdate_Success() {
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var contact model.Contact
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &contact))
|
||||
s.Equal("Jane Updated", contact.Name)
|
||||
s.Equal("jane.updated@example.com", contact.Email)
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].(map[string]interface{})
|
||||
s.Equal("Jane Updated", payload["name"])
|
||||
s.Equal("jane.updated@example.com", payload["email"])
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestUpdate_InvalidAccountID() {
|
||||
@@ -514,9 +524,7 @@ func (s *ContactHandlerCRUDTestSuite) TestDelete_Success() {
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.Contains(resp, "message")
|
||||
s.Empty(w.Body.String())
|
||||
|
||||
// Verify soft-delete
|
||||
var found model.Contact
|
||||
@@ -581,7 +589,9 @@ func (s *ContactHandlerCRUDTestSuite) TestListContactInboxes_Success() {
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.Contains(resp, "contact_inboxes")
|
||||
s.Contains(resp, "payload")
|
||||
payload := resp["payload"].([]interface{})
|
||||
s.Len(payload, 1)
|
||||
|
||||
meta := resp["meta"].(map[string]interface{})
|
||||
s.Equal(float64(1), meta["count"])
|
||||
@@ -618,7 +628,7 @@ func (s *ContactHandlerCRUDTestSuite) TestListContactInboxes_EmptyResult() {
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
cis := resp["contact_inboxes"]
|
||||
cis := resp["payload"]
|
||||
s.NotNil(cis)
|
||||
|
||||
meta := resp["meta"].(map[string]interface{})
|
||||
@@ -683,7 +693,7 @@ func (s *ContactHandlerCRUDTestSuite) TestListNotes_Success() {
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.Contains(resp, "notes")
|
||||
s.Contains(resp, "payload")
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestListNotes_InvalidAccountID() {
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type chatwootContactMeta struct {
|
||||
Count int64 `json:"count"`
|
||||
CurrentPage int `json:"current_page"`
|
||||
HasMore *bool `json:"has_more,omitempty"`
|
||||
}
|
||||
|
||||
type chatwootCompanyMeta struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
func contactListResponse(ctx context.Context, db *gorm.DB, contacts []model.Contact, total int64, page int, includeInboxes bool, hasMore *bool) map[string]any {
|
||||
payload := make([]any, 0, len(contacts))
|
||||
for i := range contacts {
|
||||
payload = append(payload, serializeCRMContact(ctx, db, &contacts[i], includeInboxes))
|
||||
}
|
||||
return map[string]any{
|
||||
"meta": chatwootContactMeta{Count: total, CurrentPage: page, HasMore: hasMore},
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
func contactPayloadResponse(ctx context.Context, db *gorm.DB, contact *model.Contact, includeInboxes bool) map[string]any {
|
||||
payload := serializeCRMContact(ctx, db, contact, includeInboxes)
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func contactCreateResponse(ctx context.Context, db *gorm.DB, contact *model.Contact, contactInbox *model.ContactInbox) map[string]any {
|
||||
payload := map[string]any{
|
||||
"contact": serializeCRMContact(ctx, db, contact, true),
|
||||
"contact_inbox": serializeContactInboxShell(contactInbox),
|
||||
}
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func serializeCRMContact(ctx context.Context, db *gorm.DB, contact *model.Contact, includeInboxes bool) map[string]any {
|
||||
payload := map[string]any{
|
||||
"additional_attributes": jsonObject(contact.AdditionalAttributes),
|
||||
"availability_status": "offline",
|
||||
"email": contact.Email,
|
||||
"id": contact.ID,
|
||||
"name": contact.Name,
|
||||
"phone_number": contact.PhoneNumber,
|
||||
"blocked": contact.Blocked,
|
||||
"identifier": contact.Identifier,
|
||||
"thumbnail": contact.AvatarURL,
|
||||
"custom_attributes": jsonObject(contact.CustomAttributes),
|
||||
}
|
||||
if contact.LastActivityAt != nil {
|
||||
payload["last_activity_at"] = *contact.LastActivityAt
|
||||
}
|
||||
if !contact.CreatedAt.IsZero() {
|
||||
payload["created_at"] = contact.CreatedAt.Unix()
|
||||
}
|
||||
if includeInboxes {
|
||||
payload["contact_inboxes"] = []any{}
|
||||
if db != nil {
|
||||
var contactInboxes []model.ContactInbox
|
||||
if err := db.WithContext(ctx).Preload("Inbox").Where("contact_id = ?", contact.ID).Order("id ASC").Find(&contactInboxes).Error; err == nil {
|
||||
items := make([]any, 0, len(contactInboxes))
|
||||
for i := range contactInboxes {
|
||||
items = append(items, serializeContactInbox(&contactInboxes[i]))
|
||||
}
|
||||
payload["contact_inboxes"] = items
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeCompanyContact(ctx context.Context, db *gorm.DB, contact *model.Contact, currentCompanyID uint) map[string]any {
|
||||
payload := serializeCRMContact(ctx, db, contact, false)
|
||||
payload["company_id"] = contact.CompanyID
|
||||
payload["linked_to_current_company"] = contact.CompanyID != nil && *contact.CompanyID == currentCompanyID
|
||||
payload["company"] = nil
|
||||
if db != nil && contact.CompanyID != nil {
|
||||
var company model.Company
|
||||
if err := db.WithContext(ctx).First(&company, *contact.CompanyID).Error; err == nil {
|
||||
payload["company"] = serializeCompany(ctx, db, &company)
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeContactInbox(contactInbox *model.ContactInbox) map[string]any {
|
||||
return map[string]any{
|
||||
"source_id": contactInbox.SourceID,
|
||||
"inbox": serializeInboxSlim(&contactInbox.Inbox),
|
||||
}
|
||||
}
|
||||
|
||||
func serializeContactInboxShell(contactInbox *model.ContactInbox) map[string]any {
|
||||
if contactInbox == nil {
|
||||
return map[string]any{"inbox": nil, "source_id": nil}
|
||||
}
|
||||
return map[string]any{"inbox": contactInbox.Inbox, "source_id": contactInbox.SourceID}
|
||||
}
|
||||
|
||||
func serializeInboxSlim(inbox *model.Inbox) map[string]any {
|
||||
if inbox == nil || inbox.ID == 0 {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
"id": inbox.ID,
|
||||
"name": inbox.Name,
|
||||
"channel_type": inbox.ChannelType,
|
||||
"account_id": inbox.AccountID,
|
||||
}
|
||||
}
|
||||
|
||||
func companyListResponse(ctx context.Context, db *gorm.DB, companies []model.Company, total int64, page int) map[string]any {
|
||||
payload := make([]any, 0, len(companies))
|
||||
for i := range companies {
|
||||
payload = append(payload, serializeCompany(ctx, db, &companies[i]))
|
||||
}
|
||||
return map[string]any{
|
||||
"meta": chatwootCompanyMeta{TotalCount: total, Page: page},
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
func companyPayloadResponse(ctx context.Context, db *gorm.DB, company *model.Company) map[string]any {
|
||||
payload := serializeCompany(ctx, db, company)
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func serializeCompany(ctx context.Context, db *gorm.DB, company *model.Company) map[string]any {
|
||||
return map[string]any{
|
||||
"id": company.ID,
|
||||
"name": company.Name,
|
||||
"contacts_count": companyContactsCount(ctx, db, company),
|
||||
"domain": company.Domain,
|
||||
"description": company.Description,
|
||||
"custom_attributes": jsonObject(company.CustomAttributes),
|
||||
"avatar_url": company.FaviconURL,
|
||||
"last_activity_at": unixTimePtr(company.LastActivityAt),
|
||||
"created_at": company.CreatedAt.Unix(),
|
||||
"updated_at": company.UpdatedAt.Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
func companyContactsResponse(ctx context.Context, db *gorm.DB, companyID uint, contacts []model.Contact, total int64, page int) map[string]any {
|
||||
payload := make([]any, 0, len(contacts))
|
||||
for i := range contacts {
|
||||
payload = append(payload, serializeCompanyContact(ctx, db, &contacts[i], companyID))
|
||||
}
|
||||
return map[string]any{
|
||||
"meta": chatwootCompanyMeta{TotalCount: total, Page: page},
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
func companyNotesResponse(ctx context.Context, db *gorm.DB, notes []model.CompanyNote) map[string]any {
|
||||
payload := make([]any, 0, len(notes))
|
||||
for i := range notes {
|
||||
item := map[string]any{
|
||||
"id": notes[i].ID,
|
||||
"content": notes[i].Content,
|
||||
"user_id": notes[i].UserID,
|
||||
"created_at": notes[i].CreatedAt.Unix(),
|
||||
"updated_at": notes[i].UpdatedAt.Unix(),
|
||||
}
|
||||
payload = append(payload, item)
|
||||
}
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func companyContactsCount(ctx context.Context, db *gorm.DB, company *model.Company) int64 {
|
||||
if db == nil || company == nil || company.ID == 0 {
|
||||
return 0
|
||||
}
|
||||
var count int64
|
||||
_ = db.WithContext(ctx).Model(&model.Contact{}).Where("account_id = ? AND company_id = ?", company.AccountID, company.ID).Count(&count).Error
|
||||
return count
|
||||
}
|
||||
|
||||
func unixTimePtr(value *time.Time) any {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return value.Unix()
|
||||
}
|
||||
Reference in New Issue
Block a user