feat(contacts): align active pagination payloads
This commit is contained in:
@@ -25,6 +25,8 @@ type ContactHandler struct {
|
||||
conversationSvc *service.ConversationService
|
||||
}
|
||||
|
||||
const chatwootContactResultsPerPage = 15
|
||||
|
||||
// NewContactHandler creates a new ContactHandler.
|
||||
func NewContactHandler(svc *service.ContactService, contactInboxSvc *service.ContactInboxService, mergeSvc *service.ContactMergeService, contactNoteSvc *service.ContactNoteService, conversationSvc ...*service.ConversationService) *ContactHandler {
|
||||
h := &ContactHandler{svc: svc, contactInboxSvc: contactInboxSvc, mergeSvc: mergeSvc, contactNoteSvc: contactNoteSvc}
|
||||
@@ -60,7 +62,7 @@ func (h *ContactHandler) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
page := getPage(c)
|
||||
perPage := getPageSize(c)
|
||||
perPage := chatwootContactResultsPerPage
|
||||
offset := (page - 1) * perPage
|
||||
sort := c.DefaultQuery("sort", "")
|
||||
|
||||
@@ -106,7 +108,7 @@ func (h *ContactHandler) Search(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
page := getPage(c)
|
||||
perPage := getPageSize(c)
|
||||
perPage := chatwootContactResultsPerPage
|
||||
offset := (page - 1) * perPage
|
||||
sort := c.DefaultQuery("sort", "")
|
||||
searchMode := search.ParseSearchMode(c.DefaultQuery("search_mode", ""))
|
||||
@@ -676,7 +678,7 @@ func (h *ContactHandler) DeleteContactInbox(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Active retrieves contacts with recent activity.
|
||||
// GET /api/v1/accounts/:id/contacts/active?sort=name&page=1&page_size=25
|
||||
// GET /api/v1/accounts/:id/contacts/active?sort=name&page=1
|
||||
// Reference: Chatwoot contacts#active
|
||||
func (h *ContactHandler) Active(c *gin.Context) {
|
||||
accountID := parseAccountIDParam(c)
|
||||
@@ -686,7 +688,7 @@ func (h *ContactHandler) Active(c *gin.Context) {
|
||||
}
|
||||
|
||||
page := getPage(c)
|
||||
perPage := getPageSize(c)
|
||||
perPage := chatwootContactResultsPerPage
|
||||
offset := (page - 1) * perPage
|
||||
sort := c.DefaultQuery("sort", "")
|
||||
if !h.svc.Ready() {
|
||||
@@ -700,10 +702,7 @@ func (h *ContactHandler) Active(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))
|
||||
}
|
||||
|
||||
// Export downloads contacts as CSV.
|
||||
@@ -932,14 +931,16 @@ func (h *ContactHandler) Filter(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
p := pagination.Parse(c)
|
||||
contacts, total, svcErr := h.svc.Filter(c.Request.Context(), accountID, params, p.Offset, p.PerPage)
|
||||
page := getPage(c)
|
||||
perPage := chatwootContactResultsPerPage
|
||||
offset := (page - 1) * perPage
|
||||
contacts, total, svcErr := h.svc.Filter(c.Request.Context(), accountID, params, offset, perPage)
|
||||
if svcErr != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to filter contacts"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, contactListResponse(c.Request.Context(), h.svc.DB(), contacts, total, p.Page, includeContactInboxes(c), nil))
|
||||
c.JSON(http.StatusOK, contactListResponse(c.Request.Context(), h.svc.DB(), contacts, total, page, includeContactInboxes(c), nil))
|
||||
}
|
||||
|
||||
// DestroyCustomAttributes removes all custom attributes from a contact.
|
||||
|
||||
@@ -87,6 +87,7 @@ func (s *ContactHandlerCRUDTestSuite) SetupSuite() {
|
||||
s.router.Use(gin.Recovery(), s.mockAuthMiddleware())
|
||||
s.router.GET("/api/v1/accounts/:id/contacts", s.handler.List)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/search", s.handler.Search)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/active", s.handler.Active)
|
||||
s.router.POST("/api/v1/accounts/:id/contacts/export", s.handler.ExportRequest)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/export/:export_id/download", s.handler.DownloadExport)
|
||||
s.router.POST("/api/v1/accounts/:id/contacts/import", s.handler.Import)
|
||||
@@ -100,6 +101,7 @@ func (s *ContactHandlerCRUDTestSuite) SetupSuite() {
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/:contact_id/labels", s.handler.ListLabels)
|
||||
s.router.POST("/api/v1/accounts/:id/contacts/:contact_id/labels", s.handler.UpdateLabels)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/:contact_id/contact_inboxes", s.handler.ListContactInboxes)
|
||||
s.router.POST("/api/v1/accounts/:id/contacts/:contact_id/destroy_custom_attributes", s.handler.DestroyCustomAttributes)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/:contact_id/notes", s.handler.ListNotes)
|
||||
s.router.POST("/api/v1/accounts/:id/contacts/:contact_id/notes", s.handler.CreateNote)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/:contact_id/notes/:note_id", s.handler.ShowNote)
|
||||
@@ -233,7 +235,7 @@ func (s *ContactHandlerCRUDTestSuite) TestList_InvalidAccountID() {
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestList_Pagination() {
|
||||
// Create additional contacts
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := 0; i < 16; i++ {
|
||||
c := &model.Contact{
|
||||
AccountID: s.account.ID,
|
||||
Name: fmt.Sprintf("Contact %d", i),
|
||||
@@ -243,7 +245,7 @@ func (s *ContactHandlerCRUDTestSuite) TestList_Pagination() {
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET",
|
||||
fmt.Sprintf("/api/v1/accounts/%d/contacts?page=1&page_size=2", s.account.ID), nil)
|
||||
fmt.Sprintf("/api/v1/accounts/%d/contacts?page=2&page_size=2", s.account.ID), nil)
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
@@ -254,11 +256,41 @@ func (s *ContactHandlerCRUDTestSuite) TestList_Pagination() {
|
||||
meta, ok := resp["meta"]
|
||||
s.True(ok)
|
||||
metaMap := meta.(map[string]interface{})
|
||||
s.Equal(float64(4), metaMap["count"]) // 1 original + 3 new
|
||||
s.Equal(float64(17), metaMap["count"]) // 1 original + 16 new
|
||||
s.Equal(float64(2), metaMap["current_page"])
|
||||
payload := resp["payload"].([]interface{})
|
||||
s.Len(payload, 2)
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestActive_ChatwootPayloadAndPageSize() {
|
||||
now := time.Now().Unix()
|
||||
for i := 0; i < 16; i++ {
|
||||
c := &model.Contact{
|
||||
AccountID: s.account.ID,
|
||||
Name: fmt.Sprintf("Active Contact %d", i),
|
||||
Email: fmt.Sprintf("active-%d@example.com", i),
|
||||
LastActivityAt: &now,
|
||||
}
|
||||
s.Require().NoError(s.db.Create(c).Error)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET",
|
||||
fmt.Sprintf("/api/v1/accounts/%d/contacts/active?page=1&page_size=2", s.account.ID), nil)
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.NotContains(resp, "contacts")
|
||||
meta := resp["meta"].(map[string]interface{})
|
||||
s.Equal(float64(16), meta["count"])
|
||||
s.Equal(float64(1), meta["current_page"])
|
||||
payload := resp["payload"].([]interface{})
|
||||
s.Len(payload, 15)
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Search Contacts
|
||||
// ===========================
|
||||
@@ -777,6 +809,29 @@ func (s *ContactHandlerCRUDTestSuite) TestLabels_UpdateListAndFilter() {
|
||||
s.Equal(float64(s.contact.ID), payload[0].(map[string]interface{})["id"])
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestDestroyCustomAttributes_SelectedKeysPayload() {
|
||||
attrs := model.JSONMap{"tier": "gold", "plan": "pro", "vip": true}
|
||||
s.contact.CustomAttributes = model.ToDatatypesJSON(&attrs)
|
||||
s.Require().NoError(s.db.Save(s.contact).Error)
|
||||
bodyBytes, _ := json.Marshal(map[string]interface{}{"custom_attributes": []string{"plan", "vip"}})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST",
|
||||
fmt.Sprintf("/api/v1/accounts/%d/contacts/%d/destroy_custom_attributes", s.account.ID, s.contact.ID),
|
||||
bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
payload := resp["payload"].(map[string]interface{})
|
||||
customAttrs := payload["custom_attributes"].(map[string]interface{})
|
||||
s.Equal(map[string]interface{}{"tier": "gold"}, customAttrs)
|
||||
s.Contains(payload, "contact_inboxes")
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestDelete_InvalidAccountID() {
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE",
|
||||
|
||||
Reference in New Issue
Block a user