feat(webhook): persist incoming provider messages

This commit is contained in:
2026-06-04 23:54:07 +08:00
parent b3865f9084
commit 5eb726b124
14 changed files with 535 additions and 84 deletions
+42 -23
View File
@@ -5,9 +5,14 @@
package webhook
import (
"github.com/gin-gonic/gin"
"context"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/gochat/gochat/internal/channel"
"github.com/gochat/gochat/internal/channel/whatsapp"
"github.com/gochat/gochat/internal/model"
)
// WhatsAppWebhookHandler is a Gin adapter that wraps the WhatsApp
@@ -17,45 +22,59 @@ import (
//
// Chatwoot-style routes:
//
// GET /webhooks/whatsapp/:phone_number → HandleWhatsAppVerification
// POST /webhooks/whatsapp/:phone_number → HandleWhatsAppWebhook
// GET /webhooks/whatsapp/:phone_number → HandleWhatsAppVerification
// POST /webhooks/whatsapp/:phone_number → HandleWhatsAppWebhook
//
// WhatsApp Cloud API webhook verification (GET):
//
// The Meta platform sends a GET request with query parameters:
// - hub.mode = "subscribe"
// - hub.verify_token = the token configured in the Meta dashboard
// - hub.challenge = a string the endpoint must echo back verbatim
// The Meta platform sends a GET request with query parameters:
// - hub.mode = "subscribe"
// - hub.verify_token = the token configured in the Meta dashboard
// - hub.challenge = a string the endpoint must echo back verbatim
//
// The handler validates hub.verify_token against the stored token and
// returns hub.challenge as the response body with HTTP 200, or returns
// HTTP 403 on mismatch.
// The handler validates hub.verify_token against the stored token and
// returns hub.challenge as the response body with HTTP 200, or returns
// HTTP 403 on mismatch.
//
// WhatsApp Cloud API webhook events (POST):
//
// Meta delivers event payloads as JSON with structure:
// {
// "object": "whatsapp_business_account",
// "entry": [ { "changes": [ ... ] } ]
// }
// Meta delivers event payloads as JSON with structure:
// {
// "object": "whatsapp_business_account",
// "entry": [ { "changes": [ ... ] } ]
// }
//
// The handler parses the payload, dispatches incoming messages and status
// updates, and must respond with HTTP 200 OK within 10 seconds to avoid
// Meta retrying delivery.
// The handler parses the payload, dispatches incoming messages and status
// updates, and must respond with HTTP 200 OK within 10 seconds to avoid
// Meta retrying delivery.
type WhatsAppWebhookHandler struct {
provider *whatsapp.WhatsAppProvider
waWebhook *whatsapp.WebhookHandler
provider *whatsapp.WhatsAppProvider
waWebhook *whatsapp.WebhookHandler
persister *IncomingPersister
}
// NewWhatsAppWebhookHandler creates a Gin adapter wrapping the WhatsApp
// sub-package's WebhookHandler. The provider is stored for future use (e.g.
// health checks or direct API calls) while waWebhook is the core handler that
// processes verification and event requests.
func NewWhatsAppWebhookHandler(provider *whatsapp.WhatsAppProvider, waWebhook *whatsapp.WebhookHandler) *WhatsAppWebhookHandler {
return &WhatsAppWebhookHandler{
func NewWhatsAppWebhookHandler(provider *whatsapp.WhatsAppProvider, waWebhook *whatsapp.WebhookHandler, db *gorm.DB) *WhatsAppWebhookHandler {
h := &WhatsAppWebhookHandler{
provider: provider,
waWebhook: waWebhook,
persister: NewIncomingPersister(db),
}
if waWebhook != nil && h.persister != nil {
waWebhook.SetIncomingPersister(whatsAppPersisterAdapter{persister: h.persister})
}
return h
}
type whatsAppPersisterAdapter struct {
persister *IncomingPersister
}
func (a whatsAppPersisterAdapter) PersistIncoming(ctx context.Context, inbox *model.Inbox, msg *channel.IncomingMessage) (interface{}, error) {
return a.persister.PersistIncoming(ctx, inbox, msg)
}
// HandleWhatsAppVerification handles GET requests for WhatsApp Cloud API
@@ -80,4 +99,4 @@ func (h *WhatsAppWebhookHandler) HandleWhatsAppVerification(c *gin.Context) {
// Expected Gin route: POST /webhooks/whatsapp/:phone_number
func (h *WhatsAppWebhookHandler) HandleWhatsAppWebhook(c *gin.Context) {
h.waWebhook.HandleWebhookEvent(c)
}
}