319 lines
10 KiB
Plaintext
319 lines
10 KiB
Plaintext
package v1
|
|
|
|
// TwilioChannelHandler handles Twilio SMS channel-specific configuration CRUD.
|
|
// Reference: Chatwoot app/controllers/api/v1/accounts/channels/twilio_controller.rb
|
|
//
|
|
// Twilio SMS API: https://www.twilio.com/docs/sms
|
|
//
|
|
// gochat maps Twilio SMS channel operations to:
|
|
// - POST /api/v1/accounts/:id/channels/twilio_channel → create Twilio SMS inbox
|
|
// - GET /api/v1/accounts/:id/channels/twilio_channel/:tw_id → get Twilio SMS channel
|
|
// - PATCH /api/v1/accounts/:id/channels/twilio_channel/:tw_id → update Twilio SMS channel
|
|
// - DELETE /api/v1/accounts/:id/channels/twilio_channel/:tw_id → delete Twilio SMS channel
|
|
// - GET /api/v1/accounts/:id/channels/twilio_channel → list Twilio SMS channels
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
twiliochannel "github.com/gochat/gochat/internal/channel/twilio"
|
|
channelmodel "github.com/gochat/gochat/internal/model/channel"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"github.com/gochat/gochat/internal/service"
|
|
applogger "github.com/gochat/gochat/pkg/logger"
|
|
)
|
|
|
|
// TwilioChannelHandler handles Twilio SMS channel management.
|
|
// Follows FacebookChannelHandler pattern: uses top-level ChannelTwilioSMSService for CRUD,
|
|
// internal TwilioProvider for webhook setup via OnCreate.
|
|
type TwilioChannelHandler struct {
|
|
twChannelSvc *service.ChannelTwilioSMSService
|
|
twProvider *twiliochannel.TwilioProvider
|
|
inboxSvc *service.InboxService
|
|
twRepo *repository.ChannelTwilioSMSRepo
|
|
}
|
|
|
|
// NewTwilioChannelHandler creates a new Twilio SMS channel handler.
|
|
func NewTwilioChannelHandler(
|
|
twChannelSvc *service.ChannelTwilioSMSService,
|
|
twProvider *twiliochannel.TwilioProvider,
|
|
inboxSvc *service.InboxService,
|
|
twRepo *repository.ChannelTwilioSMSRepo,
|
|
) *TwilioChannelHandler {
|
|
return &TwilioChannelHandler{
|
|
twChannelSvc: twChannelSvc,
|
|
twProvider: twProvider,
|
|
inboxSvc: inboxSvc,
|
|
twRepo: twRepo,
|
|
}
|
|
}
|
|
|
|
// CreateTwilioSMSChannel creates a new Twilio SMS channel and its associated inbox.
|
|
// POST /api/v1/accounts/:id/channels/twilio_channel
|
|
func (h *TwilioChannelHandler) CreateTwilioSMSChannel(c *gin.Context) {
|
|
accountIDStr := c.Param("id")
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
AccountSID string `json:"account_sid" validate:"required"`
|
|
AuthToken string `json:"auth_token" validate:"required"`
|
|
PhoneNumber string `json:"phone_number" validate:"required"`
|
|
MessagingServiceSID string `json:"messaging_service_sid"`
|
|
InboxName string `json:"inbox_name"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
applogger.L().Errorf("Failed to bind Twilio SMS create request: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
|
|
// Create inbox first
|
|
inboxReq := service.CreateInboxRequest{
|
|
Name: req.InboxName,
|
|
ChannelType: "twilio_sms",
|
|
}
|
|
if req.InboxName == "" {
|
|
inboxReq.Name = req.PhoneNumber
|
|
}
|
|
|
|
inbox, err := h.inboxSvc.Create(ctx, uint(accountID), inboxReq)
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to create inbox for Twilio SMS channel: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create inbox"})
|
|
return
|
|
}
|
|
|
|
// Create Twilio SMS channel record
|
|
ch := &channelmodel.ChannelTwilioSMS{
|
|
AccountSID: req.AccountSID,
|
|
PhoneNumber: req.PhoneNumber,
|
|
MessagingServiceSID: req.MessagingServiceSID,
|
|
AccountID: uint(accountID),
|
|
InboxID: inbox.ID,
|
|
}
|
|
|
|
if err := h.twChannelSvc.Create(ctx, ch); err != nil {
|
|
applogger.L().Errorf("Failed to create Twilio SMS channel: %v", err)
|
|
// Rollback inbox
|
|
if delErr := h.inboxSvc.DeleteByAccount(ctx, uint(accountID), inbox.ID); delErr != nil {
|
|
applogger.L().Warnf("Failed to rollback inbox after Twilio SMS channel creation failure: %v", delErr)
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create Twilio SMS channel"})
|
|
return
|
|
}
|
|
|
|
// Invoke provider OnCreate for webhook setup
|
|
if onCreateErr := h.twProvider.OnCreate(ctx, inbox, ch); onCreateErr != nil {
|
|
applogger.L().Warnf("Twilio SMS OnCreate webhook setup failed (non-blocking): %v", onCreateErr)
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"id": ch.ID,
|
|
"account_id": ch.AccountID,
|
|
"inbox_id": ch.InboxID,
|
|
"account_sid": ch.AccountSID,
|
|
"phone_number": ch.PhoneNumber,
|
|
"messaging_service_sid": ch.MessagingServiceSID,
|
|
})
|
|
}
|
|
|
|
// GetTwilioSMSChannel retrieves a Twilio SMS channel by ID.
|
|
// GET /api/v1/accounts/:id/channels/twilio_channel/:tw_id
|
|
func (h *TwilioChannelHandler) GetTwilioSMSChannel(c *gin.Context) {
|
|
accountIDStr := c.Param("id")
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"})
|
|
return
|
|
}
|
|
|
|
twIDStr := c.Param("tw_id")
|
|
twID, err := strconv.ParseUint(twIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid tw_id"})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
|
|
ch, err := h.twChannelSvc.GetByID(ctx, uint(twID))
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to get Twilio SMS channel: %v", err)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Twilio SMS channel not found"})
|
|
return
|
|
}
|
|
|
|
// Verify account ownership
|
|
if ch.AccountID != uint(accountID) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Twilio SMS channel does not belong to this account"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"id": ch.ID,
|
|
"account_id": ch.AccountID,
|
|
"inbox_id": ch.InboxID,
|
|
"account_sid": ch.AccountSID,
|
|
"phone_number": ch.PhoneNumber,
|
|
"messaging_service_sid": ch.MessagingServiceSID,
|
|
})
|
|
}
|
|
|
|
// ListTwilioSMSChannels lists all Twilio SMS channels for an account.
|
|
// GET /api/v1/accounts/:id/channels/twilio_channel
|
|
func (h *TwilioChannelHandler) ListTwilioSMSChannels(c *gin.Context) {
|
|
accountIDStr := c.Param("id")
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
|
|
channels, err := h.twChannelSvc.ListByAccount(ctx, uint(accountID))
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to list Twilio SMS channels: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list Twilio SMS channels"})
|
|
return
|
|
}
|
|
|
|
result := make([]gin.H, 0, len(channels))
|
|
for _, ch := range channels {
|
|
result = append(result, gin.H{
|
|
"id": ch.ID,
|
|
"account_id": ch.AccountID,
|
|
"inbox_id": ch.InboxID,
|
|
"account_sid": ch.AccountSID,
|
|
"phone_number": ch.PhoneNumber,
|
|
"messaging_service_sid": ch.MessagingServiceSID,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"channels": result})
|
|
}
|
|
|
|
// DeleteTwilioSMSChannel deletes a Twilio SMS channel and its associated inbox.
|
|
// DELETE /api/v1/accounts/:id/channels/twilio_channel/:tw_id
|
|
func (h *TwilioChannelHandler) DeleteTwilioSMSChannel(c *gin.Context) {
|
|
accountIDStr := c.Param("id")
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"})
|
|
return
|
|
}
|
|
|
|
twIDStr := c.Param("tw_id")
|
|
twID, err := strconv.ParseUint(twIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid tw_id"})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
|
|
ch, err := h.twChannelSvc.GetByID(ctx, uint(twID))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Twilio SMS channel not found"})
|
|
return
|
|
}
|
|
|
|
// Verify account ownership
|
|
if ch.AccountID != uint(accountID) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Twilio SMS channel does not belong to this account"})
|
|
return
|
|
}
|
|
|
|
// Invoke provider OnDestroy for webhook cleanup
|
|
if onDestroyErr := h.twProvider.OnDestroy(ctx, inboxFromID(ch.InboxID), ch); onDestroyErr != nil {
|
|
applogger.L().Warnf("Twilio SMS OnDestroy webhook cleanup failed (non-blocking): %v", onDestroyErr)
|
|
}
|
|
|
|
// Delete the channel record
|
|
if err := h.twChannelSvc.Delete(ctx, uint(twID)); err != nil {
|
|
applogger.L().Errorf("Failed to delete Twilio SMS channel: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete Twilio SMS channel"})
|
|
return
|
|
}
|
|
|
|
// Delete the associated inbox
|
|
if ch.InboxID > 0 {
|
|
if delErr := h.inboxSvc.DeleteByAccount(ctx, uint(accountID), ch.InboxID); delErr != nil {
|
|
applogger.L().Warnf("Failed to delete inbox for Twilio SMS channel: %v", delErr)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Twilio SMS channel deleted successfully"})
|
|
}
|
|
|
|
// UpdateTwilioSMSChannel updates a Twilio SMS channel configuration.
|
|
// PATCH /api/v1/accounts/:id/channels/twilio_channel/:tw_id
|
|
func (h *TwilioChannelHandler) UpdateTwilioSMSChannel(c *gin.Context) {
|
|
accountIDStr := c.Param("id")
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"})
|
|
return
|
|
}
|
|
|
|
twIDStr := c.Param("tw_id")
|
|
twID, err := strconv.ParseUint(twIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid tw_id"})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
|
|
ch, err := h.twChannelSvc.GetByID(ctx, uint(twID))
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to get Twilio SMS channel for update: %v", err)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Twilio SMS channel not found"})
|
|
return
|
|
}
|
|
|
|
// Verify account ownership
|
|
if ch.AccountID != uint(accountID) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Twilio SMS channel does not belong to this account"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
PhoneNumber *string `json:"phone_number"`
|
|
MessagingServiceSID *string `json:"messaging_service_sid"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
if req.PhoneNumber != nil {
|
|
ch.PhoneNumber = *req.PhoneNumber
|
|
}
|
|
if req.MessagingServiceSID != nil {
|
|
ch.MessagingServiceSID = *req.MessagingServiceSID
|
|
}
|
|
|
|
if err := h.twChannelSvc.Update(ctx, ch); err != nil {
|
|
applogger.L().Errorf("Failed to update Twilio SMS channel: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update Twilio SMS channel"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"id": ch.ID,
|
|
"account_id": ch.AccountID,
|
|
"inbox_id": ch.InboxID,
|
|
"account_sid": ch.AccountSID,
|
|
"phone_number": ch.PhoneNumber,
|
|
"messaging_service_sid": ch.MessagingServiceSID,
|
|
})
|
|
} |