@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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.`
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user