├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CONTRIBUTION.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── bump-version.sh ├── config-service ├── pom.xml └── src │ └── main │ ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ └── Application.java │ └── resources │ └── application.properties ├── docs ├── .build │ └── markdown-toc.sh ├── .gitignore ├── .openapi-generator-ignore ├── .openapi-generator │ ├── FILES │ └── VERSION ├── README.md ├── _config.yml ├── _layouts │ ├── default.html │ └── javadoc.html ├── apidocs │ ├── allclasses-index.html │ ├── allclasses.html │ ├── allpackages-index.html │ ├── constant-values.html │ ├── de │ │ └── cxp │ │ │ └── ocs │ │ │ ├── Application.FacetMixin.html │ │ │ ├── Application.SingleStringArgsCreator.html │ │ │ ├── Application.WithTypeInfo.html │ │ │ ├── Application.html │ │ │ ├── DocumentMapper.html │ │ │ ├── ExceptionResponse.html │ │ │ ├── SearchContext.html │ │ │ ├── SearchController.html │ │ │ ├── SearchPlugins.html │ │ │ ├── SuggestProperties.html │ │ │ ├── SuggestServiceImpl.html │ │ │ ├── api │ │ │ ├── SuggestService.html │ │ │ ├── class-use │ │ │ │ └── SuggestService.html │ │ │ ├── indexer │ │ │ │ ├── FullIndexationService.html │ │ │ │ ├── ImportSession.html │ │ │ │ ├── UpdateIndexService.Result.html │ │ │ │ ├── UpdateIndexService.html │ │ │ │ ├── class-use │ │ │ │ │ ├── FullIndexationService.html │ │ │ │ │ ├── ImportSession.html │ │ │ │ │ ├── UpdateIndexService.Result.html │ │ │ │ │ └── UpdateIndexService.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ ├── package-use.html │ │ │ └── searcher │ │ │ │ ├── SearchService.html │ │ │ │ ├── class-use │ │ │ │ └── SearchService.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── class-use │ │ │ ├── Application.FacetMixin.html │ │ │ ├── Application.SingleStringArgsCreator.html │ │ │ ├── Application.WithTypeInfo.html │ │ │ ├── Application.html │ │ │ ├── DocumentMapper.html │ │ │ ├── ExceptionResponse.html │ │ │ ├── SearchContext.html │ │ │ ├── SearchController.html │ │ │ ├── SearchPlugins.html │ │ │ ├── SuggestProperties.html │ │ │ └── SuggestServiceImpl.html │ │ │ ├── client │ │ │ ├── ImportClient.html │ │ │ ├── SearchClient.html │ │ │ ├── SuggestClient.html │ │ │ ├── class-use │ │ │ │ ├── ImportClient.html │ │ │ │ ├── SearchClient.html │ │ │ │ └── SuggestClient.html │ │ │ ├── deserializer │ │ │ │ ├── DocumentDeserializer.html │ │ │ │ ├── FacetEntryDeserializer.html │ │ │ │ ├── ObjectMapperFactory.AttributeCreator.html │ │ │ │ ├── ObjectMapperFactory.FacetMixin.html │ │ │ │ ├── ObjectMapperFactory.SearchQueryCreator.html │ │ │ │ ├── ObjectMapperFactory.WithTypeInfo.html │ │ │ │ ├── ObjectMapperFactory.html │ │ │ │ ├── ProductDeserializer.html │ │ │ │ ├── class-use │ │ │ │ │ ├── DocumentDeserializer.html │ │ │ │ │ ├── FacetEntryDeserializer.html │ │ │ │ │ ├── ObjectMapperFactory.AttributeCreator.html │ │ │ │ │ ├── ObjectMapperFactory.FacetMixin.html │ │ │ │ │ ├── ObjectMapperFactory.SearchQueryCreator.html │ │ │ │ │ ├── ObjectMapperFactory.WithTypeInfo.html │ │ │ │ │ ├── ObjectMapperFactory.html │ │ │ │ │ └── ProductDeserializer.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ │ │ ├── conf │ │ │ ├── ApplicationProperties.html │ │ │ ├── DefaultIndexerConfigurationProvider.html │ │ │ ├── FieldUsageApplier.html │ │ │ ├── IndexConfiguration.html │ │ │ ├── class-use │ │ │ │ ├── ApplicationProperties.html │ │ │ │ ├── DefaultIndexerConfigurationProvider.html │ │ │ │ ├── FieldUsageApplier.html │ │ │ │ └── IndexConfiguration.html │ │ │ ├── converter │ │ │ │ ├── ConfigureableField.html │ │ │ │ ├── FlagFieldConfiguration.PatternMatch.html │ │ │ │ ├── FlagFieldConfiguration.html │ │ │ │ ├── PatternConfiguration.html │ │ │ │ ├── PatternWithReplacementConfiguration.html │ │ │ │ ├── SplitValueConfiguration.html │ │ │ │ ├── class-use │ │ │ │ │ ├── ConfigureableField.html │ │ │ │ │ ├── FlagFieldConfiguration.PatternMatch.html │ │ │ │ │ ├── FlagFieldConfiguration.html │ │ │ │ │ ├── PatternConfiguration.html │ │ │ │ │ ├── PatternWithReplacementConfiguration.html │ │ │ │ │ └── SplitValueConfiguration.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ │ │ ├── config │ │ │ ├── ApplicationProperties.html │ │ │ ├── ApplicationSearchProperties.html │ │ │ ├── BoostMode.html │ │ │ ├── ConnectionConfiguration.html │ │ │ ├── DataProcessorConfiguration.html │ │ │ ├── DefaultSearchConfigrationProvider.html │ │ │ ├── FacetConfiguration.FacetConfig.html │ │ │ ├── FacetConfiguration.html │ │ │ ├── FacetType.html │ │ │ ├── Field.html │ │ │ ├── FieldConfigAccess.html │ │ │ ├── FieldConfigIndex.html │ │ │ ├── FieldConfiguration.html │ │ │ ├── FieldConstants.html │ │ │ ├── FieldLevel.html │ │ │ ├── FieldType.html │ │ │ ├── FieldUsage.html │ │ │ ├── IndexSettings.html │ │ │ ├── QueryBuildingSetting.html │ │ │ ├── QueryConfiguration.QueryCondition.html │ │ │ ├── QueryConfiguration.html │ │ │ ├── QueryProcessingConfiguration.html │ │ │ ├── QueryStrategy.html │ │ │ ├── ScoreMode.html │ │ │ ├── ScoreOption.html │ │ │ ├── ScoreType.html │ │ │ ├── ScoringConfiguration.ScoringFunction.html │ │ │ ├── ScoringConfiguration.html │ │ │ ├── SearchConfiguration.html │ │ │ ├── SortOptionConfiguration.html │ │ │ ├── class-use │ │ │ │ ├── ApplicationProperties.html │ │ │ │ ├── ApplicationSearchProperties.html │ │ │ │ ├── BoostMode.html │ │ │ │ ├── ConnectionConfiguration.html │ │ │ │ ├── DataProcessorConfiguration.html │ │ │ │ ├── DefaultSearchConfigrationProvider.html │ │ │ │ ├── FacetConfiguration.FacetConfig.html │ │ │ │ ├── FacetConfiguration.html │ │ │ │ ├── FacetType.html │ │ │ │ ├── Field.html │ │ │ │ ├── FieldConfigAccess.html │ │ │ │ ├── FieldConfigIndex.html │ │ │ │ ├── FieldConfiguration.html │ │ │ │ ├── FieldConstants.html │ │ │ │ ├── FieldLevel.html │ │ │ │ ├── FieldType.html │ │ │ │ ├── FieldUsage.html │ │ │ │ ├── IndexSettings.html │ │ │ │ ├── QueryBuildingSetting.html │ │ │ │ ├── QueryConfiguration.QueryCondition.html │ │ │ │ ├── QueryConfiguration.html │ │ │ │ ├── QueryProcessingConfiguration.html │ │ │ │ ├── QueryStrategy.html │ │ │ │ ├── ScoreMode.html │ │ │ │ ├── ScoreOption.html │ │ │ │ ├── ScoreType.html │ │ │ │ ├── ScoringConfiguration.ScoringFunction.html │ │ │ │ ├── ScoringConfiguration.html │ │ │ │ ├── SearchConfiguration.html │ │ │ │ └── SortOptionConfiguration.html │ │ │ ├── logging │ │ │ │ ├── MarkerFilter.html │ │ │ │ ├── class-use │ │ │ │ │ └── MarkerFilter.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ │ │ ├── controller │ │ │ ├── FullIndexationController.html │ │ │ ├── IndexationExceptionHandler.html │ │ │ ├── IndexerCache.html │ │ │ ├── UpdateIndexController.html │ │ │ ├── class-use │ │ │ │ ├── FullIndexationController.html │ │ │ │ ├── IndexationExceptionHandler.html │ │ │ │ ├── IndexerCache.html │ │ │ │ └── UpdateIndexController.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ │ │ ├── elasticsearch │ │ │ ├── ElasticSearchBuilder.html │ │ │ ├── ElasticsearchIndexer.html │ │ │ ├── ElasticsearchSuggestDataProvider.html │ │ │ ├── FieldConfigFetcher.html │ │ │ ├── IndexableItemMapperFactory.IncludeOnlyNonEmptyMixin.html │ │ │ ├── IndexableItemMapperFactory.IncludeOnlyNonNullMixin.html │ │ │ ├── IndexableItemMapperFactory.VariantItemMixin.html │ │ │ ├── IndexableItemMapperFactory.html │ │ │ ├── RestClientBuilderFactory.html │ │ │ ├── ResultMapper.html │ │ │ ├── ScoringCreator.html │ │ │ ├── Searcher.html │ │ │ ├── SettingsProxy.html │ │ │ ├── SortingHandler.html │ │ │ ├── SpellCorrector.html │ │ │ ├── class-use │ │ │ │ ├── ElasticSearchBuilder.html │ │ │ │ ├── ElasticsearchIndexer.html │ │ │ │ ├── ElasticsearchSuggestDataProvider.html │ │ │ │ ├── FieldConfigFetcher.html │ │ │ │ ├── IndexableItemMapperFactory.IncludeOnlyNonEmptyMixin.html │ │ │ │ ├── IndexableItemMapperFactory.IncludeOnlyNonNullMixin.html │ │ │ │ ├── IndexableItemMapperFactory.VariantItemMixin.html │ │ │ │ ├── IndexableItemMapperFactory.html │ │ │ │ ├── RestClientBuilderFactory.html │ │ │ │ ├── ResultMapper.html │ │ │ │ ├── ScoringCreator.html │ │ │ │ ├── Searcher.html │ │ │ │ ├── SettingsProxy.html │ │ │ │ ├── SortingHandler.html │ │ │ │ └── SpellCorrector.html │ │ │ ├── facets │ │ │ │ ├── CategoryFacetCreator.html │ │ │ │ ├── FacetConfigurationApplyer.html │ │ │ │ ├── FacetCreator.html │ │ │ │ ├── FacetCreatorFactory.html │ │ │ │ ├── FacetFactory.html │ │ │ │ ├── IntervalFacetCreator.html │ │ │ │ ├── NestedFacetCreator.html │ │ │ │ ├── RangeFacetCreator.html │ │ │ │ ├── TermFacetCreator.html │ │ │ │ ├── VariantFacetCreator.html │ │ │ │ ├── class-use │ │ │ │ │ ├── CategoryFacetCreator.html │ │ │ │ │ ├── FacetConfigurationApplyer.html │ │ │ │ │ ├── FacetCreator.html │ │ │ │ │ ├── FacetCreatorFactory.html │ │ │ │ │ ├── FacetFactory.html │ │ │ │ │ ├── IntervalFacetCreator.html │ │ │ │ │ ├── NestedFacetCreator.html │ │ │ │ │ ├── RangeFacetCreator.html │ │ │ │ │ ├── TermFacetCreator.html │ │ │ │ │ └── VariantFacetCreator.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ ├── package-use.html │ │ │ ├── prodset │ │ │ │ ├── DynamicProductSetResolver.html │ │ │ │ ├── HeroProductHandler.html │ │ │ │ ├── ProductSetResolver.html │ │ │ │ ├── StaticProductSetResolver.html │ │ │ │ ├── class-use │ │ │ │ │ ├── DynamicProductSetResolver.html │ │ │ │ │ ├── HeroProductHandler.html │ │ │ │ │ ├── ProductSetResolver.html │ │ │ │ │ └── StaticProductSetResolver.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ └── query │ │ │ │ ├── AsciifyUserQueryPreprocessor.html │ │ │ │ ├── FiltersBuilder.html │ │ │ │ ├── MasterVariantQuery.html │ │ │ │ ├── analyzer │ │ │ │ ├── QuerqyQueryExpander.html │ │ │ │ ├── WhitespaceAnalyzer.html │ │ │ │ ├── WhitespaceWithShingles.html │ │ │ │ ├── class-use │ │ │ │ │ ├── QuerqyQueryExpander.html │ │ │ │ │ ├── WhitespaceAnalyzer.html │ │ │ │ │ └── WhitespaceWithShingles.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ │ ├── builder │ │ │ │ ├── ConditionalQueries.ComposedPredicate.html │ │ │ │ ├── ConditionalQueries.PatternCondition.html │ │ │ │ ├── ConditionalQueries.TermCountCondition.html │ │ │ │ ├── ConditionalQueries.html │ │ │ │ ├── ConfigurableQueryFactory.html │ │ │ │ ├── DefaultQueryFactory.html │ │ │ │ ├── ESQueryFactoryBuilder.html │ │ │ │ ├── MatchAllQueryFactory.html │ │ │ │ ├── NgramQueryFactory.html │ │ │ │ ├── PredictionQueryFactory.html │ │ │ │ ├── class-use │ │ │ │ │ ├── ConditionalQueries.ComposedPredicate.html │ │ │ │ │ ├── ConditionalQueries.PatternCondition.html │ │ │ │ │ ├── ConditionalQueries.TermCountCondition.html │ │ │ │ │ ├── ConditionalQueries.html │ │ │ │ │ ├── ConfigurableQueryFactory.html │ │ │ │ │ ├── DefaultQueryFactory.html │ │ │ │ │ ├── ESQueryFactoryBuilder.html │ │ │ │ │ ├── MatchAllQueryFactory.html │ │ │ │ │ ├── NgramQueryFactory.html │ │ │ │ │ └── PredictionQueryFactory.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ │ ├── class-use │ │ │ │ ├── AsciifyUserQueryPreprocessor.html │ │ │ │ ├── FiltersBuilder.html │ │ │ │ └── MasterVariantQuery.html │ │ │ │ ├── filter │ │ │ │ ├── FilterContext.html │ │ │ │ ├── InternalResultFilter.html │ │ │ │ ├── InternalResultFilterAdapter.html │ │ │ │ ├── NumberResultFilter.html │ │ │ │ ├── NumberResultFilterAdapter.html │ │ │ │ ├── TermResultFilter.html │ │ │ │ ├── TermResultFilterAdapter.html │ │ │ │ ├── class-use │ │ │ │ │ ├── FilterContext.html │ │ │ │ │ ├── InternalResultFilter.html │ │ │ │ │ ├── InternalResultFilterAdapter.html │ │ │ │ │ ├── NumberResultFilter.html │ │ │ │ │ ├── NumberResultFilterAdapter.html │ │ │ │ │ ├── TermResultFilter.html │ │ │ │ │ └── TermResultFilterAdapter.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ │ ├── model │ │ │ │ ├── EscapeUtil.html │ │ │ │ ├── QueryStringTerm.html │ │ │ │ ├── WeightedWord.html │ │ │ │ ├── WordAssociation.html │ │ │ │ ├── class-use │ │ │ │ │ ├── EscapeUtil.html │ │ │ │ │ ├── QueryStringTerm.html │ │ │ │ │ ├── WeightedWord.html │ │ │ │ │ └── WordAssociation.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── indexer │ │ │ ├── AbstractIndexer.html │ │ │ ├── DocumentPatcher.html │ │ │ ├── IndexItemConverter.html │ │ │ ├── IndexerFactory.html │ │ │ ├── class-use │ │ │ │ ├── AbstractIndexer.html │ │ │ │ ├── DocumentPatcher.html │ │ │ │ ├── IndexItemConverter.html │ │ │ │ └── IndexerFactory.html │ │ │ ├── model │ │ │ │ ├── DataItem.html │ │ │ │ ├── FacetEntry.html │ │ │ │ ├── IndexableItem.html │ │ │ │ ├── MasterItem.html │ │ │ │ ├── VariantItem.html │ │ │ │ ├── class-use │ │ │ │ │ ├── DataItem.html │ │ │ │ │ ├── FacetEntry.html │ │ │ │ │ ├── IndexableItem.html │ │ │ │ │ ├── MasterItem.html │ │ │ │ │ └── VariantItem.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ │ │ ├── model │ │ │ ├── index │ │ │ │ ├── Attribute.html │ │ │ │ ├── BulkImportData.html │ │ │ │ ├── Category.html │ │ │ │ ├── Document.html │ │ │ │ ├── Product.html │ │ │ │ ├── class-use │ │ │ │ │ ├── Attribute.html │ │ │ │ │ ├── BulkImportData.html │ │ │ │ │ ├── Category.html │ │ │ │ │ ├── Document.html │ │ │ │ │ └── Product.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── params │ │ │ │ ├── ArrangedSearchQuery.html │ │ │ │ ├── DynamicProductSet.html │ │ │ │ ├── FilteredSearchQuery.html │ │ │ │ ├── ProductSet.html │ │ │ │ ├── SearchQuery.html │ │ │ │ ├── StaticProductSet.html │ │ │ │ ├── class-use │ │ │ │ │ ├── ArrangedSearchQuery.html │ │ │ │ │ ├── DynamicProductSet.html │ │ │ │ │ ├── FilteredSearchQuery.html │ │ │ │ │ ├── ProductSet.html │ │ │ │ │ ├── SearchQuery.html │ │ │ │ │ └── StaticProductSet.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── result │ │ │ │ ├── Facet.html │ │ │ │ ├── FacetEntry.html │ │ │ │ ├── HierarchialFacetEntry.html │ │ │ │ ├── IntervalFacetEntry.html │ │ │ │ ├── RangeFacetEntry.html │ │ │ │ ├── ResultHit.html │ │ │ │ ├── SearchResult.html │ │ │ │ ├── SearchResultSlice.html │ │ │ │ ├── SortOrder.html │ │ │ │ ├── Sorting.html │ │ │ │ ├── class-use │ │ │ │ │ ├── Facet.html │ │ │ │ │ ├── FacetEntry.html │ │ │ │ │ ├── HierarchialFacetEntry.html │ │ │ │ │ ├── IntervalFacetEntry.html │ │ │ │ │ ├── RangeFacetEntry.html │ │ │ │ │ ├── ResultHit.html │ │ │ │ │ ├── SearchResult.html │ │ │ │ │ ├── SearchResultSlice.html │ │ │ │ │ ├── SortOrder.html │ │ │ │ │ └── Sorting.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ └── suggest │ │ │ │ ├── Suggestion.html │ │ │ │ ├── class-use │ │ │ │ └── Suggestion.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ ├── package-use.html │ │ │ ├── plugin │ │ │ ├── ExtensionSupplierRegistry.html │ │ │ ├── PluginManager.html │ │ │ ├── class-use │ │ │ │ ├── ExtensionSupplierRegistry.html │ │ │ │ └── PluginManager.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ │ │ ├── preprocessor │ │ │ ├── CombiFieldBuilder.html │ │ │ ├── ConfigureableDataprocessor.html │ │ │ ├── class-use │ │ │ │ ├── CombiFieldBuilder.html │ │ │ │ └── ConfigureableDataprocessor.html │ │ │ ├── impl │ │ │ │ ├── AsciiFoldingDataProcessor.html │ │ │ │ ├── ExtractCategoryLevelDataProcessor.html │ │ │ │ ├── FlagFieldDataProcessor.html │ │ │ │ ├── RemoveFieldContentDelimiterProcessor.html │ │ │ │ ├── RemoveValuesDataProcessor.html │ │ │ │ ├── ReplacePatternInValuesDataProcessor.html │ │ │ │ ├── SkipDocumentDataProcessor.html │ │ │ │ ├── SplitValueDataProcessor.html │ │ │ │ ├── WordSplitterDataProcessor.html │ │ │ │ ├── class-use │ │ │ │ │ ├── AsciiFoldingDataProcessor.html │ │ │ │ │ ├── ExtractCategoryLevelDataProcessor.html │ │ │ │ │ ├── FlagFieldDataProcessor.html │ │ │ │ │ ├── RemoveFieldContentDelimiterProcessor.html │ │ │ │ │ ├── RemoveValuesDataProcessor.html │ │ │ │ │ ├── ReplacePatternInValuesDataProcessor.html │ │ │ │ │ ├── SkipDocumentDataProcessor.html │ │ │ │ │ ├── SplitValueDataProcessor.html │ │ │ │ │ └── WordSplitterDataProcessor.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ ├── package-use.html │ │ │ └── util │ │ │ │ ├── CategorySearchData.html │ │ │ │ ├── class-use │ │ │ │ └── CategorySearchData.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── smartsuggest │ │ │ ├── QuerySuggestManager.QuerySuggestManagerBuilder.html │ │ │ ├── QuerySuggestManager.html │ │ │ ├── class-use │ │ │ │ ├── QuerySuggestManager.QuerySuggestManagerBuilder.html │ │ │ │ └── QuerySuggestManager.html │ │ │ ├── limiter │ │ │ │ ├── ConfigurableShareLimiter.html │ │ │ │ ├── CutOffLimiter.html │ │ │ │ ├── GroupedCutOffLimiter.html │ │ │ │ ├── Limiter.html │ │ │ │ ├── class-use │ │ │ │ │ ├── ConfigurableShareLimiter.html │ │ │ │ │ ├── CutOffLimiter.html │ │ │ │ │ ├── GroupedCutOffLimiter.html │ │ │ │ │ └── Limiter.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── monitoring │ │ │ │ ├── DistributionSummaryAdapter.html │ │ │ │ ├── Instrumentable.html │ │ │ │ ├── MeterRegistryAdapter.html │ │ │ │ ├── class-use │ │ │ │ │ ├── DistributionSummaryAdapter.html │ │ │ │ │ ├── Instrumentable.html │ │ │ │ │ └── MeterRegistryAdapter.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ ├── package-use.html │ │ │ ├── querysuggester │ │ │ │ ├── CompoundQuerySuggester.html │ │ │ │ ├── NoopQuerySuggester.html │ │ │ │ ├── QueryIndexer.html │ │ │ │ ├── QuerySuggester.html │ │ │ │ ├── QuerySuggesterProxy.html │ │ │ │ ├── SuggestException.html │ │ │ │ ├── SuggesterEngine.html │ │ │ │ ├── SuggesterFactory.html │ │ │ │ ├── Suggestion.html │ │ │ │ ├── class-use │ │ │ │ │ ├── CompoundQuerySuggester.html │ │ │ │ │ ├── NoopQuerySuggester.html │ │ │ │ │ ├── QueryIndexer.html │ │ │ │ │ ├── QuerySuggester.html │ │ │ │ │ ├── QuerySuggesterProxy.html │ │ │ │ │ ├── SuggestException.html │ │ │ │ │ ├── SuggesterEngine.html │ │ │ │ │ ├── SuggesterFactory.html │ │ │ │ │ └── Suggestion.html │ │ │ │ ├── lucene │ │ │ │ │ ├── LuceneQuerySuggester.html │ │ │ │ │ ├── LuceneSuggesterFactory.html │ │ │ │ │ ├── PerfResult.StepTime.html │ │ │ │ │ ├── PerfResult.html │ │ │ │ │ ├── class-use │ │ │ │ │ │ ├── LuceneQuerySuggester.html │ │ │ │ │ │ ├── LuceneSuggesterFactory.html │ │ │ │ │ │ ├── PerfResult.StepTime.html │ │ │ │ │ │ └── PerfResult.html │ │ │ │ │ ├── package-summary.html │ │ │ │ │ ├── package-tree.html │ │ │ │ │ └── package-use.html │ │ │ │ ├── modified │ │ │ │ │ ├── ModifiedTermsService.html │ │ │ │ │ ├── class-use │ │ │ │ │ │ └── ModifiedTermsService.html │ │ │ │ │ ├── package-summary.html │ │ │ │ │ ├── package-tree.html │ │ │ │ │ └── package-use.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── spi │ │ │ │ ├── CommonPayloadFields.html │ │ │ │ ├── DummySuggestDataProvider.html │ │ │ │ ├── MergingSuggestDataProvider.html │ │ │ │ ├── SuggestData.html │ │ │ │ ├── SuggestDataProvider.html │ │ │ │ ├── SuggestRecord.html │ │ │ │ ├── class-use │ │ │ │ │ ├── CommonPayloadFields.html │ │ │ │ │ ├── DummySuggestDataProvider.html │ │ │ │ │ ├── MergingSuggestDataProvider.html │ │ │ │ │ ├── SuggestData.html │ │ │ │ │ ├── SuggestDataProvider.html │ │ │ │ │ └── SuggestRecord.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── updater │ │ │ │ ├── SuggestionsUpdater.html │ │ │ │ ├── class-use │ │ │ │ │ └── SuggestionsUpdater.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ └── util │ │ │ │ ├── Util.html │ │ │ │ ├── class-use │ │ │ │ └── Util.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ ├── spi │ │ │ ├── indexer │ │ │ │ ├── DocumentPostProcessor.html │ │ │ │ ├── DocumentPreProcessor.html │ │ │ │ ├── IndexerConfigurationProvider.html │ │ │ │ ├── class-use │ │ │ │ │ ├── DocumentPostProcessor.html │ │ │ │ │ ├── DocumentPreProcessor.html │ │ │ │ │ └── IndexerConfigurationProvider.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ └── search │ │ │ │ ├── ConfigurableExtension.html │ │ │ │ ├── ESQueryFactory.html │ │ │ │ ├── RescorerProvider.html │ │ │ │ ├── SearchConfigurationProvider.html │ │ │ │ ├── UserQueryAnalyzer.html │ │ │ │ ├── UserQueryPreprocessor.html │ │ │ │ ├── class-use │ │ │ │ ├── ConfigurableExtension.html │ │ │ │ ├── ESQueryFactory.html │ │ │ │ ├── RescorerProvider.html │ │ │ │ ├── SearchConfigurationProvider.html │ │ │ │ ├── UserQueryAnalyzer.html │ │ │ │ └── UserQueryPreprocessor.html │ │ │ │ ├── package-summary.html │ │ │ │ ├── package-tree.html │ │ │ │ └── package-use.html │ │ │ └── util │ │ │ ├── ConfigurationException.html │ │ │ ├── DocumentDeserializer.html │ │ │ ├── ESQueryUtils.html │ │ │ ├── InternalSearchParams.html │ │ │ ├── MinMaxSet.html │ │ │ ├── NotFoundException.html │ │ │ ├── OnceInAWhileRunner.html │ │ │ ├── ProductDeserializer.html │ │ │ ├── SearchParamsParser.html │ │ │ ├── SearchQueryBuilder.html │ │ │ ├── StringUtils.html │ │ │ ├── Util.html │ │ │ ├── class-use │ │ │ ├── ConfigurationException.html │ │ │ ├── DocumentDeserializer.html │ │ │ ├── ESQueryUtils.html │ │ │ ├── InternalSearchParams.html │ │ │ ├── MinMaxSet.html │ │ │ ├── NotFoundException.html │ │ │ ├── OnceInAWhileRunner.html │ │ │ ├── ProductDeserializer.html │ │ │ ├── SearchParamsParser.html │ │ │ ├── SearchQueryBuilder.html │ │ │ ├── StringUtils.html │ │ │ └── Util.html │ │ │ ├── package-summary.html │ │ │ ├── package-tree.html │ │ │ └── package-use.html │ ├── deprecated-list.html │ ├── element-list │ ├── help-doc.html │ ├── index-all.html │ ├── index.html │ ├── jquery │ │ ├── external │ │ │ └── jquery │ │ │ │ └── jquery.js │ │ ├── images │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ ├── jquery-3.5.1.js │ │ ├── jquery-ui.css │ │ ├── jquery-ui.js │ │ ├── jquery-ui.min.css │ │ ├── jquery-ui.min.js │ │ ├── jquery-ui.structure.css │ │ ├── jquery-ui.structure.min.css │ │ ├── jszip-utils │ │ │ └── dist │ │ │ │ ├── jszip-utils-ie.js │ │ │ │ ├── jszip-utils-ie.min.js │ │ │ │ ├── jszip-utils.js │ │ │ │ └── jszip-utils.min.js │ │ └── jszip │ │ │ └── dist │ │ │ ├── jszip.js │ │ │ └── jszip.min.js │ ├── member-search-index.js │ ├── overview-summary.html │ ├── overview-tree.html │ ├── package-search-index.js │ ├── resources │ │ ├── glass.png │ │ └── x.png │ ├── script.js │ ├── search.js │ ├── serialized-form.html │ ├── stylesheet.css │ └── type-search-index.js ├── assets │ ├── css │ │ └── style.scss │ └── ocss_logo_only.png ├── configuration.md ├── index.md ├── indexer_service.md ├── integration_guideline.md ├── javadoc.md ├── openapi │ ├── .openapi-generator-ignore │ ├── .openapi-generator │ │ ├── FILES │ │ └── VERSION │ ├── Apis │ │ ├── IndexerApi.md │ │ ├── SearchApi.md │ │ ├── SuggestApi.md │ │ └── UpdateApi.md │ ├── Models │ │ ├── ArrangedSearchQuery.md │ │ ├── Attribute.md │ │ ├── BulkImportData.md │ │ ├── Category.md │ │ ├── Document.md │ │ ├── Document_data_value.md │ │ ├── DynamicProductSet.md │ │ ├── DynamicProductSet_allOf.md │ │ ├── Facet.md │ │ ├── FacetEntry.md │ │ ├── GenericProductSet.md │ │ ├── HierarchialFacetEntry.md │ │ ├── HierarchialFacetEntry_allOf.md │ │ ├── ImportSession.md │ │ ├── IntervalFacetEntry.md │ │ ├── IntervalFacetEntry_allOf.md │ │ ├── Product.md │ │ ├── ProductSet.md │ │ ├── Product_allOf.md │ │ ├── QueryStringProductSet.md │ │ ├── RangeFacetEntry.md │ │ ├── RangeFacetEntry_allOf.md │ │ ├── ResultHit.md │ │ ├── SearchQuery.md │ │ ├── SearchResult.md │ │ ├── SearchResultSlice.md │ │ ├── Sorting.md │ │ ├── StaticProductSet.md │ │ ├── StaticProductSet_allOf.md │ │ └── Suggestion.md │ └── index.md ├── plugin_guide.md ├── quick_start_demo.md ├── search_service.md └── suggest_service.md ├── indexer-service ├── data │ └── nodes │ │ └── 0 │ │ └── node.lock ├── pom.xml └── src │ ├── main │ ├── java │ │ └── de │ │ │ └── cxp │ │ │ └── ocs │ │ │ ├── Application.java │ │ │ ├── conf │ │ │ ├── ApplicationProperties.java │ │ │ ├── DefaultIndexerConfigurationProvider.java │ │ │ ├── FieldUsageApplier.java │ │ │ ├── IndexConfiguration.java │ │ │ ├── IndexConfigurationMerger.java │ │ │ └── converter │ │ │ │ ├── ConfigureableField.java │ │ │ │ ├── FlagFieldConfiguration.java │ │ │ │ ├── PatternConfiguration.java │ │ │ │ ├── PatternWithReplacementConfiguration.java │ │ │ │ └── SplitValueConfiguration.java │ │ │ ├── controller │ │ │ ├── FullIndexationController.java │ │ │ ├── IndexationExceptionHandler.java │ │ │ ├── IndexerCache.java │ │ │ └── UpdateIndexController.java │ │ │ ├── elasticsearch │ │ │ ├── AbandonedIndexCleanupTask.java │ │ │ ├── ElasticsearchIndexClient.java │ │ │ ├── ElasticsearchIndexer.java │ │ │ └── IndexableItemMapperFactory.java │ │ │ ├── indexer │ │ │ ├── AbstractIndexer.java │ │ │ ├── DocumentPatcher.java │ │ │ ├── IndexItemConverter.java │ │ │ └── IndexerFactory.java │ │ │ └── preprocessor │ │ │ ├── ConfigureableDataprocessor.java │ │ │ ├── impl │ │ │ ├── AsciiFoldingDataProcessor.java │ │ │ ├── AttributeToDataFieldConverter.java │ │ │ ├── ExtractCategoryLevelDataProcessor.java │ │ │ ├── FlagFieldDataProcessor.java │ │ │ ├── RemoveFieldContentDelimiterProcessor.java │ │ │ ├── RemoveValuesDataProcessor.java │ │ │ ├── ReplacePatternInValuesDataProcessor.java │ │ │ ├── SkipDocumentDataProcessor.java │ │ │ ├── SplitValueDataProcessor.java │ │ │ └── WordSplitterDataProcessor.java │ │ │ └── util │ │ │ └── CategorySearchData.java │ └── resources │ │ ├── application-preset.yml │ │ ├── application.yml │ │ ├── bootstrap.properties │ │ ├── elasticsearch │ │ ├── _component_templates │ │ │ └── ocs_default.json │ │ └── _index_templates │ │ │ ├── ocs_czech_template.json │ │ │ ├── ocs_default_template.json │ │ │ ├── ocs_english_template.json │ │ │ ├── ocs_french_template.json │ │ │ └── ocs_german_template.json │ │ └── logback-spring.xml │ └── test │ ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ ├── elasticsearch │ │ ├── ElasticsearchCRUDTest.java │ │ ├── ElasticsearchContainerUtil.java │ │ ├── ElasticsearchFullIndexationTest.java │ │ └── ElasticsearchIndexerTest.java │ │ ├── indexer │ │ └── IndexItemConverterTest.java │ │ └── preprocessor │ │ └── impl │ │ ├── AttributeToDataFieldConverterTest.java │ │ └── ExtractCategoryLevelDataProcessorTest.java │ └── resources │ └── application.yml ├── integration-tests ├── pom.xml └── src │ └── test │ ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ ├── ITBulkIndexationWorks.java │ │ ├── ITPartialUpdates.java │ │ ├── ITSearchService.java │ │ ├── ITUpdateApi.java │ │ ├── OCSStack.java │ │ ├── usecase │ │ ├── FilterAndSortByFacetValues.java │ │ ├── GermanNormalizationTest.java │ │ ├── HeroProductsTest.java │ │ ├── MultipleCategoryTreesTest.java │ │ ├── QuerqyRulesTest.java │ │ └── QueryRelaxation.java │ │ └── util │ │ └── DataIndexer.java │ └── resources │ ├── indexer.application-test.yml │ ├── querqy-test-rules.txt │ ├── searcher.application-test.yml │ └── testdata.jsonl ├── ocs-commons ├── pom.xml └── src │ └── main │ └── java │ └── de │ └── cxp │ └── ocs │ ├── DocumentMapper.java │ ├── config │ ├── ConnectionConfiguration.java │ ├── FieldConfigIncompatibilityException.java │ ├── FieldConfigIndex.java │ └── IndexedField.java │ ├── elasticsearch │ ├── ElasticSearchBuilder.java │ ├── FieldConfigFetcher.java │ └── RestClientBuilderFactory.java │ ├── plugin │ ├── ExtensionSupplierRegistry.java │ └── PluginManager.java │ └── util │ ├── MinMaxSet.java │ ├── OnceInAWhileRunner.java │ ├── StringUtils.java │ └── Util.java ├── ocs-java-client ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── cxp │ │ └── ocs │ │ └── client │ │ ├── DocumentBulkSplit.java │ │ ├── ImportApi.java │ │ ├── ImportClient.java │ │ ├── SearchApi.java │ │ ├── SearchClient.java │ │ ├── SuggestApi.java │ │ ├── SuggestClient.java │ │ └── deserializer │ │ ├── DocumentDeserializer.java │ │ ├── FacetEntryDeserializer.java │ │ ├── ObjectMapperFactory.java │ │ └── ProductDeserializer.java │ └── test │ └── java │ └── de │ └── cxp │ └── ocs │ └── client │ └── deserializer │ └── SerializationTest.java ├── ocs-plugin-spi ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── cxp │ │ └── ocs │ │ ├── config │ │ ├── BoostMode.java │ │ ├── DataProcessorConfiguration.java │ │ ├── FacetConfiguration.java │ │ ├── FacetType.java │ │ ├── Field.java │ │ ├── FieldConfigAccess.java │ │ ├── FieldConfiguration.java │ │ ├── FieldConstants.java │ │ ├── FieldLevel.java │ │ ├── FieldType.java │ │ ├── FieldUsage.java │ │ ├── IndexSettings.java │ │ ├── QueryBuildingSetting.java │ │ ├── QueryConfiguration.java │ │ ├── QueryProcessingConfiguration.java │ │ ├── QueryStrategy.java │ │ ├── ScoreMode.java │ │ ├── ScoreOption.java │ │ ├── ScoreType.java │ │ ├── ScoringConfiguration.java │ │ ├── SearchConfiguration.java │ │ └── SortOptionConfiguration.java │ │ ├── elasticsearch │ │ ├── model │ │ │ ├── filter │ │ │ │ └── InternalResultFilter.java │ │ │ ├── package-info.java │ │ │ ├── query │ │ │ │ ├── AnalyzedQuery.java │ │ │ │ ├── ExtendedQuery.java │ │ │ │ ├── MatchAllQuery.java │ │ │ │ ├── MultiTermQuery.java │ │ │ │ ├── MultiVariantQuery.java │ │ │ │ ├── QueryBoosting.java │ │ │ │ └── SingleTermQuery.java │ │ │ ├── term │ │ │ │ ├── AssociatedTerm.java │ │ │ │ ├── ConceptTerm.java │ │ │ │ ├── Occur.java │ │ │ │ ├── QueryFilterTerm.java │ │ │ │ ├── QueryStringTerm.java │ │ │ │ ├── RawTerm.java │ │ │ │ └── WeightedTerm.java │ │ │ ├── util │ │ │ │ ├── EscapeUtil.java │ │ │ │ └── QueryStringUtil.java │ │ │ └── visitor │ │ │ │ ├── AbstractTermVisitor.java │ │ │ │ └── QueryTermVisitor.java │ │ └── query │ │ │ └── TextMatchQuery.java │ │ ├── indexer │ │ └── model │ │ │ ├── DataItem.java │ │ │ ├── FacetEntry.java │ │ │ ├── IndexableItem.java │ │ │ ├── MasterItem.java │ │ │ └── VariantItem.java │ │ ├── spi │ │ ├── indexer │ │ │ ├── DocumentPostProcessor.java │ │ │ ├── DocumentPreProcessor.java │ │ │ └── IndexerConfigurationProvider.java │ │ └── search │ │ │ ├── ConfigurableExtension.java │ │ │ ├── CustomFacetCreator.java │ │ │ ├── ESQueryFactory.java │ │ │ ├── RescorerProvider.java │ │ │ ├── SearchConfigurationProvider.java │ │ │ ├── UserQueryAnalyzer.java │ │ │ └── UserQueryPreprocessor.java │ │ └── util │ │ └── LinkBuilder.java │ └── test │ └── java │ └── de │ └── cxp │ └── ocs │ ├── config │ └── FieldTest.java │ └── elasticsearch │ └── model │ └── util │ └── EscapeUtilTest.java ├── ocss-frontend ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json ├── Dockerfile ├── README.md ├── app │ ├── (configuration) │ │ ├── config │ │ │ ├── [owner] │ │ │ │ └── [repo] │ │ │ │ │ ├── indexer │ │ │ │ │ ├── field-configuration │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── general │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── search │ │ │ │ │ ├── facet-configuration │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── query-processing-configuration │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── scoring-configuration │ │ │ │ │ └── page.tsx │ │ │ │ │ └── sort-configuration │ │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ └── signin │ │ │ └── page.tsx │ ├── (demoshop) │ │ ├── page.tsx │ │ └── product │ │ │ └── [id] │ │ │ ├── loading.tsx │ │ │ ├── not-found.tsx │ │ │ └── page.tsx │ ├── (proxy) │ │ ├── search-api │ │ │ └── [...search-api] │ │ │ │ └── route.ts │ │ └── suggest-api │ │ │ └── [...suggest-api] │ │ │ └── route.ts │ ├── api │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── error.tsx │ ├── layout.tsx │ ├── loading.tsx │ └── not-found.tsx ├── components.json ├── components │ ├── configuration │ │ ├── boost-mode-configuration.tsx │ │ ├── commit-history.tsx │ │ ├── commit-ref.tsx │ │ ├── configuration-state-initialization.tsx │ │ ├── configuration-target-select.tsx │ │ ├── create-facet-button.tsx │ │ ├── create-field-button.tsx │ │ ├── create-query-processing-configuration-button.tsx │ │ ├── create-score-function-button.tsx │ │ ├── create-sort-option-button.tsx │ │ ├── edit-facet-button.tsx │ │ ├── edit-field-button.tsx │ │ ├── edit-query-processing-configuration-button.tsx │ │ ├── edit-score-function-button.tsx │ │ ├── edit-sort-option-button.tsx │ │ ├── facet-configuration.tsx │ │ ├── field-configuration.tsx │ │ ├── general-form.tsx │ │ ├── index-config-form.tsx │ │ ├── index-config-select.tsx │ │ ├── max-facets-configuration.tsx │ │ ├── query-processing-configuration.tsx │ │ ├── repo-selection.tsx │ │ ├── save-configuration-button.tsx │ │ ├── save-configuration-form.tsx │ │ ├── score-mode-configuration.tsx │ │ ├── scoring-configuration.tsx │ │ ├── search-tenant-select.tsx │ │ ├── sidebar-nav.tsx │ │ ├── sign-in-button.tsx │ │ ├── sign-out-button.tsx │ │ └── sort-configuration.tsx │ ├── demoshop │ │ ├── bookmarks-button.tsx │ │ ├── checkbox-filter.tsx │ │ ├── clear-search-query-button.tsx │ │ ├── config-sortable-item.tsx │ │ ├── create-bookmark-form.tsx │ │ ├── data-table-pagination.tsx │ │ ├── data-table.tsx │ │ ├── demoshop-button.tsx │ │ ├── filter-options.tsx │ │ ├── hierarchical-filter.tsx │ │ ├── interval-filter.tsx │ │ ├── pagination.tsx │ │ ├── product-banner.tsx │ │ ├── product-card-editor.tsx │ │ ├── product-card.tsx │ │ ├── product-cards-list.tsx │ │ ├── product-data-field-configuration-initialization-client.tsx │ │ ├── product-data-field-configuration-initialization.tsx │ │ ├── product-data-tables.tsx │ │ ├── range-filter-slider.tsx │ │ ├── results-debug-dialog.tsx │ │ ├── search-bar.tsx │ │ ├── search-results-fallback.tsx │ │ ├── search-results.tsx │ │ ├── smart-query-redirect.tsx │ │ ├── sort-select.tsx │ │ ├── sortable-item.tsx │ │ └── tenant-select-button.tsx │ ├── misc │ │ ├── auth-buttons.tsx │ │ ├── config-button.tsx │ │ ├── icons.tsx │ │ ├── layout.tsx │ │ ├── loader.tsx │ │ ├── main-nav.tsx │ │ ├── next-auth-provider.tsx │ │ ├── providers.tsx │ │ ├── site-header.tsx │ │ ├── tailwind-indicator.tsx │ │ ├── theme-provider.tsx │ │ └── theme-toggle.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── range-slider.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── tooltip.tsx │ │ ├── use-default-config-switch.tsx │ │ └── use-toast.ts ├── config │ └── site.ts ├── env.mjs ├── hooks │ └── use-commit-history.ts ├── lib │ ├── auth.ts │ ├── bookmarks.ts │ ├── default-configs.ts │ ├── fonts.ts │ ├── github.ts │ ├── global-state.ts │ ├── product-card-configuration.ts │ ├── productsetservice.ts │ ├── search-api.ts │ ├── suggest-api.ts │ └── utils.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public │ ├── OCSS_Bildlogo.png │ ├── OCSS_Komplett.png │ ├── OCSS_Schriftlogo.png │ ├── OCSS_Standard.png │ ├── favicon.ico │ └── logo.tsx ├── reset.d.ts ├── styles │ └── globals.css ├── tailwind.config.js ├── tsconfig.json └── types │ ├── api.ts │ ├── bookmarks.ts │ ├── config.ts │ ├── cookies.ts │ ├── github.ts │ ├── nav.ts │ ├── productsetservice.ts │ └── searchParams.ts ├── open-commerce-search-api ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ ├── api │ │ ├── SuggestService.java │ │ ├── indexer │ │ │ ├── FullIndexationService.java │ │ │ ├── ImportSession.java │ │ │ └── UpdateIndexService.java │ │ └── searcher │ │ │ └── SearchService.java │ │ └── model │ │ ├── index │ │ ├── Attribute.java │ │ ├── BulkImportData.java │ │ ├── Category.java │ │ ├── Document.java │ │ └── Product.java │ │ ├── params │ │ ├── ArrangedSearchQuery.java │ │ ├── DynamicProductSet.java │ │ ├── FilteredSearchQuery.java │ │ ├── GenericProductSet.java │ │ ├── ProductSet.java │ │ ├── QueryStringProductSet.java │ │ ├── SearchQuery.java │ │ └── StaticProductSet.java │ │ ├── result │ │ ├── Facet.java │ │ ├── FacetEntry.java │ │ ├── HierarchialFacetEntry.java │ │ ├── IntervalFacetEntry.java │ │ ├── RangeFacetEntry.java │ │ ├── ResultHit.java │ │ ├── SearchResult.java │ │ ├── SearchResultSlice.java │ │ ├── SortOrder.java │ │ └── Sorting.java │ │ └── suggest │ │ └── Suggestion.java │ └── resources │ ├── openapi-configuration.json │ └── openapi.yaml ├── operations ├── docker-compose │ ├── .gitignore │ ├── README.md │ ├── _env_example │ ├── application.indexer-service.yml │ ├── application.search-service.yml │ ├── dc-cloudconfig.sh │ ├── dc-local.sh │ ├── docker-compose.base.yml │ ├── docker-compose.cloudconfig.yml │ ├── docker-compose.frontend.yml │ ├── docker-compose.local.yml │ └── elasticsearch │ │ └── docker-entrypoint-es.sh ├── k8s │ ├── .gitignore │ ├── README.md │ ├── base │ │ ├── elasticsearch.yaml │ │ ├── indexer-api.yaml │ │ ├── indexer-ingress.yaml │ │ ├── indexer-service.yaml │ │ ├── kustomization.yaml │ │ ├── search-api.yaml │ │ ├── search-ingress.yaml │ │ ├── search-service.yaml │ │ ├── suggest-api.yaml │ │ ├── suggest-ingress.yaml │ │ └── suggest-service.yaml │ └── overlays │ │ └── example │ │ ├── README.md │ │ └── kustomization.yaml ├── ocs-index-data.sh └── tests │ └── rally │ ├── README.md │ ├── challenges │ ├── index.json │ ├── search-while-index.json │ └── search.json │ ├── create-es-rally-track.sh │ ├── custom_runner │ └── ocss_search_runner.py │ ├── docker-compose-results.yaml │ ├── rally.ini │ └── track.py ├── pom.xml ├── search-service ├── pom.xml └── src │ ├── main │ ├── java │ │ └── de │ │ │ └── cxp │ │ │ └── ocs │ │ │ ├── Application.java │ │ │ ├── ExceptionResponse.java │ │ │ ├── SearchContext.java │ │ │ ├── SearchContextLoader.java │ │ │ ├── SearchController.java │ │ │ ├── SearchPlugins.java │ │ │ ├── config │ │ │ ├── ApplicationProperties.java │ │ │ ├── ApplicationSearchProperties.java │ │ │ ├── DefaultSearchConfigurationProvider.java │ │ │ ├── SchedulerConfig.java │ │ │ └── logging │ │ │ │ └── MarkerFilter.java │ │ │ ├── elasticsearch │ │ │ ├── QueryStringParser.java │ │ │ ├── ScoringCreator.java │ │ │ ├── Searcher.java │ │ │ ├── SortingHandler.java │ │ │ ├── SpellCorrector.java │ │ │ ├── facets │ │ │ │ ├── CategoryFacetCreator.java │ │ │ │ ├── ConfiguredIntervalFacetCreator.java │ │ │ │ ├── FacetConfigurationApplyer.java │ │ │ │ ├── FacetCoverageFilter.java │ │ │ │ ├── FacetCreator.java │ │ │ │ ├── FacetCreatorClassifier.java │ │ │ │ ├── FacetCreatorInitializer.java │ │ │ │ ├── FacetDependencyFilter.java │ │ │ │ ├── FacetFactory.java │ │ │ │ ├── FacetFilter.java │ │ │ │ ├── FacetSizeFilter.java │ │ │ │ ├── FixedIntervalFacetCreator.java │ │ │ │ ├── IndexNameFacetCreator.java │ │ │ │ ├── IntervalFacetCreator.java │ │ │ │ ├── NestedCustomFacetCreator.java │ │ │ │ ├── NestedFacetCountCorrector.java │ │ │ │ ├── NestedFacetCreator.java │ │ │ │ ├── RangeFacetCreator.java │ │ │ │ ├── TermFacetCreator.java │ │ │ │ ├── VariantFacetCreator.java │ │ │ │ └── helper │ │ │ │ │ └── NumericFacetEntryBuilder.java │ │ │ ├── mapper │ │ │ │ ├── ResultMapper.java │ │ │ │ └── VariantPickingStrategy.java │ │ │ ├── prodset │ │ │ │ ├── DynamicProductSetResolver.java │ │ │ │ ├── HeroProductHandler.java │ │ │ │ ├── HeroProductsQuery.java │ │ │ │ ├── NoopProductSetResolver.java │ │ │ │ ├── ProductSetResolver.java │ │ │ │ ├── QueryStringProductSetResolver.java │ │ │ │ └── StaticProductSetResolver.java │ │ │ └── query │ │ │ │ ├── AsciifyUserQueryPreprocessor.java │ │ │ │ ├── CodePointFilterUserQueryPreprocessor.java │ │ │ │ ├── FiltersBuilder.java │ │ │ │ ├── NonAlphanumericStripPreprocessor.java │ │ │ │ ├── QueryStringBuilder.java │ │ │ │ ├── ScoringContext.java │ │ │ │ ├── SearchField.java │ │ │ │ ├── SearchQueryContext.java │ │ │ │ ├── SearchQueryWrapper.java │ │ │ │ ├── StandardQueryFactory.java │ │ │ │ ├── analyzer │ │ │ │ ├── AsciifyQuerqyQueryAnalyzer.java │ │ │ │ ├── NonAlphanumericWordSplitAnalyzer.java │ │ │ │ ├── QuerqyQueryExpander.java │ │ │ │ ├── WhitespaceAnalyzer.java │ │ │ │ ├── WhitespaceWithShingles.java │ │ │ │ └── querqy │ │ │ │ │ ├── TransformingWhitespaceQuerqyParser.java │ │ │ │ │ └── TransformingWhitespaceQuerqyParserFactory.java │ │ │ │ ├── builder │ │ │ │ ├── .gitignore │ │ │ │ ├── ConditionalQueries.java │ │ │ │ ├── ConfigurableQueryFactory.java │ │ │ │ ├── CountedTerm.java │ │ │ │ ├── DefaultQueryFactory.java │ │ │ │ ├── DisMaxQueryFactory.java │ │ │ │ ├── ESQueryFactoryBuilder.java │ │ │ │ ├── EnforcedSpellCorrectionQueryFactory.java │ │ │ │ ├── FallbackConsumer.java │ │ │ │ ├── MatchAllQueryFactory.java │ │ │ │ ├── NgramQueryFactory.java │ │ │ │ ├── NoResultQueryFactory.java │ │ │ │ ├── PredictedQuery.java │ │ │ │ ├── PredictionQueryFactory.java │ │ │ │ ├── QueryPredictor.java │ │ │ │ ├── RelaxedQueryFactory.java │ │ │ │ └── VariantQueryFactory.java │ │ │ │ ├── filter │ │ │ │ ├── FilterContext.java │ │ │ │ ├── InternalResultFilterAdapter.java │ │ │ │ ├── NumberResultFilter.java │ │ │ │ ├── NumberResultFilterAdapter.java │ │ │ │ ├── PathResultFilter.java │ │ │ │ ├── PathResultFilterAdapter.java │ │ │ │ ├── TermResultFilter.java │ │ │ │ └── TermResultFilterAdapter.java │ │ │ │ └── sort │ │ │ │ └── SortInstruction.java │ │ │ └── util │ │ │ ├── ConfigurationException.java │ │ │ ├── DefaultLinkBuilder.java │ │ │ ├── ESQueryUtils.java │ │ │ ├── FacetEntrySorter.java │ │ │ ├── InternalSearchParams.java │ │ │ ├── NotFoundException.java │ │ │ ├── SearchParamsParser.java │ │ │ └── TraceOptions.java │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ ├── de.cxp.ocs.spi.search.UserQueryAnalyzer │ │ │ └── de.cxp.ocs.spi.search.UserQueryPreprocessor │ │ ├── application-preset.yml │ │ ├── application.yml │ │ ├── bootstrap.properties │ │ └── logback-spring.xml │ └── test │ └── java │ └── de │ └── cxp │ └── ocs │ ├── elasticsearch │ ├── QueryStringParserTest.java │ ├── facets │ │ ├── FacetCreatorInitializerTest.java │ │ ├── FacetDependencyFilterTest.java │ │ └── helper │ │ │ └── NumericFacetEntryBuilderTest.java │ ├── prodset │ │ └── HeroProductHandlerTest.java │ └── query │ │ ├── CodePointFilterUserQueryPreprocessorTest.java │ │ ├── NonAlphanumericStripPreprocessorTest.java │ │ ├── QueryStringBuilderTest.java │ │ ├── StandardQueryFactoryTest.java │ │ └── analyzer │ │ ├── AnalyzerUtil.java │ │ ├── AsciifyQuerqyQueryAnalyzerTest.java │ │ ├── NonAlphanumericWordSplitAnalyzerTest.java │ │ ├── QuerqyQueryExpanderBuilder.java │ │ └── QuerqyQueryExpanderTest.java │ └── util │ ├── FacetEntrySorterTest.java │ ├── SearchParamsParserTest.java │ ├── SearchQueryBuilderTest.java │ ├── TestUtils.java │ └── TraceOptionsTest.java └── suggest-service-parent ├── ocs-suggest-data-provider ├── pom.xml └── src │ └── main │ ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ └── elasticsearch │ │ ├── ElasticsearchSuggestDataProvider.java │ │ └── SettingsProxy.java │ └── resources │ ├── META-INF │ └── services │ │ └── de.cxp.ocs.smartsuggest.spi.SuggestDataProvider │ └── ocs-suggest.default.properties ├── pom.xml ├── s3-suggest-archive-provider ├── pom.xml └── src │ ├── main │ ├── java │ │ └── de │ │ │ └── cxp │ │ │ └── ocs │ │ │ └── smartsuggest │ │ │ └── S3ArchiveProvider.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── de.cxp.ocs.smartsuggest.spi.IndexArchiveProvider │ └── test │ └── java │ └── de │ └── cxp │ └── ocs │ └── smartsuggest │ ├── S3ArchiveProviderTest.java │ └── Util.java ├── smartsuggest-lib ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── cxp │ │ └── ocs │ │ └── smartsuggest │ │ ├── QuerySuggestManager.java │ │ ├── limiter │ │ ├── ConfigurableShareLimiter.java │ │ ├── CutOffLimiter.java │ │ ├── GroupedCutOffLimiter.java │ │ ├── Limiter.java │ │ └── SuggestDeduplicator.java │ │ ├── monitoring │ │ ├── DistributionSummaryAdapter.java │ │ ├── Instrumentable.java │ │ └── MeterRegistryAdapter.java │ │ ├── querysuggester │ │ ├── CompoundQuerySuggester.java │ │ ├── GroupingSuggester.java │ │ ├── NoopQuerySuggester.java │ │ ├── QueryIndexer.java │ │ ├── QuerySuggester.java │ │ ├── QuerySuggesterProxy.java │ │ ├── SuggestException.java │ │ ├── SuggesterEngine.java │ │ ├── SuggesterFactory.java │ │ ├── Suggestion.java │ │ ├── lucene │ │ │ ├── LuceneQuerySuggester.java │ │ │ ├── LuceneSuggesterFactory.java │ │ │ ├── PerfResult.java │ │ │ ├── SuggestionBestMatchIterator.java │ │ │ ├── SuggestionIterator.java │ │ │ └── SuggestionVariantIterator.java │ │ └── modified │ │ │ └── ModifiedTermsService.java │ │ ├── spi │ │ ├── AbstractDataProvider.java │ │ ├── CommonPayloadFields.java │ │ ├── CompoundIndexArchiveProvider.java │ │ ├── DatedData.java │ │ ├── IndexArchive.java │ │ ├── IndexArchiveProvider.java │ │ ├── MergingSuggestDataProvider.java │ │ ├── SuggestConfig.java │ │ ├── SuggestConfigProvider.java │ │ ├── SuggestData.java │ │ ├── SuggestDataProvider.java │ │ ├── SuggestRecord.java │ │ ├── standard │ │ │ ├── CompoundSuggestConfigProvider.java │ │ │ └── DefaultSuggestConfigProvider.java │ │ └── test │ │ │ ├── IndexArchiveProviderTestKit.java │ │ │ └── SdpMock.java │ │ ├── updater │ │ └── SuggestionsUpdater.java │ │ └── util │ │ ├── FileUtils.java │ │ └── Util.java │ └── test │ ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ └── smartsuggest │ │ ├── InstrumentationTest.java │ │ ├── QuerySuggestManagerTest.java │ │ ├── RemoteSuggestDataProviderSimulation.java │ │ ├── limiter │ │ ├── ConfigurableShareLimiterTest.java │ │ ├── GroupedCutOffLimiterTest.java │ │ └── SuggestDeduplicatorTest.java │ │ ├── querysuggester │ │ ├── CompoundQuerySuggesterTest.java │ │ └── lucene │ │ │ ├── LuceneQuerySuggesterPersistenceTest.java │ │ │ └── LuceneQuerySuggesterTest.java │ │ ├── spi │ │ ├── IndexArchiveProviderTestKitTest.java │ │ └── MergingSuggestDataProviderTest.java │ │ ├── updater │ │ ├── LocalCompoundIndexArchiveProvider.java │ │ ├── LocalIndexArchiveProvider.java │ │ └── SuggestionsUpdaterTest.java │ │ └── util │ │ ├── FakeSuggester.java │ │ ├── FakeSuggesterFactory.java │ │ ├── TestDataProvider.java │ │ ├── TestSetupUtil.java │ │ └── UtilTest.java │ └── resources │ ├── log4j.properties │ └── stopwords │ └── de.txt └── suggest-service ├── pom.xml └── src ├── main ├── java │ └── de │ │ └── cxp │ │ └── ocs │ │ ├── Application.java │ │ ├── InfoReqHandler.java │ │ └── suggest │ │ ├── SuggestServiceImpl.java │ │ └── SuggestServiceProperties.java └── resources │ ├── config.yml │ ├── logback.xml │ ├── reference.conf │ └── static │ ├── auto-complete.css │ ├── auto-complete.min.js │ ├── demo.html │ └── favicon.png └── test └── java └── de └── cxp └── ocs └── suggest └── SuggestServicePropertiesTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # eclipse files 26 | .project 27 | .settings 28 | .classpath 29 | .checkstyle 30 | **/.classpath 31 | **/.factorypath 32 | 33 | # idea files 34 | .idea 35 | *.iml 36 | 37 | # maven 38 | target 39 | bin 40 | 41 | # custom application properties for testing 42 | application-*properties 43 | application-*yml 44 | application-*yaml 45 | rules 46 | indexer-service/src/main/resources/elasticsearch/_index_templates/custom* 47 | indexer-service/src/main/resources/elasticsearch/_component_templates/ocs_custom* 48 | 49 | #kubernetes ops custom files 50 | operations/k8s/custom/ 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Open Commerce Search Stack 3 | 4 | The Open Commerce Search Stack (OCSS) is a small abstraction layer on top of existing open source search solutions (Elasticsearch, Lucene and Querqy) that removes the hastle of dealing with the details of information retrieval. It was designed to handle most problems of search in e-commerce using known best practices. 5 | The API first approach aims for simplicity and broad adaption. 6 | [More information about the motivation and background of OCSS can be found in our blog](https://blog.searchhub.io/introducing-open-commerce-search-stack-ocss). 7 | 8 | ## [Read full Documentation](https://commerceexperts.github.io/open-commerce-search/) 9 | -------------------------------------------------------------------------------- /config-service/src/main/java/de/cxp/ocs/Application.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @EnableConfigServer 8 | @SpringBootApplication 9 | public class Application { 10 | public static void main(String[] args) { 11 | SpringApplication.run(Application.class, args); 12 | } 13 | } -------------------------------------------------------------------------------- /config-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8530 2 | spring.cloud.config.server.git.uri=${SCCS_GIT_URL} 3 | spring.cloud.config.server.git.username=${SCCS_GIT_USERNAME} 4 | spring.cloud.config.server.git.password=${SCCS_GIT_PASSWORD} -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile 2 | Gemfile.lock 3 | _site/ 4 | .jekyll-metadata 5 | .openapi-generator-ignore 6 | .openapi-generator/ 7 | -------------------------------------------------------------------------------- /docs/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | -------------------------------------------------------------------------------- /docs/.openapi-generator/FILES: -------------------------------------------------------------------------------- 1 | index.html 2 | -------------------------------------------------------------------------------- /docs/.openapi-generator/VERSION: -------------------------------------------------------------------------------- 1 | 5.3.0-SNAPSHOT -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs Writing Help 2 | 3 | To test the final output, a jekyll server can be started via docker that publishes the docs at `localhost:4000`. 4 | Start the docker container from this 'docs' directory like that: 5 | 6 | docker run -it --name "jekyll-ocss-docs" --volume="$PWD/vendor/bundle:/usr/local/bundle" --volume="$PWD:/srv/jekyll" --publish [::1]:4000:4000 jekyll/jekyll bash 7 | 8 | Inside the started container you get an interactive shell. Run these commands: 9 | 10 | bundle install 11 | bundle exec jekyll serve -H 0.0.0.0 --livereload --incremental 12 | 13 | If you won't delete the container, you can simply start it again and run the last command again. This avoids the long running 'bundle install' step. 14 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate 2 | title: Open Commerce Search Stack 3 | logo_src: /open-commerce-search/assets/ocss_logo_only.png 4 | description: The Documentation 5 | show_downloads: false 6 | repository: CommerceExperts/open-commerce-search 7 | -------------------------------------------------------------------------------- /docs/apidocs/element-list: -------------------------------------------------------------------------------- 1 | de.cxp.ocs 2 | de.cxp.ocs.api 3 | de.cxp.ocs.api.indexer 4 | de.cxp.ocs.api.searcher 5 | de.cxp.ocs.client 6 | de.cxp.ocs.client.deserializer 7 | de.cxp.ocs.conf 8 | de.cxp.ocs.conf.converter 9 | de.cxp.ocs.config 10 | de.cxp.ocs.config.logging 11 | de.cxp.ocs.controller 12 | de.cxp.ocs.elasticsearch 13 | de.cxp.ocs.elasticsearch.facets 14 | de.cxp.ocs.elasticsearch.prodset 15 | de.cxp.ocs.elasticsearch.query 16 | de.cxp.ocs.elasticsearch.query.analyzer 17 | de.cxp.ocs.elasticsearch.query.builder 18 | de.cxp.ocs.elasticsearch.query.filter 19 | de.cxp.ocs.elasticsearch.query.model 20 | de.cxp.ocs.indexer 21 | de.cxp.ocs.indexer.model 22 | de.cxp.ocs.model.index 23 | de.cxp.ocs.model.params 24 | de.cxp.ocs.model.result 25 | de.cxp.ocs.model.suggest 26 | de.cxp.ocs.plugin 27 | de.cxp.ocs.preprocessor 28 | de.cxp.ocs.preprocessor.impl 29 | de.cxp.ocs.preprocessor.util 30 | de.cxp.ocs.smartsuggest 31 | de.cxp.ocs.smartsuggest.limiter 32 | de.cxp.ocs.smartsuggest.monitoring 33 | de.cxp.ocs.smartsuggest.querysuggester 34 | de.cxp.ocs.smartsuggest.querysuggester.lucene 35 | de.cxp.ocs.smartsuggest.querysuggester.modified 36 | de.cxp.ocs.smartsuggest.spi 37 | de.cxp.ocs.smartsuggest.updater 38 | de.cxp.ocs.smartsuggest.util 39 | de.cxp.ocs.spi.indexer 40 | de.cxp.ocs.spi.search 41 | de.cxp.ocs.util 42 | -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-bg_glass_65_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-bg_glass_65_dadada_1x400.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /docs/apidocs/jquery/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/docs/apidocs/jquery/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /docs/apidocs/overview-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |26 | * Settings for the single document pre/post processors. As a key the full 27 | * canonical class name of the according processor is expected. The value is 28 | * a custom string-to-string map with arbitrary settings for a processor. 29 | *
30 | *31 | * Check the java-doc of the available processors for more details. 32 | *
33 | */ 34 | private final Map{ 14 | 15 | public TextMatchQuery(Q masterQuery, Q variantQuery, boolean isWithSpellCorrection, boolean acceptNoResult) { 16 | this(masterQuery, variantQuery, isWithSpellCorrection, acceptNoResult, null); 17 | } 18 | 19 | @Setter 20 | private Q masterLevelQuery; 21 | 22 | private Q variantLevelQuery; 23 | 24 | /** 25 | * Set to 'true' if the according queries already consider spelling errors, 26 | * e.g. because a fuzzy search is done anyways. 27 | */ 28 | private boolean isWithSpellCorrection = false; 29 | 30 | // TODO: do this with configuration so it's not necessary to decide at the 31 | // builder level 32 | private boolean acceptNoResult = false; 33 | 34 | private String queryDescription; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/indexer/model/FacetEntry.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.indexer.model; 2 | 3 | import java.util.Collection; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.NonNull; 9 | import lombok.experimental.Accessors; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Data 14 | @Accessors(chain = true) 15 | public class FacetEntry{ 16 | 17 | @NonNull 18 | private String name; 19 | 20 | // optional attribute ID 21 | private String id; 22 | 23 | @NonNull 24 | private Object value; 25 | 26 | public FacetEntry(String name) { 27 | this.name = name; 28 | } 29 | 30 | public FacetEntry(String name, T value) { 31 | this.name = name; 32 | this.value = value; 33 | } 34 | 35 | public FacetEntry(String name, Collection values) { 36 | this.name = name; 37 | this.value = values; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/indexer/model/IndexableItem.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.indexer.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.ToString; 10 | 11 | /** 12 | * {@link DataItem} that can be used to be indexed directly. This is not 13 | * the case for sub-items such as {@link VariantItem}s 14 | */ 15 | @AllArgsConstructor 16 | @Data 17 | @ToString(callSuper = true) 18 | @EqualsAndHashCode(callSuper = true) 19 | public class IndexableItem extends DataItem { 20 | 21 | private final String id; 22 | 23 | /** 24 | * A list of categories a item belongs to. 25 | */ 26 | private final List > pathFacetData = new ArrayList<>(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/indexer/model/MasterItem.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.indexer.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NonNull; 9 | 10 | @Data 11 | @EqualsAndHashCode(callSuper = true) 12 | public class MasterItem extends IndexableItem { 13 | 14 | public MasterItem(@NonNull String id) { 15 | super(id); 16 | } 17 | 18 | /** 19 | * A list of variants that belong to this master item. Can be empty 20 | * in cases no variants are managed. 21 | */ 22 | private final List variants = new ArrayList<>(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/indexer/model/VariantItem.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.indexer.model; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor 7 | @EqualsAndHashCode(callSuper = true) 8 | public class VariantItem extends DataItem { 9 | 10 | public VariantItem(MasterItem master) { 11 | this.master = master; 12 | } 13 | 14 | /** 15 | * The master item that belongs to this variant. 16 | */ 17 | private MasterItem master; 18 | 19 | public MasterItem getMaster() { 20 | return master; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/spi/search/ConfigurableExtension.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.spi.search; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * super-interface for all extension interfaces that optionally can accepts 7 | * custom settings. 8 | */ 9 | public interface ConfigurableExtension { 10 | 11 | default void initialize(Map settings) {} 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/spi/search/ESQueryFactory.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.spi.search; 2 | 3 | import java.util.Map; 4 | 5 | import org.elasticsearch.index.query.QueryBuilder; 6 | 7 | import de.cxp.ocs.config.FieldConfigAccess; 8 | import de.cxp.ocs.config.QueryBuildingSetting; 9 | import de.cxp.ocs.elasticsearch.model.query.ExtendedQuery; 10 | import de.cxp.ocs.elasticsearch.query.TextMatchQuery; 11 | 12 | /** 13 | * 14 | * A reusable query factory that receives the analyzed user query to build 15 | * Elasticsearch queries (one for Master level and one for the variant level). 16 | *
17 | *18 | * The implementation must have a no-args-constructor and must be thread-safe. 19 | */ 20 | public interface ESQueryFactory { 21 | 22 | void initialize(String name, Map
settings, Map fieldWeights, FieldConfigAccess fieldConfig); 23 | 24 | TextMatchQuery createQuery(ExtendedQuery parsedQuery); 25 | 26 | boolean allowParallelSpellcheckExecution(); 27 | 28 | String getName(); 29 | } 30 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/spi/search/RescorerProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.spi.search; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | 6 | import org.elasticsearch.search.rescore.RescorerBuilder; 7 | 8 | public interface RescorerProvider extends ConfigurableExtension { 9 | 10 | // TODO: fix leaky abstraction: remove dependency to Elasticsearch 11 | Optional > get(String userQuery, Map customParams); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/spi/search/SearchConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.spi.search; 2 | 3 | import java.util.Set; 4 | 5 | import de.cxp.ocs.config.SearchConfiguration; 6 | 7 | /** 8 | * SPI Interface to provide search configurations. 9 | */ 10 | public interface SearchConfigurationProvider { 11 | 12 | /** 13 | * Gives access to the default configuration provider. 14 | * 15 | * @param defaultSearchConfigrationProvider 16 | * default configuration provider 17 | */ 18 | void setDefaultProvider(SearchConfigurationProvider defaultSearchConfigrationProvider); 19 | 20 | /** 21 | * @return 22 | * the list of all configured tenants 23 | */ 24 | Set getConfiguredTenants(); 25 | 26 | /** 27 | * @param tenant 28 | * tenant name 29 | * @return 30 | * the search configuration for the specified tenant 31 | */ 32 | SearchConfiguration getTenantSearchConfiguration(String tenant); 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/spi/search/UserQueryAnalyzer.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.spi.search; 2 | 3 | import de.cxp.ocs.elasticsearch.model.query.ExtendedQuery; 4 | 5 | public interface UserQueryAnalyzer extends ConfigurableExtension { 6 | 7 | ExtendedQuery analyze(String userQuery); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ocs-plugin-spi/src/main/java/de/cxp/ocs/spi/search/UserQueryPreprocessor.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.spi.search; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Can be used to modify the user query prior it is processed by the 7 | * UserQueryAnalyzer, for example to normalize the query. 8 | */ 9 | public interface UserQueryPreprocessor extends ConfigurableExtension { 10 | 11 | String preProcess(String userQuery); 12 | 13 | /** 14 | * Extended preProcess method that also get the meta-data map passed that can be filled with relevant meta data. 15 | * That meta data will then be part of the search response. 16 | * 17 | * @param userQuery 18 | * @param resultMetaData 19 | * @return 20 | */ 21 | default String preProcess(String userQuery, Map resultMetaData) { 22 | return preProcess(userQuery); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /ocss-frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /ocss-frontend/.env.example: -------------------------------------------------------------------------------- 1 | SUGGEST_API_AUTH="" 2 | SUGGEST_API_URL="http://127.0.0.1:8536" 3 | SEARCH_API_AUTH="" 4 | SEARCH_API_URL="http://127.0.0.1:8534" 5 | NEXTAUTH_SECRET="" 6 | NEXTAUTH_URL="" 7 | GITHUB_OAUTH_ID="" 8 | GITHUB_OAUTH_SECRET="" 9 | -------------------------------------------------------------------------------- /ocss-frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | .cache 3 | public 4 | node_modules 5 | *.esm.js 6 | -------------------------------------------------------------------------------- /ocss-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "root": true, 4 | "extends": [ 5 | "next/core-web-vitals", 6 | "prettier", 7 | "plugin:tailwindcss/recommended" 8 | ], 9 | "plugins": ["tailwindcss", "unused-imports"], 10 | "rules": { 11 | "@next/next/no-html-link-for-pages": "off", 12 | "react/jsx-key": "off", 13 | "tailwindcss/no-custom-classname": "off", 14 | "no-unused-vars": "off", 15 | "unused-imports/no-unused-imports": "error", 16 | "unused-imports/no-unused-vars": [ 17 | "warn", 18 | { 19 | "vars": "all", 20 | "varsIgnorePattern": "^_", 21 | "args": "after-used", 22 | "argsIgnorePattern": "^_" 23 | } 24 | ] 25 | }, 26 | "settings": { 27 | "tailwindcss": { 28 | "callees": ["cn"], 29 | "config": "./tailwind.config.js" 30 | }, 31 | "next": { 32 | "rootDir": ["./"] 33 | } 34 | }, 35 | "overrides": [ 36 | { 37 | "files": ["*.ts", "*.tsx"], 38 | "parser": "@typescript-eslint/parser" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /ocss-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # intellij 4 | .idea/ 5 | 6 | # dependencies 7 | node_modules 8 | .pnp 9 | .pnp.js 10 | 11 | # testing 12 | coverage 13 | 14 | # next.js 15 | .next/ 16 | out/ 17 | build 18 | 19 | # ts 20 | tsconfig.tsbuildinfo 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | .pnpm-debug.log* 31 | 32 | # local env files 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # turbo 39 | .turbo 40 | 41 | .contentlayer 42 | .env 43 | .env-* 44 | 45 | # VI 46 | .swp -------------------------------------------------------------------------------- /ocss-frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | cache 2 | .cache 3 | package.json 4 | package-lock.json 5 | public 6 | CHANGELOG.md 7 | .yarn 8 | dist 9 | node_modules 10 | .next 11 | build 12 | .contentlayer -------------------------------------------------------------------------------- /ocss-frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bradlc.vscode-tailwindcss", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "ms-azuretools.vscode-docker" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /ocss-frontend/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug server-side", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "npm run dev" 9 | }, 10 | { 11 | "name": "Next.js: debug client-side", 12 | "type": "chrome", 13 | "request": "launch", 14 | "url": "http://localhost:3000" 15 | }, 16 | { 17 | "name": "Next.js: debug full stack", 18 | "type": "node-terminal", 19 | "request": "launch", 20 | "command": "npm run dev", 21 | "serverReadyAction": { 22 | "pattern": "started server on .+, url: (https?://.+)", 23 | "uriFormat": "%s", 24 | "action": "debugWithChrome" 25 | } 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /ocss-frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/config/[owner]/[repo]/indexer/general/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useSearchParams } from "next/navigation" 4 | import { useRecoilState } from "recoil" 5 | 6 | import { SearchParamsMap } from "@/types/searchParams" 7 | import { isConfigurationLoadedState } from "@/lib/global-state" 8 | import { Separator } from "@/components/ui/separator" 9 | import { GeneralForm } from "@/components/configuration/general-form" 10 | import Loader from "@/components/misc/loader" 11 | 12 | export default function GeneralIndexerSettings() { 13 | const [isConfigurationLoaded, setIsConfigurationLoadedState] = useRecoilState( 14 | isConfigurationLoadedState 15 | ) 16 | const searchParams = useSearchParams() 17 | 18 | const configParam = searchParams.get(SearchParamsMap.config) 19 | 20 | return ( 21 | 22 |37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/config/[owner]/[repo]/indexer/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | type IndexerConfigurationPageProps = { 4 | params: { repo: string; owner: string } 5 | } 6 | 7 | export default async function IndexerConfigurationPage({ 8 | params: { repo, owner }, 9 | }: IndexerConfigurationPageProps) { 10 | return redirect(`/config/${owner}/${repo}/indexer/general`) 11 | } 12 | -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/config/[owner]/[repo]/loading.tsx: -------------------------------------------------------------------------------- 1 | import Loader from "@/components/misc/loader" 2 | 3 | export default Loader 4 | -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/config/[owner]/[repo]/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | type ConfigurationPageProps = { 4 | params: { repo: string; owner: string } 5 | } 6 | 7 | export default async function ConfigurationPage({ 8 | params: { repo, owner }, 9 | }: ConfigurationPageProps) { 10 | return redirect( 11 | `/config/${owner}/${repo}/search/query-processing-configuration` 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/config/[owner]/[repo]/search/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | type SearchConfigurationPageProps = { 4 | params: { repo: string; owner: string } 5 | } 6 | 7 | export default async function SearchConfigurationPage({ 8 | params: { repo, owner }, 9 | }: SearchConfigurationPageProps) { 10 | return redirect( 11 | `/config/${owner}/${repo}/search/query-processing-configuration` 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/config/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation" 2 | import { env } from "@/env.mjs" 3 | 4 | import RepoSelection from "@/components/configuration/repo-selection" 5 | 6 | export default async function RepoSelectionPage() { 7 | if ( 8 | !( 9 | env.GITHUB_OAUTH_ID && 10 | env.GITHUB_OAUTH_SECRET && 11 | env.NEXTAUTH_URL && 12 | env.NEXTAUTH_SECRET 13 | ) 14 | ) { 15 | notFound() 16 | } 17 | 18 | return ( 19 |23 |28 |General
24 |25 | Manage general settings of your indexer service. 26 |
27 |29 | {isConfigurationLoaded ? ( 30 | 31 | ) : ( 32 | 33 |35 | )} 36 |34 | 20 | {/* @ts-ignore Async server component */} 21 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /ocss-frontend/app/(configuration)/signin/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation" 2 | import { env } from "@/env.mjs" 3 | 4 | import SignInButton from "@/components/configuration/sign-in-button" 5 | 6 | export default function SignIn() { 7 | if ( 8 | !( 9 | env.GITHUB_OAUTH_ID && 10 | env.GITHUB_OAUTH_SECRET && 11 | env.NEXTAUTH_URL && 12 | env.NEXTAUTH_SECRET 13 | ) 14 | ) { 15 | notFound() 16 | } 17 | 18 | return ( 19 |22 | 20 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /ocss-frontend/app/(demoshop)/product/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Icons } from "@/components/misc/icons" 2 | 3 | export default function ProductPageLoading() { 4 | return ( 5 |21 |27 |Configuration
22 |Please sign in using your Github account to configure your OCSS.
23 |24 |26 |25 | 6 |8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /ocss-frontend/app/(demoshop)/product/[id]/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFoundPage() { 2 | return ( 3 |7 | 4 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /ocss-frontend/app/(proxy)/suggest-api/[...suggest-api]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server" 2 | import { env } from "@/env.mjs" 3 | 4 | export async function GET(request: Request) { 5 | if (!env.SEARCH_API_URL) { 6 | return NextResponse.error() 7 | } 8 | 9 | const requestURL = new URL(request.url) 10 | const res = await fetch( 11 | `${env.SUGGEST_API_URL}${requestURL.pathname}${requestURL.search}`, 12 | { 13 | headers: { 14 | Authorization: `Basic ${env.SUGGEST_API_AUTH}`, 15 | Accept: "application/json", 16 | "Content-Type": "application/json", 17 | }, 18 | } 19 | ) 20 | 21 | if (res.status !== 200) { 22 | return NextResponse.error() 23 | } 24 | 25 | const data = await res.json() 26 | 27 | return NextResponse.json(data) 28 | } 29 | -------------------------------------------------------------------------------- /ocss-frontend/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | 3 | import { authOptions } from "@/lib/auth" 4 | 5 | const handler = NextAuth(authOptions) 6 | 7 | export { handler as GET, handler as POST } 8 | -------------------------------------------------------------------------------- /ocss-frontend/app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | export default function ErrorPage() { 4 | return ( 5 |404
5 |6 | This product could not be found. 7 |
8 |6 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /ocss-frontend/app/loading.tsx: -------------------------------------------------------------------------------- 1 | import Loader from "@/components/misc/loader" 2 | 3 | export default Loader 4 | -------------------------------------------------------------------------------- /ocss-frontend/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFoundPage() { 2 | return ( 3 |Oops!
7 |8 | Something very bad happened. We apologize for any inconveniences caused 9 | by this system error. 10 |
11 |4 | 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /ocss-frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tailwind": { 6 | "config": "tailwind.config.js", 7 | "css": "app/globals.css", 8 | "baseColor": "slate", 9 | "cssVariables": true 10 | }, 11 | "aliases": { 12 | "components": "@/components", 13 | "utils": "@/lib/utils" 14 | } 15 | } -------------------------------------------------------------------------------- /ocss-frontend/components/configuration/boost-mode-configuration.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { BoostMode, boostModes } from "@/types/config" 4 | import { 5 | Select, 6 | SelectContent, 7 | SelectItem, 8 | SelectTrigger, 9 | SelectValue, 10 | } from "@/components/ui/select" 11 | 12 | type BoostModeConfigurationProps = { 13 | boostMode?: BoostMode 14 | onBoostModeChange?: (newBoostMode: BoostMode) => void 15 | } 16 | 17 | export default function BoostModeConfiguration({ 18 | boostMode, 19 | onBoostModeChange, 20 | }: BoostModeConfigurationProps) { 21 | return ( 22 | <> 23 |404
5 |This page could not be found.
6 |Boost mode
24 | 36 | > 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /ocss-frontend/components/configuration/commit-ref.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useSearchParams } from "next/navigation" 4 | 5 | import { SearchParamsMap } from "@/types/searchParams" 6 | 7 | export default function CommitRef() { 8 | const searchParams = useSearchParams() 9 | const commitParam = searchParams.get(SearchParamsMap.commit) 10 | 11 | return {commitParam} 12 | } 13 | -------------------------------------------------------------------------------- /ocss-frontend/components/configuration/max-facets-configuration.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Input } from "../ui/input" 4 | 5 | type MaxFacetsConfigurationProps = { 6 | onValueChange: (value: string) => void 7 | value: number | undefined 8 | } 9 | 10 | export default function MaxFacetsConfiguration({ 11 | onValueChange, 12 | value, 13 | }: MaxFacetsConfigurationProps) { 14 | return ( 15 | <> 16 |Max facets
17 | { 20 | onValueChange(e.target.value) 21 | }} 22 | placeholder="Enter max facets" 23 | type="number" 24 | /> 25 | > 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /ocss-frontend/components/configuration/score-mode-configuration.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ScoreMode, scoreModes } from "@/types/config" 4 | import { 5 | Select, 6 | SelectContent, 7 | SelectItem, 8 | SelectTrigger, 9 | SelectValue, 10 | } from "@/components/ui/select" 11 | 12 | type ScoreModeConfigurationProps = { 13 | scoreMode?: ScoreMode 14 | onScoreModeChange?: (newScoreMode: ScoreMode) => void 15 | } 16 | 17 | export default function ScoreModeConfiguration({ 18 | scoreMode, 19 | onScoreModeChange, 20 | }: ScoreModeConfigurationProps) { 21 | return ( 22 | <> 23 |Score mode
24 | 36 | > 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /ocss-frontend/components/configuration/sign-in-button.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { signIn } from "next-auth/react" 4 | 5 | import { Button } from "@/components/ui/button" 6 | import { Icons } from "@/components/misc/icons" 7 | 8 | export default function SignInButton() { 9 | return ( 10 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /ocss-frontend/components/configuration/sign-out-button.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { LogOutIcon } from "lucide-react" 4 | import { signOut } from "next-auth/react" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { Button } from "@/components/ui/button" 8 | 9 | type SignOutButtonProps = { 10 | className: string 11 | } 12 | 13 | export default function SignOutButton({ className }: SignOutButtonProps) { 14 | return ( 15 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /ocss-frontend/components/demoshop/clear-search-query-button.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useRouter } from "next/navigation" 4 | import { Eraser } from "lucide-react" 5 | 6 | import { SearchParamsMap } from "@/types/searchParams" 7 | 8 | import { Button } from "../ui/button" 9 | 10 | type ClearSearchQueryButtonProps = { 11 | tenant: string 12 | } 13 | 14 | export default function ClearSearchQueryButton({ 15 | tenant, 16 | }: ClearSearchQueryButtonProps) { 17 | const router = useRouter() 18 | 19 | return ( 20 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /ocss-frontend/components/demoshop/product-data-field-configuration-initialization-client.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect } from "react" 4 | import { useRecoilState } from "recoil" 5 | 6 | import { ProductDataFieldConfiguration } from "@/types/config" 7 | import { productDataFieldConfigurationState } from "@/lib/global-state" 8 | 9 | type ProductDataFieldConfigurationInitializationClientProps = { 10 | initialProductDataFieldConfiguration: ProductDataFieldConfiguration[] 11 | } 12 | 13 | export default function ProductDataFieldConfigurationInitializationClient({ 14 | initialProductDataFieldConfiguration, 15 | }: ProductDataFieldConfigurationInitializationClientProps) { 16 | const [productDataFieldConfiguration, setProductDataFieldConfiguration] = 17 | useRecoilState(productDataFieldConfigurationState) 18 | 19 | useEffect(() => { 20 | setProductDataFieldConfiguration(initialProductDataFieldConfiguration) 21 | }, [initialProductDataFieldConfiguration, setProductDataFieldConfiguration]) 22 | 23 | return <>> 24 | } 25 | -------------------------------------------------------------------------------- /ocss-frontend/components/demoshop/product-data-field-configuration-initialization.tsx: -------------------------------------------------------------------------------- 1 | import { getProductDataFieldConfigurationFromCookie } from "@/lib/product-card-configuration" 2 | 3 | import ProductDataFieldConfigurationInitializationClient from "./product-data-field-configuration-initialization-client" 4 | 5 | export default async function ProductDataFieldConfigurationInitialization() { 6 | const initialProductDataFieldConfiguration = 7 | await getProductDataFieldConfigurationFromCookie() 8 | 9 | return ( 10 | <> 11 |16 | > 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /ocss-frontend/components/demoshop/sortable-item.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { UniqueIdentifier } from "@dnd-kit/core" 3 | import { useSortable } from "@dnd-kit/sortable" 4 | import { CSS } from "@dnd-kit/utilities" 5 | 6 | type SortableItemProps = { 7 | identifier: UniqueIdentifier 8 | } & React.ComponentProps<"div"> 9 | 10 | export function SortableItem({ identifier, ...props }: SortableItemProps) { 11 | const { 12 | attributes, 13 | listeners, 14 | setNodeRef, 15 | transform, 16 | transition, 17 | isDragging, 18 | } = useSortable({ id: identifier }) 19 | 20 | const style = { 21 | transform: CSS.Translate.toString(transform), 22 | transition, 23 | } 24 | 25 | return ( 26 | 34 | {props.children} 35 |36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/auth-buttons.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { signIn, signOut } from "next-auth/react" 4 | 5 | import { Button } from "../ui/button" 6 | 7 | export const LoginButton = () => { 8 | return ( 9 | 10 | ) 11 | } 12 | 13 | export const LogoutButton = () => { 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/config-button.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import { env } from "@/env.mjs" 3 | import { Settings } from "lucide-react" 4 | 5 | export default function ConfigButton() { 6 | let href = "/config" 7 | if (env.DEFAULT_CONFIG_REPO_OWNER && env.DEFAULT_CONFIG_REPO_NAME) { 8 | href = `/config/${env.DEFAULT_CONFIG_REPO_OWNER}/${env.DEFAULT_CONFIG_REPO_NAME}` 9 | } 10 | 11 | return ( 12 | <> 13 | {env.GITHUB_OAUTH_ID && 14 | env.GITHUB_OAUTH_SECRET && 15 | env.NEXTAUTH_URL && 16 | env.NEXTAUTH_SECRET && ( 17 | 18 |19 |
22 | 23 | )} 24 | > 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/layout.tsx: -------------------------------------------------------------------------------- 1 | import { SiteHeader } from "@/components/misc/site-header" 2 | 3 | type LayoutProps = { 4 | children: React.ReactNode 5 | } 6 | 7 | export function Layout({ children }: LayoutProps) { 8 | return ( 9 | <> 10 |20 | Config 21 | 11 | {children} 12 | > 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/loader.tsx: -------------------------------------------------------------------------------- 1 | import { Icons } from "@/components/misc/icons" 2 | 3 | export default function Loader() { 4 | return ( 5 |6 |8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/main-nav.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | import Link from "next/link" 3 | 4 | import { siteConfig } from "@/config/site" 5 | import { getTenants } from "@/lib/search-api" 6 | import { Icons } from "@/components/misc/icons" 7 | 8 | import DemoshopButton from "../demoshop/demoshop-button" 9 | import ConfigButton from "./config-button" 10 | 11 | export async function MainNav() { 12 | const tenants = await getTenants() 13 | 14 | return ( 15 |7 | 16 | 17 |27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/next-auth-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { SessionProvider } from "next-auth/react" 4 | 5 | type Props = { 6 | children?: React.ReactNode 7 | } 8 | 9 | export const NextAuthProvider = ({ children }: Props) => { 10 | return18 | 19 | {siteConfig.name} 20 | 21 | 22 | 26 | {children} 11 | } 12 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { RecoilRoot } from "recoil" 4 | 5 | import { NextAuthProvider } from "./next-auth-provider" 6 | import { ThemeProvider } from "./theme-provider" 7 | 8 | type ProvidersProps = { 9 | children: React.ReactNode 10 | } 11 | 12 | export default function Providers({ children }: ProvidersProps) { 13 | return ( 14 |15 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === "production") return <>> 3 | 4 | return ( 5 |16 | 18 |{children} 17 |6 |15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ThemeProvider as NextThemesProvider } from "next-themes" 4 | import { ThemeProviderProps } from "next-themes/dist/types" 5 | 6 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 7 | returnxs7 |8 | sm 9 |10 |md11 |lg12 |xl13 |2xl14 |{children} 8 | } 9 | -------------------------------------------------------------------------------- /ocss-frontend/components/misc/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | 5 | import { Button } from "@/components/ui/button" 6 | import { Icons } from "@/components/misc/icons" 7 | 8 | export function ThemeToggle() { 9 | const { setTheme, theme } = useTheme() 10 | 11 | return ( 12 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { Check } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 27 | )) 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 29 | 30 | export { Checkbox } 31 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const HoverCard = HoverCardPrimitive.Root 9 | 10 | const HoverCardTrigger = HoverCardPrimitive.Trigger 11 | 12 | const HoverCardContent = React.forwardRef< 13 | React.ElementRef24 | 26 |25 | , 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 26 | )) 27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName 28 | 29 | export { HoverCard, HoverCardTrigger, HoverCardContent } 30 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef ( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef , 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef , 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes ) { 7 | return ( 8 | 12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SliderPrimitive from "@radix-ui/react-slider" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Slider = React.forwardRef< 9 | React.ElementRef , 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | )) 26 | Slider.displayName = SliderPrimitive.Root.displayName 27 | 28 | export { Slider } 29 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitives from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef21 | 23 |22 | 24 | , 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 26 | )) 27 | Switch.displayName = SwitchPrimitives.Root.displayName 28 | 29 | export { Switch } 30 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes25 | {} 7 | 8 | const Textarea = React.forwardRef ( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 | 19 | ) 20 | } 21 | ) 22 | Textarea.displayName = "Textarea" 23 | 24 | export { Textarea } 25 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/components/ui/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider 9 | 10 | const Tooltip = TooltipPrimitive.Root 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef21 | 30 | ) 31 | })} 32 |22 | {title &&27 | {action} 28 |{title} } 23 | {description && ( 24 |{description} 25 | )} 26 |29 | 33 | , 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 27 | )) 28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 29 | 30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 31 | -------------------------------------------------------------------------------- /ocss-frontend/components/ui/use-default-config-switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Card, CardHeader } from "@/components/ui/card" 4 | 5 | import { Switch } from "./switch" 6 | 7 | type UseDefaultConfigSwitchProps = { 8 | onCheckedChange: (useDefaultConfig: boolean) => void 9 | checked: boolean 10 | } 11 | 12 | export default function UseDefaultConfigSwitch({ 13 | onCheckedChange, 14 | checked, 15 | }: UseDefaultConfigSwitchProps) { 16 | return ( 17 | 18 |25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /ocss-frontend/config/site.ts: -------------------------------------------------------------------------------- 1 | export type SiteConfig = typeof siteConfig 2 | 3 | export const siteConfig = { 4 | name: "OCSS", 5 | description: 6 | "Revolutionize your e-commerce search with OCSS: the simple, hassle-free solution that abstracts over Elasticsearch, Lucene, and Querqy.", 7 | links: { 8 | github: "https://github.com/CommerceExperts/open-commerce-search", 9 | home: "/", 10 | docs: "https://commerceexperts.github.io/open-commerce-search/", 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /ocss-frontend/lib/bookmarks.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { cookies } from "next/headers" 4 | 5 | import { Bookmark } from "@/types/bookmarks" 6 | import { InternalCookies } from "@/types/cookies" 7 | 8 | export async function setBookmarksCookie(items: Bookmark[]) { 9 | cookies().set(InternalCookies.bookmarks, JSON.stringify(items)) 10 | } 11 | 12 | export async function getBookmarksFromCookie() { 13 | return JSON.parse( 14 | cookies().get(InternalCookies.bookmarks)?.value ?? "[]" 15 | ) as Bookmark[] 16 | } 17 | -------------------------------------------------------------------------------- /ocss-frontend/lib/default-configs.ts: -------------------------------------------------------------------------------- 1 | // Default configuration for indexer and search service in YAML 2 | 3 | export const defaultIndexerServiceConfig = ` 4 | spring: 5 | logging: 6 | level: 7 | de.cxp.ocs: DEBUG 8 | ocs: 9 | index-config: {} 10 | default-index-config: 11 | field-configuration: 12 | dynamic-fields: [] 13 | fields: {} 14 | 15 | ` 16 | export const defaultSearchServiceConfig = ` 17 | ocs: 18 | tenant-config: {} 19 | default-tenant-config: 20 | facet-configuration: 21 | maxFacets: 5 22 | facets: [] 23 | ` 24 | -------------------------------------------------------------------------------- /ocss-frontend/lib/fonts.ts: -------------------------------------------------------------------------------- 1 | import { JetBrains_Mono as FontMono, Inter as FontSans } from "next/font/google" 2 | 3 | export const fontSans = FontSans({ 4 | subsets: ["latin"], 5 | variable: "--font-sans", 6 | }) 7 | 8 | export const fontMono = FontMono({ 9 | subsets: ["latin"], 10 | variable: "--font-mono", 11 | }) 12 | -------------------------------------------------------------------------------- /ocss-frontend/lib/product-card-configuration.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { cookies } from "next/headers" 4 | 5 | import { ProductDataFieldConfiguration } from "@/types/config" 6 | import { InternalCookies } from "@/types/cookies" 7 | 8 | export async function setProductDataFieldConfigurationCookie( 9 | items: ProductDataFieldConfiguration[] 10 | ) { 11 | cookies().set( 12 | InternalCookies.productDataFieldConfiguration, 13 | JSON.stringify(items) 14 | ) 15 | } 16 | 17 | export async function getProductDataFieldConfigurationFromCookie() { 18 | return JSON.parse( 19 | cookies().get(InternalCookies.productDataFieldConfiguration)?.value ?? "[]" 20 | ) as ProductDataFieldConfiguration[] 21 | } 22 | -------------------------------------------------------------------------------- /ocss-frontend/lib/suggest-api.ts: -------------------------------------------------------------------------------- 1 | import { Suggestion } from "@/types/api" 2 | 3 | export async function suggest( 4 | tenant: string, 5 | query: string, 6 | limit: number, 7 | basepath: string 8 | ) { 9 | const res = await fetch( 10 | `${basepath}/suggest-api/v1/${tenant}/suggest?userQuery=${query}&limit=${limit}`, 11 | { cache: "no-store" } 12 | ) 13 | 14 | if (!res.ok) { 15 | return [] 16 | } 17 | 18 | const data = (await res.json()) as Suggestion[] 19 | 20 | return data 21 | } 22 | -------------------------------------------------------------------------------- /ocss-frontend/middleware.ts: -------------------------------------------------------------------------------- 1 | export { default } from "next-auth/middleware" 2 | 3 | export const config = { matcher: ["/config/:path*"] } 4 | -------------------------------------------------------------------------------- /ocss-frontend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | ///19 | 24 |20 | 23 |Use default config
21 |22 | 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /ocss-frontend/next.config.mjs: -------------------------------------------------------------------------------- 1 | import "./env.mjs" 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | reactStrictMode: true, 6 | output: "standalone", 7 | basePath: process.env.BASEPATH, 8 | images: { 9 | // remotePatterns: [ 10 | // { 11 | // protocol: "https", 12 | // hostname: "**", 13 | // }, 14 | // ], 15 | unoptimized: true, 16 | }, 17 | } 18 | 19 | export default nextConfig 20 | -------------------------------------------------------------------------------- /ocss-frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /ocss-frontend/prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | module.exports = { 3 | endOfLine: "lf", 4 | semi: false, 5 | singleQuote: false, 6 | tabWidth: 2, 7 | trailingComma: "es5", 8 | importOrder: [ 9 | "^(react/(.*)$)|^(react$)", 10 | "^(next/(.*)$)|^(next$)", 11 | " ", 12 | "", 13 | "^types$", 14 | "^@/types/(.*)$", 15 | "^@/config/(.*)$", 16 | "^@/lib/(.*)$", 17 | "^@/hooks/(.*)$", 18 | "^@/components/ui/(.*)$", 19 | "^@/components/(.*)$", 20 | "^@/styles/(.*)$", 21 | "^@/app/(.*)$", 22 | "", 23 | "^[./]", 24 | ], 25 | importOrderSeparation: false, 26 | importOrderSortSpecifiers: true, 27 | importOrderBuiltinModulesToTop: true, 28 | importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"], 29 | importOrderMergeDuplicateimports: true, 30 | importOrderCombineTypeAndValueimports: true, 31 | plugins: ["@ianvs/prettier-plugin-sort-imports"], 32 | } 33 | -------------------------------------------------------------------------------- /ocss-frontend/public/OCSS_Bildlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/ocss-frontend/public/OCSS_Bildlogo.png -------------------------------------------------------------------------------- /ocss-frontend/public/OCSS_Komplett.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/ocss-frontend/public/OCSS_Komplett.png -------------------------------------------------------------------------------- /ocss-frontend/public/OCSS_Schriftlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/ocss-frontend/public/OCSS_Schriftlogo.png -------------------------------------------------------------------------------- /ocss-frontend/public/OCSS_Standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/ocss-frontend/public/OCSS_Standard.png -------------------------------------------------------------------------------- /ocss-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/ocss-frontend/public/favicon.ico -------------------------------------------------------------------------------- /ocss-frontend/reset.d.ts: -------------------------------------------------------------------------------- 1 | import "@total-typescript/ts-reset" 2 | -------------------------------------------------------------------------------- /ocss-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./*"] 19 | }, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "strictNullChecks": true 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /ocss-frontend/types/bookmarks.ts: -------------------------------------------------------------------------------- 1 | export type Bookmark = { 2 | query: string 3 | heroProductIds: string[] 4 | tenant: string 5 | } 6 | -------------------------------------------------------------------------------- /ocss-frontend/types/cookies.ts: -------------------------------------------------------------------------------- 1 | export enum InternalCookies { 2 | productDataFieldConfiguration = "product-data-field-configuration", 3 | bookmarks = "bookmarks", 4 | } 5 | -------------------------------------------------------------------------------- /ocss-frontend/types/nav.ts: -------------------------------------------------------------------------------- 1 | export type NavItem = { 2 | title: string 3 | href?: string 4 | disabled?: boolean 5 | external?: boolean 6 | } 7 | -------------------------------------------------------------------------------- /ocss-frontend/types/searchParams.ts: -------------------------------------------------------------------------------- 1 | export type SearchParam = string | string[] | undefined 2 | 3 | export enum SearchParamsMap { 4 | tenant = "tenant", 5 | query = "query", 6 | sort = "sort", 7 | page = "page", 8 | 9 | config = "config", 10 | commit = "commit", 11 | heroProductIds = "hpids", 12 | } 13 | 14 | export type SearchParams = { 15 | [SearchParamsMap.tenant]: SearchParam 16 | [SearchParamsMap.query]: SearchParam 17 | [SearchParamsMap.sort]: SearchParam 18 | [SearchParamsMap.page]: SearchParam 19 | [SearchParamsMap.config]: SearchParam 20 | [SearchParamsMap.commit]: SearchParam 21 | [SearchParamsMap.commit]: SearchParam 22 | [SearchParamsMap.heroProductIds]: SearchParam 23 | [key: string]: string | string[] | undefined 24 | } 25 | -------------------------------------------------------------------------------- /open-commerce-search-api/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/api/indexer/ImportSession.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.api.indexer; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import io.swagger.v3.oas.annotations.media.Schema.AccessMode; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | 8 | @Schema(accessMode = AccessMode.READ_ONLY) 9 | @Data 10 | @AllArgsConstructor 11 | public class ImportSession { 12 | 13 | @Schema(required = true) 14 | final public String finalIndexName; 15 | 16 | @Schema(required = true) 17 | final public String temporaryIndexName; 18 | } 19 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/index/BulkImportData.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.index; 2 | 3 | import de.cxp.ocs.api.indexer.ImportSession; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Data; 6 | 7 | @Schema(description = "composite object that is used to add documents to the index.") 8 | @Data 9 | public class BulkImportData { 10 | 11 | @Schema( 12 | required = true, 13 | description = "Import session information that were retrieved by the startImport method.") 14 | public ImportSession session; 15 | 16 | @Schema( 17 | required = true, 18 | description = "An array of Documents or Products that should be indexed into the given index." 19 | + "Products have the speciality to contain other documents as variants.", 20 | anyOf = { Document.class, Product.class }) 21 | public Document[] documents; 22 | } 23 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/index/Category.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.index; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | 9 | @Schema( 10 | name = "Category", 11 | description = "Model that represents a single category inside a category path.", 12 | example = "{\"id\": \"7001\", \"name\": \"Sale\"}") 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Accessors(chain = true) 17 | public class Category { 18 | 19 | @Schema(description = "Optional ID for a consistent filtering") 20 | public String id; 21 | 22 | @Schema(required = true, description = "actual category name") 23 | public String name; 24 | 25 | public static Category of(String name) { 26 | return new Category(null, name); 27 | } 28 | 29 | public String toString() { 30 | return id == null ? name : name + ":" + id; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/params/ArrangedSearchQuery.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.params; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Accessors; 7 | 8 | @NoArgsConstructor 9 | @Data 10 | @EqualsAndHashCode(callSuper = true) 11 | @Accessors(chain = true) 12 | public class ArrangedSearchQuery extends FilteredSearchQuery { 13 | 14 | /** 15 | * One or more sets of documents/products that should be placed at the top 16 | * of the result. They will be returned in separate result slices, but they 17 | * will be excluded from the organic user search result. 18 | */ 19 | public ProductSet[] arrangedProductSets; 20 | 21 | /** 22 | * Per default the "natural unarranged results" defined by the included query+filters are part of the response. If 23 | * those are not required, set this flag to 'false'. 24 | */ 25 | public boolean includeMainResult = true; 26 | } 27 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/params/DynamicProductSet.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.params; 2 | 3 | import java.util.Map; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | /** 13 | * A product set defined by dynamic search query text, filters and optional 14 | * sorting order. 15 | */ 16 | @Data 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Accessors(chain = true) 20 | @EqualsAndHashCode(callSuper = false) 21 | @Schema(allOf = { ProductSet.class }) 22 | public class DynamicProductSet extends ProductSet { 23 | 24 | public final String type = "dynamic"; 25 | 26 | public String name; 27 | 28 | public String query; 29 | 30 | public String sort; 31 | 32 | public Map filters; 33 | 34 | /** 35 | * The maximum amount of products to pick into the set. These will be the 36 | * first products provided by the other parameters. 37 | */ 38 | @Schema(description = "The maximum amount of products to pick based on the given query and filters", minimum = "1") 39 | public int limit = 3; 40 | 41 | @Override 42 | public int getSize() { 43 | return limit; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/params/FilteredSearchQuery.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.params; 2 | 3 | import java.util.Map; 4 | 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.experimental.Accessors; 8 | 9 | @Data 10 | @EqualsAndHashCode(callSuper = true) 11 | @Accessors(chain = true) 12 | public class FilteredSearchQuery extends SearchQuery { 13 | 14 | public Map filters; 15 | } 16 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/params/GenericProductSet.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.params; 2 | 3 | import java.util.Map; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Accessors(chain = true) 15 | @Getter 16 | @EqualsAndHashCode(callSuper = false) 17 | @Schema(allOf = { ProductSet.class }, description = "A non-specific product set usable for custom product set resolvers.") 18 | public class GenericProductSet extends ProductSet { 19 | 20 | public final String type = "generic"; 21 | private String name; 22 | private int size = 0; 23 | 24 | private Map parameters; 25 | } 26 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/params/StaticProductSet.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.params; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.*; 5 | import lombok.experimental.Accessors; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @RequiredArgsConstructor 10 | @AllArgsConstructor 11 | @Accessors(chain = true) 12 | @EqualsAndHashCode(callSuper = false) 13 | @Schema(allOf = { ProductSet.class }) 14 | public class StaticProductSet extends ProductSet { 15 | 16 | public String type = "static"; 17 | 18 | @NonNull 19 | public String[] ids; 20 | 21 | @NonNull 22 | public String name; 23 | 24 | @Override 25 | public int getSize() { 26 | return ids.length; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/result/ResultHit.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.result; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import de.cxp.ocs.model.index.Document; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @Accessors(chain = true) 14 | public class ResultHit { 15 | 16 | /** 17 | * the index name where this hit is coming from. 18 | */ 19 | public String index; 20 | 21 | /** 22 | * The found document. 23 | */ 24 | public Document document; 25 | 26 | /** 27 | * Optional: Which parts of the query matched that document. 28 | * Mainly used for debugging / search transparency. 29 | */ 30 | public String[] matchedQueries; 31 | 32 | /** 33 | * Optional: Arbitrary meta data for debug information or other insights about the result hit, 34 | * i.e. the score or variant picking details. 35 | */ 36 | public Map metaData; 37 | 38 | public ResultHit withMetaData(String key, Object value) { 39 | if (metaData == null) { 40 | metaData = new HashMap<>(); 41 | } 42 | metaData.put(key, value); 43 | return this; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/result/SortOrder.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.result; 2 | 3 | 4 | public enum SortOrder { 5 | /** 6 | * Ascending order. 7 | */ 8 | ASC { 9 | 10 | @Override 11 | public String toString() { 12 | return "asc"; 13 | } 14 | }, 15 | /** 16 | * Descending order. 17 | */ 18 | DESC { 19 | 20 | @Override 21 | public String toString() { 22 | return "desc"; 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/result/Sorting.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.result; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class Sorting { 12 | 13 | public String label; 14 | public String field; 15 | public SortOrder sortOrder; 16 | 17 | /** 18 | * Is set to true, if this sorting is active in the current result. 19 | */ 20 | public boolean isActive = false; 21 | 22 | /** 23 | * URL conform query parameters, that has to be used to activate that sort 24 | * option. 25 | */ 26 | @Schema(format = "URI") 27 | public String link; 28 | } 29 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/java/de/cxp/ocs/model/suggest/Suggestion.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.model.suggest; 2 | 3 | import java.util.Map; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.NonNull; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | @Data 13 | @Accessors(chain = true) 14 | @NoArgsConstructor 15 | @RequiredArgsConstructor 16 | public class Suggestion { 17 | 18 | @NonNull 19 | @Schema(description = "The phrase that is suggested and/or used as suggestion label.") 20 | public String phrase; 21 | 22 | @Schema( 23 | description = "Optional type of that suggestion. Should be different for the different kind of suggested data. " 24 | + "Default: 'keyword'", 25 | example = "keyword, brand, category, product") 26 | public String type = "keyword"; 27 | 28 | @Schema(description = "arbitrary payload attached to that suggestion. Default: null") 29 | Map payload; 30 | } 31 | -------------------------------------------------------------------------------- /open-commerce-search-api/src/main/resources/openapi-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettyPrint" : true, 3 | "cacheTTL": 0, 4 | "openAPI": { 5 | "info": { 6 | "version": "${project.version}", 7 | "title": "Open Commerce Search Stack API", 8 | "description": "A common product search API that separates its usage from required search expertise", 9 | "contact": { 10 | "email": "info@commerce-experts.com" 11 | }, 12 | "license": { 13 | "name": "Apache 2.0", 14 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /operations/docker-compose/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env-* 3 | docker-compose.custom.yml 4 | -------------------------------------------------------------------------------- /operations/docker-compose/_env_example: -------------------------------------------------------------------------------- 1 | SCCS_GIT_URL=https://github.com/ / .git #github configuration repo url 2 | SCCS_GIT_USERNAME= 3 | SCCS_GIT_PASSWORD= #can be a github access token 4 | 5 | OCS_FRONTEND_BASEPATH= #optional 6 | NEXTAUTH_SECRET= #github oauth access token 7 | NEXTAUTH_URL= #domain the app is reachable from 8 | 9 | GITHUB_DEFAULT_CONFIG_OWNER= #github-account of the configuration repo 10 | GITHUB_DEFAULT_CONFIG_REPO_NAME= #github repo for ocs-configuration 11 | DEFAULT_TENANT= #optional 12 | -------------------------------------------------------------------------------- /operations/docker-compose/application.indexer-service.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | logging: 3 | level: 4 | de.cxp.ocs: DEBUG 5 | -------------------------------------------------------------------------------- /operations/docker-compose/application.search-service.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | logging: 3 | level: 4 | de.cxp.ocs: DEBUG 5 | -------------------------------------------------------------------------------- /operations/docker-compose/dc-cloudconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -e docker-compose.custom.yml ]; then 4 | CUSTOM_YML_PARAM="-f docker-compose.custom.yml" 5 | fi 6 | 7 | docker compose -f docker-compose.base.yml -f docker-compose.cloudconfig.yml -f docker-compose.frontend.yml $CUSTOM_YML_PARAM "$@" 8 | -------------------------------------------------------------------------------- /operations/docker-compose/dc-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -e docker-compose.custom.yml ]; then 4 | CUSTOM_YML_PARAM="-f docker-compose.custom.yml" 5 | fi 6 | 7 | docker compose -f docker-compose.base.yml -f docker-compose.local.yml $CUSTOM_YML_PARAM "$@" 8 | -------------------------------------------------------------------------------- /operations/docker-compose/docker-compose.local.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | # extends the docker-compose.base.yml 4 | #> docker compose -f docker-compose.base.yml -f docker-compose.local.yml 5 | 6 | services: 7 | indexer: 8 | environment: 9 | - "spring_profiles_active=preset,custom" 10 | volumes: 11 | - ./application.indexer-service.yml:/app/resources/application-custom.yml:ro 12 | 13 | searcher: 14 | environment: 15 | - "spring_profiles_active=preset,custom" 16 | volumes: 17 | - ./application.search-service.yml:/app/resources/application-custom.yml:ro 18 | 19 | -------------------------------------------------------------------------------- /operations/docker-compose/elasticsearch/docker-entrypoint-es.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install plugins here 4 | ./bin/elasticsearch-plugin install analysis-stempel 5 | 6 | exec /usr/local/bin/docker-entrypoint.sh elasticsearch 7 | -------------------------------------------------------------------------------- /operations/k8s/.gitignore: -------------------------------------------------------------------------------- 1 | overlays/* 2 | -------------------------------------------------------------------------------- /operations/k8s/base/elasticsearch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: elasticsearch.k8s.elastic.co/v1 2 | kind: Elasticsearch 3 | metadata: 4 | name: ocs 5 | spec: 6 | version: 7.17.9 7 | nodeSets: 8 | - name: default 9 | count: 1 10 | config: 11 | node.master: true 12 | node.data: true 13 | node.ingest: true 14 | node.store.allow_mmap: false 15 | http: 16 | tls: 17 | selfSignedCertificate: 18 | disabled: true -------------------------------------------------------------------------------- /operations/k8s/base/indexer-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: indexer-api-service 5 | labels: 6 | role: indexer-api 7 | spec: 8 | selector: 9 | role: indexer-api 10 | ports: 11 | - name: indexer-api 12 | port: 8535 13 | -------------------------------------------------------------------------------- /operations/k8s/base/indexer-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: indexer-ingress 5 | labels: 6 | role: ingress 7 | spec: 8 | ingressClassName: nginx 9 | rules: 10 | - http: 11 | paths: 12 | - path: /indexer-api 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: indexer-api-service 17 | port: 18 | name: indexer-api -------------------------------------------------------------------------------- /operations/k8s/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | # namespace where the ocs stack should be deployed 5 | namespace: ocs-stack 6 | 7 | # scale up search service here 8 | replicas: 9 | - name: search-service 10 | count: 2 11 | 12 | # change stage label here 13 | commonLabels: 14 | component: ocs-stack 15 | stage: development 16 | 17 | resources: 18 | - indexer-service.yaml 19 | - search-service.yaml 20 | - suggest-service.yaml 21 | - indexer-api.yaml 22 | - search-api.yaml 23 | - suggest-api.yaml 24 | - search-ingress.yaml 25 | - suggest-ingress.yaml 26 | - indexer-ingress.yaml 27 | - elasticsearch.yaml -------------------------------------------------------------------------------- /operations/k8s/base/search-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: search-api-service 5 | labels: 6 | role: search-api 7 | spec: 8 | selector: 9 | role: search-api 10 | ports: 11 | - name: search-api 12 | port: 8534 13 | -------------------------------------------------------------------------------- /operations/k8s/base/search-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: search-ingress 5 | labels: 6 | role: ingress 7 | spec: 8 | ingressClassName: nginx 9 | rules: 10 | - http: 11 | paths: 12 | - path: /search-api 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: search-api-service 17 | port: 18 | name: search-api -------------------------------------------------------------------------------- /operations/k8s/base/suggest-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: suggest-api-service 5 | labels: 6 | role: suggest-api 7 | spec: 8 | selector: 9 | role: suggest-api 10 | ports: 11 | - name: suggest-api 12 | port: 8081 13 | -------------------------------------------------------------------------------- /operations/k8s/base/suggest-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: suggest-ingress 5 | labels: 6 | role: ingress 7 | spec: 8 | ingressClassName: nginx 9 | rules: 10 | - http: 11 | paths: 12 | - path: /suggest-api 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: suggest-api-service 17 | port: 18 | name: suggest-api -------------------------------------------------------------------------------- /operations/k8s/overlays/example/README.md: -------------------------------------------------------------------------------- 1 | # Example Kustomize Config 2 | 3 | This directory contains an example kustomize overlay. 4 | Create a similar one to adjust your deployment. 5 | 6 | -------------------------------------------------------------------------------- /operations/tests/rally/docker-compose-results.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | elasticsearch: 4 | image: docker.elastic.co/elasticsearch/elasticsearch:7.9.2-amd64 5 | container_name: elasticsearch 6 | environment: 7 | - node.name=elasticsearch 8 | - discovery.type=single-node 9 | expose: 10 | - 9200 11 | - 9300 12 | ports: 13 | - 9400:9200 14 | networks: 15 | - benchmark 16 | kibana: 17 | image: docker.elastic.co/kibana/kibana:7.9.2 18 | environment: 19 | ELASTICSEARCH_HOSTS: http://elasticsearch:9200 20 | ports: 21 | - 5601:5601 22 | networks: 23 | - benchmark 24 | 25 | networks: 26 | benchmark: 27 | -------------------------------------------------------------------------------- /operations/tests/rally/rally.ini: -------------------------------------------------------------------------------- 1 | [meta] 2 | config.version = 17 3 | 4 | [system] 5 | env.name = local 6 | 7 | 8 | [node] 9 | root.dir = /rally/.rally/benchmarks 10 | src.root.dir = /rally/.rally/benchmarks/src 11 | 12 | [source] 13 | remote.repo.url = https://github.com/elastic/elasticsearch.git 14 | elasticsearch.src.subdir = elasticsearch 15 | 16 | [benchmarks] 17 | local.dataset.cache = /rally/.rally/benchmarks/data 18 | 19 | 20 | [reporting] 21 | datastore.type = elasticsearch 22 | datastore.host = 127.0.0.1 23 | datastore.port = 9400 24 | datastore.secure = false 25 | datastore.user = 26 | datastore.password = 27 | 28 | [tracks] 29 | default.url = https://github.com/elastic/rally-tracks 30 | 31 | [teams] 32 | default.url = https://github.com/elastic/rally-teams 33 | 34 | [defaults] 35 | preserve_benchmark_candidate = False 36 | 37 | [distributions] 38 | release.cache = true 39 | 40 | -------------------------------------------------------------------------------- /operations/tests/rally/track.py: -------------------------------------------------------------------------------- 1 | from .custom_runner.ocss_search_runner import OCSSSearchRunner 2 | 3 | # register custom runners here 4 | 5 | 6 | def register(registry): 7 | registry.register_runner("ocss-search", OCSSSearchRunner(), async_runner=True) -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/ExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @Builder 10 | public class ExceptionResponse { 11 | 12 | String message; 13 | int code; 14 | 15 | /** 16 | * Unique ID of the error that is also logged to the server log output. 17 | */ 18 | String errorId; 19 | 20 | Exception exception; 21 | 22 | final long timestamp = System.currentTimeMillis(); 23 | } 24 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/SearchContext.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs; 2 | 3 | import java.util.List; 4 | 5 | import de.cxp.ocs.config.FieldConfigIndex; 6 | import de.cxp.ocs.config.SearchConfiguration; 7 | import de.cxp.ocs.elasticsearch.prodset.HeroProductHandler; 8 | import de.cxp.ocs.spi.search.UserQueryPreprocessor; 9 | import lombok.Getter; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | /** 13 | * Holder for several tenant specific objects. 14 | */ 15 | @RequiredArgsConstructor 16 | @Getter 17 | public class SearchContext { 18 | 19 | public final FieldConfigIndex fieldConfigIndex; 20 | 21 | public final SearchConfiguration config; 22 | 23 | public final List userQueryPreprocessors; 24 | 25 | public final HeroProductHandler heroProductHandler; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/config/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.boot.context.properties.NestedConfigurationProperty; 10 | import org.springframework.stereotype.Component; 11 | 12 | import lombok.Getter; 13 | 14 | /** 15 | * Properties are configured in the application.yml file. 16 | */ 17 | @ConfigurationProperties(prefix = "ocs", ignoreUnknownFields = true) 18 | @Component 19 | @Getter 20 | public class ApplicationProperties { 21 | 22 | private final Set disabledPlugins = new HashSet<>(); 23 | 24 | private final Map preferedPlugins = new HashMap<>(); 25 | 26 | @NestedConfigurationProperty 27 | private final ConnectionConfiguration connectionConfiguration = new ConnectionConfiguration(); 28 | 29 | @NestedConfigurationProperty 30 | ApplicationSearchProperties defaultTenantConfig = new ApplicationSearchProperties(); 31 | 32 | @NestedConfigurationProperty 33 | private final Map tenantConfig = new HashMap<>(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/config/SchedulerConfig.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.config; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @Configuration 8 | @EnableScheduling 9 | @ConditionalOnProperty(name = "ocs.scheduler.enabled") 10 | public class SchedulerConfig { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/facets/FacetFilter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.facets; 2 | 3 | import de.cxp.ocs.config.FacetConfiguration.FacetConfig; 4 | import de.cxp.ocs.elasticsearch.query.filter.FilterContext; 5 | import de.cxp.ocs.model.result.Facet; 6 | 7 | public interface FacetFilter { 8 | 9 | boolean isVisibleFacet(Facet facet, FacetConfig config, FilterContext filterContext, int totalMatchCount); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/facets/NestedFacetCountCorrector.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.facets; 2 | 3 | import org.elasticsearch.search.aggregations.AggregationBuilder; 4 | import org.elasticsearch.search.aggregations.AggregationBuilders; 5 | import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket; 6 | import org.elasticsearch.search.aggregations.bucket.nested.ParsedReverseNested; 7 | 8 | import lombok.NonNull; 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @RequiredArgsConstructor 12 | class NestedFacetCountCorrector { 13 | 14 | @NonNull 15 | private final String nestedPath; 16 | 17 | public String getNestedPathPrefix() { 18 | return nestedPath.isEmpty() ? nestedPath : nestedPath + "."; 19 | } 20 | 21 | public void correctValueAggBuilder(AggregationBuilder aggBuilder) { 22 | aggBuilder.subAggregation(AggregationBuilders.reverseNested("_reverse")); 23 | } 24 | 25 | public long getCorrectedDocumentCount(Bucket valueBucket) { 26 | return ((ParsedReverseNested) valueBucket.getAggregations().get("_reverse")).getDocCount(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/prodset/HeroProductsQuery.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.prodset; 2 | 3 | import org.elasticsearch.index.query.QueryBuilder; 4 | import org.elasticsearch.index.query.QueryBuilders; 5 | 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public class HeroProductsQuery { 12 | 13 | private final QueryBuilder mainQuery; 14 | 15 | private final QueryBuilder variantBoostQuery; 16 | 17 | public QueryBuilder applyToMasterQuery(QueryBuilder masterMatchQuery) { 18 | return _apply(masterMatchQuery, mainQuery); 19 | } 20 | 21 | public QueryBuilder applyToVariantQuery(QueryBuilder variantsMatchQuery) { 22 | return _apply(variantsMatchQuery, variantBoostQuery); 23 | } 24 | 25 | private QueryBuilder _apply(QueryBuilder originalQuery, QueryBuilder heroQuery) { 26 | if (heroQuery == null) { 27 | return originalQuery; 28 | } 29 | if (originalQuery == null) { 30 | return heroQuery; 31 | } 32 | return QueryBuilders.disMaxQuery() 33 | .add(heroQuery) 34 | .add(originalQuery) 35 | .tieBreaker(0f); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/prodset/NoopProductSetResolver.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.prodset; 2 | 3 | import java.util.Set; 4 | 5 | import de.cxp.ocs.SearchContext; 6 | import de.cxp.ocs.elasticsearch.Searcher; 7 | import de.cxp.ocs.model.params.ProductSet; 8 | import de.cxp.ocs.model.params.StaticProductSet; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | public class NoopProductSetResolver implements ProductSetResolver { 13 | 14 | @Override 15 | public boolean runAsync() { 16 | return false; 17 | } 18 | 19 | @Override 20 | public StaticProductSet resolve(ProductSet set, Set excludedIds, Searcher searcher, SearchContext searchContext) { 21 | log.info("received productSet {} of type {} but no resolver defined for it!", set.getName(), set.getType()); 22 | return new StaticProductSet(set.getType(), new String[0], set.getName()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/prodset/ProductSetResolver.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.prodset; 2 | 3 | import java.util.Set; 4 | 5 | import de.cxp.ocs.SearchContext; 6 | import de.cxp.ocs.elasticsearch.Searcher; 7 | import de.cxp.ocs.model.params.ProductSet; 8 | import de.cxp.ocs.model.params.StaticProductSet; 9 | 10 | public interface ProductSetResolver { 11 | 12 | /** 13 | * 14 | * This has no effect any more! 15 | * 16 | * 17 | * 18 | * Resolvers that need a bit more time can defined itself as async, so that in case of several product sets in a 19 | * single request, all of them could run async in parallel. 20 | *
21 | * 22 | * @return true if this product set resolver needs more time and should run async 23 | */ 24 | @Deprecated 25 | default boolean runAsync() { 26 | return false; 27 | } 28 | 29 | StaticProductSet resolve(ProductSet set, SetexcludedIds, Searcher searcher, SearchContext searchContext); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/AsciifyUserQueryPreprocessor.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query; 2 | 3 | import de.cxp.ocs.spi.search.UserQueryPreprocessor; 4 | import de.cxp.ocs.util.StringUtils; 5 | 6 | public class AsciifyUserQueryPreprocessor implements UserQueryPreprocessor { 7 | 8 | @Override 9 | public String preProcess(String userQuery) { 10 | return StringUtils.asciify(userQuery); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/NonAlphanumericStripPreprocessor.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query; 2 | 3 | import static de.cxp.ocs.elasticsearch.query.analyzer.NonAlphanumericWordSplitAnalyzer.BIND_CHARS; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import de.cxp.ocs.spi.search.UserQueryPreprocessor; 8 | 9 | public class NonAlphanumericStripPreprocessor implements UserQueryPreprocessor { 10 | 11 | @Override 12 | public String preProcess(String userQuery) { 13 | String[] split = userQuery.toLowerCase().split("[^\\p{L}\\p{N}" + BIND_CHARS + "]+"); 14 | StringBuilder joinedWords = new StringBuilder(userQuery.length()); 15 | for (String word : split) { 16 | word = StringUtils.strip(word, BIND_CHARS); 17 | if (word.isEmpty()) continue; 18 | if (joinedWords.length() > 0) { 19 | joinedWords.append(' '); 20 | } 21 | joinedWords.append(word); 22 | } 23 | return joinedWords.toString(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/SearchField.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query; 2 | 3 | import de.cxp.ocs.config.Field; 4 | import de.cxp.ocs.config.FieldConstants; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NonNull; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @AllArgsConstructor 11 | @RequiredArgsConstructor 12 | public class SearchField { 13 | 14 | @Getter 15 | @NonNull 16 | private final Field field; 17 | 18 | private String subField; 19 | 20 | public String getFullName() { 21 | String suffix = subField == null || subField.isBlank() ? "" : "." + subField; 22 | return FieldConstants.SEARCH_DATA + "." + field.getName() + suffix; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/SearchQueryContext.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.elasticsearch.index.query.QueryBuilder; 7 | import org.elasticsearch.search.sort.SortBuilder; 8 | 9 | import de.cxp.ocs.elasticsearch.prodset.HeroProductsQuery; 10 | import de.cxp.ocs.elasticsearch.query.filter.FilterContext; 11 | import lombok.Data; 12 | 13 | /** 14 | * All parts that build together the final search query. 15 | * 16 | * @author Rudolf Batt 17 | */ 18 | @Data 19 | public class SearchQueryContext { 20 | 21 | public TextMatchQuery text; 22 | 23 | public FilterContext filters; 24 | 25 | public ScoringContext scoring; 26 | 27 | public List > variantSortings = new ArrayList<>(); 28 | 29 | public HeroProductsQuery heroProducts; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/SearchQueryWrapper.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query; 2 | 3 | import org.elasticsearch.common.unit.Fuzziness; 4 | import org.elasticsearch.index.query.QueryBuilder; 5 | 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public class SearchQueryWrapper { 12 | 13 | private final QueryBuilder queryBuilder; 14 | private final Fuzziness fuzziness; 15 | private final String queryDescription; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/analyzer/WhitespaceAnalyzer.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.analyzer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import de.cxp.ocs.elasticsearch.model.query.AnalyzedQuery; 7 | import de.cxp.ocs.elasticsearch.model.query.ExtendedQuery; 8 | import de.cxp.ocs.elasticsearch.model.query.MultiTermQuery; 9 | import de.cxp.ocs.elasticsearch.model.query.SingleTermQuery; 10 | import de.cxp.ocs.elasticsearch.model.term.WeightedTerm; 11 | import de.cxp.ocs.spi.search.UserQueryAnalyzer; 12 | 13 | public class WhitespaceAnalyzer implements UserQueryAnalyzer { 14 | 15 | @Override 16 | public ExtendedQuery analyze(String userQuery) { 17 | List terms = toQueryStringWordList(userQuery.toLowerCase().trim().split("\\s+")); 18 | if (terms.isEmpty()) { 19 | return ExtendedQuery.MATCH_ALL; 20 | } 21 | AnalyzedQuery termsQuery = terms.size() == 1 ? new SingleTermQuery(userQuery, terms.get(0)) : new MultiTermQuery(terms); 22 | return new ExtendedQuery(termsQuery); 23 | } 24 | 25 | public static List toQueryStringWordList(String[] words) { 26 | List queryWords = new ArrayList<>(words.length); 27 | for (String word : words) { 28 | queryWords.add(new WeightedTerm(word)); 29 | } 30 | return queryWords; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/analyzer/WhitespaceWithShingles.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.analyzer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import de.cxp.ocs.elasticsearch.model.query.AnalyzedQuery; 7 | import de.cxp.ocs.elasticsearch.model.query.ExtendedQuery; 8 | import de.cxp.ocs.elasticsearch.model.query.MultiTermQuery; 9 | import de.cxp.ocs.elasticsearch.model.query.SingleTermQuery; 10 | import de.cxp.ocs.elasticsearch.model.term.WeightedTerm; 11 | import de.cxp.ocs.spi.search.UserQueryAnalyzer; 12 | 13 | public class WhitespaceWithShingles implements UserQueryAnalyzer { 14 | 15 | @Override 16 | public ExtendedQuery analyze(String userQuery) { 17 | String[] split = userQuery.toLowerCase().trim().split("\\s+"); 18 | List terms = new ArrayList<>(); 19 | 20 | for (int i = 0; i < split.length - 1; i++) { 21 | terms.add(new WeightedTerm(split[i])); 22 | terms.add(new WeightedTerm(split[i] + split[i + 1])); 23 | } 24 | terms.add(new WeightedTerm(split[split.length - 1])); 25 | 26 | AnalyzedQuery termsQuery = terms.size() == 0 ? new SingleTermQuery(userQuery, terms.get(0)) : new MultiTermQuery(terms); 27 | return new ExtendedQuery(termsQuery); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/analyzer/querqy/TransformingWhitespaceQuerqyParser.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.analyzer.querqy; 2 | 3 | import java.util.EnumSet; 4 | 5 | import de.cxp.ocs.util.StringUtils; 6 | import querqy.model.Query; 7 | import querqy.parser.QuerqyParser; 8 | 9 | public class TransformingWhitespaceQuerqyParser implements QuerqyParser { 10 | 11 | public static enum TransformationFlags { 12 | ASCIIFY, LOWERCASE 13 | } 14 | 15 | private final EnumSet transformations; 16 | private final QuerqyParser delegate; 17 | 18 | public TransformingWhitespaceQuerqyParser(EnumSet transformations, QuerqyParser delegate) { 19 | this.transformations = EnumSet.copyOf(transformations); 20 | this.delegate = delegate; 21 | } 22 | 23 | @Override 24 | public Query parse(String input) { 25 | String transformedInput = input; 26 | if (transformations.contains(TransformationFlags.ASCIIFY)) { 27 | transformedInput = StringUtils.asciify(transformedInput); 28 | } 29 | if (transformations.contains(TransformationFlags.LOWERCASE)) { 30 | transformedInput = transformedInput.toLowerCase(); 31 | } 32 | return delegate.parse(transformedInput); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/analyzer/querqy/TransformingWhitespaceQuerqyParserFactory.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.analyzer.querqy; 2 | 3 | import java.util.EnumSet; 4 | 5 | import de.cxp.ocs.elasticsearch.query.analyzer.querqy.TransformingWhitespaceQuerqyParser.TransformationFlags; 6 | import lombok.NonNull; 7 | import lombok.RequiredArgsConstructor; 8 | import querqy.parser.QuerqyParser; 9 | import querqy.rewrite.commonrules.QuerqyParserFactory; 10 | import querqy.rewrite.commonrules.WhiteSpaceQuerqyParserFactory; 11 | 12 | @RequiredArgsConstructor 13 | public class TransformingWhitespaceQuerqyParserFactory implements QuerqyParserFactory { 14 | 15 | private final QuerqyParserFactory delegate = new WhiteSpaceQuerqyParserFactory(); 16 | 17 | @NonNull 18 | private final EnumSet transformationFlags; 19 | 20 | @Override 21 | public QuerqyParser createParser() { 22 | return new TransformingWhitespaceQuerqyParser(transformationFlags, delegate.createParser()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/builder/.gitignore: -------------------------------------------------------------------------------- 1 | /QueryMetaFetcher.java 2 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/builder/CountedTerm.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.builder; 2 | 3 | import de.cxp.ocs.elasticsearch.model.term.QueryStringTerm; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.experimental.Delegate; 9 | 10 | @RequiredArgsConstructor 11 | @AllArgsConstructor 12 | public class CountedTerm implements QueryStringTerm { 13 | 14 | @Delegate 15 | private final QueryStringTerm decorated; 16 | 17 | @Getter 18 | @Setter 19 | private int termFrequency = -1; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/builder/EnforcedSpellCorrectionQueryFactory.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.builder; 2 | 3 | import de.cxp.ocs.spi.search.ESQueryFactory; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.experimental.Delegate; 7 | 8 | @RequiredArgsConstructor 9 | public class EnforcedSpellCorrectionQueryFactory implements ESQueryFactory { 10 | 11 | @Delegate 12 | @NonNull 13 | private final ESQueryFactory delegate; 14 | 15 | @Override 16 | public boolean allowParallelSpellcheckExecution() { 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/builder/FallbackConsumer.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.builder; 2 | 3 | import de.cxp.ocs.spi.search.ESQueryFactory; 4 | 5 | public interface FallbackConsumer { 6 | 7 | void setFallbackQueryBuilder(ESQueryFactory fallbackQuery); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/filter/InternalResultFilterAdapter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.filter; 2 | 3 | import org.elasticsearch.index.query.QueryBuilder; 4 | 5 | import de.cxp.ocs.elasticsearch.model.filter.InternalResultFilter; 6 | 7 | public interface InternalResultFilterAdapter { 8 | 9 | /** 10 | * Build query for given filter using the specified field-prefix. 11 | * Negation of the filter MUST NOT be handled inside the adapter! 12 | * 13 | * @param fieldPrefix 14 | * the prefix to the field name including the separator "." 15 | * @param filter 16 | * the filter with name and values and all required information. 17 | * @return an initialized Elasticsearch query builder 18 | */ 19 | QueryBuilder getAsQuery(String fieldPrefix, F filter); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/filter/PathResultFilterAdapter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.filter; 2 | 3 | import org.elasticsearch.index.query.QueryBuilder; 4 | import org.elasticsearch.index.query.QueryBuilders; 5 | 6 | import lombok.Data; 7 | 8 | @Data 9 | public class PathResultFilterAdapter implements InternalResultFilterAdapter { 10 | 11 | @Override 12 | public QueryBuilder getAsQuery(String fieldPrefix, PathResultFilter filter) { 13 | String filterValueField = fieldPrefix + (filter.isFilterOnId() ? "id" : "value"); 14 | String[] filterValues = filter.isFilterOnId() ? filter.getLeastPathValues() : filter.getValues(); 15 | 16 | return QueryBuilders.boolQuery() 17 | .must(QueryBuilders.termQuery(fieldPrefix + "name", filter.getField().getName())) 18 | .must(QueryBuilders.termsQuery(filterValueField, filterValues)); 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/elasticsearch/query/sort/SortInstruction.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.sort; 2 | 3 | import de.cxp.ocs.config.Field; 4 | import de.cxp.ocs.model.result.SortOrder; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | public class SortInstruction { 11 | 12 | private final Field field; 13 | private final String rawSortValue; 14 | private final SortOrder sortOrder; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/util/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.util; 2 | 3 | 4 | /** 5 | * A internal exception because of invalid configuration. Requires a message. 6 | * Should never be use for a response, just for logging. 7 | */ 8 | public class ConfigurationException extends Exception { 9 | 10 | private static final long serialVersionUID = 930904517583581836L; 11 | 12 | public ConfigurationException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /search-service/src/main/java/de/cxp/ocs/util/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.util; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value = HttpStatus.NOT_FOUND) 7 | public class NotFoundException extends Exception { 8 | 9 | private static final long serialVersionUID = 7968636297702034034L; 10 | 11 | public NotFoundException(String entityName) { 12 | super(entityName + " not found"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /search-service/src/main/resources/META-INF/services/de.cxp.ocs.spi.search.UserQueryAnalyzer: -------------------------------------------------------------------------------- 1 | de.cxp.ocs.elasticsearch.query.analyzer.WhitespaceAnalyzer 2 | de.cxp.ocs.elasticsearch.query.analyzer.WhitespaceWithShingles 3 | de.cxp.ocs.elasticsearch.query.analyzer.QuerqyQueryExpander 4 | de.cxp.ocs.elasticsearch.query.analyzer.NonAlphanumericWordSplitAnalyzer -------------------------------------------------------------------------------- /search-service/src/main/resources/META-INF/services/de.cxp.ocs.spi.search.UserQueryPreprocessor: -------------------------------------------------------------------------------- 1 | de.cxp.ocs.elasticsearch.query.AsciifyUserQueryPreprocessor 2 | de.cxp.ocs.elasticsearch.query.CodePointFilterUserQueryPreprocessor 3 | de.cxp.ocs.elasticsearch.query.NonAlphanumericStripPreprocessor 4 | -------------------------------------------------------------------------------- /search-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8534 3 | compression: 4 | enabled: true 5 | mime-types: 6 | - text/html 7 | - text/css 8 | - application/javascript 9 | - application/json 10 | 11 | endpoints: 12 | prometheus: 13 | sensitive: true 14 | 15 | management: 16 | endpoints: 17 | web: 18 | exposure: 19 | include: "health,metrics,prometheus,refresh" 20 | metrics: 21 | enabled: true 22 | tags: 23 | application: "${spring.application.name}" 24 | 25 | spring: 26 | cloud.config.enabled: false 27 | jackson.deserialization.fail_on_unknown_properties: false 28 | 29 | ocs: 30 | connection-configuration: 31 | hosts: ${ES_HOSTS:http://localhost:9200} 32 | auth: ${ES_AUTH} 33 | -------------------------------------------------------------------------------- /search-service/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=search-service 2 | spring.main.banner-mode=OFF 3 | 4 | # Optional Spring Cloud Config 5 | #spring.cloud.config.enabled=true 6 | #spring.config.import=configserver:http://localhost:8530/ -------------------------------------------------------------------------------- /search-service/src/test/java/de/cxp/ocs/elasticsearch/query/analyzer/AnalyzerUtil.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.elasticsearch.query.analyzer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import de.cxp.ocs.elasticsearch.model.query.AnalyzedQuery; 7 | import de.cxp.ocs.elasticsearch.model.query.ExtendedQuery; 8 | import de.cxp.ocs.elasticsearch.model.term.QueryStringTerm; 9 | import de.cxp.ocs.elasticsearch.model.visitor.QueryTermVisitor; 10 | 11 | public class AnalyzerUtil { 12 | 13 | public static List extractTerms(ExtendedQuery analyzedQuery) { 14 | List result = new ArrayList<>(); 15 | analyzedQuery.getSearchQuery().accept(new QueryTermVisitor() { 16 | 17 | @Override 18 | public void visitTerm(QueryStringTerm term) { 19 | result.add(term); 20 | } 21 | 22 | @Override 23 | public void visitSubQuery(AnalyzedQuery term) { 24 | term.accept(this); 25 | } 26 | }); 27 | result.addAll(analyzedQuery.getFilters()); 28 | return result; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /search-service/src/test/java/de/cxp/ocs/util/TestUtils.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.util; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | public final class TestUtils { 6 | 7 | private TestUtils() {} 8 | 9 | public static T assertAndCastInstanceOf(Object term, Class clazz) { 10 | assertTrue(clazz.isAssignableFrom(term.getClass()), "expected object of type " + clazz + " but was " + term.getClass()); 11 | return clazz.cast(term); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /suggest-service-parent/ocs-suggest-data-provider/src/main/resources/META-INF/services/de.cxp.ocs.smartsuggest.spi.SuggestDataProvider: -------------------------------------------------------------------------------- 1 | de.cxp.ocs.elasticsearch.ElasticsearchSuggestDataProvider -------------------------------------------------------------------------------- /suggest-service-parent/ocs-suggest-data-provider/src/main/resources/ocs-suggest.default.properties: -------------------------------------------------------------------------------- 1 | # can be a comma separated list to specify several endpoints 2 | elasticsearch.hosts=localhost:9200 3 | 4 | # optional basic auth can be specified as well 5 | #elasticsearch.auth=user:password 6 | 7 | # enable availability of index name like that 8 | #suggest.index. .enable=true 9 | # if not set, the data provider will simply check, if index exists 10 | 11 | # specify fields from which suggestions should be fetched: 12 | #suggest.index. .sourceFields=brand,category 13 | # or set a default value for all indexes: 14 | #suggest.index.default.sourceFields=brand 15 | 16 | # configure if similar queries from different sources should be deduplicated: 17 | #suggest.index. .deduplicate 18 | #suggest.index.default.deduplicate 19 | 20 | # configure maxFetchSize (for aggregations and records) per index 21 | #suggest.index. .maxFetchSize=1000 22 | # or rely on default: 23 | suggest.index.default.maxFetchSize=1000 24 | -------------------------------------------------------------------------------- /suggest-service-parent/s3-suggest-archive-provider/src/main/resources/META-INF/services/de.cxp.ocs.smartsuggest.spi.IndexArchiveProvider: -------------------------------------------------------------------------------- 1 | de.cxp.ocs.smartsuggest.S3ArchiveProvider 2 | -------------------------------------------------------------------------------- /suggest-service-parent/s3-suggest-archive-provider/src/test/java/de/cxp/ocs/smartsuggest/Util.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import java.util.Random; 6 | 7 | public class Util { 8 | 9 | public static int getFreePort() { 10 | Random r = new Random(); 11 | 12 | int foundPort = 0; 13 | while (foundPort == 0) { 14 | int randomPort = 41000 + r.nextInt(1000); 15 | try (ServerSocket serverSocket = new ServerSocket(randomPort)) { 16 | if (serverSocket.getLocalPort() == randomPort) { 17 | foundPort = randomPort; 18 | } 19 | } 20 | catch (IOException e) { 21 | // ignore, search another port 22 | } 23 | } 24 | return foundPort; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/limiter/CutOffLimiter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.limiter; 2 | 3 | import java.util.List; 4 | 5 | import de.cxp.ocs.smartsuggest.querysuggester.Suggestion; 6 | 7 | /** 8 | * Simplest implementation, that just cut's off the given list with the 9 | * specified limit. 10 | */ 11 | public class CutOffLimiter implements Limiter { 12 | 13 | @Override 14 | public List limit(List suggestions, int limit) { 15 | return suggestions.size() > limit ? suggestions.subList(0, limit) : suggestions; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/limiter/Limiter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.limiter; 2 | 3 | import java.util.List; 4 | 5 | import de.cxp.ocs.smartsuggest.querysuggester.Suggestion; 6 | 7 | /** 8 | * 9 | * A Limiter is used for results that have different kind of suggestions, for 10 | * example if several different data sources are used at different suggesters, 11 | * as it's done at the 12 | * {@link de.cxp.ocs.smartsuggest.querysuggester.CompoundQuerySuggester}. 13 | *
14 | *15 | * It could also be used to retrieve more suggestions, group or sort them by a 16 | * certain criterion and limit them afterwards. 17 | *
18 | */ 19 | public interface Limiter { 20 | 21 | Listlimit(List suggestions, int limit); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/monitoring/DistributionSummaryAdapter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.monitoring; 2 | 3 | import io.micrometer.core.instrument.DistributionSummary; 4 | 5 | public interface DistributionSummaryAdapter { 6 | 7 | void record(double size); 8 | 9 | static DistributionSummaryAdapter of(DistributionSummary summary) { 10 | return summary::record; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/monitoring/Instrumentable.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.monitoring; 2 | 3 | import io.micrometer.core.instrument.Tag; 4 | 5 | public interface Instrumentable { 6 | 7 | /** 8 | * Optional meter registry (adapter that gives access to the actual 9 | * meter-registry). If not available, no metrics should be measured. 10 | * 11 | * @param metricsRegistryAdapter 12 | * optional adapter 13 | * @param tags 14 | * these "standard" tags should be used for all added sensors. More 15 | * tags can be added. 16 | */ 17 | void instrument(MeterRegistryAdapter metricsRegistryAdapter, Iterable tags); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/monitoring/MeterRegistryAdapter.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.monitoring; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | import lombok.NonNull; 5 | 6 | public interface MeterRegistryAdapter { 7 | 8 | @NonNull 9 | MeterRegistry getMetricsRegistry(); 10 | 11 | static MeterRegistryAdapter of(MeterRegistry registry) { 12 | return () -> registry; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/GroupingSuggester.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import de.cxp.ocs.smartsuggest.limiter.Limiter; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.experimental.Accessors; 10 | 11 | @RequiredArgsConstructor 12 | public class GroupingSuggester implements QuerySuggester { 13 | 14 | private final QuerySuggester delegate; 15 | private final Limiter limiter; 16 | 17 | @Setter 18 | @Accessors(chain = true) 19 | private int prefetchLimitFactor = 1; 20 | 21 | @Override 22 | public List suggest(String term, int maxResults, Set tags) throws SuggestException { 23 | List fetchedResults = delegate.suggest(term, maxResults * prefetchLimitFactor, tags); 24 | return limiter.limit(fetchedResults, maxResults); 25 | } 26 | 27 | @Override 28 | public long recordCount() { 29 | return delegate.recordCount(); 30 | } 31 | 32 | @Override 33 | public boolean isReady() { 34 | return delegate.isReady(); 35 | } 36 | 37 | @Override 38 | public void close() throws Exception { 39 | delegate.close(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/NoopQuerySuggester.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester; 2 | 3 | import static java.util.Collections.emptyList; 4 | 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import lombok.AllArgsConstructor; 9 | import lombok.NoArgsConstructor; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class NoopQuerySuggester implements QuerySuggester { 14 | 15 | private boolean isReady = false; 16 | 17 | @Override 18 | public void close() throws Exception {} 19 | 20 | @Override 21 | public List suggest(String term, int maxResults, Set tags) throws SuggestException { 22 | return emptyList(); 23 | } 24 | 25 | @Override 26 | public boolean isReady() { 27 | return isReady; 28 | } 29 | 30 | @Override 31 | public long recordCount() { 32 | return 0; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/QueryIndexer.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester; 2 | 3 | import java.io.IOException; 4 | import java.time.Instant; 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | import de.cxp.ocs.smartsuggest.spi.SuggestRecord; 8 | 9 | public interface QueryIndexer { 10 | 11 | /** 12 | * @return The time of the last successful indexing 13 | */ 14 | Instant getIndexModTime(); 15 | 16 | /** 17 | * @param suggestions 18 | * the suggestions to index 19 | * @param modificationTime 20 | * timestamp (millis) of the data origin 21 | * @return future that is ready as soon as the indexation is done 22 | * @throws IOException 23 | * in case indexation fails 24 | */ 25 | CompletableFuture index(Iterable suggestions, long modificationTime) throws IOException; 26 | } 27 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/SuggestException.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester; 2 | 3 | import java.io.Serial; 4 | 5 | public class SuggestException extends RuntimeException { 6 | 7 | @Serial private static final long serialVersionUID = -4055077488050336590L; 8 | 9 | public SuggestException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | public SuggestException(Throwable cause) { 14 | super(cause); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/SuggesterEngine.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester; 2 | 3 | public enum SuggesterEngine { 4 | 5 | /** 6 | * Implementation that uses Apache Lucene 7 | */ 8 | LUCENE, 9 | 10 | } 11 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/SuggesterFactory.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester; 2 | 3 | import de.cxp.ocs.smartsuggest.monitoring.Instrumentable; 4 | import de.cxp.ocs.smartsuggest.spi.IndexArchive; 5 | import de.cxp.ocs.smartsuggest.spi.SuggestConfig; 6 | import de.cxp.ocs.smartsuggest.spi.SuggestData; 7 | 8 | import java.io.IOException; 9 | 10 | public interface SuggesterFactory extends Instrumentable { 11 | 12 | T getSuggester(SuggestData suggestData, SuggestConfig suggestConfig); 13 | 14 | IndexArchive createArchive(QuerySuggester querySuggester) throws IOException; 15 | 16 | T recover(IndexArchive baseDir, SuggestConfig suggestConfig) throws IOException; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/lucene/PerfResult.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester.lucene; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import org.apache.commons.lang3.time.StopWatch; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Data 13 | public class PerfResult { 14 | 15 | private String inputQuery; 16 | private int resultCount; 17 | private StopWatch totalTime; 18 | private List steps = new ArrayList<>(); 19 | 20 | @ToString.Exclude 21 | private long _lastSplit = 0; 22 | 23 | public PerfResult(String inputQuery) { 24 | this.inputQuery = inputQuery; 25 | totalTime = new StopWatch(); 26 | totalTime.start(); 27 | } 28 | 29 | @ToString 30 | @Getter 31 | @AllArgsConstructor 32 | public static class StepTime { 33 | 34 | String step; 35 | long timeMs; 36 | int results; 37 | } 38 | 39 | public void addStep(String step, int results) { 40 | // get time since last step 41 | long splitTime = totalTime.getDuration().toMillis(); 42 | steps.add(new StepTime(step, splitTime - _lastSplit, results)); 43 | _lastSplit = splitTime; 44 | } 45 | 46 | public void stop() { 47 | totalTime.stop(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/lucene/SuggestionBestMatchIterator.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester.lucene; 2 | 3 | import de.cxp.ocs.smartsuggest.spi.SuggestRecord; 4 | 5 | import java.util.Iterator; 6 | 7 | class SuggestionBestMatchIterator extends SuggestionIterator { 8 | 9 | SuggestionBestMatchIterator(Iterator innerIterator) { 10 | super(innerIterator); 11 | } 12 | 13 | @Override 14 | protected String getSearchText(SuggestRecord suggestion) { 15 | return suggestion.getPrimaryText(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/querysuggester/lucene/SuggestionVariantIterator.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.querysuggester.lucene; 2 | 3 | import de.cxp.ocs.smartsuggest.spi.SuggestRecord; 4 | 5 | import java.util.Iterator; 6 | 7 | class SuggestionVariantIterator extends SuggestionIterator { 8 | 9 | private final static String EMPTY = ""; 10 | private final static int MaxTermLength = 32000; 11 | 12 | SuggestionVariantIterator(Iterator innerIterator) { 13 | super(innerIterator); 14 | } 15 | 16 | @Override 17 | protected String getSearchText(SuggestRecord suggestion) { 18 | String searchText = suggestion.getSecondaryText(); 19 | if (searchText == null) { 20 | searchText = EMPTY; 21 | } 22 | else if (searchText.length() > MaxTermLength) { 23 | searchText = searchText.substring(0, MaxTermLength); 24 | } 25 | return searchText; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/CommonPayloadFields.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Common keys for the payload attached to {@link SuggestRecord}s. 8 | */ 9 | public final class CommonPayloadFields { 10 | 11 | public static final String PAYLOAD_TYPE_KEY = "type"; 12 | public static final String PAYLOAD_TYPE_OTHER = "other"; 13 | public static final String PAYLOAD_COUNT_KEY = "count"; 14 | public static final String PAYLOAD_LABEL_KEY = "meta.label"; 15 | public static final String PAYLOAD_GROUPMATCH_KEY = "meta.matchGroupName"; 16 | public static final String PAYLOAD_WEIGHT_KEY = "meta.weight"; 17 | public static final String PAYLOAD_MATCH_KEY = "meta.matchKey"; 18 | 19 | 20 | private CommonPayloadFields() {} 21 | 22 | public static Map payloadOfTypeAndCount(String type, String count) { 23 | Map payload = new HashMap<>(2); 24 | payload.put(PAYLOAD_TYPE_KEY, type); 25 | payload.put(PAYLOAD_COUNT_KEY, count); 26 | return payload; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/DatedData.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi; 2 | 3 | /** 4 | * Data that's actuality can be assigned to a date and time. 5 | */ 6 | public interface DatedData { 7 | 8 | /** 9 | * Get unix timestamp in millis for the last time this data was modified. 10 | * 11 | * @return unix time in millis 12 | */ 13 | long getModificationTime(); 14 | } 15 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/IndexArchive.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Wrapper around an archive file that can be stored and loaded. The file is expected to be a .tar.gz file. 7 | * 8 | * @param zippedTarFile 9 | * The file is expected to be a .tar.gz file 10 | * @param dataModificationTime 11 | * modification time of the data in this archive (not the file creation time). 12 | */ 13 | public record IndexArchive(File zippedTarFile, long dataModificationTime) implements DatedData { 14 | 15 | @Override 16 | public long getModificationTime() { 17 | return dataModificationTime; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/IndexArchiveProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi; 2 | 3 | import java.io.IOException; 4 | 5 | public interface IndexArchiveProvider extends AbstractDataProvider { 6 | 7 | void store(String indexName, IndexArchive archive) throws IOException; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/SuggestConfigProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi; 2 | 3 | import lombok.NonNull; 4 | 5 | public interface SuggestConfigProvider { 6 | 7 | /** 8 | * Retrieve config for a given index. In case only some index specific values should be set, the default suggest 9 | * config can be used since it may contain global settings (if not null). 10 | * 11 | * @param indexName 12 | * @param defaultSuggestConfig 13 | * copy of the suggest config that was set as default for the whole service. 14 | * It can be modified and returned or a different config object can be returned. 15 | * Returning null is considered equivalent to returning that default config. 16 | * @return 17 | */ 18 | SuggestConfig getConfig(@NonNull 19 | String indexName, SuggestConfig defaultSuggestConfig); 20 | 21 | /** 22 | * Return priority when this config provider should be asked for the configuration. Providers with a low priority 23 | * value (e.g. 1) will be asked first and providers with a higher value will be called later and can overwrite 24 | * previously set values. 25 | * 26 | * @return priority value, defaults to 100 27 | */ 28 | default int getPriority() { 29 | return 100; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/SuggestDataProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi; 2 | 3 | /** 4 | * Implementations of this SPI are capable of providing source data for the suggest index. 5 | */ 6 | public interface SuggestDataProvider extends AbstractDataProvider { 7 | 8 | /** 9 | * In case the same data provider implementation more than once for an index 10 | * to provide different kind of data, each instance should return a different 11 | * name, so that the data sources can be archived and recovered separately. 12 | * Per default the class-name is returned. 13 | * 14 | * @return name to distinguish data source 15 | */ 16 | default String getName() { 17 | return this.getClass().getSimpleName(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/standard/DefaultSuggestConfigProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi.standard; 2 | 3 | import de.cxp.ocs.smartsuggest.spi.SuggestConfig; 4 | import de.cxp.ocs.smartsuggest.spi.SuggestConfigProvider; 5 | import lombok.NonNull; 6 | 7 | public class DefaultSuggestConfigProvider implements SuggestConfigProvider { 8 | 9 | private SuggestConfig defaultSuggestConfig = new SuggestConfig(); 10 | 11 | public DefaultSuggestConfigProvider() {} 12 | 13 | public DefaultSuggestConfigProvider(@NonNull SuggestConfig defaultSuggestConfig) { 14 | this.defaultSuggestConfig = defaultSuggestConfig; 15 | } 16 | 17 | @Override 18 | public SuggestConfig getConfig(@NonNull String indexName, SuggestConfig indexConfig) { 19 | return indexConfig == null ? defaultSuggestConfig.clone() : indexConfig; 20 | } 21 | 22 | @Override 23 | public int getPriority() { 24 | return Integer.MAX_VALUE; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/main/java/de/cxp/ocs/smartsuggest/spi/test/SdpMock.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.spi.test; 2 | 3 | import de.cxp.ocs.smartsuggest.spi.SuggestData; 4 | import de.cxp.ocs.smartsuggest.spi.SuggestDataProvider; 5 | import lombok.NonNull; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.experimental.Accessors; 9 | 10 | @RequiredArgsConstructor 11 | @Accessors(fluent = true) 12 | @Setter 13 | class SdpMock implements SuggestDataProvider { 14 | 15 | @NonNull 16 | private final String indexName; 17 | private SuggestData suggestData = null; 18 | private String name = "sdpMock"; 19 | 20 | @Override 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | @Override 26 | public boolean hasData(String indexName) { 27 | return this.indexName.equals(indexName) && suggestData != null; 28 | } 29 | 30 | @Override 31 | public long getLastDataModTime(String indexName) { 32 | return suggestData == null ? -1L : suggestData.getModificationTime(); 33 | } 34 | 35 | @Override 36 | public SuggestData loadData(String indexName) { 37 | return suggestData; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/test/java/de/cxp/ocs/smartsuggest/updater/LocalIndexArchiveProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.updater; 2 | 3 | import de.cxp.ocs.smartsuggest.spi.IndexArchive; 4 | import de.cxp.ocs.smartsuggest.spi.IndexArchiveProvider; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | public class LocalIndexArchiveProvider implements IndexArchiveProvider { 11 | 12 | final private Map cached = new HashMap<>(); 13 | 14 | @Override 15 | public void store(String indexName, IndexArchive archive) { 16 | cached.put(indexName, archive); 17 | } 18 | 19 | @Override 20 | public boolean hasData(String indexName) { 21 | return cached.containsKey(indexName); 22 | } 23 | 24 | @Override 25 | public long getLastDataModTime(String indexName) { 26 | return Optional.ofNullable(cached.get(indexName)).map(IndexArchive::dataModificationTime).orElse(-1L); 27 | } 28 | 29 | @Override 30 | public IndexArchive loadData(String indexName) { 31 | return cached.get(indexName); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/test/java/de/cxp/ocs/smartsuggest/util/TestDataProvider.java: -------------------------------------------------------------------------------- 1 | package de.cxp.ocs.smartsuggest.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import de.cxp.ocs.smartsuggest.spi.SuggestData; 7 | import de.cxp.ocs.smartsuggest.spi.SuggestDataProvider; 8 | 9 | public class TestDataProvider implements SuggestDataProvider { 10 | 11 | Map modDates = new HashMap<>(); 12 | Map suggestData = new HashMap<>(); 13 | 14 | public TestDataProvider putData(String indexName, SuggestData data) { 15 | suggestData.put(indexName, data); 16 | modDates.put(indexName, data.getModificationTime() > 0 ? data.getModificationTime() : System.currentTimeMillis()); 17 | return this; 18 | } 19 | 20 | @Override 21 | public boolean hasData(String indexName) { 22 | return suggestData.containsKey(indexName); 23 | } 24 | 25 | @Override 26 | public long getLastDataModTime(String indexName) { 27 | return modDates.getOrDefault(indexName, -1L); 28 | } 29 | 30 | @Override 31 | public SuggestData loadData(String indexName) { 32 | return suggestData.get(indexName); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /suggest-service-parent/smartsuggest-lib/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The logging properties used 3 | # 4 | log4j.rootLogger=INFO, out 5 | 6 | # uncomment the following line to turn on Camel debugging 7 | #log4j.logger.org.apache.camel=DEBUG 8 | 9 | # CONSOLE appender not used by default 10 | log4j.appender.out=org.apache.log4j.ConsoleAppender 11 | log4j.appender.out.layout=org.apache.log4j.PatternLayout 12 | #log4j.appender.out.layout.ConversionPattern=[%30.30t] %-30.30c{1} %-5p %m%n 13 | log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n 14 | 15 | -------------------------------------------------------------------------------- /suggest-service-parent/suggest-service/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | #rapidoid config 2 | log: 3 | level: info 4 | fancy: false -------------------------------------------------------------------------------- /suggest-service-parent/suggest-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /suggest-service-parent/suggest-service/src/main/resources/static/auto-complete.css: -------------------------------------------------------------------------------- 1 | .autocomplete-suggestions { 2 | text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1); 3 | 4 | /* core styles should not be changed */ 5 | position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; 6 | } 7 | .autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; } 8 | .autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; } 9 | .autocomplete-suggestion.selected { background: #f0f0f0; } 10 | -------------------------------------------------------------------------------- /suggest-service-parent/suggest-service/src/main/resources/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommerceExperts/open-commerce-search/4a5cc11a7a871b647c92fdf5a5bf9694ef5483b3/suggest-service-parent/suggest-service/src/main/resources/static/favicon.png --------------------------------------------------------------------------------4 | 6 | 10 | 11 |7 | 9 |%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 |12 | 14 |13 |