Implement Company Search and Update API Documentation

- Introduced a new search functionality for companies, allowing advanced filtering capabilities including tags, business criteria, and location.
- Updated the API documentation to reflect changes in the search endpoint, enhancing clarity for API consumers.
- Refactored existing company-related API endpoints for consistency, including renaming and restructuring routes.
- Enhanced response structures to return company entities directly, simplifying the response handling in API endpoints.
- Removed unused query parameters and handlers, streamlining the company management functionality.
This commit is contained in:
n.nakhostin
2025-09-08 11:23:15 +03:30
parent 777bbfd714
commit ee830f8c1b
16 changed files with 589 additions and 5338 deletions
+89 -538
View File
@@ -5,70 +5,38 @@ import (
"errors"
"time"
"tm/pkg/logger"
"tm/pkg/mongo"
orm "tm/pkg/mongo"
"tm/pkg/response"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Repository defines the interface for company data operations
type Repository interface {
Create(ctx context.Context, company *Company) error
Update(ctx context.Context, company *Company) error
GetByID(ctx context.Context, id string) (*Company, error)
GetByIDs(ctx context.Context, ids []string) ([]Company, error)
GetByName(ctx context.Context, name string) (*Company, error)
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error)
GetByTaxID(ctx context.Context, taxID string) (*Company, error)
GetByIDs(ctx context.Context, ids []string) ([]*Company, error)
Update(ctx context.Context, company *Company) error
Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Company, int64, error)
Delete(ctx context.Context, id string) error
List(ctx context.Context, limit, offset int) ([]*Company, error)
Search(ctx context.Context, search string, companyType *string, status *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, cpvCodes []string, categories []string, keywords []string, specializations []string, employeeCountMin *int, employeeCountMax *int, annualRevenueMin *float64, annualRevenueMax *float64, foundedYearMin *int, foundedYearMax *int, limit, offset int, sortBy, sortOrder string) ([]*Company, error)
Count(ctx context.Context) (int64, error)
CountByType(ctx context.Context, companyType CompanyType) (int64, error)
CountByStatus(ctx context.Context, status CompanyStatus) (int64, error)
CountByIndustry(ctx context.Context, industry string) (int64, error)
CountVerified(ctx context.Context) (int64, error)
UpdateStatus(ctx context.Context, id string, status CompanyStatus) error
UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error
UpdateTags(ctx context.Context, id string, tags *CompanyTags) error
AddTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error
RemoveTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error
GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error)
SearchByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error)
}
// companyRepository implements the Repository interface using the MongoDB ORM
type companyRepository struct {
ormRepo mongo.Repository[Company]
ormRepo orm.Repository[Company]
logger logger.Logger
}
// NewCompanyRepository creates a new company repository
func NewCompanyRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository {
func NewCompanyRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
// Create indexes using the ORM's index management
indexes := []mongo.Index{
*mongo.CreateUniqueIndex("name_idx", bson.D{{Key: "name", Value: 1}}),
*mongo.CreateUniqueIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
*mongo.CreateUniqueIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}),
*mongo.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}),
*mongo.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
*mongo.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}),
*mongo.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}),
*mongo.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}),
*mongo.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}),
*mongo.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}),
*mongo.NewIndex("employee_count_idx", bson.D{{Key: "employee_count", Value: 1}}),
*mongo.NewIndex("annual_revenue_idx", bson.D{{Key: "annual_revenue", Value: 1}}),
*mongo.NewIndex("founded_year_idx", bson.D{{Key: "founded_year", Value: 1}}),
*mongo.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
// Tag indexes for efficient search
*mongo.NewIndex("cpv_codes_idx", bson.D{{Key: "tags.cpv_codes", Value: 1}}),
*mongo.NewIndex("categories_idx", bson.D{{Key: "tags.categories", Value: 1}}),
*mongo.NewIndex("keywords_idx", bson.D{{Key: "tags.keywords", Value: 1}}),
*mongo.NewIndex("specializations_idx", bson.D{{Key: "tags.specializations", Value: 1}}),
*mongo.NewIndex("certifications_idx", bson.D{{Key: "tags.certifications", Value: 1}}),
// Text index for search
*mongo.CreateTextIndex("search_idx", "name", "description", "industry"),
indexes := []orm.Index{
*orm.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
}
// Create indexes
@@ -80,7 +48,7 @@ func NewCompanyRepository(mongoManager *mongo.ConnectionManager, logger logger.L
}
// Create ORM repository
ormRepo := mongo.NewRepository[Company](mongoManager.GetCollection("companies"), logger)
ormRepo := orm.NewRepository[Company](mongoManager.GetCollection("companies"), logger)
return &companyRepository{
ormRepo: ormRepo,
@@ -93,7 +61,6 @@ func (r *companyRepository) Create(ctx context.Context, company *Company) error
// Set created/updated timestamps using Unix timestamps
now := time.Now().Unix()
company.SetCreatedAt(now)
company.SetUpdatedAt(now)
// Use ORM to create company
err := r.ormRepo.Create(ctx, company)
@@ -119,7 +86,7 @@ func (r *companyRepository) Create(ctx context.Context, company *Company) error
func (r *companyRepository) GetByID(ctx context.Context, id string) (*Company, error) {
company, err := r.ormRepo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("company not found")
}
r.logger.Error("Failed to get company by ID", map[string]interface{}{
@@ -132,108 +99,43 @@ func (r *companyRepository) GetByID(ctx context.Context, id string) (*Company, e
return company, nil
}
// GetByName retrieves a company by name
func (r *companyRepository) GetByName(ctx context.Context, name string) (*Company, error) {
filter := bson.M{"name": name}
company, err := r.ormRepo.FindOne(ctx, filter)
// GetByIDs retrieves companies by IDs
func (r *companyRepository) GetByIDs(ctx context.Context, ids []string) ([]Company, error) {
companies, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": ids}}, orm.NewPaginationBuilder().Build())
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
return nil, errors.New("company not found")
}
r.logger.Error("Failed to get company by name", map[string]interface{}{
"error": err.Error(),
"name": name,
})
return nil, err
}
return companies.Items, nil
}
// GetByName retrieves a company by name
func (r *companyRepository) GetByName(ctx context.Context, name string) (*Company, error) {
company, err := r.ormRepo.FindOne(ctx, bson.M{"name": name})
if err != nil {
return nil, err
}
return company, nil
}
// GetByRegistrationNumber retrieves a company by registration number
func (r *companyRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) {
filter := bson.M{"registration_number": registrationNumber}
company, err := r.ormRepo.FindOne(ctx, filter)
company, err := r.ormRepo.FindOne(ctx, bson.M{"registration_number": registrationNumber})
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
return nil, errors.New("company not found")
}
r.logger.Error("Failed to get company by registration number", map[string]interface{}{
"error": err.Error(),
"registration_number": registrationNumber,
})
return nil, err
}
return company, nil
}
// GetByTaxID retrieves a company by tax ID
func (r *companyRepository) GetByTaxID(ctx context.Context, taxID string) (*Company, error) {
filter := bson.M{"tax_id": taxID}
company, err := r.ormRepo.FindOne(ctx, filter)
company, err := r.ormRepo.FindOne(ctx, bson.M{"tax_id": taxID})
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
return nil, errors.New("company not found")
}
r.logger.Error("Failed to get company by tax ID", map[string]interface{}{
"error": err.Error(),
"tax_id": taxID,
})
return nil, err
}
return company, nil
}
// GetByIDs retrieves multiple companies by their IDs
func (r *companyRepository) GetByIDs(ctx context.Context, ids []string) ([]*Company, error) {
if len(ids) == 0 {
return []*Company{}, nil
}
// Convert string IDs to ObjectIDs for MongoDB query
objectIDs := make([]interface{}, len(ids))
for i, id := range ids {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
r.logger.Warn("Invalid company ID format", map[string]interface{}{
"id": id,
"error": err.Error(),
})
continue
}
objectIDs[i] = objectID
}
if len(objectIDs) == 0 {
return []*Company{}, nil
}
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
companies, err := r.ormRepo.FindMany(ctx, filter, len(ids))
if err != nil {
r.logger.Error("Failed to get companies by IDs", map[string]interface{}{
"error": err.Error(),
"ids": ids,
})
return nil, err
}
// Convert to pointer slice
result := make([]*Company, len(companies))
for i := range companies {
result[i] = &companies[i]
}
r.logger.Info("Retrieved companies by IDs", map[string]interface{}{
"requested_count": len(ids),
"found_count": len(result),
})
return result, nil
}
// Update updates a company
func (r *companyRepository) Update(ctx context.Context, company *Company) error {
// Set updated timestamp using Unix timestamp
@@ -257,20 +159,16 @@ func (r *companyRepository) Update(ctx context.Context, company *Company) error
return nil
}
// Delete deletes a company (soft delete by setting status to inactive)
// Delete deletes a company
func (r *companyRepository) Delete(ctx context.Context, id string) error {
// Get company first
company, err := r.GetByID(ctx, id)
_, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Update status to inactive
company.Status = CompanyStatusInactive
company.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, company)
err = r.ormRepo.Delete(ctx, id)
if err != nil {
r.logger.Error("Failed to delete company", map[string]interface{}{
"error": err.Error(),
@@ -286,246 +184,122 @@ func (r *companyRepository) Delete(ctx context.Context, id string) error {
return nil
}
// List retrieves companies with pagination
func (r *companyRepository) List(ctx context.Context, limit, offset int) ([]*Company, error) {
// Build pagination
pagination := mongo.NewPaginationBuilder().
Limit(limit).
Skip(offset).
SortDesc("created_at").
Build()
// Only active companies by default
filter := bson.M{"status": bson.M{"$ne": CompanyStatusInactive}}
// Use ORM to find companies
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
if err != nil {
r.logger.Error("Failed to list companies", map[string]interface{}{
"error": err.Error(),
"limit": limit,
"offset": offset,
})
return nil, err
}
// Convert []Company to []*Company
companies := make([]*Company, len(result.Items))
for i := range result.Items {
companies[i] = &result.Items[i]
}
return companies, nil
}
// Search retrieves companies with search and filters
func (r *companyRepository) Search(ctx context.Context, search string, companyType *string, status *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, cpvCodes []string, categories []string, keywords []string, specializations []string, employeeCountMin *int, employeeCountMax *int, annualRevenueMin *float64, annualRevenueMax *float64, foundedYearMin *int, foundedYearMax *int, limit, offset int, sortBy, sortOrder string) ([]*Company, error) {
func (r *companyRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Company, int64, error) {
// Build filter
filter := bson.M{}
if search != "" {
filter["$text"] = bson.M{"$search": search}
if form.Search != nil {
filter["$text"] = bson.M{"$search": *form.Search}
}
if companyType != nil {
filter["type"] = *companyType
if form.Type != nil {
filter["type"] = *form.Type
}
if status != nil {
filter["status"] = *status
if form.Status != nil {
filter["status"] = *form.Status
}
if industry != nil {
filter["industry"] = *industry
if form.Industry != nil {
filter["industry"] = *form.Industry
}
if isVerified != nil {
filter["is_verified"] = *isVerified
if form.IsVerified != nil {
filter["is_verified"] = *form.IsVerified
}
if isCompliant != nil {
filter["is_compliant"] = *isCompliant
if form.IsCompliant != nil {
filter["is_compliant"] = *form.IsCompliant
}
if language != nil {
filter["language"] = *language
if form.Language != nil {
filter["language"] = *form.Language
}
if currency != nil {
filter["currency"] = *currency
if form.Currency != nil {
filter["currency"] = *form.Currency
}
// Tag filters
if len(cpvCodes) > 0 {
filter["tags.cpv_codes"] = bson.M{"$in": cpvCodes}
if len(form.CPVCodes) > 0 {
filter["tags.cpv_codes"] = bson.M{"$in": form.CPVCodes}
}
if len(categories) > 0 {
filter["tags.categories"] = bson.M{"$in": categories}
if len(form.Categories) > 0 {
filter["tags.categories"] = bson.M{"$in": form.Categories}
}
if len(keywords) > 0 {
filter["tags.keywords"] = bson.M{"$in": keywords}
if len(form.Keywords) > 0 {
filter["tags.keywords"] = bson.M{"$in": form.Keywords}
}
if len(specializations) > 0 {
filter["tags.specializations"] = bson.M{"$in": specializations}
if len(form.Specializations) > 0 {
filter["tags.specializations"] = bson.M{"$in": form.Specializations}
}
// Range filters
if employeeCountMin != nil || employeeCountMax != nil {
if form.EmployeeCountMin != nil || form.EmployeeCountMax != nil {
rangeFilter := bson.M{}
if employeeCountMin != nil {
rangeFilter["$gte"] = *employeeCountMin
if form.EmployeeCountMin != nil {
rangeFilter["$gte"] = *form.EmployeeCountMin
}
if employeeCountMax != nil {
rangeFilter["$lte"] = *employeeCountMax
if form.EmployeeCountMax != nil {
rangeFilter["$lte"] = *form.EmployeeCountMax
}
filter["employee_count"] = rangeFilter
}
if annualRevenueMin != nil || annualRevenueMax != nil {
if form.AnnualRevenueMin != nil || form.AnnualRevenueMax != nil {
rangeFilter := bson.M{}
if annualRevenueMin != nil {
rangeFilter["$gte"] = *annualRevenueMin
if form.AnnualRevenueMin != nil {
rangeFilter["$gte"] = *form.AnnualRevenueMin
}
if annualRevenueMax != nil {
rangeFilter["$lte"] = *annualRevenueMax
if form.AnnualRevenueMax != nil {
rangeFilter["$lte"] = *form.AnnualRevenueMax
}
filter["annual_revenue"] = rangeFilter
}
if foundedYearMin != nil || foundedYearMax != nil {
if form.FoundedYearMin != nil || form.FoundedYearMax != nil {
rangeFilter := bson.M{}
if foundedYearMin != nil {
rangeFilter["$gte"] = *foundedYearMin
if form.FoundedYearMin != nil {
rangeFilter["$gte"] = *form.FoundedYearMin
}
if foundedYearMax != nil {
rangeFilter["$lte"] = *foundedYearMax
if form.FoundedYearMax != nil {
rangeFilter["$lte"] = *form.FoundedYearMax
}
filter["founded_year"] = rangeFilter
}
// Build sort
sort := "created_at"
if pagination.SortBy != "" {
sort = pagination.SortBy
}
sortOrder := "desc"
if pagination.SortOrder != "" {
sortOrder = pagination.SortOrder
}
// Build pagination
pagination := mongo.NewPaginationBuilder().
Limit(limit).
Skip(offset)
paginationBuilder := orm.NewPaginationBuilder().
Limit(pagination.Limit).
Skip(pagination.Offset).
SortBy(sort, sortOrder).
Build()
// Set sort
if sortBy != "" {
if sortOrder == "desc" {
pagination.SortDesc(sortBy)
} else {
pagination.SortAsc(sortBy)
}
} else {
pagination.SortDesc("created_at") // Default sort
}
// Use ORM to find companies
result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
// Use ORM to find users
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
if err != nil {
r.logger.Error("Failed to search companies", map[string]interface{}{
"error": err.Error(),
"search": search,
})
return nil, err
}
// Convert []Company to []*Company
companies := make([]*Company, len(result.Items))
for i := range result.Items {
companies[i] = &result.Items[i]
}
return companies, nil
}
// Count counts all companies
func (r *companyRepository) Count(ctx context.Context) (int64, error) {
filter := bson.M{"status": bson.M{"$ne": CompanyStatusInactive}}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count companies", map[string]interface{}{
r.logger.Error("Failed to list users", map[string]interface{}{
"error": err.Error(),
})
return 0, err
return nil, 0, err
}
return count, nil
}
// CountByType counts companies by type
func (r *companyRepository) CountByType(ctx context.Context, companyType CompanyType) (int64, error) {
filter := bson.M{
"type": companyType,
"status": bson.M{"$ne": CompanyStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count companies by type", map[string]interface{}{
"error": err.Error(),
"company_type": companyType,
})
return 0, err
}
return count, nil
}
// CountByStatus counts companies by status
func (r *companyRepository) CountByStatus(ctx context.Context, status CompanyStatus) (int64, error) {
filter := bson.M{"status": status}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count companies by status", map[string]interface{}{
"error": err.Error(),
"status": status,
})
return 0, err
}
return count, nil
}
// CountByIndustry counts companies by industry
func (r *companyRepository) CountByIndustry(ctx context.Context, industry string) (int64, error) {
filter := bson.M{
"industry": industry,
"status": bson.M{"$ne": CompanyStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count companies by industry", map[string]interface{}{
"error": err.Error(),
"industry": industry,
})
return 0, err
}
return count, nil
}
// CountVerified counts verified companies
func (r *companyRepository) CountVerified(ctx context.Context) (int64, error) {
filter := bson.M{
"is_verified": true,
"status": bson.M{"$ne": CompanyStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count verified companies", map[string]interface{}{
"error": err.Error(),
})
return 0, err
}
return count, nil
return result.Items, result.TotalCount, nil
}
// UpdateStatus updates company status
@@ -591,226 +365,3 @@ func (r *companyRepository) UpdateVerification(ctx context.Context, id string, i
return nil
}
// UpdateTags updates company tags
func (r *companyRepository) UpdateTags(ctx context.Context, id string, tags *CompanyTags) error {
// Get company first
company, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Update tags
company.Tags = tags
company.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, company)
if err != nil {
r.logger.Error("Failed to update company tags", map[string]interface{}{
"error": err.Error(),
"company_id": id,
})
return err
}
r.logger.Info("Company tags updated successfully", map[string]interface{}{
"company_id": id,
})
return nil
}
// AddTags adds tags to a company
func (r *companyRepository) AddTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error {
// Get company first
company, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Initialize tags if nil
if company.Tags == nil {
company.Tags = &CompanyTags{}
}
// Add tags (avoiding duplicates)
company.Tags.CPVCodes = r.addUniqueStrings(company.Tags.CPVCodes, cpvCodes)
company.Tags.Categories = r.addUniqueStrings(company.Tags.Categories, categories)
company.Tags.Keywords = r.addUniqueStrings(company.Tags.Keywords, keywords)
company.Tags.Specializations = r.addUniqueStrings(company.Tags.Specializations, specializations)
company.Tags.Certifications = r.addUniqueStrings(company.Tags.Certifications, certifications)
company.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, company)
if err != nil {
r.logger.Error("Failed to add tags to company", map[string]interface{}{
"error": err.Error(),
"company_id": id,
})
return err
}
r.logger.Info("Tags added to company successfully", map[string]interface{}{
"company_id": id,
})
return nil
}
// RemoveTags removes tags from a company
func (r *companyRepository) RemoveTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error {
// Get company first
company, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Initialize tags if nil
if company.Tags == nil {
company.Tags = &CompanyTags{}
}
// Remove tags
company.Tags.CPVCodes = r.removeStrings(company.Tags.CPVCodes, cpvCodes)
company.Tags.Categories = r.removeStrings(company.Tags.Categories, categories)
company.Tags.Keywords = r.removeStrings(company.Tags.Keywords, keywords)
company.Tags.Specializations = r.removeStrings(company.Tags.Specializations, specializations)
company.Tags.Certifications = r.removeStrings(company.Tags.Certifications, certifications)
company.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, company)
if err != nil {
r.logger.Error("Failed to remove tags from company", map[string]interface{}{
"error": err.Error(),
"company_id": id,
})
return err
}
r.logger.Info("Tags removed from company successfully", map[string]interface{}{
"company_id": id,
})
return nil
}
// GetCompanyStats returns company statistics
func (r *companyRepository) GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) {
// This would typically use aggregation pipelines for better performance
// For now, we'll use basic counts
totalCompanies, _ := r.Count(ctx)
activeCompanies, _ := r.CountByStatus(ctx, CompanyStatusActive)
verifiedCompanies, _ := r.CountVerified(ctx)
// For this implementation, we'll return basic stats
// In a real implementation, you'd use MongoDB aggregation pipelines
stats := &CompanyStatsResponse{
TotalCompanies: totalCompanies,
ActiveCompanies: activeCompanies,
VerifiedCompanies: verifiedCompanies,
CompaniesByType: make(map[string]int64),
CompaniesByIndustry: make(map[string]int64),
CompaniesByStatus: make(map[string]int64),
TopCategories: []CategoryCount{},
TopCPVCodes: []CPVCodeCount{},
}
return stats, nil
}
// SearchByTags searches companies by tags
func (r *companyRepository) SearchByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) {
filter := bson.M{}
// Build tag filters
var tagFilters []bson.M
if len(cpvCodes) > 0 {
tagFilters = append(tagFilters, bson.M{"tags.cpv_codes": bson.M{"$in": cpvCodes}})
}
if len(categories) > 0 {
tagFilters = append(tagFilters, bson.M{"tags.categories": bson.M{"$in": categories}})
}
if len(keywords) > 0 {
tagFilters = append(tagFilters, bson.M{"tags.keywords": bson.M{"$in": keywords}})
}
if len(specializations) > 0 {
tagFilters = append(tagFilters, bson.M{"tags.specializations": bson.M{"$in": specializations}})
}
if len(tagFilters) > 0 {
filter["$or"] = tagFilters
}
// Only active companies
filter["status"] = bson.M{"$ne": CompanyStatusInactive}
// Build pagination
pagination := mongo.NewPaginationBuilder().
Limit(limit).
Skip(offset).
SortDesc("created_at").
Build()
// Use ORM to find companies
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
if err != nil {
r.logger.Error("Failed to search companies by tags", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
// Convert []Company to []*Company
companies := make([]*Company, len(result.Items))
for i := range result.Items {
companies[i] = &result.Items[i]
}
return companies, nil
}
// Helper functions for tag management
func (r *companyRepository) addUniqueStrings(existing []string, new []string) []string {
existingMap := make(map[string]bool)
for _, s := range existing {
existingMap[s] = true
}
result := make([]string, len(existing))
copy(result, existing)
for _, s := range new {
if !existingMap[s] {
result = append(result, s)
existingMap[s] = true
}
}
return result
}
func (r *companyRepository) removeStrings(existing []string, toRemove []string) []string {
removeMap := make(map[string]bool)
for _, s := range toRemove {
removeMap[s] = true
}
var result []string
for _, s := range existing {
if !removeMap[s] {
result = append(result, s)
}
}
return result
}