feat(conversations): align chatwoot message serializers

This commit is contained in:
2026-06-05 01:52:07 +08:00
parent c964d6c8fd
commit 9f89cbfe7c
12 changed files with 908 additions and 208 deletions
+61 -10
View File
@@ -1,6 +1,7 @@
package v1
import (
"encoding/json"
"net/http"
"strings"
@@ -10,6 +11,7 @@ import (
"github.com/gochat/gochat/internal/service"
"github.com/gochat/gochat/pkg/pagination"
"github.com/gochat/gochat/pkg/response"
"gorm.io/datatypes"
)
// MessageHandler handles message-related API endpoints.
@@ -43,21 +45,31 @@ func NewMessageHandler(svc *service.MessageService) *MessageHandler {
// GET /api/v1/accounts/:account_id/conversations/:conversation_id/messages
// Reference: Chatwoot conversations#messages (index)
func (h *MessageHandler) List(c *gin.Context) {
accountID, err := parseUintParam(c, "account_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
return
}
conversationID, err := parseUintParam(c, "conversation_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id")
return
}
p := pagination.Parse(c)
messages, total, svcErr := h.svc.ListByConversation(c.Request.Context(), conversationID, p.Offset, p.PerPage)
conversation, svcErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.OKWithMeta(c, toInterfaceSlice(messages), p.Page, p.PerPage, total)
p := pagination.Parse(c)
messages, _, svcErr := h.svc.ListByConversation(c.Request.Context(), conversation.ID, p.Offset, p.PerPage)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeMessageIndex(c.Request.Context(), h.svc.DB(), conversation, messages))
}
// @Summary Create a message in a conversation
@@ -92,7 +104,7 @@ func (h *MessageHandler) Create(c *gin.Context) {
}
var req service.CreateMessageRequest
if err := c.ShouldBindJSON(&req); err != nil {
if err := bindCreateMessageRequest(c, &req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
@@ -104,7 +116,12 @@ func (h *MessageHandler) Create(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "conversation_id is required")
return
}
req.ConversationID = conversationID
conversation, resolveErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if resolveErr != nil {
handleServiceError(c, resolveErr)
return
}
req.ConversationID = conversation.ID
}
message, svcErr := h.svc.Create(c.Request.Context(), accountID, userID, req)
@@ -112,7 +129,8 @@ func (h *MessageHandler) Create(c *gin.Context) {
handleServiceError(c, svcErr)
return
}
response.Created(c, message)
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, req.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
// @Summary Get a single message
@@ -178,7 +196,8 @@ func (h *MessageHandler) Update(c *gin.Context) {
handleServiceError(c, svcErr)
return
}
response.OK(c, message)
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, message.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
// @Summary Delete a message
@@ -264,7 +283,8 @@ func (h *MessageHandler) Retry(c *gin.Context) {
handleServiceError(c, svcErr)
return
}
response.OK(c, message)
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, message.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
// Translate translates a message's content to a target language using LLM.
@@ -304,3 +324,34 @@ func (h *MessageHandler) Translate(c *gin.Context) {
}
response.OK(c, result)
}
func bindCreateMessageRequest(c *gin.Context, req *service.CreateMessageRequest) error {
if strings.HasPrefix(c.ContentType(), "multipart/form-data") {
if err := c.Request.ParseMultipartForm(32 << 20); err != nil {
return err
}
req.Content = c.PostForm("content")
req.MessageType = c.PostForm("message_type")
req.ContentType = c.PostForm("content_type")
req.SourceID = c.PostForm("source_id")
req.EchoID = c.PostForm("echo_id")
req.Private = strings.EqualFold(c.PostForm("private"), "true") || c.PostForm("private") == "1"
if raw := c.PostForm("content_attributes"); raw != "" {
req.ContentAttributes = []byte(raw)
}
return nil
}
var raw map[string]json.RawMessage
if err := c.ShouldBindJSON(&raw); err != nil {
return err
}
bytes, _ := json.Marshal(raw)
if err := json.Unmarshal(bytes, req); err != nil {
return err
}
if value, ok := raw["content_attributes"]; ok && string(value) != "null" {
req.ContentAttributes = datatypes.JSON(value)
}
return nil
}