From 57a6612e845c078aec023d0cc49d6bfc175a1b37 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Mon, 18 Oct 2021 14:39:42 +0200 Subject: [PATCH] fix: product ordering --- .../medusa-interfaces/src/base-service.js | 16 ++--- packages/medusa/src/loaders/search-index.js | 10 ++-- packages/medusa/src/repositories/order.ts | 2 +- packages/medusa/src/repositories/product.ts | 43 +++++++++----- packages/medusa/src/services/product.js | 58 +++++++++---------- 5 files changed, 67 insertions(+), 62 deletions(-) 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/src/loaders/search-index.js b/packages/medusa/src/loaders/search-index.js index 8c01a2fdbc..ec2ef503c3 100644 --- a/packages/medusa/src/loaders/search-index.js +++ b/packages/medusa/src/loaders/search-index.js @@ -6,12 +6,13 @@ async function loadProductsIntoSearchEngine(container) { const productService = container.resolve("productService") const TAKE = 20 - let skip = 0 let hasMore = true + let lastSeenId = "" + while (hasMore) { const products = await productService.list( - {}, + { id: { gt: lastSeenId } }, { select: [ "id", @@ -39,8 +40,7 @@ async function loadProductsIntoSearchEngine(container) { "options", ], take: TAKE, - skip, - order: { created_at: "ASC" }, + order: { id: "ASC" }, } ) @@ -50,7 +50,7 @@ async function loadProductsIntoSearchEngine(container) { products, indexTypes.products ) - skip += products.length + 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.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.`