Files
ITWorx-Pulse-Public/internal/notification/dispatcher.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

68 lines
2.1 KiB
Go

package notification
import (
"context"
"errors"
"fmt"
"time"
)
type Dispatcher struct {
Store Store
Channels map[string]ChannelSender
Limiter *RateLimiter
}
func (dispatcher Dispatcher) DispatchDue(ctx context.Context, now time.Time, limit int) (int, error) {
if dispatcher.Store == nil {
return 0, ErrUnavailable
}
items, err := dispatcher.Store.ClaimDue(ctx, now.UTC(), limit)
if err != nil {
return 0, err
}
delivered := 0
for _, item := range items {
sender, ok := dispatcher.Channels[item.ChannelID]
if !ok || sender == nil {
_, completeErr := dispatcher.Store.Complete(ctx, item.ID, item.Attempts, false, errors.New("notification channel sender unavailable"), now)
if completeErr != nil {
return delivered, completeErr
}
continue
}
delivery := Delivery{ID: item.ID, EventType: item.EventType, Subject: item.Subject, Body: item.Body, IdempotencyKey: item.IdempotencyKey, Attempt: item.Attempts}
sendErr := sender.Send(ctx, delivery)
if _, completeErr := dispatcher.Store.Complete(ctx, item.ID, item.Attempts, sendErr == nil, sendErr, now); completeErr != nil {
return delivered, completeErr
}
if sendErr == nil {
delivered++
}
}
return delivered, nil
}
// Test sends a bounded, in-memory test delivery. It deliberately does not
// enqueue or mutate an outbox record, and it uses the actor/channel pair as a
// rate-limit key so test actions cannot become a delivery amplification path.
func (dispatcher Dispatcher) Test(ctx context.Context, actor, channelID string, delivery Delivery, now time.Time) error {
if actor == "" || len(actor) > 160 || channelID == "" {
return fmt.Errorf("%w: test identity is invalid", ErrInvalid)
}
if dispatcher.Limiter == nil {
return ErrUnavailable
}
if !dispatcher.Limiter.Allow(actor+":"+channelID, now.UTC()) {
return fmt.Errorf("%w: notification test rate limit exceeded", ErrConflict)
}
sender, ok := dispatcher.Channels[channelID]
if !ok || sender == nil {
return ErrNotFound
}
if err := delivery.Validate(); err != nil {
return err
}
return sender.Send(ctx, delivery)
}