package mail import ( "context" "github.com/google/uuid" ) // AdminListDeliveriesPage bundles the pagination metadata returned to // the admin API. The same shape is reused by AdminListDeadLettersPage // — keeping it explicit clarifies the wire contract for handlers. type AdminListDeliveriesPage struct { Items []Delivery Page int PageSize int Total int64 } // AdminListDeadLettersPage mirrors AdminListDeliveriesPage for the // dead-letter listing. type AdminListDeadLettersPage struct { Items []DeadLetter Page int PageSize int Total int64 } // AdminListDeliveries returns the requested delivery page. page is // 1-indexed; pageSize is bounded by the caller (handler defaults). func (s *Service) AdminListDeliveries(ctx context.Context, page, pageSize int) (AdminListDeliveriesPage, error) { page, pageSize = normalisePaging(page, pageSize) offset := (page - 1) * pageSize items, total, err := s.deps.Store.ListDeliveries(ctx, offset, pageSize) if err != nil { return AdminListDeliveriesPage{}, err } return AdminListDeliveriesPage{ Items: items, Page: page, PageSize: pageSize, Total: total, }, nil } // AdminGetDelivery returns the delivery row by id, or // ErrDeliveryNotFound when the row does not exist. func (s *Service) AdminGetDelivery(ctx context.Context, deliveryID uuid.UUID) (Delivery, error) { return s.deps.Store.GetDelivery(ctx, deliveryID) } // AdminListAttempts returns every attempt for the delivery in // attempt_no order. ErrDeliveryNotFound is returned when the delivery // row itself does not exist; an empty list (no rows yet) returns nil // without error. func (s *Service) AdminListAttempts(ctx context.Context, deliveryID uuid.UUID) ([]Attempt, error) { if _, err := s.deps.Store.GetDelivery(ctx, deliveryID); err != nil { return nil, err } return s.deps.Store.ListAttempts(ctx, deliveryID) } // AdminResendDelivery re-arms the targeted row for another delivery // cycle. The contract: ErrDeliveryNotFound when the row is missing, // ErrResendOnSent when the row is in the terminal `sent` state. // Otherwise the row is reset to status='pending' with attempts=0 and // next_attempt_at=now(); the worker picks it up on the next tick. func (s *Service) AdminResendDelivery(ctx context.Context, deliveryID uuid.UUID) (Delivery, error) { return s.deps.Store.ResendNonSent(ctx, deliveryID, s.deps.Now()) } // AdminListDeadLetters returns the dead-letter page newest-first. func (s *Service) AdminListDeadLetters(ctx context.Context, page, pageSize int) (AdminListDeadLettersPage, error) { page, pageSize = normalisePaging(page, pageSize) offset := (page - 1) * pageSize items, total, err := s.deps.Store.ListDeadLetters(ctx, offset, pageSize) if err != nil { return AdminListDeadLettersPage{}, err } return AdminListDeadLettersPage{ Items: items, Page: page, PageSize: pageSize, Total: total, }, nil } // normalisePaging clamps page and pageSize to the values handlers can // safely pass through to the store. The defaults match what the // existing admin endpoints use elsewhere in `internal/server`. func normalisePaging(page, pageSize int) (int, int) { if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = 25 } if pageSize > 200 { pageSize = 200 } return page, pageSize }