diff --git a/internal/tender/repository.go b/internal/tender/repository.go index 474f8d9..3e9b740 100644 --- a/internal/tender/repository.go +++ b/internal/tender/repository.go @@ -40,6 +40,14 @@ func collectionName() string { return "tenders" } +// tenderSearchListProjection excludes large fields not needed for list/search API payloads. +func tenderSearchListProjection() bson.M { + return bson.M{ + "content_xml": 0, + "document_summaries": 0, + } +} + // tenderRepository implements TenderRepository interface using MongoDB ORM type tenderRepository struct { ormRepo orm.Repository[Tender] @@ -332,6 +340,7 @@ func (r *tenderRepository) Search(ctx context.Context, form *SearchForm, paginat Limit(pagination.Limit). Skip(pagination.Offset). SortBy(sort, sortOrder). + Projection(tenderSearchListProjection()). Build() result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder) diff --git a/pkg/mongo/repository.go b/pkg/mongo/repository.go index 3ee1538..d5b124b 100644 --- a/pkg/mongo/repository.go +++ b/pkg/mongo/repository.go @@ -26,6 +26,8 @@ type Pagination struct { Cursor string `json:"cursor"` // Base64 encoded cursor for cursor-based pagination SortField string `json:"sortField"` // Field to sort by (default: "_id") SortOrder int `json:"sortOrder"` // Sort order: 1 for ascending, -1 for descending + // Projection limits fields returned from Find (e.g. exclude large blobs on list endpoints). + Projection bson.M `json:"projection,omitempty"` } // PaginatedResult represents a paginated result set @@ -138,6 +140,10 @@ func (r *repository[T]) FindAll(ctx context.Context, filter bson.M, pagination P SetSort(sort). SetLimit(int64(pagination.Limit + 1)) // +1 to check if there are more documents + if len(pagination.Projection) > 0 { + findOptions.SetProjection(pagination.Projection) + } + // Handle cursor-based pagination if pagination.Cursor != "" { cursorFilter, err := r.buildCursorFilter(pagination.Cursor, pagination.SortField, pagination.SortOrder) @@ -181,13 +187,14 @@ func (r *repository[T]) FindAll(ctx context.Context, filter bson.M, pagination P items = items[:pagination.Limit] // Remove the extra item } - // Get total count - totalCount, err := r.Count(ctx, filter) - if err != nil { + // Get total count for pagination metadata + var totalCount int64 + var countErr error + totalCount, countErr = r.Count(ctx, filter) + if countErr != nil { r.logger.Error("Failed to get total count", map[string]interface{}{ - "error": err.Error(), + "error": countErr.Error(), }) - // Don't fail the entire query, just set count to 0 totalCount = 0 } diff --git a/pkg/mongo/utils.go b/pkg/mongo/utils.go index 5189433..9557793 100644 --- a/pkg/mongo/utils.go +++ b/pkg/mongo/utils.go @@ -7,8 +7,8 @@ import ( // Index represents a MongoDB index type Index struct { - Name string `json:"name"` - Keys bson.D `json:"keys"` + Name string `json:"name"` + Keys bson.D `json:"keys"` Options *options.IndexOptionsBuilder `json:"options"` } @@ -33,7 +33,6 @@ func (i *Index) WithSparse(sparse bool) *Index { return i } - // WithExpireAfterSeconds sets the TTL for the index func (i *Index) WithExpireAfterSeconds(seconds int32) *Index { i.Options.SetExpireAfterSeconds(seconds) @@ -294,6 +293,12 @@ func (pb *PaginationBuilder) SortDesc(field string) *PaginationBuilder { return pb.SortBy(field, "desc") } +// Projection sets optional MongoDB field projection for Find (e.g. exclude large fields). +func (pb *PaginationBuilder) Projection(projection bson.M) *PaginationBuilder { + pb.pagination.Projection = projection + return pb +} + // Build returns the final pagination options func (pb *PaginationBuilder) Build() Pagination { return pb.pagination