Enhance company and company category search forms by adding new sortable fields (email, phone, country, state, language, currency) and updating related API documentation.
This commit is contained in:
@@ -77,7 +77,7 @@ type SearchForm struct {
|
||||
FoundedYearMax *int `query:"founded_year_max" valid:"optional,range(1800|2100)"`
|
||||
Limit *int `query:"limit" valid:"optional,range(1|100)"`
|
||||
Offset *int `query:"offset" valid:"optional,range(0|1000000)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(name|type|industry|created_at|updated_at|status|employee_count|annual_revenue)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(name|type|industry|created_at|updated_at|status|employee_count|annual_revenue|email|phone|country|state|language|currency)"`
|
||||
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ func (h *Handler) Delete(c echo.Context) error {
|
||||
// @Param is_compliant query boolean false "Compliance status"
|
||||
// @Param limit query integer false "Limit" default(20)
|
||||
// @Param offset query integer false "Offset" default(0)
|
||||
// @Param sort_by query string false "Field to sort by" Enums(name, type, industry, created_at, updated_at, status, employee_count, annual_revenue, email, phone, country, state, language, currency) default(created_at)
|
||||
// @Param sort_order query string false "Sort order" Enums(asc, desc) default(desc)
|
||||
// @Success 200 {object} response.APIResponse{data=CompanyListResponse} "Companies search completed"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid query parameters"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters"
|
||||
|
||||
@@ -56,6 +56,17 @@ func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Re
|
||||
}
|
||||
}
|
||||
|
||||
func mapCompanySortField(sortBy string) string {
|
||||
switch sortBy {
|
||||
case "country":
|
||||
return "address.country"
|
||||
case "state":
|
||||
return "address.state"
|
||||
default:
|
||||
return sortBy
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new company
|
||||
func (r *companyRepository) Create(ctx context.Context, company *Company) error {
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
@@ -285,7 +296,7 @@ func (r *companyRepository) Search(ctx context.Context, form *SearchForm, pagina
|
||||
pagination.Limit,
|
||||
pagination.Offset,
|
||||
pagination.Cursor,
|
||||
pagination.SortBy,
|
||||
mapCompanySortField(pagination.SortBy),
|
||||
pagination.SortOrder,
|
||||
filter,
|
||||
orm.ListPaginationOptions{},
|
||||
|
||||
@@ -15,7 +15,7 @@ type SearchForm struct {
|
||||
Published *bool `query:"published" valid:"optional"`
|
||||
Limit *int `query:"limit" valid:"optional,range(1|100)"`
|
||||
Offset *int `query:"offset" valid:"optional,range(0|1000000)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(name|created_at|updated_at|published_at)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(name|description|published|created_at|updated_at|published_at)"`
|
||||
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ func (h *Handler) Delete(c echo.Context) error {
|
||||
// @Param published query boolean false "Published status filter"
|
||||
// @Param limit query integer false "Limit" default(20)
|
||||
// @Param offset query integer false "Offset" default(0)
|
||||
// @Param sort_by query string false "Sort by field" Enums(name,created_at,updated_at,published_at)
|
||||
// @Param sort_by query string false "Sort by field" Enums(name,description,published,created_at,updated_at,published_at)
|
||||
// @Param sort_order query string false "Sort order" Enums(asc,desc)
|
||||
// @Success 200 {object} response.APIResponse{data=CategoryListResponse} "Categories search completed"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid query parameters"
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
@@ -189,6 +190,10 @@ func documentSortValue(docMap bson.M, sortField string) (interface{}, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
if strings.Contains(sortField, ".") {
|
||||
return nestedDocumentValue(docMap, sortField)
|
||||
}
|
||||
|
||||
sortValue, exists := docMap[sortField]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("sort field %s not found in document", sortField)
|
||||
@@ -196,6 +201,30 @@ func documentSortValue(docMap bson.M, sortField string) (interface{}, error) {
|
||||
return sortValue, nil
|
||||
}
|
||||
|
||||
func nestedDocumentValue(docMap bson.M, path string) (interface{}, error) {
|
||||
parts := strings.Split(path, ".")
|
||||
var current interface{} = docMap
|
||||
|
||||
for _, part := range parts {
|
||||
nested, ok := current.(bson.M)
|
||||
if !ok {
|
||||
if nestedMap, ok := current.(map[string]interface{}); ok {
|
||||
nested = bson.M(nestedMap)
|
||||
} else {
|
||||
return nil, fmt.Errorf("sort field %s not found in document", path)
|
||||
}
|
||||
}
|
||||
|
||||
value, exists := nested[part]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("sort field %s not found in document", path)
|
||||
}
|
||||
current = value
|
||||
}
|
||||
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func documentIDHex(docMap bson.M) (string, error) {
|
||||
id, exists := docMap["_id"]
|
||||
if !exists {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
func TestEncodeDecodePageCursor(t *testing.T) {
|
||||
encoded, err := encodePageCursor("created_at", -1, int64(1700000000), "507f1f77bcf86cd799439011", "abc123", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
if strings.Contains(encoded, "+") || strings.Contains(encoded, "/") {
|
||||
t.Fatalf("expected URL-safe cursor, got %q", encoded)
|
||||
}
|
||||
|
||||
decoded, err := decodePageCursor(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
|
||||
if decoded.SortField != "created_at" || decoded.SortOrder != -1 {
|
||||
t.Fatalf("unexpected sort: %+v", decoded)
|
||||
}
|
||||
if decoded.FiltersHash != "abc123" || decoded.PageOffset != 10 {
|
||||
t.Fatalf("unexpected metadata: %+v", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildListPaginationCursorAndOffsetConflict(t *testing.T) {
|
||||
_, err := BuildListPagination(10, 5, "cursor-token", "created_at", "desc", bson.M{}, ListPaginationOptions{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when cursor and offset are both set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildListPaginationOffsetMode(t *testing.T) {
|
||||
p, err := BuildListPagination(10, 20, "", "created_at", "desc", bson.M{"status": "active"}, ListPaginationOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("build: %v", err)
|
||||
}
|
||||
if p.Skip != 20 || p.Limit != 10 || p.Cursor != "" {
|
||||
t.Fatalf("unexpected pagination: %+v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashFilterStable(t *testing.T) {
|
||||
a := HashFilter(bson.M{"status": "active"})
|
||||
b := HashFilter(bson.M{"status": "active"})
|
||||
if a == "" || a != b {
|
||||
t.Fatalf("expected stable non-empty hash, got %q and %q", a, b)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user