feat(crm): align contact company payloads

This commit is contained in:
2026-06-05 02:34:06 +08:00
parent c7053d975d
commit 3481597c6f
13 changed files with 606 additions and 186 deletions
+58 -25
View File
@@ -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))
}