Files
Mazyar 843e1508df
continuous-integration/drone/push Build is passing
Enhance tender search functionality with pagination and filtering improvements
- 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.
2026-07-10 16:23:46 +03:30

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)
}