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 }