Selaa lähdekoodia

Simplify handling of localizable entities

Do away with the separate interfaces, and unify the localized/multi-language entities.
Michael Bromley 7 vuotta sitten
vanhempi
sitoutus
487896af55
44 muutettua tiedostoa jossa 287 lisäystä ja 396 poistoa
  1. 4 0
      README.md
  2. 4 4
      modules/core/api/auth/auth.controller.ts
  3. 1 1
      modules/core/api/customer/customer.controller.ts
  4. 3 4
      modules/core/api/customer/customer.resolver.ts
  5. 6 4
      modules/core/api/product/product.api.graphql
  6. 12 5
      modules/core/api/product/product.resolver.ts
  7. 4 4
      modules/core/auth/auth.service.ts
  8. 2 2
      modules/core/auth/roles-guard.ts
  9. 5 6
      modules/core/entity/address/address.entity.ts
  10. 0 16
      modules/core/entity/address/address.interface.ts
  11. 3 5
      modules/core/entity/administrator/administrator.entity.ts
  12. 0 14
      modules/core/entity/administrator/administrator.interface.ts
  13. 6 8
      modules/core/entity/customer/customer.entity.ts
  14. 0 18
      modules/core/entity/customer/customer.interface.ts
  15. 6 6
      modules/core/entity/product-option-group/product-option-group-translation.entity.ts
  16. 10 9
      modules/core/entity/product-option-group/product-option-group.entity.ts
  17. 0 9
      modules/core/entity/product-option-group/product-option-group.interface.ts
  18. 6 6
      modules/core/entity/product-option/product-option-translation.entity.ts
  19. 10 9
      modules/core/entity/product-option/product-option.entity.ts
  20. 0 10
      modules/core/entity/product-option/product-option.interface.ts
  21. 1 1
      modules/core/entity/product-variant/create-product-variant.dto.ts
  22. 5 5
      modules/core/entity/product-variant/product-variant-translation.entity.ts
  23. 10 9
      modules/core/entity/product-variant/product-variant.entity.ts
  24. 0 13
      modules/core/entity/product-variant/product-variant.interface.ts
  25. 1 1
      modules/core/entity/product/create-product.dto.ts
  26. 6 6
      modules/core/entity/product/product-translation.entity.ts
  27. 16 15
      modules/core/entity/product/product.entity.ts
  28. 18 3
      modules/core/entity/product/product.graphql
  29. 0 15
      modules/core/entity/product/product.interface.ts
  30. 7 0
      modules/core/entity/product/update-product.dto.ts
  31. 2 3
      modules/core/entity/user/user.entity.ts
  32. 0 16
      modules/core/entity/user/user.interface.ts
  33. 3 16
      modules/core/locale/locale-types.ts
  34. 26 33
      modules/core/locale/translate-entity.spec.ts
  35. 7 7
      modules/core/locale/translate-entity.ts
  36. 13 14
      modules/core/repository/product-repository.ts
  37. 11 13
      modules/core/repository/product-variant-repository.ts
  38. 5 7
      modules/core/service/customer.service.ts
  39. 7 7
      modules/core/service/product-variant.service.spec.ts
  40. 6 11
      modules/core/service/product-variant.service.ts
  41. 6 6
      modules/core/service/product.service.spec.ts
  42. 8 9
      modules/core/service/product.service.ts
  43. 1 1
      modules/mock-data/mock-data-client.service.ts
  44. 46 45
      modules/mock-data/mock-data.service.ts

+ 4 - 0
README.md

@@ -1 +1,5 @@
 # Vendure
+
+A headless ecommerce framework built on TypeScript and Node.
+
+Currently under initial development. Nothing much to see for now.

+ 4 - 4
modules/core/api/auth/auth.controller.ts

@@ -2,7 +2,7 @@ import { Body, Controller, Get, Post, Req } from '@nestjs/common';
 import { AuthService } from '../../auth/auth.service';
 import { Role } from '../../auth/role';
 import { RolesGuard } from '../../auth/roles-guard';
-import { UserEntity } from '../../entity/user/user.entity';
+import { User } from '../../entity/user/user.entity';
 import { LoginDto } from './login.dto';
 
 @Controller('auth')
@@ -31,14 +31,14 @@ export class AuthController {
     @RolesGuard([Role.Authenticated])
     @Get('me')
     async me(@Req() request) {
-        const user = request.user as UserEntity;
+        const user = request.user as User;
         return this.publiclyAccessibleUser(user);
     }
 
     /**
-     * Exposes a subset of the UserEntity properties which we want to expose to the public API.
+     * Exposes a subset of the User properties which we want to expose to the public API.
      */
-    private publiclyAccessibleUser(user: UserEntity): Pick<UserEntity, 'id' | 'identifier' | 'roles'> {
+    private publiclyAccessibleUser(user: User): Pick<User, 'id' | 'identifier' | 'roles'> {
         return {
             id: user.id,
             identifier: user.identifier,

+ 1 - 1
modules/core/api/customer/customer.controller.ts

@@ -1,5 +1,5 @@
 import { Controller, Get, Param } from '@nestjs/common';
-import { Customer } from '../../entity/customer/customer.interface';
+import { Customer } from '../../entity/customer/customer.entity';
 import { CustomerService } from '../../service/customer.service';
 
 @Controller('customers')

+ 3 - 4
modules/core/api/customer/customer.resolver.ts

@@ -1,7 +1,6 @@
 import { Query, ResolveProperty, Resolver } from '@nestjs/graphql';
-import { Address } from '../../entity/address/address.interface';
-import { CustomerEntity } from '../../entity/customer/customer.entity';
-import { Customer } from '../../entity/customer/customer.interface';
+import { Address } from '../../entity/address/address.entity';
+import { Customer } from '../../entity/customer/customer.entity';
 import { CustomerService } from '../../service/customer.service';
 
 @Resolver('Customer')
@@ -19,7 +18,7 @@ export class CustomerResolver {
     }
 
     @ResolveProperty('addresses')
-    addresses(customer: CustomerEntity): Promise<Address[]> {
+    addresses(customer: Customer): Promise<Address[]> {
         return this.customerService.findAddressesByCustomerId(customer.id);
     }
 }

+ 6 - 4
modules/core/api/product/product.api.graphql

@@ -1,9 +1,11 @@
 type Query {
-  products(lang: LanguageCode): [Product]
-  product(id: Int!, lang: LanguageCode): Product
+    products(lang: LanguageCode): [Product]
+    product(id: Int!, lang: LanguageCode): Product
 }
 
 type Mutation {
-  "Create a new Product"
-  createProduct(input: CreateProductInput): Product
+    "Create a new Product"
+    createProduct(input: CreateProductInput): Product
+    "Update an existing Product"
+    updateProduct(productId: Int, input: UpdateProductInput): Product
 }

+ 12 - 5
modules/core/api/product/product.resolver.ts

@@ -1,6 +1,6 @@
 import { Mutation, Query, Resolver } from '@nestjs/graphql';
 import { CreateProductDto } from '../../entity/product/create-product.dto';
-import { Product } from '../../entity/product/product.interface';
+import { Product } from '../../entity/product/product.entity';
 import { ProductVariantService } from '../../service/product-variant.service';
 import { ProductService } from '../../service/product.service';
 
@@ -19,17 +19,24 @@ export class ProductResolver {
     }
 
     @Mutation()
-    async createProduct(_, args: MutationInput<CreateProductDto>): Promise<Product> {
-        const product = await this.productService.create(args.input);
+    async createProduct(_, args): Promise<Product> {
+        const { input } = args;
+        const product = await this.productService.create(input);
 
-        if (args.input.variants && args.input.variants.length) {
-            for (const variant of args.input.variants) {
+        if (input.variants && input.variants.length) {
+            for (const variant of input.variants) {
                 await this.productVariantService.create(product, variant);
             }
         }
 
         return product;
     }
+    /*
+    @Mutation()
+    updateProduct(_, args): Promise<Product> {
+        const { productId, input } = args;
+
+    }*/
 }
 
 export interface MutationInput<T> {

+ 4 - 4
modules/core/auth/auth.service.ts

@@ -2,7 +2,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import * as jwt from 'jsonwebtoken';
 import { Connection } from 'typeorm';
-import { UserEntity } from '../entity/user/user.entity';
+import { User } from '../entity/user/user.entity';
 import { JwtPayload } from './auth-types';
 import { PasswordService } from './password.service';
 import { Role } from './role';
@@ -14,8 +14,8 @@ export const JWT_SECRET = 'some_secret';
 export class AuthService {
     constructor(private passwordService: PasswordService, @InjectConnection() private connection: Connection) {}
 
-    async createToken(identifier: string, password: string): Promise<{ user: UserEntity; token: string }> {
-        const user = await this.connection.getRepository(UserEntity).findOne({
+    async createToken(identifier: string, password: string): Promise<{ user: User; token: string }> {
+        const user = await this.connection.getRepository(User).findOne({
             where: {
                 identifier,
             },
@@ -37,7 +37,7 @@ export class AuthService {
     }
 
     async validateUser(payload: JwtPayload): Promise<any> {
-        return await this.connection.getRepository(UserEntity).findOne({
+        return await this.connection.getRepository(User).findOne({
             where: {
                 identifier: payload.identifier,
             },

+ 2 - 2
modules/core/auth/roles-guard.ts

@@ -1,7 +1,7 @@
 import { CanActivate, ExecutionContext, UseGuards } from '@nestjs/common';
 import { AuthGuard } from '@nestjs/passport';
 import { ExtractJwt, Strategy } from 'passport-jwt';
-import { UserEntity } from '../entity/user/user.entity';
+import { User } from '../entity/user/user.entity';
 import { Role } from './role';
 
 /**
@@ -38,7 +38,7 @@ function authenticatedOnly(roles: Role[]): boolean {
 function forRoles(roles: Role[]) {
     return {
         canActivate(context: ExecutionContext) {
-            const user: UserEntity = context.switchToHttp().getRequest().user;
+            const user: User = context.switchToHttp().getRequest().user;
             if (!user) {
                 return false;
             }

+ 5 - 6
modules/core/entity/address/address.entity.ts

@@ -1,14 +1,13 @@
 import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
-import { CustomerEntity } from '../customer/customer.entity';
-import { UserEntity } from '../user/user.entity';
-import { Address } from './address.interface';
+import { Customer } from '../customer/customer.entity';
+import { User } from '../user/user.entity';
 
 @Entity('address')
-export class AddressEntity implements Address {
+export class Address {
     @PrimaryGeneratedColumn() id: number;
 
-    @ManyToOne(type => CustomerEntity, customer => customer.addresses)
-    customer: CustomerEntity;
+    @ManyToOne(type => Customer, customer => customer.addresses)
+    customer: Customer;
 
     @Column() fullName: string;
 

+ 0 - 16
modules/core/entity/address/address.interface.ts

@@ -1,16 +0,0 @@
-export interface Address {
-    id: number;
-    fullName: string;
-    company: string;
-    streetLine1: string;
-    streetLine2: string;
-    city: string;
-    province: string;
-    postalCode: string;
-    country: string;
-    phoneNumber: string;
-    defaultShippingAddress: boolean;
-    defaultBillingAddress: boolean;
-    createdAt: string;
-    updatedAt: string;
-}

+ 3 - 5
modules/core/entity/administrator/administrator.entity.ts

@@ -7,12 +7,10 @@ import {
     PrimaryGeneratedColumn,
     UpdateDateColumn,
 } from 'typeorm';
-import { UserEntity } from '../user/user.entity';
-import { User } from '../user/user.interface';
-import { Administrator } from './administrator.interface';
+import { User } from '../user/user.entity';
 
 @Entity('administrator')
-export class AdministratorEntity implements Administrator {
+export class Administrator {
     @PrimaryGeneratedColumn() id: number;
 
     @Column() firstName: string;
@@ -21,7 +19,7 @@ export class AdministratorEntity implements Administrator {
 
     @Column() emailAddress: string;
 
-    @OneToOne(type => UserEntity)
+    @OneToOne(type => User)
     @JoinColumn()
     user: User;
 

+ 0 - 14
modules/core/entity/administrator/administrator.interface.ts

@@ -1,14 +0,0 @@
-import { User } from '../user/user.interface';
-
-/**
- * An administrator of the system.
- */
-export interface Administrator {
-    id: number;
-    firstName: string;
-    lastName: string;
-    emailAddress: string;
-    user: User;
-    createdAt: string;
-    updatedAt: string;
-}

+ 6 - 8
modules/core/entity/customer/customer.entity.ts

@@ -8,13 +8,11 @@ import {
     PrimaryGeneratedColumn,
     UpdateDateColumn,
 } from 'typeorm';
-import { AddressEntity } from '../address/address.entity';
-import { UserEntity } from '../user/user.entity';
-import { User } from '../user/user.interface';
-import { Customer } from './customer.interface';
+import { Address } from '../address/address.entity';
+import { User } from '../user/user.entity';
 
 @Entity('customer')
-export class CustomerEntity implements Customer {
+export class Customer {
     @PrimaryGeneratedColumn() id: number;
 
     @Column() firstName: string;
@@ -25,10 +23,10 @@ export class CustomerEntity implements Customer {
 
     @Column() emailAddress: string;
 
-    @OneToMany(type => AddressEntity, address => address.customer)
-    addresses: AddressEntity[];
+    @OneToMany(type => Address, address => address.customer)
+    addresses: Address[];
 
-    @OneToOne(type => UserEntity, { eager: true })
+    @OneToOne(type => User, { eager: true })
     @JoinColumn()
     user?: User;
 

+ 0 - 18
modules/core/entity/customer/customer.interface.ts

@@ -1,18 +0,0 @@
-import { Address } from '../address/address.interface';
-import { User } from '../user/user.interface';
-
-/**
- * A customer, i.e. a user who has trasacted with the shop in some way. A Customer may also be associated with
- * a registered User, but in the case of anonymous checkouts, there will be no associated User.
- */
-export interface Customer {
-    id: number;
-    firstName: string;
-    lastName: string;
-    phoneNumber: string;
-    emailAddress: string;
-    addresses: Address[];
-    user?: User;
-    createdAt: string;
-    updatedAt: string;
-}

+ 6 - 6
modules/core/entity/product-option-group/product-option-group-translation.entity.ts

@@ -1,16 +1,16 @@
 import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
+import { LanguageCode } from '../../locale/language-code';
 import { Translation } from '../../locale/locale-types';
-import { ProductOptionGroupEntity } from './product-option-group.entity';
-import { ProductOptionGroup } from './product-option-group.interface';
+import { ProductOptionGroup } from './product-option-group.entity';
 
 @Entity('product_option_group_translation')
-export class ProductOptionGroupTranslationEntity implements Translation<ProductOptionGroup> {
+export class ProductOptionGroupTranslation implements Translation<ProductOptionGroup> {
     @PrimaryGeneratedColumn() id: number;
 
-    @Column() languageCode: string;
+    @Column() languageCode: LanguageCode;
 
     @Column() name: string;
 
-    @ManyToOne(type => ProductOptionGroupEntity, base => base.translations)
-    base: ProductOptionGroupEntity;
+    @ManyToOne(type => ProductOptionGroup, base => base.translations)
+    base: ProductOptionGroup;
 }

+ 10 - 9
modules/core/entity/product-option-group/product-option-group.entity.ts

@@ -1,22 +1,23 @@
 import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
-import { Translatable } from '../../locale/locale-types';
-import { ProductOptionEntity } from '../product-option/product-option.entity';
-import { ProductOptionGroupTranslationEntity } from './product-option-group-translation.entity';
-import { ProductOptionGroup } from './product-option-group.interface';
+import { LocaleString, Translatable, Translation } from '../../locale/locale-types';
+import { ProductOption } from '../product-option/product-option.entity';
+import { ProductOptionGroupTranslation } from './product-option-group-translation.entity';
 
 @Entity('product_option_group')
-export class ProductOptionGroupEntity implements Translatable<ProductOptionGroup> {
+export class ProductOptionGroup implements Translatable {
     @PrimaryGeneratedColumn() id: number;
 
+    name: LocaleString;
+
     @Column() code: string;
 
     @CreateDateColumn() createdAt: string;
 
     @UpdateDateColumn() updatedAt: string;
 
-    @OneToMany(type => ProductOptionGroupTranslationEntity, translation => translation.base)
-    translations: ProductOptionGroupTranslationEntity[];
+    @OneToMany(type => ProductOptionGroupTranslation, translation => translation.base)
+    translations: Translation<ProductOptionGroup>[];
 
-    @OneToMany(type => ProductOptionEntity, product => product)
-    options: ProductOptionEntity[];
+    @OneToMany(type => ProductOption, product => product)
+    options: ProductOption[];
 }

+ 0 - 9
modules/core/entity/product-option-group/product-option-group.interface.ts

@@ -1,9 +0,0 @@
-import { LocaleString } from '../../locale/locale-types';
-
-export interface ProductOptionGroup {
-    id: number;
-    code: string;
-    name: LocaleString;
-    createdAt: string;
-    updatedAt: string;
-}

+ 6 - 6
modules/core/entity/product-option/product-option-translation.entity.ts

@@ -1,16 +1,16 @@
 import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
+import { LanguageCode } from '../../locale/language-code';
 import { Translation } from '../../locale/locale-types';
-import { ProductOptionEntity } from './product-option.entity';
-import { ProductOption } from './product-option.interface';
+import { ProductOption } from './product-option.entity';
 
 @Entity('product_option_translation')
-export class ProductOptionTranslationEntity implements Translation<ProductOption> {
+export class ProductOptionTranslation implements Translation<ProductOption> {
     @PrimaryGeneratedColumn() id: number;
 
-    @Column() languageCode: string;
+    @Column() languageCode: LanguageCode;
 
     @Column() name: string;
 
-    @ManyToOne(type => ProductOptionEntity, base => base.translations)
-    base: ProductOptionEntity;
+    @ManyToOne(type => ProductOption, base => base.translations)
+    base: ProductOption;
 }

+ 10 - 9
modules/core/entity/product-option/product-option.entity.ts

@@ -7,24 +7,25 @@ import {
     PrimaryGeneratedColumn,
     UpdateDateColumn,
 } from 'typeorm';
-import { Translatable } from '../../locale/locale-types';
-import { ProductOptionGroupEntity } from '../product-option-group/product-option-group.entity';
-import { ProductOptionTranslationEntity } from './product-option-translation.entity';
-import { ProductOption } from './product-option.interface';
+import { LocaleString, Translatable, Translation } from '../../locale/locale-types';
+import { ProductOptionGroup } from '../product-option-group/product-option-group.entity';
+import { ProductOptionTranslation } from './product-option-translation.entity';
 
 @Entity('product_option')
-export class ProductOptionEntity implements Translatable<ProductOption> {
+export class ProductOption implements Translatable {
     @PrimaryGeneratedColumn() id: number;
 
+    name: LocaleString;
+
     @Column() code: string;
 
     @CreateDateColumn() createdAt: string;
 
     @UpdateDateColumn() updatedAt: string;
 
-    @OneToMany(type => ProductOptionTranslationEntity, translation => translation.base)
-    translations: ProductOptionTranslationEntity[];
+    @OneToMany(type => ProductOptionTranslation, translation => translation.base)
+    translations: Translation<ProductOption>[];
 
-    @ManyToOne(type => ProductOptionGroupEntity)
-    group: ProductOptionGroupEntity;
+    @ManyToOne(type => ProductOptionGroup)
+    group: ProductOptionGroup;
 }

+ 0 - 10
modules/core/entity/product-option/product-option.interface.ts

@@ -1,10 +0,0 @@
-import { LocaleString } from '../../locale/locale-types';
-import { ProductOptionGroup } from '../product-option-group/product-option-group.interface';
-
-export interface ProductOption {
-    id: number;
-    code: string;
-    name: LocaleString;
-    createdAt: string;
-    updatedAt: string;
-}

+ 1 - 1
modules/core/entity/product-variant/create-product-variant.dto.ts

@@ -1,5 +1,5 @@
 import { TranslatedInput } from '../../locale/locale-types';
-import { ProductVariant } from './product-variant.interface';
+import { ProductVariant } from './product-variant.entity';
 
 export interface CreateProductVariantDto extends TranslatedInput<ProductVariant> {
     sku: string;

+ 5 - 5
modules/core/entity/product-variant/product-variant-translation.entity.ts

@@ -1,16 +1,16 @@
 import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
+import { LanguageCode } from '../../locale/language-code';
 import { Translation } from '../../locale/locale-types';
-import { ProductVariantEntity } from './product-variant.entity';
-import { ProductVariant } from './product-variant.interface';
+import { ProductVariant } from './product-variant.entity';
 
 @Entity('product_variant_translation')
 export class ProductVariantTranslationEntity implements Translation<ProductVariant> {
     @PrimaryGeneratedColumn() id: number;
 
-    @Column() languageCode: string;
+    @Column() languageCode: LanguageCode;
 
     @Column() name: string;
 
-    @ManyToOne(type => ProductVariantEntity, base => base.translations)
-    base: ProductVariantEntity;
+    @ManyToOne(type => ProductVariant, base => base.translations)
+    base: ProductVariant;
 }

+ 10 - 9
modules/core/entity/product-variant/product-variant.entity.ts

@@ -9,16 +9,17 @@ import {
     PrimaryGeneratedColumn,
     UpdateDateColumn,
 } from 'typeorm';
-import { Translatable, Translation } from '../../locale/locale-types';
-import { ProductOptionEntity } from '../product-option/product-option.entity';
-import { ProductEntity } from '../product/product.entity';
+import { LocaleString, Translatable, Translation } from '../../locale/locale-types';
+import { ProductOption } from '../product-option/product-option.entity';
+import { Product } from '../product/product.entity';
 import { ProductVariantTranslationEntity } from './product-variant-translation.entity';
-import { ProductVariant } from './product-variant.interface';
 
 @Entity('product_variant')
-export class ProductVariantEntity implements Translatable<ProductVariant> {
+export class ProductVariant implements Translatable {
     @PrimaryGeneratedColumn() id: number;
 
+    name: LocaleString;
+
     @Column() sku: string;
 
     @Column() image: string;
@@ -32,10 +33,10 @@ export class ProductVariantEntity implements Translatable<ProductVariant> {
     @OneToMany(type => ProductVariantTranslationEntity, translation => translation.base)
     translations: Translation<ProductVariant>[];
 
-    @ManyToOne(type => ProductEntity, product => product.variants)
-    product: ProductEntity;
+    @ManyToOne(type => Product, product => product.variants)
+    product: Product;
 
-    @ManyToMany(type => ProductOptionEntity)
+    @ManyToMany(type => ProductOption)
     @JoinTable()
-    options: ProductOptionEntity[];
+    options: ProductOption[];
 }

+ 0 - 13
modules/core/entity/product-variant/product-variant.interface.ts

@@ -1,13 +0,0 @@
-import { LocaleString } from '../../locale/locale-types';
-import { ProductOption } from '../product-option/product-option.interface';
-
-export class ProductVariant {
-    id: number;
-    sku: string;
-    name: LocaleString;
-    image: string;
-    price: number;
-    options: ProductOption[];
-    createdAt: string;
-    updatedAt: string;
-}

+ 1 - 1
modules/core/entity/product/create-product.dto.ts

@@ -1,6 +1,6 @@
 import { TranslatedInput } from '../../locale/locale-types';
 import { CreateProductVariantDto } from '../product-variant/create-product-variant.dto';
-import { Product } from './product.interface';
+import { Product } from './product.entity';
 
 export interface CreateProductDto extends TranslatedInput<Product> {
     image?: string;

+ 6 - 6
modules/core/entity/product/product-translation.entity.ts

@@ -1,13 +1,13 @@
 import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
+import { LanguageCode } from '../../locale/language-code';
 import { Translation } from '../../locale/locale-types';
-import { ProductEntity } from './product.entity';
-import { Product } from './product.interface';
+import { Product } from './product.entity';
 
 @Entity('product_translation')
-export class ProductTranslationEntity implements Translation<Product> {
+export class ProductTranslation implements Translation<Product> {
     @PrimaryGeneratedColumn() id: number;
 
-    @Column() languageCode: string;
+    @Column() languageCode: LanguageCode;
 
     @Column() name: string;
 
@@ -15,6 +15,6 @@ export class ProductTranslationEntity implements Translation<Product> {
 
     @Column() description: string;
 
-    @ManyToOne(type => ProductEntity, base => base.translations)
-    base: ProductEntity;
+    @ManyToOne(type => Product, base => base.translations)
+    base: Product;
 }

+ 16 - 15
modules/core/entity/product/product.entity.ts

@@ -8,33 +8,34 @@ import {
     PrimaryGeneratedColumn,
     UpdateDateColumn,
 } from 'typeorm';
-import { Translatable, Translation } from '../../locale/locale-types';
-import { ProductOptionGroupEntity } from '../product-option-group/product-option-group.entity';
-import { ProductOptionGroup } from '../product-option-group/product-option-group.interface';
-import { ProductOptionEntity } from '../product-option/product-option.entity';
-import { ProductOption } from '../product-option/product-option.interface';
-import { ProductVariantEntity } from '../product-variant/product-variant.entity';
-import { ProductVariant } from '../product-variant/product-variant.interface';
-import { ProductTranslationEntity } from './product-translation.entity';
-import { Product } from './product.interface';
+import { LocaleString, Translatable, Translation } from '../../locale/locale-types';
+import { ProductOptionGroup } from '../product-option-group/product-option-group.entity';
+import { ProductVariant } from '../product-variant/product-variant.entity';
+import { ProductTranslation } from './product-translation.entity';
 
 @Entity('product')
-export class ProductEntity implements Translatable<Product> {
+export class Product implements Translatable {
     @PrimaryGeneratedColumn() id: number;
 
+    name: LocaleString;
+
+    slug: LocaleString;
+
+    description: LocaleString;
+
     @Column() image: string;
 
     @CreateDateColumn() createdAt: string;
 
     @UpdateDateColumn() updatedAt: string;
 
-    @OneToMany(type => ProductTranslationEntity, translation => translation.base)
+    @OneToMany(type => ProductTranslation, translation => translation.base)
     translations: Translation<Product>[];
 
-    @OneToMany(type => ProductVariantEntity, variant => variant.product)
-    variants: ProductVariantEntity[];
+    @OneToMany(type => ProductVariant, variant => variant.product)
+    variants: ProductVariant[];
 
-    @ManyToMany(type => ProductOptionGroupEntity)
+    @ManyToMany(type => ProductOptionGroup)
     @JoinTable()
-    optionGroups: ProductOptionGroupEntity[];
+    optionGroups: ProductOptionGroup[];
 }

+ 18 - 3
modules/core/entity/product/product.graphql

@@ -1,14 +1,23 @@
 type Product {
-    id: Int
+    id: Int!
     name: String
     slug: String
     description: String
     image: String
     variants: [ProductVariant]
     optionGroups: [ProductOptionGroup]
+    translations: [ProductTranslation]
 }
 
-input CreateProductTranslation {
+type ProductTranslation {
+    id: Int!
+    languageCode: LanguageCode!
+    name: String!
+    slug: String!
+    description: String
+}
+
+input ProductTranslationInput {
     languageCode: LanguageCode!
     name: String!
     slug: String
@@ -16,8 +25,14 @@ input CreateProductTranslation {
 }
 
 input CreateProductInput {
-    translations: [CreateProductTranslation]!
+    translations: [ProductTranslationInput]!
     image: String
     variants: [CreateProductVariantInput]
     optionGroupCodes: [String]
 }
+
+input UpdateProductInput {
+    translations: [ProductTranslationInput]!
+    image: String
+    optionGroupCodes: [String]
+}

+ 0 - 15
modules/core/entity/product/product.interface.ts

@@ -1,15 +0,0 @@
-import { LocaleString } from '../../locale/locale-types';
-import { ProductOptionGroup } from '../product-option-group/product-option-group.interface';
-import { ProductVariant } from '../product-variant/product-variant.interface';
-
-export interface Product {
-    id: number;
-    name: LocaleString;
-    slug: LocaleString;
-    description: LocaleString;
-    image: string;
-    optionGroups: ProductOptionGroup[];
-    variants: ProductVariant[];
-    createdAt: string;
-    updatedAt: string;
-}

+ 7 - 0
modules/core/entity/product/update-product.dto.ts

@@ -0,0 +1,7 @@
+import { TranslatedInput } from '../../locale/locale-types';
+import { Product } from './product.entity';
+
+export interface CreateProductDto extends TranslatedInput<Product> {
+    image?: string;
+    optionGroupCodes?: [string];
+}

+ 2 - 3
modules/core/entity/user/user.entity.ts

@@ -1,10 +1,9 @@
 import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
 import { Role } from '../../auth/role';
-import { AddressEntity } from '../address/address.entity';
-import { User } from './user.interface';
+import { Address } from '../address/address.entity';
 
 @Entity('user')
-export class UserEntity implements User {
+export class User {
     @PrimaryGeneratedColumn() id: number;
 
     @Column() identifier: string;

+ 0 - 16
modules/core/entity/user/user.interface.ts

@@ -1,16 +0,0 @@
-import { Role } from '../../auth/role';
-import { Address } from '../address/address.interface';
-
-/**
- * A registered user of the system, either a Customer or Administrator. The User interface / entity is responsible
- * for the identity of the user for the purposes of authentication & authorization.
- */
-export class User {
-    id: number;
-    identifier: string;
-    passwordHash: string;
-    roles: Role[];
-    lastLogin: string;
-    createdAt: string;
-    updatedAt: string;
-}

+ 3 - 16
modules/core/locale/locale-types.ts

@@ -14,13 +14,7 @@ export type NonTranslateableKeys<T> = { [K in keyof T]: T[K] extends LocaleStrin
 /**
  * Entities which have localizable string properties should implement this type.
  */
-export type Translatable<T> =
-    // Translatable must include all non-translatable keys of the interface
-    { [K in NonTranslateableKeys<T>]: T[K] extends Array<any> ? Array<Translatable<T[K][number]>> : T[K] | Translatable<T[K]> } &
-    // Translatable must not include any translatable keys (these are instead handled by the Translation)
-    { [K in TranslatableKeys<T>]?: never } &
-    // Translatable must include a reference to all translations of the translatable keys
-    { translations: Translation<T>[] };
+export interface Translatable { translations: Translation<any>[]; }
 
 // prettier-ignore
 /**
@@ -28,16 +22,9 @@ export type Translatable<T> =
  */
 export type Translation<T> =
     // Translation must include the languageCode and a reference to the base Translatable entity it is associated with
-    { languageCode: string; base: Translatable<T>; } &
+    { languageCode: LanguageCode; base: T; } &
     // Translation must include all translatable keys as a string type
-    { [K in TranslatableKeys<T>]: string } &
-    { [key: string]: any };
-
-// prettier-ignore
-export type TranslatedEntity<T> =
-    // Translatable must include all non-translatable keys of the interface
-    { [K in NonTranslateableKeys<T>]: T[K] extends Array<any> ? Array<Translatable<T[K][number]>> : T[K] } &
-    { [K in TranslatableKeys<T>]: string };
+    { [K in TranslatableKeys<T>]: string; };
 
 export type LocalizedInput<T> = { [K in TranslatableKeys<T>]: string } & { languageCode: LanguageCode };
 

+ 26 - 33
modules/core/locale/translate-entity.spec.ts

@@ -1,30 +1,29 @@
-import { ProductOptionTranslationEntity } from '../entity/product-option/product-option-translation.entity';
-import { ProductOptionEntity } from '../entity/product-option/product-option.entity';
-import { ProductOption } from '../entity/product-option/product-option.interface';
+import { ProductOptionTranslation } from '../entity/product-option/product-option-translation.entity';
+import { ProductOption } from '../entity/product-option/product-option.entity';
 import { ProductVariantTranslationEntity } from '../entity/product-variant/product-variant-translation.entity';
-import { ProductVariantEntity } from '../entity/product-variant/product-variant.entity';
-import { ProductVariant } from '../entity/product-variant/product-variant.interface';
-import { ProductTranslationEntity } from '../entity/product/product-translation.entity';
-import { ProductEntity } from '../entity/product/product.entity';
+import { ProductVariant } from '../entity/product-variant/product-variant.entity';
+import { ProductTranslation } from '../entity/product/product-translation.entity';
+import { Product } from '../entity/product/product.entity';
+import { LanguageCode } from './language-code';
 import { Translatable, Translation } from './locale-types';
 import { translateDeep, translateEntity } from './translate-entity';
 
-const LANGUAGE_CODE = 'en';
+const LANGUAGE_CODE = LanguageCode.EN;
 const PRODUCT_NAME = 'English Name';
 const VARIANT_NAME = 'English Variant';
 const OPTION_NAME = 'English Option';
 
 describe('translateEntity()', () => {
-    let product: ProductEntity;
-    let productTranslation: ProductTranslationEntity;
+    let product: Product;
+    let productTranslation: ProductTranslation;
 
     beforeEach(() => {
-        productTranslation = new ProductTranslationEntity();
+        productTranslation = new ProductTranslation();
         productTranslation.id = 2;
         productTranslation.languageCode = LANGUAGE_CODE;
         productTranslation.name = PRODUCT_NAME;
 
-        product = new ProductEntity();
+        product = new Product();
         product.id = 1;
         product.translations = [productTranslation];
     });
@@ -47,17 +46,11 @@ describe('translateEntity()', () => {
         expect(result).not.toHaveProperty('languageCode');
     });
 
-    it('should remove the translations property from the translatable', () => {
-        const result = translateEntity(product);
-
-        expect(result).not.toHaveProperty('translations');
-    });
-
     it('throw if there are no translations available', () => {
         product.translations = [];
 
         expect(() => translateEntity(product)).toThrow(
-            'Translatable entity "ProductEntity" has not been translated into the requested language',
+            'Translatable entity "Product" has not been translated into the requested language',
         );
     });
 });
@@ -68,10 +61,10 @@ describe('translateDeep()', () => {
         singleRealVariant: ProductVariant;
     }
 
-    class TestProductEntity implements Translatable<TestProduct> {
+    class TestProductEntity implements Translatable {
         id: number;
         singleTestVariant: TestVariantEntity;
-        singleRealVariant: ProductVariantEntity;
+        singleRealVariant: ProductVariant;
         translations: Translation<TestProduct>[];
     }
 
@@ -79,33 +72,33 @@ describe('translateDeep()', () => {
         singleOption: ProductOption;
     }
 
-    class TestVariantEntity implements Translatable<TestVariant> {
+    class TestVariantEntity implements Translatable {
         id: number;
-        singleOption: ProductOptionEntity;
+        singleOption: ProductOption;
         translations: Translation<TestVariant>[];
     }
 
     let testProduct: TestProductEntity;
     let testVariant: TestVariantEntity;
-    let product: ProductEntity;
-    let productTranslation: ProductTranslationEntity;
-    let productVariant: ProductVariantEntity;
+    let product: Product;
+    let productTranslation: ProductTranslation;
+    let productVariant: ProductVariant;
     let productVariantTranslation: ProductVariantTranslationEntity;
-    let productOption: ProductOptionEntity;
-    let productOptionTranslation: ProductOptionTranslationEntity;
+    let productOption: ProductOption;
+    let productOptionTranslation: ProductOptionTranslation;
 
     beforeEach(() => {
-        productTranslation = new ProductTranslationEntity();
+        productTranslation = new ProductTranslation();
         productTranslation.id = 2;
         productTranslation.languageCode = LANGUAGE_CODE;
         productTranslation.name = PRODUCT_NAME;
 
-        productOptionTranslation = new ProductOptionTranslationEntity();
+        productOptionTranslation = new ProductOptionTranslation();
         productOptionTranslation.id = 31;
         productOptionTranslation.languageCode = LANGUAGE_CODE;
         productOptionTranslation.name = OPTION_NAME;
 
-        productOption = new ProductOptionEntity();
+        productOption = new ProductOption();
         productOption.id = 3;
         productOption.translations = [productOptionTranslation];
 
@@ -114,12 +107,12 @@ describe('translateDeep()', () => {
         productVariantTranslation.languageCode = LANGUAGE_CODE;
         productVariantTranslation.name = VARIANT_NAME;
 
-        productVariant = new ProductVariantEntity();
+        productVariant = new ProductVariant();
         productVariant.id = 3;
         productVariant.translations = [productVariantTranslation];
         productVariant.options = [productOption];
 
-        product = new ProductEntity();
+        product = new Product();
         product.id = 1;
         product.translations = [productTranslation];
         product.variants = [productVariant];

+ 7 - 7
modules/core/locale/translate-entity.ts

@@ -1,4 +1,4 @@
-import { Translatable, TranslatedEntity } from './locale-types';
+import { Translatable } from './locale-types';
 
 // prettier-ignore
 export type TranslatableRelationsKeys<T> = {
@@ -31,7 +31,7 @@ export class NotTranslatedError extends Error {}
  * Converts a Translatable entity into the public-facing entity by unwrapping
  * the translated strings from the first of the Translation entities.
  */
-export function translateEntity<T>(translatable: Translatable<T>): TranslatedEntity<T> {
+export function translateEntity<T extends Translatable>(translatable: T): T {
     if (!translatable.translations || translatable.translations.length === 0) {
         throw new NotTranslatedError(
             `Translatable entity "${
@@ -42,7 +42,7 @@ export function translateEntity<T>(translatable: Translatable<T>): TranslatedEnt
     const translation = translatable.translations[0];
 
     const translated = { ...(translatable as any) };
-    delete translated.translations;
+    // delete translated.translations;
 
     for (const [key, value] of Object.entries(translation)) {
         if (key !== 'languageCode' && key !== 'id') {
@@ -55,11 +55,11 @@ export function translateEntity<T>(translatable: Translatable<T>): TranslatedEnt
 /**
  * Translates an entity and its deeply-nested translatable properties. Supports up to 2 levels of nesting.
  */
-export function translateDeep<T>(
-    translatable: Translatable<T>,
+export function translateDeep<T extends Translatable>(
+    translatable: T,
     translatableRelations: DeepTranslatableRelations<T> = [],
 ): T {
-    let translatedEntity: TranslatedEntity<T>;
+    let translatedEntity: T;
     try {
         translatedEntity = translateEntity(translatable);
     } catch (e) {
@@ -96,7 +96,7 @@ export function translateDeep<T>(
         }
     }
 
-    return translatedEntity as any;
+    return translatedEntity;
 }
 
 function translateLeaf(object: any, property: string): any {

+ 13 - 14
modules/core/repository/product-repository.ts

@@ -1,18 +1,17 @@
 import { AbstractRepository, EntityRepository, SelectQueryBuilder } from 'typeorm';
 import { CreateProductDto } from '../entity/product/create-product.dto';
-import { ProductTranslationEntity } from '../entity/product/product-translation.entity';
-import { ProductEntity } from '../entity/product/product.entity';
-import { Product } from '../entity/product/product.interface';
+import { ProductTranslation } from '../entity/product/product-translation.entity';
+import { Product } from '../entity/product/product.entity';
 import { LanguageCode } from '../locale/language-code';
 import { translateDeep } from '../locale/translate-entity';
 
-@EntityRepository(ProductEntity)
-export class ProductRepository extends AbstractRepository<ProductEntity> {
+@EntityRepository(Product)
+export class ProductRepository extends AbstractRepository<Product> {
     /**
      * Returns an array of Products including ProductVariants, translated into the
      * specified language.
      */
-    find(languageCode: LanguageCode): Promise<ProductEntity[]> {
+    find(languageCode: LanguageCode): Promise<Product[]> {
         return this.getProductQueryBuilder(languageCode).getMany();
     }
 
@@ -20,7 +19,7 @@ export class ProductRepository extends AbstractRepository<ProductEntity> {
      * Returns single Product including ProductVariants, translated into the
      * specified language.
      */
-    findOne(id: number, languageCode: LanguageCode): Promise<ProductEntity | undefined> {
+    findOne(id: number, languageCode: LanguageCode): Promise<Product | undefined> {
         return this.getProductQueryBuilder(languageCode)
             .andWhere('product.id = :id', { id })
             .getOne();
@@ -29,7 +28,7 @@ export class ProductRepository extends AbstractRepository<ProductEntity> {
     /**
      * Creates a new Product with one or more ProductTranslations.
      */
-    async create(productEntity: ProductEntity, translations: ProductTranslationEntity[]): Promise<ProductEntity> {
+    async create(productEntity: Product, translations: ProductTranslation[]): Promise<Product> {
         for (const translation of translations) {
             await this.manager.save(translation);
         }
@@ -37,31 +36,31 @@ export class ProductRepository extends AbstractRepository<ProductEntity> {
         return this.manager.save(productEntity);
     }
 
-    private getProductQueryBuilder(languageCode: LanguageCode): SelectQueryBuilder<ProductEntity> {
+    private getProductQueryBuilder(languageCode: LanguageCode): SelectQueryBuilder<Product> {
         return this.manager
-            .createQueryBuilder(ProductEntity, 'product')
+            .createQueryBuilder(Product, 'product')
             .leftJoinAndSelect('product.variants', 'variant')
             .leftJoinAndSelect('product.optionGroups', 'option_group')
             .leftJoinAndSelect(
                 'product.translations',
                 'product_translation',
-                'product_translation.languageCode = :code',
+                // 'product_translation.languageCode = :code',
             )
             .leftJoinAndSelect(
                 'variant.translations',
                 'product_variant_translation',
-                'product_variant_translation.languageCode = :code',
+                // 'product_variant_translation.languageCode = :code',
             )
             .leftJoinAndSelect(
                 'option_group.translations',
                 'option_group_translation',
-                'option_group_translation.languageCode = :code',
+                // 'option_group_translation.languageCode = :code',
             )
             .leftJoinAndSelect('variant.options', 'variant_options')
             .leftJoinAndSelect(
                 'variant_options.translations',
                 'variant_options_translation',
-                'variant_options_translation.languageCode = :code',
+                // 'variant_options_translation.languageCode = :code',
             )
             .setParameters({ code: languageCode });
     }

+ 11 - 13
modules/core/repository/product-variant-repository.ts

@@ -1,20 +1,18 @@
 import { AbstractRepository, EntityRepository, SelectQueryBuilder } from 'typeorm';
 import { ProductVariantTranslationEntity } from '../entity/product-variant/product-variant-translation.entity';
-import { ProductVariantEntity } from '../entity/product-variant/product-variant.entity';
-import { ProductVariant } from '../entity/product-variant/product-variant.interface';
-import { ProductTranslationEntity } from '../entity/product/product-translation.entity';
-import { ProductEntity } from '../entity/product/product.entity';
-import { Product } from '../entity/product/product.interface';
+import { ProductVariant } from '../entity/product-variant/product-variant.entity';
+import { ProductTranslation } from '../entity/product/product-translation.entity';
+import { Product } from '../entity/product/product.entity';
 import { LanguageCode } from '../locale/language-code';
 import { translateDeep } from '../locale/translate-entity';
 
-@EntityRepository(ProductEntity)
-export class ProductVariantRepository extends AbstractRepository<ProductVariantEntity> {
+@EntityRepository(Product)
+export class ProductVariantRepository extends AbstractRepository<ProductVariant> {
     /**
      * Returns an array of Products including ProductVariants, translated into the
      * specified language.
      */
-    localeFindByProductId(productId: number, languageCode: LanguageCode): Promise<ProductVariantEntity[]> {
+    localeFindByProductId(productId: number, languageCode: LanguageCode): Promise<ProductVariant[]> {
         return this.getProductVariantQueryBuilder(productId, languageCode).getMany();
     }
 
@@ -22,10 +20,10 @@ export class ProductVariantRepository extends AbstractRepository<ProductVariantE
      * Creates a new Product with one or more ProductTranslations.
      */
     async create(
-        product: ProductEntity | Product,
-        productVariantEntity: ProductVariantEntity,
+        product: Product,
+        productVariantEntity: ProductVariant,
         translations: ProductVariantTranslationEntity[],
-    ): Promise<ProductVariantEntity> {
+    ): Promise<ProductVariant> {
         for (const translation of translations) {
             await this.manager.save(translation);
         }
@@ -37,11 +35,11 @@ export class ProductVariantRepository extends AbstractRepository<ProductVariantE
     private getProductVariantQueryBuilder(
         productId: number,
         languageCode: LanguageCode,
-    ): SelectQueryBuilder<ProductVariantEntity> {
+    ): SelectQueryBuilder<ProductVariant> {
         const code = languageCode || LanguageCode.EN;
 
         return this.manager
-            .createQueryBuilder(ProductVariantEntity, 'variant')
+            .createQueryBuilder(ProductVariant, 'variant')
             .leftJoinAndSelect('product_variant.options', 'option')
             .leftJoinAndSelect(
                 'variant.translations',

+ 5 - 7
modules/core/service/customer.service.ts

@@ -1,26 +1,24 @@
 import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import { Connection } from 'typeorm';
-import { AddressEntity } from '../entity/address/address.entity';
-import { Address } from '../entity/address/address.interface';
-import { CustomerEntity } from '../entity/customer/customer.entity';
-import { Customer } from '../entity/customer/customer.interface';
+import { Address } from '../entity/address/address.entity';
+import { Customer } from '../entity/customer/customer.entity';
 
 @Injectable()
 export class CustomerService {
     constructor(@InjectConnection() private connection: Connection) {}
 
     findAll(): Promise<Customer[]> {
-        return this.connection.manager.find(CustomerEntity);
+        return this.connection.manager.find(Customer);
     }
 
     findOne(userId: number): Promise<Customer | undefined> {
-        return this.connection.manager.findOne(CustomerEntity, userId);
+        return this.connection.manager.findOne(Customer, userId);
     }
 
     findAddressesByCustomerId(customerId: number): Promise<Address[]> {
         return this.connection
-            .getRepository(AddressEntity)
+            .getRepository(Address)
             .createQueryBuilder('address')
             .where('address.customerId = :id', { id: customerId })
             .getMany();

+ 7 - 7
modules/core/service/product-variant.service.spec.ts

@@ -1,9 +1,9 @@
 import { Test } from '@nestjs/testing';
 import { Connection } from 'typeorm';
-import { ProductOptionEntity } from '../entity/product-option/product-option.entity';
+import { ProductOption } from '../entity/product-option/product-option.entity';
 import { ProductVariantTranslationEntity } from '../entity/product-variant/product-variant-translation.entity';
-import { ProductVariantEntity } from '../entity/product-variant/product-variant.entity';
-import { ProductEntity } from '../entity/product/product.entity';
+import { ProductVariant } from '../entity/product-variant/product-variant.entity';
+import { Product } from '../entity/product/product.entity';
 import { LanguageCode } from '../locale/language-code';
 import { ProductVariantRepository } from '../repository/product-variant-repository';
 import { MockConnection } from '../repository/repository.mock';
@@ -24,7 +24,7 @@ describe('ProductVariantService', () => {
 
     describe('create()', () => {
         it('calls ProductVariantRepository.create with product and translation entities', () => {
-            const productEntity = new ProductEntity();
+            const productEntity = new Product();
             productVariantService.create(productEntity, {
                 sku: '123456',
                 price: 123,
@@ -42,15 +42,15 @@ describe('ProductVariantService', () => {
 
             const [arg1, arg2, arg3] = connection.getCustomRepository(ProductVariantRepository).create.mock.calls[0];
             expect(arg1).toBe(productEntity);
-            expect(arg2 instanceof ProductVariantEntity).toBe(true);
+            expect(arg2 instanceof ProductVariant).toBe(true);
             expect(Array.isArray(arg3)).toBe(true);
             expect(arg3.length).toBe(2);
             expect(arg3[0] instanceof ProductVariantTranslationEntity).toBe(true);
         });
 
         it('adds Options to the productVariant when specified', async () => {
-            const productEntity = new ProductEntity();
-            const productOptionRepository = connection.registerMockRepository(ProductOptionEntity);
+            const productEntity = new Product();
+            const productOptionRepository = connection.registerMockRepository(ProductOption);
             const mockOptions = [{ code: 'option1' }, { code: 'option2' }, { code: 'option3' }];
             productOptionRepository.find.mockReturnValue(mockOptions);
 

+ 6 - 11
modules/core/service/product-variant.service.ts

@@ -1,13 +1,11 @@
 import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import { Connection } from 'typeorm';
-import { ProductOptionEntity } from '../entity/product-option/product-option.entity';
+import { ProductOption } from '../entity/product-option/product-option.entity';
 import { CreateProductVariantDto } from '../entity/product-variant/create-product-variant.dto';
 import { ProductVariantTranslationEntity } from '../entity/product-variant/product-variant-translation.entity';
-import { ProductVariantEntity } from '../entity/product-variant/product-variant.entity';
-import { ProductVariant } from '../entity/product-variant/product-variant.interface';
-import { ProductEntity } from '../entity/product/product.entity';
-import { Product } from '../entity/product/product.interface';
+import { ProductVariant } from '../entity/product-variant/product-variant.entity';
+import { Product } from '../entity/product/product.entity';
 import { translateDeep } from '../locale/translate-entity';
 import { ProductVariantRepository } from '../repository/product-variant-repository';
 
@@ -15,18 +13,15 @@ import { ProductVariantRepository } from '../repository/product-variant-reposito
 export class ProductVariantService {
     constructor(@InjectConnection() private connection: Connection) {}
 
-    async create(
-        product: Product | ProductEntity,
-        createProductVariantDto: CreateProductVariantDto,
-    ): Promise<ProductVariant> {
+    async create(product: Product, createProductVariantDto: CreateProductVariantDto): Promise<ProductVariant> {
         const { sku, price, image, optionCodes, translations } = createProductVariantDto;
-        const productVariant = new ProductVariantEntity();
+        const productVariant = new ProductVariant();
         productVariant.sku = sku;
         productVariant.price = price;
         productVariant.image = image!;
 
         if (optionCodes && optionCodes.length) {
-            const options = await this.connection.getRepository(ProductOptionEntity).find();
+            const options = await this.connection.getRepository(ProductOption).find();
             const selectedOptions = options.filter(o => optionCodes.includes(o.code));
             productVariant.options = selectedOptions;
         }

+ 6 - 6
modules/core/service/product.service.spec.ts

@@ -1,8 +1,8 @@
 import { Test } from '@nestjs/testing';
 import { Connection } from 'typeorm';
-import { ProductOptionGroupEntity } from '../entity/product-option-group/product-option-group.entity';
-import { ProductTranslationEntity } from '../entity/product/product-translation.entity';
-import { ProductEntity } from '../entity/product/product.entity';
+import { ProductOptionGroup } from '../entity/product-option-group/product-option-group.entity';
+import { ProductTranslation } from '../entity/product/product-translation.entity';
+import { Product } from '../entity/product/product.entity';
 import { LanguageCode } from '../locale/language-code';
 import { ProductRepository } from '../repository/product-repository';
 import { MockConnection } from '../repository/repository.mock';
@@ -41,14 +41,14 @@ describe('ProductService', () => {
             });
 
             const [arg1, arg2] = connection.getCustomRepository(ProductRepository).create.mock.calls[0];
-            expect(arg1 instanceof ProductEntity).toBe(true);
+            expect(arg1 instanceof Product).toBe(true);
             expect(Array.isArray(arg2)).toBe(true);
             expect(arg2.length).toBe(2);
-            expect(arg2[0] instanceof ProductTranslationEntity).toBe(true);
+            expect(arg2[0] instanceof ProductTranslation).toBe(true);
         });
 
         it('adds OptionGroups to the product when specified', async () => {
-            const productOptionGroupRepository = connection.registerMockRepository(ProductOptionGroupEntity);
+            const productOptionGroupRepository = connection.registerMockRepository(ProductOptionGroup);
             const productRepository = connection.getCustomRepository(ProductRepository);
             const mockOptionGroups = [{ code: 'optionGroup1' }, { code: 'optionGroup2' }, { code: 'optionGroup3' }];
             productOptionGroupRepository.find.mockReturnValue(mockOptionGroups);

+ 8 - 9
modules/core/service/product.service.ts

@@ -1,11 +1,10 @@
 import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import { Connection } from 'typeorm';
-import { ProductOptionGroupEntity } from '../entity/product-option-group/product-option-group.entity';
+import { ProductOptionGroup } from '../entity/product-option-group/product-option-group.entity';
 import { CreateProductDto } from '../entity/product/create-product.dto';
-import { ProductTranslationEntity } from '../entity/product/product-translation.entity';
-import { ProductEntity } from '../entity/product/product.entity';
-import { Product } from '../entity/product/product.interface';
+import { ProductTranslation } from '../entity/product/product-translation.entity';
+import { Product } from '../entity/product/product.entity';
 import { LanguageCode } from '../locale/language-code';
 import { translateDeep } from '../locale/translate-entity';
 import { ProductRepository } from '../repository/product-repository';
@@ -30,18 +29,18 @@ export class ProductService {
 
     async create(createProductDto: CreateProductDto): Promise<Product> {
         const { variants, optionGroupCodes, image, translations } = createProductDto;
-        const productEntity = new ProductEntity();
-        const productTranslations: ProductTranslationEntity[] = [];
+        const productEntity = new Product();
+        const productTranslations: ProductTranslation[] = [];
 
         if (optionGroupCodes && optionGroupCodes.length) {
-            const optionGroups = await this.connection.getRepository(ProductOptionGroupEntity).find();
+            const optionGroups = await this.connection.getRepository(ProductOptionGroup).find();
             const selectedOptionGroups = optionGroups.filter(og => optionGroupCodes.includes(og.code));
             productEntity.optionGroups = selectedOptionGroups;
         }
 
         for (const input of createProductDto.translations) {
             const { languageCode, name, description, slug } = input;
-            const translation = new ProductTranslationEntity();
+            const translation = new ProductTranslation();
             translation.languageCode = languageCode;
             translation.name = name;
             translation.slug = slug;
@@ -55,7 +54,7 @@ export class ProductService {
             .then(product => translateDeep(product));
     }
 
-    private translateProductEntity(product: ProductEntity): Product {
+    private translateProductEntity(product: Product): Product {
         return translateDeep(product, ['optionGroups', 'variants', ['variants', 'options']]);
     }
 }

+ 1 - 1
modules/mock-data/mock-data-client.service.ts

@@ -2,7 +2,7 @@ import * as faker from 'faker/locale/en_GB';
 import { request } from 'graphql-request';
 import { CreateProductVariantDto } from '../core/entity/product-variant/create-product-variant.dto';
 import { CreateProductDto } from '../core/entity/product/create-product.dto';
-import { Product } from '../core/entity/product/product.interface';
+import { Product } from '../core/entity/product/product.entity';
 import { LanguageCode } from '../core/locale/language-code';
 import { LocalizedInput } from '../core/locale/locale-types';
 

+ 46 - 45
modules/mock-data/mock-data.service.ts

@@ -2,18 +2,19 @@ import * as faker from 'faker/locale/en_GB';
 import { Connection, createConnection } from 'typeorm';
 import { PasswordService } from '../core/auth/password.service';
 import { Role } from '../core/auth/role';
-import { AddressEntity } from '../core/entity/address/address.entity';
-import { AdministratorEntity } from '../core/entity/administrator/administrator.entity';
-import { CustomerEntity } from '../core/entity/customer/customer.entity';
-import { ProductOptionGroupTranslationEntity } from '../core/entity/product-option-group/product-option-group-translation.entity';
-import { ProductOptionGroupEntity } from '../core/entity/product-option-group/product-option-group.entity';
-import { ProductOptionTranslationEntity } from '../core/entity/product-option/product-option-translation.entity';
-import { ProductOptionEntity } from '../core/entity/product-option/product-option.entity';
+import { Address } from '../core/entity/address/address.entity';
+import { Administrator } from '../core/entity/administrator/administrator.entity';
+import { Customer } from '../core/entity/customer/customer.entity';
+import { ProductOptionGroupTranslation } from '../core/entity/product-option-group/product-option-group-translation.entity';
+import { ProductOptionGroup } from '../core/entity/product-option-group/product-option-group.entity';
+import { ProductOptionTranslation } from '../core/entity/product-option/product-option-translation.entity';
+import { ProductOption } from '../core/entity/product-option/product-option.entity';
 import { ProductVariantTranslationEntity } from '../core/entity/product-variant/product-variant-translation.entity';
-import { ProductVariantEntity } from '../core/entity/product-variant/product-variant.entity';
-import { ProductTranslationEntity } from '../core/entity/product/product-translation.entity';
-import { ProductEntity } from '../core/entity/product/product.entity';
-import { UserEntity } from '../core/entity/user/user.entity';
+import { ProductVariant } from '../core/entity/product-variant/product-variant.entity';
+import { ProductTranslation } from '../core/entity/product/product-translation.entity';
+import { Product } from '../core/entity/product/product.entity';
+import { User } from '../core/entity/user/user.entity';
+import { LanguageCode } from '../core/locale/language-code';
 
 // tslint:disable:no-console
 /**
@@ -53,17 +54,17 @@ export class MockDataService {
         console.log('Cleared all tables');
     }
 
-    async populateOptions(): Promise<ProductOptionGroupEntity> {
-        const sizeGroup = new ProductOptionGroupEntity();
+    async populateOptions(): Promise<ProductOptionGroup> {
+        const sizeGroup = new ProductOptionGroup();
         sizeGroup.code = 'size';
 
-        const sizeGroupEN = new ProductOptionGroupTranslationEntity();
-        sizeGroupEN.languageCode = 'en';
+        const sizeGroupEN = new ProductOptionGroupTranslation();
+        sizeGroupEN.languageCode = LanguageCode.EN;
         sizeGroupEN.name = 'Size';
         await this.connection.manager.save(sizeGroupEN);
-        const sizeGroupDE = new ProductOptionGroupTranslationEntity();
+        const sizeGroupDE = new ProductOptionGroupTranslation();
 
-        sizeGroupDE.languageCode = 'de';
+        sizeGroupDE.languageCode = LanguageCode.DE;
         sizeGroupDE.name = 'Größe';
         await this.connection.manager.save(sizeGroupDE);
 
@@ -76,17 +77,17 @@ export class MockDataService {
         return sizeGroup;
     }
 
-    private async populateSizeOptions(sizeGroup: ProductOptionGroupEntity) {
-        const sizeSmall = new ProductOptionEntity();
+    private async populateSizeOptions(sizeGroup: ProductOptionGroup) {
+        const sizeSmall = new ProductOption();
         sizeSmall.code = 'small';
 
-        const sizeSmallEN = new ProductOptionTranslationEntity();
-        sizeSmallEN.languageCode = 'en';
+        const sizeSmallEN = new ProductOptionTranslation();
+        sizeSmallEN.languageCode = LanguageCode.EN;
         sizeSmallEN.name = 'Small';
         await this.connection.manager.save(sizeSmallEN);
 
-        const sizeSmallDE = new ProductOptionTranslationEntity();
-        sizeSmallDE.languageCode = 'de';
+        const sizeSmallDE = new ProductOptionTranslation();
+        sizeSmallDE.languageCode = LanguageCode.DE;
         sizeSmallDE.name = 'Klein';
         await this.connection.manager.save(sizeSmallDE);
 
@@ -94,16 +95,16 @@ export class MockDataService {
         sizeSmall.group = sizeGroup;
         await this.connection.manager.save(sizeSmall);
 
-        const sizeLarge = new ProductOptionEntity();
+        const sizeLarge = new ProductOption();
         sizeLarge.code = 'large';
 
-        const sizeLargeEN = new ProductOptionTranslationEntity();
-        sizeLargeEN.languageCode = 'en';
+        const sizeLargeEN = new ProductOptionTranslation();
+        sizeLargeEN.languageCode = LanguageCode.EN;
         sizeLargeEN.name = 'Large';
         await this.connection.manager.save(sizeLargeEN);
 
-        const sizeLargeDE = new ProductOptionTranslationEntity();
-        sizeLargeDE.languageCode = 'de';
+        const sizeLargeDE = new ProductOptionTranslation();
+        sizeLargeDE.languageCode = LanguageCode.DE;
         sizeLargeDE.name = 'Groß';
         await this.connection.manager.save(sizeLargeDE);
 
@@ -114,33 +115,33 @@ export class MockDataService {
         sizeGroup.options = [sizeSmall, sizeLarge];
     }
 
-    async populateProducts(optionGroup: ProductOptionGroupEntity) {
+    async populateProducts(optionGroup: ProductOptionGroup) {
         for (let i = 0; i < 5; i++) {
             const addOption = i === 2 || i === 4;
 
-            const product = new ProductEntity();
+            const product = new Product();
             product.image = faker.image.imageUrl();
 
             const name = faker.commerce.productName();
             const slug = name.toLowerCase().replace(/\s+/g, '-');
             const description = faker.lorem.sentence();
 
-            const translation1 = this.makeProductTranslation('en', name, slug, description);
-            const translation2 = this.makeProductTranslation('de', name, slug, description);
+            const translation1 = this.makeProductTranslation(LanguageCode.EN, name, slug, description);
+            const translation2 = this.makeProductTranslation(LanguageCode.DE, name, slug, description);
             await this.connection.manager.save(translation1);
             await this.connection.manager.save(translation2);
 
             // 1 - 4 variants
             const variantCount = Math.floor(Math.random() * 4) + 1;
-            const variants: ProductVariantEntity[] = [];
+            const variants: ProductVariant[] = [];
             for (let j = 0; j < variantCount; j++) {
-                const variant = new ProductVariantEntity();
+                const variant = new ProductVariant();
                 const variantName = `${name} variant ${j + 1}`;
                 variant.image = faker.image.imageUrl();
                 variant.price = faker.random.number({ min: 100, max: 12000 });
 
-                const variantTranslation1 = this.makeProductVariantTranslation('en', variantName);
-                const variantTranslation2 = this.makeProductVariantTranslation('de', variantName);
+                const variantTranslation1 = this.makeProductVariantTranslation(LanguageCode.EN, variantName);
+                const variantTranslation2 = this.makeProductVariantTranslation(LanguageCode.DE, variantName);
                 await this.connection.manager.save(variantTranslation1);
                 await this.connection.manager.save(variantTranslation2);
 
@@ -169,20 +170,20 @@ export class MockDataService {
         const passwordService = new PasswordService();
 
         for (let i = 0; i < 5; i++) {
-            const customer = new CustomerEntity();
+            const customer = new Customer();
             customer.firstName = faker.name.firstName();
             customer.lastName = faker.name.lastName();
             customer.emailAddress = faker.internet.email(customer.firstName, customer.lastName);
             customer.phoneNumber = faker.phone.phoneNumber();
 
-            const user = new UserEntity();
+            const user = new User();
             user.passwordHash = await passwordService.hash('test');
             user.identifier = customer.emailAddress;
             user.roles = [Role.Customer];
 
             await this.connection.manager.save(user);
 
-            const address = new AddressEntity();
+            const address = new Address();
             address.fullName = `${customer.firstName} ${customer.lastName}`;
             address.streetLine1 = faker.address.streetAddress();
             address.city = faker.address.city();
@@ -202,14 +203,14 @@ export class MockDataService {
     async populateAdministrators() {
         const passwordService = new PasswordService();
 
-        const user = new UserEntity();
+        const user = new User();
         user.passwordHash = await passwordService.hash('admin');
         user.identifier = 'admin';
         user.roles = [Role.Superadmin];
 
         await this.connection.manager.save(user);
 
-        const administrator = new AdministratorEntity();
+        const administrator = new Administrator();
         administrator.emailAddress = 'admin@test.com';
         administrator.firstName = 'Super';
         administrator.lastName = 'Admin';
@@ -219,12 +220,12 @@ export class MockDataService {
     }
 
     private makeProductTranslation(
-        langCode: string,
+        langCode: LanguageCode,
         name: string,
         slug: string,
         description: string,
-    ): ProductTranslationEntity {
-        const productTranslation = new ProductTranslationEntity();
+    ): ProductTranslation {
+        const productTranslation = new ProductTranslation();
         productTranslation.languageCode = langCode;
         productTranslation.name = `${langCode} ${name}`;
         productTranslation.slug = `${langCode} ${slug}`;
@@ -232,7 +233,7 @@ export class MockDataService {
         return productTranslation;
     }
 
-    private makeProductVariantTranslation(langCode: string, name: string): ProductVariantTranslationEntity {
+    private makeProductVariantTranslation(langCode: LanguageCode, name: string): ProductVariantTranslationEntity {
         const productVariantTranslation = new ProductVariantTranslationEntity();
         productVariantTranslation.languageCode = langCode;
         productVariantTranslation.name = `${langCode} ${name}`;