106 lines
2.9 KiB
Go
106 lines
2.9 KiB
Go
package response
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
const (
|
|
// DefaultListLimit is the default page size for admin list endpoints.
|
|
DefaultListLimit = 10
|
|
// MaxListLimit is the maximum allowed page size.
|
|
MaxListLimit = 100
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidPagination is returned when pagination query parameters are invalid.
|
|
ErrInvalidPagination = errors.New("invalid pagination parameters")
|
|
// ErrPaginationConflict is returned when cursor and offset are both supplied.
|
|
ErrPaginationConflict = errors.New("cursor and offset pagination cannot be used together")
|
|
)
|
|
|
|
// NewPagination parses and validates hybrid pagination query parameters from the request.
|
|
func NewPagination(c echo.Context) (*Pagination, error) {
|
|
limit := DefaultListLimit
|
|
if limitStr := c.QueryParam("limit"); limitStr != "" {
|
|
parsed, err := strconv.Atoi(limitStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: limit must be an integer", ErrInvalidPagination)
|
|
}
|
|
limit = parsed
|
|
}
|
|
if limit < 1 || limit > MaxListLimit {
|
|
return nil, fmt.Errorf("%w: limit must be between 1 and %d", ErrInvalidPagination, MaxListLimit)
|
|
}
|
|
|
|
cursor := c.QueryParam("cursor")
|
|
offsetParam := c.QueryParam("offset")
|
|
if cursor != "" && offsetParam != "" {
|
|
return nil, ErrPaginationConflict
|
|
}
|
|
|
|
offset := 0
|
|
if offsetParam != "" {
|
|
parsed, err := strconv.Atoi(offsetParam)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: offset must be an integer", ErrInvalidPagination)
|
|
}
|
|
if parsed < 0 {
|
|
return nil, fmt.Errorf("%w: offset cannot be negative", ErrInvalidPagination)
|
|
}
|
|
offset = parsed
|
|
}
|
|
|
|
sortBy := c.QueryParam("sort_by")
|
|
if sortBy == "" {
|
|
sortBy = "created_at"
|
|
}
|
|
|
|
sortOrder := c.QueryParam("sort_order")
|
|
if sortOrder == "" {
|
|
sortOrder = "desc"
|
|
}
|
|
|
|
return &Pagination{
|
|
Limit: limit,
|
|
Offset: offset,
|
|
Cursor: cursor,
|
|
SortBy: sortBy,
|
|
SortOrder: sortOrder,
|
|
}, nil
|
|
}
|
|
|
|
// ListMeta builds pagination metadata for a list page.
|
|
// pageOffset is the zero-based index of the first item in this page (from offset or cursor).
|
|
func (p *Pagination) ListMeta(total int64, nextCursor string, hasMore bool, pageOffset int) *Meta {
|
|
meta := &Meta{
|
|
Limit: p.Limit,
|
|
Offset: pageOffset,
|
|
HasMore: hasMore,
|
|
}
|
|
if hasMore && nextCursor != "" {
|
|
meta.NextCursor = nextCursor
|
|
}
|
|
if total >= 0 {
|
|
meta.Total = int(total)
|
|
if p.Limit > 0 {
|
|
meta.Page = pageOffset/p.Limit + 1
|
|
meta.Pages = int((total + int64(p.Limit) - 1) / int64(p.Limit))
|
|
}
|
|
}
|
|
return meta
|
|
}
|
|
|
|
// ListMetaFromPage builds metadata from a paginated repository result.
|
|
func (p *Pagination) ListMetaFromPage(total int64, nextCursor string, hasMore bool, pageOffset int) *Meta {
|
|
return p.ListMeta(total, nextCursor, hasMore, pageOffset)
|
|
}
|
|
|
|
// IsPaginationError reports whether err is a client-facing pagination error.
|
|
func IsPaginationError(err error) bool {
|
|
return errors.Is(err, ErrInvalidPagination) || errors.Is(err, ErrPaginationConflict)
|
|
}
|