Browse Source

refactor(server): Reusable logic for updating translatable entities

Also fixes #8
Michael Bromley 7 years ago
parent
commit
a793b0cf1b

+ 7 - 6
server/src/api/facet/facet.resolver.ts

@@ -2,6 +2,7 @@ import { Mutation, Query, Resolver } from '@nestjs/graphql';
 
 import { PaginatedList } from '../../../../shared/shared-types';
 import { DEFAULT_LANGUAGE_CODE } from '../../common/constants';
+import { CreateFacetValueDto, UpdateFacetValueDto } from '../../entity/facet-value/facet-value.dto';
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../entity/facet/facet.entity';
 import { I18nError } from '../../i18n/i18n-error';
@@ -50,20 +51,20 @@ export class FacetResolver {
 
     @Mutation()
     @ApplyIdCodec()
-    async createFacetValues(_, args): Promise<Translated<FacetValue[]>> {
-        const { input } = args;
+    async createFacetValues(_, args): Promise<Array<Translated<FacetValue>>> {
+        const { input } = args as { input: CreateFacetValueDto[] };
         const facetId = input[0].facetId;
         const facet = await this.facetService.findOne(facetId, DEFAULT_LANGUAGE_CODE);
         if (!facet) {
             throw new I18nError(`error.invalid-facetId`, { facetId });
         }
-        return input.map(facetValue => this.facetValueService.create(facet, facetValue));
+        return Promise.all(input.map(facetValue => this.facetValueService.create(facet, facetValue)));
     }
 
     @Mutation()
     @ApplyIdCodec()
-    async updateFacetValues(_, args): Promise<Translated<FacetValue[]>> {
-        const { input } = args;
-        return input.map(facetValue => this.facetValueService.update(facetValue));
+    async updateFacetValues(_, args): Promise<Array<Translated<FacetValue>>> {
+        const { input } = args as { input: UpdateFacetValueDto[] };
+        return Promise.all(input.map(facetValue => this.facetValueService.update(facetValue)));
     }
 }

+ 3 - 1
server/src/api/product/product.resolver.ts

@@ -3,6 +3,7 @@ import { Mutation, Query, Resolver } from '@nestjs/graphql';
 import { PaginatedList } from '../../../../shared/shared-types';
 import { DEFAULT_LANGUAGE_CODE } from '../../common/constants';
 import { assertFound } from '../../common/utils';
+import { UpdateProductVariantDto } from '../../entity/product-variant/create-product-variant.dto';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { Product } from '../../entity/product/product.entity';
 import { Translated } from '../../locale/locale-types';
@@ -68,6 +69,7 @@ export class ProductResolver {
     @Mutation()
     @ApplyIdCodec()
     async updateProductVariants(_, args): Promise<Array<Translated<ProductVariant>>> {
-        return this.productService.updateProductVariants(args.input);
+        const { input } = args as { input: UpdateProductVariantDto[] };
+        return Promise.all(input.map(variant => this.productVariantService.update(variant)));
     }
 }

+ 1 - 1
server/src/common/create-translatable.ts

@@ -27,6 +27,6 @@ export function createTranslatable<T extends Translatable>(
         if (typeof beforeSave === 'function') {
             await beforeSave(entity);
         }
-        return await connection.manager.save(entity);
+        return connection.manager.save(entity);
     };
 }

+ 31 - 0
server/src/common/update-translatable.ts

@@ -0,0 +1,31 @@
+import { Connection } from 'typeorm';
+
+import { ID, Type } from '../../../shared/shared-types';
+import { Translatable, TranslatedInput, Translation } from '../locale/locale-types';
+import { TranslationUpdaterService } from '../locale/translation-updater.service';
+
+/**
+ * Returns a "save" function which uses the provided connection and dto to
+ * update a translatable entity and its translations to the DB.
+ */
+export function updateTranslatable<T extends Translatable>(
+    entityType: Type<T>,
+    translationType: Type<Translation<T>>,
+    translationUpdaterService: TranslationUpdaterService,
+) {
+    return async function saveTranslatable(
+        connection: Connection,
+        dto: TranslatedInput<T> & { id: ID },
+    ): Promise<T> {
+        const existingTranslations = await connection.getRepository(translationType).find({
+            where: { base: dto.id },
+            relations: ['base'],
+        });
+
+        const translationUpdater = translationUpdaterService.create(translationType);
+        const diff = translationUpdater.diff(existingTranslations, dto.translations);
+
+        const entity = await translationUpdater.applyDiff(new entityType(dto), diff);
+        return connection.manager.save(entity);
+    };
+}

+ 9 - 0
server/src/entity/product-variant/create-product-variant.dto.ts

@@ -1,3 +1,4 @@
+import { ID } from '../../../../shared/shared-types';
 import { TranslatedInput } from '../../locale/locale-types';
 
 import { ProductVariant } from './product-variant.entity';
@@ -8,3 +9,11 @@ export interface CreateProductVariantDto extends TranslatedInput<ProductVariant>
     image?: string;
     optionCodes?: string[];
 }
+
+export interface UpdateProductVariantDto extends TranslatedInput<ProductVariant> {
+    id: ID;
+    sku: string;
+    price: number;
+    image?: string;
+    optionCodes?: string[];
+}

+ 3 - 11
server/src/service/facet-value.service.ts

@@ -5,6 +5,7 @@ import { Connection } from 'typeorm';
 import { ID } from '../../../shared/shared-types';
 import { DEFAULT_LANGUAGE_CODE } from '../common/constants';
 import { createTranslatable } from '../common/create-translatable';
+import { updateTranslatable } from '../common/update-translatable';
 import { assertFound } from '../common/utils';
 import { FacetValueTranslation } from '../entity/facet-value/facet-value-translation.entity';
 import { CreateFacetValueDto, UpdateFacetValueDto } from '../entity/facet-value/facet-value.dto';
@@ -45,17 +46,8 @@ export class FacetValueService {
     }
 
     async update(updateFacetValueDto: UpdateFacetValueDto): Promise<Translated<FacetValue>> {
-        const existingTranslations = await this.connection.getRepository(FacetValueTranslation).find({
-            where: { base: updateFacetValueDto.id },
-            relations: ['base'],
-        });
-
-        const translationUpdater = this.translationUpdaterService.create(FacetValueTranslation);
-        const diff = translationUpdater.diff(existingTranslations, updateFacetValueDto.translations);
-
-        const facetValue = await translationUpdater.applyDiff(new FacetValue(updateFacetValueDto), diff);
-        await this.connection.manager.save(facetValue);
-
+        const save = updateTranslatable(FacetValue, FacetValueTranslation, this.translationUpdaterService);
+        const facetValue = await save(this.connection, updateFacetValueDto);
         return assertFound(this.findOne(facetValue.id, DEFAULT_LANGUAGE_CODE));
     }
 }

+ 3 - 11
server/src/service/facet.service.ts

@@ -7,6 +7,7 @@ import { buildListQuery } from '../common/build-list-query';
 import { ListQueryOptions } from '../common/common-types';
 import { DEFAULT_LANGUAGE_CODE } from '../common/constants';
 import { createTranslatable } from '../common/create-translatable';
+import { updateTranslatable } from '../common/update-translatable';
 import { assertFound } from '../common/utils';
 import { FacetTranslation } from '../entity/facet/facet-translation.entity';
 import { CreateFacetDto, UpdateFacetDto } from '../entity/facet/facet.dto';
@@ -52,17 +53,8 @@ export class FacetService {
     }
 
     async update(updateFacetDto: UpdateFacetDto): Promise<Translated<Facet>> {
-        const existingTranslations = await this.connection.getRepository(FacetTranslation).find({
-            where: { base: updateFacetDto.id },
-            relations: ['base'],
-        });
-
-        const translationUpdater = this.translationUpdaterService.create(FacetTranslation);
-        const diff = translationUpdater.diff(existingTranslations, updateFacetDto.translations);
-
-        const facet = await translationUpdater.applyDiff(new Facet(updateFacetDto), diff);
-        await this.connection.manager.save(facet);
-
+        const save = updateTranslatable(Facet, FacetTranslation, this.translationUpdaterService);
+        const facet = await save(this.connection, updateFacetDto);
         return assertFound(this.findOne(facet.id, DEFAULT_LANGUAGE_CODE));
     }
 }

+ 7 - 14
server/src/service/product-option-group.service.ts

@@ -5,6 +5,7 @@ import { Connection, FindManyOptions, Like } from 'typeorm';
 import { ID } from '../../../shared/shared-types';
 import { DEFAULT_LANGUAGE_CODE } from '../common/constants';
 import { createTranslatable } from '../common/create-translatable';
+import { updateTranslatable } from '../common/update-translatable';
 import { assertFound } from '../common/utils';
 import { ProductOptionGroupTranslation } from '../entity/product-option-group/product-option-group-translation.entity';
 import {
@@ -57,20 +58,12 @@ export class ProductOptionGroupService {
     async update(
         updateProductOptionGroupDto: UpdateProductOptionGroupDto,
     ): Promise<Translated<ProductOptionGroup>> {
-        const existingTranslations = await this.connection.getRepository(ProductOptionGroupTranslation).find({
-            where: { base: updateProductOptionGroupDto.id },
-            relations: ['base'],
-        });
-
-        const translationUpdater = this.translationUpdaterService.create(ProductOptionGroupTranslation);
-        const diff = translationUpdater.diff(existingTranslations, updateProductOptionGroupDto.translations);
-
-        const productOptionGroup = await translationUpdater.applyDiff(
-            new ProductOptionGroup(updateProductOptionGroupDto),
-            diff,
+        const save = updateTranslatable(
+            ProductOptionGroup,
+            ProductOptionGroupTranslation,
+            this.translationUpdaterService,
         );
-        await this.connection.manager.save(productOptionGroup);
-
-        return assertFound(this.findOne(productOptionGroup.id, DEFAULT_LANGUAGE_CODE));
+        const group = await save(this.connection, updateProductOptionGroupDto);
+        return assertFound(this.findOne(group.id, DEFAULT_LANGUAGE_CODE));
     }
 }

+ 21 - 1
server/src/service/product-variant.service.ts

@@ -6,8 +6,13 @@ import { ID } from '../../../shared/shared-types';
 import { generateAllCombinations } from '../../../shared/shared-utils';
 import { DEFAULT_LANGUAGE_CODE } from '../common/constants';
 import { createTranslatable } from '../common/create-translatable';
+import { updateTranslatable } from '../common/update-translatable';
+import { assertFound } from '../common/utils';
 import { ProductOption } from '../entity/product-option/product-option.entity';
-import { CreateProductVariantDto } from '../entity/product-variant/create-product-variant.dto';
+import {
+    CreateProductVariantDto,
+    UpdateProductVariantDto,
+} from '../entity/product-variant/create-product-variant.dto';
 import { ProductVariantTranslation } from '../entity/product-variant/product-variant-translation.entity';
 import { ProductVariant } from '../entity/product-variant/product-variant.entity';
 import { Product } from '../entity/product/product.entity';
@@ -39,6 +44,21 @@ export class ProductVariantService {
         return save(this.connection, createProductVariantDto);
     }
 
+    async update(updateProductVariantsDto: UpdateProductVariantDto): Promise<Translated<ProductVariant>> {
+        const save = updateTranslatable(
+            ProductVariant,
+            ProductVariantTranslation,
+            this.translationUpdaterService,
+        );
+        await save(this.connection, updateProductVariantsDto);
+        const variant = await assertFound(
+            this.connection.manager.getRepository(ProductVariant).findOne(updateProductVariantsDto.id, {
+                relations: ['options'],
+            }),
+        );
+        return translateDeep(variant, DEFAULT_LANGUAGE_CODE, ['options']);
+    }
+
     async generateVariantsForProduct(
         productId: ID,
         defaultPrice?: number,

+ 4 - 26
server/src/service/product.service.ts

@@ -7,9 +7,9 @@ import { buildListQuery } from '../common/build-list-query';
 import { ListQueryOptions } from '../common/common-types';
 import { DEFAULT_LANGUAGE_CODE } from '../common/constants';
 import { createTranslatable } from '../common/create-translatable';
+import { updateTranslatable } from '../common/update-translatable';
 import { assertFound } from '../common/utils';
 import { ProductOptionGroup } from '../entity/product-option-group/product-option-group.entity';
-import { ProductVariant } from '../entity/product-variant/product-variant.entity';
 import { ProductTranslation } from '../entity/product/product-translation.entity';
 import { CreateProductDto, UpdateProductDto } from '../entity/product/product.dto';
 import { Product } from '../entity/product/product.entity';
@@ -76,31 +76,9 @@ export class ProductService {
     }
 
     async update(updateProductDto: UpdateProductDto): Promise<Translated<Product>> {
-        const existingTranslations = await this.connection.getRepository(ProductTranslation).find({
-            where: { base: updateProductDto.id },
-            relations: ['base'],
-        });
-
-        const translationUpdater = this.translationUpdaterService.create(ProductTranslation);
-        const diff = translationUpdater.diff(existingTranslations, updateProductDto.translations);
-
-        const product = await translationUpdater.applyDiff(new Product(updateProductDto), diff);
-        await this.connection.manager.save(product);
-
-        return assertFound(this.findOne(updateProductDto.id, DEFAULT_LANGUAGE_CODE));
-    }
-
-    async updateProductVariants(updateProductVariants: any[]): Promise<Array<Translated<ProductVariant>>> {
-        for (const variant of updateProductVariants) {
-            await this.connection.getRepository(ProductVariant).update(variant.id, variant);
-        }
-
-        return await this.connection
-            .getRepository(ProductVariant)
-            .findByIds(updateProductVariants.map(v => v.id), { relations: ['options'] })
-            .then(variants => {
-                return variants.map(v => translateDeep(v, DEFAULT_LANGUAGE_CODE, ['options']));
-            });
+        const save = updateTranslatable(Product, ProductTranslation, this.translationUpdaterService);
+        const product = await save(this.connection, updateProductDto);
+        return assertFound(this.findOne(product.id, DEFAULT_LANGUAGE_CODE));
     }
 
     async addOptionGroupToProduct(productId: ID, optionGroupId: ID): Promise<Translated<Product>> {