package notify import ( "context" "errors" "net/http" "time" "github.com/google/uuid" "github.com/vasyakrg/dns-autoresolver/internal/store" ) // ChannelStore is the narrow store dependency Dispatcher needs: the set of // enabled notification channels for a project. type ChannelStore interface { ListEnabledChannels(ctx context.Context, projectID uuid.UUID) ([]store.Channel, error) } // Decryptor decrypts a channel's stored secret (bot token, signing key, ...). type Decryptor interface { Decrypt(enc string) ([]byte, error) } // Dispatcher fans an Event out to every enabled channel of a project, // picking the Notifier implementation by channel type. A failure on one // channel does not stop delivery to the others; all errors are aggregated // via errors.Join. type Dispatcher struct { store ChannelStore cipher Decryptor byType map[string]Notifier } // NewDispatcher builds a Dispatcher wired with the default Telegram and // Webhook notifiers. func NewDispatcher(store ChannelStore, cipher Decryptor) *Dispatcher { return &Dispatcher{ store: store, cipher: cipher, byType: map[string]Notifier{ "telegram": &Telegram{BaseURL: "https://api.telegram.org", HTTP: &http.Client{Timeout: 15 * time.Second}}, "webhook": &Webhook{HTTP: &http.Client{ Timeout: 15 * time.Second, Transport: newWebhookTransport(false), }}, }, } } // Send delivers ev to every enabled channel of projectID. Errors from // individual channels are aggregated (via errors.Join) rather than aborting // delivery to the remaining channels. func (d *Dispatcher) Send(ctx context.Context, projectID uuid.UUID, ev Event) error { channels, err := d.store.ListEnabledChannels(ctx, projectID) if err != nil { return err } var errs []error for _, ch := range channels { n, ok := d.byType[ch.Type] if !ok { continue } secret := "" if ch.SecretEnc != "" { b, err := d.cipher.Decrypt(ch.SecretEnc) if err != nil { errs = append(errs, err) continue } secret = string(b) } if err := n.Send(ctx, ch.Config, secret, ev); err != nil { errs = append(errs, err) } } return errors.Join(errs...) }