Files
tm_back/pkg/response/response.go
T
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

223 lines
5.8 KiB
Go

package response
import (
"errors"
"net/http"
"github.com/asaskevich/govalidator"
"github.com/labstack/echo/v4"
orm "tm/pkg/mongo"
)
// APIResponse represents a standard API response structure
type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
// APIError represents error details in API response
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// Meta represents metadata for paginated responses
type Meta struct {
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Page int `json:"page"`
Pages int `json:"pages"`
HasMore bool `json:"has_more"`
NextCursor string `json:"next_cursor,omitempty"`
}
type Pagination struct {
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"`
SortOrder string `query:"sort_order" valid:"optional,in(asc|desc)"`
IncludeTotal bool `query:"include_total" valid:"optional"`
}
// Success returns a successful response
func Success(c echo.Context, data interface{}, message string) error {
return c.JSON(http.StatusOK, APIResponse{
Success: true,
Message: message,
Data: data,
})
}
// SuccessWithMeta returns a successful response with metadata
func SuccessWithMeta(c echo.Context, data interface{}, meta *Meta, message string) error {
return c.JSON(http.StatusOK, APIResponse{
Success: true,
Message: message,
Data: data,
Meta: meta,
})
}
// Created returns a 201 Created response
func Created(c echo.Context, data interface{}, message string) error {
return c.JSON(http.StatusCreated, APIResponse{
Success: true,
Message: message,
Data: data,
})
}
// Accepted returns a 202 Accepted response (for async operations)
func Accepted(c echo.Context, data interface{}, message string) error {
return c.JSON(http.StatusAccepted, APIResponse{
Success: true,
Message: message,
Data: data,
})
}
// BadRequest returns a 400 Bad Request response
func BadRequest(c echo.Context, message, details string) error {
return c.JSON(http.StatusBadRequest, APIResponse{
Success: false,
Message: "Bad Request",
Error: &APIError{
Code: "BAD_REQUEST",
Message: message,
Details: details,
},
})
}
// Unauthorized returns a 401 Unauthorized response
func Unauthorized(c echo.Context, message string) error {
return c.JSON(http.StatusUnauthorized, APIResponse{
Success: false,
Message: "Unauthorized",
Error: &APIError{
Code: "UNAUTHORIZED",
Message: message,
},
})
}
// Forbidden returns a 403 Forbidden response
func Forbidden(c echo.Context, message string) error {
return c.JSON(http.StatusForbidden, APIResponse{
Success: false,
Message: "Forbidden",
Error: &APIError{
Code: "FORBIDDEN",
Message: message,
},
})
}
// NotFound returns a 404 Not Found response
func NotFound(c echo.Context, message string) error {
return c.JSON(http.StatusNotFound, APIResponse{
Success: false,
Message: "Not Found",
Error: &APIError{
Code: "NOT_FOUND",
Message: message,
},
})
}
// Conflict returns a 409 Conflict response
func Conflict(c echo.Context, message string) error {
return c.JSON(http.StatusConflict, APIResponse{
Success: false,
Message: "Conflict",
Error: &APIError{
Code: "CONFLICT",
Message: message,
},
})
}
// TooManyRequests returns a 429 Too Many Requests response
func TooManyRequests(c echo.Context, message string) error {
return c.JSON(http.StatusTooManyRequests, APIResponse{
Success: false,
Message: "Too Many Requests",
Error: &APIError{
Code: "TOO_MANY_REQUESTS",
Message: message,
},
})
}
// ValidationError returns a 422 Unprocessable Entity response
func ValidationError(c echo.Context, message, details string) error {
return c.JSON(http.StatusUnprocessableEntity, APIResponse{
Success: false,
Message: "Validation Error",
Error: &APIError{
Code: "VALIDATION_ERROR",
Message: message,
Details: details,
},
})
}
// InternalServerError returns a 500 Internal Server Error response
func InternalServerError(c echo.Context, message string) error {
return c.JSON(http.StatusInternalServerError, APIResponse{
Success: false,
Message: "Internal Server Error",
Error: &APIError{
Code: "INTERNAL_SERVER_ERROR",
Message: message,
},
})
}
// ServiceUnavailable returns a 503 Service Unavailable response
func ServiceUnavailable(c echo.Context, message string) error {
return c.JSON(http.StatusServiceUnavailable, APIResponse{
Success: false,
Message: "Service Unavailable",
Error: &APIError{
Code: "SERVICE_UNAVAILABLE",
Message: message,
},
})
}
// Parse parses the form and validates it
func Parse[T any](c echo.Context) (*T, error) {
var form T
if err := c.Bind(&form); err != nil {
return nil, err
}
if _, err := govalidator.ValidateStruct(form); err != nil {
return nil, err
}
return &form, nil
}
// 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())
}
// 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)
}