| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- import { LanguageCode, LogicalOperator, PriceRange, SortOrder } from '@vendure/common/lib/generated-types';
- import { DeepRequired, ID, RequestContext, UserInputError } from '@vendure/core';
- import { SearchConfig } from './options';
- import { CustomScriptMapping, ElasticSearchInput, ElasticSearchSortInput, SearchRequestBody } from './types';
- /**
- * Given a SearchInput object, returns the corresponding Elasticsearch body.
- */
- export function buildElasticBody(
- input: ElasticSearchInput,
- searchConfig: DeepRequired<SearchConfig>,
- channelId: ID,
- languageCode: LanguageCode,
- enabledOnly: boolean = false,
- ctx: RequestContext,
- ): SearchRequestBody {
- const {
- term,
- facetValueIds,
- facetValueOperator,
- collectionId,
- collectionSlug,
- groupByProduct,
- skip,
- take,
- sort,
- priceRangeWithTax,
- priceRange,
- facetValueFilters,
- inStock,
- } = input;
- const query: any = {
- bool: {},
- };
- ensureBoolFilterExists(query);
- query.bool.filter.push({ term: { channelId } });
- query.bool.filter.push({ term: { languageCode } });
- if (term) {
- query.bool.must = [
- {
- multi_match: {
- query: term,
- fuzziness: 'AUTO',
- type: searchConfig.multiMatchType,
- fields: [
- `productName^${searchConfig.boostFields.productName}`,
- `productVariantName^${searchConfig.boostFields.productVariantName}`,
- `description^${searchConfig.boostFields.description}`,
- `sku^${searchConfig.boostFields.sku}`,
- ],
- },
- },
- ];
- }
- if (facetValueIds && facetValueIds.length) {
- ensureBoolFilterExists(query);
- const operator = facetValueOperator === LogicalOperator.AND ? 'must' : 'should';
- query.bool.filter = query.bool.filter.concat([
- {
- bool: { [operator]: facetValueIds.map(id => ({ term: { facetValueIds: id } })) },
- },
- ]);
- }
- if (facetValueFilters && facetValueFilters.length) {
- ensureBoolFilterExists(query);
- facetValueFilters.forEach(facetValueFilter => {
- if (facetValueFilter.and && facetValueFilter.or && facetValueFilter.or.length) {
- throw new UserInputError('error.facetfilterinput-invalid-input');
- }
- if (facetValueFilter.and) {
- query.bool.filter.push({ term: { facetValueIds: facetValueFilter.and } });
- }
- if (facetValueFilter.or && facetValueFilter.or.length) {
- query.bool.filter.push({
- bool: { ['should']: facetValueFilter.or.map(id => ({ term: { facetValueIds: id } })) },
- });
- }
- });
- }
- if (collectionId) {
- ensureBoolFilterExists(query);
- query.bool.filter.push({ term: { collectionIds: collectionId } });
- }
- if (collectionSlug) {
- ensureBoolFilterExists(query);
- query.bool.filter.push({ term: { collectionSlugs: collectionSlug } });
- }
- if (enabledOnly) {
- ensureBoolFilterExists(query);
- query.bool.filter.push({ term: { enabled: true } });
- }
- if (priceRange) {
- ensureBoolFilterExists(query);
- query.bool.filter = query.bool.filter.concat(createPriceFilters(priceRange, false));
- }
- if (priceRangeWithTax) {
- ensureBoolFilterExists(query);
- query.bool.filter = query.bool.filter.concat(createPriceFilters(priceRangeWithTax, true));
- }
- if (inStock !== undefined) {
- ensureBoolFilterExists(query);
- if (groupByProduct) {
- query.bool.filter.push({ term: { productInStock: inStock } });
- } else {
- query.bool.filter.push({ term: { inStock } });
- }
- }
- const sortArray: ElasticSearchSortInput = [];
- if (sort) {
- if (sort.name) {
- sortArray.push({
- 'productName.keyword': { order: sort.name === SortOrder.ASC ? 'asc' : 'desc' },
- });
- }
- if (sort.price) {
- const priceField = 'price';
- sortArray.push({ [priceField]: { order: sort.price === SortOrder.ASC ? 'asc' : 'desc' } });
- }
- }
- const scriptFields: any | undefined = createScriptFields(
- searchConfig.scriptFields,
- input,
- groupByProduct,
- );
- const body: SearchRequestBody = {
- query: searchConfig.mapQuery
- ? searchConfig.mapQuery(query, input, searchConfig, channelId, enabledOnly, ctx)
- : query,
- sort: searchConfig.mapSort ? searchConfig.mapSort(sortArray, input) : sortArray,
- from: skip || 0,
- size: take || 10,
- track_total_hits: searchConfig.totalItemsMaxSize,
- ...(scriptFields !== undefined
- ? {
- _source: true,
- script_fields: scriptFields,
- }
- : undefined),
- };
- if (groupByProduct) {
- body.collapse = { field: 'productId' };
- }
- return body;
- }
- function ensureBoolFilterExists(query: { bool: { filter?: any } }) {
- if (!query.bool.filter) {
- query.bool.filter = [];
- }
- }
- function createScriptFields(
- scriptFields: { [fieldName: string]: CustomScriptMapping<[ElasticSearchInput]> },
- input: ElasticSearchInput,
- groupByProduct?: boolean,
- ): any | undefined {
- if (scriptFields) {
- const fields = Object.keys(scriptFields);
- if (fields.length) {
- const result: any = {};
- for (const name of fields) {
- const scriptField = scriptFields[name];
- if (scriptField.context === 'product' && groupByProduct === true) {
- result[name] = scriptField.scriptFn(input);
- }
- if (scriptField.context === 'variant' && groupByProduct === false) {
- result[name] = scriptField.scriptFn(input);
- }
- if (scriptField.context === 'both' || scriptField.context === undefined) {
- result[name] = scriptField.scriptFn(input);
- }
- }
- return result;
- }
- }
- return undefined;
- }
- function createPriceFilters(range: PriceRange, withTax: boolean): any[] {
- const withTaxFix = withTax ? 'WithTax' : '';
- return [
- {
- range: {
- ['price' + withTaxFix]: {
- gte: range.min,
- lte: range.max,
- },
- },
- },
- ];
- }
|