diff --git a/.eslintignore b/.eslintignore index e04e19f31f..197796e10e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -19,6 +19,7 @@ /packages/medusa/src/subscribers/notification.js /packages/medusa/src/subscribers/order.js +/packages/medusa/src/subscribers/product.js /packages/medusa/src/loaders/api.js /packages/medusa/src/loaders/database.js @@ -64,6 +65,7 @@ /packages/medusa-payment-manual /packages/medusa-payment-paypal /packages/medusa-payment-stripe +/packages/medusa-plugin-meilisearch /packages/medusa-plugin-add-ons /packages/medusa-plugin-brightpearl /packages/medusa-plugin-contentful diff --git a/packages/medusa-interfaces/src/base-service.js b/packages/medusa-interfaces/src/base-service.js index 8ded092f51..8f1238ce90 100644 --- a/packages/medusa-interfaces/src/base-service.js +++ b/packages/medusa-interfaces/src/base-service.js @@ -19,7 +19,7 @@ class BaseService { * Used to build TypeORM queries. */ buildQuery_(selector, config = {}) { - const build = obj => { + const build = (obj) => { const where = Object.entries(obj).reduce((acc, [key, value]) => { switch (true) { case value instanceof FindOperator: @@ -49,11 +49,11 @@ class BaseService { }) acc[key] = Raw( - a => + (a) => subquery .map((s, index) => `${a} ${s.operator} :${index}`) .join(" AND "), - subquery.map(s => s.value) + subquery.map((s) => s.value) ) break default: @@ -149,7 +149,7 @@ class BaseService { return work(this.transactionManager_) } else { const temp = this.manager_ - const doWork = async m => { + const doWork = async (m) => { this.manager_ = m this.transactionManager_ = m try { @@ -167,17 +167,17 @@ class BaseService { if (isolation) { let result try { - result = await this.manager_.transaction(isolation, m => doWork(m)) + result = await this.manager_.transaction(isolation, (m) => doWork(m)) return result } catch (error) { if (this.shouldRetryTransaction(error)) { - return this.manager_.transaction(isolation, m => doWork(m)) + return this.manager_.transaction(isolation, (m) => doWork(m)) } else { throw error } } } - return this.manager_.transaction(m => doWork(m)) + return this.manager_.transaction((m) => doWork(m)) } } @@ -230,7 +230,7 @@ class BaseService { */ runDecorators_(obj, fields = [], expandFields = []) { return this.decorators_.reduce(async (acc, next) => { - return acc.then(res => next(res, fields, expandFields)).catch(() => acc) + return acc.then((res) => next(res, fields, expandFields)).catch(() => acc) }, Promise.resolve(obj)) } } diff --git a/packages/medusa-plugin-meilisearch/.eslintignore b/packages/medusa-plugin-meilisearch/.eslintignore new file mode 100644 index 0000000000..9f560229da --- /dev/null +++ b/packages/medusa-plugin-meilisearch/.eslintignore @@ -0,0 +1,8 @@ +/src/subscribers + +/api +/services +/models +/subscribers +/loaders +/utils diff --git a/packages/medusa-plugin-meilisearch/.eslintrc b/packages/medusa-plugin-meilisearch/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-meilisearch/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-meilisearch/.prettierrc b/packages/medusa-plugin-meilisearch/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-meilisearch/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-meilisearch/src/loaders/index.js b/packages/medusa-plugin-meilisearch/src/loaders/index.js index b7ba033487..d6b3e76e11 100644 --- a/packages/medusa-plugin-meilisearch/src/loaders/index.js +++ b/packages/medusa-plugin-meilisearch/src/loaders/index.js @@ -8,6 +8,7 @@ export default async (container, options) => { ) ) } catch (err) { + // ignore console.log(err) } } diff --git a/packages/medusa-plugin-meilisearch/src/services/meilisearch.js b/packages/medusa-plugin-meilisearch/src/services/meilisearch.js index 7def3c2c40..8d53c5f06a 100644 --- a/packages/medusa-plugin-meilisearch/src/services/meilisearch.js +++ b/packages/medusa-plugin-meilisearch/src/services/meilisearch.js @@ -59,7 +59,9 @@ class MeiliSearchService extends SearchService { } transformProducts(products) { - if (!products) return [] + if (!products) { + return [] + } return products.map(transformProduct) } } diff --git a/packages/medusa-plugin-meilisearch/src/subscribers/product-search.js b/packages/medusa-plugin-meilisearch/src/subscribers/product-search.js deleted file mode 100644 index bcfdfbf55e..0000000000 --- a/packages/medusa-plugin-meilisearch/src/subscribers/product-search.js +++ /dev/null @@ -1,106 +0,0 @@ -import { indexTypes } from "medusa-core-utils" -import { transformProduct } from "../utils/transform-product" - -class ProductSearchSubscriber { - constructor( - { eventBusService, meilisearchService, productService }, - options - ) { - this.eventBus_ = eventBusService - - this.meilisearchService_ = meilisearchService - - this.productService_ = productService - - this.productIndexName = productService.constructor.IndexName - - this.eventBus_.subscribe("product.created", this.handleProductCreation) - - this.eventBus_.subscribe("product.updated", this.handleProductUpdate) - - this.eventBus_.subscribe("product.deleted", this.handleProductDeletion) - - this.eventBus_.subscribe( - "product-variant.created", - this.handleProductVariantChange - ) - - this.eventBus_.subscribe( - "product-variant.updated", - this.handleProductVariantChange - ) - - this.eventBus_.subscribe( - "product-variant.deleted", - this.handleProductVariantChange - ) - } - - handleProductCreation = async (data) => { - const product = await this.retrieveProduct_(data.id) - await this.meilisearchService_.addDocuments( - this.productIndexName, - [product], - indexTypes.products - ) - } - - retrieveProduct_ = async (product_id) => { - const product = await this.productService_.retrieve(product_id, { - select: [ - "id", - "title", - "subtitle", - "description", - "handle", - "is_giftcard", - "discountable", - "thumbnail", - "profile_id", - "collection_id", - "type_id", - "origin_country", - "created_at", - "updated_at", - ], - relations: [ - "variants", - "tags", - "type", - "collection", - "variants.prices", - "variants.options", - "options", - ], - }) - const transformedProduct = transformProduct(product) - return transformedProduct - } - - handleProductUpdate = async (data) => { - const product = await this.retrieveProduct_(data.id) - await this.meilisearchService_.addDocuments( - this.productIndexName, - [product], - indexTypes.products - ) - } - - handleProductDeletion = async (data) => { - await this.meilisearchService_.deleteDocument( - this.productIndexName, - data.id - ) - } - - handleProductVariantChange = async (data) => { - const product = await this.retrieveProduct_(data.product_id) - await this.meilisearchService_.addDocuments( - this.productIndexName, - [product], - indexTypes.products - ) - } -} - -export default ProductSearchSubscriber diff --git a/packages/medusa-plugin-meilisearch/src/utils/transform-product.js b/packages/medusa-plugin-meilisearch/src/utils/transform-product.js index a1e99660d5..75ab6eaf6f 100644 --- a/packages/medusa-plugin-meilisearch/src/utils/transform-product.js +++ b/packages/medusa-plugin-meilisearch/src/utils/transform-product.js @@ -20,9 +20,8 @@ export const transformProduct = (product) => { variantKeys.forEach((k) => { if (k === "options" && variant[k]) { const values = variant[k].map((option) => option.value) - obj[`${prefix}_options_value`] = obj[`${prefix}_options_value`].concat( - values - ) + obj[`${prefix}_options_value`] = + obj[`${prefix}_options_value`].concat(values) return } return variant[k] && obj[`${prefix}_${k}`].push(variant[k]) diff --git a/packages/medusa-plugin-meilisearch/yarn.lock b/packages/medusa-plugin-meilisearch/yarn.lock index dec1edc80c..15a5be80b3 100644 --- a/packages/medusa-plugin-meilisearch/yarn.lock +++ b/packages/medusa-plugin-meilisearch/yarn.lock @@ -953,18 +953,6 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@hapi/hoek@^9.0.0": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" - integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1166,23 +1154,6 @@ readdirp "^2.2.1" upath "^1.1.1" -"@sideway/address@^4.1.0": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1" - integrity sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -3531,22 +3502,6 @@ jest@^25.5.2: import-local "^3.0.2" jest-cli "^25.5.4" -joi-objectid@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668" - integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ== - -joi@^17.3.0: - version "17.4.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7" - integrity sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.0" - "@sideway/formula" "^3.0.0" - "@sideway/pinpoint" "^2.0.0" - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3777,21 +3732,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -medusa-core-utils@^1.1.22: - version "1.1.22" - resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.22.tgz#84ce0af0a7c672191d758ea462056e30a39d08b1" - integrity sha512-kMuRkWOuNG4Bw6epg/AYu95UJuE+rjHTeTWRLbEPrYGjWREV82tLWVDI21/QcccmaHmMU98Rkw2z9JwyFZIiyw== - dependencies: - joi "^17.3.0" - joi-objectid "^3.0.1" - -medusa-interfaces@^1.1.23: - version "1.1.23" - resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.1.23.tgz#b552a8c1d0eaddeff30472ab238652b9e1a56e73" - integrity sha512-dHCOnsyYQvjrtRd3p0ZqQZ4M/zmo4M/BAgVfRrYSyGrMdQ86TK9Z1DQDCHEzM1216AxEfXz2JYUD7ilTfG2iHQ== - dependencies: - medusa-core-utils "^1.1.22" - meilisearch@^0.20.0: version "0.20.0" resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.20.0.tgz#42899fec7a2ddefcd035e30ed5dd47aa65a6727f" diff --git a/packages/medusa/src/loaders/search-index.js b/packages/medusa/src/loaders/search-index.js index 91c660c2d0..ec2ef503c3 100644 --- a/packages/medusa/src/loaders/search-index.js +++ b/packages/medusa/src/loaders/search-index.js @@ -6,11 +6,11 @@ async function loadProductsIntoSearchEngine(container) { const productService = container.resolve("productService") const TAKE = 20 - const totalCount = await productService.count() - let iterCount = 0, - lastSeenId = "" + let hasMore = true - while (iterCount < totalCount) { + let lastSeenId = "" + + while (hasMore) { const products = await productService.list( { id: { gt: lastSeenId } }, { @@ -44,14 +44,16 @@ async function loadProductsIntoSearchEngine(container) { } ) - await searchService.addDocuments( - ProductService.IndexName, - products, - indexTypes.products - ) - - iterCount += products.length - lastSeenId = products[products.length - 1].id + if (products.length > 0) { + await searchService.addDocuments( + ProductService.IndexName, + products, + indexTypes.products + ) + lastSeenId = products[products.length - 1].id + } else { + hasMore = false + } } } diff --git a/packages/medusa/src/repositories/order.ts b/packages/medusa/src/repositories/order.ts index 4c0727cb49..9ef6c1b9f8 100644 --- a/packages/medusa/src/repositories/order.ts +++ b/packages/medusa/src/repositories/order.ts @@ -34,7 +34,7 @@ export class OrderRepository extends Repository { const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id") - return map(entities, e => merge({}, ...entitiesAndRelationsById[e.id])) + return map(entities, (e) => merge({}, ...entitiesAndRelationsById[e.id])) } public async findOneWithRelations( diff --git a/packages/medusa/src/repositories/product.ts b/packages/medusa/src/repositories/product.ts index f686ef80d2..dae618da4e 100644 --- a/packages/medusa/src/repositories/product.ts +++ b/packages/medusa/src/repositories/product.ts @@ -1,22 +1,35 @@ import { flatten, groupBy, map, merge } from "lodash" -import { EntityRepository, FindManyOptions, Repository } from "typeorm" +import { + OrderByCondition, + EntityRepository, + FindManyOptions, + Repository, +} from "typeorm" import { Product } from "../models/product" +type DefaultWithoutRelations = Omit, "relations"> + +type CustomOptions = { + where?: DefaultWithoutRelations["where"] & { tags?: string[] } + order?: OrderByCondition + skip?: number + take?: number +} + +type FindWithRelationsOptions = CustomOptions + @EntityRepository(Product) export class ProductRepository extends Repository { public async findWithRelations( relations: Array = [], - idsOrOptionsWithoutRelations: Omit< - FindManyOptions, - "relations" - > = {} + idsOrOptionsWithoutRelations: FindWithRelationsOptions = {} ): Promise { - let entities + let entities: Product[] if (Array.isArray(idsOrOptionsWithoutRelations)) { entities = await this.findByIds(idsOrOptionsWithoutRelations) } else { - // Since tags are in a one-to-many realtion they cant be included in a - // regular query, to solve this add the join on tags seperately if + // Since tags are in a one-to-many realtion they cant be included in a + // regular query, to solve this add the join on tags seperately if // the query exists const tags = idsOrOptionsWithoutRelations.where.tags delete idsOrOptionsWithoutRelations.where.tags @@ -25,17 +38,15 @@ export class ProductRepository extends Repository { .where(idsOrOptionsWithoutRelations.where) .skip(idsOrOptionsWithoutRelations.skip) .take(idsOrOptionsWithoutRelations.take) - - if (tags) { + .orderBy(idsOrOptionsWithoutRelations.order) + + if (tags) { qb = qb .leftJoinAndSelect("product.tags", "tags") - .andWhere( - `tags.id IN (:...ids)`, { ids: tags._value} - ) + .andWhere(`tags.id IN (:...ids)`, { ids: tags._value }) } - - entities = await qb - .getMany() + + entities = await qb.getMany() } const entitiesIds = entities.map(({ id }) => id) diff --git a/packages/medusa/src/services/product-variant.js b/packages/medusa/src/services/product-variant.js index ad2f922b73..0e86a3118e 100644 --- a/packages/medusa/src/services/product-variant.js +++ b/packages/medusa/src/services/product-variant.js @@ -119,7 +119,7 @@ class ProductVariantService extends BaseService { * @return {Promise} resolves to the creation result. */ async create(productOrProductId, variant) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productRepo = manager.getCustomRepository(this.productRepository_) const variantRepo = manager.getCustomRepository( this.productVariantRepository_ @@ -148,8 +148,8 @@ class ProductVariantService extends BaseService { ) } - product.options.forEach(option => { - if (!variant.options.find(vo => option.id === vo.option_id)) { + product.options.forEach((option) => { + if (!variant.options.find((vo) => option.id === vo.option_id)) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `Variant options do not contain value for ${option.title}` @@ -158,10 +158,10 @@ class ProductVariantService extends BaseService { }) let variantExists = undefined - variantExists = product.variants.find(v => { - return v.options.every(option => { + variantExists = product.variants.find((v) => { + return v.options.every((option) => { const variantOption = variant.options.find( - o => option.option_id === o.option_id + (o) => option.option_id === o.option_id ) return option.value === variantOption.value @@ -220,7 +220,7 @@ class ProductVariantService extends BaseService { * @return {Promise} */ async publish(variantId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const variantRepo = manager.getCustomRepository( this.productVariantRepository_ ) @@ -252,7 +252,7 @@ class ProductVariantService extends BaseService { * @return {Promise} resolves to the update result. */ async update(variantOrVariantId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const variantRepo = manager.getCustomRepository( this.productVariantRepository_ ) @@ -328,7 +328,7 @@ class ProductVariantService extends BaseService { * @return {Promise} the result of the update operation */ async setCurrencyPrice(variantId, price) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const moneyAmountRepo = manager.getCustomRepository( this.moneyAmountRepository_ ) @@ -367,7 +367,7 @@ class ProductVariantService extends BaseService { * @return {number} the price specific to the region */ async getRegionPrice(variantId, regionId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const moneyAmountRepo = manager.getCustomRepository( this.moneyAmountRepository_ ) @@ -415,7 +415,7 @@ class ProductVariantService extends BaseService { * @return {Promise} the result of the update operation */ async setRegionPrice(variantId, price) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const moneyAmountRepo = manager.getCustomRepository( this.moneyAmountRepository_ ) @@ -452,7 +452,7 @@ class ProductVariantService extends BaseService { * @return {Promise} the result of the update operation. */ async updateOptionValue(variantId, optionId, optionValue) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productOptionValueRepo = manager.getCustomRepository( this.productOptionValueRepository_ ) @@ -487,7 +487,7 @@ class ProductVariantService extends BaseService { * @return {Promise} the result of the update operation. */ async addOptionValue(variantId, optionId, optionValue) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productOptionValueRepo = manager.getCustomRepository( this.productOptionValueRepository_ ) @@ -511,7 +511,7 @@ class ProductVariantService extends BaseService { * @return {Promise} empty promise */ async deleteOptionValue(variantId, optionId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productOptionValueRepo = manager.getCustomRepository( this.productOptionValueRepository_ ) @@ -561,7 +561,7 @@ class ProductVariantService extends BaseService { }, } - query.where = qb => { + query.where = (qb) => { qb.where(where).andWhere([ { sku: ILike(`%${q}%`) }, { title: ILike(`%${q}%`) }, @@ -581,7 +581,7 @@ class ProductVariantService extends BaseService { * @return {Promise} empty promise */ async delete(variantId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const variantRepo = manager.getCustomRepository( this.productVariantRepository_ ) @@ -592,10 +592,12 @@ class ProductVariantService extends BaseService { await variantRepo.softRemove(variant) - await this.eventBus_.emit(ProductVariantService.Events.DELETED, { - id: variant.id, - product_id: variant.product_id, - }) + await this.eventBus_ + .withTransaction(manager) + .emit(ProductVariantService.Events.DELETED, { + id: variant.id, + product_id: variant.product_id, + }) return Promise.resolve() }) diff --git a/packages/medusa/src/services/product.js b/packages/medusa/src/services/product.js index 46173cbc4d..876cb33801 100644 --- a/packages/medusa/src/services/product.js +++ b/packages/medusa/src/services/product.js @@ -128,7 +128,7 @@ class ProductService extends BaseService { .select(["product.id"]) .where(where) .andWhere( - new Brackets(qb => { + new Brackets((qb) => { qb.where(`product.description ILIKE :q`, { q: `%${q}%` }) .orWhere(`product.title ILIKE :q`, { q: `%${q}%` }) .orWhere(`variant.title ILIKE :q`, { q: `%${q}%` }) @@ -140,7 +140,7 @@ class ProductService extends BaseService { return productRepo.findWithRelations( rels, - raw.map(i => i.id) + raw.map((i) => i.id) ) } @@ -282,7 +282,7 @@ class ProductService extends BaseService { * @return {Promise} resolves to the creation result. */ async create(productObject) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productRepo = manager.getCustomRepository(this.productRepository_) const optionRepo = manager.getCustomRepository( this.productOptionRepository_ @@ -316,7 +316,7 @@ class ProductService extends BaseService { product = await productRepo.save(product) product.options = await Promise.all( - options.map(async o => { + options.map(async (o) => { const res = optionRepo.create({ ...o, product_id: product.id }) await optionRepo.save(res) return res @@ -366,7 +366,7 @@ class ProductService extends BaseService { * @return {Promise} resolves to the update result. */ async update(productId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productRepo = manager.getCustomRepository(this.productRepository_) const productVariantRepo = manager.getCustomRepository( this.productVariantRepository_ @@ -376,15 +376,8 @@ class ProductService extends BaseService { relations: ["variants", "tags", "images"], }) - const { - variants, - metadata, - options, - images, - tags, - type, - ...rest - } = update + const { variants, metadata, options, images, tags, type, ...rest } = + update if (!product.thumbnail && !update.thumbnail && images?.length) { product.thumbnail = images[0] @@ -409,7 +402,7 @@ class ProductService extends BaseService { if (variants) { // Iterate product variants and update their properties accordingly for (const variant of product.variants) { - const exists = variants.find(v => v.id && variant.id === v.id) + const exists = variants.find((v) => v.id && variant.id === v.id) if (!exists) { await productVariantRepo.remove(variant) } @@ -420,7 +413,7 @@ class ProductService extends BaseService { newVariant.variant_rank = i if (newVariant.id) { - const variant = product.variants.find(v => v.id === newVariant.id) + const variant = product.variants.find((v) => v.id === newVariant.id) if (!variant) { throw new MedusaError( @@ -471,7 +464,7 @@ class ProductService extends BaseService { * @return {Promise} empty promise */ async delete(productId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productRepo = manager.getCustomRepository(this.productRepository_) // Should not fail, if product does not exist, since delete is idempotent @@ -500,7 +493,7 @@ class ProductService extends BaseService { * @return {Promise} the result of the model update operation */ async addOption(productId, optionTitle) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productOptionRepo = manager.getCustomRepository( this.productOptionRepository_ ) @@ -509,7 +502,7 @@ class ProductService extends BaseService { relations: ["options", "variants"], }) - if (product.options.find(o => o.title === optionTitle)) { + if (product.options.find((o) => o.title === optionTitle)) { throw new MedusaError( MedusaError.Types.DUPLICATE_ERROR, `An option with the title: ${optionTitle} already exists` @@ -539,7 +532,7 @@ class ProductService extends BaseService { } async reorderVariants(productId, variantOrder) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productRepo = manager.getCustomRepository(this.productRepository_) const product = await this.retrieve(productId, { @@ -553,8 +546,8 @@ class ProductService extends BaseService { ) } - product.variants = variantOrder.map(vId => { - const variant = product.variants.find(v => v.id === vId) + product.variants = variantOrder.map((vId) => { + const variant = product.variants.find((v) => v.id === vId) if (!variant) { throw new MedusaError( MedusaError.Types.INVALID_DATA, @@ -583,7 +576,7 @@ class ProductService extends BaseService { * @return {Promise} the result of the update operation */ async reorderOptions(productId, optionOrder) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productRepo = manager.getCustomRepository(this.productRepository_) const product = await this.retrieve(productId, { relations: ["options"] }) @@ -595,8 +588,8 @@ class ProductService extends BaseService { ) } - product.options = optionOrder.map(oId => { - const option = product.options.find(o => o.id === oId) + product.options = optionOrder.map((oId) => { + const option = product.options.find((o) => o.id === oId) if (!option) { throw new MedusaError( MedusaError.Types.INVALID_DATA, @@ -624,7 +617,7 @@ class ProductService extends BaseService { * @return {Promise} the updated product */ async updateOption(productId, optionId, data) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productOptionRepo = manager.getCustomRepository( this.productOptionRepository_ ) @@ -634,7 +627,8 @@ class ProductService extends BaseService { const { title, values } = data const optionExists = product.options.some( - o => o.title.toUpperCase() === title.toUpperCase() && o.id !== optionId + (o) => + o.title.toUpperCase() === title.toUpperCase() && o.id !== optionId ) if (optionExists) { throw new MedusaError( @@ -673,7 +667,7 @@ class ProductService extends BaseService { * @return {Promise} the updated product */ async deleteOption(productId, optionId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const productOptionRepo = manager.getCustomRepository( this.productOptionRepository_ ) @@ -701,17 +695,17 @@ class ProductService extends BaseService { const firstVariant = product.variants[0] const valueToMatch = firstVariant.options.find( - o => o.option_id === optionId + (o) => o.option_id === optionId ).value const equalsFirst = await Promise.all( - product.variants.map(async v => { - const option = v.options.find(o => o.option_id === optionId) + product.variants.map(async (v) => { + const option = v.options.find((o) => o.option_id === optionId) return option.value === valueToMatch }) ) - if (!equalsFirst.every(v => v)) { + if (!equalsFirst.every((v) => v)) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `To delete an option, first delete all variants, such that when option is deleted, no duplicate variants will exist.` diff --git a/packages/medusa/src/services/search.js b/packages/medusa/src/services/search.js index f0893e313b..1bca213329 100644 --- a/packages/medusa/src/services/search.js +++ b/packages/medusa/src/services/search.js @@ -2,7 +2,7 @@ import { SearchService } from "medusa-interfaces" /** * Default class that implements SearchService but provides stuv implementation for all methods - * @implements SearchService + * @extends SearchService */ class DefaultSearchService extends SearchService { constructor(container) { diff --git a/packages/medusa/src/subscribers/product.js b/packages/medusa/src/subscribers/product.js new file mode 100644 index 0000000000..875b650aaf --- /dev/null +++ b/packages/medusa/src/subscribers/product.js @@ -0,0 +1,113 @@ +import ProductVariantService from "../services/product-variant" +import ProductService from "../services/product" +import { indexTypes } from "medusa-core-utils" + +const searchFields = [ + "id", + "title", + "subtitle", + "description", + "handle", + "is_giftcard", + "discountable", + "thumbnail", + "profile_id", + "collection_id", + "type_id", + "origin_country", + "created_at", + "updated_at", +] + +const searchRelations = [ + "variants", + "tags", + "type", + "collection", + "variants.prices", + "variants.options", + "options", +] + +class ProductSearchSubscriber { + constructor({ eventBusService, searchService, productService }) { + this.eventBus_ = eventBusService + + this.searchService_ = searchService + + this.productService_ = productService + + this.eventBus_.subscribe( + ProductService.Events.CREATED, + this.handleProductCreation + ) + + this.eventBus_.subscribe( + ProductService.Events.UPDATED, + this.handleProductUpdate + ) + + this.eventBus_.subscribe( + ProductService.Events.DELETED, + this.handleProductDeletion + ) + + this.eventBus_.subscribe( + ProductVariantService.Events.CREATED, + this.handleProductVariantChange + ) + + this.eventBus_.subscribe( + ProductVariantService.Events.UPDATED, + this.handleProductVariantChange + ) + + this.eventBus_.subscribe( + ProductVariantService.Events.DELETED, + this.handleProductVariantChange + ) + } + + handleProductCreation = async (data) => { + const product = await this.retrieveProduct_(data.id) + await this.searchService.addDocuments( + ProductService.IndexName, + [product], + indexTypes.products + ) + } + + retrieveProduct_ = async (product_id) => { + return await this.productService_.retrieve(product_id, { + select: searchFields, + relations: searchRelations, + }) + } + + handleProductUpdate = async (data) => { + const product = await this.retrieveProduct_(data.id) + await this.meilisearchService_.addDocuments( + ProductService.IndexName, + [product], + indexTypes.products + ) + } + + handleProductDeletion = async (data) => { + await this.meilisearchService_.deleteDocument( + ProductService.IndexName, + data.id + ) + } + + handleProductVariantChange = async (data) => { + const product = await this.retrieveProduct_(data.product_id) + await this.meilisearchService_.addDocuments( + ProductService.IndexName, + [product], + indexTypes.products + ) + } +} + +export default ProductSearchSubscriber