Browse Source

refactor(core): Pass RequestContext into TransactionalConnection

Michael Bromley 5 years ago
parent
commit
b5f41f04f7
68 changed files with 968 additions and 659 deletions
  1. 2 1
      packages/core/e2e/authentication-strategy.e2e-spec.ts
  2. 1 2
      packages/core/e2e/fixtures/test-authentication-strategies.ts
  3. 15 0
      packages/core/src/api/common/request-context.ts
  4. 32 12
      packages/core/src/api/resolvers/admin/administrator.resolver.ts
  5. 4 4
      packages/core/src/api/resolvers/admin/asset.resolver.ts
  6. 19 10
      packages/core/src/api/resolvers/admin/channel.resolver.ts
  7. 12 6
      packages/core/src/api/resolvers/admin/customer-group.resolver.ts
  8. 15 6
      packages/core/src/api/resolvers/admin/customer.resolver.ts
  9. 19 11
      packages/core/src/api/resolvers/admin/facet.resolver.ts
  10. 7 5
      packages/core/src/api/resolvers/admin/global-settings.resolver.ts
  11. 19 8
      packages/core/src/api/resolvers/admin/payment-method.resolver.ts
  12. 5 2
      packages/core/src/api/resolvers/admin/promotion.resolver.ts
  13. 4 4
      packages/core/src/api/resolvers/admin/role.resolver.ts
  14. 12 6
      packages/core/src/api/resolvers/admin/tax-category.resolver.ts
  15. 3 3
      packages/core/src/api/resolvers/admin/tax-rate.resolver.ts
  16. 4 4
      packages/core/src/api/resolvers/base/base-auth.resolver.ts
  17. 9 4
      packages/core/src/api/resolvers/entity/customer-entity.resolver.ts
  18. 1 1
      packages/core/src/api/resolvers/entity/customer-group-entity.resolver.ts
  19. 1 1
      packages/core/src/api/resolvers/entity/facet-entity.resolver.ts
  20. 2 1
      packages/core/src/api/resolvers/entity/facet-value-entity.resolver.ts
  21. 12 6
      packages/core/src/api/resolvers/entity/order-entity.resolver.ts
  22. 1 1
      packages/core/src/api/resolvers/entity/payment-entity.resolver.ts
  23. 1 1
      packages/core/src/api/resolvers/entity/role-entity.resolver.ts
  24. 1 1
      packages/core/src/api/resolvers/entity/user-entity.resolver.ts
  25. 3 3
      packages/core/src/api/resolvers/shop/shop-auth.resolver.ts
  26. 2 2
      packages/core/src/api/resolvers/shop/shop-customer.resolver.ts
  27. 4 4
      packages/core/src/api/resolvers/shop/shop-order.resolver.ts
  28. 1 1
      packages/core/src/config/promotion/conditions/customer-group-condition.ts
  29. 6 0
      packages/core/src/data-import/providers/importer/fast-importer.service.ts
  30. 10 6
      packages/core/src/data-import/providers/importer/importer.ts
  31. 2 2
      packages/core/src/data-import/providers/populator/populator.ts
  32. 1 4
      packages/core/src/plugin/default-search-plugin/fulltext-search.service.ts
  33. 2 1
      packages/core/src/service/controllers/tax-rate.controller.ts
  34. 23 14
      packages/core/src/service/helpers/external-authentication/external-authentication.service.ts
  35. 10 1
      packages/core/src/service/helpers/list-query-builder/list-query-builder.ts
  36. 2 2
      packages/core/src/service/helpers/order-state-machine/order-state-machine.ts
  37. 4 1
      packages/core/src/service/helpers/slug-validator/slug-validator.ts
  38. 12 6
      packages/core/src/service/helpers/translatable-saver/translatable-saver.ts
  39. 9 3
      packages/core/src/service/helpers/translatable-saver/translation-differ.ts
  40. 35 25
      packages/core/src/service/services/administrator.service.ts
  41. 56 32
      packages/core/src/service/services/asset.service.ts
  42. 5 5
      packages/core/src/service/services/auth.service.ts
  43. 20 18
      packages/core/src/service/services/channel.service.ts
  44. 25 21
      packages/core/src/service/services/collection.service.ts
  45. 8 6
      packages/core/src/service/services/country.service.ts
  46. 29 23
      packages/core/src/service/services/customer-group.service.ts
  47. 79 60
      packages/core/src/service/services/customer.service.ts
  48. 27 27
      packages/core/src/service/services/facet-value.service.ts
  49. 22 16
      packages/core/src/service/services/facet.service.ts
  50. 7 6
      packages/core/src/service/services/global-settings.service.ts
  51. 13 9
      packages/core/src/service/services/history.service.ts
  52. 52 44
      packages/core/src/service/services/order.service.ts
  53. 24 19
      packages/core/src/service/services/payment-method.service.ts
  54. 5 3
      packages/core/src/service/services/product-option-group.service.ts
  55. 4 2
      packages/core/src/service/services/product-option.service.ts
  56. 36 22
      packages/core/src/service/services/product-variant.service.ts
  57. 28 22
      packages/core/src/service/services/product.service.ts
  58. 20 12
      packages/core/src/service/services/promotion.service.ts
  59. 19 16
      packages/core/src/service/services/role.service.ts
  60. 16 14
      packages/core/src/service/services/session.service.ts
  61. 15 9
      packages/core/src/service/services/shipping-method.service.ts
  62. 15 9
      packages/core/src/service/services/stock-movement.service.ts
  63. 14 13
      packages/core/src/service/services/tax-category.service.ts
  64. 16 16
      packages/core/src/service/services/tax-rate.service.ts
  65. 67 42
      packages/core/src/service/services/user.service.ts
  66. 13 13
      packages/core/src/service/services/zone.service.ts
  67. 4 3
      packages/core/src/service/transaction/transactional-connection.ts
  68. 2 2
      packages/dev-server/test-plugins/keycloak-auth/keycloak-authentication-strategy.ts

+ 2 - 1
packages/core/e2e/authentication-strategy.e2e-spec.ts

@@ -5,8 +5,9 @@ import gql from 'graphql-tag';
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
 import { NativeAuthenticationStrategy } from '../src/config/auth/native-authentication-strategy';
+import { DefaultLogger } from '../src/config/logger/default-logger';
 
 import { TestAuthenticationStrategy, VALID_AUTH_TOKEN } from './fixtures/test-authentication-strategies';
 import {

+ 1 - 2
packages/core/e2e/fixtures/test-authentication-strategies.ts

@@ -47,8 +47,7 @@ export class TestAuthenticationStrategy implements AuthenticationStrategy<TestAu
         if (data.token !== VALID_AUTH_TOKEN) {
             return false;
         }
-        const user = await this.externalAuthenticationService.findUser(this.name, data.token);
-
+        const user = await this.externalAuthenticationService.findUser(ctx, this.name, data.token);
         if (user) {
             return user;
         }

+ 15 - 0
packages/core/src/api/common/request-context.ts

@@ -54,6 +54,21 @@ export class RequestContext {
         this._translationFn = translationFn || (((key: string) => key) as any);
     }
 
+    /**
+     * @description
+     * Creates an "empty" RequestContext object. This is only intended to be used
+     * when a service method must be called outside the normal request-response
+     * cycle, e.g. when programmatically populating data.
+     */
+    static empty(): RequestContext {
+        return new RequestContext({
+            apiType: 'admin',
+            authorizedAsOwnerOnly: false,
+            channel: new Channel(),
+            isAuthorized: true,
+        });
+    }
+
     /**
      * @description
      * Creates a new RequestContext object from a plain object which is the result of

+ 32 - 12
packages/core/src/api/resolvers/admin/administrator.resolver.ts

@@ -13,7 +13,9 @@ import { PaginatedList } from '@vendure/common/lib/shared-types';
 
 import { Administrator } from '../../../entity/administrator/administrator.entity';
 import { AdministratorService } from '../../../service/services/administrator.service';
+import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
+import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Administrator')
 export class AdministratorResolver {
@@ -21,40 +23,58 @@ export class AdministratorResolver {
 
     @Query()
     @Allow(Permission.ReadAdministrator)
-    administrators(@Args() args: QueryAdministratorsArgs): Promise<PaginatedList<Administrator>> {
-        return this.administratorService.findAll(args.options || undefined);
+    administrators(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryAdministratorsArgs,
+    ): Promise<PaginatedList<Administrator>> {
+        return this.administratorService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
     @Allow(Permission.ReadAdministrator)
-    administrator(@Args() args: QueryAdministratorArgs): Promise<Administrator | undefined> {
-        return this.administratorService.findOne(args.id);
+    administrator(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryAdministratorArgs,
+    ): Promise<Administrator | undefined> {
+        return this.administratorService.findOne(ctx, args.id);
     }
 
     @Mutation()
     @Allow(Permission.CreateAdministrator)
-    createAdministrator(@Args() args: MutationCreateAdministratorArgs): Promise<Administrator> {
+    createAdministrator(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationCreateAdministratorArgs,
+    ): Promise<Administrator> {
         const { input } = args;
-        return this.administratorService.create(input);
+        return this.administratorService.create(ctx, input);
     }
 
     @Mutation()
     @Allow(Permission.CreateAdministrator)
-    updateAdministrator(@Args() args: MutationUpdateAdministratorArgs): Promise<Administrator> {
+    updateAdministrator(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdateAdministratorArgs,
+    ): Promise<Administrator> {
         const { input } = args;
-        return this.administratorService.update(input);
+        return this.administratorService.update(ctx, input);
     }
 
     @Mutation()
     @Allow(Permission.UpdateAdministrator)
-    assignRoleToAdministrator(@Args() args: MutationAssignRoleToAdministratorArgs): Promise<Administrator> {
-        return this.administratorService.assignRole(args.administratorId, args.roleId);
+    assignRoleToAdministrator(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationAssignRoleToAdministratorArgs,
+    ): Promise<Administrator> {
+        return this.administratorService.assignRole(ctx, args.administratorId, args.roleId);
     }
 
     @Mutation()
     @Allow(Permission.DeleteAdministrator)
-    deleteAdministrator(@Args() args: MutationDeleteAdministratorArgs): Promise<DeletionResponse> {
+    deleteAdministrator(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeleteAdministratorArgs,
+    ): Promise<DeletionResponse> {
         const { id } = args;
-        return this.administratorService.softDelete(id);
+        return this.administratorService.softDelete(ctx, id);
     }
 }

+ 4 - 4
packages/core/src/api/resolvers/admin/asset.resolver.ts

@@ -22,14 +22,14 @@ export class AssetResolver {
 
     @Query()
     @Allow(Permission.ReadCatalog)
-    async asset(@Args() args: QueryAssetArgs): Promise<Asset | undefined> {
-        return this.assetService.findOne(args.id);
+    async asset(@Ctx() ctx: RequestContext, @Args() args: QueryAssetArgs): Promise<Asset | undefined> {
+        return this.assetService.findOne(ctx, args.id);
     }
 
     @Query()
     @Allow(Permission.ReadCatalog)
-    async assets(@Args() args: QueryAssetsArgs): Promise<PaginatedList<Asset>> {
-        return this.assetService.findAll(args.options || undefined);
+    async assets(@Ctx() ctx: RequestContext, @Args() args: QueryAssetsArgs): Promise<PaginatedList<Asset>> {
+        return this.assetService.findAll(ctx, args.options || undefined);
     }
 
     @Mutation()

+ 19 - 10
packages/core/src/api/resolvers/admin/channel.resolver.ts

@@ -22,13 +22,13 @@ export class ChannelResolver {
     @Query()
     @Allow(Permission.ReadSettings)
     channels(@Ctx() ctx: RequestContext): Promise<Channel[]> {
-        return this.channelService.findAll();
+        return this.channelService.findAll(ctx);
     }
 
     @Query()
     @Allow(Permission.ReadSettings)
     async channel(@Ctx() ctx: RequestContext, @Args() args: QueryChannelArgs): Promise<Channel | undefined> {
-        return this.channelService.findOne(args.id);
+        return this.channelService.findOne(ctx, args.id);
     }
 
     @Query()
@@ -39,24 +39,33 @@ export class ChannelResolver {
 
     @Mutation()
     @Allow(Permission.SuperAdmin)
-    async createChannel(@Args() args: MutationCreateChannelArgs): Promise<Channel> {
-        const channel = await this.channelService.create(args.input);
+    async createChannel(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationCreateChannelArgs,
+    ): Promise<Channel> {
+        const channel = await this.channelService.create(ctx, args.input);
         const superAdminRole = await this.roleService.getSuperAdminRole();
         const customerRole = await this.roleService.getCustomerRole();
-        await this.roleService.assignRoleToChannel(superAdminRole.id, channel.id);
-        await this.roleService.assignRoleToChannel(customerRole.id, channel.id);
+        await this.roleService.assignRoleToChannel(ctx, superAdminRole.id, channel.id);
+        await this.roleService.assignRoleToChannel(ctx, customerRole.id, channel.id);
         return channel;
     }
 
     @Mutation()
     @Allow(Permission.SuperAdmin)
-    async updateChannel(@Args() args: MutationUpdateChannelArgs): Promise<Channel> {
-        return this.channelService.update(args.input);
+    async updateChannel(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdateChannelArgs,
+    ): Promise<Channel> {
+        return this.channelService.update(ctx, args.input);
     }
 
     @Mutation()
     @Allow(Permission.SuperAdmin)
-    async deleteChannel(@Args() args: MutationDeleteChannelArgs): Promise<DeletionResponse> {
-        return this.channelService.delete(args.id);
+    async deleteChannel(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeleteChannelArgs,
+    ): Promise<DeletionResponse> {
+        return this.channelService.delete(ctx, args.id);
     }
 }

+ 12 - 6
packages/core/src/api/resolvers/admin/customer-group.resolver.ts

@@ -28,7 +28,7 @@ export class CustomerGroupResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: QueryCustomerGroupsArgs,
     ): Promise<PaginatedList<CustomerGroup>> {
-        return this.customerGroupService.findAll(args.options || undefined);
+        return this.customerGroupService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
@@ -37,7 +37,7 @@ export class CustomerGroupResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: QueryCustomerGroupArgs,
     ): Promise<CustomerGroup | undefined> {
-        return this.customerGroupService.findOne(args.id);
+        return this.customerGroupService.findOne(ctx, args.id);
     }
 
     @Mutation()
@@ -51,14 +51,20 @@ export class CustomerGroupResolver {
 
     @Mutation()
     @Allow(Permission.UpdateCustomer)
-    async updateCustomerGroup(@Args() args: MutationUpdateCustomerGroupArgs): Promise<CustomerGroup> {
-        return this.customerGroupService.update(args.input);
+    async updateCustomerGroup(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdateCustomerGroupArgs,
+    ): Promise<CustomerGroup> {
+        return this.customerGroupService.update(ctx, args.input);
     }
 
     @Mutation()
     @Allow(Permission.DeleteCustomer)
-    async deleteCustomerGroup(@Args() args: MutationDeleteCustomerGroupArgs): Promise<DeletionResponse> {
-        return this.customerGroupService.delete(args.id);
+    async deleteCustomerGroup(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeleteCustomerGroupArgs,
+    ): Promise<DeletionResponse> {
+        return this.customerGroupService.delete(ctx, args.id);
     }
 
     @Mutation()

+ 15 - 6
packages/core/src/api/resolvers/admin/customer.resolver.ts

@@ -31,14 +31,20 @@ export class CustomerResolver {
 
     @Query()
     @Allow(Permission.ReadCustomer)
-    async customers(@Args() args: QueryCustomersArgs): Promise<PaginatedList<Customer>> {
-        return this.customerService.findAll(args.options || undefined);
+    async customers(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryCustomersArgs,
+    ): Promise<PaginatedList<Customer>> {
+        return this.customerService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
     @Allow(Permission.ReadCustomer)
-    async customer(@Args() args: QueryCustomerArgs): Promise<Customer | undefined> {
-        return this.customerService.findOne(args.id);
+    async customer(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryCustomerArgs,
+    ): Promise<Customer | undefined> {
+        return this.customerService.findOne(ctx, args.id);
     }
 
     @Mutation()
@@ -93,8 +99,11 @@ export class CustomerResolver {
 
     @Mutation()
     @Allow(Permission.DeleteCustomer)
-    async deleteCustomer(@Args() args: MutationDeleteCustomerArgs): Promise<DeletionResponse> {
-        return this.customerService.softDelete(args.id);
+    async deleteCustomer(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeleteCustomerArgs,
+    ): Promise<DeletionResponse> {
+        return this.customerService.softDelete(ctx, args.id);
     }
 
     @Mutation()

+ 19 - 11
packages/core/src/api/resolvers/admin/facet.resolver.ts

@@ -38,7 +38,7 @@ export class FacetResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: QueryFacetsArgs,
     ): Promise<PaginatedList<Translated<Facet>>> {
-        return this.facetService.findAll(ctx.languageCode, args.options || undefined);
+        return this.facetService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
@@ -47,18 +47,21 @@ export class FacetResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: QueryFacetArgs,
     ): Promise<Translated<Facet> | undefined> {
-        return this.facetService.findOne(args.id, ctx.languageCode);
+        return this.facetService.findOne(ctx, args.id);
     }
 
     @Mutation()
     @Allow(Permission.CreateCatalog)
-    async createFacet(@Args() args: MutationCreateFacetArgs): Promise<Translated<Facet>> {
+    async createFacet(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationCreateFacetArgs,
+    ): Promise<Translated<Facet>> {
         const { input } = args;
-        const facet = await this.facetService.create(args.input);
+        const facet = await this.facetService.create(ctx, args.input);
 
         if (input.values && input.values.length) {
             for (const value of input.values) {
-                const newValue = await this.facetValueService.create(facet, value);
+                const newValue = await this.facetValueService.create(ctx, facet, value);
                 facet.values.push(newValue);
             }
         }
@@ -67,9 +70,12 @@ export class FacetResolver {
 
     @Mutation()
     @Allow(Permission.UpdateCatalog)
-    async updateFacet(@Args() args: MutationUpdateFacetArgs): Promise<Translated<Facet>> {
+    async updateFacet(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdateFacetArgs,
+    ): Promise<Translated<Facet>> {
         const { input } = args;
-        return this.facetService.update(args.input);
+        return this.facetService.update(ctx, args.input);
     }
 
     @Mutation()
@@ -84,24 +90,26 @@ export class FacetResolver {
     @Mutation()
     @Allow(Permission.CreateCatalog)
     async createFacetValues(
+        @Ctx() ctx: RequestContext,
         @Args() args: MutationCreateFacetValuesArgs,
     ): Promise<Array<Translated<FacetValue>>> {
         const { input } = args;
         const facetId = input[0].facetId;
-        const facet = await this.facetService.findOne(facetId, this.configService.defaultLanguageCode);
+        const facet = await this.facetService.findOne(ctx, facetId);
         if (!facet) {
             throw new EntityNotFoundError('Facet', facetId);
         }
-        return Promise.all(input.map((facetValue) => this.facetValueService.create(facet, facetValue)));
+        return Promise.all(input.map(facetValue => this.facetValueService.create(ctx, facet, facetValue)));
     }
 
     @Mutation()
     @Allow(Permission.UpdateCatalog)
     async updateFacetValues(
+        @Ctx() ctx: RequestContext,
         @Args() args: MutationUpdateFacetValuesArgs,
     ): Promise<Array<Translated<FacetValue>>> {
         const { input } = args;
-        return Promise.all(input.map((facetValue) => this.facetValueService.update(facetValue)));
+        return Promise.all(input.map(facetValue => this.facetValueService.update(ctx, facetValue)));
     }
 
     @Mutation()
@@ -110,6 +118,6 @@ export class FacetResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: MutationDeleteFacetValuesArgs,
     ): Promise<DeletionResponse[]> {
-        return Promise.all(args.ids.map((id) => this.facetValueService.delete(ctx, id, args.force || false)));
+        return Promise.all(args.ids.map(id => this.facetValueService.delete(ctx, id, args.force || false)));
     }
 }

+ 7 - 5
packages/core/src/api/resolvers/admin/global-settings.resolver.ts

@@ -11,7 +11,9 @@ import { CustomFields } from '../../../config/custom-field/custom-field-types';
 import { ChannelService } from '../../../service/services/channel.service';
 import { GlobalSettingsService } from '../../../service/services/global-settings.service';
 import { OrderService } from '../../../service/services/order.service';
+import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
+import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('GlobalSettings')
 export class GlobalSettingsResolver {
@@ -24,8 +26,8 @@ export class GlobalSettingsResolver {
 
     @Query()
     @Allow(Permission.Authenticated)
-    async globalSettings() {
-        return this.globalSettingsService.getSettings();
+    async globalSettings(@Ctx() ctx: RequestContext) {
+        return this.globalSettingsService.getSettings(ctx);
     }
 
     /**
@@ -53,12 +55,12 @@ export class GlobalSettingsResolver {
 
     @Mutation()
     @Allow(Permission.UpdateSettings)
-    async updateGlobalSettings(@Args() args: MutationUpdateGlobalSettingsArgs) {
+    async updateGlobalSettings(@Ctx() ctx: RequestContext, @Args() args: MutationUpdateGlobalSettingsArgs) {
         // This validation is performed here in the resolver rather than at the service
         // layer to avoid a circular dependency [ChannelService <> GlobalSettingsService]
         const { availableLanguages } = args.input;
         if (availableLanguages) {
-            const channels = await this.channelService.findAll();
+            const channels = await this.channelService.findAll(ctx);
             const unavailableDefaults = channels.filter(
                 c => !availableLanguages.includes(c.defaultLanguageCode),
             );
@@ -69,6 +71,6 @@ export class GlobalSettingsResolver {
                 });
             }
         }
-        return this.globalSettingsService.updateSettings(args.input);
+        return this.globalSettingsService.updateSettings(ctx, args.input);
     }
 }

+ 19 - 8
packages/core/src/api/resolvers/admin/payment-method.resolver.ts

@@ -1,15 +1,17 @@
 import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
+    MutationUpdatePaymentMethodArgs,
+    Permission,
     QueryPaymentMethodArgs,
     QueryPaymentMethodsArgs,
-    Permission,
-    MutationUpdatePaymentMethodArgs,
 } from '@vendure/common/lib/generated-types';
 import { PaginatedList } from '@vendure/common/lib/shared-types';
 
 import { PaymentMethod } from '../../../entity/payment-method/payment-method.entity';
 import { PaymentMethodService } from '../../../service/services/payment-method.service';
+import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
+import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('PaymentMethod')
 export class PaymentMethodResolver {
@@ -17,19 +19,28 @@ export class PaymentMethodResolver {
 
     @Query()
     @Allow(Permission.ReadSettings)
-    paymentMethods(@Args() args: QueryPaymentMethodsArgs): Promise<PaginatedList<PaymentMethod>> {
-        return this.paymentMethodService.findAll(args.options || undefined);
+    paymentMethods(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryPaymentMethodsArgs,
+    ): Promise<PaginatedList<PaymentMethod>> {
+        return this.paymentMethodService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
     @Allow(Permission.ReadSettings)
-    paymentMethod(@Args() args: QueryPaymentMethodArgs): Promise<PaymentMethod | undefined> {
-        return this.paymentMethodService.findOne(args.id);
+    paymentMethod(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryPaymentMethodArgs,
+    ): Promise<PaymentMethod | undefined> {
+        return this.paymentMethodService.findOne(ctx, args.id);
     }
 
     @Mutation()
     @Allow(Permission.UpdateSettings)
-    updatePaymentMethod(@Args() args: MutationUpdatePaymentMethodArgs): Promise<PaymentMethod> {
-        return this.paymentMethodService.update(args.input);
+    updatePaymentMethod(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdatePaymentMethodArgs,
+    ): Promise<PaymentMethod> {
+        return this.paymentMethodService.update(ctx, args.input);
     }
 }

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

@@ -96,8 +96,11 @@ export class PromotionResolver {
 
     @Mutation()
     @Allow(Permission.DeletePromotion)
-    deletePromotion(@Args() args: MutationDeletePromotionArgs): Promise<DeletionResponse> {
-        return this.promotionService.softDeletePromotion(args.id);
+    deletePromotion(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeletePromotionArgs,
+    ): Promise<DeletionResponse> {
+        return this.promotionService.softDeletePromotion(ctx, args.id);
     }
 
     /**

+ 4 - 4
packages/core/src/api/resolvers/admin/role.resolver.ts

@@ -22,14 +22,14 @@ export class RoleResolver {
 
     @Query()
     @Allow(Permission.ReadAdministrator)
-    roles(@Args() args: QueryRolesArgs): Promise<PaginatedList<Role>> {
-        return this.roleService.findAll(args.options || undefined);
+    roles(@Ctx() ctx: RequestContext, @Args() args: QueryRolesArgs): Promise<PaginatedList<Role>> {
+        return this.roleService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
     @Allow(Permission.ReadAdministrator)
-    role(@Args() args: QueryRoleArgs): Promise<Role | undefined> {
-        return this.roleService.findOne(args.id);
+    role(@Ctx() ctx: RequestContext, @Args() args: QueryRoleArgs): Promise<Role | undefined> {
+        return this.roleService.findOne(ctx, args.id);
     }
 
     @Mutation()

+ 12 - 6
packages/core/src/api/resolvers/admin/tax-category.resolver.ts

@@ -21,7 +21,7 @@ export class TaxCategoryResolver {
     @Query()
     @Allow(Permission.ReadSettings, Permission.ReadCatalog)
     taxCategories(@Ctx() ctx: RequestContext): Promise<TaxCategory[]> {
-        return this.taxCategoryService.findAll();
+        return this.taxCategoryService.findAll(ctx);
     }
 
     @Query()
@@ -30,19 +30,25 @@ export class TaxCategoryResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: QueryTaxCategoryArgs,
     ): Promise<TaxCategory | undefined> {
-        return this.taxCategoryService.findOne(args.id);
+        return this.taxCategoryService.findOne(ctx, args.id);
     }
 
     @Mutation()
     @Allow(Permission.CreateSettings)
-    async createTaxCategory(@Args() args: MutationCreateTaxCategoryArgs): Promise<TaxCategory> {
-        return this.taxCategoryService.create(args.input);
+    async createTaxCategory(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationCreateTaxCategoryArgs,
+    ): Promise<TaxCategory> {
+        return this.taxCategoryService.create(ctx, args.input);
     }
 
     @Mutation()
     @Allow(Permission.UpdateSettings)
-    async updateTaxCategory(@Args() args: MutationUpdateTaxCategoryArgs): Promise<TaxCategory> {
-        return this.taxCategoryService.update(args.input);
+    async updateTaxCategory(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdateTaxCategoryArgs,
+    ): Promise<TaxCategory> {
+        return this.taxCategoryService.update(ctx, args.input);
     }
 
     @Mutation()

+ 3 - 3
packages/core/src/api/resolvers/admin/tax-rate.resolver.ts

@@ -23,13 +23,13 @@ export class TaxRateResolver {
     @Query()
     @Allow(Permission.ReadSettings, Permission.ReadCatalog)
     taxRates(@Ctx() ctx: RequestContext, @Args() args: QueryTaxRatesArgs): Promise<PaginatedList<TaxRate>> {
-        return this.taxRateService.findAll(args.options || undefined);
+        return this.taxRateService.findAll(ctx, args.options || undefined);
     }
 
     @Query()
     @Allow(Permission.ReadSettings, Permission.ReadCatalog)
     async taxRate(@Ctx() ctx: RequestContext, @Args() args: QueryTaxRateArgs): Promise<TaxRate | undefined> {
-        return this.taxRateService.findOne(args.id);
+        return this.taxRateService.findOne(ctx, args.id);
     }
 
     @Mutation()
@@ -56,6 +56,6 @@ export class TaxRateResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: MutationDeleteTaxRateArgs,
     ): Promise<DeletionResponse> {
-        return this.taxRateService.delete(args.id);
+        return this.taxRateService.delete(ctx, args.id);
     }
 }

+ 4 - 4
packages/core/src/api/resolvers/base/base-auth.resolver.ts

@@ -73,12 +73,12 @@ export class BaseAuthResolver {
             throw new ForbiddenError();
         }
         if (apiType === 'admin') {
-            const administrator = await this.administratorService.findOneByUserId(userId);
+            const administrator = await this.administratorService.findOneByUserId(ctx, userId);
             if (!administrator) {
                 throw new ForbiddenError();
             }
         }
-        const user = userId && (await this.userService.getUserById(userId));
+        const user = userId && (await this.userService.getUserById(ctx, userId));
         return user ? this.publiclyAccessibleUser(user) : null;
     }
 
@@ -95,7 +95,7 @@ export class BaseAuthResolver {
         const { apiType } = ctx;
         const session = await this.authService.authenticate(ctx, apiType, method, data);
         if (apiType && apiType === 'admin') {
-            const administrator = await this.administratorService.findOneByUserId(session.user.id);
+            const administrator = await this.administratorService.findOneByUserId(ctx, session.user.id);
             if (!administrator) {
                 throw new UnauthorizedError();
             }
@@ -124,7 +124,7 @@ export class BaseAuthResolver {
         if (!activeUserId) {
             throw new InternalServerError(`error.no-active-user-id`);
         }
-        return this.userService.updatePassword(activeUserId, currentPassword, newPassword);
+        return this.userService.updatePassword(ctx, activeUserId, currentPassword, newPassword);
     }
 
     /**

+ 9 - 4
packages/core/src/api/resolvers/entity/customer-entity.resolver.ts

@@ -54,7 +54,7 @@ export class CustomerEntityResolver {
             return customer.user;
         }
 
-        return this.userService.getUserByEmailAddress(customer.emailAddress);
+        return this.userService.getUserByEmailAddress(ctx, customer.emailAddress);
     }
 }
 
@@ -67,16 +67,21 @@ export class CustomerAdminEntityResolver {
         if (customer.groups) {
             return customer.groups;
         }
-        return this.customerService.getCustomerGroups(customer.id);
+        return this.customerService.getCustomerGroups(ctx, customer.id);
     }
 
     @ResolveField()
-    async history(@Api() apiType: ApiType, @Parent() order: Order, @Args() args: any) {
+    async history(
+        @Ctx() ctx: RequestContext,
+        @Api() apiType: ApiType,
+        @Parent() order: Order,
+        @Args() args: any,
+    ) {
         const publicOnly = apiType === 'shop';
         const options: HistoryEntryListOptions = { ...args.options };
         if (!options.sort) {
             options.sort = { createdAt: SortOrder.ASC };
         }
-        return this.historyService.getHistoryForCustomer(order.id, publicOnly, options);
+        return this.historyService.getHistoryForCustomer(ctx, order.id, publicOnly, options);
     }
 }

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

@@ -18,6 +18,6 @@ export class CustomerGroupEntityResolver {
         @Parent() customerGroup: CustomerGroup,
         @Args() args: QueryCustomersArgs,
     ): Promise<PaginatedList<Customer>> {
-        return this.customerGroupService.getGroupCustomers(customerGroup.id, args.options || undefined);
+        return this.customerGroupService.getGroupCustomers(ctx, customerGroup.id, args.options || undefined);
     }
 }

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

@@ -15,6 +15,6 @@ export class FacetEntityResolver {
         if (facet.values) {
             return facet.values;
         }
-        return this.facetValueService.findByFacetId(facet.id, ctx.languageCode);
+        return this.facetValueService.findByFacetId(ctx, facet.id);
     }
 }

+ 2 - 1
packages/core/src/api/resolvers/entity/facet-value-entity.resolver.ts

@@ -2,6 +2,7 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
 
 import { FacetValue } from '../../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../../entity/facet/facet.entity';
+import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { FacetService } from '../../../service/services/facet.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
@@ -15,6 +16,6 @@ export class FacetValueEntityResolver {
         if (facetValue.facet) {
             return facetValue.facet;
         }
-        return this.facetService.findByFacetValueId(facetValue.id, ctx.languageCode);
+        return this.facetService.findByFacetValueId(ctx, facetValue.id);
     }
 }

+ 12 - 6
packages/core/src/api/resolvers/entity/order-entity.resolver.ts

@@ -2,6 +2,7 @@ import { Args, Parent, ResolveField, Resolver } from '@nestjs/graphql';
 import { HistoryEntryListOptions, OrderHistoryArgs, SortOrder } from '@vendure/common/lib/generated-types';
 
 import { Order } from '../../../entity/order/order.entity';
+import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { HistoryService } from '../../../service/services/history.service';
 import { OrderService } from '../../../service/services/order.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
@@ -19,11 +20,11 @@ export class OrderEntityResolver {
     ) {}
 
     @ResolveField()
-    async payments(@Parent() order: Order) {
+    async payments(@Ctx() ctx: RequestContext, @Parent() order: Order) {
         if (order.payments) {
             return order.payments;
         }
-        return this.orderService.getOrderPayments(order.id);
+        return this.orderService.getOrderPayments(ctx, order.id);
     }
 
     @ResolveField()
@@ -39,18 +40,23 @@ export class OrderEntityResolver {
     }
 
     @ResolveField()
-    async fulfillments(@Parent() order: Order) {
-        return this.orderService.getOrderFulfillments(order);
+    async fulfillments(@Ctx() ctx: RequestContext, @Parent() order: Order) {
+        return this.orderService.getOrderFulfillments(ctx, order);
     }
 
     @ResolveField()
-    async history(@Api() apiType: ApiType, @Parent() order: Order, @Args() args: OrderHistoryArgs) {
+    async history(
+        @Ctx() ctx: RequestContext,
+        @Api() apiType: ApiType,
+        @Parent() order: Order,
+        @Args() args: OrderHistoryArgs,
+    ) {
         const publicOnly = apiType === 'shop';
         const options: HistoryEntryListOptions = { ...args.options };
         if (!options.sort) {
             options.sort = { createdAt: SortOrder.ASC };
         }
-        return this.historyService.getHistoryForOrder(order.id, publicOnly, options);
+        return this.historyService.getHistoryForOrder(ctx, order.id, publicOnly, options);
     }
 
     @ResolveField()

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

@@ -15,7 +15,7 @@ export class PaymentEntityResolver {
         if (payment.refunds) {
             return payment.refunds;
         } else {
-            return this.orderService.getPaymentRefunds(payment.id);
+            return this.orderService.getPaymentRefunds(ctx, payment.id);
         }
     }
 }

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

@@ -15,6 +15,6 @@ export class RoleEntityResolver {
         if (role.channels) {
             return role.channels;
         }
-        return this.roleService.getChannelsForRole(role.id);
+        return this.roleService.getChannelsForRole(ctx, role.id);
     }
 }

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

@@ -23,7 +23,7 @@ export class UserEntityResolver {
             methodEntities = user.authenticationMethods;
         }
         methodEntities = await this.userService
-            .getUserById(user.id)
+            .getUserById(ctx, user.id)
             .then(u => u?.authenticationMethods ?? []);
 
         return methodEntities.map(m => ({

+ 3 - 3
packages/core/src/api/resolvers/shop/shop-auth.resolver.ts

@@ -50,7 +50,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
     ) {
         super(authService, userService, administratorService, configService);
         this.nativeAuthStrategyIsConfigured = !!this.configService.authOptions.shopAuthenticationStrategy.find(
-            (strategy) => strategy.name === NATIVE_AUTH_STRATEGY_NAME,
+            strategy => strategy.name === NATIVE_AUTH_STRATEGY_NAME,
         );
     }
 
@@ -194,7 +194,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
         this.requireNativeAuthStrategy();
         const result = await super.updatePassword(ctx, args.currentPassword, args.newPassword);
         if (result && ctx.activeUserId) {
-            const customer = await this.customerService.findOneByUserId(ctx.activeUserId);
+            const customer = await this.customerService.findOneByUserId(ctx, ctx.activeUserId);
             if (customer) {
                 await this.historyService.createHistoryEntryForCustomer({
                     ctx,
@@ -234,7 +234,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
     private requireNativeAuthStrategy() {
         if (!this.nativeAuthStrategyIsConfigured) {
             const authStrategyNames = this.configService.authOptions.shopAuthenticationStrategy
-                .map((s) => s.name)
+                .map(s => s.name)
                 .join(', ');
             const errorMessage =
                 'This GraphQL operation requires that the NativeAuthenticationStrategy be configured for the Shop API.\n' +

+ 2 - 2
packages/core/src/api/resolvers/shop/shop-customer.resolver.ts

@@ -24,7 +24,7 @@ export class ShopCustomerResolver {
     async activeCustomer(@Ctx() ctx: RequestContext): Promise<Customer | undefined> {
         const userId = ctx.activeUserId;
         if (userId) {
-            return this.customerService.findOneByUserId(userId);
+            return this.customerService.findOneByUserId(ctx, userId);
         }
     }
 
@@ -87,7 +87,7 @@ export class ShopCustomerResolver {
         if (!userId) {
             throw new ForbiddenError();
         }
-        const customer = await this.customerService.findOneByUserId(userId);
+        const customer = await this.customerService.findOneByUserId(ctx, userId);
         if (!customer) {
             throw new InternalServerError(`error.no-customer-found-for-current-user`);
         }

+ 4 - 4
packages/core/src/api/resolvers/shop/shop-order.resolver.ts

@@ -317,7 +317,7 @@ export class ShopOrderResolver {
                     }
                 }
                 if (order.active === false && ctx.session?.activeOrderId === sessionOrder.id) {
-                    await this.sessionService.unsetActiveOrder(ctx.session);
+                    await this.sessionService.unsetActiveOrder(ctx, ctx.session);
                 }
                 return order;
             }
@@ -333,7 +333,7 @@ export class ShopOrderResolver {
             }
             const sessionOrder = await this.getOrderFromContext(ctx);
             if (sessionOrder) {
-                const customer = await this.customerService.createOrUpdate(args.input, true);
+                const customer = await this.customerService.createOrUpdate(ctx, args.input, true);
                 return this.orderService.addCustomerToOrder(ctx, sessionOrder.id, customer);
             }
         }
@@ -354,7 +354,7 @@ export class ShopOrderResolver {
         if (order && order.active === false) {
             // edge case where an inactive order may not have been
             // removed from the session, i.e. the regular process was interrupted
-            await this.sessionService.unsetActiveOrder(ctx.session);
+            await this.sessionService.unsetActiveOrder(ctx, ctx.session);
             order = undefined;
         }
         if (!order) {
@@ -367,7 +367,7 @@ export class ShopOrderResolver {
             }
 
             if (order) {
-                await this.sessionService.setActiveOrder(ctx.session, order);
+                await this.sessionService.setActiveOrder(ctx, ctx.session, order);
             }
         }
         return order || undefined;

+ 1 - 1
packages/core/src/config/promotion/conditions/customer-group-condition.ts

@@ -33,7 +33,7 @@ export const customerGroup = new PromotionCondition({
         const customerId = order.customer.id;
         let groupIds = cache.get(customerId);
         if (!groupIds) {
-            const groups = await customerService.getCustomerGroups(customerId);
+            const groups = await customerService.getCustomerGroups(undefined, customerId);
             groupIds = groups.map(g => g.id);
             cache.set(customerId, groupIds);
         }

+ 6 - 0
packages/core/src/data-import/providers/importer/fast-importer.service.ts

@@ -7,6 +7,7 @@ import {
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
 
+import { RequestContext } from '../../../api/common/request-context';
 import { Channel } from '../../../entity/channel/channel.entity';
 import { ProductOptionGroupTranslation } from '../../../entity/product-option-group/product-option-group-translation.entity';
 import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
@@ -46,6 +47,7 @@ export class FastImporterService {
 
     async createProduct(input: CreateProductInput): Promise<ID> {
         const product = await this.translatableSaver.create({
+            ctx: RequestContext.empty(),
             input,
             entityType: Product,
             translationType: ProductTranslation,
@@ -75,6 +77,7 @@ export class FastImporterService {
 
     async createProductOptionGroup(input: CreateProductOptionGroupInput): Promise<ID> {
         const group = await this.translatableSaver.create({
+            ctx: RequestContext.empty(),
             input,
             entityType: ProductOptionGroup,
             translationType: ProductOptionGroupTranslation,
@@ -84,6 +87,7 @@ export class FastImporterService {
 
     async createProductOption(input: CreateProductOptionInput): Promise<ID> {
         const option = await this.translatableSaver.create({
+            ctx: RequestContext.empty(),
             input,
             entityType: ProductOption,
             translationType: ProductOptionTranslation,
@@ -110,6 +114,7 @@ export class FastImporterService {
         }
 
         const createdVariant = await this.translatableSaver.create({
+            ctx: RequestContext.empty(),
             input,
             entityType: ProductVariant,
             translationType: ProductVariantTranslation,
@@ -141,6 +146,7 @@ export class FastImporterService {
         }
         if (input.stockOnHand != null && input.stockOnHand !== 0) {
             await this.stockMovementService.adjustProductVariantStock(
+                RequestContext.empty(),
                 createdVariant.id,
                 0,
                 input.stockOnHand,

+ 10 - 6
packages/core/src/data-import/providers/importer/importer.ts

@@ -140,7 +140,7 @@ export class Importer {
         let errors: string[] = [];
         let imported = 0;
         const languageCode = ctx.languageCode;
-        const taxCategories = await this.taxCategoryService.findAll();
+        const taxCategories = await this.taxCategoryService.findAll(ctx);
         await this.fastImporter.initialize();
         for (const { product, variants } of rows) {
             const createProductAssets = await this.assetImporter.getAssets(product.assetPaths);
@@ -252,7 +252,7 @@ export class Importer {
                 if (existing) {
                     facetEntity = existing;
                 } else {
-                    facetEntity = await this.facetService.create({
+                    facetEntity = await this.facetService.create(RequestContext.empty(), {
                         isPrivate: false,
                         code: normalizeString(facetName, '-'),
                         translations: [{ languageCode, name: facetName }],
@@ -271,10 +271,14 @@ export class Importer {
                 if (existing) {
                     facetValueEntity = existing;
                 } else {
-                    facetValueEntity = await this.facetValueService.create(facetEntity, {
-                        code: normalizeString(valueName, '-'),
-                        translations: [{ languageCode, name: valueName }],
-                    });
+                    facetValueEntity = await this.facetValueService.create(
+                        RequestContext.empty(),
+                        facetEntity,
+                        {
+                            code: normalizeString(valueName, '-'),
+                            translations: [{ languageCode, name: valueName }],
+                        },
+                    );
                 }
                 this.facetValueMap.set(facetValueMapKey, facetValueEntity);
             }

+ 2 - 2
packages/core/src/data-import/providers/populator/populator.ts

@@ -134,7 +134,7 @@ export class Populator {
             );
         }
         const defaultZoneId = defaultZone.entity.id;
-        await this.channelService.update({
+        await this.channelService.update(RequestContext.empty(), {
             id: channel.id,
             defaultTaxZoneId: defaultZoneId,
             defaultShippingZoneId: defaultZoneId,
@@ -177,7 +177,7 @@ export class Populator {
         const taxCategories: TaxCategory[] = [];
 
         for (const taxRate of taxRates) {
-            const category = await this.taxCategoryService.create({ name: taxRate.name });
+            const category = await this.taxCategoryService.create(ctx, { name: taxRate.name });
 
             for (const { entity } of zoneMap.values()) {
                 await this.taxRateService.create(ctx, {

+ 1 - 4
packages/core/src/plugin/default-search-plugin/fulltext-search.service.ts

@@ -64,10 +64,7 @@ export class FulltextSearchService {
         enabledOnly: boolean = false,
     ): Promise<Array<{ facetValue: FacetValue; count: number }>> {
         const facetValueIdsMap = await this.searchStrategy.getFacetValueIds(ctx, input, enabledOnly);
-        const facetValues = await this.facetValueService.findByIds(
-            Array.from(facetValueIdsMap.keys()),
-            ctx.languageCode,
-        );
+        const facetValues = await this.facetValueService.findByIds(ctx, Array.from(facetValueIdsMap.keys()));
         return facetValues.map((facetValue, index) => {
             return {
                 facetValue,

+ 2 - 1
packages/core/src/service/controllers/tax-rate.controller.ts

@@ -2,6 +2,7 @@ import { Controller } from '@nestjs/common';
 import { MessagePattern } from '@nestjs/microservices';
 import { from, Observable } from 'rxjs';
 
+import { RequestContext } from '../../api/common/request-context';
 import { TaxRateService } from '../services/tax-rate.service';
 import { TransactionalConnection } from '../transaction/transactional-connection';
 import { TaxRateUpdatedMessage } from '../types/tax-rate-messages';
@@ -16,6 +17,6 @@ export class TaxRateController {
      */
     @MessagePattern(TaxRateUpdatedMessage.pattern)
     taxRateUpdated(): Observable<TaxRateUpdatedMessage['response']> {
-        return from(this.taxRateService.updateActiveTaxRates().then(() => true));
+        return from(this.taxRateService.updateActiveTaxRates(RequestContext.empty()).then(() => true));
     }
 }

+ 23 - 14
packages/core/src/service/helpers/external-authentication/external-authentication.service.ts

@@ -4,6 +4,7 @@ import { HistoryEntryType } from '@vendure/common/lib/generated-types';
 import { RequestContext } from '../../../api/common/request-context';
 import { Administrator } from '../../../entity/administrator/administrator.entity';
 import { ExternalAuthenticationMethod } from '../../../entity/authentication-method/external-authentication-method.entity';
+import { Collection } from '../../../entity/collection/collection.entity';
 import { Customer } from '../../../entity/customer/customer.entity';
 import { Role } from '../../../entity/role/role.entity';
 import { User } from '../../../entity/user/user.entity';
@@ -35,12 +36,16 @@ export class ExternalAuthenticationService {
      * Looks up a User based on their identifier from an external authentication
      * provider, ensuring this User is associated with a Customer account.
      */
-    async findCustomerUser(strategy: string, externalIdentifier: string): Promise<User | undefined> {
-        const user = await this.findUser(strategy, externalIdentifier);
+    async findCustomerUser(
+        ctx: RequestContext,
+        strategy: string,
+        externalIdentifier: string,
+    ): Promise<User | undefined> {
+        const user = await this.findUser(ctx, strategy, externalIdentifier);
 
         if (user) {
             // Ensure this User is associated with a Customer
-            const customer = await this.customerService.findOneByUserId(user.id);
+            const customer = await this.customerService.findOneByUserId(ctx, user.id);
             if (customer) {
                 return user;
             }
@@ -52,11 +57,15 @@ export class ExternalAuthenticationService {
      * Looks up a User based on their identifier from an external authentication
      * provider, ensuring this User is associated with an Administrator account.
      */
-    async findAdministratorUser(strategy: string, externalIdentifier: string): Promise<User | undefined> {
-        const user = await this.findUser(strategy, externalIdentifier);
+    async findAdministratorUser(
+        ctx: RequestContext,
+        strategy: string,
+        externalIdentifier: string,
+    ): Promise<User | undefined> {
+        const user = await this.findUser(ctx, strategy, externalIdentifier);
         if (user) {
             // Ensure this User is associated with an Administrator
-            const administrator = await this.administratorService.findOneByUserId(user.id);
+            const administrator = await this.administratorService.findOneByUserId(ctx, user.id);
             if (administrator) {
                 return user;
             }
@@ -88,7 +97,7 @@ export class ExternalAuthenticationService {
             verified: config.verified || false,
         });
 
-        const authMethod = await this.connection.getRepository(ExternalAuthenticationMethod).save(
+        const authMethod = await this.connection.getRepository(ctx, ExternalAuthenticationMethod).save(
             new ExternalAuthenticationMethod({
                 externalIdentifier: config.externalIdentifier,
                 strategy: config.strategy,
@@ -96,9 +105,9 @@ export class ExternalAuthenticationService {
         );
 
         newUser.authenticationMethods = [authMethod];
-        const savedUser = await this.connection.getRepository(User).save(newUser);
+        const savedUser = await this.connection.getRepository(ctx, User).save(newUser);
 
-        const customer = await this.connection.getRepository(Customer).save(
+        const customer = await this.connection.getRepository(ctx, Customer).save(
             new Customer({
                 emailAddress: config.emailAddress,
                 firstName: config.firstName,
@@ -154,7 +163,7 @@ export class ExternalAuthenticationService {
             verified: true,
         });
 
-        const authMethod = await this.connection.getRepository(ExternalAuthenticationMethod).save(
+        const authMethod = await this.connection.getRepository(ctx, ExternalAuthenticationMethod).save(
             new ExternalAuthenticationMethod({
                 externalIdentifier: config.externalIdentifier,
                 strategy: config.strategy,
@@ -162,9 +171,9 @@ export class ExternalAuthenticationService {
         );
 
         newUser.authenticationMethods = [authMethod];
-        const savedUser = await this.connection.getRepository(User).save(newUser);
+        const savedUser = await this.connection.getRepository(ctx, User).save(newUser);
 
-        const administrator = await this.connection.getRepository(Administrator).save(
+        const administrator = await this.connection.getRepository(ctx, Administrator).save(
             new Administrator({
                 emailAddress: config.emailAddress,
                 firstName: config.firstName,
@@ -176,9 +185,9 @@ export class ExternalAuthenticationService {
         return newUser;
     }
 
-    private findUser(strategy: string, externalIdentifier: string): Promise<User | undefined> {
+    findUser(ctx: RequestContext, strategy: string, externalIdentifier: string): Promise<User | undefined> {
         return this.connection
-            .getRepository(User)
+            .getRepository(ctx, User)
             .createQueryBuilder('user')
             .leftJoinAndSelect('user.authenticationMethods', 'authMethod')
             .where('authMethod.strategy = :strategy', { strategy })

+ 10 - 1
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -3,6 +3,7 @@ import { ID, Type } from '@vendure/common/lib/shared-types';
 import { FindConditions, FindManyOptions, FindOneOptions, SelectQueryBuilder } from 'typeorm';
 import { FindOptionsUtils } from 'typeorm/find-options/FindOptionsUtils';
 
+import { RequestContext } from '../../../api/common/request-context';
 import { ListQueryOptions } from '../../../common/types/common-types';
 import { VendureEntity } from '../../../entity/base/base.entity';
 import { TransactionalConnection } from '../../transaction/transactional-connection';
@@ -16,6 +17,11 @@ export type ExtendedListQueryOptions<T extends VendureEntity> = {
     channelId?: ID;
     where?: FindConditions<T>;
     orderBy?: FindOneOptions<T>['order'];
+    /**
+     * When a RequestContext is passed, then the query will be
+     * executed as part of any outer transaction.
+     */
+    ctx?: RequestContext;
 };
 
 @Injectable()
@@ -43,7 +49,10 @@ export class ListQueryBuilder {
         );
         const filter = parseFilterParams(rawConnection, entity, options.filter);
 
-        const qb = this.connection.getRepository(entity).createQueryBuilder(entity.name.toLowerCase());
+        const repo = extendedOptions.ctx
+            ? this.connection.getRepository(extendedOptions.ctx, entity)
+            : this.connection.getRepository(entity);
+        const qb = repo.createQueryBuilder(entity.name.toLowerCase());
         FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
             relations: extendedOptions.relations,
             take,

+ 2 - 2
packages/core/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -106,8 +106,8 @@ export class OrderStateMachine {
         if (toState === 'PaymentAuthorized' || toState === 'PaymentSettled') {
             data.order.active = false;
             data.order.orderPlacedAt = new Date();
-            await this.stockMovementService.createSalesForOrder(data.order);
-            await this.promotionService.addPromotionsToOrder(data.order);
+            await this.stockMovementService.createSalesForOrder(data.ctx, data.order);
+            await this.promotionService.addPromotionsToOrder(data.ctx, data.order);
         }
         if (toState === 'Cancelled') {
             data.order.active = false;

+ 4 - 1
packages/core/src/service/helpers/slug-validator/slug-validator.ts

@@ -3,7 +3,9 @@ import { LanguageCode } from '@vendure/common/lib/generated-types';
 import { normalizeString } from '@vendure/common/lib/normalize-string';
 import { ID, Type } from '@vendure/common/lib/shared-types';
 
+import { RequestContext } from '../../../api/common/request-context';
 import { VendureEntity } from '../../../entity/base/base.entity';
+import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 export type InputWithSlug = {
@@ -30,6 +32,7 @@ export class SlugValidator {
      * Mutates the input.
      */
     async validateSlugs<T extends InputWithSlug, E extends TranslationEntity>(
+        ctx: RequestContext,
         input: T,
         translationEntity: Type<E>,
     ): Promise<T> {
@@ -42,7 +45,7 @@ export class SlugValidator {
                     const alreadySuffixed = /-\d+$/;
                     do {
                         const qb = this.connection
-                            .getRepository(translationEntity)
+                            .getRepository(ctx, translationEntity)
                             .createQueryBuilder('translation')
                             .where(`translation.slug = :slug`, { slug: t.slug })
                             .andWhere(`translation.languageCode = :languageCode`, {

+ 12 - 6
packages/core/src/service/helpers/translatable-saver/translatable-saver.ts

@@ -2,14 +2,17 @@ import { Injectable } from '@nestjs/common';
 import { omit } from '@vendure/common/lib/omit';
 import { ID, Type } from '@vendure/common/lib/shared-types';
 
+import { RequestContext } from '../../../api/common/request-context';
 import { Translatable, TranslatedInput, Translation } from '../../../common/types/locale-types';
 import { VendureEntity } from '../../../entity/base/base.entity';
+import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { TransactionalConnection } from '../../transaction/transactional-connection';
 import { patchEntity } from '../utils/patch-entity';
 
 import { TranslationDiffer } from './translation-differ';
 
 export interface CreateTranslatableOptions<T extends Translatable> {
+    ctx: RequestContext;
     entityType: Type<T>;
     translationType: Type<Translation<T>>;
     input: TranslatedInput<T>;
@@ -33,7 +36,7 @@ export class TranslatableSaver {
      * to the `translations` array.
      */
     async create<T extends Translatable & VendureEntity>(options: CreateTranslatableOptions<T>): Promise<T> {
-        const { entityType, translationType, input, beforeSave, typeOrmSubscriberData } = options;
+        const { ctx, entityType, translationType, input, beforeSave, typeOrmSubscriberData } = options;
 
         const entity = new entityType(input);
         const translations: Array<Translation<T>> = [];
@@ -42,7 +45,7 @@ export class TranslatableSaver {
             for (const translationInput of input.translations) {
                 const translation = new translationType(translationInput);
                 translations.push(translation);
-                await this.connection.getRepository(translationType).save(translation as any);
+                await this.connection.getRepository(ctx, translationType).save(translation as any);
             }
         }
 
@@ -51,7 +54,7 @@ export class TranslatableSaver {
             await beforeSave(entity);
         }
         return await this.connection
-            .getRepository(entityType)
+            .getRepository(ctx, entityType)
             .save(entity as any, { data: typeOrmSubscriberData });
     }
 
@@ -60,8 +63,8 @@ export class TranslatableSaver {
      * perform the correct operation on the translations.
      */
     async update<T extends Translatable & VendureEntity>(options: UpdateTranslatableOptions<T>): Promise<T> {
-        const { entityType, translationType, input, beforeSave, typeOrmSubscriberData } = options;
-        const existingTranslations = await this.connection.getRepository(translationType).find({
+        const { ctx, entityType, translationType, input, beforeSave, typeOrmSubscriberData } = options;
+        const existingTranslations = await this.connection.getRepository(ctx, translationType).find({
             where: { base: input.id },
             relations: ['base'],
         });
@@ -69,6 +72,7 @@ export class TranslatableSaver {
         const differ = new TranslationDiffer(translationType, this.connection);
         const diff = differ.diff(existingTranslations, input.translations);
         const entity = await differ.applyDiff(
+            ctx,
             new entityType({ ...input, translations: existingTranslations }),
             diff,
         );
@@ -76,6 +80,8 @@ export class TranslatableSaver {
         if (typeof beforeSave === 'function') {
             await beforeSave(entity);
         }
-        return this.connection.getRepository(entityType).save(updatedEntity, { data: typeOrmSubscriberData });
+        return this.connection
+            .getRepository(ctx, entityType)
+            .save(updatedEntity, { data: typeOrmSubscriberData });
     }
 }

+ 9 - 3
packages/core/src/service/helpers/translatable-saver/translation-differ.ts

@@ -1,8 +1,10 @@
 import { DeepPartial } from '@vendure/common/lib/shared-types';
 
+import { RequestContext } from '../../../api/common/request-context';
 import { EntityNotFoundError } from '../../../common/error/errors';
 import { Translatable, Translation, TranslationInput } from '../../../common/types/locale-types';
 import { foundIn, not } from '../../../common/utils';
+import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 export type TranslationContructor<T> = new (
@@ -45,12 +47,16 @@ export class TranslationDiffer<Entity extends Translatable> {
         }
     }
 
-    async applyDiff(entity: Entity, { toUpdate, toAdd }: TranslationDiff<Entity>): Promise<Entity> {
+    async applyDiff(
+        ctx: RequestContext,
+        entity: Entity,
+        { toUpdate, toAdd }: TranslationDiff<Entity>,
+    ): Promise<Entity> {
         if (toUpdate.length) {
             for (const translation of toUpdate) {
                 // any cast below is required due to TS issue: https://github.com/Microsoft/TypeScript/issues/21592
                 const updated = await this.connection
-                    .getRepository(this.translationCtor)
+                    .getRepository(ctx, this.translationCtor)
                     .save(translation as any);
                 const index = entity.translations.findIndex(t => t.languageCode === updated.languageCode);
                 entity.translations.splice(index, 1, updated);
@@ -63,7 +69,7 @@ export class TranslationDiffer<Entity extends Translatable> {
                 let newTranslation: any;
                 try {
                     newTranslation = await this.connection
-                        .getRepository(this.translationCtor)
+                        .getRepository(ctx, this.translationCtor)
                         .save(translation as any);
                 } catch (err) {
                     const entityName = entity.constructor.name;

+ 35 - 25
packages/core/src/service/services/administrator.service.ts

@@ -6,6 +6,7 @@ import {
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 
+import { RequestContext } from '../../api/common/request-context';
 import { EntityNotFoundError } from '../../common/error/errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { ConfigService } from '../../config';
@@ -36,9 +37,16 @@ export class AdministratorService {
         await this.ensureSuperAdminExists();
     }
 
-    findAll(options?: ListQueryOptions<Administrator>): Promise<PaginatedList<Administrator>> {
+    findAll(
+        ctx: RequestContext,
+        options?: ListQueryOptions<Administrator>,
+    ): Promise<PaginatedList<Administrator>> {
         return this.listQueryBuilder
-            .build(Administrator, options, { relations: ['user', 'user.roles'], where: { deletedAt: null } })
+            .build(Administrator, options, {
+                relations: ['user', 'user.roles'],
+                where: { deletedAt: null },
+                ctx,
+            })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,
@@ -46,8 +54,8 @@ export class AdministratorService {
             }));
     }
 
-    findOne(administratorId: ID): Promise<Administrator | undefined> {
-        return this.connection.getRepository(Administrator).findOne(administratorId, {
+    findOne(ctx: RequestContext, administratorId: ID): Promise<Administrator | undefined> {
+        return this.connection.getRepository(ctx, Administrator).findOne(administratorId, {
             relations: ['user', 'user.roles'],
             where: {
                 deletedAt: null,
@@ -55,8 +63,8 @@ export class AdministratorService {
         });
     }
 
-    findOneByUserId(userId: ID): Promise<Administrator | undefined> {
-        return this.connection.getRepository(Administrator).findOne({
+    findOneByUserId(ctx: RequestContext, userId: ID): Promise<Administrator | undefined> {
+        return this.connection.getRepository(ctx, Administrator).findOne({
             where: {
                 user: { id: userId },
                 deletedAt: null,
@@ -64,37 +72,39 @@ export class AdministratorService {
         });
     }
 
-    async create(input: CreateAdministratorInput): Promise<Administrator> {
+    async create(ctx: RequestContext, input: CreateAdministratorInput): Promise<Administrator> {
         const administrator = new Administrator(input);
-        administrator.user = await this.userService.createAdminUser(input.emailAddress, input.password);
-        let createdAdministrator = await this.connection.getRepository(Administrator).save(administrator);
+        administrator.user = await this.userService.createAdminUser(ctx, input.emailAddress, input.password);
+        let createdAdministrator = await this.connection
+            .getRepository(ctx, Administrator)
+            .save(administrator);
         for (const roleId of input.roleIds) {
-            createdAdministrator = await this.assignRole(createdAdministrator.id, roleId);
+            createdAdministrator = await this.assignRole(ctx, createdAdministrator.id, roleId);
         }
         return createdAdministrator;
     }
 
-    async update(input: UpdateAdministratorInput): Promise<Administrator> {
-        const administrator = await this.findOne(input.id);
+    async update(ctx: RequestContext, input: UpdateAdministratorInput): Promise<Administrator> {
+        const administrator = await this.findOne(ctx, input.id);
         if (!administrator) {
             throw new EntityNotFoundError('Administrator', input.id);
         }
         let updatedAdministrator = patchEntity(administrator, input);
-        await this.connection.getRepository(Administrator).save(administrator, { reload: false });
+        await this.connection.getRepository(ctx, Administrator).save(administrator, { reload: false });
 
         if (input.password) {
-            const user = await this.userService.getUserById(administrator.user.id);
+            const user = await this.userService.getUserById(ctx, administrator.user.id);
             if (user) {
                 const nativeAuthMethod = user.getNativeAuthenticationMethod();
                 nativeAuthMethod.passwordHash = await this.passwordCipher.hash(input.password);
-                await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
+                await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
             }
         }
         if (input.roleIds) {
             administrator.user.roles = [];
-            await this.connection.getRepository(User).save(administrator.user, { reload: false });
+            await this.connection.getRepository(ctx, User).save(administrator.user, { reload: false });
             for (const roleId of input.roleIds) {
-                updatedAdministrator = await this.assignRole(administrator.id, roleId);
+                updatedAdministrator = await this.assignRole(ctx, administrator.id, roleId);
             }
         }
         return updatedAdministrator;
@@ -103,27 +113,27 @@ export class AdministratorService {
     /**
      * Assigns a Role to the Administrator's User entity.
      */
-    async assignRole(administratorId: ID, roleId: ID): Promise<Administrator> {
-        const administrator = await this.findOne(administratorId);
+    async assignRole(ctx: RequestContext, administratorId: ID, roleId: ID): Promise<Administrator> {
+        const administrator = await this.findOne(ctx, administratorId);
         if (!administrator) {
             throw new EntityNotFoundError('Administrator', administratorId);
         }
-        const role = await this.roleService.findOne(roleId);
+        const role = await this.roleService.findOne(ctx, roleId);
         if (!role) {
             throw new EntityNotFoundError('Role', roleId);
         }
         administrator.user.roles.push(role);
-        await this.connection.getRepository(User).save(administrator.user, { reload: false });
+        await this.connection.getRepository(ctx, User).save(administrator.user, { reload: false });
         return administrator;
     }
 
-    async softDelete(id: ID) {
+    async softDelete(ctx: RequestContext, id: ID) {
         const administrator = await getEntityOrThrow(this.connection, Administrator, id, {
             relations: ['user'],
         });
-        await this.connection.getRepository(Administrator).update({ id }, { deletedAt: new Date() });
+        await this.connection.getRepository(ctx, Administrator).update({ id }, { deletedAt: new Date() });
         // tslint:disable-next-line:no-non-null-assertion
-        await this.userService.softDelete(administrator.user!.id);
+        await this.userService.softDelete(ctx, administrator.user!.id);
         return {
             result: DeletionResult.DELETED,
         };
@@ -144,7 +154,7 @@ export class AdministratorService {
 
         if (!superAdminUser) {
             const superAdminRole = await this.roleService.getSuperAdminRole();
-            const administrator = await this.create({
+            const administrator = await this.create(RequestContext.empty(), {
                 emailAddress: superadminCredentials.identifier,
                 password: superadminCredentials.password,
                 firstName: 'Super',

+ 56 - 32
packages/core/src/service/services/asset.service.ts

@@ -23,6 +23,7 @@ import { Asset } from '../../entity/asset/asset.entity';
 import { OrderableAsset } from '../../entity/asset/orderable-asset.entity';
 import { VendureEntity } from '../../entity/base/base.entity';
 import { Collection } from '../../entity/collection/collection.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { Product } from '../../entity/product/product.entity';
 import { EventBus } from '../../event-bus/event-bus';
@@ -63,13 +64,13 @@ export class AssetService {
             });
     }
 
-    findOne(id: ID): Promise<Asset | undefined> {
-        return this.connection.getRepository(Asset).findOne(id);
+    findOne(ctx: RequestContext, id: ID): Promise<Asset | undefined> {
+        return this.connection.getRepository(ctx, Asset).findOne(id);
     }
 
-    findAll(options?: ListQueryOptions<Asset>): Promise<PaginatedList<Asset>> {
+    findAll(ctx: RequestContext, options?: ListQueryOptions<Asset>): Promise<PaginatedList<Asset>> {
         return this.listQueryBuilder
-            .build(Asset, options)
+            .build(Asset, options, { ctx })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,
@@ -103,7 +104,11 @@ export class AssetService {
         return assets.sort((a, b) => a.position - b.position).map(a => a.asset);
     }
 
-    async updateFeaturedAsset<T extends EntityWithAssets>(entity: T, input: EntityAssetInput): Promise<T> {
+    async updateFeaturedAsset<T extends EntityWithAssets>(
+        ctx: RequestContext,
+        entity: T,
+        input: EntityAssetInput,
+    ): Promise<T> {
         const { assetIds, featuredAssetId } = input;
         if (featuredAssetId === null || (assetIds && assetIds.length === 0)) {
             entity.featuredAsset = null;
@@ -112,7 +117,7 @@ export class AssetService {
         if (featuredAssetId === undefined) {
             return entity;
         }
-        const featuredAsset = await this.findOne(featuredAssetId);
+        const featuredAsset = await this.findOne(ctx, featuredAssetId);
         if (featuredAsset) {
             entity.featuredAsset = featuredAsset;
         }
@@ -122,20 +127,24 @@ export class AssetService {
     /**
      * Updates the assets / featuredAsset of an entity, ensuring that only valid assetIds are used.
      */
-    async updateEntityAssets<T extends EntityWithAssets>(entity: T, input: EntityAssetInput): Promise<T> {
+    async updateEntityAssets<T extends EntityWithAssets>(
+        ctx: RequestContext,
+        entity: T,
+        input: EntityAssetInput,
+    ): Promise<T> {
         if (!entity.id) {
             throw new InternalServerError('error.entity-must-have-an-id');
         }
         const { assetIds, featuredAssetId } = input;
         if (assetIds && assetIds.length) {
-            const assets = await this.connection.getRepository(Asset).findByIds(assetIds);
+            const assets = await this.connection.getRepository(ctx, Asset).findByIds(assetIds);
             const sortedAssets = assetIds
                 .map(id => assets.find(a => idsAreEqual(a.id, id)))
                 .filter(notNullOrUndefined);
-            await this.removeExistingOrderableAssets(entity);
-            entity.assets = await this.createOrderableAssets(entity, sortedAssets);
+            await this.removeExistingOrderableAssets(ctx, entity);
+            entity.assets = await this.createOrderableAssets(ctx, entity, sortedAssets);
         } else if (assetIds && assetIds.length === 0) {
-            await this.removeExistingOrderableAssets(entity);
+            await this.removeExistingOrderableAssets(ctx, entity);
         }
         return entity;
     }
@@ -155,7 +164,7 @@ export class AssetService {
     async create(ctx: RequestContext, input: CreateAssetInput): Promise<Asset> {
         const { createReadStream, filename, mimetype } = await input.file;
         const stream = createReadStream();
-        const asset = await this.createAssetInternal(stream, filename, mimetype);
+        const asset = await this.createAssetInternal(ctx, stream, filename, mimetype);
         this.eventBus.publish(new AssetEvent(ctx, asset, 'created'));
         return asset;
     }
@@ -168,20 +177,20 @@ export class AssetService {
             input.focalPoint.y = to3dp(input.focalPoint.y);
         }
         patchEntity(asset, input);
-        const updatedAsset = await this.connection.getRepository(Asset).save(asset);
+        const updatedAsset = await this.connection.getRepository(ctx, Asset).save(asset);
         this.eventBus.publish(new AssetEvent(ctx, updatedAsset, 'updated'));
         return updatedAsset;
     }
 
     async delete(ctx: RequestContext, ids: ID[], force: boolean = false): Promise<DeletionResponse> {
-        const assets = await this.connection.getRepository(Asset).findByIds(ids);
+        const assets = await this.connection.getRepository(ctx, Asset).findByIds(ids);
         const usageCount = {
             products: 0,
             variants: 0,
             collections: 0,
         };
         for (const asset of assets) {
-            const usages = await this.findAssetUsages(asset);
+            const usages = await this.findAssetUsages(ctx, asset);
             usageCount.products += usages.products.length;
             usageCount.variants += usages.variants.length;
             usageCount.collections += usages.collections.length;
@@ -202,7 +211,7 @@ export class AssetService {
             // Create a new asset so that the id is still available
             // after deletion (the .remove() method sets it to undefined)
             const deletedAsset = new Asset(asset);
-            await this.connection.getRepository(Asset).remove(asset);
+            await this.connection.getRepository(ctx, Asset).remove(asset);
             try {
                 await this.configService.assetOptions.assetStorageStrategy.deleteFile(asset.source);
                 await this.configService.assetOptions.assetStorageStrategy.deleteFile(asset.preview);
@@ -224,13 +233,18 @@ export class AssetService {
         if (typeof filePath === 'string') {
             const filename = path.basename(filePath);
             const mimetype = mime.lookup(filename) || 'application/octet-stream';
-            return this.createAssetInternal(stream, filename, mimetype);
+            return this.createAssetInternal(RequestContext.empty(), stream, filename, mimetype);
         } else {
             throw new InternalServerError(`error.path-should-be-a-string-got-buffer`);
         }
     }
 
-    private async createAssetInternal(stream: Stream, filename: string, mimetype: string): Promise<Asset> {
+    private async createAssetInternal(
+        ctx: RequestContext,
+        stream: Stream,
+        filename: string,
+        mimetype: string,
+    ): Promise<Asset> {
         const { assetOptions } = this.configService;
         if (!this.validateMimeType(mimetype)) {
             throw new UserInputError('error.mime-type-not-permitted', { mimetype });
@@ -266,7 +280,7 @@ export class AssetService {
             preview: previewFileIdentifier,
             focalPoint: null,
         });
-        return this.connection.getRepository(Asset).save(asset);
+        return this.connection.getRepository(ctx, Asset).save(asset);
     }
 
     private async getSourceFileName(fileName: string): Promise<string> {
@@ -305,14 +319,23 @@ export class AssetService {
         }
     }
 
-    private createOrderableAssets(entity: EntityWithAssets, assets: Asset[]): Promise<OrderableAsset[]> {
-        const orderableAssets = assets.map((asset, i) => this.getOrderableAsset(entity, asset, i));
-        return this.connection.getRepository(orderableAssets[0].constructor).save(orderableAssets);
+    private createOrderableAssets(
+        ctx: RequestContext,
+        entity: EntityWithAssets,
+        assets: Asset[],
+    ): Promise<OrderableAsset[]> {
+        const orderableAssets = assets.map((asset, i) => this.getOrderableAsset(ctx, entity, asset, i));
+        return this.connection.getRepository(ctx, orderableAssets[0].constructor).save(orderableAssets);
     }
 
-    private getOrderableAsset(entity: EntityWithAssets, asset: Asset, index: number): OrderableAsset {
+    private getOrderableAsset(
+        ctx: RequestContext,
+        entity: EntityWithAssets,
+        asset: Asset,
+        index: number,
+    ): OrderableAsset {
         const entityIdProperty = this.getHostEntityIdProperty(entity);
-        const orderableAssetType = this.getOrderableAssetType(entity);
+        const orderableAssetType = this.getOrderableAssetType(ctx, entity);
         return new orderableAssetType({
             assetId: asset.id,
             position: index,
@@ -320,17 +343,17 @@ export class AssetService {
         });
     }
 
-    private async removeExistingOrderableAssets(entity: EntityWithAssets) {
+    private async removeExistingOrderableAssets(ctx: RequestContext, entity: EntityWithAssets) {
         const propertyName = this.getHostEntityIdProperty(entity);
-        const orderableAssetType = this.getOrderableAssetType(entity);
-        await this.connection.getRepository(orderableAssetType).delete({
+        const orderableAssetType = this.getOrderableAssetType(ctx, entity);
+        await this.connection.getRepository(ctx, orderableAssetType).delete({
             [propertyName]: entity.id,
         });
     }
 
-    private getOrderableAssetType(entity: EntityWithAssets): Type<OrderableAsset> {
+    private getOrderableAssetType(ctx: RequestContext, entity: EntityWithAssets): Type<OrderableAsset> {
         const assetRelation = this.connection
-            .getRepository(entity.constructor)
+            .getRepository(ctx, entity.constructor)
             .metadata.relations.find(r => r.propertyName === 'assets');
         if (!assetRelation || typeof assetRelation.type === 'string') {
             throw new InternalServerError('error.could-not-find-matching-orderable-asset');
@@ -365,21 +388,22 @@ export class AssetService {
      * Find the entities which reference the given Asset as a featuredAsset.
      */
     private async findAssetUsages(
+        ctx: RequestContext,
         asset: Asset,
     ): Promise<{ products: Product[]; variants: ProductVariant[]; collections: Collection[] }> {
-        const products = await this.connection.getRepository(Product).find({
+        const products = await this.connection.getRepository(ctx, Product).find({
             where: {
                 featuredAsset: asset,
                 deletedAt: null,
             },
         });
-        const variants = await this.connection.getRepository(ProductVariant).find({
+        const variants = await this.connection.getRepository(ctx, ProductVariant).find({
             where: {
                 featuredAsset: asset,
                 deletedAt: null,
             },
         });
-        const collections = await this.connection.getRepository(Collection).find({
+        const collections = await this.connection.getRepository(ctx, Collection).find({
             where: {
                 featuredAsset: asset,
             },

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

@@ -66,7 +66,7 @@ export class AuthService {
     ): Promise<AuthenticatedSession> {
         if (!user.roles || !user.roles[0]?.channels) {
             const userWithRoles = await this.connection
-                .getRepository(User)
+                .getRepository(ctx, User)
                 .createQueryBuilder('user')
                 .leftJoinAndSelect('user.roles', 'role')
                 .leftJoinAndSelect('role.channels', 'channel')
@@ -79,10 +79,10 @@ export class AuthService {
             throw new NotVerifiedError();
         }
         if (ctx.session && ctx.session.activeOrderId) {
-            await this.sessionService.deleteSessionsByActiveOrderId(ctx.session.activeOrderId);
+            await this.sessionService.deleteSessionsByActiveOrderId(ctx, ctx.session.activeOrderId);
         }
         user.lastLogin = new Date();
-        await this.connection.getRepository(User).save(user, { reload: false });
+        await this.connection.getRepository(ctx, User).save(user, { reload: false });
         const session = await this.sessionService.createNewAuthenticatedSession(
             ctx,
             user,
@@ -111,7 +111,7 @@ export class AuthService {
      * Deletes all sessions for the user associated with the given session token.
      */
     async destroyAuthenticatedSession(ctx: RequestContext, sessionToken: string): Promise<void> {
-        const session = await this.connection.getRepository(AuthenticatedSession).findOne({
+        const session = await this.connection.getRepository(ctx, AuthenticatedSession).findOne({
             where: { token: sessionToken },
             relations: ['user', 'user.authenticationMethods'],
         });
@@ -125,7 +125,7 @@ export class AuthService {
                 await authenticationStrategy.onLogOut(session.user);
             }
             this.eventBus.publish(new LogoutEvent(ctx));
-            return this.sessionService.deleteSessionsByUser(session.user);
+            return this.sessionService.deleteSessionsByUser(ctx, session.user);
         }
     }
 

+ 20 - 18
packages/core/src/service/services/channel.service.ts

@@ -63,6 +63,7 @@ export class ChannelService {
      * Assigns the entity to the given Channels and saves.
      */
     async assignToChannels<T extends ChannelAware & VendureEntity>(
+        ctx: RequestContext,
         entityType: Type<T>,
         entityId: ID,
         channelIds: ID[],
@@ -74,7 +75,7 @@ export class ChannelService {
             const channel = await getEntityOrThrow(this.connection, Channel, id);
             entity.channels.push(channel);
         }
-        await this.connection.getRepository(entityType).save(entity as any, { reload: false });
+        await this.connection.getRepository(ctx, entityType).save(entity as any, { reload: false });
         return entity;
     }
 
@@ -82,6 +83,7 @@ export class ChannelService {
      * Removes the entity from the given Channels and saves.
      */
     async removeFromChannels<T extends ChannelAware & VendureEntity>(
+        ctx: RequestContext,
         entityType: Type<T>,
         entityId: ID,
         channelIds: ID[],
@@ -92,7 +94,7 @@ export class ChannelService {
         for (const id of channelIds) {
             entity.channels = entity.channels.filter(c => !idsAreEqual(c.id, id));
         }
-        await this.connection.getRepository(entityType).save(entity as any, { reload: false });
+        await this.connection.getRepository(ctx, entityType).save(entity as any, { reload: false });
         return entity;
     }
 
@@ -123,19 +125,19 @@ export class ChannelService {
         return defaultChannel;
     }
 
-    findAll(): Promise<Channel[]> {
+    findAll(ctx: RequestContext): Promise<Channel[]> {
         return this.connection
-            .getRepository(Channel)
+            .getRepository(ctx, Channel)
             .find({ relations: ['defaultShippingZone', 'defaultTaxZone'] });
     }
 
-    findOne(id: ID): Promise<Channel | undefined> {
+    findOne(ctx: RequestContext, id: ID): Promise<Channel | undefined> {
         return this.connection
-            .getRepository(Channel)
+            .getRepository(ctx, Channel)
             .findOne(id, { relations: ['defaultShippingZone', 'defaultTaxZone'] });
     }
 
-    async create(input: CreateChannelInput): Promise<Channel> {
+    async create(ctx: RequestContext, input: CreateChannelInput): Promise<Channel> {
         const channel = new Channel(input);
         if (input.defaultTaxZoneId) {
             channel.defaultTaxZone = await getEntityOrThrow(this.connection, Zone, input.defaultTaxZoneId);
@@ -147,19 +149,19 @@ export class ChannelService {
                 input.defaultShippingZoneId,
             );
         }
-        const newChannel = await this.connection.getRepository(Channel).save(channel);
+        const newChannel = await this.connection.getRepository(ctx, Channel).save(channel);
         await this.updateAllChannels();
         return channel;
     }
 
-    async update(input: UpdateChannelInput): Promise<Channel> {
-        const channel = await this.findOne(input.id);
+    async update(ctx: RequestContext, input: UpdateChannelInput): Promise<Channel> {
+        const channel = await this.findOne(ctx, input.id);
         if (!channel) {
             throw new EntityNotFoundError('Channel', input.id);
         }
         if (input.defaultLanguageCode) {
             const availableLanguageCodes = await this.globalSettingsService
-                .getSettings()
+                .getSettings(ctx)
                 .then(s => s.availableLanguages);
             if (!availableLanguageCodes.includes(input.defaultLanguageCode)) {
                 throw new UserInputError('error.language-not-available-in-global-settings', {
@@ -182,15 +184,15 @@ export class ChannelService {
                 input.defaultShippingZoneId,
             );
         }
-        await this.connection.getRepository(Channel).save(updatedChannel, { reload: false });
+        await this.connection.getRepository(ctx, Channel).save(updatedChannel, { reload: false });
         await this.updateAllChannels();
-        return assertFound(this.findOne(channel.id));
+        return assertFound(this.findOne(ctx, channel.id));
     }
 
-    async delete(id: ID): Promise<DeletionResponse> {
+    async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         await getEntityOrThrow(this.connection, Channel, id);
-        await this.connection.getRepository(Channel).delete(id);
-        await this.connection.getRepository(ProductVariantPrice).delete({
+        await this.connection.getRepository(ctx, Channel).delete(id);
+        await this.connection.getRepository(ctx, ProductVariantPrice).delete({
             channelId: id,
         });
         return {
@@ -225,7 +227,7 @@ export class ChannelService {
         }
     }
 
-    private async updateAllChannels() {
-        this.allChannels = await this.findAll();
+    private async updateAllChannels(ctx?: RequestContext) {
+        this.allChannels = await this.findAll(ctx || RequestContext.empty());
     }
 }

+ 25 - 21
packages/core/src/service/services/collection.service.ts

@@ -26,6 +26,7 @@ import { ConfigService } from '../../config/config.service';
 import { Logger } from '../../config/logger/vendure-logger';
 import { CollectionTranslation } from '../../entity/collection/collection-translation.entity';
 import { Collection } from '../../entity/collection/collection.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { CollectionModificationEvent } from '../../event-bus/events/collection-modification-event';
@@ -106,6 +107,7 @@ export class CollectionService implements OnModuleInit {
                 channelId: ctx.channelId,
                 where: { isRoot: false },
                 orderBy: { position: 'ASC' },
+                ctx,
             })
             .getManyAndCount()
             .then(async ([collections, totalItems]) => {
@@ -131,7 +133,7 @@ export class CollectionService implements OnModuleInit {
     }
 
     async findOneBySlug(ctx: RequestContext, slug: string): Promise<Translated<Collection> | undefined> {
-        const translation = await this.connection.getRepository(CollectionTranslation).findOne({
+        const translation = await this.connection.getRepository(ctx, CollectionTranslation).findOne({
             relations: ['base'],
             where: {
                 languageCode: ctx.languageCode,
@@ -155,7 +157,7 @@ export class CollectionService implements OnModuleInit {
                 ? '"child"."parentId"'
                 : 'child.parentId';
         const parent = await this.connection
-            .getRepository(Collection)
+            .getRepository(ctx, Collection)
             .createQueryBuilder('collection')
             .leftJoinAndSelect('collection.translations', 'translation')
             .where(
@@ -195,7 +197,7 @@ export class CollectionService implements OnModuleInit {
         publicOnly: boolean,
     ): Promise<Array<Translated<Collection>>> {
         const qb = this.connection
-            .getRepository(Collection)
+            .getRepository(ctx, Collection)
             .createQueryBuilder('collection')
             .leftJoinAndSelect('collection.translations', 'translation')
             .leftJoin('collection.productVariants', 'variant')
@@ -222,7 +224,7 @@ export class CollectionService implements OnModuleInit {
     ): Promise<Array<Translated<Collection>>> {
         const getChildren = async (id: ID, _descendants: Collection[] = [], depth = 1) => {
             const children = await this.connection
-                .getRepository(Collection)
+                .getRepository(ctx, Collection)
                 .find({ where: { parent: { id } } });
             for (const child of children) {
                 _descendants.push(child);
@@ -249,7 +251,7 @@ export class CollectionService implements OnModuleInit {
     ): Promise<Array<Translated<Collection> | Collection>> {
         const getParent = async (id: ID, _ancestors: Collection[] = []): Promise<Collection[]> => {
             const parent = await this.connection
-                .getRepository(Collection)
+                .getRepository(ctx, Collection)
                 .createQueryBuilder()
                 .relation(Collection, 'parent')
                 .of(id)
@@ -273,8 +275,9 @@ export class CollectionService implements OnModuleInit {
     }
 
     async create(ctx: RequestContext, input: CreateCollectionInput): Promise<Translated<Collection>> {
-        await this.slugValidator.validateSlugs(input, CollectionTranslation);
+        await this.slugValidator.validateSlugs(ctx, input, CollectionTranslation);
         const collection = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: Collection,
             translationType: CollectionTranslation,
@@ -286,10 +289,10 @@ export class CollectionService implements OnModuleInit {
                 }
                 coll.position = await this.getNextPositionInParent(ctx, input.parentId || undefined);
                 coll.filters = this.getCollectionFiltersFromInput(input);
-                await this.assetService.updateFeaturedAsset(coll, input);
+                await this.assetService.updateFeaturedAsset(ctx, coll, input);
             },
         });
-        await this.assetService.updateEntityAssets(collection, input);
+        await this.assetService.updateEntityAssets(ctx, collection, input);
         this.applyFiltersQueue.add({
             ctx: ctx.serialize(),
             collectionIds: [collection.id],
@@ -298,8 +301,9 @@ export class CollectionService implements OnModuleInit {
     }
 
     async update(ctx: RequestContext, input: UpdateCollectionInput): Promise<Translated<Collection>> {
-        await this.slugValidator.validateSlugs(input, CollectionTranslation);
+        await this.slugValidator.validateSlugs(ctx, input, CollectionTranslation);
         const collection = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: Collection,
             translationType: CollectionTranslation,
@@ -307,8 +311,8 @@ export class CollectionService implements OnModuleInit {
                 if (input.filters) {
                     coll.filters = this.getCollectionFiltersFromInput(input);
                 }
-                await this.assetService.updateFeaturedAsset(coll, input);
-                await this.assetService.updateEntityAssets(coll, input);
+                await this.assetService.updateFeaturedAsset(ctx, coll, input);
+                await this.assetService.updateEntityAssets(ctx, coll, input);
             },
         });
         if (input.filters) {
@@ -325,7 +329,7 @@ export class CollectionService implements OnModuleInit {
         const descendants = await this.getDescendants(ctx, collection.id);
         for (const coll of [...descendants.reverse(), collection]) {
             const affectedVariantIds = await this.getCollectionProductVariantIds(coll);
-            await this.connection.getRepository(Collection).remove(coll);
+            await this.connection.getRepository(ctx, Collection).remove(coll);
             this.eventBus.publish(new CollectionModificationEvent(ctx, coll, affectedVariantIds));
         }
         return {
@@ -353,7 +357,7 @@ export class CollectionService implements OnModuleInit {
         }
 
         let siblings = await this.connection
-            .getRepository(Collection)
+            .getRepository(ctx, Collection)
             .createQueryBuilder('collection')
             .leftJoin('collection.parent', 'parent')
             .where('parent.id = :id', { id: input.parentId })
@@ -364,7 +368,7 @@ export class CollectionService implements OnModuleInit {
         }
         siblings = moveToIndex(input.index, target, siblings);
 
-        await this.connection.getRepository(Collection).save(siblings);
+        await this.connection.getRepository(ctx, Collection).save(siblings);
         this.applyFiltersQueue.add({
             ctx: ctx.serialize(),
             collectionIds: [target.id],
@@ -430,12 +434,12 @@ export class CollectionService implements OnModuleInit {
     /**
      * Returns the IDs of the Collection's ProductVariants.
      */
-    async getCollectionProductVariantIds(collection: Collection): Promise<ID[]> {
+    async getCollectionProductVariantIds(collection: Collection, ctx?: RequestContext): Promise<ID[]> {
         if (collection.productVariants) {
             return collection.productVariants.map(v => v.id);
         } else {
             const productVariants = await this.connection
-                .getRepository(ProductVariant)
+                .getRepository(ctx, ProductVariant)
                 .createQueryBuilder('variant')
                 .innerJoin('variant.collections', 'collection', 'collection.id = :id', { id: collection.id })
                 .getMany();
@@ -449,7 +453,7 @@ export class CollectionService implements OnModuleInit {
     private async getNextPositionInParent(ctx: RequestContext, maybeParentId?: ID): Promise<number> {
         const parentId = maybeParentId || (await this.getRootCollection(ctx)).id;
         const result = await this.connection
-            .getRepository(Collection)
+            .getRepository(ctx, Collection)
             .createQueryBuilder('collection')
             .leftJoin('collection.parent', 'parent')
             .select('MAX(collection.position)', 'index')
@@ -464,7 +468,7 @@ export class CollectionService implements OnModuleInit {
     ): Promise<Collection | undefined> {
         if (parentId) {
             return this.connection
-                .getRepository(Collection)
+                .getRepository(ctx, Collection)
                 .createQueryBuilder('collection')
                 .leftJoin('collection.channels', 'channel')
                 .where('collection.id = :id', { id: parentId })
@@ -483,7 +487,7 @@ export class CollectionService implements OnModuleInit {
         }
 
         const existingRoot = await this.connection
-            .getRepository(Collection)
+            .getRepository(ctx, Collection)
             .createQueryBuilder('collection')
             .leftJoin('collection.channels', 'channel')
             .leftJoinAndSelect('collection.translations', 'translation')
@@ -496,7 +500,7 @@ export class CollectionService implements OnModuleInit {
             return this.rootCollection;
         }
 
-        const rootTranslation = await this.connection.getRepository(CollectionTranslation).save(
+        const rootTranslation = await this.connection.getRepository(ctx, CollectionTranslation).save(
             new CollectionTranslation({
                 languageCode: this.configService.defaultLanguageCode,
                 name: ROOT_COLLECTION_NAME,
@@ -513,7 +517,7 @@ export class CollectionService implements OnModuleInit {
             filters: [],
         });
 
-        await this.connection.getRepository(Collection).save(newRoot);
+        await this.connection.getRepository(ctx, Collection).save(newRoot);
         this.rootCollection = newRoot;
         return newRoot;
     }

+ 8 - 6
packages/core/src/service/services/country.service.ts

@@ -12,7 +12,7 @@ import { UserInputError } from '../../common/error/errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { Translated } from '../../common/types/locale-types';
 import { assertFound } from '../../common/utils';
-import { Address } from '../../entity';
+import { Address, Collection, ProductOptionGroup } from '../../entity';
 import { CountryTranslation } from '../../entity/country/country-translation.entity';
 import { Country } from '../../entity/country/country.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
@@ -37,7 +37,7 @@ export class CountryService {
         options?: ListQueryOptions<Country>,
     ): Promise<PaginatedList<Translated<Country>>> {
         return this.listQueryBuilder
-            .build(Country, options)
+            .build(Country, options, { ctx })
             .getManyAndCount()
             .then(([countries, totalItems]) => {
                 const items = countries.map(country => translateDeep(country, ctx.languageCode));
@@ -50,13 +50,13 @@ export class CountryService {
 
     findOne(ctx: RequestContext, countryId: ID): Promise<Translated<Country> | undefined> {
         return this.connection
-            .getRepository(Country)
+            .getRepository(ctx, Country)
             .findOne(countryId)
             .then(country => country && translateDeep(country, ctx.languageCode));
     }
 
     async findOneByCode(ctx: RequestContext, countryCode: string): Promise<Translated<Country>> {
-        const country = await this.connection.getRepository(Country).findOne({
+        const country = await this.connection.getRepository(ctx, Country).findOne({
             where: {
                 code: countryCode,
             },
@@ -69,6 +69,7 @@ export class CountryService {
 
     async create(ctx: RequestContext, input: CreateCountryInput): Promise<Translated<Country>> {
         const country = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: Country,
             translationType: CountryTranslation,
@@ -79,6 +80,7 @@ export class CountryService {
 
     async update(ctx: RequestContext, input: UpdateCountryInput): Promise<Translated<Country>> {
         const country = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: Country,
             translationType: CountryTranslation,
@@ -90,7 +92,7 @@ export class CountryService {
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const country = await getEntityOrThrow(this.connection, Country, id);
         const addressesUsingCountry = await this.connection
-            .getRepository(Address)
+            .getRepository(ctx, Address)
             .createQueryBuilder('address')
             .where('address.country = :id', { id })
             .getCount();
@@ -102,7 +104,7 @@ export class CountryService {
             };
         } else {
             await this.zoneService.updateZonesCache();
-            await this.connection.getRepository(Country).remove(country);
+            await this.connection.getRepository(ctx, Country).remove(country);
             return {
                 result: DeletionResult.DELETED,
                 message: '',

+ 29 - 23
packages/core/src/service/services/customer-group.service.ts

@@ -15,8 +15,10 @@ import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { RequestContext } from '../../api/common/request-context';
 import { UserInputError } from '../../common/error/errors';
 import { assertFound, idsAreEqual } from '../../common/utils';
+import { Collection } from '../../entity/collection/collection.entity';
 import { CustomerGroup } from '../../entity/customer-group/customer-group.entity';
 import { Customer } from '../../entity/customer/customer.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
@@ -32,20 +34,24 @@ export class CustomerGroupService {
         private historyService: HistoryService,
     ) {}
 
-    findAll(options?: CustomerGroupListOptions): Promise<PaginatedList<CustomerGroup>> {
+    findAll(ctx: RequestContext, options?: CustomerGroupListOptions): Promise<PaginatedList<CustomerGroup>> {
         return this.listQueryBuilder
-            .build(CustomerGroup, options)
+            .build(CustomerGroup, options, { ctx })
             .getManyAndCount()
             .then(([items, totalItems]) => ({ items, totalItems }));
     }
 
-    findOne(customerGroupId: ID): Promise<CustomerGroup | undefined> {
-        return this.connection.getRepository(CustomerGroup).findOne(customerGroupId);
+    findOne(ctx: RequestContext, customerGroupId: ID): Promise<CustomerGroup | undefined> {
+        return this.connection.getRepository(ctx, CustomerGroup).findOne(customerGroupId);
     }
 
-    getGroupCustomers(customerGroupId: ID, options?: CustomerListOptions): Promise<PaginatedList<Customer>> {
+    getGroupCustomers(
+        ctx: RequestContext,
+        customerGroupId: ID,
+        options?: CustomerListOptions,
+    ): Promise<PaginatedList<Customer>> {
         return this.listQueryBuilder
-            .build(Customer, options)
+            .build(Customer, options, { ctx })
             .leftJoin('customer.groups', 'group')
             .andWhere('group.id = :groupId', { groupId: customerGroupId })
             .getManyAndCount()
@@ -55,9 +61,9 @@ export class CustomerGroupService {
     async create(ctx: RequestContext, input: CreateCustomerGroupInput): Promise<CustomerGroup> {
         const customerGroup = new CustomerGroup(input);
 
-        const newCustomerGroup = await this.connection.getRepository(CustomerGroup).save(customerGroup);
+        const newCustomerGroup = await this.connection.getRepository(ctx, CustomerGroup).save(customerGroup);
         if (input.customerIds) {
-            const customers = await this.getCustomersFromIds(input.customerIds);
+            const customers = await this.getCustomersFromIds(ctx, input.customerIds);
             for (const customer of customers) {
                 customer.groups = [...(customer.groups || []), newCustomerGroup];
                 await this.historyService.createHistoryEntryForCustomer({
@@ -69,22 +75,22 @@ export class CustomerGroupService {
                     },
                 });
             }
-            await this.connection.getRepository(Customer).save(customers);
+            await this.connection.getRepository(ctx, Customer).save(customers);
         }
-        return assertFound(this.findOne(newCustomerGroup.id));
+        return assertFound(this.findOne(ctx, newCustomerGroup.id));
     }
 
-    async update(input: UpdateCustomerGroupInput): Promise<CustomerGroup> {
+    async update(ctx: RequestContext, input: UpdateCustomerGroupInput): Promise<CustomerGroup> {
         const customerGroup = await getEntityOrThrow(this.connection, CustomerGroup, input.id);
         const updatedCustomerGroup = patchEntity(customerGroup, input);
-        await this.connection.getRepository(CustomerGroup).save(updatedCustomerGroup, { reload: false });
-        return assertFound(this.findOne(customerGroup.id));
+        await this.connection.getRepository(ctx, CustomerGroup).save(updatedCustomerGroup, { reload: false });
+        return assertFound(this.findOne(ctx, customerGroup.id));
     }
 
-    async delete(id: ID): Promise<DeletionResponse> {
+    async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const group = await getEntityOrThrow(this.connection, CustomerGroup, id);
         try {
-            await this.connection.getRepository(CustomerGroup).remove(group);
+            await this.connection.getRepository(ctx, CustomerGroup).remove(group);
             return {
                 result: DeletionResult.DELETED,
             };
@@ -100,7 +106,7 @@ export class CustomerGroupService {
         ctx: RequestContext,
         input: MutationAddCustomersToGroupArgs,
     ): Promise<CustomerGroup> {
-        const customers = await this.getCustomersFromIds(input.customerIds);
+        const customers = await this.getCustomersFromIds(ctx, input.customerIds);
         const group = await getEntityOrThrow(this.connection, CustomerGroup, input.customerGroupId);
         for (const customer of customers) {
             if (!customer.groups.map(g => g.id).includes(input.customerGroupId)) {
@@ -116,15 +122,15 @@ export class CustomerGroupService {
             }
         }
 
-        await this.connection.getRepository(Customer).save(customers, { reload: false });
-        return assertFound(this.findOne(group.id));
+        await this.connection.getRepository(ctx, Customer).save(customers, { reload: false });
+        return assertFound(this.findOne(ctx, group.id));
     }
 
     async removeCustomersFromGroup(
         ctx: RequestContext,
         input: MutationRemoveCustomersFromGroupArgs,
     ): Promise<CustomerGroup> {
-        const customers = await this.getCustomersFromIds(input.customerIds);
+        const customers = await this.getCustomersFromIds(ctx, input.customerIds);
         const group = await getEntityOrThrow(this.connection, CustomerGroup, input.customerGroupId);
         for (const customer of customers) {
             if (!customer.groups.map(g => g.id).includes(input.customerGroupId)) {
@@ -140,13 +146,13 @@ export class CustomerGroupService {
                 },
             });
         }
-        await this.connection.getRepository(Customer).save(customers, { reload: false });
-        return assertFound(this.findOne(group.id));
+        await this.connection.getRepository(ctx, Customer).save(customers, { reload: false });
+        return assertFound(this.findOne(ctx, group.id));
     }
 
-    private getCustomersFromIds(ids: ID[]): Promise<Customer[]> {
+    private getCustomersFromIds(ctx: RequestContext, ids: ID[]): Promise<Customer[]> {
         return this.connection
-            .getRepository(Customer)
+            .getRepository(ctx, Customer)
             .findByIds(ids, { where: { deletedAt: null }, relations: ['groups'] });
     }
 }

+ 79 - 60
packages/core/src/service/services/customer.service.ts

@@ -26,6 +26,7 @@ import { NATIVE_AUTH_STRATEGY_NAME } from '../../config/auth/native-authenticati
 import { ConfigService } from '../../config/config.service';
 import { Address } from '../../entity/address/address.entity';
 import { NativeAuthenticationMethod } from '../../entity/authentication-method/native-authentication-method.entity';
+import { Collection } from '../../entity/collection/collection.entity';
 import { CustomerGroup } from '../../entity/customer-group/customer-group.entity';
 import { Customer } from '../../entity/customer/customer.entity';
 import { HistoryEntry } from '../../entity/history-entry/history-entry.entity';
@@ -58,19 +59,22 @@ export class CustomerService {
         private historyService: HistoryService,
     ) {}
 
-    findAll(options: ListQueryOptions<Customer> | undefined): Promise<PaginatedList<Customer>> {
+    findAll(
+        ctx: RequestContext,
+        options: ListQueryOptions<Customer> | undefined,
+    ): Promise<PaginatedList<Customer>> {
         return this.listQueryBuilder
-            .build(Customer, options, { where: { deletedAt: null } })
+            .build(Customer, options, { where: { deletedAt: null }, ctx })
             .getManyAndCount()
             .then(([items, totalItems]) => ({ items, totalItems }));
     }
 
-    findOne(id: ID): Promise<Customer | undefined> {
-        return this.connection.getRepository(Customer).findOne(id, { where: { deletedAt: null } });
+    findOne(ctx: RequestContext, id: ID): Promise<Customer | undefined> {
+        return this.connection.getRepository(ctx, Customer).findOne(id, { where: { deletedAt: null } });
     }
 
-    findOneByUserId(userId: ID): Promise<Customer | undefined> {
-        return this.connection.getRepository(Customer).findOne({
+    findOneByUserId(ctx: RequestContext, userId: ID): Promise<Customer | undefined> {
+        return this.connection.getRepository(ctx, Customer).findOne({
             where: {
                 user: { id: userId },
                 deletedAt: null,
@@ -80,7 +84,7 @@ export class CustomerService {
 
     findAddressesByCustomerId(ctx: RequestContext, customerId: ID): Promise<Address[]> {
         return this.connection
-            .getRepository(Address)
+            .getRepository(ctx, Address)
             .createQueryBuilder('address')
             .leftJoinAndSelect('address.country', 'country')
             .leftJoinAndSelect('country.translations', 'countryTranslation')
@@ -94,9 +98,9 @@ export class CustomerService {
             });
     }
 
-    async getCustomerGroups(customerId: ID): Promise<CustomerGroup[]> {
+    async getCustomerGroups(ctx: RequestContext | undefined, customerId: ID): Promise<CustomerGroup[]> {
         const customerWithGroups = await this.connection
-            .getRepository(Customer)
+            .getRepository(ctx, Customer)
             .findOne(customerId, { relations: ['groups'] });
         if (customerWithGroups) {
             return customerWithGroups.groups;
@@ -109,13 +113,13 @@ export class CustomerService {
         input.emailAddress = normalizeEmailAddress(input.emailAddress);
         const customer = new Customer(input);
 
-        const existingCustomer = await this.connection.getRepository(Customer).findOne({
+        const existingCustomer = await this.connection.getRepository(ctx, Customer).findOne({
             where: {
                 emailAddress: input.emailAddress,
                 deletedAt: null,
             },
         });
-        const existingUser = await this.connection.getRepository(User).findOne({
+        const existingUser = await this.connection.getRepository(ctx, User).findOne({
             where: {
                 identifier: input.emailAddress,
                 deletedAt: null,
@@ -125,17 +129,17 @@ export class CustomerService {
         if (existingCustomer || existingUser) {
             throw new UserInputError(`error.email-address-must-be-unique`);
         }
-        customer.user = await this.userService.createCustomerUser(input.emailAddress, password);
+        customer.user = await this.userService.createCustomerUser(ctx, input.emailAddress, password);
 
         if (password && password !== '') {
             const verificationToken = customer.user.getNativeAuthenticationMethod().verificationToken;
             if (verificationToken) {
-                customer.user = await this.userService.verifyUserByToken(verificationToken);
+                customer.user = await this.userService.verifyUserByToken(ctx, verificationToken);
             }
         } else {
             this.eventBus.publish(new AccountRegistrationEvent(ctx, customer.user));
         }
-        const createdCustomer = await await this.connection.getRepository(Customer).save(customer);
+        const createdCustomer = await await this.connection.getRepository(ctx, Customer).save(customer);
 
         await this.historyService.createHistoryEntryForCustomer({
             ctx,
@@ -165,7 +169,7 @@ export class CustomerService {
                 throw new UserInputError(`error.missing-password-on-registration`);
             }
         }
-        let user = await this.userService.getUserByEmailAddress(input.emailAddress);
+        let user = await this.userService.getUserByEmailAddress(ctx, input.emailAddress);
         const hasNativeAuthMethod = !!user?.authenticationMethods.find(
             m => m instanceof NativeAuthenticationMethod,
         );
@@ -177,7 +181,7 @@ export class CustomerService {
             }
         }
         const customFields = (input as any).customFields;
-        const customer = await this.createOrUpdate({
+        const customer = await this.createOrUpdate(ctx, {
             emailAddress: input.emailAddress,
             title: input.title || '',
             firstName: input.firstName || '',
@@ -194,20 +198,25 @@ export class CustomerService {
             },
         });
         if (!user) {
-            user = await this.userService.createCustomerUser(input.emailAddress, input.password || undefined);
+            user = await this.userService.createCustomerUser(
+                ctx,
+                input.emailAddress,
+                input.password || undefined,
+            );
         }
         if (!hasNativeAuthMethod) {
             user = await this.userService.addNativeAuthenticationMethod(
+                ctx,
                 user,
                 input.emailAddress,
                 input.password || undefined,
             );
         }
         if (!user.verified) {
-            user = await this.userService.setVerificationToken(user);
+            user = await this.userService.setVerificationToken(ctx, user);
         }
         customer.user = user;
-        await this.connection.getRepository(Customer).save(customer, { reload: false });
+        await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
         if (!user.verified) {
             this.eventBus.publish(new AccountRegistrationEvent(ctx, user));
         } else {
@@ -224,9 +233,9 @@ export class CustomerService {
     }
 
     async refreshVerificationToken(ctx: RequestContext, emailAddress: string): Promise<void> {
-        const user = await this.userService.getUserByEmailAddress(emailAddress);
+        const user = await this.userService.getUserByEmailAddress(ctx, emailAddress);
         if (user) {
-            await this.userService.setVerificationToken(user);
+            await this.userService.setVerificationToken(ctx, user);
             if (!user.verified) {
                 this.eventBus.publish(new AccountRegistrationEvent(ctx, user));
             }
@@ -238,9 +247,9 @@ export class CustomerService {
         verificationToken: string,
         password?: string,
     ): Promise<Customer | undefined> {
-        const user = await this.userService.verifyUserByToken(verificationToken, password);
+        const user = await this.userService.verifyUserByToken(ctx, verificationToken, password);
         if (user) {
-            const customer = await this.findOneByUserId(user.id);
+            const customer = await this.findOneByUserId(ctx, user.id);
             if (!customer) {
                 throw new InternalServerError('error.cannot-locate-customer-for-user');
             }
@@ -252,15 +261,15 @@ export class CustomerService {
                     strategy: NATIVE_AUTH_STRATEGY_NAME,
                 },
             });
-            return this.findOneByUserId(user.id);
+            return this.findOneByUserId(ctx, user.id);
         }
     }
 
     async requestPasswordReset(ctx: RequestContext, emailAddress: string): Promise<void> {
-        const user = await this.userService.setPasswordResetToken(emailAddress);
+        const user = await this.userService.setPasswordResetToken(ctx, emailAddress);
         if (user) {
             this.eventBus.publish(new PasswordResetEvent(ctx, user));
-            const customer = await this.findOneByUserId(user.id);
+            const customer = await this.findOneByUserId(ctx, user.id);
             if (!customer) {
                 throw new InternalServerError('error.cannot-locate-customer-for-user');
             }
@@ -278,9 +287,9 @@ export class CustomerService {
         passwordResetToken: string,
         password: string,
     ): Promise<Customer | undefined> {
-        const user = await this.userService.resetPasswordByToken(passwordResetToken, password);
+        const user = await this.userService.resetPasswordByToken(ctx, passwordResetToken, password);
         if (user) {
-            const customer = await this.findOneByUserId(user.id);
+            const customer = await this.findOneByUserId(ctx, user.id);
             if (!customer) {
                 throw new InternalServerError('error.cannot-locate-customer-for-user');
             }
@@ -299,15 +308,18 @@ export class CustomerService {
         userId: ID,
         newEmailAddress: string,
     ): Promise<boolean> {
-        const userWithConflictingIdentifier = await this.userService.getUserByEmailAddress(newEmailAddress);
+        const userWithConflictingIdentifier = await this.userService.getUserByEmailAddress(
+            ctx,
+            newEmailAddress,
+        );
         if (userWithConflictingIdentifier) {
             throw new UserInputError('error.email-address-not-available');
         }
-        const user = await this.userService.getUserById(userId);
+        const user = await this.userService.getUserById(ctx, userId);
         if (!user) {
             return false;
         }
-        const customer = await this.findOneByUserId(user.id);
+        const customer = await this.findOneByUserId(ctx, user.id);
         if (!customer) {
             return false;
         }
@@ -323,15 +335,15 @@ export class CustomerService {
         });
         if (this.configService.authOptions.requireVerification) {
             user.getNativeAuthenticationMethod().pendingIdentifier = newEmailAddress;
-            await this.userService.setIdentifierChangeToken(user);
+            await this.userService.setIdentifierChangeToken(ctx, user);
             this.eventBus.publish(new IdentifierChangeRequestEvent(ctx, user));
             return true;
         } else {
             const oldIdentifier = user.identifier;
             user.identifier = newEmailAddress;
             customer.emailAddress = newEmailAddress;
-            await this.connection.getRepository(User).save(user, { reload: false });
-            await this.connection.getRepository(Customer).save(customer, { reload: false });
+            await this.connection.getRepository(ctx, User).save(user, { reload: false });
+            await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
             this.eventBus.publish(new IdentifierChangeEvent(ctx, user, oldIdentifier));
             await this.historyService.createHistoryEntryForCustomer({
                 customerId: customer.id,
@@ -347,17 +359,17 @@ export class CustomerService {
     }
 
     async updateEmailAddress(ctx: RequestContext, token: string): Promise<boolean> {
-        const { user, oldIdentifier } = await this.userService.changeIdentifierByToken(token);
+        const { user, oldIdentifier } = await this.userService.changeIdentifierByToken(ctx, token);
         if (!user) {
             return false;
         }
-        const customer = await this.findOneByUserId(user.id);
+        const customer = await this.findOneByUserId(ctx, user.id);
         if (!customer) {
             return false;
         }
         this.eventBus.publish(new IdentifierChangeEvent(ctx, user, oldIdentifier));
         customer.emailAddress = user.identifier;
-        await this.connection.getRepository(Customer).save(customer, { reload: false });
+        await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
         await this.historyService.createHistoryEntryForCustomer({
             customerId: customer.id,
             ctx,
@@ -373,7 +385,7 @@ export class CustomerService {
     async update(ctx: RequestContext, input: UpdateCustomerInput): Promise<Customer> {
         const customer = await getEntityOrThrow(this.connection, Customer, input.id);
         const updatedCustomer = patchEntity(customer, input);
-        await this.connection.getRepository(Customer).save(customer, { reload: false });
+        await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
         await this.historyService.createHistoryEntryForCustomer({
             customerId: customer.id,
             ctx,
@@ -382,19 +394,20 @@ export class CustomerService {
                 input,
             },
         });
-        return assertFound(this.findOne(customer.id));
+        return assertFound(this.findOne(ctx, customer.id));
     }
 
     /**
      * For guest checkouts, we assume that a matching email address is the same customer.
      */
     async createOrUpdate(
+        ctx: RequestContext,
         input: Partial<CreateCustomerInput> & { emailAddress: string },
         throwOnExistingUser: boolean = false,
     ): Promise<Customer> {
         input.emailAddress = normalizeEmailAddress(input.emailAddress);
         let customer: Customer;
-        const existing = await this.connection.getRepository(Customer).findOne({
+        const existing = await this.connection.getRepository(ctx, Customer).findOne({
             where: {
                 emailAddress: input.emailAddress,
                 deletedAt: null,
@@ -409,11 +422,11 @@ export class CustomerService {
         } else {
             customer = new Customer(input);
         }
-        return this.connection.getRepository(Customer).save(customer);
+        return this.connection.getRepository(ctx, Customer).save(customer);
     }
 
     async createAddress(ctx: RequestContext, customerId: ID, input: CreateAddressInput): Promise<Address> {
-        const customer = await this.connection.getRepository(Customer).findOne(customerId, {
+        const customer = await this.connection.getRepository(ctx, Customer).findOne(customerId, {
             where: { deletedAt: null },
             relations: ['addresses'],
         });
@@ -426,10 +439,10 @@ export class CustomerService {
             ...input,
             country,
         });
-        const createdAddress = await this.connection.getRepository(Address).save(address);
+        const createdAddress = await this.connection.getRepository(ctx, Address).save(address);
         customer.addresses.push(createdAddress);
-        await this.connection.getRepository(Customer).save(customer, { reload: false });
-        await this.enforceSingleDefaultAddress(createdAddress.id, input);
+        await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
+        await this.enforceSingleDefaultAddress(ctx, createdAddress.id, input);
         await this.historyService.createHistoryEntryForCustomer({
             customerId: customer.id,
             ctx,
@@ -449,8 +462,8 @@ export class CustomerService {
             address.country = translateDeep(address.country, ctx.languageCode);
         }
         let updatedAddress = patchEntity(address, input);
-        updatedAddress = await this.connection.getRepository(Address).save(updatedAddress);
-        await this.enforceSingleDefaultAddress(input.id, input);
+        updatedAddress = await this.connection.getRepository(ctx, Address).save(updatedAddress);
+        await this.enforceSingleDefaultAddress(ctx, input.id, input);
 
         await this.historyService.createHistoryEntryForCustomer({
             customerId: address.customer.id,
@@ -469,7 +482,7 @@ export class CustomerService {
             relations: ['customer', 'country'],
         });
         address.country = translateDeep(address.country, ctx.languageCode);
-        await this.reassignDefaultsForDeletedAddress(address);
+        await this.reassignDefaultsForDeletedAddress(ctx, address);
         await this.historyService.createHistoryEntryForCustomer({
             customerId: address.customer.id,
             ctx,
@@ -478,15 +491,17 @@ export class CustomerService {
                 address: addressToLine(address),
             },
         });
-        await this.connection.getRepository(Address).remove(address);
+        await this.connection.getRepository(ctx, Address).remove(address);
         return true;
     }
 
-    async softDelete(customerId: ID): Promise<DeletionResponse> {
+    async softDelete(ctx: RequestContext, customerId: ID): Promise<DeletionResponse> {
         const customer = await getEntityOrThrow(this.connection, Customer, customerId);
-        await this.connection.getRepository(Customer).update({ id: customerId }, { deletedAt: new Date() });
+        await this.connection
+            .getRepository(ctx, Customer)
+            .update({ id: customerId }, { deletedAt: new Date() });
         // tslint:disable-next-line:no-non-null-assertion
-        await this.userService.softDelete(customer.user!.id);
+        await this.userService.softDelete(ctx, customer.user!.id);
         return {
             result: DeletionResult.DELETED,
         };
@@ -519,7 +534,7 @@ export class CustomerService {
 
     async deleteCustomerNote(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         try {
-            await this.historyService.deleteCustomerHistoryEntry(id);
+            await this.historyService.deleteCustomerHistoryEntry(ctx, id);
             return {
                 result: DeletionResult.DELETED,
             };
@@ -531,9 +546,13 @@ export class CustomerService {
         }
     }
 
-    private async enforceSingleDefaultAddress(addressId: ID, input: CreateAddressInput | UpdateAddressInput) {
+    private async enforceSingleDefaultAddress(
+        ctx: RequestContext,
+        addressId: ID,
+        input: CreateAddressInput | UpdateAddressInput,
+    ) {
         const result = await this.connection
-            .getRepository(Address)
+            .getRepository(ctx, Address)
             .findOne(addressId, { relations: ['customer', 'customer.addresses'] });
         if (result) {
             const customerAddressIds = result.customer.addresses
@@ -542,12 +561,12 @@ export class CustomerService {
 
             if (customerAddressIds.length) {
                 if (input.defaultBillingAddress === true) {
-                    await this.connection.getRepository(Address).update(customerAddressIds, {
+                    await this.connection.getRepository(ctx, Address).update(customerAddressIds, {
                         defaultBillingAddress: false,
                     });
                 }
                 if (input.defaultShippingAddress === true) {
-                    await this.connection.getRepository(Address).update(customerAddressIds, {
+                    await this.connection.getRepository(ctx, Address).update(customerAddressIds, {
                         defaultShippingAddress: false,
                     });
                 }
@@ -560,12 +579,12 @@ export class CustomerService {
      * billing. If so, attempt to transfer default status to one of the other addresses if there are
      * any.
      */
-    private async reassignDefaultsForDeletedAddress(addressToDelete: Address) {
+    private async reassignDefaultsForDeletedAddress(ctx: RequestContext, addressToDelete: Address) {
         if (!addressToDelete.defaultBillingAddress && !addressToDelete.defaultShippingAddress) {
             return;
         }
         const result = await this.connection
-            .getRepository(Address)
+            .getRepository(ctx, Address)
             .findOne(addressToDelete.id, { relations: ['customer', 'customer.addresses'] });
         if (result) {
             const customerAddresses = result.customer.addresses;
@@ -579,7 +598,7 @@ export class CustomerService {
                 if (addressToDelete.defaultBillingAddress) {
                     otherAddresses[0].defaultBillingAddress = true;
                 }
-                await this.connection.getRepository(Address).save(otherAddresses[0], { reload: false });
+                await this.connection.getRepository(ctx, Address).save(otherAddresses[0], { reload: false });
             }
         }
     }

+ 27 - 27
packages/core/src/service/services/facet-value.service.ts

@@ -13,7 +13,7 @@ import { RequestContext } from '../../api/common/request-context';
 import { Translated } from '../../common/types/locale-types';
 import { assertFound } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
-import { Product, ProductVariant } from '../../entity';
+import { Collection, Product, ProductVariant } from '../../entity';
 import { FacetValueTranslation } from '../../entity/facet-value/facet-value-translation.entity';
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../entity/facet/facet.entity';
@@ -39,65 +39,62 @@ export class FacetValueService {
             .then(facetValues => facetValues.map(facetValue => translateDeep(facetValue, lang, ['facet'])));
     }
 
-    findOne(id: ID, lang: LanguageCode): Promise<Translated<FacetValue> | undefined> {
+    findOne(ctx: RequestContext, id: ID): Promise<Translated<FacetValue> | undefined> {
         return this.connection
-            .getRepository(FacetValue)
+            .getRepository(ctx, FacetValue)
             .findOne(id, {
                 relations: ['facet'],
             })
-            .then(facetValue => facetValue && translateDeep(facetValue, lang, ['facet']));
+            .then(facetValue => facetValue && translateDeep(facetValue, ctx.languageCode, ['facet']));
     }
 
-    findByIds(ids: ID[]): Promise<FacetValue[]>;
-    findByIds(ids: ID[], lang: LanguageCode): Promise<Array<Translated<FacetValue>>>;
-    findByIds(ids: ID[], lang?: LanguageCode): Promise<FacetValue[]> {
+    findByIds(ctx: RequestContext, ids: ID[]): Promise<Array<Translated<FacetValue>>> {
         const facetValues = this.connection
-            .getRepository(FacetValue)
+            .getRepository(ctx, FacetValue)
             .findByIds(ids, { relations: ['facet'] });
-        if (lang) {
-            return facetValues.then(values =>
-                values.map(facetValue => translateDeep(facetValue, lang, ['facet'])),
-            );
-        } else {
-            return facetValues;
-        }
+        return facetValues.then(values =>
+            values.map(facetValue => translateDeep(facetValue, ctx.languageCode, ['facet'])),
+        );
     }
 
-    findByFacetId(id: ID, lang: LanguageCode): Promise<Array<Translated<FacetValue>>> {
+    findByFacetId(ctx: RequestContext, id: ID): Promise<Array<Translated<FacetValue>>> {
         return this.connection
-            .getRepository(FacetValue)
+            .getRepository(ctx, FacetValue)
             .find({
                 where: {
                     facet: { id },
                 },
             })
-            .then(values => values.map(facetValue => translateDeep(facetValue, lang)));
+            .then(values => values.map(facetValue => translateDeep(facetValue, ctx.languageCode)));
     }
 
     async create(
+        ctx: RequestContext,
         facet: Facet,
         input: CreateFacetValueInput | CreateFacetValueWithFacetInput,
     ): Promise<Translated<FacetValue>> {
         const facetValue = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: FacetValue,
             translationType: FacetValueTranslation,
             beforeSave: fv => (fv.facet = facet),
         });
-        return assertFound(this.findOne(facetValue.id, this.configService.defaultLanguageCode));
+        return assertFound(this.findOne(ctx, facetValue.id));
     }
 
-    async update(input: UpdateFacetValueInput): Promise<Translated<FacetValue>> {
+    async update(ctx: RequestContext, input: UpdateFacetValueInput): Promise<Translated<FacetValue>> {
         const facetValue = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: FacetValue,
             translationType: FacetValueTranslation,
         });
-        return assertFound(this.findOne(facetValue.id, this.configService.defaultLanguageCode));
+        return assertFound(this.findOne(ctx, facetValue.id));
     }
 
     async delete(ctx: RequestContext, id: ID, force: boolean = false): Promise<DeletionResponse> {
-        const { productCount, variantCount } = await this.checkFacetValueUsage([id]);
+        const { productCount, variantCount } = await this.checkFacetValueUsage(ctx, [id]);
 
         const isInUse = !!(productCount || variantCount);
         const both = !!(productCount && variantCount) ? 'both' : 'single';
@@ -107,11 +104,11 @@ export class FacetValueService {
 
         if (!isInUse) {
             const facetValue = await getEntityOrThrow(this.connection, FacetValue, id);
-            await this.connection.getRepository(FacetValue).remove(facetValue);
+            await this.connection.getRepository(ctx, FacetValue).remove(facetValue);
             result = DeletionResult.DELETED;
         } else if (force) {
             const facetValue = await getEntityOrThrow(this.connection, FacetValue, id);
-            await this.connection.getRepository(FacetValue).remove(facetValue);
+            await this.connection.getRepository(ctx, FacetValue).remove(facetValue);
             message = ctx.translate('message.facet-value-force-deleted', i18nVars);
             result = DeletionResult.DELETED;
         } else {
@@ -128,16 +125,19 @@ export class FacetValueService {
     /**
      * Checks for usage of the given FacetValues in any Products or Variants, and returns the counts.
      */
-    async checkFacetValueUsage(facetValueIds: ID[]): Promise<{ productCount: number; variantCount: number }> {
+    async checkFacetValueUsage(
+        ctx: RequestContext,
+        facetValueIds: ID[],
+    ): Promise<{ productCount: number; variantCount: number }> {
         const consumingProducts = await this.connection
-            .getRepository(Product)
+            .getRepository(ctx, Product)
             .createQueryBuilder('product')
             .leftJoinAndSelect('product.facetValues', 'facetValues')
             .where('facetValues.id IN (:...facetValueIds)', { facetValueIds })
             .getMany();
 
         const consumingVariants = await this.connection
-            .getRepository(ProductVariant)
+            .getRepository(ctx, ProductVariant)
             .createQueryBuilder('variant')
             .leftJoinAndSelect('variant.facetValues', 'facetValues')
             .where('facetValues.id IN (:...facetValueIds)', { facetValueIds })

+ 22 - 16
packages/core/src/service/services/facet.service.ts

@@ -13,6 +13,7 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { Translated } from '../../common/types/locale-types';
 import { assertFound } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
+import { Collection } from '../../entity/collection/collection.entity';
 import { FacetTranslation } from '../../entity/facet/facet-translation.entity';
 import { Facet } from '../../entity/facet/facet.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
@@ -34,17 +35,17 @@ export class FacetService {
     ) {}
 
     findAll(
-        lang: LanguageCode,
+        ctx: RequestContext,
         options?: ListQueryOptions<Facet>,
     ): Promise<PaginatedList<Translated<Facet>>> {
         const relations = ['values', 'values.facet'];
 
         return this.listQueryBuilder
-            .build(Facet, options, { relations })
+            .build(Facet, options, { relations, ctx })
             .getManyAndCount()
             .then(([facets, totalItems]) => {
                 const items = facets.map(facet =>
-                    translateDeep(facet, lang, ['values', ['values', 'facet']]),
+                    translateDeep(facet, ctx.languageCode, ['values', ['values', 'facet']]),
                 );
                 return {
                     items,
@@ -53,13 +54,13 @@ export class FacetService {
             });
     }
 
-    findOne(facetId: ID, lang: LanguageCode): Promise<Translated<Facet> | undefined> {
+    findOne(ctx: RequestContext, facetId: ID): Promise<Translated<Facet> | undefined> {
         const relations = ['values', 'values.facet'];
 
         return this.connection
-            .getRepository(Facet)
+            .getRepository(ctx, Facet)
             .findOne(facetId, { relations })
-            .then(facet => facet && translateDeep(facet, lang, ['values', ['values', 'facet']]));
+            .then(facet => facet && translateDeep(facet, ctx.languageCode, ['values', ['values', 'facet']]));
     }
 
     findByCode(facetCode: string, lang: LanguageCode): Promise<Translated<Facet> | undefined> {
@@ -75,35 +76,37 @@ export class FacetService {
             .then(facet => facet && translateDeep(facet, lang, ['values', ['values', 'facet']]));
     }
 
-    async findByFacetValueId(id: ID, lang: LanguageCode): Promise<Translated<Facet> | undefined> {
+    async findByFacetValueId(ctx: RequestContext, id: ID): Promise<Translated<Facet> | undefined> {
         const facet = await this.connection
-            .getRepository(Facet)
+            .getRepository(ctx, Facet)
             .createQueryBuilder('facet')
             .leftJoinAndSelect('facet.translations', 'translations')
             .leftJoin('facet.values', 'facetValue')
             .where('facetValue.id = :id', { id })
             .getOne();
         if (facet) {
-            return translateDeep(facet, lang);
+            return translateDeep(facet, ctx.languageCode);
         }
     }
 
-    async create(input: CreateFacetInput): Promise<Translated<Facet>> {
+    async create(ctx: RequestContext, input: CreateFacetInput): Promise<Translated<Facet>> {
         const facet = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: Facet,
             translationType: FacetTranslation,
         });
-        return assertFound(this.findOne(facet.id, this.configService.defaultLanguageCode));
+        return assertFound(this.findOne(ctx, facet.id));
     }
 
-    async update(input: UpdateFacetInput): Promise<Translated<Facet>> {
+    async update(ctx: RequestContext, input: UpdateFacetInput): Promise<Translated<Facet>> {
         const facet = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: Facet,
             translationType: FacetTranslation,
         });
-        return assertFound(this.findOne(facet.id, this.configService.defaultLanguageCode));
+        return assertFound(this.findOne(ctx, facet.id));
     }
 
     async delete(ctx: RequestContext, id: ID, force: boolean = false): Promise<DeletionResponse> {
@@ -111,7 +114,10 @@ export class FacetService {
         let productCount = 0;
         let variantCount = 0;
         if (facet.values.length) {
-            const counts = await this.facetValueService.checkFacetValueUsage(facet.values.map(fv => fv.id));
+            const counts = await this.facetValueService.checkFacetValueUsage(
+                ctx,
+                facet.values.map(fv => fv.id),
+            );
             productCount = counts.productCount;
             variantCount = counts.variantCount;
         }
@@ -123,10 +129,10 @@ export class FacetService {
         let result: DeletionResult;
 
         if (!isInUse) {
-            await this.connection.getRepository(Facet).remove(facet);
+            await this.connection.getRepository(ctx, Facet).remove(facet);
             result = DeletionResult.DELETED;
         } else if (force) {
-            await this.connection.getRepository(Facet).remove(facet);
+            await this.connection.getRepository(ctx, Facet).remove(facet);
             message = ctx.translate('message.facet-force-deleted', i18nVars);
             result = DeletionResult.DELETED;
         } else {

+ 7 - 6
packages/core/src/service/services/global-settings.service.ts

@@ -1,6 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { UpdateGlobalSettingsInput } from '@vendure/common/lib/generated-types';
 
+import { RequestContext } from '../../api/common/request-context';
 import { InternalServerError } from '../../common/error/errors';
 import { ConfigService } from '../../config/config.service';
 import { GlobalSettings } from '../../entity/global-settings/global-settings.entity';
@@ -16,7 +17,7 @@ export class GlobalSettingsService {
      */
     async initGlobalSettings() {
         try {
-            await this.getSettings();
+            await this.getSettings(RequestContext.empty());
         } catch (err) {
             const settings = new GlobalSettings({
                 availableLanguages: [this.configService.defaultLanguageCode],
@@ -25,17 +26,17 @@ export class GlobalSettingsService {
         }
     }
 
-    async getSettings(): Promise<GlobalSettings> {
-        const settings = await this.connection.getRepository(GlobalSettings).findOne();
+    async getSettings(ctx: RequestContext): Promise<GlobalSettings> {
+        const settings = await this.connection.getRepository(ctx, GlobalSettings).findOne();
         if (!settings) {
             throw new InternalServerError(`error.global-settings-not-found`);
         }
         return settings;
     }
 
-    async updateSettings(input: UpdateGlobalSettingsInput): Promise<GlobalSettings> {
-        const settings = await this.getSettings();
+    async updateSettings(ctx: RequestContext, input: UpdateGlobalSettingsInput): Promise<GlobalSettings> {
+        const settings = await this.getSettings(ctx);
         patchEntity(settings, input);
-        return this.connection.getRepository(GlobalSettings).save(settings);
+        return this.connection.getRepository(ctx, GlobalSettings).save(settings);
     }
 }

+ 13 - 9
packages/core/src/service/services/history.service.ts

@@ -139,6 +139,7 @@ export class HistoryService {
     ) {}
 
     async getHistoryForOrder(
+        ctx: RequestContext,
         orderId: ID,
         publicOnly: boolean,
         options?: HistoryEntryListOptions,
@@ -150,6 +151,7 @@ export class HistoryService {
                     ...(publicOnly ? { isPublic: true } : {}),
                 },
                 relations: ['administrator'],
+                ctx,
             })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
@@ -171,10 +173,11 @@ export class HistoryService {
             order: { id: orderId },
             administrator,
         });
-        return this.connection.getRepository(OrderHistoryEntry).save(entry);
+        return this.connection.getRepository(ctx, OrderHistoryEntry).save(entry);
     }
 
     async getHistoryForCustomer(
+        ctx: RequestContext,
         customerId: ID,
         publicOnly: boolean,
         options?: HistoryEntryListOptions,
@@ -186,6 +189,7 @@ export class HistoryService {
                     ...(publicOnly ? { isPublic: true } : {}),
                 },
                 relations: ['administrator'],
+                ctx,
             })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
@@ -207,7 +211,7 @@ export class HistoryService {
             customer: { id: customerId },
             administrator,
         });
-        return this.connection.getRepository(CustomerHistoryEntry).save(entry);
+        return this.connection.getRepository(ctx, CustomerHistoryEntry).save(entry);
     }
 
     async updateOrderHistoryEntry<T extends keyof OrderHistoryEntryData>(
@@ -228,12 +232,12 @@ export class HistoryService {
         if (administrator) {
             entry.administrator = administrator;
         }
-        return this.connection.getRepository(OrderHistoryEntry).save(entry);
+        return this.connection.getRepository(ctx, OrderHistoryEntry).save(entry);
     }
 
-    async deleteOrderHistoryEntry(id: ID): Promise<void> {
+    async deleteOrderHistoryEntry(ctx: RequestContext, id: ID): Promise<void> {
         const entry = await getEntityOrThrow(this.connection, OrderHistoryEntry, id);
-        await this.connection.getRepository(OrderHistoryEntry).remove(entry);
+        await this.connection.getRepository(ctx, OrderHistoryEntry).remove(entry);
     }
 
     async updateCustomerHistoryEntry<T extends keyof CustomerHistoryEntryData>(
@@ -251,17 +255,17 @@ export class HistoryService {
         if (administrator) {
             entry.administrator = administrator;
         }
-        return this.connection.getRepository(CustomerHistoryEntry).save(entry);
+        return this.connection.getRepository(ctx, CustomerHistoryEntry).save(entry);
     }
 
-    async deleteCustomerHistoryEntry(id: ID): Promise<void> {
+    async deleteCustomerHistoryEntry(ctx: RequestContext, id: ID): Promise<void> {
         const entry = await getEntityOrThrow(this.connection, CustomerHistoryEntry, id);
-        await this.connection.getRepository(CustomerHistoryEntry).remove(entry);
+        await this.connection.getRepository(ctx, CustomerHistoryEntry).remove(entry);
     }
 
     private async getAdministratorFromContext(ctx: RequestContext): Promise<Administrator | undefined> {
         const administrator = ctx.activeUserId
-            ? await this.administratorService.findOneByUserId(ctx.activeUserId)
+            ? await this.administratorService.findOneByUserId(ctx, ctx.activeUserId)
             : undefined;
         return administrator;
     }

+ 52 - 44
packages/core/src/service/services/order.service.ts

@@ -37,6 +37,7 @@ import { OrderItem } from '../../entity/order-item/order-item.entity';
 import { OrderLine } from '../../entity/order-line/order-line.entity';
 import { Order } from '../../entity/order/order.entity';
 import { Payment } from '../../entity/payment/payment.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { Promotion } from '../../entity/promotion/promotion.entity';
 import { Refund } from '../../entity/refund/refund.entity';
@@ -108,6 +109,7 @@ export class OrderService {
             .build(Order, options, {
                 relations: ['lines', 'customer', 'lines.productVariant', 'channels'],
                 channelId: ctx.channelId,
+                ctx,
             })
             .getManyAndCount()
             .then(([items, totalItems]) => {
@@ -120,7 +122,7 @@ export class OrderService {
 
     async findOne(ctx: RequestContext, orderId: ID): Promise<Order | undefined> {
         const order = await this.connection
-            .getRepository(Order)
+            .getRepository(ctx, Order)
             .createQueryBuilder('order')
             .leftJoin('order.channels', 'channel')
             .leftJoinAndSelect('order.customer', 'customer')
@@ -151,7 +153,7 @@ export class OrderService {
     }
 
     async findOneByCode(ctx: RequestContext, orderCode: string): Promise<Order | undefined> {
-        const order = await this.connection.getRepository(Order).findOne({
+        const order = await this.connection.getRepository(ctx, Order).findOne({
             relations: ['customer'],
             where: {
                 code: orderCode,
@@ -176,6 +178,7 @@ export class OrderService {
                     'channels',
                 ],
                 channelId: ctx.channelId,
+                ctx,
             })
             .andWhere('order.customer.id = :customerId', { customerId })
             .getManyAndCount()
@@ -194,8 +197,8 @@ export class OrderService {
             });
     }
 
-    getOrderPayments(orderId: ID): Promise<Payment[]> {
-        return this.connection.getRepository(Payment).find({
+    getOrderPayments(ctx: RequestContext, orderId: ID): Promise<Payment[]> {
+        return this.connection.getRepository(ctx, Payment).find({
             where: {
                 order: { id: orderId } as any,
             },
@@ -209,8 +212,8 @@ export class OrderService {
         return refund.orderItems;
     }
 
-    getPaymentRefunds(paymentId: ID): Promise<Refund[]> {
-        return this.connection.getRepository(Refund).find({
+    getPaymentRefunds(ctx: RequestContext, paymentId: ID): Promise<Refund[]> {
+        return this.connection.getRepository(ctx, Refund).find({
             where: {
                 paymentId,
             },
@@ -218,10 +221,10 @@ export class OrderService {
     }
 
     async getActiveOrderForUser(ctx: RequestContext, userId: ID): Promise<Order | undefined> {
-        const customer = await this.customerService.findOneByUserId(userId);
+        const customer = await this.customerService.findOneByUserId(ctx, userId);
         if (customer) {
             const activeOrder = await this.connection
-                .getRepository(Order)
+                .getRepository(ctx, Order)
                 .createQueryBuilder('order')
                 .innerJoinAndSelect('order.channels', 'channel', 'channel.id = :channelId', {
                     channelId: ctx.channelId,
@@ -251,19 +254,19 @@ export class OrderService {
             currencyCode: ctx.channel.currencyCode,
         });
         if (userId) {
-            const customer = await this.customerService.findOneByUserId(userId);
+            const customer = await this.customerService.findOneByUserId(ctx, userId);
             if (customer) {
                 newOrder.customer = customer;
             }
         }
         this.channelService.assignToCurrentChannel(newOrder, ctx);
-        return this.connection.getRepository(Order).save(newOrder);
+        return this.connection.getRepository(ctx, Order).save(newOrder);
     }
 
     async updateCustomFields(ctx: RequestContext, orderId: ID, customFields: any) {
         let order = await this.getOrderOrThrow(ctx, orderId);
         order = patchEntity(order, { customFields });
-        return this.connection.getRepository(Order).save(order);
+        return this.connection.getRepository(ctx, Order).save(order);
     }
 
     async addItemToOrder(
@@ -287,9 +290,9 @@ export class OrderService {
 
         if (!orderLine) {
             const newLine = this.createOrderLineFromVariant(productVariant, customFields);
-            orderLine = await this.connection.getRepository(OrderLine).save(newLine);
+            orderLine = await this.connection.getRepository(ctx, OrderLine).save(newLine);
             order.lines.push(orderLine);
-            await this.connection.getRepository(Order).save(order, { reload: false });
+            await this.connection.getRepository(ctx, Order).save(order, { reload: false });
         }
         return this.adjustOrderLine(ctx, order, orderLine.id, orderLine.quantity + quantity);
     }
@@ -325,7 +328,7 @@ export class OrderService {
                     orderLine.customFields || {},
                 );
                 for (let i = currentQuantity; i < quantity; i++) {
-                    const orderItem = await this.connection.getRepository(OrderItem).save(
+                    const orderItem = await this.connection.getRepository(ctx, OrderItem).save(
                         new OrderItem({
                             unitPrice: calculatedPrice.price,
                             pendingAdjustments: [],
@@ -341,7 +344,7 @@ export class OrderService {
                 orderLine.items = orderLine.items.slice(0, quantity);
             }
         }
-        await this.connection.getRepository(OrderLine).save(orderLine, { reload: false });
+        await this.connection.getRepository(ctx, OrderLine).save(orderLine, { reload: false });
         return this.applyPriceAdjustments(ctx, order, orderLine);
     }
 
@@ -351,14 +354,14 @@ export class OrderService {
         const orderLine = this.getOrderLineOrThrow(order, orderLineId);
         order.lines = order.lines.filter(line => !idsAreEqual(line.id, orderLineId));
         const updatedOrder = await this.applyPriceAdjustments(ctx, order);
-        await this.connection.getRepository(OrderLine).remove(orderLine);
+        await this.connection.getRepository(ctx, OrderLine).remove(orderLine);
         return updatedOrder;
     }
 
     async removeAllItemsFromOrder(ctx: RequestContext, orderId: ID): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         this.assertAddingItemsState(order);
-        await this.connection.getRepository(OrderLine).remove(order.lines);
+        await this.connection.getRepository(ctx, OrderLine).remove(order.lines);
         order.lines = [];
         const updatedOrder = await this.applyPriceAdjustments(ctx, order);
         return updatedOrder;
@@ -370,6 +373,7 @@ export class OrderService {
             return order;
         }
         const promotion = await this.promotionService.validateCouponCode(
+            ctx,
             couponCode,
             order.customer && order.customer.id,
         );
@@ -414,14 +418,14 @@ export class OrderService {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const country = await this.countryService.findOneByCode(ctx, input.countryCode);
         order.shippingAddress = { ...input, countryCode: input.countryCode, country: country.name };
-        return this.connection.getRepository(Order).save(order);
+        return this.connection.getRepository(ctx, Order).save(order);
     }
 
     async setBillingAddress(ctx: RequestContext, orderId: ID, input: CreateAddressInput): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const country = await this.countryService.findOneByCode(ctx, input.countryCode);
         order.billingAddress = { ...input, countryCode: input.countryCode, country: country.name };
-        return this.connection.getRepository(Order).save(order);
+        return this.connection.getRepository(ctx, Order).save(order);
     }
 
     async getEligibleShippingMethods(ctx: RequestContext, orderId: ID): Promise<ShippingMethodQuote[]> {
@@ -445,17 +449,17 @@ export class OrderService {
             throw new UserInputError(`error.shipping-method-unavailable`);
         }
         order.shippingMethod = selectedMethod.method;
-        await this.connection.getRepository(Order).save(order, { reload: false });
+        await this.connection.getRepository(ctx, Order).save(order, { reload: false });
         await this.applyPriceAdjustments(ctx, order);
-        return this.connection.getRepository(Order).save(order);
+        return this.connection.getRepository(ctx, Order).save(order);
     }
 
     async transitionToState(ctx: RequestContext, orderId: ID, state: OrderState): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
-        order.payments = await this.getOrderPayments(orderId);
+        order.payments = await this.getOrderPayments(ctx, orderId);
         const fromState = order.state;
         await this.orderStateMachine.transition(ctx, order, state);
-        await this.connection.getRepository(Order).save(order, { reload: false });
+        await this.connection.getRepository(ctx, Order).save(order, { reload: false });
         this.eventBus.publish(new OrderStateTransitionEvent(fromState, state, ctx, order));
         return order;
     }
@@ -472,9 +476,9 @@ export class OrderService {
             input.metadata,
         );
 
-        const existingPayments = await this.getOrderPayments(orderId);
+        const existingPayments = await this.getOrderPayments(ctx, orderId);
         order.payments = [...existingPayments, payment];
-        await this.connection.getRepository(Order).save(order, { reload: false });
+        await this.connection.getRepository(ctx, Order).save(order, { reload: false });
 
         if (payment.state === 'Error') {
             throw new InternalServerError(payment.errorMessage);
@@ -491,13 +495,17 @@ export class OrderService {
 
     async settlePayment(ctx: RequestContext, paymentId: ID): Promise<Payment> {
         const payment = await getEntityOrThrow(this.connection, Payment, paymentId, { relations: ['order'] });
-        const settlePaymentResult = await this.paymentMethodService.settlePayment(payment, payment.order);
+        const settlePaymentResult = await this.paymentMethodService.settlePayment(
+            ctx,
+            payment,
+            payment.order,
+        );
         if (settlePaymentResult.success) {
             const fromState = payment.state;
             const toState = 'Settled';
             await this.paymentStateMachine.transition(ctx, payment.order, payment, toState);
             payment.metadata = { ...payment.metadata, ...settlePaymentResult.metadata };
-            await this.connection.getRepository(Payment).save(payment, { reload: false });
+            await this.connection.getRepository(ctx, Payment).save(payment, { reload: false });
             this.eventBus.publish(
                 new PaymentStateTransitionEvent(fromState, toState, ctx, payment, payment.order),
             );
@@ -529,7 +537,7 @@ export class OrderService {
             }
         }
 
-        const fulfillment = await this.connection.getRepository(Fulfillment).save(
+        const fulfillment = await this.connection.getRepository(ctx, Fulfillment).save(
             new Fulfillment({
                 trackingCode: input.trackingCode,
                 method: input.method,
@@ -567,7 +575,7 @@ export class OrderService {
         return fulfillment;
     }
 
-    async getOrderFulfillments(order: Order): Promise<Fulfillment[]> {
+    async getOrderFulfillments(ctx: RequestContext, order: Order): Promise<Fulfillment[]> {
         let lines: OrderLine[];
         if (
             order.lines &&
@@ -577,7 +585,7 @@ export class OrderService {
         ) {
             lines = order.lines;
         } else {
-            lines = await this.connection.getRepository(OrderLine).find({
+            lines = await this.connection.getRepository(ctx, OrderLine).find({
                 where: {
                     order: order.id,
                 },
@@ -649,11 +657,11 @@ export class OrderService {
         }
 
         // Perform the cancellation
-        await this.stockMovementService.createCancellationsForOrderItems(items);
+        await this.stockMovementService.createCancellationsForOrderItems(ctx, items);
         items.forEach(i => (i.cancelled = true));
-        await this.connection.getRepository(OrderItem).save(items, { reload: false });
+        await this.connection.getRepository(ctx, OrderItem).save(items, { reload: false });
 
-        const orderWithItems = await this.connection.getRepository(Order).findOne(order.id, {
+        const orderWithItems = await this.connection.getRepository(ctx, Order).findOne(order.id, {
             relations: ['lines', 'lines.items'],
         });
         if (!orderWithItems) {
@@ -720,7 +728,7 @@ export class OrderService {
         const fromState = refund.state;
         const toState = 'Settled';
         await this.refundStateMachine.transition(ctx, refund.payment.order, refund, toState);
-        await this.connection.getRepository(Refund).save(refund);
+        await this.connection.getRepository(ctx, Refund).save(refund);
         this.eventBus.publish(
             new RefundStateTransitionEvent(fromState, toState, ctx, refund, refund.payment.order),
         );
@@ -730,14 +738,14 @@ export class OrderService {
     async addCustomerToOrder(ctx: RequestContext, orderId: ID, customer: Customer): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         order.customer = customer;
-        await this.connection.getRepository(Order).save(order, { reload: false });
+        await this.connection.getRepository(ctx, Order).save(order, { reload: false });
         // Check that any applied couponCodes are still valid now that
         // we know the Customer.
         if (order.couponCodes) {
             let codesRemoved = false;
             for (const couponCode of order.couponCodes.slice()) {
                 try {
-                    await this.promotionService.validateCouponCode(couponCode, customer.id);
+                    await this.promotionService.validateCouponCode(ctx, couponCode, customer.id);
                 } catch (err) {
                     order.couponCodes = order.couponCodes.filter(c => c !== couponCode);
                     codesRemoved = true;
@@ -778,7 +786,7 @@ export class OrderService {
 
     async deleteOrderNote(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         try {
-            await this.historyService.deleteOrderHistoryEntry(id);
+            await this.historyService.deleteOrderHistoryEntry(ctx, id);
             return {
                 result: DeletionResult.DELETED,
             };
@@ -809,17 +817,17 @@ export class OrderService {
         const { orderToDelete, linesToInsert } = mergeResult;
         let { order } = mergeResult;
         if (orderToDelete) {
-            await this.connection.getRepository(Order).delete(orderToDelete.id);
+            await this.connection.getRepository(ctx, Order).delete(orderToDelete.id);
         }
         if (order && linesToInsert) {
             for (const line of linesToInsert) {
                 order = await this.addItemToOrder(ctx, order.id, line.productVariantId, line.quantity);
             }
         }
-        const customer = await this.customerService.findOneByUserId(user.id);
+        const customer = await this.customerService.findOneByUserId(ctx, user.id);
         if (order && customer) {
             order.customer = customer;
-            await this.connection.getRepository(Order).save(order, { reload: false });
+            await this.connection.getRepository(ctx, Order).save(order, { reload: false });
         }
         return order;
     }
@@ -901,7 +909,7 @@ export class OrderService {
         order: Order,
         updatedOrderLine?: OrderLine,
     ): Promise<Order> {
-        const promotions = await this.connection.getRepository(Promotion).find({
+        const promotions = await this.connection.getRepository(ctx, Promotion).find({
             where: { enabled: true, deletedAt: null },
             order: { priorityScore: 'ASC' },
         });
@@ -911,8 +919,8 @@ export class OrderService {
             promotions,
             updatedOrderLine,
         );
-        await this.connection.getRepository(Order).save(order, { reload: false });
-        await this.connection.getRepository(OrderItem).save(updatedItems, { reload: false });
+        await this.connection.getRepository(ctx, Order).save(order, { reload: false });
+        await this.connection.getRepository(ctx, OrderItem).save(updatedItems, { reload: false });
         return order;
     }
 
@@ -925,7 +933,7 @@ export class OrderService {
         const orders = new Map<ID, Order>();
         const items = new Map<ID, OrderItem>();
 
-        const lines = await this.connection.getRepository(OrderLine).findByIds(
+        const lines = await this.connection.getRepository(ctx, OrderLine).findByIds(
             orderLinesInput.map(l => l.orderLineId),
             {
                 relations: ['order', 'items', 'items.fulfillment', 'order.channels'],

+ 24 - 19
packages/core/src/service/services/payment-method.service.ts

@@ -46,9 +46,12 @@ export class PaymentMethodService {
         await this.ensurePaymentMethodsExist();
     }
 
-    findAll(options?: ListQueryOptions<PaymentMethod>): Promise<PaginatedList<PaymentMethod>> {
+    findAll(
+        ctx: RequestContext,
+        options?: ListQueryOptions<PaymentMethod>,
+    ): Promise<PaginatedList<PaymentMethod>> {
         return this.listQueryBuilder
-            .build(PaymentMethod, options)
+            .build(PaymentMethod, options, { ctx })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,
@@ -56,11 +59,11 @@ export class PaymentMethodService {
             }));
     }
 
-    findOne(paymentMethodId: ID): Promise<PaymentMethod | undefined> {
-        return this.connection.getRepository(PaymentMethod).findOne(paymentMethodId);
+    findOne(ctx: RequestContext, paymentMethodId: ID): Promise<PaymentMethod | undefined> {
+        return this.connection.getRepository(ctx, PaymentMethod).findOne(paymentMethodId);
     }
 
-    async update(input: UpdatePaymentMethodInput): Promise<PaymentMethod> {
+    async update(ctx: RequestContext, input: UpdatePaymentMethodInput): Promise<PaymentMethod> {
         const paymentMethod = await getEntityOrThrow(this.connection, PaymentMethod, input.id);
         const updatedPaymentMethod = patchEntity(paymentMethod, omit(input, ['configArgs']));
         if (input.configArgs) {
@@ -74,7 +77,7 @@ export class PaymentMethodService {
                 updatedPaymentMethod.configArgs = input.configArgs.filter(handlerHasArgDefinition);
             }
         }
-        return this.connection.getRepository(PaymentMethod).save(updatedPaymentMethod);
+        return this.connection.getRepository(ctx, PaymentMethod).save(updatedPaymentMethod);
     }
 
     async createPayment(
@@ -83,22 +86,22 @@ export class PaymentMethodService {
         method: string,
         metadata: PaymentMetadata,
     ): Promise<Payment> {
-        const { paymentMethod, handler } = await this.getMethodAndHandler(method);
+        const { paymentMethod, handler } = await this.getMethodAndHandler(ctx, method);
         const result = await handler.createPayment(order, paymentMethod.configArgs, metadata || {});
         const initialState = 'Created';
         const payment = await this.connection
-            .getRepository(Payment)
+            .getRepository(ctx, Payment)
             .save(new Payment({ ...result, state: initialState }));
         await this.paymentStateMachine.transition(ctx, order, payment, result.state);
-        await this.connection.getRepository(Payment).save(payment, { reload: false });
+        await this.connection.getRepository(ctx, Payment).save(payment, { reload: false });
         this.eventBus.publish(
             new PaymentStateTransitionEvent(initialState, result.state, ctx, payment, order),
         );
         return payment;
     }
 
-    async settlePayment(payment: Payment, order: Order) {
-        const { paymentMethod, handler } = await this.getMethodAndHandler(payment.method);
+    async settlePayment(ctx: RequestContext, payment: Payment, order: Order) {
+        const { paymentMethod, handler } = await this.getMethodAndHandler(ctx, payment.method);
         return handler.settlePayment(order, payment, paymentMethod.configArgs);
     }
 
@@ -109,7 +112,7 @@ export class PaymentMethodService {
         items: OrderItem[],
         payment: Payment,
     ): Promise<Refund> {
-        const { paymentMethod, handler } = await this.getMethodAndHandler(payment.method);
+        const { paymentMethod, handler } = await this.getMethodAndHandler(ctx, payment.method);
         const itemAmount = items.reduce((sum, item) => sum + item.unitPriceWithTax, 0);
         const refundAmount = itemAmount + input.shipping + input.adjustment;
         let refund = new Refund({
@@ -135,11 +138,11 @@ export class PaymentMethodService {
             refund.transactionId = createRefundResult.transactionId || '';
             refund.metadata = createRefundResult.metadata || {};
         }
-        refund = await this.connection.getRepository(Refund).save(refund);
+        refund = await this.connection.getRepository(ctx, Refund).save(refund);
         if (createRefundResult) {
             const fromState = refund.state;
             await this.refundStateMachine.transition(ctx, order, refund, createRefundResult.state);
-            await this.connection.getRepository(Refund).save(refund, { reload: false });
+            await this.connection.getRepository(ctx, Refund).save(refund, { reload: false });
             this.eventBus.publish(
                 new RefundStateTransitionEvent(fromState, createRefundResult.state, ctx, refund, order),
             );
@@ -156,9 +159,10 @@ export class PaymentMethodService {
     }
 
     private async getMethodAndHandler(
+        ctx: RequestContext,
         method: string,
     ): Promise<{ paymentMethod: PaymentMethod; handler: PaymentMethodHandler }> {
-        const paymentMethod = await this.connection.getRepository(PaymentMethod).findOne({
+        const paymentMethod = await this.connection.getRepository(ctx, PaymentMethod).findOne({
             where: {
                 code: method,
                 enabled: true,
@@ -172,8 +176,9 @@ export class PaymentMethodService {
     }
 
     private async ensurePaymentMethodsExist() {
+        const paymentMethodRepo = await this.connection.getRepository(PaymentMethod);
         const paymentMethodHandlers = this.configService.paymentOptions.paymentMethodHandlers;
-        const existingPaymentMethods = await this.connection.getRepository(PaymentMethod).find();
+        const existingPaymentMethods = await paymentMethodRepo.find();
         const toCreate = paymentMethodHandlers.filter(
             h => !existingPaymentMethods.find(pm => pm.code === h.code),
         );
@@ -190,7 +195,7 @@ export class PaymentMethodService {
                 continue;
             }
             paymentMethod.configArgs = this.buildConfigArgsArray(handler, paymentMethod.configArgs);
-            await this.connection.getRepository(PaymentMethod).save(paymentMethod, { reload: false });
+            await paymentMethodRepo.save(paymentMethod, { reload: false });
         }
         for (const handler of toCreate) {
             let paymentMethod = existingPaymentMethods.find(pm => pm.code === handler.code);
@@ -203,9 +208,9 @@ export class PaymentMethodService {
                 });
             }
             paymentMethod.configArgs = this.buildConfigArgsArray(handler, paymentMethod.configArgs);
-            await this.connection.getRepository(PaymentMethod).save(paymentMethod, { reload: false });
+            await paymentMethodRepo.save(paymentMethod, { reload: false });
         }
-        await this.connection.getRepository(PaymentMethod).remove(toRemove);
+        await paymentMethodRepo.remove(toRemove);
     }
 
     private buildConfigArgsArray(

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

@@ -29,14 +29,14 @@ export class ProductOptionGroupService {
             };
         }
         return this.connection
-            .getRepository(ProductOptionGroup)
+            .getRepository(ctx, ProductOptionGroup)
             .find(findOptions)
             .then(groups => groups.map(group => translateDeep(group, ctx.languageCode, ['options'])));
     }
 
     findOne(ctx: RequestContext, id: ID): Promise<Translated<ProductOptionGroup> | undefined> {
         return this.connection
-            .getRepository(ProductOptionGroup)
+            .getRepository(ctx, ProductOptionGroup)
             .findOne(id, {
                 relations: ['options'],
             })
@@ -45,7 +45,7 @@ export class ProductOptionGroupService {
 
     getOptionGroupsByProductId(ctx: RequestContext, id: ID): Promise<Array<Translated<ProductOptionGroup>>> {
         return this.connection
-            .getRepository(ProductOptionGroup)
+            .getRepository(ctx, ProductOptionGroup)
             .find({
                 relations: ['options'],
                 where: {
@@ -63,6 +63,7 @@ export class ProductOptionGroupService {
         input: CreateProductOptionGroupInput,
     ): Promise<Translated<ProductOptionGroup>> {
         const group = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: ProductOptionGroup,
             translationType: ProductOptionGroupTranslation,
@@ -75,6 +76,7 @@ export class ProductOptionGroupService {
         input: UpdateProductOptionGroupInput,
     ): Promise<Translated<ProductOptionGroup>> {
         const group = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: ProductOptionGroup,
             translationType: ProductOptionGroupTranslation,

+ 4 - 2
packages/core/src/service/services/product-option.service.ts

@@ -23,7 +23,7 @@ export class ProductOptionService {
 
     findAll(ctx: RequestContext): Promise<Array<Translated<ProductOption>>> {
         return this.connection
-            .getRepository(ProductOption)
+            .getRepository(ctx, ProductOption)
             .find({
                 relations: ['group'],
             })
@@ -32,7 +32,7 @@ export class ProductOptionService {
 
     findOne(ctx: RequestContext, id: ID): Promise<Translated<ProductOption> | undefined> {
         return this.connection
-            .getRepository(ProductOption)
+            .getRepository(ctx, ProductOption)
             .findOne(id, {
                 relations: ['group'],
             })
@@ -49,6 +49,7 @@ export class ProductOptionService {
                 ? group
                 : await getEntityOrThrow(this.connection, ProductOptionGroup, group);
         const option = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: ProductOption,
             translationType: ProductOptionTranslation,
@@ -59,6 +60,7 @@ export class ProductOptionService {
 
     async update(ctx: RequestContext, input: UpdateProductOptionInput): Promise<Translated<ProductOption>> {
         const option = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: ProductOption,
             translationType: ProductOptionTranslation,

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

@@ -58,7 +58,7 @@ export class ProductVariantService {
     findOne(ctx: RequestContext, productVariantId: ID): Promise<Translated<ProductVariant> | undefined> {
         const relations = ['product', 'product.featuredAsset', 'taxCategory'];
         return this.connection
-            .getRepository(ProductVariant)
+            .getRepository(ctx, ProductVariant)
             .findOne(productVariantId, { relations })
             .then(result => {
                 if (result) {
@@ -71,7 +71,7 @@ export class ProductVariantService {
 
     findByIds(ctx: RequestContext, ids: ID[]): Promise<Array<Translated<ProductVariant>>> {
         return this.connection
-            .getRepository(ProductVariant)
+            .getRepository(ctx, ProductVariant)
             .findByIds(ids, {
                 relations: [
                     'options',
@@ -95,7 +95,7 @@ export class ProductVariantService {
 
     getVariantsByProductId(ctx: RequestContext, productId: ID): Promise<Array<Translated<ProductVariant>>> {
         return this.connection
-            .getRepository(ProductVariant)
+            .getRepository(ctx, ProductVariant)
             .find({
                 where: {
                     product: { id: productId } as any,
@@ -134,6 +134,7 @@ export class ProductVariantService {
             .build(ProductVariant, options, {
                 relations: ['taxCategory'],
                 channelId: ctx.channelId,
+                ctx,
             })
             .leftJoin('productvariant.collections', 'collection')
             .leftJoin('productvariant.product', 'product')
@@ -165,14 +166,14 @@ export class ProductVariantService {
 
     getOptionsForVariant(ctx: RequestContext, variantId: ID): Promise<Array<Translated<ProductOption>>> {
         return this.connection
-            .getRepository(ProductVariant)
+            .getRepository(ctx, ProductVariant)
             .findOne(variantId, { relations: ['options'] })
             .then(variant => (!variant ? [] : variant.options.map(o => translateDeep(o, ctx.languageCode))));
     }
 
     getFacetValuesForVariant(ctx: RequestContext, variantId: ID): Promise<Array<Translated<FacetValue>>> {
         return this.connection
-            .getRepository(ProductVariant)
+            .getRepository(ctx, ProductVariant)
             .findOne(variantId, { relations: ['facetValues', 'facetValues.facet'] })
             .then(variant =>
                 !variant ? [] : variant.facetValues.map(o => translateDeep(o, ctx.languageCode, ['facet'])),
@@ -226,9 +227,10 @@ export class ProductVariantService {
         if (input.price == null) {
             input.price = 0;
         }
-        input.taxCategoryId = (await this.getTaxCategoryForNewVariant(input.taxCategoryId)).id;
+        input.taxCategoryId = (await this.getTaxCategoryForNewVariant(ctx, input.taxCategoryId)).id;
 
         const createdVariant = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: ProductVariant,
             translationType: ProductVariantTranslation,
@@ -236,35 +238,38 @@ export class ProductVariantService {
                 const { optionIds } = input;
                 if (optionIds && optionIds.length) {
                     const selectedOptions = await this.connection
-                        .getRepository(ProductOption)
+                        .getRepository(ctx, ProductOption)
                         .findByIds(optionIds);
                     variant.options = selectedOptions;
                 }
                 if (input.facetValueIds) {
-                    variant.facetValues = await this.facetValueService.findByIds(input.facetValueIds);
+                    variant.facetValues = await this.facetValueService.findByIds(ctx, input.facetValueIds);
                 }
                 if (input.trackInventory == null) {
-                    variant.trackInventory = (await this.globalSettingsService.getSettings()).trackInventory;
+                    variant.trackInventory = (
+                        await this.globalSettingsService.getSettings(ctx)
+                    ).trackInventory;
                 }
                 variant.product = { id: input.productId } as any;
                 variant.taxCategory = { id: input.taxCategoryId } as any;
-                await this.assetService.updateFeaturedAsset(variant, input);
+                await this.assetService.updateFeaturedAsset(ctx, variant, input);
             },
             typeOrmSubscriberData: {
                 channelId: ctx.channelId,
                 taxCategoryId: input.taxCategoryId,
             },
         });
-        await this.assetService.updateEntityAssets(createdVariant, input);
+        await this.assetService.updateEntityAssets(ctx, createdVariant, input);
         if (input.stockOnHand != null && input.stockOnHand !== 0) {
             await this.stockMovementService.adjustProductVariantStock(
+                ctx,
                 createdVariant.id,
                 0,
                 input.stockOnHand,
             );
         }
 
-        await this.createProductVariantPrice(createdVariant.id, createdVariant.price, ctx.channelId);
+        await this.createProductVariantPrice(ctx, createdVariant.id, createdVariant.price, ctx.channelId);
         return createdVariant.id;
     }
 
@@ -274,28 +279,33 @@ export class ProductVariantService {
             throw new UserInputError('error.stockonhand-cannot-be-negative');
         }
         await this.translatableSaver.update({
+            ctx,
             input,
             entityType: ProductVariant,
             translationType: ProductVariantTranslation,
             beforeSave: async updatedVariant => {
                 if (input.taxCategoryId) {
-                    const taxCategory = await this.taxCategoryService.findOne(input.taxCategoryId);
+                    const taxCategory = await this.taxCategoryService.findOne(ctx, input.taxCategoryId);
                     if (taxCategory) {
                         updatedVariant.taxCategory = taxCategory;
                     }
                 }
                 if (input.facetValueIds) {
-                    updatedVariant.facetValues = await this.facetValueService.findByIds(input.facetValueIds);
+                    updatedVariant.facetValues = await this.facetValueService.findByIds(
+                        ctx,
+                        input.facetValueIds,
+                    );
                 }
                 if (input.stockOnHand != null) {
                     await this.stockMovementService.adjustProductVariantStock(
+                        ctx,
                         existingVariant.id,
                         existingVariant.stockOnHand,
                         input.stockOnHand,
                     );
                 }
-                await this.assetService.updateFeaturedAsset(updatedVariant, input);
-                await this.assetService.updateEntityAssets(updatedVariant, input);
+                await this.assetService.updateFeaturedAsset(ctx, updatedVariant, input);
+                await this.assetService.updateEntityAssets(ctx, updatedVariant, input);
             },
             typeOrmSubscriberData: {
                 channelId: ctx.channelId,
@@ -303,7 +313,7 @@ export class ProductVariantService {
             },
         });
         if (input.price != null) {
-            const variantPriceRepository = this.connection.getRepository(ProductVariantPrice);
+            const variantPriceRepository = this.connection.getRepository(ctx, ProductVariantPrice);
             const variantPrice = await variantPriceRepository.findOne({
                 where: {
                     variant: input.id,
@@ -323,6 +333,7 @@ export class ProductVariantService {
      * Creates a ProductVariantPrice for the given ProductVariant/Channel combination.
      */
     async createProductVariantPrice(
+        ctx: RequestContext,
         productVariantId: ID,
         price: number,
         channelId: ID,
@@ -332,13 +343,13 @@ export class ProductVariantService {
             channelId,
         });
         variantPrice.variant = new ProductVariant({ id: productVariantId });
-        return this.connection.getRepository(ProductVariantPrice).save(variantPrice);
+        return this.connection.getRepository(ctx, ProductVariantPrice).save(variantPrice);
     }
 
     async softDelete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const variant = await getEntityOrThrow(this.connection, ProductVariant, id);
         variant.deletedAt = new Date();
-        await this.connection.getRepository(ProductVariant).save(variant, { reload: false });
+        await this.connection.getRepository(ctx, ProductVariant).save(variant, { reload: false });
         this.eventBus.publish(new ProductVariantEvent(ctx, [variant], 'deleted'));
         return {
             result: DeletionResult.DELETED,
@@ -447,17 +458,20 @@ export class ProductVariantService {
             .join(glue);
     }
 
-    private async getTaxCategoryForNewVariant(taxCategoryId: ID | null | undefined): Promise<TaxCategory> {
+    private async getTaxCategoryForNewVariant(
+        ctx: RequestContext,
+        taxCategoryId: ID | null | undefined,
+    ): Promise<TaxCategory> {
         let taxCategory: TaxCategory;
         if (taxCategoryId) {
             taxCategory = await getEntityOrThrow(this.connection, TaxCategory, taxCategoryId);
         } else {
-            const taxCategories = await this.taxCategoryService.findAll();
+            const taxCategories = await this.taxCategoryService.findAll(ctx);
             taxCategory = taxCategories[0];
         }
         if (!taxCategory) {
             // there is no TaxCategory set up, so create a default
-            taxCategory = await this.taxCategoryService.create({ name: 'Standard Tax' });
+            taxCategory = await this.taxCategoryService.create(ctx, { name: 'Standard Tax' });
         }
         return taxCategory;
     }

+ 28 - 22
packages/core/src/service/services/product.service.ts

@@ -67,6 +67,7 @@ export class ProductService {
                 relations: this.relations,
                 channelId: ctx.channelId,
                 where: { deletedAt: null },
+                ctx,
             })
             .getManyAndCount()
             .then(async ([products, totalItems]) => {
@@ -112,7 +113,7 @@ export class ProductService {
 
     getFacetValuesForProduct(ctx: RequestContext, productId: ID): Promise<Array<Translated<FacetValue>>> {
         return this.connection
-            .getRepository(Product)
+            .getRepository(ctx, Product)
             .findOne(productId, { relations: ['facetValues', 'facetValues.facet'] })
             .then(variant =>
                 !variant ? [] : variant.facetValues.map(o => translateDeep(o, ctx.languageCode, ['facet'])),
@@ -120,7 +121,7 @@ export class ProductService {
     }
 
     async findOneBySlug(ctx: RequestContext, slug: string): Promise<Translated<Product> | undefined> {
-        const translation = await this.connection.getRepository(ProductTranslation).findOne({
+        const translation = await this.connection.getRepository(ctx, ProductTranslation).findOne({
             relations: ['base'],
             where: {
                 languageCode: ctx.languageCode,
@@ -134,37 +135,39 @@ export class ProductService {
     }
 
     async create(ctx: RequestContext, input: CreateProductInput): Promise<Translated<Product>> {
-        await this.slugValidator.validateSlugs(input, ProductTranslation);
+        await this.slugValidator.validateSlugs(ctx, input, ProductTranslation);
         const product = await this.translatableSaver.create({
+            ctx,
             input,
             entityType: Product,
             translationType: ProductTranslation,
             beforeSave: async p => {
                 this.channelService.assignToCurrentChannel(p, ctx);
                 if (input.facetValueIds) {
-                    p.facetValues = await this.facetValueService.findByIds(input.facetValueIds);
+                    p.facetValues = await this.facetValueService.findByIds(ctx, input.facetValueIds);
                 }
-                await this.assetService.updateFeaturedAsset(p, input);
+                await this.assetService.updateFeaturedAsset(ctx, p, input);
             },
         });
-        await this.assetService.updateEntityAssets(product, input);
+        await this.assetService.updateEntityAssets(ctx, product, input);
         this.eventBus.publish(new ProductEvent(ctx, product, 'created'));
         return assertFound(this.findOne(ctx, product.id));
     }
 
     async update(ctx: RequestContext, input: UpdateProductInput): Promise<Translated<Product>> {
         await getEntityOrThrow(this.connection, Product, input.id);
-        await this.slugValidator.validateSlugs(input, ProductTranslation);
+        await this.slugValidator.validateSlugs(ctx, input, ProductTranslation);
         const product = await this.translatableSaver.update({
+            ctx,
             input,
             entityType: Product,
             translationType: ProductTranslation,
             beforeSave: async p => {
                 if (input.facetValueIds) {
-                    p.facetValues = await this.facetValueService.findByIds(input.facetValueIds);
+                    p.facetValues = await this.facetValueService.findByIds(ctx, input.facetValueIds);
                 }
-                await this.assetService.updateFeaturedAsset(p, input);
-                await this.assetService.updateEntityAssets(p, input);
+                await this.assetService.updateFeaturedAsset(ctx, p, input);
+                await this.assetService.updateEntityAssets(ctx, p, input);
             },
         });
         this.eventBus.publish(new ProductEvent(ctx, product, 'updated'));
@@ -174,7 +177,7 @@ export class ProductService {
     async softDelete(ctx: RequestContext, productId: ID): Promise<DeletionResponse> {
         const product = await getEntityOrThrow(this.connection, Product, productId, ctx.channelId);
         product.deletedAt = new Date();
-        await this.connection.getRepository(Product).save(product, { reload: false });
+        await this.connection.getRepository(ctx, Product).save(product, { reload: false });
         this.eventBus.publish(new ProductEvent(ctx, product, 'deleted'));
         return {
             result: DeletionResult.DELETED,
@@ -194,15 +197,16 @@ export class ProductService {
             throw new ForbiddenError();
         }
         const productsWithVariants = await this.connection
-            .getRepository(Product)
+            .getRepository(ctx, Product)
             .findByIds(input.productIds, {
                 relations: ['variants'],
             });
         const priceFactor = input.priceFactor != null ? input.priceFactor : 1;
         for (const product of productsWithVariants) {
-            await this.channelService.assignToChannels(Product, product.id, [input.channelId]);
+            await this.channelService.assignToChannels(ctx, Product, product.id, [input.channelId]);
             for (const variant of product.variants) {
                 await this.productVariantService.createProductVariantPrice(
+                    ctx,
                     variant.id,
                     variant.price * priceFactor,
                     input.channelId,
@@ -231,9 +235,9 @@ export class ProductService {
         if (idsAreEqual(input.channelId, this.channelService.getDefaultChannel().id)) {
             throw new UserInputError('error.products-cannot-be-removed-from-default-channel');
         }
-        const products = await this.connection.getRepository(Product).findByIds(input.productIds);
+        const products = await this.connection.getRepository(ctx, Product).findByIds(input.productIds);
         for (const product of products) {
-            await this.channelService.removeFromChannels(Product, product.id, [input.channelId]);
+            await this.channelService.removeFromChannels(ctx, Product, product.id, [input.channelId]);
             this.eventBus.publish(new ProductChannelEvent(ctx, product, input.channelId, 'removed'));
         }
         return this.findByIds(
@@ -247,8 +251,10 @@ export class ProductService {
         productId: ID,
         optionGroupId: ID,
     ): Promise<Translated<Product>> {
-        const product = await this.getProductWithOptionGroups(productId);
-        const optionGroup = await this.connection.getRepository(ProductOptionGroup).findOne(optionGroupId);
+        const product = await this.getProductWithOptionGroups(ctx, productId);
+        const optionGroup = await this.connection
+            .getRepository(ctx, ProductOptionGroup)
+            .findOne(optionGroupId);
         if (!optionGroup) {
             throw new EntityNotFoundError('ProductOptionGroup', optionGroupId);
         }
@@ -259,7 +265,7 @@ export class ProductService {
             product.optionGroups = [optionGroup];
         }
 
-        await this.connection.getRepository(Product).save(product, { reload: false });
+        await this.connection.getRepository(ctx, Product).save(product, { reload: false });
         return assertFound(this.findOne(ctx, productId));
     }
 
@@ -268,7 +274,7 @@ export class ProductService {
         productId: ID,
         optionGroupId: ID,
     ): Promise<Translated<Product>> {
-        const product = await this.getProductWithOptionGroups(productId);
+        const product = await this.getProductWithOptionGroups(ctx, productId);
         const optionGroup = product.optionGroups.find(g => idsAreEqual(g.id, optionGroupId));
         if (!optionGroup) {
             throw new EntityNotFoundError('ProductOptionGroup', optionGroupId);
@@ -281,12 +287,12 @@ export class ProductService {
         }
         product.optionGroups = product.optionGroups.filter(g => g.id !== optionGroupId);
 
-        await this.connection.getRepository(Product).save(product, { reload: false });
+        await this.connection.getRepository(ctx, Product).save(product, { reload: false });
         return assertFound(this.findOne(ctx, productId));
     }
 
-    private async getProductWithOptionGroups(productId: ID): Promise<Product> {
-        const product = await this.connection.getRepository(Product).findOne(productId, {
+    private async getProductWithOptionGroups(ctx: RequestContext, productId: ID): Promise<Product> {
+        const product = await this.connection.getRepository(ctx, Product).findOne(productId, {
             relations: ['optionGroups', 'variants', 'variants.options'],
             where: { deletedAt: null },
         });

+ 20 - 12
packages/core/src/service/services/promotion.service.ts

@@ -28,6 +28,7 @@ import { ConfigService } from '../../config/config.service';
 import { PromotionAction } from '../../config/promotion/promotion-action';
 import { PromotionCondition } from '../../config/promotion/promotion-condition';
 import { Order } from '../../entity/order/order.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { Promotion } from '../../entity/promotion/promotion.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
@@ -64,6 +65,7 @@ export class PromotionService {
                 where: { deletedAt: null },
                 channelId: ctx.channelId,
                 relations: ['channels'],
+                ctx,
             })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
@@ -110,7 +112,7 @@ export class PromotionService {
         });
         this.validatePromotionConditions(promotion);
         this.channelService.assignToCurrentChannel(promotion, ctx);
-        const newPromotion = await this.connection.getRepository(Promotion).save(promotion);
+        const newPromotion = await this.connection.getRepository(ctx, Promotion).save(promotion);
         await this.updatePromotions();
         return assertFound(this.findOne(ctx, newPromotion.id));
     }
@@ -126,21 +128,23 @@ export class PromotionService {
         }
         this.validatePromotionConditions(updatedPromotion);
         promotion.priorityScore = this.calculatePriorityScore(input);
-        await this.connection.getRepository(Promotion).save(updatedPromotion, { reload: false });
+        await this.connection.getRepository(ctx, Promotion).save(updatedPromotion, { reload: false });
         await this.updatePromotions();
         return assertFound(this.findOne(ctx, updatedPromotion.id));
     }
 
-    async softDeletePromotion(promotionId: ID): Promise<DeletionResponse> {
+    async softDeletePromotion(ctx: RequestContext, promotionId: ID): Promise<DeletionResponse> {
         await getEntityOrThrow(this.connection, Promotion, promotionId);
-        await this.connection.getRepository(Promotion).update({ id: promotionId }, { deletedAt: new Date() });
+        await this.connection
+            .getRepository(ctx, Promotion)
+            .update({ id: promotionId }, { deletedAt: new Date() });
         return {
             result: DeletionResult.DELETED,
         };
     }
 
-    async validateCouponCode(couponCode: string, customerId?: ID): Promise<Promotion> {
-        const promotion = await this.connection.getRepository(Promotion).findOne({
+    async validateCouponCode(ctx: RequestContext, couponCode: string, customerId?: ID): Promise<Promotion> {
+        const promotion = await this.connection.getRepository(ctx, Promotion).findOne({
             where: {
                 couponCode,
                 enabled: true,
@@ -154,7 +158,7 @@ export class PromotionService {
             throw new CouponCodeExpiredError(couponCode);
         }
         if (customerId && promotion.perCustomerUsageLimit != null) {
-            const usageCount = await this.countPromotionUsagesForCustomer(promotion.id, customerId);
+            const usageCount = await this.countPromotionUsagesForCustomer(ctx, promotion.id, customerId);
             if (promotion.perCustomerUsageLimit <= usageCount) {
                 throw new CouponCodeLimitError(promotion.perCustomerUsageLimit);
             }
@@ -162,7 +166,7 @@ export class PromotionService {
         return promotion;
     }
 
-    async addPromotionsToOrder(order: Order): Promise<Order> {
+    async addPromotionsToOrder(ctx: RequestContext, order: Order): Promise<Order> {
         const allAdjustments: Adjustment[] = [];
         for (const line of order.lines) {
             allAdjustments.push(...line.adjustments);
@@ -172,14 +176,18 @@ export class PromotionService {
             .filter(a => a.type === AdjustmentType.PROMOTION)
             .map(a => AdjustmentSource.decodeSourceId(a.adjustmentSource).id);
         const promotionIds = unique(allPromotionIds);
-        const promotions = await this.connection.getRepository(Promotion).findByIds(promotionIds);
+        const promotions = await this.connection.getRepository(ctx, Promotion).findByIds(promotionIds);
         order.promotions = promotions;
-        return this.connection.getRepository(Order).save(order);
+        return this.connection.getRepository(ctx, Order).save(order);
     }
 
-    private async countPromotionUsagesForCustomer(promotionId: ID, customerId: ID): Promise<number> {
+    private async countPromotionUsagesForCustomer(
+        ctx: RequestContext,
+        promotionId: ID,
+        customerId: ID,
+    ): Promise<number> {
         const qb = this.connection
-            .getRepository(Order)
+            .getRepository(ctx, Order)
             .createQueryBuilder('order')
             .leftJoin('order.promotions', 'promotion')
             .where('promotion.id = :promotionId', { promotionId })

+ 19 - 16
packages/core/src/service/services/role.service.ts

@@ -25,6 +25,7 @@ import {
 import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
 import { Channel } from '../../entity/channel/channel.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { Role } from '../../entity/role/role.entity';
 import { User } from '../../entity/user/user.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
@@ -48,9 +49,9 @@ export class RoleService {
         await this.ensureCustomerRoleExists();
     }
 
-    findAll(options?: ListQueryOptions<Role>): Promise<PaginatedList<Role>> {
+    findAll(ctx: RequestContext, options?: ListQueryOptions<Role>): Promise<PaginatedList<Role>> {
         return this.listQueryBuilder
-            .build(Role, options, { relations: ['channels'] })
+            .build(Role, options, { relations: ['channels'], ctx })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,
@@ -58,14 +59,14 @@ export class RoleService {
             }));
     }
 
-    findOne(roleId: ID): Promise<Role | undefined> {
-        return this.connection.getRepository(Role).findOne(roleId, {
+    findOne(ctx: RequestContext, roleId: ID): Promise<Role | undefined> {
+        return this.connection.getRepository(ctx, Role).findOne(roleId, {
             relations: ['channels'],
         });
     }
 
-    getChannelsForRole(roleId: ID): Promise<Channel[]> {
-        return this.findOne(roleId).then(role => (role ? role.channels : []));
+    getChannelsForRole(ctx: RequestContext, roleId: ID): Promise<Channel[]> {
+        return this.findOne(ctx, roleId).then(role => (role ? role.channels : []));
     }
 
     getSuperAdminRole(): Promise<Role> {
@@ -127,12 +128,12 @@ export class RoleService {
         } else {
             targetChannels = [ctx.channel];
         }
-        return this.createRoleForChannels(input, targetChannels);
+        return this.createRoleForChannels(ctx, input, targetChannels);
     }
 
     async update(ctx: RequestContext, input: UpdateRoleInput): Promise<Role> {
         this.checkPermissionsAreValid(input.permissions);
-        const role = await this.findOne(input.id);
+        const role = await this.findOne(ctx, input.id);
         if (!role) {
             throw new EntityNotFoundError('Role', input.id);
         }
@@ -149,26 +150,26 @@ export class RoleService {
         if (input.channelIds && ctx.activeUserId) {
             updatedRole.channels = await this.getPermittedChannels(input.channelIds, ctx.activeUserId);
         }
-        await this.connection.getRepository(Role).save(updatedRole, { reload: false });
-        return assertFound(this.findOne(role.id));
+        await this.connection.getRepository(ctx, Role).save(updatedRole, { reload: false });
+        return assertFound(this.findOne(ctx, role.id));
     }
 
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
-        const role = await this.findOne(id);
+        const role = await this.findOne(ctx, id);
         if (!role) {
             throw new EntityNotFoundError('Role', id);
         }
         if (role.code === SUPER_ADMIN_ROLE_CODE || role.code === CUSTOMER_ROLE_CODE) {
             throw new InternalServerError(`error.cannot-delete-role`, { roleCode: role.code });
         }
-        await this.connection.getRepository(Role).remove(role);
+        await this.connection.getRepository(ctx, Role).remove(role);
         return {
             result: DeletionResult.DELETED,
         };
     }
 
-    async assignRoleToChannel(roleId: ID, channelId: ID) {
-        await this.channelService.assignToChannels(Role, roleId, [channelId]);
+    async assignRoleToChannel(ctx: RequestContext, roleId: ID, channelId: ID) {
+        await this.channelService.assignToChannels(ctx, Role, roleId, [channelId]);
     }
 
     private async getPermittedChannels(channelIds: ID[], activeUserId: ID): Promise<Channel[]> {
@@ -222,6 +223,7 @@ export class RoleService {
             }
         } catch (err) {
             await this.createRoleForChannels(
+                RequestContext.empty(),
                 {
                     code: SUPER_ADMIN_ROLE_CODE,
                     description: SUPER_ADMIN_ROLE_DESCRIPTION,
@@ -237,6 +239,7 @@ export class RoleService {
             await this.getCustomerRole();
         } catch (err) {
             await this.createRoleForChannels(
+                RequestContext.empty(),
                 {
                     code: CUSTOMER_ROLE_CODE,
                     description: CUSTOMER_ROLE_DESCRIPTION,
@@ -247,13 +250,13 @@ export class RoleService {
         }
     }
 
-    private createRoleForChannels(input: CreateRoleInput, channels: Channel[]) {
+    private createRoleForChannels(ctx: RequestContext, input: CreateRoleInput, channels: Channel[]) {
         const role = new Role({
             code: input.code,
             description: input.description,
             permissions: unique([Permission.Authenticated, ...input.permissions]),
         });
         role.channels = channels;
-        return this.connection.getRepository(Role).save(role);
+        return this.connection.getRepository(ctx, Role).save(role);
     }
 }

+ 16 - 14
packages/core/src/service/services/session.service.ts

@@ -1,12 +1,10 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { ID } from '@vendure/common/lib/shared-types';
 import crypto from 'crypto';
 import ms from 'ms';
-import { Connection, EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
+import { EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
-import { AuthenticationStrategy } from '../../config/auth/authentication-strategy';
 import { ConfigService } from '../../config/config.service';
 import { CachedSession, SessionCacheStrategy } from '../../config/session-cache/session-cache-strategy';
 import { Channel } from '../../entity/channel/channel.entity';
@@ -75,7 +73,7 @@ export class SessionService implements EntitySubscriberInterface {
                 : undefined;
         const existingOrder = await this.orderService.getActiveOrderForUser(ctx, user.id);
         const activeOrder = await this.orderService.mergeOrders(ctx, user, guestOrder, existingOrder);
-        const authenticatedSession = await this.connection.getRepository(AuthenticatedSession).save(
+        const authenticatedSession = await this.connection.getRepository(ctx, AuthenticatedSession).save(
             new AuthenticatedSession({
                 token,
                 user,
@@ -167,13 +165,17 @@ export class SessionService implements EntitySubscriberInterface {
         }
     }
 
-    async setActiveOrder(serializedSession: CachedSession, order: Order): Promise<CachedSession> {
+    async setActiveOrder(
+        ctx: RequestContext,
+        serializedSession: CachedSession,
+        order: Order,
+    ): Promise<CachedSession> {
         const session = await this.connection
             .getRepository(Session)
             .findOne(serializedSession.id, { relations: ['user', 'user.roles', 'user.roles.channels'] });
         if (session) {
             session.activeOrder = order;
-            await this.connection.getRepository(Session).save(session, { reload: false });
+            await this.connection.getRepository(ctx, Session).save(session, { reload: false });
             const updatedSerializedSession = this.serializeSession(session);
             await this.sessionCacheStrategy.set(updatedSerializedSession);
             return updatedSerializedSession;
@@ -181,14 +183,14 @@ export class SessionService implements EntitySubscriberInterface {
         return serializedSession;
     }
 
-    async unsetActiveOrder(serializedSession: CachedSession): Promise<CachedSession> {
+    async unsetActiveOrder(ctx: RequestContext, serializedSession: CachedSession): Promise<CachedSession> {
         if (serializedSession.activeOrderId) {
             const session = await this.connection
                 .getRepository(Session)
                 .findOne(serializedSession.id, { relations: ['user', 'user.roles', 'user.roles.channels'] });
             if (session) {
                 session.activeOrder = null;
-                await this.connection.getRepository(Session).save(session);
+                await this.connection.getRepository(ctx, Session).save(session);
                 const updatedSerializedSession = this.serializeSession(session);
                 await this.configService.authOptions.sessionCacheStrategy.set(updatedSerializedSession);
                 return updatedSerializedSession;
@@ -200,11 +202,11 @@ export class SessionService implements EntitySubscriberInterface {
     /**
      * Deletes all existing sessions for the given user.
      */
-    async deleteSessionsByUser(user: User): Promise<void> {
+    async deleteSessionsByUser(ctx: RequestContext, user: User): Promise<void> {
         const userSessions = await this.connection
-            .getRepository(AuthenticatedSession)
+            .getRepository(ctx, AuthenticatedSession)
             .find({ where: { user } });
-        await this.connection.getRepository(AuthenticatedSession).remove(userSessions);
+        await this.connection.getRepository(ctx, AuthenticatedSession).remove(userSessions);
         for (const session of userSessions) {
             await this.sessionCacheStrategy.delete(session.token);
         }
@@ -213,9 +215,9 @@ export class SessionService implements EntitySubscriberInterface {
     /**
      * Deletes all existing sessions with the given activeOrder.
      */
-    async deleteSessionsByActiveOrderId(activeOrderId: ID): Promise<void> {
-        const sessions = await this.connection.getRepository(Session).find({ where: { activeOrderId } });
-        await this.connection.getRepository(Session).remove(sessions);
+    async deleteSessionsByActiveOrderId(ctx: RequestContext, activeOrderId: ID): Promise<void> {
+        const sessions = await this.connection.getRepository(ctx, Session).find({ where: { activeOrderId } });
+        await this.connection.getRepository(ctx, Session).remove(sessions);
         for (const session of sessions) {
             await this.sessionCacheStrategy.delete(session.token);
         }

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

@@ -15,6 +15,7 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
 import { Channel } from '../../entity/channel/channel.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ShippingMethod } from '../../entity/shipping-method/shipping-method.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { ShippingConfiguration } from '../helpers/shipping-configuration/shipping-configuration';
@@ -38,7 +39,7 @@ export class ShippingMethodService {
     ) {}
 
     async initShippingMethods() {
-        await this.updateActiveShippingMethods();
+        await this.updateActiveShippingMethods(RequestContext.empty());
     }
 
     findAll(
@@ -50,6 +51,7 @@ export class ShippingMethodService {
                 relations: ['channels'],
                 where: { deletedAt: null },
                 channelId: ctx.channelId,
+                ctx,
             })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
@@ -73,8 +75,10 @@ export class ShippingMethodService {
             calculator: this.shippingConfiguration.parseCalculatorInput(input.calculator),
         });
         this.channelService.assignToCurrentChannel(shippingMethod, ctx);
-        const newShippingMethod = await this.connection.getRepository(ShippingMethod).save(shippingMethod);
-        await this.updateActiveShippingMethods();
+        const newShippingMethod = await this.connection
+            .getRepository(ctx, ShippingMethod)
+            .save(shippingMethod);
+        await this.updateActiveShippingMethods(ctx);
         return assertFound(this.findOne(ctx, newShippingMethod.id));
     }
 
@@ -92,8 +96,10 @@ export class ShippingMethodService {
                 input.calculator,
             );
         }
-        await this.connection.getRepository(ShippingMethod).save(updatedShippingMethod, { reload: false });
-        await this.updateActiveShippingMethods();
+        await this.connection
+            .getRepository(ctx, ShippingMethod)
+            .save(updatedShippingMethod, { reload: false });
+        await this.updateActiveShippingMethods(ctx);
         return assertFound(this.findOne(ctx, shippingMethod.id));
     }
 
@@ -102,8 +108,8 @@ export class ShippingMethodService {
             where: { deletedAt: null },
         });
         shippingMethod.deletedAt = new Date();
-        await this.connection.getRepository(ShippingMethod).save(shippingMethod, { reload: false });
-        await this.updateActiveShippingMethods();
+        await this.connection.getRepository(ctx, ShippingMethod).save(shippingMethod, { reload: false });
+        await this.updateActiveShippingMethods(ctx);
         return {
             result: DeletionResult.DELETED,
         };
@@ -121,8 +127,8 @@ export class ShippingMethodService {
         return this.activeShippingMethods.filter(sm => sm.channels.find(c => c.id === channel.id));
     }
 
-    private async updateActiveShippingMethods() {
-        this.activeShippingMethods = await this.connection.getRepository(ShippingMethod).find({
+    private async updateActiveShippingMethods(ctx: RequestContext) {
+        this.activeShippingMethods = await this.connection.getRepository(ctx, ShippingMethod).find({
             relations: ['channels'],
             where: { deletedAt: null },
         });

+ 15 - 9
packages/core/src/service/services/stock-movement.service.ts

@@ -10,6 +10,7 @@ import { ShippingCalculator } from '../../config/shipping-method/shipping-calcul
 import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
 import { OrderItem } from '../../entity/order-item/order-item.entity';
 import { Order } from '../../entity/order/order.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { ShippingMethod } from '../../entity/shipping-method/shipping-method.entity';
 import { Cancellation } from '../../entity/stock-movement/cancellation.entity';
@@ -33,7 +34,7 @@ export class StockMovementService {
         options: StockMovementListOptions,
     ): Promise<PaginatedList<StockMovement>> {
         return this.listQueryBuilder
-            .build<StockMovement>(StockMovement as any, options)
+            .build<StockMovement>(StockMovement as any, options, { ctx })
             .leftJoin('stockmovement.productVariant', 'productVariant')
             .andWhere('productVariant.id = :productVariantId', { productVariantId })
             .getManyAndCount()
@@ -46,6 +47,7 @@ export class StockMovementService {
     }
 
     async adjustProductVariantStock(
+        ctx: RequestContext,
         productVariantId: ID,
         oldStockLevel: number,
         newStockLevel: number,
@@ -59,10 +61,10 @@ export class StockMovementService {
             quantity: delta,
             productVariant: { id: productVariantId },
         });
-        return this.connection.getRepository(StockAdjustment).save(adjustment);
+        return this.connection.getRepository(ctx, StockAdjustment).save(adjustment);
     }
 
-    async createSalesForOrder(order: Order): Promise<Sale[]> {
+    async createSalesForOrder(ctx: RequestContext, order: Order): Promise<Sale[]> {
         if (order.active !== false) {
             throw new InternalServerError('error.cannot-create-sales-for-active-order');
         }
@@ -78,14 +80,16 @@ export class StockMovementService {
 
             if (productVariant.trackInventory === true) {
                 productVariant.stockOnHand -= line.quantity;
-                await this.connection.getRepository(ProductVariant).save(productVariant, { reload: false });
+                await this.connection
+                    .getRepository(ctx, ProductVariant)
+                    .save(productVariant, { reload: false });
             }
         }
-        return this.connection.getRepository(Sale).save(sales);
+        return this.connection.getRepository(ctx, Sale).save(sales);
     }
 
-    async createCancellationsForOrderItems(items: OrderItem[]): Promise<Cancellation[]> {
-        const orderItems = await this.connection.getRepository(OrderItem).findByIds(
+    async createCancellationsForOrderItems(ctx: RequestContext, items: OrderItem[]): Promise<Cancellation[]> {
+        const orderItems = await this.connection.getRepository(ctx, OrderItem).findByIds(
             items.map(i => i.id),
             {
                 relations: ['line', 'line.productVariant'],
@@ -112,9 +116,11 @@ export class StockMovementService {
 
             if (productVariant.trackInventory === true) {
                 productVariant.stockOnHand += 1;
-                await this.connection.getRepository(ProductVariant).save(productVariant, { reload: false });
+                await this.connection
+                    .getRepository(ctx, ProductVariant)
+                    .save(productVariant, { reload: false });
             }
         }
-        return this.connection.getRepository(Cancellation).save(cancellations);
+        return this.connection.getRepository(ctx, Cancellation).save(cancellations);
     }
 }

+ 14 - 13
packages/core/src/service/services/tax-category.service.ts

@@ -10,6 +10,7 @@ import { ID } from '@vendure/common/lib/shared-types';
 import { RequestContext } from '../../api/common/request-context';
 import { EntityNotFoundError } from '../../common/error/errors';
 import { assertFound } from '../../common/utils';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { TaxCategory } from '../../entity/tax-category/tax-category.entity';
 import { TaxRate } from '../../entity/tax-rate/tax-rate.entity';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
@@ -20,34 +21,34 @@ import { TransactionalConnection } from '../transaction/transactional-connection
 export class TaxCategoryService {
     constructor(private connection: TransactionalConnection) {}
 
-    findAll(): Promise<TaxCategory[]> {
-        return this.connection.getRepository(TaxCategory).find();
+    findAll(ctx: RequestContext): Promise<TaxCategory[]> {
+        return this.connection.getRepository(ctx, TaxCategory).find();
     }
 
-    findOne(taxCategoryId: ID): Promise<TaxCategory | undefined> {
-        return this.connection.getRepository(TaxCategory).findOne(taxCategoryId);
+    findOne(ctx: RequestContext, taxCategoryId: ID): Promise<TaxCategory | undefined> {
+        return this.connection.getRepository(ctx, TaxCategory).findOne(taxCategoryId);
     }
 
-    async create(input: CreateTaxCategoryInput): Promise<TaxCategory> {
+    async create(ctx: RequestContext, input: CreateTaxCategoryInput): Promise<TaxCategory> {
         const taxCategory = new TaxCategory(input);
-        const newTaxCategory = await this.connection.getRepository(TaxCategory).save(taxCategory);
-        return assertFound(this.findOne(newTaxCategory.id));
+        const newTaxCategory = await this.connection.getRepository(ctx, TaxCategory).save(taxCategory);
+        return assertFound(this.findOne(ctx, newTaxCategory.id));
     }
 
-    async update(input: UpdateTaxCategoryInput): Promise<TaxCategory> {
-        const taxCategory = await this.findOne(input.id);
+    async update(ctx: RequestContext, input: UpdateTaxCategoryInput): Promise<TaxCategory> {
+        const taxCategory = await this.findOne(ctx, input.id);
         if (!taxCategory) {
             throw new EntityNotFoundError('TaxCategory', input.id);
         }
         const updatedTaxCategory = patchEntity(taxCategory, input);
-        await this.connection.getRepository(TaxCategory).save(updatedTaxCategory, { reload: false });
-        return assertFound(this.findOne(taxCategory.id));
+        await this.connection.getRepository(ctx, TaxCategory).save(updatedTaxCategory, { reload: false });
+        return assertFound(this.findOne(ctx, taxCategory.id));
     }
 
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const taxCategory = await getEntityOrThrow(this.connection, TaxCategory, id);
         const dependentRates = await this.connection
-            .getRepository(TaxRate)
+            .getRepository(ctx, TaxRate)
             .count({ where: { category: id } });
 
         if (0 < dependentRates) {
@@ -62,7 +63,7 @@ export class TaxCategoryService {
         }
 
         try {
-            await this.connection.getRepository(TaxCategory).remove(taxCategory);
+            await this.connection.getRepository(ctx, TaxCategory).remove(taxCategory);
             return {
                 result: DeletionResult.DELETED,
             };

+ 16 - 16
packages/core/src/service/services/tax-rate.service.ts

@@ -48,12 +48,12 @@ export class TaxRateService {
     ) {}
 
     async initTaxRates() {
-        return this.updateActiveTaxRates();
+        return this.updateActiveTaxRates(RequestContext.empty());
     }
 
-    findAll(options?: ListQueryOptions<TaxRate>): Promise<PaginatedList<TaxRate>> {
+    findAll(ctx: RequestContext, options?: ListQueryOptions<TaxRate>): Promise<PaginatedList<TaxRate>> {
         return this.listQueryBuilder
-            .build(TaxRate, options, { relations: ['category', 'zone', 'customerGroup'] })
+            .build(TaxRate, options, { relations: ['category', 'zone', 'customerGroup'], ctx })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,
@@ -61,8 +61,8 @@ export class TaxRateService {
             }));
     }
 
-    findOne(taxRateId: ID): Promise<TaxRate | undefined> {
-        return this.connection.getRepository(TaxRate).findOne(taxRateId, {
+    findOne(ctx: RequestContext, taxRateId: ID): Promise<TaxRate | undefined> {
+        return this.connection.getRepository(ctx, TaxRate).findOne(taxRateId, {
             relations: ['category', 'zone', 'customerGroup'],
         });
     }
@@ -78,15 +78,15 @@ export class TaxRateService {
                 input.customerGroupId,
             );
         }
-        const newTaxRate = await this.connection.getRepository(TaxRate).save(taxRate);
-        await this.updateActiveTaxRates();
+        const newTaxRate = await this.connection.getRepository(ctx, TaxRate).save(taxRate);
+        await this.updateActiveTaxRates(ctx);
         await this.workerService.send(new TaxRateUpdatedMessage(newTaxRate.id)).toPromise();
         this.eventBus.publish(new TaxRateModificationEvent(ctx, newTaxRate));
-        return assertFound(this.findOne(newTaxRate.id));
+        return assertFound(this.findOne(ctx, newTaxRate.id));
     }
 
     async update(ctx: RequestContext, input: UpdateTaxRateInput): Promise<TaxRate> {
-        const taxRate = await this.findOne(input.id);
+        const taxRate = await this.findOne(ctx, input.id);
         if (!taxRate) {
             throw new EntityNotFoundError('TaxRate', input.id);
         }
@@ -104,17 +104,17 @@ export class TaxRateService {
                 input.customerGroupId,
             );
         }
-        await this.connection.getRepository(TaxRate).save(updatedTaxRate, { reload: false });
-        await this.updateActiveTaxRates();
+        await this.connection.getRepository(ctx, TaxRate).save(updatedTaxRate, { reload: false });
+        await this.updateActiveTaxRates(ctx);
         await this.workerService.send(new TaxRateUpdatedMessage(updatedTaxRate.id)).toPromise();
         this.eventBus.publish(new TaxRateModificationEvent(ctx, updatedTaxRate));
-        return assertFound(this.findOne(taxRate.id));
+        return assertFound(this.findOne(ctx, taxRate.id));
     }
 
-    async delete(id: ID): Promise<DeletionResponse> {
+    async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const taxRate = await getEntityOrThrow(this.connection, TaxRate, id);
         try {
-            await this.connection.getRepository(TaxRate).remove(taxRate);
+            await this.connection.getRepository(ctx, TaxRate).remove(taxRate);
             return {
                 result: DeletionResult.DELETED,
             };
@@ -135,8 +135,8 @@ export class TaxRateService {
         return rate || this.defaultTaxRate;
     }
 
-    async updateActiveTaxRates() {
-        this.activeTaxRates = await this.connection.getRepository(TaxRate).find({
+    async updateActiveTaxRates(ctx: RequestContext) {
+        this.activeTaxRates = await this.connection.getRepository(ctx, TaxRate).find({
             relations: ['category', 'zone', 'customerGroup'],
             where: {
                 enabled: true,

+ 67 - 42
packages/core/src/service/services/user.service.ts

@@ -1,6 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { ID } from '@vendure/common/lib/shared-types';
 
+import { RequestContext } from '../../api/common/request-context';
 import {
     IdentifierChangeTokenError,
     IdentifierChangeTokenExpiredError,
@@ -12,6 +13,7 @@ import {
 } from '../../common/error/errors';
 import { ConfigService } from '../../config/config.service';
 import { NativeAuthenticationMethod } from '../../entity/authentication-method/native-authentication-method.entity';
+import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { User } from '../../entity/user/user.entity';
 import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
@@ -30,14 +32,14 @@ export class UserService {
         private verificationTokenGenerator: VerificationTokenGenerator,
     ) {}
 
-    async getUserById(userId: ID): Promise<User | undefined> {
-        return this.connection.getRepository(User).findOne(userId, {
+    async getUserById(ctx: RequestContext, userId: ID): Promise<User | undefined> {
+        return this.connection.getRepository(ctx, User).findOne(userId, {
             relations: ['roles', 'roles.channels', 'authenticationMethods'],
         });
     }
 
-    async getUserByEmailAddress(emailAddress: string): Promise<User | undefined> {
-        return this.connection.getRepository(User).findOne({
+    async getUserByEmailAddress(ctx: RequestContext, emailAddress: string): Promise<User | undefined> {
+        return this.connection.getRepository(ctx, User).findOne({
             where: {
                 identifier: emailAddress,
                 deletedAt: null,
@@ -46,17 +48,22 @@ export class UserService {
         });
     }
 
-    async createCustomerUser(identifier: string, password?: string): Promise<User> {
+    async createCustomerUser(ctx: RequestContext, identifier: string, password?: string): Promise<User> {
         const user = new User();
         user.identifier = identifier;
         const customerRole = await this.roleService.getCustomerRole();
         user.roles = [customerRole];
         return this.connection
-            .getRepository(User)
-            .save(await this.addNativeAuthenticationMethod(user, identifier, password));
+            .getRepository(ctx, User)
+            .save(await this.addNativeAuthenticationMethod(ctx, user, identifier, password));
     }
 
-    async addNativeAuthenticationMethod(user: User, identifier: string, password?: string): Promise<User> {
+    async addNativeAuthenticationMethod(
+        ctx: RequestContext,
+        user: User,
+        identifier: string,
+        password?: string,
+    ): Promise<User> {
         const authenticationMethod = new NativeAuthenticationMethod();
         if (this.configService.authOptions.requireVerification) {
             authenticationMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
@@ -70,42 +77,48 @@ export class UserService {
             authenticationMethod.passwordHash = '';
         }
         authenticationMethod.identifier = identifier;
-        await this.connection.getRepository(NativeAuthenticationMethod).save(authenticationMethod);
+        await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(authenticationMethod);
         user.authenticationMethods = [...(user.authenticationMethods ?? []), authenticationMethod];
         return user;
     }
 
-    async createAdminUser(identifier: string, password: string): Promise<User> {
+    async createAdminUser(ctx: RequestContext, identifier: string, password: string): Promise<User> {
         const user = new User({
             identifier,
             verified: true,
         });
-        const authenticationMethod = await this.connection.getRepository(NativeAuthenticationMethod).save(
-            new NativeAuthenticationMethod({
-                identifier,
-                passwordHash: await this.passwordCipher.hash(password),
-            }),
-        );
+        const authenticationMethod = await this.connection
+            .getRepository(ctx, NativeAuthenticationMethod)
+            .save(
+                new NativeAuthenticationMethod({
+                    identifier,
+                    passwordHash: await this.passwordCipher.hash(password),
+                }),
+            );
         user.authenticationMethods = [authenticationMethod];
-        return this.connection.getRepository(User).save(user);
+        return this.connection.getRepository(ctx, User).save(user);
     }
 
-    async softDelete(userId: ID) {
+    async softDelete(ctx: RequestContext, userId: ID) {
         await getEntityOrThrow(this.connection, User, userId);
-        await this.connection.getRepository(User).update({ id: userId }, { deletedAt: new Date() });
+        await this.connection.getRepository(ctx, User).update({ id: userId }, { deletedAt: new Date() });
     }
 
-    async setVerificationToken(user: User): Promise<User> {
+    async setVerificationToken(ctx: RequestContext, user: User): Promise<User> {
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
         user.verified = false;
-        await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
-        return this.connection.getRepository(User).save(user);
+        await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
+        return this.connection.getRepository(ctx, User).save(user);
     }
 
-    async verifyUserByToken(verificationToken: string, password?: string): Promise<User | undefined> {
+    async verifyUserByToken(
+        ctx: RequestContext,
+        verificationToken: string,
+        password?: string,
+    ): Promise<User | undefined> {
         const user = await this.connection
-            .getRepository(User)
+            .getRepository(ctx, User)
             .createQueryBuilder('user')
             .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethod')
             .addSelect('authenticationMethod.passwordHash')
@@ -126,28 +139,32 @@ export class UserService {
                 }
                 nativeAuthMethod.verificationToken = null;
                 user.verified = true;
-                await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
-                return this.connection.getRepository(User).save(user);
+                await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
+                return this.connection.getRepository(ctx, User).save(user);
             } else {
                 throw new VerificationTokenExpiredError();
             }
         }
     }
 
-    async setPasswordResetToken(emailAddress: string): Promise<User | undefined> {
-        const user = await this.getUserByEmailAddress(emailAddress);
+    async setPasswordResetToken(ctx: RequestContext, emailAddress: string): Promise<User | undefined> {
+        const user = await this.getUserByEmailAddress(ctx, emailAddress);
         if (!user) {
             return;
         }
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.passwordResetToken = await this.verificationTokenGenerator.generateVerificationToken();
-        await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
+        await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
         return user;
     }
 
-    async resetPasswordByToken(passwordResetToken: string, password: string): Promise<User | undefined> {
+    async resetPasswordByToken(
+        ctx: RequestContext,
+        passwordResetToken: string,
+        password: string,
+    ): Promise<User | undefined> {
         const user = await this.connection
-            .getRepository(User)
+            .getRepository(ctx, User)
             .createQueryBuilder('user')
             .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethod')
             .where('authenticationMethod.passwordResetToken = :passwordResetToken', { passwordResetToken })
@@ -157,17 +174,20 @@ export class UserService {
                 const nativeAuthMethod = user.getNativeAuthenticationMethod();
                 nativeAuthMethod.passwordHash = await this.passwordCipher.hash(password);
                 nativeAuthMethod.passwordResetToken = null;
-                await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
-                return this.connection.getRepository(User).save(user);
+                await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
+                return this.connection.getRepository(ctx, User).save(user);
             } else {
                 throw new PasswordResetTokenExpiredError();
             }
         }
     }
 
-    async changeIdentifierByToken(token: string): Promise<{ user: User; oldIdentifier: string }> {
+    async changeIdentifierByToken(
+        ctx: RequestContext,
+        token: string,
+    ): Promise<{ user: User; oldIdentifier: string }> {
         const user = await this.connection
-            .getRepository(User)
+            .getRepository(ctx, User)
             .createQueryBuilder('user')
             .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethod')
             .where('authenticationMethod.identifierChangeToken = :identifierChangeToken', {
@@ -191,15 +211,20 @@ export class UserService {
         nativeAuthMethod.identifierChangeToken = null;
         nativeAuthMethod.pendingIdentifier = null;
         await this.connection
-            .getRepository(NativeAuthenticationMethod)
+            .getRepository(ctx, NativeAuthenticationMethod)
             .save(nativeAuthMethod, { reload: false });
-        await this.connection.getRepository(User).save(user, { reload: false });
+        await this.connection.getRepository(ctx, User).save(user, { reload: false });
         return { user, oldIdentifier };
     }
 
-    async updatePassword(userId: ID, currentPassword: string, newPassword: string): Promise<boolean> {
+    async updatePassword(
+        ctx: RequestContext,
+        userId: ID,
+        currentPassword: string,
+        newPassword: string,
+    ): Promise<boolean> {
         const user = await this.connection
-            .getRepository(User)
+            .getRepository(ctx, User)
             .createQueryBuilder('user')
             .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethods')
             .addSelect('authenticationMethods.passwordHash')
@@ -215,15 +240,15 @@ export class UserService {
         }
         nativeAuthMethod.passwordHash = await this.passwordCipher.hash(newPassword);
         await this.connection
-            .getRepository(NativeAuthenticationMethod)
+            .getRepository(ctx, NativeAuthenticationMethod)
             .save(nativeAuthMethod, { reload: false });
         return true;
     }
 
-    async setIdentifierChangeToken(user: User): Promise<User> {
+    async setIdentifierChangeToken(ctx: RequestContext, user: User): Promise<User> {
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.identifierChangeToken = this.verificationTokenGenerator.generateVerificationToken();
-        await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
+        await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
         return user;
     }
 }

+ 13 - 13
packages/core/src/service/services/zone.service.ts

@@ -12,7 +12,7 @@ import { unique } from '@vendure/common/lib/unique';
 
 import { RequestContext } from '../../api/common/request-context';
 import { assertFound } from '../../common/utils';
-import { Channel, TaxRate } from '../../entity';
+import { Channel, ProductOptionGroup, TaxRate } from '../../entity';
 import { Country } from '../../entity/country/country.entity';
 import { Zone } from '../../entity/zone/zone.entity';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
@@ -41,7 +41,7 @@ export class ZoneService implements OnModuleInit {
 
     findOne(ctx: RequestContext, zoneId: ID): Promise<Zone | undefined> {
         return this.connection
-            .getRepository(Zone)
+            .getRepository(ctx, Zone)
             .findOne(zoneId, {
                 relations: ['members'],
             })
@@ -56,9 +56,9 @@ export class ZoneService implements OnModuleInit {
     async create(ctx: RequestContext, input: CreateZoneInput): Promise<Zone> {
         const zone = new Zone(input);
         if (input.memberIds) {
-            zone.members = await this.getCountriesFromIds(input.memberIds);
+            zone.members = await this.getCountriesFromIds(ctx, input.memberIds);
         }
-        const newZone = await this.connection.getRepository(Zone).save(zone);
+        const newZone = await this.connection.getRepository(ctx, Zone).save(zone);
         await this.updateZonesCache();
         return assertFound(this.findOne(ctx, newZone.id));
     }
@@ -66,7 +66,7 @@ export class ZoneService implements OnModuleInit {
     async update(ctx: RequestContext, input: UpdateZoneInput): Promise<Zone> {
         const zone = await getEntityOrThrow(this.connection, Zone, input.id);
         const updatedZone = patchEntity(zone, input);
-        await this.connection.getRepository(Zone).save(updatedZone, { reload: false });
+        await this.connection.getRepository(ctx, Zone).save(updatedZone, { reload: false });
         await this.updateZonesCache();
         return assertFound(this.findOne(ctx, zone.id));
     }
@@ -75,7 +75,7 @@ export class ZoneService implements OnModuleInit {
         const zone = await getEntityOrThrow(this.connection, Zone, id);
 
         const channelsUsingZone = await this.connection
-            .getRepository(Channel)
+            .getRepository(ctx, Channel)
             .createQueryBuilder('channel')
             .where('channel.defaultTaxZone = :id', { id })
             .orWhere('channel.defaultShippingZone = :id', { id })
@@ -91,7 +91,7 @@ export class ZoneService implements OnModuleInit {
         }
 
         const taxRatesUsingZone = await this.connection
-            .getRepository(TaxRate)
+            .getRepository(ctx, TaxRate)
             .createQueryBuilder('taxRate')
             .where('taxRate.zone = :id', { id })
             .getMany();
@@ -104,7 +104,7 @@ export class ZoneService implements OnModuleInit {
                 }),
             };
         } else {
-            await this.connection.getRepository(Zone).remove(zone);
+            await this.connection.getRepository(ctx, Zone).remove(zone);
             await this.updateZonesCache();
             return {
                 result: DeletionResult.DELETED,
@@ -114,11 +114,11 @@ export class ZoneService implements OnModuleInit {
     }
 
     async addMembersToZone(ctx: RequestContext, input: MutationAddMembersToZoneArgs): Promise<Zone> {
-        const countries = await this.getCountriesFromIds(input.memberIds);
+        const countries = await this.getCountriesFromIds(ctx, input.memberIds);
         const zone = await getEntityOrThrow(this.connection, Zone, input.zoneId, { relations: ['members'] });
         const members = unique(zone.members.concat(countries), 'id');
         zone.members = members;
-        await this.connection.getRepository(Zone).save(zone, { reload: false });
+        await this.connection.getRepository(ctx, Zone).save(zone, { reload: false });
         await this.updateZonesCache();
         return assertFound(this.findOne(ctx, zone.id));
     }
@@ -129,13 +129,13 @@ export class ZoneService implements OnModuleInit {
     ): Promise<Zone> {
         const zone = await getEntityOrThrow(this.connection, Zone, input.zoneId, { relations: ['members'] });
         zone.members = zone.members.filter(country => !input.memberIds.includes(country.id));
-        await this.connection.getRepository(Zone).save(zone, { reload: false });
+        await this.connection.getRepository(ctx, Zone).save(zone, { reload: false });
         await this.updateZonesCache();
         return assertFound(this.findOne(ctx, zone.id));
     }
 
-    private getCountriesFromIds(ids: ID[]): Promise<Country[]> {
-        return this.connection.getRepository(Country).findByIds(ids);
+    private getCountriesFromIds(ctx: RequestContext, ids: ID[]): Promise<Country[]> {
+        return this.connection.getRepository(ctx, Country).findByIds(ids);
     }
 
     /**

+ 4 - 3
packages/core/src/service/transaction/transactional-connection.ts

@@ -39,11 +39,11 @@ export class TransactionalConnection {
      */
     getRepository<Entity>(target: ObjectType<Entity> | EntitySchema<Entity> | string): Repository<Entity>;
     getRepository<Entity>(
-        ctx: RequestContext,
+        ctx: RequestContext | undefined,
         target: ObjectType<Entity> | EntitySchema<Entity> | string,
     ): Repository<Entity>;
     getRepository<Entity>(
-        ctxOrTarget: RequestContext | ObjectType<Entity> | EntitySchema<Entity> | string,
+        ctxOrTarget: RequestContext | ObjectType<Entity> | EntitySchema<Entity> | string | undefined,
         maybeTarget?: ObjectType<Entity> | EntitySchema<Entity> | string,
     ): Repository<Entity> {
         if (ctxOrTarget instanceof RequestContext) {
@@ -56,7 +56,8 @@ export class TransactionalConnection {
                 return getRepository(maybeTarget!);
             }
         } else {
-            return getRepository(ctxOrTarget);
+            // tslint:disable-next-line:no-non-null-assertion
+            return getRepository(ctxOrTarget ?? maybeTarget!);
         }
     }
 }

+ 2 - 2
packages/dev-server/test-plugins/keycloak-auth/keycloak-authentication-strategy.ts

@@ -71,8 +71,8 @@ export class KeycloakAuthenticationStrategy implements AuthenticationStrategy<Ke
             return user;
         }
 
-        const roles = await this.roleService.findAll();
-        const merchantRole = roles.items.find((r) => r.code === 'merchant');
+        const roles = await this.roleService.findAll(ctx);
+        const merchantRole = roles.items.find(r => r.code === 'merchant');
 
         if (!merchantRole) {
             Logger.error(`Could not find "merchant" role`);