feat(messages): queue delivery statuses
This commit is contained in:
@@ -13,11 +13,13 @@ package webhook
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gochat/gochat/internal/channel"
|
||||
twiliochannel "github.com/gochat/gochat/internal/channel/twilio"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
channelmodel "github.com/gochat/gochat/internal/model/channel"
|
||||
"github.com/gochat/gochat/internal/worker"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -31,6 +33,13 @@ type TwilioWebhookHandler struct {
|
||||
persister *IncomingPersister
|
||||
}
|
||||
|
||||
func (h *TwilioWebhookHandler) WithWorkerPool(wp *worker.WorkerPool) *TwilioWebhookHandler {
|
||||
if h != nil && h.persister != nil {
|
||||
h.persister.SetWorkerPool(wp)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// NewTwilioWebhookHandler creates a Twilio SMS webhook handler for Gin integration.
|
||||
func NewTwilioWebhookHandler(twilioWebhook *twiliochannel.WebhookHandler, db *gorm.DB, dispatcher ...*channel.Dispatcher) *TwilioWebhookHandler {
|
||||
return &TwilioWebhookHandler{
|
||||
@@ -74,33 +83,65 @@ func (h *TwilioWebhookHandler) HandleTwilioInboundSMS(c *gin.Context) {
|
||||
// HandleTwilioDeliveryStatus processes a Twilio delivery status callback.
|
||||
func (h *TwilioWebhookHandler) HandleTwilioDeliveryStatus(c *gin.Context) {
|
||||
phoneNumber := c.Param("phone_number")
|
||||
if phoneNumber == "" {
|
||||
applogger.L().Warn("Twilio status webhook: missing phone_number in path")
|
||||
c.Status(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
// Lookup inbox from database
|
||||
inbox, err := h.lookupInboxByPhoneNumber(phoneNumber)
|
||||
if err != nil {
|
||||
applogger.L().Warnf("Twilio status webhook: inbox lookup failed for phone_number %s: %v", phoneNumber, err)
|
||||
c.Status(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
applogger.L().Errorf("Twilio status webhook: parse form failed for inbox %d: %v", inbox.ID, err)
|
||||
c.Status(http.StatusOK)
|
||||
applogger.L().Errorf("Twilio status webhook: parse form failed: %v", err)
|
||||
c.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
var inbox *model.Inbox
|
||||
var err error
|
||||
if phoneNumber != "" {
|
||||
inbox, err = h.lookupInboxByPhoneNumber(phoneNumber)
|
||||
} else {
|
||||
inbox, err = h.lookupDeliveryStatusInbox(c.Request.Form)
|
||||
}
|
||||
if err != nil {
|
||||
applogger.L().Warnf("Twilio status webhook: inbox lookup failed: %v", err)
|
||||
c.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
messageSID := c.Request.FormValue("MessageSid")
|
||||
messageStatus := c.Request.FormValue("MessageStatus")
|
||||
if mapped, ok := mapTwilioMessageStatus(messageStatus); ok {
|
||||
if err := h.persister.UpdateMessageStatus(c.Request.Context(), inbox, messageSID, mapped, nil); err != nil {
|
||||
if err := h.persister.UpdateMessageStatusWithError(c.Request.Context(), inbox, messageSID, mapped, nil, twilioExternalError(c.Request.FormValue("ErrorCode"), c.Request.FormValue("ErrorMessage"), messageStatus)); err != nil {
|
||||
applogger.L().Errorf("Twilio status webhook: status persistence failed for inbox %d sid=%s status=%s: %v", inbox.ID, messageSID, messageStatus, err)
|
||||
}
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func twilioExternalError(errorCode, errorMessage, status string) string {
|
||||
if errorCode == "" || (status != "failed" && status != "undelivered") {
|
||||
return ""
|
||||
}
|
||||
if errorMessage != "" {
|
||||
return fmt.Sprintf("%s - %s", errorCode, errorMessage)
|
||||
}
|
||||
return fmt.Sprintf("Twilio delivery failed with error code %s", errorCode)
|
||||
}
|
||||
|
||||
func (h *TwilioWebhookHandler) lookupDeliveryStatusInbox(params url.Values) (*model.Inbox, error) {
|
||||
if h.db == nil {
|
||||
return nil, fmt.Errorf("twilio webhook database is not configured")
|
||||
}
|
||||
var twilioChannel channelmodel.ChannelTwilioSMS
|
||||
query := h.db
|
||||
if sid := params.Get("MessagingServiceSid"); sid != "" {
|
||||
query = query.Where(&channelmodel.ChannelTwilioSMS{MessagingServiceSID: sid})
|
||||
} else if accountSID, from := params.Get("AccountSid"), params.Get("From"); accountSID != "" && from != "" {
|
||||
query = query.Where(&channelmodel.ChannelTwilioSMS{AccountSID: accountSID, PhoneNumber: from})
|
||||
} else {
|
||||
return nil, fmt.Errorf("delivery status missing MessagingServiceSid or AccountSid/From")
|
||||
}
|
||||
if err := query.First(&twilioChannel).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var inbox model.Inbox
|
||||
if err := h.db.Where("id = ? AND channel_type IN ?", twilioChannel.InboxID, []string{"twilio_sms", "sms"}).First(&inbox).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &inbox, nil
|
||||
}
|
||||
|
||||
func mapTwilioMessageStatus(status string) (model.MessageStatus, bool) {
|
||||
|
||||
Reference in New Issue
Block a user