843e1508df
continuous-integration/drone/push Build is passing
- Updated the tender search handler to include a new `include_total` query parameter, allowing clients to request total count and pagination metadata for search results. - Refactored the `tenderSearchListProjection` to exclude heavy fields from the list response, optimizing data retrieval. - Modified the `buildSearchFilter` method to utilize the `processing_metadata.documents_scraped` flag for filtering, improving search accuracy. - Added unit tests for the new pagination features and search filter logic, ensuring robust validation of the search functionality. This update significantly enhances the tender search experience by providing more flexible pagination options and improving the efficiency of data handling.
109 lines
3.0 KiB
Go
109 lines
3.0 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"
|
|
}
|
|
|
|
includeTotal := c.QueryParam("include_total") == "true"
|
|
|
|
return &Pagination{
|
|
Limit: limit,
|
|
Offset: offset,
|
|
Cursor: cursor,
|
|
SortBy: sortBy,
|
|
SortOrder: sortOrder,
|
|
IncludeTotal: includeTotal,
|
|
}, 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)
|
|
}
|