Merge pull request #577 from medusajs/fix/meili

Fix/meili
This commit is contained in:
Sebastian Rindom
2021-10-18 15:59:17 +02:00
committed by GitHub
17 changed files with 228 additions and 276 deletions
+2
View File
@@ -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
@@ -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))
}
}
@@ -0,0 +1,8 @@
/src/subscribers
/api
/services
/models
/subscribers
/loaders
/utils
@@ -1,9 +0,0 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
@@ -1,7 +0,0 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
@@ -8,6 +8,7 @@ export default async (container, options) => {
)
)
} catch (err) {
// ignore
console.log(err)
}
}
@@ -59,7 +59,9 @@ class MeiliSearchService extends SearchService {
}
transformProducts(products) {
if (!products) return []
if (!products) {
return []
}
return products.map(transformProduct)
}
}
@@ -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
@@ -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])
@@ -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"
+14 -12
View File
@@ -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
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ export class OrderRepository extends Repository<Order> {
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entities, e => merge({}, ...entitiesAndRelationsById[e.id]))
return map(entities, (e) => merge({}, ...entitiesAndRelationsById[e.id]))
}
public async findOneWithRelations(
+27 -16
View File
@@ -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<FindManyOptions<Product>, "relations">
type CustomOptions = {
where?: DefaultWithoutRelations["where"] & { tags?: string[] }
order?: OrderByCondition
skip?: number
take?: number
}
type FindWithRelationsOptions = CustomOptions
@EntityRepository(Product)
export class ProductRepository extends Repository<Product> {
public async findWithRelations(
relations: Array<keyof Product> = [],
idsOrOptionsWithoutRelations: Omit<
FindManyOptions<Product>,
"relations"
> = {}
idsOrOptionsWithoutRelations: FindWithRelationsOptions = {}
): Promise<Product[]> {
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<Product> {
.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)
+22 -20
View File
@@ -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()
})
+26 -32
View File
@@ -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.`
+1 -1
View File
@@ -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) {
+113
View File
@@ -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