Browse Source

refactor(server): Rework the id decode/encode pipeline

The former arrangement caused a bug of double-encoding ids of resolved properties. This new architecture solves that bug, and also has the bonus of removing the need to perform decoding in property resolver functions. It can now always be safely assumed that ids in the resolvers are already decoded.
Michael Bromley 7 years ago
parent
commit
70d79459a8

+ 2 - 0
server/dev-config.ts

@@ -2,6 +2,7 @@ import path from 'path';
 
 import { ADMIN_API_PATH, API_PORT, SHOP_API_PATH } from '../shared/shared-constants';
 
+import { TestingEntityIdStrategy } from './e2e/config/testing-entity-id-strategy';
 import { examplePaymentHandler } from './src/config/payment-method/example-payment-method-config';
 import { OrderProcessOptions, VendureConfig } from './src/config/vendure-config';
 import { defaultEmailTypes } from './src/email/default-email-types';
@@ -19,6 +20,7 @@ export const devConfig: VendureConfig = {
         sessionSecret: 'some-secret',
         requireVerification: false,
     },
+    entityIdStrategy: new TestingEntityIdStrategy(),
     port: API_PORT,
     adminApiPath: ADMIN_API_PATH,
     shopApiPath: SHOP_API_PATH,

+ 47 - 0
server/e2e/entity-id-strategy.e2e-spec.ts

@@ -0,0 +1,47 @@
+import gql from 'graphql-tag';
+import path from 'path';
+
+import { CREATE_FACET } from '../../admin-ui/src/app/data/definitions/facet-definitions';
+import { CreateFacet, LanguageCode } from '../../shared/generated-types';
+
+import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
+import { TestShopClient } from './test-client';
+import { TestServer } from './test-server';
+
+describe('EntityIdStrategy', () => {
+    const shopClient = new TestShopClient();
+    const server = new TestServer();
+
+    beforeAll(async () => {
+        await server.init({
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
+            customerCount: 1,
+        });
+        await shopClient.init();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    it('Does not doubly-encode ids from resolved properties', async () => {
+        const result = await shopClient.query(gql`
+            query {
+                product(id: "T_1", languageCode: en) {
+                    id
+                    variants {
+                        id
+                        options {
+                            id
+                            name
+                        }
+                    }
+                }
+            }
+        `);
+
+        expect(result.product.id).toBe('T_1');
+        expect(result.product.variants[0].id).toBe('T_1');
+        expect(result.product.variants[0].options[0].id).toBe('T_1');
+    });
+});

+ 1 - 1
server/e2e/shop-order.e2e-spec.ts

@@ -24,7 +24,7 @@ import { TestAdminClient, TestShopClient } from './test-client';
 import { TestServer } from './test-server';
 import { assertThrowsWithMessage } from './test-utils';
 
-describe('Orders', () => {
+describe('Shop orders', () => {
     const adminClient = new TestAdminClient();
     const shopClient = new TestShopClient();
     const server = new TestServer();

+ 109 - 0
server/src/api/api-internal-modules.ts

@@ -0,0 +1,109 @@
+import { Module } from '@nestjs/common';
+
+import { ConfigModule } from '../config/config.module';
+import { DataImportModule } from '../data-import/data-import.module';
+import { PluginModule } from '../plugin/plugin.module';
+import { ServiceModule } from '../service/service.module';
+
+import { IdCodecService } from './common/id-codec.service';
+import { AdministratorResolver } from './resolvers/admin/administrator.resolver';
+import { AssetResolver } from './resolvers/admin/asset.resolver';
+import { AuthResolver } from './resolvers/admin/auth.resolver';
+import { ChannelResolver } from './resolvers/admin/channel.resolver';
+import { CountryResolver } from './resolvers/admin/country.resolver';
+import { CustomerGroupResolver } from './resolvers/admin/customer-group.resolver';
+import { CustomerResolver } from './resolvers/admin/customer.resolver';
+import { FacetResolver } from './resolvers/admin/facet.resolver';
+import { GlobalSettingsResolver } from './resolvers/admin/global-settings.resolver';
+import { ImportResolver } from './resolvers/admin/import.resolver';
+import { OrderResolver } from './resolvers/admin/order.resolver';
+import { PaymentMethodResolver } from './resolvers/admin/payment-method.resolver';
+import { ProductCategoryResolver } from './resolvers/admin/product-category.resolver';
+import { ProductOptionResolver } from './resolvers/admin/product-option.resolver';
+import { ProductResolver } from './resolvers/admin/product.resolver';
+import { PromotionResolver } from './resolvers/admin/promotion.resolver';
+import { RoleResolver } from './resolvers/admin/role.resolver';
+import { SearchResolver } from './resolvers/admin/search.resolver';
+import { ShippingMethodResolver } from './resolvers/admin/shipping-method.resolver';
+import { TaxCategoryResolver } from './resolvers/admin/tax-category.resolver';
+import { TaxRateResolver } from './resolvers/admin/tax-rate.resolver';
+import { ZoneResolver } from './resolvers/admin/zone.resolver';
+import { CustomerEntityResolver } from './resolvers/entity/customer-entity.resolver';
+import { OrderEntityResolver } from './resolvers/entity/order-entity.resolver';
+import { OrderLineEntityResolver } from './resolvers/entity/order-line-entity.resolver';
+import { ProductCategoryEntityResolver } from './resolvers/entity/product-category-entity.resolver';
+import { ProductEntityResolver } from './resolvers/entity/product-entity.resolver';
+import { ProductOptionGroupEntityResolver } from './resolvers/entity/product-option-group-entity.resolver';
+import { ProductVariantEntityResolver } from './resolvers/entity/product-variant-entity.resolver';
+import { ShopAuthResolver } from './resolvers/shop/shop-auth.resolver';
+import { ShopCustomerResolver } from './resolvers/shop/shop-customer.resolver';
+import { ShopOrderResolver } from './resolvers/shop/shop-order.resolver';
+import { ShopProductsResolver } from './resolvers/shop/shop-products.resolver';
+
+const adminResolvers = [
+    AdministratorResolver,
+    AssetResolver,
+    AuthResolver,
+    ChannelResolver,
+    CountryResolver,
+    CustomerGroupResolver,
+    CustomerResolver,
+    FacetResolver,
+    GlobalSettingsResolver,
+    ImportResolver,
+    OrderResolver,
+    PaymentMethodResolver,
+    ProductCategoryResolver,
+    ProductOptionResolver,
+    ProductResolver,
+    PromotionResolver,
+    RoleResolver,
+    SearchResolver,
+    ShippingMethodResolver,
+    TaxCategoryResolver,
+    TaxRateResolver,
+    ZoneResolver,
+];
+
+const shopResolvers = [ShopAuthResolver, ShopCustomerResolver, ShopOrderResolver, ShopProductsResolver];
+
+export const entityResolvers = [
+    CustomerEntityResolver,
+    OrderEntityResolver,
+    OrderLineEntityResolver,
+    ProductCategoryEntityResolver,
+    ProductEntityResolver,
+    ProductOptionGroupEntityResolver,
+    ProductVariantEntityResolver,
+];
+
+/**
+ * The internal module containing some shared providers used by more than
+ * one API module.
+ */
+@Module({
+    imports: [ConfigModule],
+    providers: [IdCodecService],
+    exports: [IdCodecService],
+})
+export class ApiSharedModule {}
+
+/**
+ * The internal module containing the Admin GraphQL API resolvers
+ */
+@Module({
+    imports: [ApiSharedModule, PluginModule, ServiceModule, DataImportModule],
+    providers: [...adminResolvers, ...entityResolvers, ...PluginModule.adminApiResolvers()],
+    exports: adminResolvers,
+})
+export class AdminApiModule {}
+
+/**
+ * The internal module containing the Shop GraphQL API resolvers
+ */
+@Module({
+    imports: [ApiSharedModule, PluginModule, ServiceModule],
+    providers: [...shopResolvers, ...entityResolvers, ...PluginModule.shopApiResolvers()],
+    exports: shopResolvers,
+})
+export class ShopApiModule {}

+ 2 - 87
server/src/api/api.module.ts

@@ -3,99 +3,15 @@ import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
 import path from 'path';
 
 import { DataImportModule } from '../data-import/data-import.module';
-import { PluginModule } from '../plugin/plugin.module';
 import { ServiceModule } from '../service/service.module';
 
+import { AdminApiModule, ApiSharedModule, entityResolvers, ShopApiModule } from './api-internal-modules';
 import { IdCodecService } from './common/id-codec.service';
 import { RequestContextService } from './common/request-context.service';
 import { configureGraphQLModule } from './config/configure-graphql-module';
 import { AssetInterceptor } from './middleware/asset-interceptor';
 import { AuthGuard } from './middleware/auth-guard';
 import { IdInterceptor } from './middleware/id-interceptor';
-import { AdministratorResolver } from './resolvers/admin/administrator.resolver';
-import { AssetResolver } from './resolvers/admin/asset.resolver';
-import { AuthResolver } from './resolvers/admin/auth.resolver';
-import { ChannelResolver } from './resolvers/admin/channel.resolver';
-import { CountryResolver } from './resolvers/admin/country.resolver';
-import { CustomerGroupResolver } from './resolvers/admin/customer-group.resolver';
-import { CustomerResolver } from './resolvers/admin/customer.resolver';
-import { FacetResolver } from './resolvers/admin/facet.resolver';
-import { GlobalSettingsResolver } from './resolvers/admin/global-settings.resolver';
-import { ImportResolver } from './resolvers/admin/import.resolver';
-import { OrderResolver } from './resolvers/admin/order.resolver';
-import { PaymentMethodResolver } from './resolvers/admin/payment-method.resolver';
-import { ProductCategoryResolver } from './resolvers/admin/product-category.resolver';
-import { ProductOptionResolver } from './resolvers/admin/product-option.resolver';
-import { ProductResolver } from './resolvers/admin/product.resolver';
-import { PromotionResolver } from './resolvers/admin/promotion.resolver';
-import { RoleResolver } from './resolvers/admin/role.resolver';
-import { SearchResolver } from './resolvers/admin/search.resolver';
-import { ShippingMethodResolver } from './resolvers/admin/shipping-method.resolver';
-import { TaxCategoryResolver } from './resolvers/admin/tax-category.resolver';
-import { TaxRateResolver } from './resolvers/admin/tax-rate.resolver';
-import { ZoneResolver } from './resolvers/admin/zone.resolver';
-import { CustomerEntityResolver } from './resolvers/entity/customer-entity.resolver';
-import { OrderEntityResolver } from './resolvers/entity/order-entity.resolver';
-import { OrderLineEntityResolver } from './resolvers/entity/order-line-entity.resolver';
-import { ProductCategoryEntityResolver } from './resolvers/entity/product-category-entity.resolver';
-import { ProductEntityResolver } from './resolvers/entity/product-entity.resolver';
-import { ProductOptionGroupEntityResolver } from './resolvers/entity/product-option-group-entity.resolver';
-import { ProductVariantEntityResolver } from './resolvers/entity/product-variant-entity.resolver';
-import { ShopAuthResolver } from './resolvers/shop/shop-auth.resolver';
-import { ShopCustomerResolver } from './resolvers/shop/shop-customer.resolver';
-import { ShopOrderResolver } from './resolvers/shop/shop-order.resolver';
-import { ShopProductsResolver } from './resolvers/shop/shop-products.resolver';
-
-const adminResolvers = [
-    AdministratorResolver,
-    AssetResolver,
-    AuthResolver,
-    ChannelResolver,
-    CountryResolver,
-    CustomerGroupResolver,
-    CustomerResolver,
-    FacetResolver,
-    GlobalSettingsResolver,
-    ImportResolver,
-    OrderResolver,
-    PaymentMethodResolver,
-    ProductCategoryResolver,
-    ProductOptionResolver,
-    ProductResolver,
-    PromotionResolver,
-    RoleResolver,
-    SearchResolver,
-    ShippingMethodResolver,
-    TaxCategoryResolver,
-    TaxRateResolver,
-    ZoneResolver,
-];
-
-const shopResolvers = [ShopAuthResolver, ShopCustomerResolver, ShopOrderResolver, ShopProductsResolver];
-
-const entityResolvers = [
-    CustomerEntityResolver,
-    OrderEntityResolver,
-    OrderLineEntityResolver,
-    ProductCategoryEntityResolver,
-    ProductEntityResolver,
-    ProductOptionGroupEntityResolver,
-    ProductVariantEntityResolver,
-];
-
-@Module({
-    imports: [PluginModule, ServiceModule, DataImportModule],
-    providers: [IdCodecService, ...adminResolvers, ...entityResolvers, ...PluginModule.adminApiResolvers()],
-    exports: adminResolvers,
-})
-class AdminApiModule {}
-
-@Module({
-    imports: [PluginModule, ServiceModule],
-    providers: [IdCodecService, ...shopResolvers, ...entityResolvers, ...PluginModule.shopApiResolvers()],
-    exports: shopResolvers,
-})
-class ShopApiModule {}
 
 /**
  * The ApiModule is responsible for the public API of the application. This is where requests
@@ -106,6 +22,7 @@ class ShopApiModule {}
     imports: [
         ServiceModule,
         DataImportModule,
+        ApiSharedModule,
         AdminApiModule,
         ShopApiModule,
         configureGraphQLModule(configService => ({
@@ -126,9 +43,7 @@ class ShopApiModule {}
         })),
     ],
     providers: [
-        ...entityResolvers,
         RequestContextService,
-        IdCodecService,
         {
             provide: APP_GUARD,
             useClass: AuthGuard,

+ 2 - 2
server/src/api/common/id-codec.service.ts

@@ -12,11 +12,11 @@ export class IdCodecService {
         this.idCodec = new IdCodec(configService.entityIdStrategy);
     }
 
-    encode(target: any, transformKeys?: string[]): string {
+    encode<T extends string | number | boolean | object | undefined>(target: T, transformKeys?: string[]): T {
         return this.idCodec.encode(target, transformKeys);
     }
 
-    decode(target: any, transformKeys?: string[]): ID {
+    decode<T extends string | number | object | undefined>(target: T, transformKeys?: string[]): T {
         return this.idCodec.decode(target, transformKeys);
     }
 }

+ 1 - 1
server/src/api/common/id-codec.ts

@@ -94,7 +94,7 @@ export class IdCodec {
         const clone = Object.assign({}, target);
         if (transformKeys) {
             for (const key of transformKeys) {
-                if (target.hasOwnProperty(key)) {
+                if (target[key]) {
                     const val = target[key];
                     if (Array.isArray(val)) {
                         clone[key] = val.map(v => transformFn(v));

+ 18 - 5
server/src/api/config/configure-graphql-module.ts

@@ -5,12 +5,14 @@ import { extendSchema, printSchema } from 'graphql';
 import { GraphQLDateTime } from 'graphql-iso-date';
 import GraphQLJSON from 'graphql-type-json';
 
-import { notNullOrUndefined } from '../../../../shared/shared-utils';
 import { ConfigModule } from '../../config/config.module';
 import { ConfigService } from '../../config/config.service';
 import { I18nModule } from '../../i18n/i18n.module';
 import { I18nService } from '../../i18n/i18n.service';
 import { getPluginAPIExtensions } from '../../plugin/plugin-utils';
+import { ApiSharedModule } from '../api-internal-modules';
+import { IdCodecService } from '../common/id-codec.service';
+import { IdEncoderExtension } from '../middleware/id-encoder-extension';
 import { TranslateErrorExtension } from '../middleware/translate-errors-extension';
 
 import { generateListOptions } from './generate-list-options';
@@ -34,18 +36,26 @@ export function configureGraphQLModule(
         useFactory: (
             configService: ConfigService,
             i18nService: I18nService,
+            idCodecService: IdCodecService,
             typesLoader: GraphQLTypesLoader,
         ) => {
-            return createGraphQLOptions(i18nService, configService, typesLoader, getOptions(configService));
+            return createGraphQLOptions(
+                i18nService,
+                configService,
+                idCodecService,
+                typesLoader,
+                getOptions(configService),
+            );
         },
-        inject: [ConfigService, I18nService, GraphQLTypesLoader],
-        imports: [ConfigModule, I18nModule],
+        inject: [ConfigService, I18nService, IdCodecService, GraphQLTypesLoader],
+        imports: [ConfigModule, I18nModule, ApiSharedModule],
     });
 }
 
 function createGraphQLOptions(
     i18nService: I18nService,
     configService: ConfigService,
+    idCodecService: IdCodecService,
     typesLoader: GraphQLTypesLoader,
     options: GraphQLApiOptions,
 ): GqlModuleOptions {
@@ -74,7 +84,10 @@ function createGraphQLOptions(
         playground: true,
         debug: true,
         context: (req: any) => req,
-        extensions: [() => new TranslateErrorExtension(i18nService)],
+        extensions: [
+            () => new TranslateErrorExtension(i18nService),
+            () => new IdEncoderExtension(idCodecService),
+        ],
         // This is handled by the Express cors plugin
         cors: false,
     };

+ 29 - 0
server/src/api/middleware/id-encoder-extension.ts

@@ -0,0 +1,29 @@
+import { Response } from 'express-serve-static-core';
+import { GraphQLResolveInfo } from 'graphql';
+import { GraphQLExtension, GraphQLResponse } from 'graphql-extensions';
+
+import { I18nRequest, I18nService } from '../../i18n/i18n.service';
+import { IdCodecService } from '../common/id-codec.service';
+
+/**
+ * Encodes the ids of outgoing responses according to the configured EntityIdStrategy.
+ *
+ * This is done here and not via a Nest Interceptor because we only need to do the
+ * encoding once, just before the response is sent. Doing the encoding in an interceptor's
+ * `intercept()` method causes the encoding to be performed once for each GraphQL
+ * property resolver in the hierarchy.
+ */
+export class IdEncoderExtension implements GraphQLExtension {
+    constructor(private idCodecService: IdCodecService) {}
+
+    willSendResponse(o: {
+        graphqlResponse: GraphQLResponse;
+        context: { req: I18nRequest; res: Response };
+    }): void | {
+        graphqlResponse: GraphQLResponse;
+        context: { req: I18nRequest; res: Response };
+    } {
+        o.graphqlResponse.data = this.idCodecService.encode(o.graphqlResponse.data);
+        return o;
+    }
+}

+ 9 - 5
server/src/api/middleware/id-interceptor.ts

@@ -2,13 +2,12 @@ import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
 import { Reflector } from '@nestjs/core';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { Observable } from 'rxjs';
-import { map } from 'rxjs/operators';
 
 import { IdCodecService } from '../common/id-codec.service';
 import { DECODE_METADATA_KEY } from '../decorators/decode.decorator';
 
 /**
- * This interceptor automatically decodes incoming requests and encodes outgoing requests so that any
+ * This interceptor automatically decodes incoming requests so that any
  * ID values are transformed correctly as per the configured EntityIdStrategy.
  *
  * ID values are defined as properties with the name "id", or properties with names matching any
@@ -21,8 +20,13 @@ export class IdInterceptor implements NestInterceptor {
     intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
         const args = GqlExecutionContext.create(context).getArgs();
         const transformKeys = this.reflector.get<string[]>(DECODE_METADATA_KEY, context.getHandler());
-
-        Object.assign(args, this.idCodecService.decode(args, transformKeys));
-        return call$.pipe(map(data => this.idCodecService.encode(data)));
+        const gqlRoot = context.getArgByIndex(0);
+        if (!gqlRoot) {
+            // Only need to decode ids if this is a root query/mutation.
+            // Internal (property-resolver) requests can then be assumed to
+            // be already decoded.
+            Object.assign(args, this.idCodecService.decode(args, transformKeys));
+        }
+        return call$;
     }
 }

+ 1 - 6
server/src/api/resolvers/admin/customer.resolver.ts

@@ -16,7 +16,6 @@ import { Address } from '../../../entity/address/address.entity';
 import { Customer } from '../../../entity/customer/customer.entity';
 import { CustomerService } from '../../../service/services/customer.service';
 import { OrderService } from '../../../service/services/order.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Decode } from '../../decorators/decode.decorator';
@@ -24,11 +23,7 @@ import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver()
 export class CustomerResolver {
-    constructor(
-        private customerService: CustomerService,
-        private orderService: OrderService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private customerService: CustomerService, private orderService: OrderService) {}
 
     @Query()
     @Allow(Permission.ReadCustomer)

+ 1 - 6
server/src/api/resolvers/admin/order.resolver.ts

@@ -5,18 +5,13 @@ import { PaginatedList } from '../../../../../shared/shared-types';
 import { Order } from '../../../entity/order/order.entity';
 import { OrderService } from '../../../service/services/order.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver()
 export class OrderResolver {
-    constructor(
-        private orderService: OrderService,
-        private shippingMethodService: ShippingMethodService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private orderService: OrderService, private shippingMethodService: ShippingMethodService) {}
 
     @Query()
     @Allow(Permission.ReadOrder)

+ 1 - 4
server/src/api/resolvers/admin/product-category.resolver.ts

@@ -1,4 +1,4 @@
-import { Args, Mutation, Parent, Query, ResolveProperty, Resolver } from '@nestjs/graphql';
+import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 
 import {
     CreateProductCategoryMutationArgs,
@@ -10,11 +10,9 @@ import {
 } from '../../../../../shared/generated-types';
 import { PaginatedList } from '../../../../../shared/shared-types';
 import { Translated } from '../../../common/types/locale-types';
-import { FacetValue } from '../../../entity/facet-value/facet-value.entity';
 import { ProductCategory } from '../../../entity/product-category/product-category.entity';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { ProductCategoryService } from '../../../service/services/product-category.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Decode } from '../../decorators/decode.decorator';
@@ -25,7 +23,6 @@ export class ProductCategoryResolver {
     constructor(
         private productCategoryService: ProductCategoryService,
         private facetValueService: FacetValueService,
-        private idCodecService: IdCodecService,
     ) {}
 
     @Query()

+ 3 - 10
server/src/api/resolvers/entity/customer-entity.resolver.ts

@@ -7,21 +7,15 @@ import { Customer } from '../../../entity/customer/customer.entity';
 import { Order } from '../../../entity/order/order.entity';
 import { CustomerService } from '../../../service/services/customer.service';
 import { OrderService } from '../../../service/services/order.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Customer')
 export class CustomerEntityResolver {
-    constructor(
-        private customerService: CustomerService,
-        private orderService: OrderService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private customerService: CustomerService, private orderService: OrderService) {}
     @ResolveProperty()
     async addresses(@Ctx() ctx: RequestContext, @Parent() customer: Customer): Promise<Address[]> {
-        const customerId = this.idCodecService.decode(customer.id);
-        return this.customerService.findAddressesByCustomerId(ctx, customerId);
+        return this.customerService.findAddressesByCustomerId(ctx, customer.id);
     }
 
     @ResolveProperty()
@@ -30,7 +24,6 @@ export class CustomerEntityResolver {
         @Parent() customer: Customer,
         @Args() args: OrdersCustomerArgs,
     ): Promise<PaginatedList<Order>> {
-        const customerId = this.idCodecService.decode(customer.id);
-        return this.orderService.findByCustomerId(ctx, customerId, args.options || undefined);
+        return this.orderService.findByCustomerId(ctx, customer.id, args.options || undefined);
     }
 }

+ 2 - 8
server/src/api/resolvers/entity/order-entity.resolver.ts

@@ -3,20 +3,14 @@ import { Parent, ResolveProperty, Resolver } from '@nestjs/graphql';
 import { Order } from '../../../entity/order/order.entity';
 import { OrderService } from '../../../service/services/order.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
-import { IdCodecService } from '../../common/id-codec.service';
 
 @Resolver('Order')
 export class OrderEntityResolver {
-    constructor(
-        private orderService: OrderService,
-        private shippingMethodService: ShippingMethodService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private orderService: OrderService, private shippingMethodService: ShippingMethodService) {}
 
     @ResolveProperty()
     async payments(@Parent() order: Order) {
-        const orderId = this.idCodecService.decode(order.id);
-        return this.orderService.getOrderPayments(orderId);
+        return this.orderService.getOrderPayments(order.id);
     }
 
     @ResolveProperty()

+ 2 - 7
server/src/api/resolvers/entity/order-line-entity.resolver.ts

@@ -4,23 +4,18 @@ import { Translated } from '../../../common/types/locale-types';
 import { assertFound } from '../../../common/utils';
 import { OrderLine, ProductVariant } from '../../../entity';
 import { ProductVariantService } from '../../../service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('OrderLine')
 export class OrderLineEntityResolver {
-    constructor(
-        private productVariantService: ProductVariantService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productVariantService: ProductVariantService) {}
 
     @ResolveProperty()
     async productVariant(
         @Ctx() ctx: RequestContext,
         @Parent() orderLine: OrderLine,
     ): Promise<Translated<ProductVariant>> {
-        const id = this.idCodecService.decode(orderLine.productVariant.id);
-        return assertFound(this.productVariantService.findOne(ctx, id));
+        return assertFound(this.productVariantService.findOne(ctx, orderLine.productVariant.id));
     }
 }

+ 2 - 6
server/src/api/resolvers/entity/product-category-entity.resolver.ts

@@ -5,7 +5,6 @@ import { FacetValue } from '../../../entity/facet-value/facet-value.entity';
 import { ProductCategory } from '../../../entity/product-category/product-category.entity';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { ProductCategoryService } from '../../../service/services/product-category.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
@@ -14,7 +13,6 @@ export class ProductCategoryEntityResolver {
     constructor(
         private productCategoryService: ProductCategoryService,
         private facetValueService: FacetValueService,
-        private idCodecService: IdCodecService,
     ) {}
 
     @ResolveProperty()
@@ -22,8 +20,7 @@ export class ProductCategoryEntityResolver {
         @Ctx() ctx: RequestContext,
         @Parent() category: ProductCategory,
     ): Promise<Array<Translated<FacetValue>>> {
-        const categoryId = this.idCodecService.decode(category.id);
-        const descendants = await this.productCategoryService.getDescendants(ctx, categoryId);
+        const descendants = await this.productCategoryService.getDescendants(ctx, category.id);
         return this.facetValueService.findByCategoryIds(ctx, descendants.map(d => d.id));
     }
 
@@ -32,8 +29,7 @@ export class ProductCategoryEntityResolver {
         @Ctx() ctx: RequestContext,
         @Parent() category: ProductCategory,
     ): Promise<Array<Translated<FacetValue>>> {
-        const categoryId = this.idCodecService.decode(category.id);
-        const ancestors = await this.productCategoryService.getAncestors(categoryId, ctx);
+        const ancestors = await this.productCategoryService.getAncestors(category.id, ctx);
         return this.facetValueService.findByCategoryIds(ctx, ancestors.map(d => d.id));
     }
 }

+ 2 - 7
server/src/api/resolvers/entity/product-entity.resolver.ts

@@ -4,23 +4,18 @@ import { Translated } from '../../../common/types/locale-types';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
 import { Product } from '../../../entity/product/product.entity';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Product')
 export class ProductEntityResolver {
-    constructor(
-        private productVariantService: ProductVariantService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productVariantService: ProductVariantService) {}
 
     @ResolveProperty()
     async variants(
         @Ctx() ctx: RequestContext,
         @Parent() product: Product,
     ): Promise<Array<Translated<ProductVariant>>> {
-        const productId = this.idCodecService.decode(product.id);
-        return this.productVariantService.getVariantsByProductId(ctx, productId);
+        return this.productVariantService.getVariantsByProductId(ctx, product.id);
     }
 }

+ 2 - 7
server/src/api/resolvers/entity/product-option-group-entity.resolver.ts

@@ -5,15 +5,11 @@ import { Translated } from '../../../common/types/locale-types';
 import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { ProductOption } from '../../../entity/product-option/product-option.entity';
 import { ProductOptionGroupService } from '../../../service/services/product-option-group.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { Allow } from '../../decorators/allow.decorator';
 
 @Resolver('ProductOptionGroup')
 export class ProductOptionGroupEntityResolver {
-    constructor(
-        private productOptionGroupService: ProductOptionGroupService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productOptionGroupService: ProductOptionGroupService) {}
 
     @ResolveProperty()
     @Allow(Permission.ReadCatalog, Permission.Public)
@@ -21,8 +17,7 @@ export class ProductOptionGroupEntityResolver {
         if (optionGroup.options) {
             return Promise.resolve(optionGroup.options);
         }
-        const id = this.idCodecService.decode(optionGroup.id);
-        const group = await this.productOptionGroupService.findOne(id, optionGroup.languageCode);
+        const group = await this.productOptionGroupService.findOne(optionGroup.id, optionGroup.languageCode);
         return group ? group.options : [];
     }
 }

+ 2 - 9
server/src/api/resolvers/entity/product-variant-entity.resolver.ts

@@ -1,21 +1,15 @@
 import { Parent, ResolveProperty, Resolver } from '@nestjs/graphql';
-import { Option } from 'commander';
 
 import { Translated } from '../../../common/types/locale-types';
 import { ProductOption } from '../../../entity';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
-import { ProductOptionService } from '../../../service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('ProductVariant')
 export class ProductVariantEntityResolver {
-    constructor(
-        private productVariantService: ProductVariantService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productVariantService: ProductVariantService) {}
 
     @ResolveProperty()
     async options(
@@ -25,7 +19,6 @@ export class ProductVariantEntityResolver {
         if (productVariant.options) {
             return productVariant.options as Array<Translated<ProductOption>>;
         }
-        const productId = this.idCodecService.decode(productVariant.id);
-        return this.productVariantService.getOptionsForVariant(ctx, productId);
+        return this.productVariantService.getOptionsForVariant(ctx, productVariant.id);
     }
 }

+ 2 - 3
server/src/api/resolvers/shop/shop-customer.resolver.ts

@@ -11,14 +11,13 @@ import { UnauthorizedError } from '../../../common/error/errors';
 import { idsAreEqual } from '../../../common/utils';
 import { Address, Customer } from '../../../entity';
 import { CustomerService } from '../../../service/services/customer.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver()
 export class ShopCustomerResolver {
-    constructor(private customerService: CustomerService, private idCodecService: IdCodecService) {}
+    constructor(private customerService: CustomerService) {}
 
     @Query()
     @Allow(Permission.Owner)
@@ -63,7 +62,7 @@ export class ShopCustomerResolver {
      */
     private checkOwnerPermissions(ctx: RequestContext, customer: Customer) {
         if (ctx.authorizedAsOwnerOnly) {
-            const userId = customer.user && this.idCodecService.decode(customer.user.id);
+            const userId = customer.user && customer.user.id;
             if (userId && !idsAreEqual(userId, ctx.activeUserId)) {
                 throw new UnauthorizedError();
             }

+ 0 - 2
server/src/api/resolvers/shop/shop-products.resolver.ts

@@ -17,7 +17,6 @@ import { ProductCategoryService } from '../../../service';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { ProductService } from '../../../service/services/product.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
@@ -27,7 +26,6 @@ export class ShopProductsResolver {
         private productService: ProductService,
         private productVariantService: ProductVariantService,
         private facetValueService: FacetValueService,
-        private idCodecService: IdCodecService,
         private productCategoryService: ProductCategoryService,
     ) {}
 

+ 1 - 1
shared/shared-constants.ts

@@ -2,7 +2,7 @@
  * This file contains constants which are shared between more than one sub-module
  * e.g. values required by both the server and admin-ui.
  */
-export const API_PORT = 3000;
+export const API_PORT = 5000;
 export const ADMIN_API_PATH = 'admin-api';
 export const SHOP_API_PATH = 'shop-api';
 export const DEFAULT_CHANNEL_CODE = '__default_channel__';