Просмотр исходного кода

feat(server): Share dev config

Michael Bromley 7 лет назад
Родитель
Сommit
faa051784e

+ 21 - 0
server/dev-config.ts

@@ -0,0 +1,21 @@
+import { VendureConfig } from './src/config/vendure-config';
+
+/**
+ * Config settings used during development
+ */
+export const devConfig: VendureConfig = {
+    port: 3000,
+    apiPath: 'api',
+    cors: true,
+    jwtSecret: 'some-secret',
+    dbConnectionOptions: {
+        type: 'mysql',
+        synchronize: true,
+        logging: true,
+        host: '192.168.99.100',
+        port: 3306,
+        username: 'root',
+        password: '',
+        database: 'vendure-dev',
+    },
+};

+ 2 - 18
server/index-dev.ts

@@ -1,26 +1,10 @@
+import { devConfig } from './dev-config';
 import { bootstrap } from './src';
-import { IntegerIdStrategy, StringIdStrategy } from './src/config/entity-id-strategy';
 
 /**
  * This bootstraps the dev server, used for testing Vendure during development.
  */
-bootstrap({
-    port: 3000,
-    cors: true,
-    jwtSecret: 'some-secret',
-    dbConnectionOptions: {
-        type: 'mysql',
-        entities: ['./src/**/entity/**/*.entity.ts'],
-        synchronize: true,
-        logging: true,
-        host: '192.168.99.100',
-        port: 3306,
-        username: 'root',
-        password: '',
-        database: 'test',
-    },
-    // entityIdStrategy: new MadBastardIdStrategy(),
-}).catch(err => {
+bootstrap(devConfig).catch(err => {
     // tslint:disable-next-line
     console.log(err);
 });

+ 12 - 0
server/mock-data/clear-all-tables.ts

@@ -0,0 +1,12 @@
+import { ConnectionOptions, createConnection } from 'typeorm';
+
+// tslint:disable:no-console
+/**
+ * A Class used for generating mock data directly into the database via TypeORM.
+ */
+export async function clearAllTables(connectionOptions: ConnectionOptions) {
+    (connectionOptions as any).entities = [__dirname + '/../src/**/*.entity.ts'];
+    const connection = await createConnection(connectionOptions);
+    await connection.synchronize(true);
+    console.log('Cleared all tables');
+}

+ 13 - 6
server/mock-data/mock-data-client.service.ts

@@ -1,6 +1,7 @@
 import * as faker from 'faker/locale/en_GB';
 import { request } from 'graphql-request';
 import { PasswordService } from '../src/auth/password.service';
+import { VendureConfig } from '../src/config/vendure-config';
 import { CreateAddressDto } from '../src/entity/address/address.dto';
 import { CreateAdministratorDto } from '../src/entity/administrator/administrator.dto';
 import { CreateCustomerDto } from '../src/entity/customer/customer.dto';
@@ -17,6 +18,12 @@ import { TranslationInput } from '../src/locale/locale-types';
  * A service for creating mock data via the GraphQL API.
  */
 export class MockDataClientService {
+    apiUrl: string;
+
+    constructor(config: VendureConfig) {
+        this.apiUrl = `http://localhost:${config.port}/${config.apiPath}`;
+    }
+
     async populateOptions(): Promise<any> {
         const query = `mutation($input: CreateProductOptionGroupInput) {
                             createProductOptionGroup(input: $input) { id }
@@ -39,7 +46,7 @@ export class MockDataClientService {
             } as CreateProductOptionGroupDto,
         };
 
-        await request('http://localhost:3000/graphql', query, variables).then(
+        await request(this.apiUrl, query, variables).then(
             data => console.log('Created Administrator:', data),
             err => console.log(err),
         );
@@ -61,7 +68,7 @@ export class MockDataClientService {
             } as CreateAdministratorDto,
         };
 
-        await request('http://localhost:3000/graphql', query, variables).then(
+        await request(this.apiUrl, query, variables).then(
             data => console.log('Created Administrator:', data),
             err => console.log(err),
         );
@@ -88,7 +95,7 @@ export class MockDataClientService {
                 password: 'test',
             };
 
-            const customer: Customer | void = await request('http://localhost:3000/graphql', query1, variables1).then(
+            const customer: Customer | void = await request(this.apiUrl, query1, variables1).then(
                 (data: any) => {
                     console.log('Created Customer:', data);
                     return data.createCustomer as Customer;
@@ -97,7 +104,7 @@ export class MockDataClientService {
             );
 
             if (customer) {
-                const query2 = `mutation($customerId: String!, $input: CreateAddressInput) {
+                const query2 = `mutation($customerId: ID!, $input: CreateAddressInput) {
                                 createCustomerAddress(customerId: $customerId, input: $input) {
                                     id
                                     streetLine1
@@ -116,7 +123,7 @@ export class MockDataClientService {
                     customerId: customer.id,
                 };
 
-                await request('http://localhost:3000/graphql', query2, variables2).then(
+                await request(this.apiUrl, query2, variables2).then(
                     data => {
                         console.log('Created Customer:', data);
                         return data as Customer;
@@ -150,7 +157,7 @@ export class MockDataClientService {
                 } as CreateProductDto,
             };
 
-            await request('http://localhost:3000/graphql', query, variables).then(
+            await request(this.apiUrl, query, variables).then(
                 data => console.log('Created Product:', data),
                 err => console.log(err),
             );

+ 0 - 242
server/mock-data/mock-data.service.ts

@@ -1,242 +0,0 @@
-import * as faker from 'faker/locale/en_GB';
-import { Connection, createConnection } from 'typeorm';
-import { PasswordService } from '../src/auth/password.service';
-import { Role } from '../src/auth/role';
-import { Address } from '../src/entity/address/address.entity';
-import { Administrator } from '../src/entity/administrator/administrator.entity';
-import { Customer } from '../src/entity/customer/customer.entity';
-import { ProductOptionGroupTranslation } from '../src/entity/product-option-group/product-option-group-translation.entity';
-import { ProductOptionGroup } from '../src/entity/product-option-group/product-option-group.entity';
-import { ProductOptionTranslation } from '../src/entity/product-option/product-option-translation.entity';
-import { ProductOption } from '../src/entity/product-option/product-option.entity';
-import { ProductVariantTranslation } from '../src/entity/product-variant/product-variant-translation.entity';
-import { ProductVariant } from '../src/entity/product-variant/product-variant.entity';
-import { ProductTranslation } from '../src/entity/product/product-translation.entity';
-import { Product } from '../src/entity/product/product.entity';
-import { User } from '../src/entity/user/user.entity';
-import { LanguageCode } from '../src/locale/language-code';
-
-// tslint:disable:no-console
-/**
- * A Class used for generating mock data directly into the database via TypeORM.
- */
-export class MockDataService {
-    connection: Connection;
-
-    async populate(): Promise<any> {
-        this.connection = await this.connect();
-        await this.clearAllTables();
-        await this.populateCustomersAndAddresses();
-        await this.populateAdministrators();
-
-        const sizeOptionGroup = await this.populateOptions();
-        await this.populateProducts(sizeOptionGroup);
-    }
-
-    async connect(): Promise<Connection> {
-        this.connection = await createConnection({
-            type: 'mysql',
-            entities: ['./**/entity/**/*.entity.ts'],
-            synchronize: true,
-            logging: false,
-            host: '192.168.99.100',
-            port: 3306,
-            username: 'root',
-            password: '',
-            database: 'test',
-        });
-
-        return this.connection;
-    }
-
-    async clearAllTables() {
-        await this.connection.synchronize(true);
-        console.log('Cleared all tables');
-    }
-
-    async populateOptions(): Promise<ProductOptionGroup> {
-        const sizeGroup = new ProductOptionGroup();
-        sizeGroup.code = 'size';
-
-        const sizeGroupEN = new ProductOptionGroupTranslation();
-        sizeGroupEN.languageCode = LanguageCode.EN;
-        sizeGroupEN.name = 'Size';
-        await this.connection.manager.save(sizeGroupEN);
-        const sizeGroupDE = new ProductOptionGroupTranslation();
-
-        sizeGroupDE.languageCode = LanguageCode.DE;
-        sizeGroupDE.name = 'Größe';
-        await this.connection.manager.save(sizeGroupDE);
-
-        sizeGroup.translations = [sizeGroupEN, sizeGroupDE];
-        await this.connection.manager.save(sizeGroup);
-
-        await this.populateSizeOptions(sizeGroup);
-
-        console.log('created size options');
-        return sizeGroup;
-    }
-
-    private async populateSizeOptions(sizeGroup: ProductOptionGroup) {
-        const sizeSmall = new ProductOption();
-        sizeSmall.code = 'small';
-
-        const sizeSmallEN = new ProductOptionTranslation();
-        sizeSmallEN.languageCode = LanguageCode.EN;
-        sizeSmallEN.name = 'Small';
-        await this.connection.manager.save(sizeSmallEN);
-
-        const sizeSmallDE = new ProductOptionTranslation();
-        sizeSmallDE.languageCode = LanguageCode.DE;
-        sizeSmallDE.name = 'Klein';
-        await this.connection.manager.save(sizeSmallDE);
-
-        sizeSmall.translations = [sizeSmallEN, sizeSmallDE];
-        sizeSmall.group = sizeGroup;
-        await this.connection.manager.save(sizeSmall);
-
-        const sizeLarge = new ProductOption();
-        sizeLarge.code = 'large';
-
-        const sizeLargeEN = new ProductOptionTranslation();
-        sizeLargeEN.languageCode = LanguageCode.EN;
-        sizeLargeEN.name = 'Large';
-        await this.connection.manager.save(sizeLargeEN);
-
-        const sizeLargeDE = new ProductOptionTranslation();
-        sizeLargeDE.languageCode = LanguageCode.DE;
-        sizeLargeDE.name = 'Groß';
-        await this.connection.manager.save(sizeLargeDE);
-
-        sizeLarge.translations = [sizeLargeEN, sizeLargeDE];
-        sizeLarge.group = sizeGroup;
-        await this.connection.manager.save(sizeLarge);
-
-        sizeGroup.options = [sizeSmall, sizeLarge];
-    }
-
-    async populateProducts(optionGroup: ProductOptionGroup) {
-        for (let i = 0; i < 5; i++) {
-            const addOption = i === 2 || i === 4;
-
-            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(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: ProductVariant[] = [];
-            for (let j = 0; j < variantCount; j++) {
-                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(LanguageCode.EN, variantName);
-                const variantTranslation2 = this.makeProductVariantTranslation(LanguageCode.DE, variantName);
-                await this.connection.manager.save(variantTranslation1);
-                await this.connection.manager.save(variantTranslation2);
-
-                if (addOption) {
-                    variant.options = [optionGroup.options[0]];
-                } else {
-                    variant.options = [];
-                }
-                variant.translations = [variantTranslation1, variantTranslation2];
-                await this.connection.manager.save(variant);
-                console.log(`${j + 1}. created product variant ${variantName}`);
-                variants.push(variant);
-            }
-
-            if (addOption) {
-                product.optionGroups = [optionGroup];
-            }
-            product.variants = variants;
-            product.translations = [translation1, translation2];
-            await this.connection.manager.save(product);
-            console.log(`${i + 1}. created product & translations for ${translation1.name}`);
-        }
-    }
-
-    async populateCustomersAndAddresses() {
-        const passwordService = new PasswordService();
-
-        for (let i = 0; i < 5; i++) {
-            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 User();
-            user.passwordHash = await passwordService.hash('test');
-            user.identifier = customer.emailAddress;
-            user.roles = [Role.Customer];
-
-            await this.connection.manager.save(user);
-
-            const address = new Address();
-            address.fullName = `${customer.firstName} ${customer.lastName}`;
-            address.streetLine1 = faker.address.streetAddress();
-            address.city = faker.address.city();
-            address.province = faker.address.county();
-            address.postalCode = faker.address.zipCode();
-            address.country = faker.address.countryCode();
-
-            await this.connection.manager.save(address);
-
-            customer.addresses = [address];
-            customer.user = user;
-            await this.connection.manager.save(customer);
-            console.log('created customer, user and address for ' + customer.firstName + ' ' + customer.lastName);
-        }
-    }
-
-    async populateAdministrators() {
-        const passwordService = new PasswordService();
-
-        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 Administrator();
-        administrator.emailAddress = 'admin@test.com';
-        administrator.firstName = 'Super';
-        administrator.lastName = 'Admin';
-        administrator.user = user;
-
-        await this.connection.manager.save(administrator);
-    }
-
-    private makeProductTranslation(
-        langCode: LanguageCode,
-        name: string,
-        slug: string,
-        description: string,
-    ): ProductTranslation {
-        const productTranslation = new ProductTranslation();
-        productTranslation.languageCode = langCode;
-        productTranslation.name = `${langCode} ${name}`;
-        productTranslation.slug = `${langCode} ${slug}`;
-        productTranslation.description = `${langCode} ${description}`;
-        return productTranslation;
-    }
-
-    private makeProductVariantTranslation(langCode: LanguageCode, name: string): ProductVariantTranslation {
-        const productVariantTranslation = new ProductVariantTranslation();
-        productVariantTranslation.languageCode = langCode;
-        productVariantTranslation.name = `${langCode} ${name}`;
-        return productVariantTranslation;
-    }
-}

+ 4 - 5
server/mock-data/populate.ts

@@ -1,12 +1,11 @@
+import { devConfig } from '../dev-config';
+import { clearAllTables } from './clear-all-tables';
 import { MockDataClientService } from './mock-data-client.service';
-import { MockDataService } from './mock-data.service';
 
 async function populate() {
-    const mockDataService = new MockDataService();
-    const mockDataClientService = new MockDataClientService();
+    await clearAllTables(devConfig.dbConnectionOptions);
 
-    await mockDataService.connect();
-    await mockDataService.clearAllTables();
+    const mockDataClientService = new MockDataClientService(devConfig);
     await mockDataClientService.populateOptions();
     await mockDataClientService.populateProducts(200);
     await mockDataClientService.populateCustomers(100);

+ 6 - 0
server/src/common/common-types.ts

@@ -3,6 +3,12 @@
  */
 export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
 
+/**
+ * Creates a type based on T, but with all properties non-optional
+ * and readonly.
+ */
+export type ReadOnlyRequired<T> = { +readonly [K in keyof T]-?: T[K] };
+
 // tslint:disable:ban-types
 /**
  * A type representing the type rather than instance of a class.

+ 1 - 1
server/src/config/merge-config.ts

@@ -18,7 +18,7 @@ function isClassInstance(item: any): boolean {
  * but modified so that it does not overwrite fields of class instances, rather it overwrites
  * the entire instance.
  */
-export function mergeConfig(target: VendureConfig, source: DeepPartial<VendureConfig>) {
+export function mergeConfig<T extends VendureConfig>(target: T, source: DeepPartial<VendureConfig>): T {
     if (!source) {
         return target;
     }

+ 7 - 7
server/src/config/vendure-config.ts

@@ -1,6 +1,6 @@
 import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
 import { ConnectionOptions } from 'typeorm';
-import { DeepPartial } from '../common/common-types';
+import { DeepPartial, ReadOnlyRequired } from '../common/common-types';
 import { LanguageCode } from '../locale/language-code';
 import { AutoIncrementIdStrategy } from './auto-increment-id-strategy';
 import { EntityIdStrategy } from './entity-id-strategy';
@@ -10,15 +10,15 @@ export interface VendureConfig {
     /**
      * The default languageCode of the app.
      */
-    defaultLanguageCode: LanguageCode;
+    defaultLanguageCode?: LanguageCode;
     /**
      * The path to the GraphQL API.
      */
-    apiPath: string;
+    apiPath?: string;
     /**
      * Set the CORS handling for the server.
      */
-    cors: boolean | CorsOptions;
+    cors?: boolean | CorsOptions;
     /**
      * Which port the Vendure server should listen on.
      */
@@ -37,14 +37,14 @@ export interface VendureConfig {
      * entities via the API. The default uses a simple auto-increment integer
      * strategy.
      */
-    entityIdStrategy: EntityIdStrategy<any>;
+    entityIdStrategy?: EntityIdStrategy<any>;
     /**
      * The connection options used by TypeORM to connect to the database.
      */
     dbConnectionOptions: ConnectionOptions;
 }
 
-const defaultConfig: VendureConfig = {
+const defaultConfig: ReadOnlyRequired<VendureConfig> = {
     defaultLanguageCode: LanguageCode.EN,
     port: 3000,
     cors: false,
@@ -71,6 +71,6 @@ export function setConfig(userConfig: DeepPartial<VendureConfig>): void {
  * used before bootstrapping the app. In all other contexts, the {@link ConfigService}
  * should be used to access config settings.
  */
-export function getConfig(): VendureConfig {
+export function getConfig(): ReadOnlyRequired<VendureConfig> {
     return activeConfig;
 }

+ 2 - 1
server/src/service/config.service.ts

@@ -1,6 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
 import { ConnectionOptions } from 'typeorm';
+import { ReadOnlyRequired } from '../common/common-types';
 import { EntityIdStrategy } from '../config/entity-id-strategy';
 import { getConfig, VendureConfig } from '../config/vendure-config';
 import { LanguageCode } from '../locale/language-code';
@@ -35,7 +36,7 @@ export class ConfigService implements VendureConfig {
         return this.activeConfig.dbConnectionOptions;
     }
 
-    private activeConfig: VendureConfig;
+    private activeConfig: ReadOnlyRequired<VendureConfig>;
 
     constructor() {
         this.activeConfig = getConfig();