feat(crm): complete contact label avatar gaps

This commit is contained in:
2026-06-05 02:53:24 +08:00
parent 5cf735076d
commit 7a033e28a0
16 changed files with 512 additions and 31 deletions
+72 -2
View File
@@ -63,7 +63,7 @@ func (h *ContactHandler) List(c *gin.Context) {
offset := (page - 1) * perPage
sort := c.DefaultQuery("sort", "")
contacts, total, svcErr := h.svc.ListByAccount(c.Request.Context(), accountID, offset, perPage, sort)
contacts, total, svcErr := h.svc.ListByAccount(c.Request.Context(), accountID, offset, perPage, sort, contactLabelsParam(c))
if svcErr != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to list contacts"})
return
@@ -110,7 +110,7 @@ func (h *ContactHandler) Search(c *gin.Context) {
sort := c.DefaultQuery("sort", "")
searchMode := search.ParseSearchMode(c.DefaultQuery("search_mode", ""))
contacts, total, svcErr := h.svc.Search(c.Request.Context(), accountID, query, offset, perPage, sort, searchMode)
contacts, total, svcErr := h.svc.Search(c.Request.Context(), accountID, query, offset, perPage, sort, searchMode, contactLabelsParam(c))
if svcErr != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to search contacts"})
return
@@ -286,6 +286,70 @@ func (h *ContactHandler) Delete(c *gin.Context) {
c.Status(http.StatusOK)
}
func (h *ContactHandler) DeleteAvatar(c *gin.Context) {
accountID := parseAccountIDParam(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
return
}
contactID, err := parseUintParam(c, "contact_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid contact id")
return
}
contact, svcErr := h.svc.DeleteAvatar(c.Request.Context(), accountID, contactID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, contactPayloadResponse(c.Request.Context(), h.svc.DB(), contact, false))
}
func (h *ContactHandler) ListLabels(c *gin.Context) {
accountID := parseAccountIDParam(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
return
}
contactID, err := parseUintParam(c, "contact_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid contact id")
return
}
labels, svcErr := h.svc.GetLabels(c.Request.Context(), accountID, contactID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, gin.H{"payload": labels})
}
func (h *ContactHandler) UpdateLabels(c *gin.Context) {
accountID := parseAccountIDParam(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
return
}
contactID, err := parseUintParam(c, "contact_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid contact id")
return
}
var req struct {
Labels []string `json:"labels"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
labels, svcErr := h.svc.UpdateLabels(c.Request.Context(), accountID, contactID, req.Labels)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, gin.H{"payload": labels})
}
// ListContactInboxes retrieves all contact_inboxes for a contact.
// GET /api/v1/accounts/:id/contacts/:contact_id/contact_inboxes
// Reference: Chatwoot contacts#contact_inboxes (nested resource)
@@ -503,6 +567,12 @@ func includeContactInboxes(c *gin.Context) bool {
return true
}
func contactLabelsParam(c *gin.Context) []string {
labels := c.QueryArray("labels[]")
labels = append(labels, c.QueryArray("labels")...)
return labels
}
// parseIntOrDefault parses an integer query parameter with a default value.
func parseIntOrDefault(c *gin.Context, key string, defaultVal int) int {
val := c.Query(key)