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
+17 -58
View File
@@ -1,11 +1,13 @@
package response
import (
"errors"
"net/http"
"strconv"
"github.com/asaskevich/govalidator"
"github.com/labstack/echo/v4"
orm "tm/pkg/mongo"
)
// APIResponse represents a standard API response structure
@@ -26,17 +28,17 @@ type APIError struct {
// Meta represents metadata for paginated responses
type Meta struct {
Total int `json:"total,omitempty"`
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Page int `json:"page,omitempty"`
Pages int `json:"pages,omitempty"`
Page int `json:"page"`
Pages int `json:"pages"`
HasMore bool `json:"has_more"`
NextCursor string `json:"next_cursor,omitempty"`
HasMore bool `json:"has_more,omitempty"`
}
type Pagination struct {
Limit int `query:"limit" valid:"optional,range(1|100)" default:"20"`
Limit int `query:"limit" valid:"optional,range(1|100)" default:"10"`
Offset int `query:"offset" valid:"optional,range(0|1000000)" default:"0"`
Cursor string `query:"cursor" valid:"optional"`
SortBy string `query:"sort_by" valid:"optional"`
@@ -192,59 +194,16 @@ func Parse[T any](c echo.Context) (*T, error) {
return &form, nil
}
func NewPagination(c echo.Context) *Pagination {
limit, err := strconv.Atoi(c.QueryParam("limit"))
if err != nil {
limit = 20
}
offset, err := strconv.Atoi(c.QueryParam("offset"))
if err != nil {
offset = 0
}
sortBy := c.QueryParam("sort_by")
if sortBy == "" {
sortBy = "created_at"
}
sortOrder := c.QueryParam("sort_order")
if sortOrder == "" {
sortOrder = "desc"
}
cursor := c.QueryParam("cursor")
return &Pagination{
Limit: limit,
Offset: offset,
Cursor: cursor,
SortBy: sortBy,
SortOrder: sortOrder,
// PaginationBadRequest writes a 400 response for invalid list pagination parameters.
func PaginationBadRequest(c echo.Context, err error) error {
msg := "Invalid pagination parameters"
if errors.Is(err, orm.ErrInvalidCursor) {
msg = "Invalid pagination cursor"
}
return BadRequest(c, msg, err.Error())
}
// Response calculates offset-based pagination metadata.
func (p *Pagination) Response(total int64) *Meta {
return p.ListMeta(total, "", false)
}
// ListMeta builds pagination metadata for list endpoints (offset and/or cursor).
// When total is negative, total/page/pages are omitted (count was skipped).
func (p *Pagination) ListMeta(total int64, nextCursor string, hasMore bool) *Meta {
meta := &Meta{
Limit: p.Limit,
Offset: p.Offset,
NextCursor: nextCursor,
HasMore: hasMore,
}
if total >= 0 {
pages := (total + int64(p.Limit) - 1) / int64(p.Limit)
meta.Total = int(total)
if p.Limit > 0 {
meta.Page = p.Offset/p.Limit + 1
}
meta.Pages = int(pages)
}
return meta
// IsListPaginationError reports client-facing pagination errors from list queries.
func IsListPaginationError(err error) bool {
return IsPaginationError(err) || errors.Is(err, orm.ErrInvalidCursor) || errors.Is(err, orm.ErrInvalidPagination)
}