// Package listdeliveries implements trusted operator listing of accepted mail // deliveries. package listdeliveries import ( "context" "errors" "fmt" "strings" "time" "galaxy/mail/internal/domain/common" deliverydomain "galaxy/mail/internal/domain/delivery" ) var ( // ErrInvalidCursor reports that the supplied opaque pagination cursor is // malformed or no longer matches durable state. ErrInvalidCursor = errors.New("list deliveries invalid cursor") // ErrServiceUnavailable reports that trusted listing could not load durable // state safely. ErrServiceUnavailable = errors.New("list deliveries service unavailable") ) const ( // DefaultLimit stores the frozen default page size used by the operator // listing surface. DefaultLimit = 50 // MaxLimit stores the frozen maximum page size accepted by the operator // listing surface. MaxLimit = 200 ) // Cursor stores one deterministic continuation position in the delivery sort // order `created_at_ms DESC, delivery_id DESC`. type Cursor struct { // CreatedAt stores the durable creation time of the last visible delivery. CreatedAt time.Time // DeliveryID stores the durable identifier of the last visible delivery. DeliveryID common.DeliveryID } // Validate reports whether cursor contains a complete continuation tuple. func (cursor Cursor) Validate() error { if err := common.ValidateTimestamp("delivery list cursor created at", cursor.CreatedAt); err != nil { return err } if err := cursor.DeliveryID.Validate(); err != nil { return fmt.Errorf("delivery list cursor delivery id: %w", err) } return nil } // Filters stores the supported operator-listing filters. type Filters struct { // Recipient stores the optional recipient envelope filter covering `to`, // `cc`, and `bcc`. Recipient common.Email // Status stores the optional delivery lifecycle filter. Status deliverydomain.Status // Source stores the optional delivery source filter. Source deliverydomain.Source // TemplateID stores the optional template family filter. TemplateID common.TemplateID // IdempotencyKey stores the optional idempotency-key filter. IdempotencyKey common.IdempotencyKey // FromCreatedAt stores the optional inclusive lower creation-time bound. FromCreatedAt *time.Time // ToCreatedAt stores the optional inclusive upper creation-time bound. ToCreatedAt *time.Time } // Validate reports whether filters is structurally valid. func (filters Filters) Validate() error { if !filters.Recipient.IsZero() { if err := filters.Recipient.Validate(); err != nil { return fmt.Errorf("recipient: %w", err) } } if filters.Status != "" && !filters.Status.IsKnown() { return fmt.Errorf("status %q is unsupported", filters.Status) } if filters.Source != "" && !filters.Source.IsKnown() { return fmt.Errorf("source %q is unsupported", filters.Source) } if !filters.TemplateID.IsZero() { if err := filters.TemplateID.Validate(); err != nil { return fmt.Errorf("template id: %w", err) } } if !filters.IdempotencyKey.IsZero() { if err := filters.IdempotencyKey.Validate(); err != nil { return fmt.Errorf("idempotency key: %w", err) } } if filters.FromCreatedAt != nil { if err := common.ValidateTimestamp("from created at", *filters.FromCreatedAt); err != nil { return err } } if filters.ToCreatedAt != nil { if err := common.ValidateTimestamp("to created at", *filters.ToCreatedAt); err != nil { return err } } if filters.FromCreatedAt != nil && filters.ToCreatedAt != nil && filters.FromCreatedAt.After(*filters.ToCreatedAt) { return errors.New("from created at must not be after to created at") } return nil } // Input stores one trusted operator-listing request. type Input struct { // Limit stores the maximum number of returned deliveries. The zero value // selects the frozen default limit. Limit int // Cursor stores the optional continuation cursor for the next page. Cursor *Cursor // Filters stores the normalized listing filters. Filters Filters } // Validate reports whether input contains a complete supported listing // request. func (input Input) Validate() error { switch { case input.Limit < 0: return errors.New("limit must not be negative") case input.Limit > MaxLimit: return fmt.Errorf("limit must be at most %d", MaxLimit) } if input.Cursor != nil { if err := input.Cursor.Validate(); err != nil { return fmt.Errorf("cursor: %w", err) } } if err := input.Filters.Validate(); err != nil { return fmt.Errorf("filters: %w", err) } return nil } // Result stores one deterministic ordered page of delivery records. type Result struct { // Items stores the returned deliveries in `created_at DESC, delivery_id // DESC` order. Items []deliverydomain.Delivery // NextCursor stores the optional cursor for the next page. NextCursor *Cursor } // Validate reports whether result contains valid delivery records and an // optional next cursor. func (result Result) Validate() error { for index, record := range result.Items { if err := record.Validate(); err != nil { return fmt.Errorf("items[%d]: %w", index, err) } } if result.NextCursor != nil { if err := result.NextCursor.Validate(); err != nil { return fmt.Errorf("next cursor: %w", err) } } return nil } // Store provides deterministic ordered listing over durable delivery state. type Store interface { // List returns one filtered ordered page of delivery records. List(context.Context, Input) (Result, error) } // Config stores the dependencies used by Service. type Config struct { // Store loads one deterministic ordered page of durable deliveries. Store Store } // Service executes trusted operator delivery-list reads. type Service struct { store Store } // New constructs Service from cfg. func New(cfg Config) (*Service, error) { if cfg.Store == nil { return nil, errors.New("new list deliveries service: nil store") } return &Service{store: cfg.Store}, nil } // Execute validates input, applies the default limit when omitted, and loads // one deterministic page of deliveries. func (service *Service) Execute(ctx context.Context, input Input) (Result, error) { if ctx == nil { return Result{}, errors.New("execute list deliveries: nil context") } if service == nil { return Result{}, errors.New("execute list deliveries: nil service") } if input.Limit == 0 { input.Limit = DefaultLimit } if err := input.Validate(); err != nil { return Result{}, fmt.Errorf("execute list deliveries: %w", err) } result, err := service.store.List(ctx, input) switch { case errors.Is(err, ErrInvalidCursor): return Result{}, err case err != nil: return Result{}, fmt.Errorf("%w: %v", ErrServiceUnavailable, err) } if err := result.Validate(); err != nil { return Result{}, fmt.Errorf("%w: invalid result: %v", ErrServiceUnavailable, err) } if len(result.Items) > input.Limit { return Result{}, fmt.Errorf("%w: invalid result: returned %d items for limit %d", ErrServiceUnavailable, len(result.Items), input.Limit) } return result, nil } // Matches reports whether record satisfies filters. func (filters Filters) Matches(record deliverydomain.Delivery) bool { if filters.Recipient != "" && !containsRecipient(record.Envelope, filters.Recipient) { return false } if filters.Status != "" && record.Status != filters.Status { return false } if filters.Source != "" && record.Source != filters.Source { return false } if filters.TemplateID != "" && record.TemplateID != filters.TemplateID { return false } if filters.IdempotencyKey != "" && record.IdempotencyKey != filters.IdempotencyKey { return false } if filters.FromCreatedAt != nil && record.CreatedAt.Before(filters.FromCreatedAt.UTC()) { return false } if filters.ToCreatedAt != nil && record.CreatedAt.After(filters.ToCreatedAt.UTC()) { return false } return true } func containsRecipient(envelope deliverydomain.Envelope, email common.Email) bool { for _, group := range [][]common.Email{envelope.To, envelope.Cc, envelope.Bcc} { for _, candidate := range group { if strings.EqualFold(candidate.String(), email.String()) { return true } } } return false }