Forráskód Böngészése

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 éve
szülő
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 { 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 { examplePaymentHandler } from './src/config/payment-method/example-payment-method-config';
 import { OrderProcessOptions, VendureConfig } from './src/config/vendure-config';
 import { OrderProcessOptions, VendureConfig } from './src/config/vendure-config';
 import { defaultEmailTypes } from './src/email/default-email-types';
 import { defaultEmailTypes } from './src/email/default-email-types';
@@ -19,6 +20,7 @@ export const devConfig: VendureConfig = {
         sessionSecret: 'some-secret',
         sessionSecret: 'some-secret',
         requireVerification: false,
         requireVerification: false,
     },
     },
+    entityIdStrategy: new TestingEntityIdStrategy(),
     port: API_PORT,
     port: API_PORT,
     adminApiPath: ADMIN_API_PATH,
     adminApiPath: ADMIN_API_PATH,
     shopApiPath: SHOP_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 { TestServer } from './test-server';
 import { assertThrowsWithMessage } from './test-utils';
 import { assertThrowsWithMessage } from './test-utils';
 
 
-describe('Orders', () => {
+describe('Shop orders', () => {
     const adminClient = new TestAdminClient();
     const adminClient = new TestAdminClient();
     const shopClient = new TestShopClient();
     const shopClient = new TestShopClient();
     const server = new TestServer();
     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 path from 'path';
 
 
 import { DataImportModule } from '../data-import/data-import.module';
 import { DataImportModule } from '../data-import/data-import.module';
-import { PluginModule } from '../plugin/plugin.module';
 import { ServiceModule } from '../service/service.module';
 import { ServiceModule } from '../service/service.module';
 
 
+import { AdminApiModule, ApiSharedModule, entityResolvers, ShopApiModule } from './api-internal-modules';
 import { IdCodecService } from './common/id-codec.service';
 import { IdCodecService } from './common/id-codec.service';
 import { RequestContextService } from './common/request-context.service';
 import { RequestContextService } from './common/request-context.service';
 import { configureGraphQLModule } from './config/configure-graphql-module';
 import { configureGraphQLModule } from './config/configure-graphql-module';
 import { AssetInterceptor } from './middleware/asset-interceptor';
 import { AssetInterceptor } from './middleware/asset-interceptor';
 import { AuthGuard } from './middleware/auth-guard';
 import { AuthGuard } from './middleware/auth-guard';
 import { IdInterceptor } from './middleware/id-interceptor';
 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
  * The ApiModule is responsible for the public API of the application. This is where requests
@@ -106,6 +22,7 @@ class ShopApiModule {}
     imports: [
     imports: [
         ServiceModule,
         ServiceModule,
         DataImportModule,
         DataImportModule,
+        ApiSharedModule,
         AdminApiModule,
         AdminApiModule,
         ShopApiModule,
         ShopApiModule,
         configureGraphQLModule(configService => ({
         configureGraphQLModule(configService => ({
@@ -126,9 +43,7 @@ class ShopApiModule {}
         })),
         })),
     ],
     ],
     providers: [
     providers: [
-        ...entityResolvers,
         RequestContextService,
         RequestContextService,
-        IdCodecService,
         {
         {
             provide: APP_GUARD,
             provide: APP_GUARD,
             useClass: AuthGuard,
             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);
         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);
         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);
         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);
         const clone = Object.assign({}, target);
         if (transformKeys) {
         if (transformKeys) {
             for (const key of transformKeys) {
             for (const key of transformKeys) {
-                if (target.hasOwnProperty(key)) {
+                if (target[key]) {
                     const val = target[key];
                     const val = target[key];
                     if (Array.isArray(val)) {
                     if (Array.isArray(val)) {
                         clone[key] = val.map(v => transformFn(v));
                         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 { GraphQLDateTime } from 'graphql-iso-date';
 import GraphQLJSON from 'graphql-type-json';
 import GraphQLJSON from 'graphql-type-json';
 
 
-import { notNullOrUndefined } from '../../../../shared/shared-utils';
 import { ConfigModule } from '../../config/config.module';
 import { ConfigModule } from '../../config/config.module';
 import { ConfigService } from '../../config/config.service';
 import { ConfigService } from '../../config/config.service';
 import { I18nModule } from '../../i18n/i18n.module';
 import { I18nModule } from '../../i18n/i18n.module';
 import { I18nService } from '../../i18n/i18n.service';
 import { I18nService } from '../../i18n/i18n.service';
 import { getPluginAPIExtensions } from '../../plugin/plugin-utils';
 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 { TranslateErrorExtension } from '../middleware/translate-errors-extension';
 
 
 import { generateListOptions } from './generate-list-options';
 import { generateListOptions } from './generate-list-options';
@@ -34,18 +36,26 @@ export function configureGraphQLModule(
         useFactory: (
         useFactory: (
             configService: ConfigService,
             configService: ConfigService,
             i18nService: I18nService,
             i18nService: I18nService,
+            idCodecService: IdCodecService,
             typesLoader: GraphQLTypesLoader,
             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(
 function createGraphQLOptions(
     i18nService: I18nService,
     i18nService: I18nService,
     configService: ConfigService,
     configService: ConfigService,
+    idCodecService: IdCodecService,
     typesLoader: GraphQLTypesLoader,
     typesLoader: GraphQLTypesLoader,
     options: GraphQLApiOptions,
     options: GraphQLApiOptions,
 ): GqlModuleOptions {
 ): GqlModuleOptions {
@@ -74,7 +84,10 @@ function createGraphQLOptions(
         playground: true,
         playground: true,
         debug: true,
         debug: true,
         context: (req: any) => req,
         context: (req: any) => req,
-        extensions: [() => new TranslateErrorExtension(i18nService)],
+        extensions: [
+            () => new TranslateErrorExtension(i18nService),
+            () => new IdEncoderExtension(idCodecService),
+        ],
         // This is handled by the Express cors plugin
         // This is handled by the Express cors plugin
         cors: false,
         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 { Reflector } from '@nestjs/core';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { Observable } from 'rxjs';
 import { Observable } from 'rxjs';
-import { map } from 'rxjs/operators';
 
 
 import { IdCodecService } from '../common/id-codec.service';
 import { IdCodecService } from '../common/id-codec.service';
 import { DECODE_METADATA_KEY } from '../decorators/decode.decorator';
 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 transformed correctly as per the configured EntityIdStrategy.
  *
  *
  * ID values are defined as properties with the name "id", or properties with names matching any
  * 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> {
     intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
         const args = GqlExecutionContext.create(context).getArgs();
         const args = GqlExecutionContext.create(context).getArgs();
         const transformKeys = this.reflector.get<string[]>(DECODE_METADATA_KEY, context.getHandler());
         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 { Customer } from '../../../entity/customer/customer.entity';
 import { CustomerService } from '../../../service/services/customer.service';
 import { CustomerService } from '../../../service/services/customer.service';
 import { OrderService } from '../../../service/services/order.service';
 import { OrderService } from '../../../service/services/order.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Allow } from '../../decorators/allow.decorator';
 import { Decode } from '../../decorators/decode.decorator';
 import { Decode } from '../../decorators/decode.decorator';
@@ -24,11 +23,7 @@ import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver()
 @Resolver()
 export class CustomerResolver {
 export class CustomerResolver {
-    constructor(
-        private customerService: CustomerService,
-        private orderService: OrderService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private customerService: CustomerService, private orderService: OrderService) {}
 
 
     @Query()
     @Query()
     @Allow(Permission.ReadCustomer)
     @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 { Order } from '../../../entity/order/order.entity';
 import { OrderService } from '../../../service/services/order.service';
 import { OrderService } from '../../../service/services/order.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Allow } from '../../decorators/allow.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver()
 @Resolver()
 export class OrderResolver {
 export class OrderResolver {
-    constructor(
-        private orderService: OrderService,
-        private shippingMethodService: ShippingMethodService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private orderService: OrderService, private shippingMethodService: ShippingMethodService) {}
 
 
     @Query()
     @Query()
     @Allow(Permission.ReadOrder)
     @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 {
 import {
     CreateProductCategoryMutationArgs,
     CreateProductCategoryMutationArgs,
@@ -10,11 +10,9 @@ import {
 } from '../../../../../shared/generated-types';
 } from '../../../../../shared/generated-types';
 import { PaginatedList } from '../../../../../shared/shared-types';
 import { PaginatedList } from '../../../../../shared/shared-types';
 import { Translated } from '../../../common/types/locale-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 { ProductCategory } from '../../../entity/product-category/product-category.entity';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { ProductCategoryService } from '../../../service/services/product-category.service';
 import { ProductCategoryService } from '../../../service/services/product-category.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Allow } from '../../decorators/allow.decorator';
 import { Decode } from '../../decorators/decode.decorator';
 import { Decode } from '../../decorators/decode.decorator';
@@ -25,7 +23,6 @@ export class ProductCategoryResolver {
     constructor(
     constructor(
         private productCategoryService: ProductCategoryService,
         private productCategoryService: ProductCategoryService,
         private facetValueService: FacetValueService,
         private facetValueService: FacetValueService,
-        private idCodecService: IdCodecService,
     ) {}
     ) {}
 
 
     @Query()
     @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 { Order } from '../../../entity/order/order.entity';
 import { CustomerService } from '../../../service/services/customer.service';
 import { CustomerService } from '../../../service/services/customer.service';
 import { OrderService } from '../../../service/services/order.service';
 import { OrderService } from '../../../service/services/order.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver('Customer')
 @Resolver('Customer')
 export class CustomerEntityResolver {
 export class CustomerEntityResolver {
-    constructor(
-        private customerService: CustomerService,
-        private orderService: OrderService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private customerService: CustomerService, private orderService: OrderService) {}
     @ResolveProperty()
     @ResolveProperty()
     async addresses(@Ctx() ctx: RequestContext, @Parent() customer: Customer): Promise<Address[]> {
     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()
     @ResolveProperty()
@@ -30,7 +24,6 @@ export class CustomerEntityResolver {
         @Parent() customer: Customer,
         @Parent() customer: Customer,
         @Args() args: OrdersCustomerArgs,
         @Args() args: OrdersCustomerArgs,
     ): Promise<PaginatedList<Order>> {
     ): 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 { Order } from '../../../entity/order/order.entity';
 import { OrderService } from '../../../service/services/order.service';
 import { OrderService } from '../../../service/services/order.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
 import { ShippingMethodService } from '../../../service/services/shipping-method.service';
-import { IdCodecService } from '../../common/id-codec.service';
 
 
 @Resolver('Order')
 @Resolver('Order')
 export class OrderEntityResolver {
 export class OrderEntityResolver {
-    constructor(
-        private orderService: OrderService,
-        private shippingMethodService: ShippingMethodService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private orderService: OrderService, private shippingMethodService: ShippingMethodService) {}
 
 
     @ResolveProperty()
     @ResolveProperty()
     async payments(@Parent() order: Order) {
     async payments(@Parent() order: Order) {
-        const orderId = this.idCodecService.decode(order.id);
-        return this.orderService.getOrderPayments(orderId);
+        return this.orderService.getOrderPayments(order.id);
     }
     }
 
 
     @ResolveProperty()
     @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 { assertFound } from '../../../common/utils';
 import { OrderLine, ProductVariant } from '../../../entity';
 import { OrderLine, ProductVariant } from '../../../entity';
 import { ProductVariantService } from '../../../service';
 import { ProductVariantService } from '../../../service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver('OrderLine')
 @Resolver('OrderLine')
 export class OrderLineEntityResolver {
 export class OrderLineEntityResolver {
-    constructor(
-        private productVariantService: ProductVariantService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productVariantService: ProductVariantService) {}
 
 
     @ResolveProperty()
     @ResolveProperty()
     async productVariant(
     async productVariant(
         @Ctx() ctx: RequestContext,
         @Ctx() ctx: RequestContext,
         @Parent() orderLine: OrderLine,
         @Parent() orderLine: OrderLine,
     ): Promise<Translated<ProductVariant>> {
     ): 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 { ProductCategory } from '../../../entity/product-category/product-category.entity';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { FacetValueService } from '../../../service/services/facet-value.service';
 import { ProductCategoryService } from '../../../service/services/product-category.service';
 import { ProductCategoryService } from '../../../service/services/product-category.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
@@ -14,7 +13,6 @@ export class ProductCategoryEntityResolver {
     constructor(
     constructor(
         private productCategoryService: ProductCategoryService,
         private productCategoryService: ProductCategoryService,
         private facetValueService: FacetValueService,
         private facetValueService: FacetValueService,
-        private idCodecService: IdCodecService,
     ) {}
     ) {}
 
 
     @ResolveProperty()
     @ResolveProperty()
@@ -22,8 +20,7 @@ export class ProductCategoryEntityResolver {
         @Ctx() ctx: RequestContext,
         @Ctx() ctx: RequestContext,
         @Parent() category: ProductCategory,
         @Parent() category: ProductCategory,
     ): Promise<Array<Translated<FacetValue>>> {
     ): 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));
         return this.facetValueService.findByCategoryIds(ctx, descendants.map(d => d.id));
     }
     }
 
 
@@ -32,8 +29,7 @@ export class ProductCategoryEntityResolver {
         @Ctx() ctx: RequestContext,
         @Ctx() ctx: RequestContext,
         @Parent() category: ProductCategory,
         @Parent() category: ProductCategory,
     ): Promise<Array<Translated<FacetValue>>> {
     ): 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));
         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 { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
 import { Product } from '../../../entity/product/product.entity';
 import { Product } from '../../../entity/product/product.entity';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver('Product')
 @Resolver('Product')
 export class ProductEntityResolver {
 export class ProductEntityResolver {
-    constructor(
-        private productVariantService: ProductVariantService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productVariantService: ProductVariantService) {}
 
 
     @ResolveProperty()
     @ResolveProperty()
     async variants(
     async variants(
         @Ctx() ctx: RequestContext,
         @Ctx() ctx: RequestContext,
         @Parent() product: Product,
         @Parent() product: Product,
     ): Promise<Array<Translated<ProductVariant>>> {
     ): 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 { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 import { ProductOption } from '../../../entity/product-option/product-option.entity';
 import { ProductOption } from '../../../entity/product-option/product-option.entity';
 import { ProductOptionGroupService } from '../../../service/services/product-option-group.service';
 import { ProductOptionGroupService } from '../../../service/services/product-option-group.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { Allow } from '../../decorators/allow.decorator';
 import { Allow } from '../../decorators/allow.decorator';
 
 
 @Resolver('ProductOptionGroup')
 @Resolver('ProductOptionGroup')
 export class ProductOptionGroupEntityResolver {
 export class ProductOptionGroupEntityResolver {
-    constructor(
-        private productOptionGroupService: ProductOptionGroupService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productOptionGroupService: ProductOptionGroupService) {}
 
 
     @ResolveProperty()
     @ResolveProperty()
     @Allow(Permission.ReadCatalog, Permission.Public)
     @Allow(Permission.ReadCatalog, Permission.Public)
@@ -21,8 +17,7 @@ export class ProductOptionGroupEntityResolver {
         if (optionGroup.options) {
         if (optionGroup.options) {
             return Promise.resolve(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 : [];
         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 { Parent, ResolveProperty, Resolver } from '@nestjs/graphql';
-import { Option } from 'commander';
 
 
 import { Translated } from '../../../common/types/locale-types';
 import { Translated } from '../../../common/types/locale-types';
 import { ProductOption } from '../../../entity';
 import { ProductOption } from '../../../entity';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
-import { ProductOptionService } from '../../../service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver('ProductVariant')
 @Resolver('ProductVariant')
 export class ProductVariantEntityResolver {
 export class ProductVariantEntityResolver {
-    constructor(
-        private productVariantService: ProductVariantService,
-        private idCodecService: IdCodecService,
-    ) {}
+    constructor(private productVariantService: ProductVariantService) {}
 
 
     @ResolveProperty()
     @ResolveProperty()
     async options(
     async options(
@@ -25,7 +19,6 @@ export class ProductVariantEntityResolver {
         if (productVariant.options) {
         if (productVariant.options) {
             return productVariant.options as Array<Translated<ProductOption>>;
             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 { idsAreEqual } from '../../../common/utils';
 import { Address, Customer } from '../../../entity';
 import { Address, Customer } from '../../../entity';
 import { CustomerService } from '../../../service/services/customer.service';
 import { CustomerService } from '../../../service/services/customer.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Allow } from '../../decorators/allow.decorator';
 import { Allow } from '../../decorators/allow.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
 @Resolver()
 @Resolver()
 export class ShopCustomerResolver {
 export class ShopCustomerResolver {
-    constructor(private customerService: CustomerService, private idCodecService: IdCodecService) {}
+    constructor(private customerService: CustomerService) {}
 
 
     @Query()
     @Query()
     @Allow(Permission.Owner)
     @Allow(Permission.Owner)
@@ -63,7 +62,7 @@ export class ShopCustomerResolver {
      */
      */
     private checkOwnerPermissions(ctx: RequestContext, customer: Customer) {
     private checkOwnerPermissions(ctx: RequestContext, customer: Customer) {
         if (ctx.authorizedAsOwnerOnly) {
         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)) {
             if (userId && !idsAreEqual(userId, ctx.activeUserId)) {
                 throw new UnauthorizedError();
                 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 { FacetValueService } from '../../../service/services/facet-value.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { ProductService } from '../../../service/services/product.service';
 import { ProductService } from '../../../service/services/product.service';
-import { IdCodecService } from '../../common/id-codec.service';
 import { RequestContext } from '../../common/request-context';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 
@@ -27,7 +26,6 @@ export class ShopProductsResolver {
         private productService: ProductService,
         private productService: ProductService,
         private productVariantService: ProductVariantService,
         private productVariantService: ProductVariantService,
         private facetValueService: FacetValueService,
         private facetValueService: FacetValueService,
-        private idCodecService: IdCodecService,
         private productCategoryService: ProductCategoryService,
         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
  * This file contains constants which are shared between more than one sub-module
  * e.g. values required by both the server and admin-ui.
  * 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 ADMIN_API_PATH = 'admin-api';
 export const SHOP_API_PATH = 'shop-api';
 export const SHOP_API_PATH = 'shop-api';
 export const DEFAULT_CHANNEL_CODE = '__default_channel__';
 export const DEFAULT_CHANNEL_CODE = '__default_channel__';