hybrid pagination

This commit is contained in:
Mazyar
2026-05-17 17:21:36 +03:30
parent 42d0a47226
commit 6dac5b482a
39 changed files with 696 additions and 386 deletions
+78
View File
@@ -0,0 +1,78 @@
package mongo
import (
"fmt"
"go.mongodb.org/mongo-driver/v2/bson"
)
const (
// DefaultListLimit is the default page size for admin list endpoints.
DefaultListLimit = 10
// MaxListLimit is the maximum allowed page size.
MaxListLimit = 100
)
// ListPaginationOptions configures optional list pagination behavior.
type ListPaginationOptions struct {
Projection bson.M
}
// BuildListPagination converts API pagination parameters into MongoDB pagination options.
// filter is used to bind cursors to the active query filters via a hash.
func BuildListPagination(
limit, offset int,
cursor, sortField, sortOrder string,
filter bson.M,
opts ListPaginationOptions,
) (Pagination, error) {
if limit <= 0 {
limit = DefaultListLimit
}
if limit > MaxListLimit {
return Pagination{}, fmt.Errorf("%w: limit must be between 1 and %d", ErrInvalidPagination, MaxListLimit)
}
if sortField == "" {
sortField = "created_at"
}
if sortOrder == "" {
sortOrder = "desc"
}
sortOrderInt := -1
if sortOrder == "asc" {
sortOrderInt = 1
}
filtersHash := HashFilter(filter)
pb := NewPaginationBuilder().
Limit(limit).
SortBy(sortField, sortOrder)
if len(opts.Projection) > 0 {
pb.Projection(opts.Projection)
}
if cursor != "" {
pageCursor, err := decodePageCursor(cursor)
if err != nil {
return Pagination{}, err
}
if !pageCursor.matches(sortField, sortOrderInt) {
return Pagination{}, fmt.Errorf("%w: cursor does not match requested sort", ErrInvalidCursor)
}
if pageCursor.FiltersHash != filtersHash {
return Pagination{}, fmt.Errorf("%w: cursor does not match current filters", ErrInvalidCursor)
}
pb.Cursor(cursor)
} else {
if offset < 0 {
return Pagination{}, fmt.Errorf("%w: offset cannot be negative", ErrInvalidPagination)
}
pb.Skip(offset)
}
return pb.Build(), nil
}