Callbacks service docs updated
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/edge/callbacks/internal/model"
|
||||
"github.com/tech/sendico/edge/callbacks/internal/signing"
|
||||
"github.com/tech/sendico/edge/callbacks/internal/storage"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
@@ -154,7 +155,7 @@ func (s *service) runWorker(ctx context.Context, workerID string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) handleTask(ctx context.Context, workerID string, task *storage.Task) {
|
||||
func (s *service) handleTask(ctx context.Context, workerID string, task *model.Task) {
|
||||
started := time.Now()
|
||||
statusCode := 0
|
||||
result := "failed"
|
||||
|
||||
@@ -4,16 +4,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// Envelope is the canonical incoming event envelope.
|
||||
type Envelope struct {
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
OccurredAt time.Time `json:"occurred_at"`
|
||||
PublishedAt time.Time `json:"published_at,omitempty"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
OrganizationRef bson.ObjectID `json:"organization_ref"`
|
||||
OccurredAt time.Time `json:"occurred_at"`
|
||||
PublishedAt time.Time `json:"published_at,omitempty"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// Service parses incoming messages and builds outbound payload bytes.
|
||||
@@ -24,10 +26,10 @@ type Service interface {
|
||||
|
||||
// Payload is the stable outbound JSON body.
|
||||
type Payload struct {
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
OccurredAt string `json:"occurred_at"`
|
||||
PublishedAt string `json:"published_at,omitempty"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
EventID string `json:"event_id"`
|
||||
Type string `json:"type"`
|
||||
OrganizationRef bson.ObjectID `json:"organization_ref"`
|
||||
OccurredAt string `json:"occurred_at"`
|
||||
PublishedAt string `json:"published_at,omitempty"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -39,8 +40,8 @@ func (s *parserService) Parse(data []byte) (*Envelope, error) {
|
||||
if strings.TrimSpace(envelope.Type) == "" {
|
||||
return nil, merrors.InvalidArgument("type is required", "type")
|
||||
}
|
||||
if strings.TrimSpace(envelope.ClientID) == "" {
|
||||
return nil, merrors.InvalidArgument("client_id is required", "client_id")
|
||||
if envelope.OrganizationRef == bson.NilObjectID {
|
||||
return nil, merrors.InvalidArgument("organization_ref is required", "organization_ref")
|
||||
}
|
||||
if envelope.OccurredAt.IsZero() {
|
||||
return nil, merrors.InvalidArgument("occurred_at is required", "occurred_at")
|
||||
@@ -51,7 +52,6 @@ func (s *parserService) Parse(data []byte) (*Envelope, error) {
|
||||
|
||||
envelope.EventID = strings.TrimSpace(envelope.EventID)
|
||||
envelope.Type = strings.TrimSpace(envelope.Type)
|
||||
envelope.ClientID = strings.TrimSpace(envelope.ClientID)
|
||||
envelope.OccurredAt = envelope.OccurredAt.UTC()
|
||||
if !envelope.PublishedAt.IsZero() {
|
||||
envelope.PublishedAt = envelope.PublishedAt.UTC()
|
||||
@@ -66,11 +66,11 @@ func (s *parserService) BuildPayload(_ context.Context, envelope *Envelope) ([]b
|
||||
}
|
||||
|
||||
payload := Payload{
|
||||
EventID: envelope.EventID,
|
||||
Type: envelope.Type,
|
||||
ClientID: envelope.ClientID,
|
||||
OccurredAt: envelope.OccurredAt.UTC().Format(time.RFC3339Nano),
|
||||
Data: envelope.Data,
|
||||
EventID: envelope.EventID,
|
||||
Type: envelope.Type,
|
||||
OrganizationRef: envelope.OrganizationRef,
|
||||
OccurredAt: envelope.OccurredAt.UTC().Format(time.RFC3339Nano),
|
||||
Data: envelope.Data,
|
||||
}
|
||||
if !envelope.PublishedAt.IsZero() {
|
||||
payload.PublishedAt = envelope.PublishedAt.UTC().Format(time.RFC3339Nano)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
np "github.com/tech/sendico/pkg/messaging/notifications/processor"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -178,7 +179,7 @@ func (s *service) handlePaymentStatusUpdated(ctx context.Context, msg *model.Pay
|
||||
result = ingestResultEmptyPayload
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(msg.EventID) == "" || strings.TrimSpace(msg.ClientID) == "" || msg.OccurredAt.IsZero() {
|
||||
if strings.TrimSpace(msg.EventID) == "" || msg.Data.OrganizationRef == bson.NilObjectID || msg.OccurredAt.IsZero() {
|
||||
result = ingestResultInvalidEvent
|
||||
return nil
|
||||
}
|
||||
@@ -195,15 +196,15 @@ func (s *service) handlePaymentStatusUpdated(ctx context.Context, msg *model.Pay
|
||||
}
|
||||
|
||||
parsed := &events.Envelope{
|
||||
EventID: strings.TrimSpace(msg.EventID),
|
||||
Type: eventType,
|
||||
ClientID: strings.TrimSpace(msg.ClientID),
|
||||
OccurredAt: msg.OccurredAt.UTC(),
|
||||
PublishedAt: msg.PublishedAt.UTC(),
|
||||
Data: data,
|
||||
EventID: strings.TrimSpace(msg.EventID),
|
||||
Type: eventType,
|
||||
OrganizationRef: msg.Data.OrganizationRef,
|
||||
OccurredAt: msg.OccurredAt.UTC(),
|
||||
PublishedAt: msg.PublishedAt.UTC(),
|
||||
Data: data,
|
||||
}
|
||||
|
||||
inserted, err := s.deps.InboxRepo.TryInsert(ctx, parsed.EventID, parsed.ClientID, parsed.Type, time.Now().UTC())
|
||||
inserted, err := s.deps.InboxRepo.TryInsert(ctx, parsed.EventID, parsed.Type, parsed.OrganizationRef, time.Now().UTC())
|
||||
if err != nil {
|
||||
result = ingestResultInboxError
|
||||
return err
|
||||
@@ -213,7 +214,7 @@ func (s *service) handlePaymentStatusUpdated(ctx context.Context, msg *model.Pay
|
||||
return nil
|
||||
}
|
||||
|
||||
endpoints, err := s.deps.Resolver.Resolve(ctx, parsed.ClientID, parsed.Type)
|
||||
endpoints, err := s.deps.Resolver.Resolve(ctx, parsed.Type, parsed.OrganizationRef)
|
||||
if err != nil {
|
||||
result = ingestResultResolveError
|
||||
return err
|
||||
|
||||
8
api/edge/callbacks/internal/model/callback.go
Normal file
8
api/edge/callbacks/internal/model/callback.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package model
|
||||
|
||||
import pmodel "github.com/tech/sendico/pkg/model"
|
||||
|
||||
type CallbackInternal struct {
|
||||
pmodel.Callback `bson:",inline" json:",inline"`
|
||||
SecretRef string `bson:"secretRef"`
|
||||
}
|
||||
22
api/edge/callbacks/internal/model/endpoint.go
Normal file
22
api/edge/callbacks/internal/model/endpoint.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
// Endpoint describes one target callback endpoint.
|
||||
type Endpoint struct {
|
||||
storable.Base
|
||||
pmodel.OrganizationBoundBase
|
||||
URL string
|
||||
SigningMode string
|
||||
SecretRef string
|
||||
Headers map[string]string
|
||||
MaxAttempts int
|
||||
MinDelay time.Duration
|
||||
MaxDelay time.Duration
|
||||
RequestTimeout time.Duration
|
||||
}
|
||||
37
api/edge/callbacks/internal/model/task.go
Normal file
37
api/edge/callbacks/internal/model/task.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// TaskStatus tracks delivery task lifecycle.
|
||||
type TaskStatus string
|
||||
|
||||
const (
|
||||
TaskStatusPending TaskStatus = "PENDING"
|
||||
TaskStatusRetry TaskStatus = "RETRY"
|
||||
TaskStatusDelivered TaskStatus = "DELIVERED"
|
||||
TaskStatusFailed TaskStatus = "FAILED"
|
||||
)
|
||||
|
||||
// Task is one callback delivery job.
|
||||
type Task struct {
|
||||
storable.Base
|
||||
EventID string
|
||||
EndpointRef bson.ObjectID
|
||||
EndpointURL string
|
||||
SigningMode string
|
||||
SecretRef string
|
||||
Headers map[string]string
|
||||
Payload []byte
|
||||
Attempt int
|
||||
MaxAttempts int
|
||||
MinDelay time.Duration
|
||||
MaxDelay time.Duration
|
||||
RequestTimeout time.Duration
|
||||
Status TaskStatus
|
||||
NextAttemptAt time.Time
|
||||
}
|
||||
@@ -4,54 +4,12 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/edge/callbacks/internal/model"
|
||||
"github.com/tech/sendico/pkg/db"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// TaskStatus tracks delivery task lifecycle.
|
||||
type TaskStatus string
|
||||
|
||||
const (
|
||||
TaskStatusPending TaskStatus = "PENDING"
|
||||
TaskStatusRetry TaskStatus = "RETRY"
|
||||
TaskStatusDelivered TaskStatus = "DELIVERED"
|
||||
TaskStatusFailed TaskStatus = "FAILED"
|
||||
)
|
||||
|
||||
// Endpoint describes one target callback endpoint.
|
||||
type Endpoint struct {
|
||||
ID bson.ObjectID
|
||||
ClientID string
|
||||
URL string
|
||||
SigningMode string
|
||||
SecretRef string
|
||||
Headers map[string]string
|
||||
MaxAttempts int
|
||||
MinDelay time.Duration
|
||||
MaxDelay time.Duration
|
||||
RequestTimeout time.Duration
|
||||
}
|
||||
|
||||
// Task is one callback delivery job.
|
||||
type Task struct {
|
||||
ID bson.ObjectID
|
||||
EventID string
|
||||
EndpointID bson.ObjectID
|
||||
EndpointURL string
|
||||
SigningMode string
|
||||
SecretRef string
|
||||
Headers map[string]string
|
||||
Payload []byte
|
||||
Attempt int
|
||||
MaxAttempts int
|
||||
MinDelay time.Duration
|
||||
MaxDelay time.Duration
|
||||
RequestTimeout time.Duration
|
||||
Status TaskStatus
|
||||
NextAttemptAt time.Time
|
||||
}
|
||||
|
||||
// TaskDefaults are applied when creating tasks.
|
||||
type TaskDefaults struct {
|
||||
MaxAttempts int
|
||||
@@ -69,18 +27,18 @@ type Options struct {
|
||||
|
||||
// InboxRepo controls event dedupe state.
|
||||
type InboxRepo interface {
|
||||
TryInsert(ctx context.Context, eventID, clientID, eventType string, at time.Time) (bool, error)
|
||||
TryInsert(ctx context.Context, eventID, ceventType string, organizationRef bson.ObjectID, at time.Time) (bool, error)
|
||||
}
|
||||
|
||||
// EndpointRepo resolves endpoints for events.
|
||||
type EndpointRepo interface {
|
||||
FindActiveByClientAndType(ctx context.Context, clientID, eventType string) ([]Endpoint, error)
|
||||
FindActive(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error)
|
||||
}
|
||||
|
||||
// TaskRepo manages callback tasks.
|
||||
type TaskRepo interface {
|
||||
UpsertTasks(ctx context.Context, eventID string, endpoints []Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error
|
||||
LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*Task, error)
|
||||
UpsertTasks(ctx context.Context, eventID string, endpoints []model.Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error
|
||||
LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*model.Task, error)
|
||||
MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error
|
||||
MarkRetry(ctx context.Context, taskID bson.ObjectID, attempt int, nextAttemptAt time.Time, lastError string, httpCode int, at time.Time) error
|
||||
MarkFailed(ctx context.Context, taskID bson.ObjectID, attempt int, lastError string, httpCode int, at time.Time) error
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/edge/callbacks/internal/model"
|
||||
"github.com/tech/sendico/pkg/db"
|
||||
"github.com/tech/sendico/pkg/db/repository"
|
||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
mutil "github.com/tech/sendico/pkg/mutil/db"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
@@ -39,10 +41,10 @@ type mongoRepository struct {
|
||||
}
|
||||
|
||||
type inboxDoc struct {
|
||||
storable.Base `bson:",inline"`
|
||||
EventID string `bson:"event_id"`
|
||||
ClientID string `bson:"client_id"`
|
||||
EventType string `bson:"event_type"`
|
||||
storable.Base `bson:",inline"`
|
||||
pmodel.OrganizationBoundBase `bson:",inline"`
|
||||
EventID string `bson:"event_id"`
|
||||
EventType string `bson:"event_type"`
|
||||
}
|
||||
|
||||
func (d *inboxDoc) Collection() string {
|
||||
@@ -64,12 +66,12 @@ type deliveryPolicy struct {
|
||||
}
|
||||
|
||||
type endpointDoc struct {
|
||||
storable.Base `bson:",inline"`
|
||||
deliveryPolicy `bson:"retry_policy"`
|
||||
ClientID string `bson:"client_id"`
|
||||
Status string `bson:"status"`
|
||||
URL string `bson:"url"`
|
||||
EventTypes []string `bson:"event_types"`
|
||||
storable.Base `bson:",inline"`
|
||||
pmodel.OrganizationBoundBase `bson:",inline"`
|
||||
deliveryPolicy `bson:"retry_policy"`
|
||||
Status string `bson:"status"`
|
||||
URL string `bson:"url"`
|
||||
EventTypes []string `bson:"event_types"`
|
||||
}
|
||||
|
||||
func (d *endpointDoc) Collection() string {
|
||||
@@ -79,18 +81,18 @@ func (d *endpointDoc) Collection() string {
|
||||
type taskDoc struct {
|
||||
storable.Base `bson:",inline"`
|
||||
deliveryPolicy `bson:"retry_policy"`
|
||||
EventID string `bson:"event_id"`
|
||||
EndpointID bson.ObjectID `bson:"endpoint_id"`
|
||||
EndpointURL string `bson:"endpoint_url"`
|
||||
Payload []byte `bson:"payload"`
|
||||
Status TaskStatus `bson:"status"`
|
||||
Attempt int `bson:"attempt"`
|
||||
LastError string `bson:"last_error,omitempty"`
|
||||
LastHTTPCode int `bson:"last_http_code,omitempty"`
|
||||
NextAttemptAt time.Time `bson:"next_attempt_at"`
|
||||
LockedUntil *time.Time `bson:"locked_until,omitempty"`
|
||||
WorkerID string `bson:"worker_id,omitempty"`
|
||||
DeliveredAt *time.Time `bson:"delivered_at,omitempty"`
|
||||
EventID string `bson:"event_id"`
|
||||
EndpointRef bson.ObjectID `bson:"endpoint_ref"`
|
||||
EndpointURL string `bson:"endpoint_url"`
|
||||
Payload []byte `bson:"payload"`
|
||||
Status model.TaskStatus `bson:"status"`
|
||||
Attempt int `bson:"attempt"`
|
||||
LastError string `bson:"last_error,omitempty"`
|
||||
LastHTTPCode int `bson:"last_http_code,omitempty"`
|
||||
NextAttemptAt time.Time `bson:"next_attempt_at"`
|
||||
LockedUntil *time.Time `bson:"locked_until,omitempty"`
|
||||
WorkerID string `bson:"worker_id,omitempty"`
|
||||
DeliveredAt *time.Time `bson:"delivered_at,omitempty"`
|
||||
}
|
||||
|
||||
func (d *taskDoc) Collection() string {
|
||||
@@ -152,7 +154,7 @@ func (m *mongoRepository) ensureIndexes() error {
|
||||
Unique: true,
|
||||
Keys: []ri.Key{
|
||||
{Field: "event_id", Sort: ri.Asc},
|
||||
{Field: "endpoint_id", Sort: ri.Asc},
|
||||
{Field: "endpoint_ref", Sort: ri.Asc},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -172,7 +174,7 @@ func (m *mongoRepository) ensureIndexes() error {
|
||||
if err := m.endpointsRepo.CreateIndex(&ri.Definition{
|
||||
Name: "idx_client_event",
|
||||
Keys: []ri.Key{
|
||||
{Field: "client_id", Sort: ri.Asc},
|
||||
{Field: "organization_ref", Sort: ri.Asc},
|
||||
{Field: "status", Sort: ri.Asc},
|
||||
{Field: "event_types", Sort: ri.Asc},
|
||||
},
|
||||
@@ -188,11 +190,11 @@ type inboxStore struct {
|
||||
repo repository.Repository
|
||||
}
|
||||
|
||||
func (r *inboxStore) TryInsert(ctx context.Context, eventID, clientID, eventType string, at time.Time) (bool, error) {
|
||||
func (r *inboxStore) TryInsert(ctx context.Context, eventID, eventType string, organizationRef bson.ObjectID, at time.Time) (bool, error) {
|
||||
doc := &inboxDoc{
|
||||
EventID: strings.TrimSpace(eventID),
|
||||
ClientID: strings.TrimSpace(clientID),
|
||||
EventType: strings.TrimSpace(eventType),
|
||||
OrganizationBoundBase: pmodel.OrganizationBoundBase{OrganizationRef: organizationRef},
|
||||
EventID: strings.TrimSpace(eventID),
|
||||
EventType: strings.TrimSpace(eventType),
|
||||
}
|
||||
|
||||
filter := repository.Filter("event_id", doc.EventID)
|
||||
@@ -212,21 +214,20 @@ type endpointStore struct {
|
||||
repo repository.Repository
|
||||
}
|
||||
|
||||
func (r *endpointStore) FindActiveByClientAndType(ctx context.Context, clientID, eventType string) ([]Endpoint, error) {
|
||||
clientID = strings.TrimSpace(clientID)
|
||||
func (r *endpointStore) FindActive(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error) {
|
||||
eventType = strings.TrimSpace(eventType)
|
||||
if clientID == "" {
|
||||
return nil, merrors.InvalidArgument("client_id is required", "client_id")
|
||||
if organizationRef == bson.NilObjectID {
|
||||
return nil, merrors.InvalidArgument("organization_ref is required", "organization_ref")
|
||||
}
|
||||
if eventType == "" {
|
||||
return nil, merrors.InvalidArgument("event type is required", "event_type")
|
||||
}
|
||||
|
||||
query := repository.Query().
|
||||
Filter(repository.Field("client_id"), clientID).
|
||||
Filter(repository.OrgField(), organizationRef).
|
||||
In(repository.Field("status"), "active", "enabled")
|
||||
|
||||
out := make([]Endpoint, 0)
|
||||
out := make([]model.Endpoint, 0)
|
||||
err := r.repo.FindManyByFilter(ctx, query, func(cur *mongo.Cursor) error {
|
||||
doc := &endpointDoc{}
|
||||
if err := cur.Decode(doc); err != nil {
|
||||
@@ -238,17 +239,17 @@ func (r *endpointStore) FindActiveByClientAndType(ctx context.Context, clientID,
|
||||
if !supportsEventType(doc.EventTypes, eventType) {
|
||||
return nil
|
||||
}
|
||||
out = append(out, Endpoint{
|
||||
ID: doc.ID,
|
||||
ClientID: doc.ClientID,
|
||||
URL: strings.TrimSpace(doc.URL),
|
||||
SigningMode: strings.TrimSpace(doc.SigningMode),
|
||||
SecretRef: strings.TrimSpace(doc.SecretRef),
|
||||
Headers: cloneHeaders(doc.Headers),
|
||||
MaxAttempts: doc.MaxAttempts,
|
||||
MinDelay: time.Duration(doc.MinDelayMS) * time.Millisecond,
|
||||
MaxDelay: time.Duration(doc.MaxDelayMS) * time.Millisecond,
|
||||
RequestTimeout: time.Duration(doc.RequestTimeoutMS) * time.Millisecond,
|
||||
out = append(out, model.Endpoint{
|
||||
Base: doc.Base,
|
||||
OrganizationBoundBase: doc.OrganizationBoundBase,
|
||||
URL: strings.TrimSpace(doc.URL),
|
||||
SigningMode: strings.TrimSpace(doc.SigningMode),
|
||||
SecretRef: strings.TrimSpace(doc.SecretRef),
|
||||
Headers: cloneHeaders(doc.Headers),
|
||||
MaxAttempts: doc.MaxAttempts,
|
||||
MinDelay: time.Duration(doc.MinDelayMS) * time.Millisecond,
|
||||
MaxDelay: time.Duration(doc.MaxDelayMS) * time.Millisecond,
|
||||
RequestTimeout: time.Duration(doc.RequestTimeoutMS) * time.Millisecond,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
@@ -281,7 +282,7 @@ type taskStore struct {
|
||||
repo repository.Repository
|
||||
}
|
||||
|
||||
func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints []Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error {
|
||||
func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints []model.Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error {
|
||||
eventID = strings.TrimSpace(eventID)
|
||||
if eventID == "" {
|
||||
return merrors.InvalidArgument("event id is required", "event_id")
|
||||
@@ -292,7 +293,7 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
|
||||
|
||||
now := at.UTC()
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.ID == bson.NilObjectID {
|
||||
if endpoint.GetID() == nil || *endpoint.GetID() == bson.NilObjectID {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -327,13 +328,13 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
|
||||
|
||||
doc := &taskDoc{}
|
||||
doc.EventID = eventID
|
||||
doc.EndpointID = endpoint.ID
|
||||
doc.EndpointRef = *endpoint.GetID()
|
||||
doc.EndpointURL = strings.TrimSpace(endpoint.URL)
|
||||
doc.SigningMode = strings.TrimSpace(endpoint.SigningMode)
|
||||
doc.SecretRef = strings.TrimSpace(endpoint.SecretRef)
|
||||
doc.Headers = cloneHeaders(endpoint.Headers)
|
||||
doc.Payload = append([]byte(nil), payload...)
|
||||
doc.Status = TaskStatusPending
|
||||
doc.Status = model.TaskStatusPending
|
||||
doc.Attempt = 0
|
||||
doc.MaxAttempts = maxAttempts
|
||||
doc.MinDelayMS = int(minDelay / time.Millisecond)
|
||||
@@ -341,7 +342,7 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
|
||||
doc.RequestTimeoutMS = int(requestTimeout / time.Millisecond)
|
||||
doc.NextAttemptAt = now
|
||||
|
||||
filter := repository.Filter("event_id", eventID).And(repository.Filter("endpoint_id", endpoint.ID))
|
||||
filter := repository.Filter("event_id", eventID).And(repository.Filter("endpoint_ref", endpoint.ID))
|
||||
if err := r.repo.Insert(ctx, doc, filter); err != nil {
|
||||
if errors.Is(err, merrors.ErrDataConflict) {
|
||||
continue
|
||||
@@ -353,7 +354,7 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*Task, error) {
|
||||
func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*model.Task, error) {
|
||||
workerID = strings.TrimSpace(workerID)
|
||||
if workerID == "" {
|
||||
return nil, merrors.InvalidArgument("worker id is required", "worker_id")
|
||||
@@ -368,7 +369,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
|
||||
)
|
||||
|
||||
query := repository.Query().
|
||||
In(repository.Field("status"), string(TaskStatusPending), string(TaskStatusRetry)).
|
||||
In(repository.Field("status"), string(model.TaskStatusPending), string(model.TaskStatusRetry)).
|
||||
Comparison(repository.Field("next_attempt_at"), builder.Lte, now).
|
||||
And(lockFilter).
|
||||
Sort(repository.Field("next_attempt_at"), true).
|
||||
@@ -390,7 +391,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
|
||||
Set(repository.Field("worker_id"), workerID)
|
||||
|
||||
conditional := repository.IDFilter(candidate.ID).And(
|
||||
repository.Query().In(repository.Field("status"), string(TaskStatusPending), string(TaskStatusRetry)),
|
||||
repository.Query().In(repository.Field("status"), string(model.TaskStatusPending), string(model.TaskStatusRetry)),
|
||||
repository.Query().Comparison(repository.Field("next_attempt_at"), builder.Lte, now),
|
||||
lockFilter,
|
||||
)
|
||||
@@ -427,7 +428,7 @@ func (r *taskStore) MarkDelivered(ctx context.Context, taskID bson.ObjectID, htt
|
||||
}
|
||||
|
||||
patch := repository.Patch().
|
||||
Set(repository.Field("status"), TaskStatusDelivered).
|
||||
Set(repository.Field("status"), model.TaskStatusDelivered).
|
||||
Set(repository.Field("last_http_code"), httpCode).
|
||||
Set(repository.Field("delivered_at"), time.Now()).
|
||||
Set(repository.Field("locked_until"), nil).
|
||||
@@ -446,7 +447,7 @@ func (r *taskStore) MarkRetry(ctx context.Context, taskID bson.ObjectID, attempt
|
||||
}
|
||||
|
||||
patch := repository.Patch().
|
||||
Set(repository.Field("status"), TaskStatusRetry).
|
||||
Set(repository.Field("status"), model.TaskStatusRetry).
|
||||
Set(repository.Field("attempt"), attempt).
|
||||
Set(repository.Field("next_attempt_at"), nextAttemptAt.UTC()).
|
||||
Set(repository.Field("last_error"), strings.TrimSpace(lastError)).
|
||||
@@ -466,7 +467,7 @@ func (r *taskStore) MarkFailed(ctx context.Context, taskID bson.ObjectID, attemp
|
||||
}
|
||||
|
||||
patch := repository.Patch().
|
||||
Set(repository.Field("status"), TaskStatusFailed).
|
||||
Set(repository.Field("status"), model.TaskStatusFailed).
|
||||
Set(repository.Field("attempt"), attempt).
|
||||
Set(repository.Field("last_error"), strings.TrimSpace(lastError)).
|
||||
Set(repository.Field("last_http_code"), httpCode).
|
||||
@@ -479,14 +480,14 @@ func (r *taskStore) MarkFailed(ctx context.Context, taskID bson.ObjectID, attemp
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapTaskDoc(doc *taskDoc) *Task {
|
||||
func mapTaskDoc(doc *taskDoc) *model.Task {
|
||||
if doc == nil {
|
||||
return nil
|
||||
}
|
||||
return &Task{
|
||||
ID: doc.ID,
|
||||
return &model.Task{
|
||||
Base: doc.Base,
|
||||
EventID: doc.EventID,
|
||||
EndpointID: doc.EndpointID,
|
||||
EndpointRef: doc.EndpointRef,
|
||||
EndpointURL: doc.EndpointURL,
|
||||
SigningMode: doc.SigningMode,
|
||||
SecretRef: doc.SecretRef,
|
||||
|
||||
@@ -3,12 +3,14 @@ package subscriptions
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tech/sendico/edge/callbacks/internal/model"
|
||||
"github.com/tech/sendico/edge/callbacks/internal/storage"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// Resolver resolves active webhook endpoints for an event.
|
||||
type Resolver interface {
|
||||
Resolve(ctx context.Context, clientID, eventType string) ([]storage.Endpoint, error)
|
||||
Resolve(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error)
|
||||
}
|
||||
|
||||
// Dependencies defines subscriptions resolver dependencies.
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/edge/callbacks/internal/model"
|
||||
"github.com/tech/sendico/edge/callbacks/internal/storage"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
@@ -21,15 +23,15 @@ func New(deps Dependencies) (Resolver, error) {
|
||||
return &service{repo: deps.EndpointRepo}, nil
|
||||
}
|
||||
|
||||
func (s *service) Resolve(ctx context.Context, clientID, eventType string) ([]storage.Endpoint, error) {
|
||||
if strings.TrimSpace(clientID) == "" {
|
||||
func (s *service) Resolve(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error) {
|
||||
if organizationRef == bson.NilObjectID {
|
||||
return nil, merrors.InvalidArgument("subscriptions: client id is required", "clientID")
|
||||
}
|
||||
if strings.TrimSpace(eventType) == "" {
|
||||
return nil, merrors.InvalidArgument("subscriptions: event type is required", "eventType")
|
||||
}
|
||||
|
||||
endpoints, err := s.repo.FindActiveByClientAndType(ctx, clientID, eventType)
|
||||
endpoints, err := s.repo.FindActive(ctx, eventType, organizationRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user