1
0
Эх сурвалжийг харах

refactor(server): Reusable logic for creating translatable entities

Michael Bromley 7 жил өмнө
parent
commit
ac8a776022

+ 32 - 0
server/src/common/create-translatable.ts

@@ -0,0 +1,32 @@
+import { Connection } from 'typeorm';
+
+import { Type } from '../../../shared/shared-types';
+import { ProductOptionTranslation } from '../entity/product-option/product-option-translation.entity';
+import { Translatable, TranslatedInput, Translation } from '../locale/locale-types';
+
+/**
+ * Returns a "save" function which uses the provided connection and dto to
+ * save a translatable entity and its translations to the DB.
+ */
+export function createTranslatable<T extends Translatable>(
+    entityType: Type<T>,
+    translationType: Type<Translation<T>>,
+    beforeSave?: (newEntity: T) => void,
+) {
+    return async function saveTranslatable(connection: Connection, dto: TranslatedInput<T>): Promise<T> {
+        const entity = new entityType(dto);
+        const translations: Array<Translation<T>> = [];
+
+        for (const input of dto.translations) {
+            const translation = new translationType(input);
+            translations.push(translation);
+            await connection.manager.save(translation);
+        }
+
+        entity.translations = translations;
+        if (typeof beforeSave === 'function') {
+            await beforeSave(entity);
+        }
+        return await connection.manager.save(entity);
+    };
+}

+ 4 - 14
server/src/service/facet-value.service.ts

@@ -4,6 +4,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 { assertFound } from '../common/utils';
 import { FacetValueTranslation } from '../entity/facet-value/facet-value-translation.entity';
 import { CreateFacetValueDto, UpdateFacetValueDto } from '../entity/facet-value/facet-value.dto';
@@ -38,20 +39,9 @@ export class FacetValueService {
     }
 
     async create(facet: Facet, createFacetValueDto: CreateFacetValueDto): Promise<Translated<FacetValue>> {
-        const facetValue = new FacetValue(createFacetValueDto);
-        const translations: FacetValueTranslation[] = [];
-
-        for (const input of createFacetValueDto.translations) {
-            const translation = new FacetValueTranslation(input);
-            translations.push(translation);
-            await this.connection.manager.save(translation);
-        }
-
-        facetValue.translations = translations;
-        facetValue.facet = facet;
-        const createdGroup = await this.connection.manager.save(facetValue);
-
-        return assertFound(this.findOne(createdGroup.id, DEFAULT_LANGUAGE_CODE));
+        const save = createTranslatable(FacetValue, FacetValueTranslation, fv => (fv.facet = facet));
+        const facetValue = await save(this.connection, createFacetValueDto);
+        return assertFound(this.findOne(facetValue.id, DEFAULT_LANGUAGE_CODE));
     }
 
     async update(updateFacetValueDto: UpdateFacetValueDto): Promise<Translated<FacetValue>> {

+ 4 - 13
server/src/service/facet.service.ts

@@ -6,6 +6,7 @@ import { ID, PaginatedList } from '../../../shared/shared-types';
 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 { assertFound } from '../common/utils';
 import { FacetTranslation } from '../entity/facet/facet-translation.entity';
 import { CreateFacetDto, UpdateFacetDto } from '../entity/facet/facet.dto';
@@ -45,19 +46,9 @@ export class FacetService {
     }
 
     async create(createFacetDto: CreateFacetDto): Promise<Translated<Facet>> {
-        const facet = new Facet(createFacetDto);
-        const translations: FacetTranslation[] = [];
-
-        for (const input of createFacetDto.translations) {
-            const translation = new FacetTranslation(input);
-            translations.push(translation);
-            await this.connection.manager.save(translation);
-        }
-
-        facet.translations = translations;
-        const createdFacet = await this.connection.manager.save(facet);
-
-        return assertFound(this.findOne(createdFacet.id, DEFAULT_LANGUAGE_CODE));
+        const save = createTranslatable(Facet, FacetTranslation);
+        const facet = await save(this.connection, createFacetDto);
+        return assertFound(this.findOne(facet.id, DEFAULT_LANGUAGE_CODE));
     }
 
     async update(updateFacetDto: UpdateFacetDto): Promise<Translated<Facet>> {

+ 4 - 13
server/src/service/product-option-group.service.ts

@@ -4,6 +4,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 { assertFound } from '../common/utils';
 import { ProductOptionGroupTranslation } from '../entity/product-option-group/product-option-group-translation.entity';
 import {
@@ -48,19 +49,9 @@ export class ProductOptionGroupService {
     async create(
         createProductOptionGroupDto: CreateProductOptionGroupDto,
     ): Promise<Translated<ProductOptionGroup>> {
-        const optionGroup = new ProductOptionGroup(createProductOptionGroupDto);
-        const translations: ProductOptionGroupTranslation[] = [];
-
-        for (const input of createProductOptionGroupDto.translations) {
-            const translation = new ProductOptionGroupTranslation(input);
-            translations.push(translation);
-            await this.connection.manager.save(translation);
-        }
-
-        optionGroup.translations = translations;
-        const createdGroup = await this.connection.manager.save(optionGroup);
-
-        return assertFound(this.findOne(createdGroup.id, DEFAULT_LANGUAGE_CODE));
+        const save = createTranslatable(ProductOptionGroup, ProductOptionGroupTranslation);
+        const group = await save(this.connection, createProductOptionGroupDto);
+        return assertFound(this.findOne(group.id, DEFAULT_LANGUAGE_CODE));
     }
 
     async update(

+ 4 - 14
server/src/service/product-option.service.ts

@@ -4,6 +4,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 { assertFound } from '../common/utils';
 import { ProductOptionGroup } from '../entity/product-option-group/product-option-group.entity';
 import { ProductOptionTranslation } from '../entity/product-option/product-option-translation.entity';
@@ -37,19 +38,8 @@ export class ProductOptionService {
         group: ProductOptionGroup,
         createProductOptionDto: CreateProductOptionDto,
     ): Promise<Translated<ProductOption>> {
-        const option = new ProductOption(createProductOptionDto);
-        const translations: ProductOptionTranslation[] = [];
-
-        for (const input of createProductOptionDto.translations) {
-            const translation = new ProductOptionTranslation(input);
-            translations.push(translation);
-            await this.connection.manager.save(translation);
-        }
-
-        option.translations = translations;
-        option.group = group;
-        const createdGroup = await this.connection.manager.save(option);
-
-        return assertFound(this.findOne(createdGroup.id, DEFAULT_LANGUAGE_CODE));
+        const save = createTranslatable(ProductOption, ProductOptionTranslation, po => (po.group = group));
+        const option = await save(this.connection, createProductOptionDto);
+        return assertFound(this.findOne(option.id, DEFAULT_LANGUAGE_CODE));
     }
 }

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

@@ -5,6 +5,7 @@ import { Connection } from 'typeorm';
 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 { ProductOption } from '../entity/product-option/product-option.entity';
 import { CreateProductVariantDto } from '../entity/product-variant/create-product-variant.dto';
 import { ProductVariantTranslation } from '../entity/product-variant/product-variant-translation.entity';
@@ -26,27 +27,16 @@ export class ProductVariantService {
         product: Product,
         createProductVariantDto: CreateProductVariantDto,
     ): Promise<ProductVariant> {
-        const { optionCodes, translations } = createProductVariantDto;
-        const variant = new ProductVariant(createProductVariantDto);
-        const variantTranslations: ProductVariantTranslation[] = [];
-
-        if (optionCodes && optionCodes.length) {
-            const options = await this.connection.getRepository(ProductOption).find();
-            const selectedOptions = options.filter(og => optionCodes.includes(og.code));
-            variant.options = selectedOptions;
-        }
-
-        for (const input of translations) {
-            const translation = new ProductVariantTranslation(input);
-            variantTranslations.push(translation);
-            await this.connection.manager.save(translation);
-        }
-
-        variant.product = product;
-        variant.translations = variantTranslations;
-        const createdVariant = await this.connection.manager.save(variant);
-
-        return createdVariant;
+        const save = createTranslatable(ProductVariant, ProductVariantTranslation, async variant => {
+            const { optionCodes } = createProductVariantDto;
+            if (optionCodes && optionCodes.length) {
+                const options = await this.connection.getRepository(ProductOption).find();
+                const selectedOptions = options.filter(og => optionCodes.includes(og.code));
+                variant.options = selectedOptions;
+            }
+            variant.product = product;
+        });
+        return save(this.connection, createProductVariantDto);
     }
 
     async generateVariantsForProduct(

+ 11 - 20
server/src/service/product.service.ts

@@ -6,6 +6,7 @@ import { ID, PaginatedList } from '../../../shared/shared-types';
 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 { assertFound } from '../common/utils';
 import { ProductOptionGroup } from '../entity/product-option-group/product-option-group.entity';
 import { ProductVariant } from '../entity/product-variant/product-variant.entity';
@@ -62,26 +63,16 @@ export class ProductService {
     }
 
     async create(createProductDto: CreateProductDto): Promise<Translated<Product>> {
-        const { optionGroupCodes, image, translations } = createProductDto;
-        const product = new Product(createProductDto);
-        const productTranslations: ProductTranslation[] = [];
-
-        if (optionGroupCodes && optionGroupCodes.length) {
-            const optionGroups = await this.connection.getRepository(ProductOptionGroup).find();
-            const selectedOptionGroups = optionGroups.filter(og => optionGroupCodes.includes(og.code));
-            product.optionGroups = selectedOptionGroups;
-        }
-
-        for (const input of translations) {
-            const translation = new ProductTranslation(input);
-            productTranslations.push(translation);
-            await this.connection.manager.save(translation);
-        }
-
-        product.translations = productTranslations;
-        const createdProduct = await this.connection.manager.save(product);
-
-        return assertFound(this.findOne(createdProduct.id, DEFAULT_LANGUAGE_CODE));
+        const save = createTranslatable(Product, ProductTranslation, async p => {
+            const { optionGroupCodes } = createProductDto;
+            if (optionGroupCodes && optionGroupCodes.length) {
+                const optionGroups = await this.connection.getRepository(ProductOptionGroup).find();
+                const selectedOptionGroups = optionGroups.filter(og => optionGroupCodes.includes(og.code));
+                p.optionGroups = selectedOptionGroups;
+            }
+        });
+        const product = await save(this.connection, createProductDto);
+        return assertFound(this.findOne(product.id, DEFAULT_LANGUAGE_CODE));
     }
 
     async update(updateProductDto: UpdateProductDto): Promise<Translated<Product>> {

+ 1 - 4
shared/shared-types.ts

@@ -12,13 +12,10 @@ export type DeepPartial<T> = {
 };
 // tslint:enable:no-shadowed-variable
 
-// tslint:disable:ban-types
 /**
  * A type representing the type rather than instance of a class.
  */
-export type Type<T> = {
-    new (...args: any[]): T;
-} & Function;
+export interface Type<T> extends Function { new (...args: any[]): T; }
 
 /**
  * A type describing the shape of a paginated list response