flamingo.me/flamingo-commerce/v3@v3.11.0/search/application/searchService.go (about)

     1  /*
     2  Package application in the search module provides a more explicit access to the domain searchservice, and combines the result with Paginations etc.
     3  The structs defined here can for example be used in the interface layers
     4  */
     5  package application
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"net/url"
    12  
    13  	"flamingo.me/flamingo-commerce/v3/search/domain"
    14  	"flamingo.me/flamingo-commerce/v3/search/utils"
    15  	"flamingo.me/flamingo/v3/framework/flamingo"
    16  	"flamingo.me/flamingo/v3/framework/web"
    17  )
    18  
    19  type (
    20  	// SearchService - Application service that offers a more explicit way to search for results - on top of the domain.ProductSearchService
    21  	SearchService struct {
    22  		searchService         domain.SearchService
    23  		paginationInfoFactory *utils.PaginationInfoFactory
    24  		defaultPageSize       float64
    25  		logger                flamingo.Logger
    26  	}
    27  
    28  	// SearchRequest is a simple DTO for the search query data
    29  	SearchRequest struct {
    30  		AdditionalFilter []domain.Filter
    31  		PageSize         int
    32  		Page             int
    33  		SortBy           string
    34  		SortDirection    string
    35  		Query            string
    36  		PaginationConfig *utils.PaginationConfig
    37  	}
    38  
    39  	// SearchResult is the DTO for the search result
    40  	SearchResult struct {
    41  		Hits           []domain.Document
    42  		SearchMeta     domain.SearchMeta
    43  		Facets         domain.FacetCollection
    44  		Suggestions    []domain.Suggestion
    45  		PaginationInfo utils.PaginationInfo
    46  	}
    47  )
    48  
    49  // Inject dependencies
    50  func (s *SearchService) Inject(
    51  	paginationInfoFactory *utils.PaginationInfoFactory,
    52  	logger flamingo.Logger,
    53  	optionals *struct {
    54  		SearchService   domain.SearchService `inject:",optional"`
    55  		DefaultPageSize float64              `inject:"config:commerce.pagination.defaultPageSize,optional"`
    56  	}) *SearchService {
    57  	s.paginationInfoFactory = paginationInfoFactory
    58  	s.logger = logger.
    59  		WithField(flamingo.LogKeyModule, "search").
    60  		WithField(flamingo.LogKeyCategory, "application.ProductSearchService")
    61  	if optionals != nil {
    62  		s.searchService = optionals.SearchService
    63  		s.defaultPageSize = optionals.DefaultPageSize
    64  	}
    65  	return s
    66  }
    67  
    68  // FindBy returns a SearchResult for the given Request
    69  func (s *SearchService) FindBy(ctx context.Context, documentType string, searchRequest SearchRequest) (*SearchResult, error) {
    70  	if s.searchService == nil {
    71  		return nil, errors.New("no searchservice available")
    72  	}
    73  	var currentURL *url.URL
    74  	request := web.RequestFromContext(ctx)
    75  	if request == nil {
    76  		currentURL = nil
    77  	} else {
    78  		currentURL = request.Request().URL
    79  	}
    80  
    81  	if searchRequest.PaginationConfig == nil {
    82  		searchRequest.PaginationConfig = s.paginationInfoFactory.DefaultConfig
    83  	}
    84  
    85  	// pageSize can either be set in the request, or we use the configured default or if nothing set we rely on the ProductSearchService later
    86  	pageSize := searchRequest.PageSize
    87  	if pageSize == 0 {
    88  		pageSize = int(s.defaultPageSize)
    89  	}
    90  
    91  	result, err := s.searchService.SearchFor(ctx, documentType, BuildFilters(searchRequest, pageSize)...)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// do a logical pageSize check - and log warning
    97  	//  10 pageSize * (3 pages* -1 ) + lastPageSize = 35 results*
    98  	if pageSize != 0 {
    99  		if err := result.SearchMeta.ValidatePageSize(pageSize); err != nil {
   100  			err = fmt.Errorf("the Searchservice seems to ignore pageSize filter, %w", err)
   101  			s.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "FindBy").Warn(err)
   102  		}
   103  	}
   104  
   105  	paginationInfo := utils.BuildWith(utils.CurrentResultInfos{
   106  		LastPage:   result.SearchMeta.NumPages,
   107  		TotalHits:  result.SearchMeta.NumResults,
   108  		PageSize:   searchRequest.PageSize,
   109  		ActivePage: result.SearchMeta.Page,
   110  	}, *searchRequest.PaginationConfig, currentURL)
   111  
   112  	return &SearchResult{
   113  		SearchMeta:     result.SearchMeta,
   114  		Facets:         result.Facets,
   115  		Suggestions:    result.Suggestion,
   116  		Hits:           result.Hits,
   117  		PaginationInfo: paginationInfo,
   118  	}, nil
   119  }
   120  
   121  // Find returns a Searchresult for all document types for the given Request
   122  func (s *SearchService) Find(ctx context.Context, searchRequest SearchRequest) (map[string]*SearchResult, error) {
   123  	if s.searchService == nil {
   124  		return nil, errors.New("no searchservice available")
   125  	}
   126  	var currentURL *url.URL
   127  	request := web.RequestFromContext(ctx)
   128  	if request == nil {
   129  		currentURL = nil
   130  	} else {
   131  		currentURL = request.Request().URL
   132  	}
   133  
   134  	if searchRequest.PaginationConfig == nil {
   135  		searchRequest.PaginationConfig = s.paginationInfoFactory.DefaultConfig
   136  	}
   137  
   138  	// pageSize can either be set in the request, or we use the configured default or if nothing set we rely on the ProductSearchService later
   139  	pageSize := searchRequest.PageSize
   140  	if pageSize == 0 {
   141  		pageSize = int(s.defaultPageSize)
   142  	}
   143  
   144  	result, err := s.searchService.Search(ctx, BuildFilters(searchRequest, pageSize)...)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	// do a logical pageSize check - and log warning
   150  	//  10 pageSize * (3 pages* -1 ) + lastPageSize = 35 results*
   151  	if pageSize != 0 {
   152  		for k, r := range result {
   153  			if err := r.SearchMeta.ValidatePageSize(pageSize); err != nil {
   154  				err = fmt.Errorf("the Searchservice seems to ignore pageSize filter for document type %q, %w", k, err)
   155  				s.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "Find").Warn(err)
   156  			}
   157  		}
   158  	}
   159  
   160  	searchResult := make(map[string]*SearchResult)
   161  
   162  	for k, r := range result {
   163  		paginationInfo := utils.BuildWith(utils.CurrentResultInfos{
   164  			LastPage:   r.SearchMeta.NumPages,
   165  			TotalHits:  r.SearchMeta.NumResults,
   166  			PageSize:   searchRequest.PageSize,
   167  			ActivePage: r.SearchMeta.Page,
   168  		}, *searchRequest.PaginationConfig, currentURL)
   169  
   170  		searchResult[k] = &SearchResult{
   171  			SearchMeta:     r.SearchMeta,
   172  			Facets:         r.Facets,
   173  			Suggestions:    r.Suggestion,
   174  			Hits:           r.Hits,
   175  			PaginationInfo: paginationInfo,
   176  		}
   177  	}
   178  
   179  	return searchResult, nil
   180  }
   181  
   182  // BuildFilters creates a slice of search filters from the request data
   183  func BuildFilters(request SearchRequest, defaultPageSize int) []domain.Filter {
   184  	var filters []domain.Filter
   185  	if request.Query != "" {
   186  		filters = append(filters, domain.NewQueryFilter(request.Query))
   187  	}
   188  
   189  	if request.Page != 0 {
   190  		filters = append(filters, domain.NewPaginationPageFilter(request.Page))
   191  	}
   192  
   193  	if request.PageSize != 0 {
   194  		filters = append(filters, domain.NewPaginationPageSizeFilter(request.PageSize))
   195  	} else if defaultPageSize != 0 {
   196  		filters = append(filters, domain.NewPaginationPageSizeFilter(defaultPageSize))
   197  	}
   198  
   199  	if request.SortBy != "" {
   200  		filters = append(filters, domain.NewSortFilter(request.SortBy, request.SortDirection))
   201  	}
   202  
   203  	filters = append(filters, request.AdditionalFilter...)
   204  
   205  	return filters
   206  }
   207  
   208  // AddAdditionalFilter adds an additional filter
   209  func (r *SearchRequest) AddAdditionalFilter(filter domain.Filter) {
   210  	r.AdditionalFilter = append(r.AdditionalFilter, filter)
   211  }
   212  
   213  // SetAdditionalFilter - adds or replaces the given filter
   214  func (r *SearchRequest) SetAdditionalFilter(filterToAdd domain.Filter) {
   215  	for k, existingFilter := range r.AdditionalFilter {
   216  		existingFilterKey, _ := existingFilter.Value()
   217  		filterToAddKey, _ := filterToAdd.Value()
   218  		if existingFilterKey == filterToAddKey {
   219  			r.AdditionalFilter[k] = filterToAdd
   220  			return
   221  		}
   222  	}
   223  	r.AddAdditionalFilter(filterToAdd)
   224  }
   225  
   226  // AddAdditionalFilters adds multiple additional filters
   227  func (r *SearchRequest) AddAdditionalFilters(filters ...domain.Filter) {
   228  	for _, filter := range filters {
   229  		r.AddAdditionalFilter(filter)
   230  	}
   231  }