flamingo.me/flamingo-commerce/v3@v3.11.0/product/interfaces/templatefunctions/findProducts.go (about)

     1  package templatefunctions
     2  
     3  import (
     4  	"context"
     5  	"log"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"flamingo.me/flamingo-commerce/v3/search/utils"
    10  
    11  	"flamingo.me/pugtemplate/pugjs"
    12  
    13  	"flamingo.me/flamingo/v3/framework/web"
    14  
    15  	"flamingo.me/flamingo-commerce/v3/search/domain"
    16  
    17  	"flamingo.me/flamingo-commerce/v3/product/application"
    18  	searchApplication "flamingo.me/flamingo-commerce/v3/search/application"
    19  )
    20  
    21  type (
    22  	// FindProducts is exported as a template function
    23  	FindProducts struct {
    24  		ProductSearchService    *application.ProductSearchService `inject:""`
    25  		DefaultPaginationConfig *utils.PaginationConfig           `inject:""`
    26  	}
    27  
    28  	// filterProcessing to modifiy the searchRequest and the result depending on black-/whitelist
    29  	filterProcessing struct {
    30  		buildSearchRequest searchApplication.SearchRequest
    31  		whiteList          []string
    32  		blackList          []string
    33  	}
    34  )
    35  
    36  // Func defines the find products function
    37  func (tf *FindProducts) Func(ctx context.Context) interface{} {
    38  
    39  	/*
    40  		widgetName - used to namespace widget - in case we need pagination
    41  		config - map with certain keys - used to specify the searchRequest better
    42  	*/
    43  	return func(namespace string, configs ...pugjs.Map) *application.SearchResult {
    44  		searchConfig := make(map[string]string)
    45  		filterConstrains := make(map[string]string)
    46  		keyValueFilters := make(map[string][]string)
    47  
    48  		for configKey, config := range configs {
    49  			switch configKey {
    50  			case 0:
    51  				searchConfig = config.AsStringMap()
    52  			case 1:
    53  				for key, value := range config.AsStringIfaceMap() {
    54  					switch value := value.(type) {
    55  					case pugjs.String:
    56  						keyValueFilters[key] = []string{value.String()}
    57  					case []string:
    58  						keyValueFilters[key] = value
    59  					case []interface{}:
    60  						var filterValues []string
    61  						for _, sv := range value {
    62  							if string, ok := sv.(string); ok {
    63  								filterValues = append(filterValues, string)
    64  							}
    65  						}
    66  						if len(filterValues) > 0 {
    67  							keyValueFilters[key] = filterValues
    68  						}
    69  					}
    70  				}
    71  
    72  			case 2:
    73  				filterConstrains = config.AsStringMap()
    74  			}
    75  		}
    76  
    77  		filterProcessing := newFilterProcessing(web.RequestFromContext(ctx), namespace, searchConfig, keyValueFilters, filterConstrains, tf.DefaultPaginationConfig)
    78  
    79  		result, e := tf.ProductSearchService.Find(ctx, &filterProcessing.buildSearchRequest)
    80  		if e != nil {
    81  			log.Printf("Error: product.interfaces.templatefunc %v", e)
    82  			return &application.SearchResult{}
    83  		}
    84  		return filterProcessing.modifyResult(result)
    85  	}
    86  }
    87  
    88  func newFilterProcessing(request *web.Request, namespace string, searchConfig map[string]string, keyValueFilters map[string][]string, filterConstrains map[string]string, paginationConfig *utils.PaginationConfig) filterProcessing {
    89  	var filterProcessing filterProcessing
    90  
    91  	// 1- set the originalSearchRequest from given searchConfig and keyValueFilters
    92  	var searchRequest = searchApplication.SearchRequest{
    93  		SortDirection:    searchConfig["sortDirection"],
    94  		SortBy:           searchConfig["sortBy"],
    95  		Query:            searchConfig["query"],
    96  		PaginationConfig: paginationConfig,
    97  	}
    98  
    99  	if searchRequest.PaginationConfig != nil {
   100  		searchRequest.PaginationConfig.NameSpace = namespace
   101  	}
   102  
   103  	pageSize, err := strconv.Atoi(searchConfig["pageSize"])
   104  	if err == nil {
   105  		searchRequest.PageSize = pageSize
   106  	}
   107  
   108  	searchRequest.AddAdditionalFilters(domain.NewKeyValueFilters(keyValueFilters)...)
   109  
   110  	// Set blackList and whiteList, also trim spaces
   111  	filterProcessing.blackList = strings.Split(filterConstrains["blackList"], ",")
   112  	for i := range filterProcessing.blackList {
   113  		filterProcessing.blackList[i] = strings.TrimSpace(filterProcessing.blackList[i])
   114  	}
   115  	if filterProcessing.blackList[0] == "" {
   116  		filterProcessing.blackList = nil
   117  	}
   118  	filterProcessing.whiteList = strings.Split(filterConstrains["whiteList"], ",")
   119  	for i := range filterProcessing.whiteList {
   120  		filterProcessing.whiteList[i] = strings.TrimSpace(filterProcessing.whiteList[i])
   121  	}
   122  	if filterProcessing.whiteList[0] == "" {
   123  		filterProcessing.whiteList = nil
   124  	}
   125  
   126  	// 2 - Use the url parameters to modify the filters:
   127  	for k, v := range request.QueryAll() {
   128  		if !strings.HasPrefix(k, namespace) {
   129  			continue
   130  		}
   131  		splitted := strings.SplitN(k, ".", 2)
   132  		if (namespace != "" && len(splitted) < 2) || (namespace == "" && len(splitted) > 1) {
   133  			continue
   134  		}
   135  
   136  		var filterKey string
   137  		if namespace != "" {
   138  			filterKey = splitted[1]
   139  		} else {
   140  			filterKey = splitted[0]
   141  		}
   142  
   143  		if filterKey == "page" && len(v) == 1 {
   144  			page, _ := strconv.ParseInt(v[0], 10, 64)
   145  			searchRequest.AddAdditionalFilter(domain.NewPaginationPageFilter(int(page)))
   146  			continue
   147  		}
   148  
   149  		if filterProcessing.isAllowed(filterKey) {
   150  			searchRequest.SetAdditionalFilter(domain.NewKeyValueFilter(filterKey, v))
   151  		}
   152  	}
   153  	filterProcessing.buildSearchRequest = searchRequest
   154  	return filterProcessing
   155  }
   156  
   157  // modifyResult - while check the result against the blacklist/whitelist
   158  func (f *filterProcessing) modifyResult(result *application.SearchResult) *application.SearchResult {
   159  	var newFacetCollection domain.FacetCollection = make(map[string]domain.Facet)
   160  	for k, facet := range result.Facets {
   161  		if f.isAllowed(k) {
   162  			newFacetCollection[k] = facet
   163  		}
   164  	}
   165  	result.Facets = newFacetCollection
   166  
   167  	var newSelectedFacets []domain.Facet
   168  	for _, facet := range result.SearchMeta.SelectedFacets {
   169  		if f.isAllowed(facet.Name) {
   170  			newSelectedFacets = append(newSelectedFacets, facet)
   171  		}
   172  	}
   173  	result.SearchMeta.SelectedFacets = newSelectedFacets
   174  
   175  	return result
   176  }
   177  
   178  // isAllowed - checks the given key against the defined whitelist and blacklist (whitelist preferred)
   179  func (f *filterProcessing) isAllowed(key string) bool {
   180  	if len(f.whiteList) > 0 {
   181  		for _, wl := range f.whiteList {
   182  			if wl == key {
   183  				return true
   184  			}
   185  		}
   186  		return false
   187  	} else if len(f.blackList) > 0 {
   188  		for _, wl := range f.blackList {
   189  			ert := wl == key
   190  			if ert {
   191  				return false
   192  			}
   193  		}
   194  	}
   195  	return true
   196  }