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
+24 -13
View File
@@ -177,11 +177,15 @@ func (h *TenderHandler) Search(c echo.Context) error {
return response.ValidationError(c, "Validation failed", err.Error())
}
// Call service
tenders, err := h.service.Search(c.Request().Context(), form, response.NewPagination(c))
pagination, err := response.NewPagination(c)
if err != nil {
if isInvalidPaginationCursor(err) {
return response.ValidationError(c, "Invalid pagination cursor", err.Error())
return response.PaginationBadRequest(c, err)
}
tenders, err := h.service.Search(c.Request().Context(), form, pagination)
if err != nil {
if response.IsListPaginationError(err) {
return response.PaginationBadRequest(c, err)
}
return response.InternalServerError(c, "Failed to list tenders")
}
@@ -259,10 +263,15 @@ func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
}
form.CustomerID = customerID
result, err := h.service.Search(c.Request().Context(), form, response.NewPagination(c))
pagination, err := response.NewPagination(c)
if err != nil {
if isInvalidPaginationCursor(err) {
return response.ValidationError(c, "Invalid pagination cursor", err.Error())
return response.PaginationBadRequest(c, err)
}
result, err := h.service.Search(c.Request().Context(), form, pagination)
if err != nil {
if response.IsListPaginationError(err) {
return response.PaginationBadRequest(c, err)
}
return response.InternalServerError(c, "Failed to retrieve tenders")
}
@@ -335,10 +344,15 @@ func (h *TenderHandler) RecommendTenders(c echo.Context) error {
}
form.CustomerID = customerID
result, err := h.service.Recommend(c.Request().Context(), form, response.NewPagination(c))
pagination, err := response.NewPagination(c)
if err != nil {
if isInvalidPaginationCursor(err) {
return response.ValidationError(c, "Invalid pagination cursor", err.Error())
return response.PaginationBadRequest(c, err)
}
result, err := h.service.Recommend(c.Request().Context(), form, pagination)
if err != nil {
if response.IsListPaginationError(err) {
return response.PaginationBadRequest(c, err)
}
return response.InternalServerError(c, "Failed to retrieve tenders")
}
@@ -664,6 +678,3 @@ func (h *TenderHandler) GetAllAISummaries(c echo.Context) error {
return response.Success(c, summaries, "AI summaries retrieved successfully")
}
func isInvalidPaginationCursor(err error) bool {
return errors.Is(err, orm.ErrInvalidCursor)
}
+14 -15
View File
@@ -40,8 +40,6 @@ func collectionName() string {
return "tenders"
}
// deepOffsetSkipCountThreshold skips CountDocuments when offset pagination goes this deep.
const deepOffsetSkipCountThreshold = 1000
// SearchPageResult is the paginated outcome of a tender search/list query.
type SearchPageResult struct {
@@ -49,6 +47,7 @@ type SearchPageResult struct {
TotalCount int64 // -1 when count was skipped
NextCursor string
HasMore bool
PageOffset int
}
// tenderSearchListProjection excludes large fields not needed for list/search API payloads.
@@ -370,21 +369,20 @@ func (r *tenderRepository) Search(ctx context.Context, form *SearchForm, paginat
sortOrder = pagination.SortOrder
}
skipCount := pagination.Cursor != "" || pagination.Offset >= deepOffsetSkipCountThreshold
paginationBuilder := orm.NewPaginationBuilder().
Limit(pagination.Limit).
SortBy(sort, sortOrder).
Projection(tenderSearchListProjection()).
SkipCount(skipCount)
if pagination.Cursor != "" {
paginationBuilder.Cursor(pagination.Cursor)
} else {
paginationBuilder.Skip(pagination.Offset)
mongoPagination, err := orm.BuildListPagination(
pagination.Limit,
pagination.Offset,
pagination.Cursor,
sort,
sortOrder,
filter,
orm.ListPaginationOptions{Projection: tenderSearchListProjection()},
)
if err != nil {
return nil, err
}
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder.Build())
result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
if err != nil {
r.logger.Error("Failed to search tenders", map[string]interface{}{
"error": err.Error(),
@@ -397,6 +395,7 @@ func (r *tenderRepository) Search(ctx context.Context, form *SearchForm, paginat
TotalCount: result.TotalCount,
NextCursor: result.NextCursor,
HasMore: result.HasMore,
PageOffset: result.PageOffset,
}, nil
}
+4 -4
View File
@@ -648,7 +648,7 @@ func (s *tenderService) Search(ctx context.Context, form *SearchForm, pagination
return &SearchResponse{
Tenders: tenderResponses,
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore),
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset),
}, nil
}
@@ -667,7 +667,7 @@ func (s *tenderService) Search(ctx context.Context, form *SearchForm, pagination
return &SearchResponse{
Tenders: tenderResponses,
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore),
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset),
},
nil
}
@@ -690,7 +690,7 @@ func (s *tenderService) Search(ctx context.Context, form *SearchForm, pagination
return &SearchResponse{
Tenders: tenderResponses,
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore),
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset),
},
nil
}
@@ -757,7 +757,7 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat
return &SearchResponse{
Tenders: tenderResponses,
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore),
Metadata: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset),
}, nil
}