diff --git a/packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts b/packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts index acc9093b73..448da2782c 100644 --- a/packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts +++ b/packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts @@ -14,11 +14,14 @@ export function findByIdsInChannel( ids: ID[], channelId: ID, findOptions?: FindManyOptions, + eager = true, ) { const qb = connection.getRepository(entity).createQueryBuilder('product'); FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, findOptions); - // tslint:disable-next-line:no-non-null-assertion - FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata); + if (eager) { + // tslint:disable-next-line:no-non-null-assertion + FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata); + } return qb .leftJoin('product.channels', 'channel') .andWhere('channel.id = :channelId', { channelId }) @@ -35,11 +38,14 @@ export function findOneInChannel( id: ID, channelId: ID, findOptions?: FindManyOptions, + eager = true, ) { const qb = connection.getRepository(entity).createQueryBuilder('product'); FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, findOptions); - // tslint:disable-next-line:no-non-null-assertion - FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata); + if (eager) { + // tslint:disable-next-line:no-non-null-assertion + FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata); + } return qb .leftJoin('product.channels', 'channel') .andWhere('product.id = :id', { id }) diff --git a/packages/core/src/service/helpers/utils/get-entity-or-throw.ts b/packages/core/src/service/helpers/utils/get-entity-or-throw.ts index 914368e0e5..186955d342 100644 --- a/packages/core/src/service/helpers/utils/get-entity-or-throw.ts +++ b/packages/core/src/service/helpers/utils/get-entity-or-throw.ts @@ -24,6 +24,7 @@ export async function getEntityOrThrow( id: ID, channelId: ID, findOptions?: FindOneOptions, + eager?: boolean, ): Promise; export async function getEntityOrThrow( connection: Connection, @@ -31,10 +32,18 @@ export async function getEntityOrThrow( id: ID, findOptionsOrChannelId?: FindOneOptions | ID, maybeFindOptions?: FindOneOptions, + eager?: boolean, ): Promise { let entity: T | undefined; if (isId(findOptionsOrChannelId)) { - entity = await findOneInChannel(connection, entityType, id, findOptionsOrChannelId, maybeFindOptions); + entity = await findOneInChannel( + connection, + entityType, + id, + findOptionsOrChannelId, + maybeFindOptions, + eager, + ); } else { entity = await connection.getRepository(entityType).findOne(id, findOptionsOrChannelId); } diff --git a/packages/core/src/service/services/product-variant.service.ts b/packages/core/src/service/services/product-variant.service.ts index 42f34f3b10..282246b471 100644 --- a/packages/core/src/service/services/product-variant.service.ts +++ b/packages/core/src/service/services/product-variant.service.ts @@ -362,25 +362,50 @@ export class ProductVariantService { } private async validateVariantOptionIds(ctx: RequestContext, input: CreateProductVariantInput) { - const product = await getEntityOrThrow(this.connection, Product, input.productId, ctx.channelId, { - relations: ['optionGroups', 'optionGroups.options', 'variants', 'variants.options'], - }); - const optionIds = [...(input.optionIds || [])]; + // this could be done with less queries but depending on the data, node will crash + // https://github.com/vendure-ecommerce/vendure/issues/328 + const optionGroups = ( + await getEntityOrThrow( + this.connection, + Product, + input.productId, + ctx.channelId, + { + relations: ['optionGroups', 'optionGroups.options'], + }, + false, + ) + ).optionGroups; - if (optionIds.length !== product.optionGroups.length) { - this.throwIncompatibleOptionsError(product.optionGroups); + const optionIds = input.optionIds || []; + + if (optionIds.length !== optionGroups.length) { + this.throwIncompatibleOptionsError(optionGroups); } if ( !samplesEach( optionIds, - product.optionGroups.map(g => g.options.map(o => o.id)), + optionGroups.map((g) => g.options.map((o) => o.id)), ) ) { - this.throwIncompatibleOptionsError(product.optionGroups); + this.throwIncompatibleOptionsError(optionGroups); } + + const product = await getEntityOrThrow( + this.connection, + Product, + input.productId, + ctx.channelId, + { + relations: ['variants', 'variants.options'], + }, + false, + ); + + const inputOptionIds = this.sortJoin(optionIds, ','); + product.variants.forEach(variant => { const variantOptionIds = this.sortJoin(variant.options, ',', 'id'); - const inputOptionIds = this.sortJoin(input.optionIds || [], ','); if (variantOptionIds === inputOptionIds) { throw new UserInputError('error.product-variant-options-combination-already-exists', { optionNames: this.sortJoin(variant.options, ', ', 'code'),