Update Configuration and Enhance Tender Approval Pagination

- Changed the server port from 8081 to 8082 for the web application.
- Updated MongoDB URI to include authentication credentials for improved security.
- Added new query parameters `limit` and `offset` for pagination in tender approval retrieval, enhancing the API's flexibility.
- Modified the response structure to include metadata for pagination, improving the clarity of responses.
- Updated API documentation to reflect the new pagination parameters and response format, ensuring consistency for API consumers.
This commit is contained in:
n.nakhostin
2025-09-06 12:07:32 +03:30
parent 61a217cf09
commit eb69a842f0
10 changed files with 171 additions and 52 deletions
+3 -3
View File
@@ -1,13 +1,13 @@
server: server:
host: "0.0.0.0" host: "0.0.0.0"
port: 8081 port: 8082
timeout: 30s timeout: 30s
read_timeout: 10s read_timeout: 10s
write_timeout: 10s write_timeout: 10s
database: database:
mongodb: mongodb:
uri: "mongodb://localhost:27017" uri: "mongodb://admin:admin1234@localhost:27017"
name: "tender_management" name: "tender_management"
timeout: 10s timeout: 10s
max_pool_size: 100 max_pool_size: 100
@@ -16,7 +16,7 @@ cache:
redis: redis:
host: "localhost" host: "localhost"
port: 6379 port: 6379
password: "" password: "admin1234"
db: 0 db: 0
pool_size: 10 pool_size: 10
+33 -5
View File
@@ -6006,6 +6006,23 @@ const docTemplate = `{
], ],
"summary": "Get tender approvals by company ID", "summary": "Get tender approvals by company ID",
"parameters": [ "parameters": [
{
"maximum": 100,
"minimum": 1,
"type": "integer",
"default": 20,
"description": "Number of approvals per page (1-100)",
"name": "limit",
"in": "query"
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Number of approvals to skip for pagination",
"name": "offset",
"in": "query"
},
{ {
"enum": [ "enum": [
"self-apply", "self-apply",
@@ -6051,10 +6068,7 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"type": "array", "$ref": "#/definitions/tender_approval.TenderApprovalWithTenderListResponse"
"items": {
"$ref": "#/definitions/tender_approval.TenderApprovalWithTenderResponse"
}
} }
} }
} }
@@ -8171,6 +8185,20 @@ const docTemplate = `{
} }
} }
}, },
"tender_approval.TenderApprovalWithTenderListResponse": {
"type": "object",
"properties": {
"metadata": {
"$ref": "#/definitions/response.Meta"
},
"tenders": {
"type": "array",
"items": {
"$ref": "#/definitions/tender_approval.TenderApprovalWithTenderResponse"
}
}
}
},
"tender_approval.TenderApprovalWithTenderResponse": { "tender_approval.TenderApprovalWithTenderResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -8585,7 +8613,7 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it // SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{ var SwaggerInfo = &swag.Spec{
Version: "2.0.0", Version: "2.0.0",
Host: "localhost:8081", Host: "localhost:8082",
BasePath: "/", BasePath: "/",
Schemes: []string{}, Schemes: []string{},
Title: "Tender Management API", Title: "Tender Management API",
+33 -5
View File
@@ -15,7 +15,7 @@
}, },
"version": "2.0.0" "version": "2.0.0"
}, },
"host": "localhost:8081", "host": "localhost:8082",
"basePath": "/", "basePath": "/",
"paths": { "paths": {
"/admin/tenders": { "/admin/tenders": {
@@ -6000,6 +6000,23 @@
], ],
"summary": "Get tender approvals by company ID", "summary": "Get tender approvals by company ID",
"parameters": [ "parameters": [
{
"maximum": 100,
"minimum": 1,
"type": "integer",
"default": 20,
"description": "Number of approvals per page (1-100)",
"name": "limit",
"in": "query"
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Number of approvals to skip for pagination",
"name": "offset",
"in": "query"
},
{ {
"enum": [ "enum": [
"self-apply", "self-apply",
@@ -6045,10 +6062,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"type": "array", "$ref": "#/definitions/tender_approval.TenderApprovalWithTenderListResponse"
"items": {
"$ref": "#/definitions/tender_approval.TenderApprovalWithTenderResponse"
}
} }
} }
} }
@@ -8165,6 +8179,20 @@
} }
} }
}, },
"tender_approval.TenderApprovalWithTenderListResponse": {
"type": "object",
"properties": {
"metadata": {
"$ref": "#/definitions/response.Meta"
},
"tenders": {
"type": "array",
"items": {
"$ref": "#/definitions/tender_approval.TenderApprovalWithTenderResponse"
}
}
}
},
"tender_approval.TenderApprovalWithTenderResponse": { "tender_approval.TenderApprovalWithTenderResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
+24 -4
View File
@@ -1045,6 +1045,15 @@ definitions:
total_approvals: total_approvals:
type: integer type: integer
type: object type: object
tender_approval.TenderApprovalWithTenderListResponse:
properties:
metadata:
$ref: '#/definitions/response.Meta'
tenders:
items:
$ref: '#/definitions/tender_approval.TenderApprovalWithTenderResponse'
type: array
type: object
tender_approval.TenderApprovalWithTenderResponse: tender_approval.TenderApprovalWithTenderResponse:
properties: properties:
company_id: company_id:
@@ -1288,7 +1297,7 @@ definitions:
username: username:
type: string type: string
type: object type: object
host: localhost:8081 host: localhost:8082
info: info:
contact: contact:
email: api-support@tender-management.com email: api-support@tender-management.com
@@ -5061,6 +5070,19 @@ paths:
description: Retrieve all tender approvals for a specific company with optional description: Retrieve all tender approvals for a specific company with optional
filtering filtering
parameters: parameters:
- default: 20
description: Number of approvals per page (1-100)
in: query
maximum: 100
minimum: 1
name: limit
type: integer
- default: 0
description: Number of approvals to skip for pagination
in: query
minimum: 0
name: offset
type: integer
- description: Filter by submission mode Enums(self-apply, partnership) - description: Filter by submission mode Enums(self-apply, partnership)
enum: enum:
- self-apply - self-apply
@@ -5093,9 +5115,7 @@ paths:
- $ref: '#/definitions/response.APIResponse' - $ref: '#/definitions/response.APIResponse'
- properties: - properties:
data: data:
items: $ref: '#/definitions/tender_approval.TenderApprovalWithTenderListResponse'
$ref: '#/definitions/tender_approval.TenderApprovalWithTenderResponse'
type: array
type: object type: object
"400": "400":
description: Bad request - Invalid company ID description: Bad request - Invalid company ID
+1 -1
View File
@@ -12,7 +12,7 @@ package main
// @license.name MIT // @license.name MIT
// @license.url https://opensource.org/licenses/MIT // @license.url https://opensource.org/licenses/MIT
// @host localhost:8081 // @host localhost:8082
// @BasePath / // @BasePath /
// @securityDefinitions.apikey BearerAuth // @securityDefinitions.apikey BearerAuth
+8 -5
View File
@@ -3,6 +3,7 @@ package tender_approval
import ( import (
"time" "time"
"tm/pkg/mongo" "tm/pkg/mongo"
"tm/pkg/response"
) )
// ApprovalStatus represents the approval status of a tender by a company // ApprovalStatus represents the approval status of a tender by a company
@@ -63,6 +64,12 @@ type TenderApprovalWithTenderResponse struct {
Tender *TenderDetails `json:"tender,omitempty"` Tender *TenderDetails `json:"tender,omitempty"`
} }
// TenderApprovalWithTenderResponse represents the response data for tender approval with tender details
type TenderApprovalWithTenderListResponse struct {
Approvals []*TenderApprovalWithTenderResponse `json:"tenders"`
Metadata *response.Meta `json:"metadata"`
}
// TenderDetails represents essential tender information for the response // TenderDetails represents essential tender information for the response
type ( type (
TenderDetails struct { TenderDetails struct {
@@ -103,11 +110,7 @@ type TenderApprovalSearchCriteria struct {
// TenderApprovalListResponseWithTender represents a paginated list of tender approvals with tender details // TenderApprovalListResponseWithTender represents a paginated list of tender approvals with tender details
type TenderApprovalListResponseWithTender struct { type TenderApprovalListResponseWithTender struct {
TenderApprovals []*TenderApprovalWithTenderResponse `json:"tender_approvals"` TenderApprovals []*TenderApprovalWithTenderResponse `json:"tender_approvals"`
Total int64 `json:"total"` Metadata *response.Meta `json:"metadata"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Page int `json:"page"`
Pages int `json:"pages"`
} }
// TenderApprovalStatistics represents statistics for tender approvals // TenderApprovalStatistics represents statistics for tender approvals
+20 -2
View File
@@ -129,16 +129,34 @@ func (h *Handler) GetTenderApprovalByTenderAndCompany(c echo.Context) error {
// @Tags TenderApprovals // @Tags TenderApprovals
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param limit query integer false "Number of approvals per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of approvals to skip for pagination" minimum(0) default(0)
// @Param submission_mode query string false "Filter by submission mode Enums(self-apply, partnership)" // @Param submission_mode query string false "Filter by submission mode Enums(self-apply, partnership)"
// @Param status query string false "Filter by status Enums(submitted, rejected)" // @Param status query string false "Filter by status Enums(submitted, rejected)"
// @Param created_from query integer false "Filter by created date from (Unix timestamp)" // @Param created_from query integer false "Filter by created date from (Unix timestamp)"
// @Param created_to query integer false "Filter by created date to (Unix timestamp)" // @Param created_to query integer false "Filter by created date to (Unix timestamp)"
// @Success 200 {object} response.APIResponse{data=[]TenderApprovalWithTenderResponse} "Tender approvals retrieved successfully" // @Success 200 {object} response.APIResponse{data=TenderApprovalWithTenderListResponse} "Tender approvals retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID" // @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID"
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /api/v1/tender-approvals [get] // @Router /api/v1/tender-approvals [get]
func (h *Handler) PublicGetTenderApprovals(c echo.Context) error { func (h *Handler) PublicGetTenderApprovals(c echo.Context) error {
// Parse pagination parameters
limit := 20
if l := c.QueryParam("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
limit = parsed
}
}
offset := 0
if o := c.QueryParam("offset"); o != "" {
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
offset = parsed
}
}
companyID := c.Get("company_id").(string) companyID := c.Get("company_id").(string)
if companyID == "" { if companyID == "" {
return response.Unauthorized(c, "Unauthorized") return response.Unauthorized(c, "Unauthorized")
@@ -182,7 +200,7 @@ func (h *Handler) PublicGetTenderApprovals(c echo.Context) error {
} }
} }
tenderApprovalsWithTender, err := h.service.GetTenderApprovalsByCompanyIDWithTender(c.Request().Context(), companyID, submissionMode, status, from, to) tenderApprovalsWithTender, err := h.service.GetTenderApprovalsByCompanyIDWithTender(c.Request().Context(), companyID, submissionMode, status, from, to, limit, offset)
if err != nil { if err != nil {
return response.InternalServerError(c, "Failed to get tender approvals") return response.InternalServerError(c, "Failed to get tender approvals")
} }
+12 -5
View File
@@ -17,7 +17,7 @@ type Repository interface {
GetByID(ctx context.Context, id string) (*TenderApproval, error) GetByID(ctx context.Context, id string) (*TenderApproval, error)
GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error)
GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error)
GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64) ([]*TenderApproval, error) GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, limit int, offset int, from *int64, to *int64) ([]*TenderApproval, int64, error)
GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error)
Update(ctx context.Context, tenderApproval *TenderApproval) error Update(ctx context.Context, tenderApproval *TenderApproval) error
Delete(ctx context.Context, id string) error Delete(ctx context.Context, id string) error
@@ -160,9 +160,16 @@ func (r *tenderApprovalRepository) GetByTenderID(ctx context.Context, tenderID s
} }
// GetByCompanyID retrieves all tender approvals for a specific company with optional filters // GetByCompanyID retrieves all tender approvals for a specific company with optional filters
func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64) ([]*TenderApproval, error) { func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, limit int, offset int, from *int64, to *int64) ([]*TenderApproval, int64, error) {
filter := bson.M{"company_id": companyID} filter := bson.M{"company_id": companyID}
pagination := mongo.Pagination{
Limit: limit,
Skip: offset,
SortField: "created_at",
SortOrder: -1,
}
// Add submission mode filter if provided // Add submission mode filter if provided
if submissionMode != nil { if submissionMode != nil {
filter["submission_mode"] = *submissionMode filter["submission_mode"] = *submissionMode
@@ -180,13 +187,13 @@ func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID
filter["created_at"] = bson.M{"$lte": *to} filter["created_at"] = bson.M{"$lte": *to}
} }
result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().SortDesc("created_at").Build()) result, err := r.ormRepo.FindAll(ctx, filter, pagination)
if err != nil { if err != nil {
r.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{ r.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"company_id": companyID, "company_id": companyID,
}) })
return nil, err return nil, 0, err
} }
// Convert []TenderApproval to []*TenderApproval // Convert []TenderApproval to []*TenderApproval
@@ -195,7 +202,7 @@ func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID
tenderApprovals[i] = &result.Items[i] tenderApprovals[i] = &result.Items[i]
} }
return tenderApprovals, nil return tenderApprovals, result.TotalCount, nil
} }
// GetByIDs retrieves multiple tender approvals by their IDs // GetByIDs retrieves multiple tender approvals by their IDs
+32 -17
View File
@@ -3,9 +3,11 @@ package tender_approval
import ( import (
"context" "context"
"errors" "errors"
"math"
"time" "time"
"tm/internal/tender" "tm/internal/tender"
"tm/pkg/logger" "tm/pkg/logger"
"tm/pkg/response"
) )
// Service defines business logic for tender approval operations // Service defines business logic for tender approval operations
@@ -17,8 +19,8 @@ type Service interface {
GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error)
GetTenderApprovalByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderApprovalWithTenderResponse, error) GetTenderApprovalByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderApprovalWithTenderResponse, error)
GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error)
GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64) ([]*TenderApproval, error) GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit int, offset int) ([]*TenderApproval, int64, error)
GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64) ([]*TenderApprovalWithTenderResponse, error) GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit int, offset int) (*TenderApprovalWithTenderListResponse, error)
GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error)
UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error) UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error)
UpdateTenderApprovalWithTender(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApprovalWithTenderResponse, error) UpdateTenderApprovalWithTender(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApprovalWithTenderResponse, error)
@@ -261,14 +263,14 @@ func (s *tenderApprovalService) GetTenderApprovalsByTenderID(ctx context.Context
} }
// GetTenderApprovalsByCompanyID retrieves all tender approvals for a specific company with optional filters // GetTenderApprovalsByCompanyID retrieves all tender approvals for a specific company with optional filters
func (s *tenderApprovalService) GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64) ([]*TenderApproval, error) { func (s *tenderApprovalService) GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit int, offset int) ([]*TenderApproval, int64, error) {
tenderApprovals, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status, from, to) tenderApprovals, total, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status, limit, offset, from, to)
if err != nil { if err != nil {
s.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{ s.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"company_id": companyID, "company_id": companyID,
}) })
return nil, err return nil, 0, err
} }
s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{ s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{
@@ -276,12 +278,12 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyID(ctx context.Contex
"count": len(tenderApprovals), "count": len(tenderApprovals),
}) })
return tenderApprovals, nil return tenderApprovals, total, nil
} }
// GetTenderApprovalsByCompanyIDWithTender retrieves all tender approvals for a specific company with tender details // GetTenderApprovalsByCompanyIDWithTender retrieves all tender approvals for a specific company with tender details
func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64) ([]*TenderApprovalWithTenderResponse, error) { func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit, offset int) (*TenderApprovalWithTenderListResponse, error) {
tenderApprovals, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status, from, to) tenderApprovals, total, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status, limit, offset, from, to)
if err != nil { if err != nil {
s.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{ s.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -291,7 +293,10 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx cont
} }
// Convert to response with tender details // Convert to response with tender details
var responses []*TenderApprovalWithTenderResponse responses := &TenderApprovalWithTenderListResponse{
Approvals: []*TenderApprovalWithTenderResponse{},
Metadata: &response.Meta{},
}
for _, approval := range tenderApprovals { for _, approval := range tenderApprovals {
// Get tender details // Get tender details
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID) tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
@@ -301,7 +306,7 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx cont
"error": err.Error(), "error": err.Error(),
}) })
// Continue without tender details rather than failing completely // Continue without tender details rather than failing completely
responses = append(responses, approval.ToResponseWithTender(nil)) responses.Approvals = append(responses.Approvals, approval.ToResponseWithTender(nil))
continue continue
} }
@@ -319,12 +324,20 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx cont
Status: string(tenderResponse.Status), Status: string(tenderResponse.Status),
} }
responses = append(responses, approval.ToResponseWithTender(tenderDetails)) responses.Approvals = append(responses.Approvals, approval.ToResponseWithTender(tenderDetails))
}
responses.Metadata = &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
Pages: int(math.Ceil(float64(total) / float64(limit))),
Page: int(math.Ceil(float64(offset) / float64(limit))),
} }
s.logger.Info("Tender approvals with tender details retrieved successfully", map[string]interface{}{ s.logger.Info("Tender approvals with tender details retrieved successfully", map[string]interface{}{
"company_id": companyID, "company_id": companyID,
"count": len(responses), "count": len(responses.Approvals),
}) })
return responses, nil return responses, nil
@@ -960,11 +973,13 @@ func (s *tenderApprovalService) ListTenderApprovalsWithTender(ctx context.Contex
meta := &TenderApprovalListResponseWithTender{ meta := &TenderApprovalListResponseWithTender{
TenderApprovals: responses, TenderApprovals: responses,
Total: total, Metadata: &response.Meta{
Limit: limit, Total: int(total),
Offset: offset, Limit: limit,
Page: page, Offset: offset,
Pages: pages, Page: page,
Pages: pages,
},
} }
return meta, nil return meta, nil
+5 -5
View File
@@ -25,11 +25,11 @@ type APIError struct {
// Meta represents metadata for paginated responses // Meta represents metadata for paginated responses
type Meta struct { type Meta struct {
Total int `json:"total,omitempty"` Total int `json:"total"`
Limit int `json:"limit,omitempty"` Limit int `json:"limit"`
Offset int `json:"offset,omitempty"` Offset int `json:"offset"`
Page int `json:"page,omitempty"` Page int `json:"page"`
Pages int `json:"pages,omitempty"` Pages int `json:"pages"`
} }
// Success returns a successful response // Success returns a successful response