fix(shangwutong): close contact sync review gaps (#4)
Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/gochat/gochat/internal/channel"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/repository"
|
||||
"github.com/gochat/gochat/internal/search"
|
||||
@@ -24,14 +23,13 @@ import (
|
||||
// ContactHandler handles contact-related API endpoints.
|
||||
// Reference: Chatwoot app/controllers/api/v1/contacts_controller.rb
|
||||
type ContactHandler struct {
|
||||
svc *service.ContactService
|
||||
contactInboxSvc *service.ContactInboxService
|
||||
mergeSvc *service.ContactMergeService
|
||||
contactNoteSvc *service.ContactNoteService
|
||||
conversationSvc *service.ConversationService
|
||||
presence contactPresenceReader
|
||||
eventPublisher *ws.EventPublisher
|
||||
channelDispatcher *channel.Dispatcher
|
||||
svc *service.ContactService
|
||||
contactInboxSvc *service.ContactInboxService
|
||||
mergeSvc *service.ContactMergeService
|
||||
contactNoteSvc *service.ContactNoteService
|
||||
conversationSvc *service.ConversationService
|
||||
presence contactPresenceReader
|
||||
eventPublisher *ws.EventPublisher
|
||||
}
|
||||
|
||||
const chatwootContactResultsPerPage = 15
|
||||
@@ -50,11 +48,6 @@ func (h *ContactHandler) WithEventPublisher(publisher *ws.EventPublisher) *Conta
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *ContactHandler) WithChannelDispatcher(dispatcher *channel.Dispatcher) *ContactHandler {
|
||||
h.channelDispatcher = dispatcher
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *ContactHandler) WithContactPresence(presence contactPresenceReader) *ContactHandler {
|
||||
h.presence = presence
|
||||
return h
|
||||
@@ -276,7 +269,6 @@ func (h *ContactHandler) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
before, _ := h.svc.GetByAccountAndID(c.Request.Context(), accountID, contactID)
|
||||
contact, svcErr := h.svc.Update(c.Request.Context(), accountID, contactID, req)
|
||||
if svcErr != nil {
|
||||
var validationErr *service.ContactValidationError
|
||||
@@ -290,12 +282,6 @@ func (h *ContactHandler) Update(c *gin.Context) {
|
||||
|
||||
payload := contactPayloadResponse(h.requestContext(c), h.svc.DB(), contact, includeContactInboxes(c))
|
||||
h.publishContactEvent(accountID, ws.EventContactUpdated, contact)
|
||||
if h.channelDispatcher != nil && req.Name != "" && (before == nil || before.Name != contact.Name) {
|
||||
event := channel.NewChannelEvent(channel.EventContactUpdated, channel.ChannelAPI, accountID, 0)
|
||||
event.ContactID, event.Data["contact"] = contact.ID, contact
|
||||
event.Data["changed_attributes"] = map[string]any{"name": contact.Name}
|
||||
_ = h.channelDispatcher.Dispatch(c.Request.Context(), event)
|
||||
}
|
||||
c.JSON(http.StatusOK, payload)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -22,6 +23,50 @@ type ShangwutongConnectorHandler struct {
|
||||
messageSvc *service.MessageService
|
||||
}
|
||||
|
||||
type shangwutongContactMetadataRequest struct {
|
||||
CID string `json:"cid"`
|
||||
}
|
||||
|
||||
func (h *ShangwutongConnectorHandler) UpdateContactMetadata(c *gin.Context) {
|
||||
inbox, ok := h.authorizedInbox(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(c.Param("source_id")) == "" {
|
||||
h.connectorError(c, http.StatusBadRequest, "invalid_source_id", "source_id is required", false)
|
||||
return
|
||||
}
|
||||
var request shangwutongContactMetadataRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil || strings.TrimSpace(request.CID) == "" {
|
||||
h.connectorError(c, http.StatusUnprocessableEntity, "invalid_contact_metadata", "cid is required", false)
|
||||
return
|
||||
}
|
||||
var contactInbox model.ContactInbox
|
||||
if err := h.db.WithContext(c.Request.Context()).Where("inbox_id = ? AND source_id = ?", inbox.ID, c.Param("source_id")).First(&contactInbox).Error; err != nil {
|
||||
h.connectorError(c, http.StatusNotFound, "not_found", "contact source not found", false)
|
||||
return
|
||||
}
|
||||
metadata := map[string]any{}
|
||||
if len(contactInbox.ChannelMetadata) > 0 {
|
||||
_ = json.Unmarshal(contactInbox.ChannelMetadata, &metadata)
|
||||
}
|
||||
if metadata["cid"] == strings.TrimSpace(request.CID) {
|
||||
c.JSON(http.StatusOK, gin.H{"updated": false})
|
||||
return
|
||||
}
|
||||
metadata["cid"] = strings.TrimSpace(request.CID)
|
||||
encoded, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
h.connectorError(c, http.StatusInternalServerError, "contact_metadata_update_failed", "failed to encode contact metadata", true)
|
||||
return
|
||||
}
|
||||
if err := h.db.WithContext(c.Request.Context()).Model(&contactInbox).Update("channel_metadata", encoded).Error; err != nil {
|
||||
h.connectorError(c, http.StatusInternalServerError, "contact_metadata_update_failed", "failed to update contact metadata", true)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
func NewShangwutongConnectorHandler(db *gorm.DB, messageSvc *service.MessageService) *ShangwutongConnectorHandler {
|
||||
return &ShangwutongConnectorHandler{db: db, messageSvc: messageSvc}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,21 @@ func TestShangwutongConnectorMessageResultIsVersionedAndIdempotent(t *testing.T)
|
||||
require.JSONEq(t, `{"shangwutong":["123456","123457"]}`, string(message.ExternalSourceIDs))
|
||||
}
|
||||
|
||||
func TestShangwutongConnectorContactMetadataIsScopedAndIdempotent(t *testing.T) {
|
||||
router, db, token, inbox, _ := setupShangwutongConnectorAPI(t)
|
||||
contact := &model.Contact{AccountID: inbox.AccountID, Name: "Visitor"}
|
||||
require.NoError(t, db.Create(contact).Error)
|
||||
ci := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "visitor"}
|
||||
require.NoError(t, db.Create(ci).Error)
|
||||
path := fmt.Sprintf("/api/v1/connector/shangwutong/inboxes/%d/contacts/visitor", inbox.ID)
|
||||
response := connectorRequest(t, router, token, http.MethodPatch, path, map[string]string{"cid": "cid-1"})
|
||||
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
|
||||
response = connectorRequest(t, router, token, http.MethodPatch, path, map[string]string{"cid": "cid-1"})
|
||||
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
|
||||
require.NoError(t, db.First(ci, ci.ID).Error)
|
||||
require.JSONEq(t, `{"cid":"cid-1"}`, string(ci.ChannelMetadata))
|
||||
}
|
||||
|
||||
func setupShangwutongConnectorAPI(t *testing.T) (*gin.Engine, *gorm.DB, string, *model.Inbox, *model.Inbox) {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
@@ -132,6 +147,7 @@ func setupShangwutongConnectorAPI(t *testing.T) (*gin.Engine, *gorm.DB, string,
|
||||
require.NoError(t, db.AutoMigrate(
|
||||
&model.Account{}, &model.Inbox{}, &model.ChannelShangwutongConfig{}, &channelmodel.ChannelAPI{},
|
||||
&model.PlatformApp{}, &model.AccessToken{}, &model.Permissible{}, &model.Contact{}, &model.Conversation{},
|
||||
&model.ContactInbox{},
|
||||
&model.Message{}, &model.Attachment{}, &model.BackgroundJob{},
|
||||
))
|
||||
active := true
|
||||
@@ -173,6 +189,7 @@ func setupShangwutongConnectorAPI(t *testing.T) (*gin.Engine, *gorm.DB, string,
|
||||
group.GET("/inboxes/:inbox_id", handler.GetInbox)
|
||||
group.PUT("/inboxes/:inbox_id/status", handler.UpdateInboxStatus)
|
||||
group.PUT("/inboxes/:inbox_id/messages/:message_id/status", handler.UpdateMessageStatus)
|
||||
group.PATCH("/inboxes/:inbox_id/contacts/:source_id", handler.UpdateContactMetadata)
|
||||
return router, db, token, inboxes[0], inboxes[1]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user