Ctrl K
ComunidadGuías…

internal/rest/alert_config.go

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

Archivo original ↗Preguntar a KiBot
package rest

import (
	"context"
	"net/http"
	"time"

	"github.com/danielgtaylor/huma/v2"

	"github.com/sarathsp06/sparrow/internal/webhooks"
	"github.com/sarathsp06/sparrow/internal/webhooks/store"
)

type createAlertConfigBody struct {
	WebhookID  string   `json:"webhook_id,omitempty" doc:"Scope the alert to one webhook. Omit for a consumer-wide alert covering every webhook."`
	Email      string   `json:"email" required:"true" format:"email" doc:"Recipient email address."`
	EventTypes []string `json:"event_types" required:"true" doc:"Sparrow system event types to alert on, at least one of: sparrow.webhook.health_changed, sparrow.webhook.delivery_failed, sparrow.webhook.disabled."`
}

type createAlertConfigInput struct {
	Consumer string `path:"consumer" doc:"Consumer whose webhooks the alert covers."`
	Body     createAlertConfigBody
}

type alertConfigItem struct {
	ID         string   `json:"id" doc:"Alert config id (UUID)."`
	Consumer   string   `json:"consumer" doc:"Consumer this alert config belongs to."`
	WebhookID  string   `json:"webhook_id,omitempty" doc:"Webhook this alert is scoped to; omitted for consumer-wide alerts."`
	Email      string   `json:"email" doc:"Recipient email address."`
	EventTypes []string `json:"event_types" doc:"Sparrow system event types this recipient is alerted on."`
	CreatedAt  string   `json:"created_at" format:"date-time" doc:"Creation timestamp, RFC3339."`
}

func toAlertConfigItem(c *store.AlertConfig) alertConfigItem {
	item := alertConfigItem{
		ID:         c.ID.String(),
		Consumer:   c.Consumer,
		Email:      c.Email,
		EventTypes: []string(c.EventTypes),
		CreatedAt:  c.CreatedAt.Format(time.RFC3339Nano),
	}
	if c.WebhookID != nil {
		item.WebhookID = c.WebhookID.String()
	}
	return item
}

type alertConfigOutput struct {
	Body alertConfigItem
}

type listAlertConfigsInput struct {
	Consumer  string `path:"consumer" doc:"Consumer to list alert configs in."`
	WebhookID string `query:"webhook_id,omitempty" doc:"Filter to alert configs scoped to one webhook. Omit to list every config in the consumer, including consumer-wide ones."`
}

type listAlertConfigsOutput struct {
	Body struct {
		Items []alertConfigItem `json:"items" doc:"Alert configs, oldest first. Not paginated."`
	}
}

type alertConfigIDInput struct {
	Consumer      string `path:"consumer" doc:"Consumer the alert config belongs to."`
	AlertConfigID string `path:"alert_config_id" doc:"Alert config id (UUID)."`
}

func registerAlertConfigRoutes(api huma.API, svc webhooks.AlertConfigManager) {
	huma.Register(api, huma.Operation{
		OperationID: "createAlertConfig",
		Method:      http.MethodPost,
		Path:        "/v1/consumers/{consumer}/alert-configs",
		Summary:     "Register an email alert recipient",
		Description: "Opts an email address into Sparrow's self-generated health-change, permanent-delivery-failure and auto-disable notifications, scoped to one webhook or the whole consumer. Emails are only sent once an alert mailer webhook is registered under the _sparrow consumer (see GET /v1/capabilities alert_delivery.configured). Not idempotent: each call adds a config.\n\n" +
			"400 for an invalid email or event type, 404 when webhook_id is not in this consumer.\n\nPortal tokens: allowed, as POST /portal/api/alert-configs.",
		Errors:        []int{400, 404},
		Tags:          []string{"Alert Configs"},
		DefaultStatus: http.StatusCreated,
	}, func(ctx context.Context, in *createAlertConfigInput) (*alertConfigOutput, error) {
		cfg, err := svc.CreateAlertConfig(ctx, in.Consumer, in.Body.WebhookID, in.Body.Email, in.Body.EventTypes)
		if err != nil {
			return nil, mapError(ctx, err, "failed to create alert config")
		}
		return &alertConfigOutput{Body: toAlertConfigItem(cfg)}, nil
	})

	huma.Register(api, huma.Operation{
		OperationID: "listAlertConfigs",
		Method:      http.MethodGet,
		Path:        "/v1/consumers/{consumer}/alert-configs",
		Summary:     "List email alert recipients",
		Description: "Lists alert configs in a consumer, oldest first, optionally filtered to one webhook's own configs. Not paginated.\n\nPortal tokens: allowed, as GET /portal/api/alert-configs.",
		Tags:        []string{"Alert Configs"},
	}, func(ctx context.Context, in *listAlertConfigsInput) (*listAlertConfigsOutput, error) {
		configs, err := svc.ListAlertConfigs(ctx, in.Consumer, in.WebhookID)
		if err != nil {
			return nil, mapError(ctx, err, "failed to list alert configs")
		}
		out := &listAlertConfigsOutput{}
		out.Body.Items = make([]alertConfigItem, 0, len(configs))
		for _, c := range configs {
			out.Body.Items = append(out.Body.Items, toAlertConfigItem(c))
		}
		return out, nil
	})

	huma.Register(api, huma.Operation{
		OperationID:   "deleteAlertConfig",
		Method:        http.MethodDelete,
		Path:          "/v1/consumers/{consumer}/alert-configs/{alert_config_id}",
		Summary:       "Delete an email alert recipient",
		Description:   "Removes an alert config. The webhook and its delivery history are unaffected. Returns 204; 404 when it is not in this consumer.\n\nPortal tokens: allowed, as DELETE /portal/api/alert-configs/{alert_config_id}.",
		Errors:        []int{404},
		Tags:          []string{"Alert Configs"},
		DefaultStatus: http.StatusNoContent,
	}, func(ctx context.Context, in *alertConfigIDInput) (*emptyOutput, error) {
		if err := svc.DeleteAlertConfig(ctx, in.Consumer, in.AlertConfigID); err != nil {
			return nil, mapError(ctx, err, "failed to delete alert config")
		}
		return &emptyOutput{Status: http.StatusNoContent}, nil
	})
}