Ctrl K
ComunidadGuías…

internal/webhooks/webhook_service_alert_config.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package webhooks

import (
	"context"
	"net/mail"

	"github.com/google/uuid"

	svcerrors "github.com/sarathsp06/sparrow/internal/errors"
	"github.com/sarathsp06/sparrow/internal/storage"
	"github.com/sarathsp06/sparrow/internal/tenant"
	"github.com/sarathsp06/sparrow/internal/webhooks/store"
)

// systemEventTypes are the Sparrow-generated event types alert configs may
// subscribe to. Kept in sync with the emission call sites in
// internal/webhooks/queue/webhook_worker.go.
var systemEventTypes = map[string]bool{
	"sparrow.webhook.health_changed":  true,
	"sparrow.webhook.delivery_failed": true,
	"sparrow.webhook.disabled":        true,
}

// CreateAlertConfig registers an email recipient for one or more Sparrow
// system event types, either for one webhook (webhookID set) or every
// webhook in the consumer (webhookID empty).
func (s *WebhookService) CreateAlertConfig(ctx context.Context, consumer, webhookID, email string, eventTypes []string) (*store.AlertConfig, error) {
	if consumer == "" {
		return nil, svcerrors.Error(svcerrors.InvalidArgument, "consumer is required")
	}
	if _, err := mail.ParseAddress(email); err != nil {
		return nil, svcerrors.Errorf(svcerrors.InvalidArgument, "invalid email: %v", err)
	}
	if len(eventTypes) == 0 {
		return nil, svcerrors.Error(svcerrors.InvalidArgument, "event_types must not be empty")
	}
	for _, et := range eventTypes {
		if !systemEventTypes[et] {
			return nil, svcerrors.Errorf(svcerrors.InvalidArgument, "unsupported event_type %q", et)
		}
	}

	tenantID := tenant.FromContext(ctx)
	cfg := &store.AlertConfig{
		TenantID:   tenantID,
		Consumer:   consumer,
		Email:      email,
		EventTypes: eventTypes,
	}
	if webhookID != "" {
		id, err := parseUUID(webhookID, "webhook ID")
		if err != nil {
			return nil, err
		}
		if _, err := s.webhookRepo.GetWebhookByID(ctx, tenantID, id, consumer); err != nil {
			if storage.IsNotFound(err) {
				return nil, svcerrors.Error(svcerrors.NotFound, "webhook not found in consumer")
			}
			return nil, err
		}
		cfg.WebhookID = &id
	}

	if err := s.webhookRepo.CreateAlertConfig(ctx, tenantID, cfg); err != nil {
		return nil, err
	}
	return cfg, nil
}

// ListAlertConfigs lists alert configs in a consumer, optionally scoped to
// one webhook's own configs (webhookID != ""). Listing without a webhookID
// returns every config in the consumer, including consumer-wide ones.
func (s *WebhookService) ListAlertConfigs(ctx context.Context, consumer string, webhookID string) ([]*store.AlertConfig, error) {
	if consumer == "" {
		return nil, svcerrors.Error(svcerrors.InvalidArgument, "consumer is required")
	}
	var whPtr *uuid.UUID
	if webhookID != "" {
		id, err := parseUUID(webhookID, "webhook ID")
		if err != nil {
			return nil, err
		}
		whPtr = &id
	}
	return s.webhookRepo.ListAlertConfigs(ctx, tenant.FromContext(ctx), consumer, whPtr)
}

// DeleteAlertConfig deletes an alert config by id, verifying it belongs to
// the given consumer.
func (s *WebhookService) DeleteAlertConfig(ctx context.Context, consumer, id string) error {
	if consumer == "" {
		return svcerrors.Error(svcerrors.InvalidArgument, "consumer is required")
	}
	parsedID, err := parseUUID(id, "alert config ID")
	if err != nil {
		return err
	}
	tenantID := tenant.FromContext(ctx)
	cfg, err := s.webhookRepo.GetAlertConfig(ctx, tenantID, parsedID)
	if err != nil {
		if storage.IsNotFound(err) {
			return svcerrors.Error(svcerrors.NotFound, "alert config not found")
		}
		return err
	}
	if cfg.Consumer != consumer {
		return svcerrors.Error(svcerrors.NotFound, "alert config not found in consumer")
	}
	return s.webhookRepo.DeleteAlertConfig(ctx, tenantID, parsedID)
}

// AlertDeliveryConfigured reports whether alert emails can actually go out:
// an active webhook under the _sparrow consumer with an unpaused subscription
// to Sparrow's system events. Without one, alert configs are recorded but
// nothing is sent.
func (s *WebhookService) AlertDeliveryConfigured(ctx context.Context) (bool, error) {
	tenantID := tenant.FromContext(ctx)
	hooks, err := s.webhookRepo.ListWebhooks(ctx, tenantID, tenant.SystemConsumer, "", true)
	if err != nil {
		return false, err
	}
	ids := make([]uuid.UUID, 0, len(hooks))
	for _, h := range hooks {
		ids = append(ids, h.ID)
	}
	subs, err := s.webhookRepo.ListSubscriptionsByWebhookIDs(ctx, tenantID, ids)
	if err != nil {
		return false, err
	}
	for _, sub := range subs {
		if sub.PausedAt == nil && (sub.EventName == store.CatchAllEventName || systemEventTypes[sub.EventName]) {
			return true, nil
		}
	}
	return false, nil
}