flamingo.me/flamingo-commerce/v3@v3.11.0/product/infrastructure/fake/searchservice.go (about)

     1  package fake
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"os"
     7  	"strconv"
     8  
     9  	searchDomain "flamingo.me/flamingo-commerce/v3/search/domain"
    10  
    11  	"flamingo.me/flamingo-commerce/v3/product/domain"
    12  )
    13  
    14  type (
    15  	// SearchService is just mocking stuff
    16  	SearchService struct {
    17  		productService         *ProductService
    18  		liveSearchJSON         string
    19  		categoryFacetItemsJSON string
    20  	}
    21  	liveSearchData struct {
    22  		Marketplacecodes []string                  `json:"marketplacecodes"`
    23  		Sugestions       []searchDomain.Suggestion `json:"sugestions"`
    24  		Promotions       []searchDomain.Promotion  `json:"promotions"`
    25  		Actions          []searchDomain.Action     `json:"actions"`
    26  	}
    27  )
    28  
    29  // Inject dependencies
    30  func (s *SearchService) Inject(
    31  	productService *ProductService,
    32  	cfg *struct {
    33  		LiveSearchJSON         string `inject:"config:commerce.product.fakeservice.jsonTestDataLiveSearch,optional"`
    34  		CategoryFacetItemsJSON string `inject:"config:commerce.product.fakeservice.jsonTestDataCategoryFacetItems,optional"`
    35  	},
    36  ) *SearchService {
    37  	s.productService = productService
    38  	if cfg != nil {
    39  		s.liveSearchJSON = cfg.LiveSearchJSON
    40  		s.categoryFacetItemsJSON = cfg.CategoryFacetItemsJSON
    41  	}
    42  
    43  	return s
    44  }
    45  
    46  // Search returns Products based on given Filters
    47  func (s *SearchService) Search(ctx context.Context, filters ...searchDomain.Filter) (*domain.SearchResult, error) {
    48  	hits := s.findProducts(ctx, filters, s.productService.GetMarketPlaceCodes())
    49  	currentPage := s.findCurrentPage(filters)
    50  	facets, selectedFacets := s.createFacets(filters)
    51  
    52  	documents := make([]searchDomain.Document, len(hits))
    53  	for i, hit := range hits {
    54  		documents[i] = hit
    55  	}
    56  
    57  	return &domain.SearchResult{
    58  		Result: searchDomain.Result{
    59  			SearchMeta: searchDomain.SearchMeta{
    60  				Query:          "",
    61  				OriginalQuery:  "",
    62  				Page:           currentPage,
    63  				NumPages:       10,
    64  				NumResults:     len(hits),
    65  				SelectedFacets: selectedFacets,
    66  				SortOptions:    s.findSorts(filters),
    67  			},
    68  			Hits:       documents,
    69  			Suggestion: []searchDomain.Suggestion{},
    70  			Facets:     facets,
    71  		},
    72  		Hits: hits,
    73  	}, nil
    74  }
    75  
    76  func (s *SearchService) livesearch(ctx context.Context, query string) (*domain.SearchResult, error) {
    77  	data := make(map[string]liveSearchData)
    78  
    79  	fileContent, err := os.ReadFile(s.liveSearchJSON)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	err = json.Unmarshal(fileContent, &data)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	liveSearchData := data[query]
    90  
    91  	hits := s.findProducts(ctx, nil, liveSearchData.Marketplacecodes)
    92  	documents := make([]searchDomain.Document, len(hits))
    93  	for i, hit := range hits {
    94  		documents[i] = hit
    95  	}
    96  
    97  	return &domain.SearchResult{
    98  		Result: searchDomain.Result{
    99  			SearchMeta: searchDomain.SearchMeta{
   100  				Page:       1,
   101  				NumPages:   1,
   102  				NumResults: len(hits),
   103  			},
   104  			Hits:       documents,
   105  			Suggestion: liveSearchData.Sugestions,
   106  			Promotions: liveSearchData.Promotions,
   107  			Actions:    liveSearchData.Actions,
   108  		},
   109  		Hits: hits,
   110  	}, nil
   111  }
   112  
   113  // SearchBy returns Products prefiltered by the given attribute (also based on additional given Filters)
   114  func (s *SearchService) SearchBy(ctx context.Context, attribute string, _ []string, filters ...searchDomain.Filter) (*domain.SearchResult, error) {
   115  	if attribute == "livesearch" && s.liveSearchJSON != "" {
   116  		var query string
   117  		for _, f := range filters {
   118  			if qf, ok := f.(*searchDomain.QueryFilter); ok {
   119  				_, q := qf.Value()
   120  				query = q[0]
   121  				break
   122  			}
   123  		}
   124  		return s.livesearch(ctx, query)
   125  	}
   126  
   127  	return s.Search(ctx, filters...)
   128  }
   129  
   130  func (s *SearchService) findProducts(ctx context.Context, filters []searchDomain.Filter, marketPlaceCodes []string) []domain.BasicProduct {
   131  	products := make([]domain.BasicProduct, 0)
   132  
   133  	// - try finding product by marketPlaceCode given in query or return nothing if query is no-results
   134  	if query, found := s.filterValue(filters, "q"); found {
   135  		if len(query) > 0 {
   136  			if query[0] == "no-results" {
   137  				return products
   138  			}
   139  			product, _ := s.productService.Get(ctx, query[0])
   140  			if product != nil {
   141  				products = append(products, product)
   142  			}
   143  		}
   144  	}
   145  
   146  	// - get default products
   147  	if len(products) == 0 {
   148  		for _, marketPlaceCode := range marketPlaceCodes {
   149  			product, _ := s.productService.Get(ctx, marketPlaceCode)
   150  			if product != nil {
   151  				products = append(products, product)
   152  			}
   153  		}
   154  	}
   155  
   156  	return products
   157  }
   158  
   159  func (s *SearchService) findCurrentPage(filters []searchDomain.Filter) int {
   160  	currentPage := 1
   161  
   162  	if page, found := s.filterValue(filters, "page"); found {
   163  		if page, err := strconv.Atoi(page[0]); err == nil {
   164  			currentPage = page
   165  		}
   166  	}
   167  
   168  	return currentPage
   169  }
   170  
   171  func (s *SearchService) findSorts(filters []searchDomain.Filter) []searchDomain.SortOption {
   172  	result := make([]searchDomain.SortOption, 0)
   173  	// first check if the searchDomain.SortFilter is given
   174  	for _, v := range filters {
   175  		if filter, ok := v.(*searchDomain.SortFilter); ok {
   176  			result = append(result, searchDomain.SortOption{
   177  				Label:        filter.Field(),
   178  				Field:        filter.Field(),
   179  				SelectedAsc:  !filter.Descending(),
   180  				SelectedDesc: filter.Descending(),
   181  			})
   182  		}
   183  	}
   184  
   185  	return result
   186  }
   187  
   188  func (s *SearchService) createFacets(filters []searchDomain.Filter) (map[string]searchDomain.Facet, []searchDomain.Facet) {
   189  	selectedFacets := make([]searchDomain.Facet, 0)
   190  
   191  	categoryFilterValue := s.categoryFilterValue(filters)
   192  
   193  	facets := map[string]searchDomain.Facet{
   194  		"brandCode": {
   195  			Type:  searchDomain.ListFacet,
   196  			Name:  "brandCode",
   197  			Label: "Brand",
   198  			Items: []*searchDomain.FacetItem{{
   199  				Label:    "Apple",
   200  				Value:    "apple",
   201  				Active:   false,
   202  				Selected: false,
   203  				Count:    2,
   204  			}},
   205  			Position: 0,
   206  		},
   207  
   208  		"retailerCode": {
   209  			Type:  searchDomain.ListFacet,
   210  			Name:  "retailerCode",
   211  			Label: "Retailer",
   212  			Items: []*searchDomain.FacetItem{{
   213  				Label:    "Test Retailer",
   214  				Value:    "retailer",
   215  				Active:   false,
   216  				Selected: false,
   217  				Count:    2,
   218  			}},
   219  			Position: 0,
   220  		},
   221  
   222  		"categoryCodes": s.createCategoryFacet(categoryFilterValue),
   223  	}
   224  
   225  	if s.hasFilterWithValue(filters, "brandCode", "apple") {
   226  		facets["brandCode"].Items[0].Active = true
   227  		facets["brandCode"].Items[0].Selected = true
   228  		selectedFacets = append(selectedFacets, facets["brandCode"])
   229  	}
   230  
   231  	if s.hasFilterWithValue(filters, "retailerCode", "retailer") {
   232  		facets["retailerCode"].Items[0].Active = true
   233  		facets["retailerCode"].Items[0].Selected = true
   234  		selectedFacets = append(selectedFacets, facets["retailerCode"])
   235  	}
   236  
   237  	if categoryFilterValue != "" {
   238  		selectedFacets = append(selectedFacets, facets["categoryCodes"])
   239  	}
   240  
   241  	return facets, selectedFacets
   242  }
   243  
   244  func (s *SearchService) filterValue(filters []searchDomain.Filter, key string) ([]string, bool) {
   245  	for _, filter := range filters {
   246  		filterKey, filterValues := filter.Value()
   247  		if filterKey == key {
   248  			return filterValues, true
   249  		}
   250  	}
   251  
   252  	return []string{}, false
   253  }
   254  
   255  func (s *SearchService) hasFilterWithValue(filters []searchDomain.Filter, key string, value string) bool {
   256  	if filterValues, found := s.filterValue(filters, key); found {
   257  		for _, filterValue := range filterValues {
   258  			if value == filterValue {
   259  				return true
   260  			}
   261  		}
   262  	}
   263  
   264  	return false
   265  }
   266  
   267  func (s *SearchService) categoryFilterValue(filters []searchDomain.Filter) string {
   268  	for _, filter := range filters {
   269  		filterKey, filterValues := filter.Value()
   270  		switch filterKey {
   271  		case "category", "categoryCodes":
   272  			if len(filterValues) > 0 {
   273  				return filterValues[0]
   274  			}
   275  		}
   276  	}
   277  
   278  	return ""
   279  }
   280  
   281  func (s *SearchService) createCategoryFacet(selectedCategory string) searchDomain.Facet {
   282  	return searchDomain.Facet{
   283  		Type:     searchDomain.TreeFacet,
   284  		Name:     "categoryCodes",
   285  		Label:    "Category",
   286  		Items:    s.createCategoryFacetItems(selectedCategory),
   287  		Position: 0,
   288  	}
   289  }
   290  
   291  func (s *SearchService) createCategoryFacetItems(selectedCategory string) []*searchDomain.FacetItem {
   292  	items, err := loadCategoryFacetItems(s.categoryFacetItemsJSON)
   293  
   294  	if err != nil {
   295  		return nil
   296  	}
   297  
   298  	selectFacetItems(selectedCategory, items)
   299  	return items
   300  }
   301  
   302  func selectFacetItems(selectedCategory string, items []*searchDomain.FacetItem) bool {
   303  	for _, item := range items {
   304  		if item.Value == selectedCategory {
   305  			item.Active = true
   306  			item.Selected = true
   307  			return true
   308  		}
   309  		childSelectedOrActive := selectFacetItems(selectedCategory, item.Items)
   310  		if childSelectedOrActive {
   311  			item.Active = true
   312  			return true
   313  		}
   314  	}
   315  	return false
   316  }