From 7c9f1f1106a18d019d57af723be58701d57d8af9 Mon Sep 17 00:00:00 2001 From: Joost Smit Date: Thu, 15 Aug 2024 10:46:24 +0200 Subject: [PATCH] wip --- src/lib/productSearchFilter.ts | 1 + src/product-search.ts | 100 +++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/product-search.ts diff --git a/src/lib/productSearchFilter.ts b/src/lib/productSearchFilter.ts index 7381490..55fa135 100644 --- a/src/lib/productSearchFilter.ts +++ b/src/lib/productSearchFilter.ts @@ -29,6 +29,7 @@ type ProductSearchFilterFunc = ( markMatchingVariants: boolean, ) => boolean; +// TODO: fieldType checking, better text/wildcard search, result boosting export const parseSearchQuery = (searchQuery: _SearchQuery): ProductSearchFilterFunc => { if (isSearchAndExpression(searchQuery)) { return (obj, markMatchingVariant) => searchQuery.and.every((expr) => { diff --git a/src/product-search.ts b/src/product-search.ts new file mode 100644 index 0000000..33c1f19 --- /dev/null +++ b/src/product-search.ts @@ -0,0 +1,100 @@ +import { InvalidInputError, Product, ProductPagedSearchResponse, ProductProjection, ProductSearchRequest } from "@commercetools/platform-sdk"; +import { AbstractStorage } from "./storage"; +import { CommercetoolsError } from "./exceptions"; +import { parseSearchQuery } from "./lib/productSearchFilter"; +import { applyPriceSelector } from "./priceSelector"; + +export class ProductSearch { + protected _storage: AbstractStorage; + + constructor(storage: AbstractStorage) { + this._storage = storage; + } + + search( + projectKey: string, + params: ProductSearchRequest, + ): ProductPagedSearchResponse { + let resources = this._storage + .all(projectKey, "product") + .map((r) => this.transform(r, params.productProjectionParameters?.staged ?? false)) + .filter((p) => { + if (!params.productProjectionParameters?.staged ?? false) { + return p.published; + } + return true; + }); + + const markMatchingVariant = params.markMatchingVariants ?? false; + + // Apply filters pre facetting + if (params.query) { + try { + const matchFunc = parseSearchQuery(params.query); + + // Filters can modify the output. So clone the resources first. + resources = resources.filter((resource) => + matchFunc(resource, markMatchingVariant), + ); + } catch (err) { + console.error(err); + throw new CommercetoolsError( + { + code: "InvalidInput", + message: (err as any).message, + }, + 400, + ); + } + } + + // Apply the priceSelector + if (params.productProjectionParameters) { + applyPriceSelector(resources, { + country: params.productProjectionParameters.priceCountry, + channel: params.productProjectionParameters.priceChannel, + customerGroup: params.productProjectionParameters.priceCustomerGroup, + currency: params.productProjectionParameters.priceCurrency, + }); + } + + // TODO: Calculate facets + // TODO: sorting based on boosts + + const offset = params.offset || 0; + const limit = params.limit || 20; + const results = resources.slice(offset, offset + limit); + + return { + total: resources.length, + offset: offset, + limit: limit, + results: results, + facets: [], + }; + } + + transform(product: Product, staged: boolean): ProductProjection { + const obj = !staged + ? product.masterData.current + : product.masterData.staged; + + return { + id: product.id, + createdAt: product.createdAt, + lastModifiedAt: product.lastModifiedAt, + version: product.version, + name: obj.name, + key: product.key, + description: obj.description, + metaDescription: obj.metaDescription, + slug: obj.slug, + categories: obj.categories, + masterVariant: obj.masterVariant, + variants: obj.variants, + productType: product.productType, + hasStagedChanges: product.masterData.hasStagedChanges, + published: product.masterData.published, + }; + } +} \ No newline at end of file