feat(notify): Telegram/Webhook нотификаторы + Dispatcher по каналам проекта
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
Reference in New Issue
Block a user