Quellcode durchsuchen

refactor(core): Make getEntityOrThrow util fn channel aware

Michael Bromley vor 6 Jahren
Ursprung
Commit
e6b24f45c7

+ 12 - 12
packages/core/e2e/__snapshots__/product.e2e-spec.ts.snap

@@ -19,18 +19,18 @@ Object {
   "optionGroups": Array [],
   "slug": "en-baked-potato",
   "translations": Array [
-    Object {
-      "description": "A baked potato",
-      "languageCode": "en",
-      "name": "en Baked Potato",
-      "slug": "en-baked-potato",
-    },
     Object {
       "description": "Eine baked Erdapfel",
       "languageCode": "de",
       "name": "de Baked Potato",
       "slug": "de-baked-potato",
     },
+    Object {
+      "description": "A baked potato",
+      "languageCode": "en",
+      "name": "en Baked Potato",
+      "slug": "en-baked-potato",
+    },
   ],
   "variants": Array [],
 }
@@ -55,18 +55,18 @@ Object {
   "optionGroups": Array [],
   "slug": "en-mashed-potato",
   "translations": Array [
-    Object {
-      "description": "A blob of mashed potato",
-      "languageCode": "en",
-      "name": "en Mashed Potato",
-      "slug": "en-mashed-potato",
-    },
     Object {
       "description": "Eine blob von gemashed Erdapfel",
       "languageCode": "de",
       "name": "de Mashed Potato",
       "slug": "de-mashed-potato",
     },
+    Object {
+      "description": "A blob of mashed potato",
+      "languageCode": "en",
+      "name": "en Mashed Potato",
+      "slug": "en-mashed-potato",
+    },
   ],
   "variants": Array [],
 }

+ 3 - 3
packages/core/e2e/product.e2e-spec.ts

@@ -488,9 +488,9 @@ describe('Product resolver', () => {
                 },
             );
             expect(result.updateProduct.translations.length).toBe(2);
-            expect(result.updateProduct.translations[0].name).toBe('en Very Mashed Potato');
-            expect(result.updateProduct.translations[0].description).toBe('Possibly the final baked potato');
-            expect(result.updateProduct.translations[1].name).toBe('de Mashed Potato');
+            expect(result.updateProduct.translations[0].name).toBe('de Mashed Potato');
+            expect(result.updateProduct.translations[1].name).toBe('en Very Mashed Potato');
+            expect(result.updateProduct.translations[1].description).toBe('Possibly the final baked potato');
         });
 
         it('updateProduct adds Assets to a product and sets featured asset', async () => {

+ 5 - 2
packages/core/src/api/resolvers/admin/shipping-method.resolver.ts

@@ -67,9 +67,12 @@ export class ShippingMethodResolver {
 
     @Mutation()
     @Allow(Permission.DeleteSettings)
-    deleteShippingMethod(@Args() args: MutationDeleteShippingMethodArgs): Promise<DeletionResponse> {
+    deleteShippingMethod(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeleteShippingMethodArgs,
+    ): Promise<DeletionResponse> {
         const { id } = args;
-        return this.shippingMethodService.softDelete(id);
+        return this.shippingMethodService.softDelete(ctx, id);
     }
 
     @Query()

+ 1 - 1
packages/core/src/api/resolvers/entity/product-entity.resolver.ts

@@ -77,7 +77,7 @@ export class ProductAdminEntityResolver {
         if (product.channels) {
             return product.channels;
         } else {
-            return this.productService.getProductChannels(product.id);
+            return this.productService.getProductChannels(ctx, product.id);
         }
     }
 }

+ 22 - 0
packages/core/src/service/helpers/utils/find-by-ids-in-channel.ts → packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts

@@ -24,3 +24,25 @@ export function findByIdsInChannel<T extends ChannelAware | VendureEntity>(
         .andWhere('channel.id = :channelId', { channelId })
         .getMany();
 }
+
+/**
+ * Like the TypeOrm `Repository.findOne()` method, but limits the results to
+ * the given Channel.
+ */
+export function findOneInChannel<T extends ChannelAware | VendureEntity>(
+    connection: Connection,
+    entity: Type<T>,
+    id: ID,
+    channelId: ID,
+    findOptions?: FindManyOptions<T>,
+) {
+    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);
+    return qb
+        .leftJoin('product.channels', 'channel')
+        .andWhere('product.id = :id', { id })
+        .andWhere('channel.id = :channelId', { channelId })
+        .getOne();
+}

+ 29 - 2
packages/core/src/service/helpers/utils/get-entity-or-throw.ts

@@ -2,21 +2,48 @@ import { ID, Type } from '@vendure/common/lib/shared-types';
 import { Connection, FindOneOptions } from 'typeorm';
 
 import { EntityNotFoundError } from '../../../common/error/errors';
-import { SoftDeletable } from '../../../common/types/common-types';
+import { ChannelAware, SoftDeletable } from '../../../common/types/common-types';
 import { VendureEntity } from '../../../entity/base/base.entity';
 
+import { findOneInChannel } from './channel-aware-orm-utils';
+
 /**
  * Attempts to find an entity of the given type and id, and throws an error if not found.
+ * If the entity is a ChannelAware type, then the `channelId` must be supplied or else
+ * the function will "fail" by resolving to the `never` type.
  */
 export async function getEntityOrThrow<T extends VendureEntity>(
     connection: Connection,
     entityType: Type<T>,
     id: ID,
     findOptions?: FindOneOptions<T>,
+): Promise<T extends ChannelAware ? never : T>;
+export async function getEntityOrThrow<T extends VendureEntity | ChannelAware>(
+    connection: Connection,
+    entityType: Type<T>,
+    id: ID,
+    channelId: ID,
+    findOptions?: FindOneOptions<T>,
+): Promise<T>;
+export async function getEntityOrThrow<T extends VendureEntity>(
+    connection: Connection,
+    entityType: Type<T>,
+    id: ID,
+    findOptionsOrChannelId?: FindOneOptions<T> | ID,
+    maybeFindOptions?: FindOneOptions<T>,
 ): Promise<T> {
-    const entity = await connection.getRepository(entityType).findOne(id, findOptions);
+    let entity: T | undefined;
+    if (isId(findOptionsOrChannelId)) {
+        entity = await findOneInChannel(connection, entityType, id, findOptionsOrChannelId, maybeFindOptions);
+    } else {
+        entity = await connection.getRepository(entityType).findOne(id, findOptionsOrChannelId);
+    }
     if (!entity || (entity.hasOwnProperty('deletedAt') && (entity as T & SoftDeletable).deletedAt !== null)) {
         throw new EntityNotFoundError(entityType.name as any, id);
     }
     return entity;
 }
+
+function isId(value: unknown): value is ID {
+    return typeof value === 'string' || typeof value === 'number';
+}

+ 1 - 1
packages/core/src/service/index.ts

@@ -1,7 +1,7 @@
 export * from './helpers/job-manager/job';
 export * from './helpers/utils/translate-entity';
 export * from './helpers/utils/patch-entity';
-export * from './helpers/utils/find-by-ids-in-channel';
+export * from './helpers/utils/channel-aware-orm-utils';
 export * from './helpers/utils/get-entity-or-throw';
 export * from './helpers/list-query-builder/list-query-builder';
 export * from './helpers/order-state-machine/order-state';

+ 10 - 4
packages/core/src/service/services/collection.service.ts

@@ -278,7 +278,7 @@ export class CollectionService implements OnModuleInit {
     }
 
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
-        const collection = await getEntityOrThrow(this.connection, Collection, id);
+        const collection = await getEntityOrThrow(this.connection, Collection, id, ctx.channelId);
         const descendants = await this.getDescendants(ctx, collection.id);
         for (const coll of [...descendants.reverse(), collection]) {
             const affectedVariantIds = await this.getCollectionProductVariantIds(coll);
@@ -291,9 +291,15 @@ export class CollectionService implements OnModuleInit {
     }
 
     async move(ctx: RequestContext, input: MoveCollectionInput): Promise<Translated<Collection>> {
-        const target = await getEntityOrThrow(this.connection, Collection, input.collectionId, {
-            relations: ['parent'],
-        });
+        const target = await getEntityOrThrow(
+            this.connection,
+            Collection,
+            input.collectionId,
+            ctx.channelId,
+            {
+                relations: ['parent'],
+            },
+        );
         const descendants = await this.getDescendants(ctx, input.collectionId);
 
         if (

+ 3 - 3
packages/core/src/service/services/product-variant.service.ts

@@ -147,7 +147,7 @@ export class ProductVariantService {
     }
 
     async create(ctx: RequestContext, input: CreateProductVariantInput): Promise<Translated<ProductVariant>> {
-        await this.validateVariantOptionIds(input);
+        await this.validateVariantOptionIds(ctx, input);
         if (!input.optionIds) {
             input.optionIds = [];
         }
@@ -379,8 +379,8 @@ export class ProductVariantService {
         return variant;
     }
 
-    private async validateVariantOptionIds(input: CreateProductVariantInput) {
-        const product = await getEntityOrThrow(this.connection, Product, input.productId, {
+    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 || [])];

+ 5 - 5
packages/core/src/service/services/product.service.ts

@@ -26,7 +26,7 @@ import { EventBus } from '../../event-bus/event-bus';
 import { CatalogModificationEvent } from '../../event-bus/events/catalog-modification-event';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
-import { findByIdsInChannel } from '../helpers/utils/find-by-ids-in-channel';
+import { findByIdsInChannel, findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { translateDeep } from '../helpers/utils/translate-entity';
 
@@ -79,7 +79,7 @@ export class ProductService {
     }
 
     async findOne(ctx: RequestContext, productId: ID): Promise<Translated<Product> | undefined> {
-        const product = await this.connection.manager.findOne(Product, productId, {
+        const product = await findOneInChannel(this.connection, Product, productId, ctx.channelId, {
             relations: this.relations,
             where: {
                 deletedAt: null,
@@ -101,8 +101,8 @@ export class ProductService {
         );
     }
 
-    async getProductChannels(productId: ID): Promise<Channel[]> {
-        const product = await getEntityOrThrow(this.connection, Product, productId, {
+    async getProductChannels(ctx: RequestContext, productId: ID): Promise<Channel[]> {
+        const product = await getEntityOrThrow(this.connection, Product, productId, ctx.channelId, {
             relations: ['channels'],
         });
         return product.channels;
@@ -160,7 +160,7 @@ export class ProductService {
     }
 
     async softDelete(ctx: RequestContext, productId: ID): Promise<DeletionResponse> {
-        const product = await getEntityOrThrow(this.connection, Product, productId);
+        const product = await getEntityOrThrow(this.connection, Product, productId, ctx.channelId);
         product.deletedAt = new Date();
         await this.connection.getRepository(Product).save(product);
         this.eventBus.publish(new CatalogModificationEvent(ctx, product));

+ 1 - 1
packages/core/src/service/services/promotion.service.ts

@@ -111,7 +111,7 @@ export class PromotionService {
     }
 
     async updatePromotion(ctx: RequestContext, input: UpdatePromotionInput): Promise<Promotion> {
-        const promotion = await getEntityOrThrow(this.connection, Promotion, input.id);
+        const promotion = await getEntityOrThrow(this.connection, Promotion, input.id, ctx.channelId);
         const updatedPromotion = patchEntity(promotion, omit(input, ['conditions', 'actions']));
         if (input.conditions) {
             updatedPromotion.conditions = input.conditions.map(c => this.parseOperationArgs('condition', c));

+ 2 - 2
packages/core/src/service/services/shipping-method.service.ts

@@ -94,8 +94,8 @@ export class ShippingMethodService {
         return assertFound(this.findOne(shippingMethod.id));
     }
 
-    async softDelete(id: ID): Promise<DeletionResponse> {
-        const shippingMethod = await getEntityOrThrow(this.connection, ShippingMethod, id, {
+    async softDelete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
+        const shippingMethod = await getEntityOrThrow(this.connection, ShippingMethod, id, ctx.channelId, {
             where: { deletedAt: null },
         });
         shippingMethod.deletedAt = new Date();