This commit is contained in:
2026-06-10 07:24:13 +08:00
parent ac7f351308
commit 2bb54b21a5
47 changed files with 2912 additions and 305 deletions
+90 -25
View File
@@ -111,27 +111,23 @@ func (h *MessageHandler) Create(c *gin.Context) {
return
}
// If conversation_id is not in the request body, use the URL param
if req.ConversationID == 0 {
conversationID, urlErr := parseUintParam(c, "conversation_id")
if urlErr != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "conversation_id is required")
return
}
conversation, resolveErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if resolveErr != nil {
handleServiceError(c, resolveErr)
return
}
req.ConversationID = conversation.ID
conversationID, urlErr := parseUintParam(c, "conversation_id")
if urlErr != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "conversation_id is required")
return
}
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)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, req.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
@@ -164,13 +160,22 @@ func (h *MessageHandler) Get(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid message id")
return
}
conversationID, err := parseUintParam(c, "conversation_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation id")
return
}
message, svcErr := h.svc.GetByAccountAndID(c.Request.Context(), accountID, messageID)
conversation, svcErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
message, svcErr := h.svc.GetByAccountConversationAndID(c.Request.Context(), accountID, conversation.ID, messageID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, message.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
@@ -187,6 +192,11 @@ func (h *MessageHandler) Update(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid message id")
return
}
conversationID, err := parseUintParam(c, "conversation_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation id")
return
}
var req service.UpdateMessageRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -194,12 +204,16 @@ func (h *MessageHandler) Update(c *gin.Context) {
return
}
message, svcErr := h.svc.Update(c.Request.Context(), accountID, messageID, req)
conversation, svcErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
message, svcErr := h.svc.UpdateInConversation(c.Request.Context(), accountID, conversation.ID, messageID, req)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, message.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
@@ -231,13 +245,22 @@ func (h *MessageHandler) Delete(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid message id")
return
}
conversationID, err := parseUintParam(c, "conversation_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation id")
return
}
message, svcErr := h.svc.Delete(c.Request.Context(), accountID, messageID)
conversation, svcErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
message, svcErr := h.svc.DeleteInConversation(c.Request.Context(), accountID, conversation.ID, messageID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, message.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
@@ -282,13 +305,24 @@ func (h *MessageHandler) Retry(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid message id")
return
}
conversationID, err := parseUintParam(c, "conversation_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation id")
return
}
message, svcErr := h.svc.Retry(c.Request.Context(), accountID, messageID)
conversation, svcErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if svcErr != nil {
handleServiceError(c, svcErr)
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrValidation, svcErr.Error())
return
}
message, svcErr := h.svc.RetryInConversation(c.Request.Context(), accountID, conversation.ID, messageID)
if svcErr != nil {
// Chatwoot retry rescues creation/status-update errors through render_could_not_create_error,
// so a missing message on this action returns 422 instead of the generic show/delete 404.
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrValidation, svcErr.Error())
return
}
conversation, _ := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, message.ConversationID)
c.JSON(http.StatusOK, serializeMessage(c.Request.Context(), h.svc.DB(), message, conversation))
}
@@ -306,6 +340,11 @@ func (h *MessageHandler) Translate(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid message id")
return
}
conversationID, err := parseUintParam(c, "conversation_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation id")
return
}
var req service.TranslateMessageRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -313,7 +352,12 @@ func (h *MessageHandler) Translate(c *gin.Context) {
return
}
result, svcErr := h.svc.Translate(c.Request.Context(), accountID, messageID, req)
conversation, svcErr := h.svc.ResolveConversationForRoute(c.Request.Context(), accountID, conversationID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
result, svcErr := h.svc.TranslateInConversation(c.Request.Context(), accountID, conversation.ID, messageID, req)
if svcErr != nil {
lower := strings.ToLower(svcErr.Error())
if strings.Contains(lower, "not found") {
@@ -327,7 +371,11 @@ func (h *MessageHandler) Translate(c *gin.Context) {
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, svcErr.Error())
return
}
response.OK(c, result)
if result.AlreadyTranslated {
c.Status(http.StatusOK)
return
}
c.JSON(http.StatusOK, gin.H{"content": result.TranslatedContent})
}
func bindCreateMessageRequest(c *gin.Context, req *service.CreateMessageRequest) error {
@@ -338,12 +386,26 @@ func bindCreateMessageRequest(c *gin.Context, req *service.CreateMessageRequest)
req.Content = c.PostForm("content")
req.MessageType = c.PostForm("message_type")
req.ContentType = c.PostForm("content_type")
if senderID, err := strconv.ParseUint(c.PostForm("sender_id"), 10, 64); err == nil {
req.SenderID = uint(senderID)
}
req.SenderType = c.PostForm("sender_type")
req.SourceID = c.PostForm("source_id")
req.EchoID = c.PostForm("echo_id")
req.ExternalCreatedAt = c.PostForm("external_created_at")
req.EmailHTMLContent = c.PostForm("email_html_content")
req.CCEmails = c.PostForm("cc_emails")
req.BCCEmails = c.PostForm("bcc_emails")
req.ToEmails = c.PostForm("to_emails")
req.Private = strings.EqualFold(c.PostForm("private"), "true") || c.PostForm("private") == "1"
req.IsVoiceMessage = strings.EqualFold(c.PostForm("is_voice_message"), "true") || c.PostForm("is_voice_message") == "1"
req.CampaignID = c.PostForm("campaign_id")
if raw := c.PostForm("content_attributes"); raw != "" {
req.ContentAttributes = []byte(raw)
}
if raw := c.PostForm("template_params"); raw != "" {
req.TemplateParams = []byte(raw)
}
if c.Request.MultipartForm != nil {
for _, key := range []string{"attachments[]", "attachments"} {
for _, file := range c.Request.MultipartForm.File[key] {
@@ -369,5 +431,8 @@ func bindCreateMessageRequest(c *gin.Context, req *service.CreateMessageRequest)
if value, ok := raw["content_attributes"]; ok && string(value) != "null" {
req.ContentAttributes = datatypes.JSON(value)
}
if value, ok := raw["template_params"]; ok && string(value) != "null" {
req.TemplateParams = datatypes.JSON(value)
}
return nil
}