This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user