package mailstore import ( "context" "database/sql" "errors" "fmt" "time" pgtable "galaxy/mail/internal/adapters/postgres/jet/mail/table" "galaxy/mail/internal/domain/attempt" "galaxy/mail/internal/domain/common" deliverydomain "galaxy/mail/internal/domain/delivery" "galaxy/mail/internal/domain/idempotency" "galaxy/mail/internal/service/acceptgenericdelivery" "galaxy/mail/internal/service/listdeliveries" "galaxy/mail/internal/service/resenddelivery" pg "github.com/go-jet/jet/v2/postgres" ) // resendIdempotencyExpiry stores the synthetic idempotency_expires_at value // applied to resend deliveries. Resend rows do not carry a caller-supplied // idempotency reservation; the fingerprint is stored as the empty string and // the loadIdempotencyByScope helper treats those rows as non-idempotent — // the expiry is therefore irrelevant in practice but must satisfy the // `NOT NULL > created_at` invariant used by the deliveries column. const resendIdempotencyExpiry = 100 * 365 * 24 * time.Hour // maxIdempotencyExpiry is the fallback expiry duration used when no caller- // supplied idempotency.Record reservation accompanies the write. var maxIdempotencyExpiry = resendIdempotencyExpiry // GetIdempotency loads the idempotency reservation for one (source, key) // scope. It is shared by the auth-acceptance and generic-acceptance flows. func (store *Store) GetIdempotency(ctx context.Context, source deliverydomain.Source, key common.IdempotencyKey) (idempotency.Record, bool, error) { if store == nil { return idempotency.Record{}, false, errors.New("get idempotency: nil store") } operationCtx, cancel, err := store.operationContext(ctx, "get idempotency") if err != nil { return idempotency.Record{}, false, err } defer cancel() record, ok, err := loadIdempotencyByScope(operationCtx, store.db, source, key) if err != nil { return idempotency.Record{}, false, fmt.Errorf("get idempotency: %w", err) } return record, ok, nil } // GetDeadLetter loads the dead_letters row for deliveryID when one exists. func (store *Store) GetDeadLetter(ctx context.Context, deliveryID common.DeliveryID) (deliverydomain.DeadLetterEntry, bool, error) { if store == nil { return deliverydomain.DeadLetterEntry{}, false, errors.New("get dead-letter: nil store") } operationCtx, cancel, err := store.operationContext(ctx, "get dead-letter") if err != nil { return deliverydomain.DeadLetterEntry{}, false, err } defer cancel() entry, ok, err := loadDeadLetter(operationCtx, store.db, deliveryID) if err != nil { return deliverydomain.DeadLetterEntry{}, false, fmt.Errorf("get dead-letter: %w", err) } return entry, ok, nil } // GetDeliveryPayload returns the raw attachment payload bundle for deliveryID // when one exists. func (store *Store) GetDeliveryPayload(ctx context.Context, deliveryID common.DeliveryID) (acceptgenericdelivery.DeliveryPayload, bool, error) { if store == nil { return acceptgenericdelivery.DeliveryPayload{}, false, errors.New("get delivery payload: nil store") } operationCtx, cancel, err := store.operationContext(ctx, "get delivery payload") if err != nil { return acceptgenericdelivery.DeliveryPayload{}, false, err } defer cancel() encoded, ok, err := loadDeliveryPayload(operationCtx, store.db, deliveryID) if err != nil { return acceptgenericdelivery.DeliveryPayload{}, false, fmt.Errorf("get delivery payload: %w", err) } if !ok { return acceptgenericdelivery.DeliveryPayload{}, false, nil } payload, err := unmarshalDeliveryPayload(deliveryID, encoded) if err != nil { return acceptgenericdelivery.DeliveryPayload{}, false, fmt.Errorf("get delivery payload: %w", err) } return payload, true, nil } // ListAttempts loads exactly expectedCount attempts in attempt_no ASC order // for deliveryID. A gap in the stored sequence surfaces as an error so // operator reads fail closed on durable-state corruption. func (store *Store) ListAttempts(ctx context.Context, deliveryID common.DeliveryID, expectedCount int) ([]attempt.Attempt, error) { if store == nil { return nil, errors.New("list attempts: nil store") } if expectedCount < 0 { return nil, errors.New("list attempts: negative expected count") } if expectedCount == 0 { return []attempt.Attempt{}, nil } if err := deliveryID.Validate(); err != nil { return nil, fmt.Errorf("list attempts: %w", err) } operationCtx, cancel, err := store.operationContext(ctx, "list attempts") if err != nil { return nil, err } defer cancel() out, err := loadAttempts(operationCtx, store.db, deliveryID, expectedCount) if err != nil { return nil, fmt.Errorf("list attempts: %w", err) } return out, nil } // List returns one filtered ordered page of delivery records keyed by // (created_at DESC, delivery_id DESC). Filters compose into SQL WHERE // clauses — every supported filter is index-friendly. func (store *Store) List(ctx context.Context, input listdeliveries.Input) (listdeliveries.Result, error) { if store == nil { return listdeliveries.Result{}, errors.New("list deliveries: nil store") } if err := input.Validate(); err != nil { return listdeliveries.Result{}, fmt.Errorf("list deliveries: %w", err) } limit := input.Limit if limit <= 0 { limit = listdeliveries.DefaultLimit } operationCtx, cancel, err := store.operationContext(ctx, "list deliveries") if err != nil { return listdeliveries.Result{}, err } defer cancel() if input.Cursor != nil { cursorStmt := pg.SELECT(pgtable.Deliveries.CreatedAt). FROM(pgtable.Deliveries). WHERE(pgtable.Deliveries.DeliveryID.EQ(pg.String(input.Cursor.DeliveryID.String()))) cursorQuery, cursorArgs := cursorStmt.Sql() row := store.db.QueryRowContext(operationCtx, cursorQuery, cursorArgs...) var createdAt sql.NullTime if err := row.Scan(&createdAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return listdeliveries.Result{}, listdeliveries.ErrInvalidCursor } return listdeliveries.Result{}, fmt.Errorf("list deliveries: validate cursor: %w", err) } if !createdAt.Valid || !createdAt.Time.UTC().Equal(input.Cursor.CreatedAt.UTC()) { return listdeliveries.Result{}, listdeliveries.ErrInvalidCursor } } conditions := make([]pg.BoolExpression, 0, 8) if input.Cursor != nil { cursorCreatedAt := pg.TimestampzT(input.Cursor.CreatedAt.UTC()) cursorID := pg.String(input.Cursor.DeliveryID.String()) // (created_at, delivery_id) < (cursorCreatedAt, cursorID) expressed as // the equivalent OR/AND expansion since jet has no row-comparison // builder. conditions = append(conditions, pg.OR( pgtable.Deliveries.CreatedAt.LT(cursorCreatedAt), pg.AND( pgtable.Deliveries.CreatedAt.EQ(cursorCreatedAt), pgtable.Deliveries.DeliveryID.LT(cursorID), ), )) } if input.Filters.Status != "" { conditions = append(conditions, pgtable.Deliveries.Status.EQ(pg.String(string(input.Filters.Status)))) } if input.Filters.Source != "" { conditions = append(conditions, pgtable.Deliveries.Source.EQ(pg.String(string(input.Filters.Source)))) } if !input.Filters.TemplateID.IsZero() { conditions = append(conditions, pgtable.Deliveries.TemplateID.EQ(pg.String(input.Filters.TemplateID.String()))) } if !input.Filters.IdempotencyKey.IsZero() { conditions = append(conditions, pgtable.Deliveries.IdempotencyKey.EQ(pg.String(input.Filters.IdempotencyKey.String()))) } if input.Filters.FromCreatedAt != nil { conditions = append(conditions, pgtable.Deliveries.CreatedAt.GT_EQ(pg.TimestampzT(input.Filters.FromCreatedAt.UTC()))) } if input.Filters.ToCreatedAt != nil { conditions = append(conditions, pgtable.Deliveries.CreatedAt.LT_EQ(pg.TimestampzT(input.Filters.ToCreatedAt.UTC()))) } if !input.Filters.Recipient.IsZero() { recipientSub := pg.SELECT(pgtable.DeliveryRecipients.DeliveryID). FROM(pgtable.DeliveryRecipients). WHERE(pg.AND( pgtable.DeliveryRecipients.Kind.NOT_EQ(pg.String(recipientKindReplyTo)), pg.LOWER(pgtable.DeliveryRecipients.Email).EQ(pg.LOWER(pg.String(input.Filters.Recipient.String()))), )) conditions = append(conditions, pgtable.Deliveries.DeliveryID.IN(recipientSub)) } stmt := pg.SELECT(deliverySelectColumns). FROM(pgtable.Deliveries) if len(conditions) > 0 { stmt = stmt.WHERE(pg.AND(conditions...)) } stmt = stmt. ORDER_BY(pgtable.Deliveries.CreatedAt.DESC(), pgtable.Deliveries.DeliveryID.DESC()). LIMIT(int64(limit + 1)) query, args := stmt.Sql() rows, err := store.db.QueryContext(operationCtx, query, args...) if err != nil { return listdeliveries.Result{}, fmt.Errorf("list deliveries: %w", err) } defer rows.Close() items := make([]deliverydomain.Delivery, 0, limit+1) for rows.Next() { record, _, err := scanDelivery(rows) if err != nil { return listdeliveries.Result{}, fmt.Errorf("list deliveries: scan: %w", err) } envelope, err := loadEnvelope(operationCtx, store.db, record.DeliveryID) if err != nil { return listdeliveries.Result{}, fmt.Errorf("list deliveries: load envelope: %w", err) } record.Envelope = envelope items = append(items, record) } if err := rows.Err(); err != nil { return listdeliveries.Result{}, fmt.Errorf("list deliveries: %w", err) } result := listdeliveries.Result{} if len(items) > limit { next := listdeliveries.Cursor{ CreatedAt: items[limit-1].CreatedAt.UTC(), DeliveryID: items[limit-1].DeliveryID, } result.NextCursor = &next items = items[:limit] } result.Items = items return result, nil } // CreateResend writes the cloned delivery, its first attempt, and the // optional cloned payload bundle inside one transaction. Resend deliveries // share the (source, idempotency_key) UNIQUE constraint, so a duplicate clone // surfaces as a generic acceptance conflict — but the resend service // generates fresh idempotency keys, so a conflict here always indicates a // caller bug rather than user-replay. func (store *Store) CreateResend(ctx context.Context, input resenddelivery.CreateResendInput) error { if store == nil { return errors.New("create resend: nil store") } if ctx == nil { return errors.New("create resend: nil context") } if err := input.Validate(); err != nil { return fmt.Errorf("create resend: %w", err) } return store.withTx(ctx, "create resend", func(ctx context.Context, tx *sql.Tx) error { // Use the delivery's own UpdatedAt as a deterministic finite expiry — // the resend has no caller-supplied idempotency.Record reservation. fallbackExpiresAt := input.Delivery.CreatedAt.Add(maxIdempotencyExpiry) first := input.FirstAttempt if err := insertDelivery(ctx, tx, input.Delivery, idempotency.Record{}, fallbackExpiresAt, &first); err != nil { if isUniqueViolation(err) { return fmt.Errorf("create resend: %w", err) } return fmt.Errorf("create resend: insert delivery: %w", err) } if err := insertAttempt(ctx, tx, input.FirstAttempt); err != nil { return fmt.Errorf("create resend: insert first attempt: %w", err) } if input.DeliveryPayload != nil { payload, err := marshalDeliveryPayload(*input.DeliveryPayload) if err != nil { return fmt.Errorf("create resend: %w", err) } payloadStmt := pgtable.DeliveryPayloads.INSERT( pgtable.DeliveryPayloads.DeliveryID, pgtable.DeliveryPayloads.Payload, ).VALUES( input.Delivery.DeliveryID.String(), payload, ) payloadQuery, payloadArgs := payloadStmt.Sql() if _, err := tx.ExecContext(ctx, payloadQuery, payloadArgs...); err != nil { return fmt.Errorf("create resend: insert delivery payload: %w", err) } } return nil }) }