104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
)
|
|
|
|
// WebhookSubscriptionService provides business logic for managing webhook subscriptions.
|
|
// Reference: Chatwoot webhook integration + P2B M8 spec
|
|
type WebhookSubscriptionService struct {
|
|
webhookSubRepo *repository.WebhookSubscriptionRepo
|
|
}
|
|
|
|
// NewWebhookSubscriptionService creates a new WebhookSubscription service with required dependencies.
|
|
func NewWebhookSubscriptionService(webhookSubRepo *repository.WebhookSubscriptionRepo) *WebhookSubscriptionService {
|
|
return &WebhookSubscriptionService{
|
|
webhookSubRepo: webhookSubRepo,
|
|
}
|
|
}
|
|
|
|
// ListSubscriptions retrieves all webhook subscriptions for an account.
|
|
func (s *WebhookSubscriptionService) ListSubscriptions(ctx context.Context, accountID uint) ([]model.WebhookSubscription, error) {
|
|
return s.webhookSubRepo.ListByAccount(ctx, accountID)
|
|
}
|
|
|
|
// CreateSubscription creates a new webhook subscription with a generated signing secret.
|
|
func (s *WebhookSubscriptionService) CreateSubscription(ctx context.Context, accountID uint, url string, events []string) (*model.WebhookSubscription, error) {
|
|
eventsJSON, err := json.Marshal(events)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal events: %w", err)
|
|
}
|
|
|
|
// Generate a random signing secret for HMAC-SHA256 webhook payload signing
|
|
secret, err := generateWebhookSecret()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("generate secret: %w", err)
|
|
}
|
|
|
|
sub := &model.WebhookSubscription{
|
|
AccountID: accountID,
|
|
URL: url,
|
|
Events: eventsJSON,
|
|
Secret: secret,
|
|
Active: true,
|
|
}
|
|
if err := s.webhookSubRepo.Create(ctx, sub); err != nil {
|
|
return nil, err
|
|
}
|
|
return sub, nil
|
|
}
|
|
|
|
// UpdateSubscription updates a webhook subscription's URL, events, or active status.
|
|
func (s *WebhookSubscriptionService) UpdateSubscription(ctx context.Context, id uint, url string, events []string, active bool) (*model.WebhookSubscription, error) {
|
|
sub, err := s.webhookSubRepo.FindByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("find subscription: %w", err)
|
|
}
|
|
|
|
if url != "" {
|
|
sub.URL = url
|
|
}
|
|
if len(events) > 0 {
|
|
eventsJSON, err := json.Marshal(events)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal events: %w", err)
|
|
}
|
|
sub.Events = eventsJSON
|
|
}
|
|
sub.Active = active
|
|
|
|
if err := s.webhookSubRepo.Update(ctx, sub); err != nil {
|
|
return nil, err
|
|
}
|
|
return sub, nil
|
|
}
|
|
|
|
// DeleteSubscription soft-deletes a webhook subscription.
|
|
func (s *WebhookSubscriptionService) DeleteSubscription(ctx context.Context, id uint) error {
|
|
return s.webhookSubRepo.Delete(ctx, id)
|
|
}
|
|
|
|
// GetSubscription retrieves a single webhook subscription by ID.
|
|
func (s *WebhookSubscriptionService) GetSubscription(ctx context.Context, id uint) (*model.WebhookSubscription, error) {
|
|
return s.webhookSubRepo.FindByID(ctx, id)
|
|
}
|
|
|
|
// ListDeliveries retrieves recent delivery records for a subscription.
|
|
func (s *WebhookSubscriptionService) ListDeliveries(ctx context.Context, subscriptionID uint, limit int) ([]model.WebhookDelivery, error) {
|
|
return s.webhookSubRepo.ListDeliveriesBySubscription(ctx, subscriptionID, limit)
|
|
}
|
|
|
|
// generateWebhookSecret creates a random 32-byte hex-encoded secret for HMAC signing.
|
|
func generateWebhookSecret() (string, error) {
|
|
b := make([]byte, 32)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("%x", b), nil
|
|
} |