diff --git a/README.md b/README.md index 3537b8c7..c034a5bb 100644 --- a/README.md +++ b/README.md @@ -255,15 +255,18 @@ export class UserService { ## Seeding -Isn't exhausting to create some sample data into your fresh migrated database, well this time is over! -How does it work? Just, create a factory for your entities and a seeds script. +Isn't it exhausting to create some sample data for your database, well this time is over! + +How does it work? Just create a factory for your entities (models) and a seed script. ### 1. Create a factory for your entity -For all the entities we want to seed, we need to define a factory. To do so we give you the awesome [faker](https://github.com/marak/Faker.js/) library as a parameter into your factory. Then create your "fake" entity as you would normally do and return it. Those factory files should be in the `src/database/factories` folder and suffixed with `Factory`. Example `src/database/factories/UserFactory.ts`. +For all entities we want to seed, we need to define a factory. To do so we give you the awesome [faker](https://github.com/marak/Faker.js/) library as a parameter into your factory. Then create your "fake" entity and return it. Those factory files should be in the `src/database/factories` folder and suffixed with `Factory` like `src/database/factories/UserFactory.ts`. + +Settings can be used to pass some static value into the factory. ```typescript -factory.define(User, (faker: typeof Faker) => { +define(User, (faker: typeof Faker, settings: { roles: string[] }) => { const gender = faker.random.number(1); const firstName = faker.name.firstName(gender); const lastName = faker.name.lastName(gender); @@ -273,94 +276,76 @@ factory.define(User, (faker: typeof Faker) => { user.firstName = firstName; user.lastName = lastName; user.email = email; + user.roles = settings.roles; return user; }); ``` -This can be used to pass some dynamic value into the factory. +Handle relation in the entity factory like this. ```typescript -factory.define(Pet, (faker: typeof Faker, args: any[]) => { - const type = args[0]; - return { - name: faker.name.firstName(), - type: type || 'dog' - }; -}); -``` - -To deal with relations you can use the entity manager like this. - -```typescript -import { SeedsInterface, FactoryInterface, times } from '../../lib/seeds'; -import { Pet } from '../../../src/api/models/Pet'; -import { User } from '../../../src/api/models/User'; - -export class CreatePets implements SeedsInterface { - - public async seed(factory: FactoryInterface): Promise { - const connection = await factory.getConnection(); - const em = connection.createEntityManager(); - - await times(10, async (n) => { - // This creates a pet in the database - const pet = await factory.get(Pet).create(); - // This only returns a entity with fake data - const user = await factory.get(User).make(); - user.pets = [pet]; - await em.save(user); - }); - } +define(Pet, (faker: typeof Faker, settings: undefined) => { + const gender = faker.random.number(1); + const name = faker.name.firstName(gender); -} + const pet = new Pet(); + pet.name = name; + pet.age = faker.random.number(); + pet.user = factory(User)({ roles: ['admin'] }) + return pet; +}); ``` ### 2. Create a seed file The seeds files define how much and how the data are connected with each other. The files will be executed alphabetically. +With the second function, accepting your settings defined in the factories, you are able to create different variations of entities. ```typescript -export class CreateUsers implements SeedsInterface { +export class CreateUsers implements Seed { - public async seed(factory: FactoryInterface): Promise { - await factory - .get(User) - .createMany(10); + public async seed(factory: Factory, connection: Connection): Promise { + await factory(User)({ roles: [] }).createMany(10); } } ``` -With the second parameter in the `.get(, )` you are able to create different variations of entities. +Here an example with nested factories. You can use the `.map()` function to alter +the generated value before they get persisted. ```typescript -export class CreateUsers implements SeedsInterface { - - public async seed(factory: FactoryInterface): Promise { - await factory - .get(User, 'admin') - .create(); - } - -} +... +await factory(User)() + .map(async (user: User) => { + const pets: Pet[] = await factory(Pet)().createMany(2); + const petIds = pets.map((pet: Pet) => pet.Id); + await user.pets().attach(petIds); + }) + .createMany(5); +... ``` -Here an example with nested factories. +To deal with relations you can use the entity manager like this. ```typescript -... -await factory.get(User) - .each(async (user: User) => { +export class CreatePets implements SeedsInterface { - const pets: Pet[] = await factory.get(Pet) - .createMany(2); + public async seed(factory: FactoryInterface, connection: Connection): Promise { + const connection = await factory.getConnection(); + const em = connection.createEntityManager(); - const petIds = pets.map((pet: Pet) => pet.Id); - await user.pets().attach(petIds); + await times(10, async (n) => { + // This creates a pet in the database + const pet = await factory(Pet)().create(); + // This only returns a entity with fake data + const user = await factory(User)({ roles: ['admin'] }).make(); + user.pets = [pet]; + await em.save(user); + }); + } - }) - .create(5); -... +} ``` ### 3. Run the seeder @@ -371,6 +356,17 @@ The last step is the easiest, just hit the following command in your terminal, b npm start db.seed ``` +#### CLI Interface + +| Command | Description | +| --------------------------------------------------- | ----------- | +| `npm start "db.seed"` | Run all seeds | +| `npm start "db.seed --run CreateBruce,CreatePets"` | Run specific seeds (file names without extension) | +| `npm start "db.seed -L"` | Log database queries to the terminal | +| `npm start "db.seed --factories "` | Add a different path to your factories (Default: `src/database/`) | +| `npm start "db.seed --seeds "` | Add a different path to your seeds (Default: `src/database/seeds/`) | +| `npm start "db.seed --config "` | Path to your ormconfig.json file | + ## Run in Docker container ### Install Docker diff --git a/package-scripts.js b/package-scripts.js index 46872f86..2e36080d 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -140,7 +140,7 @@ module.exports = { script: series( 'nps banner.seed', 'nps config', - runFast('./src/lib/seeds/cli.ts') + runFast('./src/lib/seed/cli.ts') ), description: 'Seeds generated records into the database' }, diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index cc3181b2..a1aa3e9d 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -18,7 +18,7 @@ export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { res.json({ name: error.name, message: error.message, - errors: error['errors'] || [], + errors: error[`errors`] || [], }); if (this.isProduction) { diff --git a/src/database/factories/PetFactory.ts b/src/database/factories/PetFactory.ts index 7679d099..b917e050 100644 --- a/src/database/factories/PetFactory.ts +++ b/src/database/factories/PetFactory.ts @@ -1,11 +1,9 @@ import * as Faker from 'faker'; import { Pet } from '../../../src/api/models/Pet'; -import { Factory } from '../../lib/seeds'; +import { define } from '../../lib/seed'; -const factory = Factory.getInstance(); - -factory.define(Pet, (faker: typeof Faker) => { +define(Pet, (faker: typeof Faker) => { const gender = faker.random.number(1); const name = faker.name.firstName(gender); diff --git a/src/database/factories/UserFactory.ts b/src/database/factories/UserFactory.ts index bdd8aa42..810ae7a5 100644 --- a/src/database/factories/UserFactory.ts +++ b/src/database/factories/UserFactory.ts @@ -1,11 +1,9 @@ import * as Faker from 'faker'; import { User } from '../../../src/api/models/User'; -import { Factory } from '../../lib/seeds'; +import { define } from '../../lib/seed'; -const factory = Factory.getInstance(); - -factory.define(User, (faker: typeof Faker) => { +define(User, (faker: typeof Faker, settings: { role: string }) => { const gender = faker.random.number(1); const firstName = faker.name.firstName(gender); const lastName = faker.name.lastName(gender); diff --git a/src/database/seeds/CreateBruce.ts b/src/database/seeds/CreateBruce.ts index 5a42afa5..0d88108d 100644 --- a/src/database/seeds/CreateBruce.ts +++ b/src/database/seeds/CreateBruce.ts @@ -1,10 +1,31 @@ +import { Connection } from 'typeorm'; + import { User } from '../../../src/api/models/User'; -import { FactoryInterface, SeedsInterface } from '../../lib/seeds'; +import { Factory, Seed } from '../../lib/seed/types'; + +export class CreateBruce implements Seed { + + public async seed(factory: Factory, connection: Connection): Promise { + // const userFactory = factory(User as any); + // const adminUserFactory = userFactory({ role: 'admin' }); + + // const bruce = await adminUserFactory.make(); + // console.log(bruce); + + // const bruce2 = await adminUserFactory.seed(); + // console.log(bruce2); + + // const bruce3 = await adminUserFactory + // .map(async (e: User) => { + // e.firstName = 'Bruce'; + // return e; + // }) + // .seed(); + // console.log(bruce3); -export class CreateBruce implements SeedsInterface { + // return bruce; - public async seed(factory: FactoryInterface): Promise { - const connection = await factory.getConnection(); + // const connection = await factory.getConnection(); const em = connection.createEntityManager(); const user = new User(); diff --git a/src/database/seeds/CreatePets.ts b/src/database/seeds/CreatePets.ts index 69071654..76ef89fc 100644 --- a/src/database/seeds/CreatePets.ts +++ b/src/database/seeds/CreatePets.ts @@ -1,16 +1,17 @@ +import { Connection } from 'typeorm'; + import { Pet } from '../../../src/api/models/Pet'; import { User } from '../../../src/api/models/User'; -import { FactoryInterface, SeedsInterface, times } from '../../lib/seeds'; +import { Factory, Seed, times } from '../../lib/seed'; -export class CreatePets implements SeedsInterface { +export class CreatePets implements Seed { - public async seed(factory: FactoryInterface): Promise { - const connection = await factory.getConnection(); + public async seed(factory: Factory, connection: Connection): Promise { const em = connection.createEntityManager(); await times(10, async (n) => { - const pet = await factory.get(Pet).create(); - const user = await factory.get(User).make(); + const pet = await factory(Pet)().seed(); + const user = await factory(User)().make(); user.pets = [pet]; await em.save(user); }); diff --git a/src/database/seeds/CreateUsers.ts b/src/database/seeds/CreateUsers.ts index 0d292e95..6f6f5871 100644 --- a/src/database/seeds/CreateUsers.ts +++ b/src/database/seeds/CreateUsers.ts @@ -1,12 +1,12 @@ +import { Connection } from 'typeorm/connection/Connection'; + import { User } from '../../../src/api/models/User'; -import { FactoryInterface, SeedsInterface } from '../../lib/seeds'; +import { Factory, Seed } from '../../lib/seed/types'; -export class CreateUsers implements SeedsInterface { +export class CreateUsers implements Seed { - public async seed(factory: FactoryInterface): Promise { - await factory - .get(User) - .createMany(10); + public async seed(factory: Factory, connection: Connection): Promise { + await factory(User)().seedMany(10); } } diff --git a/src/lib/seed/EntityFactory.ts b/src/lib/seed/EntityFactory.ts new file mode 100644 index 00000000..b9931878 --- /dev/null +++ b/src/lib/seed/EntityFactory.ts @@ -0,0 +1,103 @@ +import * as Faker from 'faker'; +import { Connection, ObjectType } from 'typeorm'; + +import { FactoryFunction } from './types'; +import { isPromiseLike } from './utils'; + +export class EntityFactory { + + private mapFunction: (entity: Entity) => Promise; + + constructor( + public name: string, + public entity: ObjectType, + private factory: FactoryFunction, + private settings?: Settings + ) { } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + + /** + * This function is used to alter the generated values of entity, before it + * is persist into the database + */ + public map(mapFunction: (entity: Entity) => Promise): EntityFactory { + this.mapFunction = mapFunction; + return this; + } + + /** + * Make a new entity, but does not persist it + */ + public async make(): Promise { + if (this.factory) { + let entity = await this.resolveEntity(this.factory(Faker, this.settings)); + if (this.mapFunction) { + entity = await this.mapFunction(entity); + } + return entity; + } + throw new Error('Could not found entity'); + } + + /** + * Seed makes a new entity and does persist it + */ + public async seed(): Promise { + const connection: Connection = (global as any).seeder.connection; + if (connection) { + const em = connection.createEntityManager(); + try { + const entity = await this.make(); + return await em.save(entity); + } catch (error) { + throw new Error('Could not save entity'); + } + } else { + throw new Error('No db connection is given'); + } + } + + public async makeMany(amount: number): Promise { + const list = []; + for (let index = 0; index < amount; index++) { + list[index] = await this.make(); + } + return list; + } + + public async seedMany(amount: number): Promise { + const list = []; + for (let index = 0; index < amount; index++) { + list[index] = await this.seed(); + } + return list; + } + + // ------------------------------------------------------------------------- + // Prrivat Helpers + // ------------------------------------------------------------------------- + + private async resolveEntity(entity: Entity): Promise { + for (const attribute in entity) { + if (entity.hasOwnProperty(attribute)) { + if (isPromiseLike(entity[attribute])) { + entity[attribute] = await entity[attribute]; + } + + if (typeof entity[attribute] === 'object') { + const subEntityFactory = entity[attribute]; + try { + entity[attribute] = await (subEntityFactory as any).make(); + } catch (e) { + throw new Error(`Could not make ${(subEntityFactory as any).name}`); + } + } + } + } + return entity; + } + +} diff --git a/src/lib/seed/cli.ts b/src/lib/seed/cli.ts new file mode 100644 index 00000000..20b52fae --- /dev/null +++ b/src/lib/seed/cli.ts @@ -0,0 +1,93 @@ +import * as Chalk from 'chalk'; +import * as commander from 'commander'; +import * as path from 'path'; + +import { loadEntityFactories } from './'; +import { getConnection } from './connection'; +import { loadSeeds } from './importer'; +import { runSeed, setConnection } from './index'; + +// Cli helper +commander + .version('1.0.0') + .description('Run database seeds of your project') + .option('-L, --logging', 'enable sql query logging') + .option('--factories ', 'add filepath for your factories') + .option('--seeds ', 'add filepath for your seeds') + .option('--run ', 'run specific seeds (file names without extension)', (val) => val.split(',')) + .option('--config ', 'path to your ormconfig.json file (must be a json)') + .parse(process.argv); + +// Get cli parameter for a different factory path +const factoryPath = (commander.factories) + ? commander.factories + : 'src/database/'; + +// Get cli parameter for a different seeds path +const seedsPath = (commander.seeds) + ? commander.seeds + : 'src/database/seeds/'; + +// Get a list of seeds +const listOfSeeds = (commander.run) + ? commander.run.map(l => l.trim()).filter(l => l.length > 0) + : []; + +// Search for seeds and factories +const run = async () => { + const log = console.log; + const chalk = Chalk.default; + + let factoryFiles; + let seedFiles; + try { + factoryFiles = await loadEntityFactories(factoryPath); + seedFiles = await loadSeeds(seedsPath); + } catch (error) { + return handleError(error); + } + + // Filter seeds + if (listOfSeeds.length > 0) { + seedFiles = seedFiles.filter(sf => listOfSeeds.indexOf(path.basename(sf).replace('.ts', '')) >= 0); + } + + // Status logging to print out the amount of factories and seeds. + log(chalk.bold('seeds')); + log('šŸ”Ž ', chalk.gray.underline(`found:`), + chalk.blue.bold(`${factoryFiles.length} factories`, chalk.gray('&'), chalk.blue.bold(`${seedFiles.length} seeds`))); + + // Get database connection and pass it to the seeder + let connection; + try { + connection = await getConnection(); + setConnection(connection); + } catch (error) { + return handleError(error); + } + + // Show seeds in the console + for (const seedFile of seedFiles) { + try { + let className = seedFile.split('/')[seedFile.split('/').length - 1]; + className = className.replace('.ts', '').replace('.js', ''); + className = className.split('-')[className.split('-').length - 1]; + log('\n' + chalk.gray.underline(`executing seed: `), chalk.green.bold(`${className}`)); + const seedFileObject: any = require(seedFile); + await runSeed(seedFileObject[className]); + } catch (error) { + console.error('Could not run seed ', error); + process.exit(1); + } + } + + log('\nšŸ‘ ', chalk.gray.underline(`finished seeding`)); + process.exit(0); +}; + +const handleError = (error) => { + console.error(error); + process.exit(1); +}; + +run(); diff --git a/src/lib/seeds/connection.ts b/src/lib/seed/connection.ts similarity index 65% rename from src/lib/seeds/connection.ts rename to src/lib/seed/connection.ts index 1d6f031e..5d7481c1 100644 --- a/src/lib/seeds/connection.ts +++ b/src/lib/seed/connection.ts @@ -15,19 +15,10 @@ const indexOfConfigPath = args.indexOf(configParam) + 1; * Returns a TypeORM database connection for our entity-manager */ export const getConnection = async (): Promise => { - const ormconfig = (hasConfigPath) ? require(`${args[indexOfConfigPath]}`) : require(`${runDir}/ormconfig.json`); + ormconfig.logging = logging; - return await createConnection({ - type: (ormconfig as any).type as any, - host: (ormconfig as any).host, - port: (ormconfig as any).port, - username: (ormconfig as any).username, - password: (ormconfig as any).password, - database: (ormconfig as any).database, - entities: (ormconfig as any).entities, - logging, - }); + return createConnection(ormconfig); }; diff --git a/src/lib/seed/importer.ts b/src/lib/seed/importer.ts new file mode 100644 index 00000000..8d188785 --- /dev/null +++ b/src/lib/seed/importer.ts @@ -0,0 +1,39 @@ +import * as glob from 'glob'; +import * as path from 'path'; + +// ------------------------------------------------------------------------- +// Util functions +// ------------------------------------------------------------------------- + +const importFactories = (files: string[]) => files.forEach(require); + +const loadFiles = + (filePattern: string) => + (pathToFolder: string) => + (successFn: (files: string[]) => void) => + (failedFn: (error: any) => void) => { + glob(path.join(process.cwd(), pathToFolder, filePattern), (error: any, files: string[]) => error + ? failedFn(error) + : successFn(files)); + }; + +const loadFactoryFiles = loadFiles('**/*Factory{.js,.ts}'); + +// ------------------------------------------------------------------------- +// Facade functions +// ------------------------------------------------------------------------- + +export const loadEntityFactories = (pathToFolder: string): Promise => { + return new Promise((resolve, reject) => { + loadFactoryFiles(pathToFolder)(files => { + importFactories(files); + resolve(files); + })(reject); + }); +}; + +export const loadSeeds = (pathToFolder: string): Promise => { + return new Promise((resolve, reject) => { + loadFiles('**/*{.js,.ts}')(pathToFolder)(resolve)(reject); + }); +}; diff --git a/src/lib/seed/index.ts b/src/lib/seed/index.ts new file mode 100644 index 00000000..e3ef9049 --- /dev/null +++ b/src/lib/seed/index.ts @@ -0,0 +1,66 @@ +import 'reflect-metadata'; +import { Connection, ObjectType } from 'typeorm'; + +import { EntityFactory } from './EntityFactory'; +import { EntityFactoryDefinition, Factory, FactoryFunction, SeedConstructor } from './types'; +import { getNameOfClass } from './utils'; + +// ------------------------------------------------------------------------- +// Handy Exports +// ------------------------------------------------------------------------- + +export * from './importer'; +export { Factory, Seed } from './types'; +export { times } from './utils'; + +// ------------------------------------------------------------------------- +// Types & Variables +// ------------------------------------------------------------------------- + +(global as any).seeder = { + connection: undefined, + entityFactories: new Map>(), +}; + +// ------------------------------------------------------------------------- +// Facade functions +// ------------------------------------------------------------------------- + +/** + * Adds the typorm connection to the seed options + */ +export const setConnection = (connection: Connection) => (global as any).seeder.connection = connection; + +/** + * Returns the typorm connection from our seed options + */ +export const getConnection = () => (global as any).seeder.connection; + +/** + * Defines a new entity factory + */ +export const define = (entity: ObjectType, factoryFn: FactoryFunction) => { + (global as any).seeder.entityFactories.set(getNameOfClass(entity), { entity, factory: factoryFn }); +}; + +/** + * Gets a defined entity factory and pass the settigns along to the entity factory function + */ +export const factory: Factory = (entity: ObjectType) => (settings?: Settings) => { + const name = getNameOfClass(entity); + const entityFactoryObject = (global as any).seeder.entityFactories.get(name); + return new EntityFactory( + name, + entity, + entityFactoryObject.factory, + settings + ); +}; + +/** + * Runs a seed class + */ +export const runSeed = async (seederConstructor: SeedConstructor): Promise => { + const seeder = new seederConstructor(); + return seeder.seed(factory, getConnection()); +}; diff --git a/src/lib/seed/types.ts b/src/lib/seed/types.ts new file mode 100644 index 00000000..b081ee09 --- /dev/null +++ b/src/lib/seed/types.ts @@ -0,0 +1,36 @@ +import * as Faker from 'faker'; +import { Connection, ObjectType } from 'typeorm'; + +import { EntityFactory } from './EntityFactory'; + +/** + * FactoryFunction is the fucntion, which generate a new filled entity + */ +export type FactoryFunction = (faker: typeof Faker, settings?: Settings) => Entity; + +/** + * Factory gets the EntityFactory to the given Entity and pass the settings along + */ +export type Factory = (entity: ObjectType) => (settings?: Settings) => EntityFactory; + +/** + * Seed are the class to create some data. Those seed are run by the cli. + */ +export interface Seed { + seed(factory: Factory, connection: Connection): Promise; +} + +/** + * Constructor of the seed class + */ +export interface SeedConstructor { + new(): Seed; +} + +/** + * Value of our EntityFactory state + */ +export interface EntityFactoryDefinition { + entity: ObjectType; + factory: FactoryFunction; +} diff --git a/src/lib/seed/utils.ts b/src/lib/seed/utils.ts new file mode 100644 index 00000000..98bcb7e4 --- /dev/null +++ b/src/lib/seed/utils.ts @@ -0,0 +1,15 @@ +/** + * Returns the name of a class + */ +export const getNameOfClass = (c: any): string => new c().constructor.name; + +/** + * Checks if the given argument is a promise + */ +export const isPromiseLike = (o: any): boolean => !!o && (typeof o === 'object' || typeof o === 'function') && typeof o.then === 'function'; + +/** + * Times repeats a function n times + */ +export const times = async (n: number, iteratee: (index: number) => Promise): Promise => + Promise.all(new Array(n).map(async (v, i) => await iteratee(i))); diff --git a/src/lib/seeds/BluePrint.ts b/src/lib/seeds/BluePrint.ts deleted file mode 100644 index 4fc496b5..00000000 --- a/src/lib/seeds/BluePrint.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as Faker from 'faker'; -import { ObjectType } from 'typeorm'; - -/** - * BluePrint has the factory function for the given EntityClass - */ -export class BluePrint { - constructor( - public EntityClass: ObjectType, - public create: (faker: typeof Faker, args: any[]) => Entity) { } -} diff --git a/src/lib/seeds/EntityFactory.ts b/src/lib/seeds/EntityFactory.ts deleted file mode 100644 index 66a852b2..00000000 --- a/src/lib/seeds/EntityFactory.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as Faker from 'faker'; -import { Connection } from 'typeorm/connection/Connection'; - -import { BluePrint } from './BluePrint'; -import { EntityFactoryInterface } from './EntityFactoryInterface'; - -export class EntityFactory implements EntityFactoryInterface { - - private identifier = 'id'; - private eachFn: (obj: any, faker: typeof Faker) => Promise; - - constructor( - private faker: typeof Faker, - private connection: Connection, - private blueprint: BluePrint, - private args: any[]) { } - - public each(iterator: (entity: Entity, faker: typeof Faker) => Promise): EntityFactory { - this.eachFn = iterator; - return this; - } - - public async make(): Promise { - return await this.makeEntity(this.blueprint.create(this.faker, this.args)); - } - - public async makeMany(amount: number): Promise { - const results: Entity[] = []; - for (let i = 0; i < amount; i++) { - const entity = await this.makeEntity(this.blueprint.create(this.faker, this.args)); - if (entity) { - results.push(entity); - } - } - return results; - } - - public async create(): Promise { - const entity = await this.build(); - if (typeof this.eachFn === 'function') { - await this.eachFn(entity, this.faker); - } - return entity; - } - - public async createMany(amount: number): Promise { - const results: Entity[] = []; - for (let i = 0; i < amount; i++) { - const entity = await this.create(); - if (entity) { - results.push(entity); - } - } - return results; - } - - private async build(): Promise { - if (this.connection) { - const entity = await this.make(); - const em = this.connection.createEntityManager(); - try { - return await em.save(this.blueprint.EntityClass, entity); - } catch (error) { - console.error('saving entity failed', error); - return; - } - } - return; - } - - private async makeEntity(entity: Entity): Promise { - for (const attribute in entity) { - if (entity.hasOwnProperty(attribute)) { - if (this.isPromiseLike(entity[attribute])) { - entity[attribute] = await entity[attribute]; - } - - if (typeof entity[attribute] === 'object' && entity[attribute] instanceof EntityFactory) { - const subEntityFactory = entity[attribute]; - const subEntity = await (subEntityFactory as any).build(); - entity[attribute] = subEntity[this.identifier]; - } - } - } - return entity; - } - - private isPromiseLike(object: any): boolean { - return !!object && (typeof object === 'object' || typeof object === 'function') && typeof object.then === 'function'; - } -} diff --git a/src/lib/seeds/EntityFactoryInterface.ts b/src/lib/seeds/EntityFactoryInterface.ts deleted file mode 100644 index 61d9954c..00000000 --- a/src/lib/seeds/EntityFactoryInterface.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as Faker from 'faker'; - -/** - * EntityFactoryInterface is the one we use in our seed files. - * This will be returne of the main factory's get method. - */ -export interface EntityFactoryInterface { - /** - * Creates a entity with faked data, but not persisted to the database. - */ - make(): Promise; - makeMany(amount: number): Promise; - /** - * Creates a new faked entity in the database. - */ - create(): Promise; - createMany(amount: number): Promise; - /** - * This is called after creating a enity to the database. Use this to - * create other seeds but combined with this enitiy. - */ - each(iterator: (entity: Entity, faker: typeof Faker) => Promise): EntityFactoryInterface; -} diff --git a/src/lib/seeds/Factory.ts b/src/lib/seeds/Factory.ts deleted file mode 100644 index 2e4d8144..00000000 --- a/src/lib/seeds/Factory.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as Faker from 'faker'; -import { ObjectType } from 'typeorm'; -import { Connection } from 'typeorm/connection/Connection'; - -import { BluePrint } from './BluePrint'; -import { EntityFactory } from './EntityFactory'; -import { FactoryInterface } from './FactoryInterface'; -import { SeedsConstructorInterface } from './SeedsInterface'; - -export class Factory implements FactoryInterface { - - public static getInstance(): Factory { - if (!Factory.instance) { - Factory.instance = new Factory(Faker); - } - return Factory.instance; - } - - private static instance: Factory; - - private connection: Connection; - private blueprints: { [key: string]: BluePrint }; - - constructor(private faker: typeof Faker) { - this.blueprints = {}; - } - - public getConnection(): Connection { - return this.connection; - } - - public setConnection(connection: Connection): void { - this.connection = connection; - } - - public async runSeed(seedClass: SeedsConstructorInterface): Promise { - const seeder = new seedClass(); - return await seeder.seed(this); - } - - public define(entityClass: ObjectType, callback: (faker: typeof Faker, args: any[]) => Entity): void { - this.blueprints[this.getNameOfEntity(entityClass)] = new BluePrint(entityClass, callback); - } - - public get(entityClass: ObjectType, ...args: any[]): EntityFactory { - return new EntityFactory( - this.faker, - this.connection, - this.blueprints[this.getNameOfEntity(entityClass)], - args - ); - } - - private getNameOfEntity(EntityClass: any): string { - return new EntityClass().constructor.name; - } - -} diff --git a/src/lib/seeds/FactoryInterface.ts b/src/lib/seeds/FactoryInterface.ts deleted file mode 100644 index 435677c1..00000000 --- a/src/lib/seeds/FactoryInterface.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as Faker from 'faker'; -import { SeedsConstructorInterface } from 'src/lib/seeds'; -import { ObjectType } from 'typeorm'; -import { Connection } from 'typeorm/connection/Connection'; - -import { EntityFactoryInterface } from './EntityFactoryInterface'; - -/** - * This interface is used to define new entity faker factories or to get such a - * entity faker factory to start seeding. - */ -export interface FactoryInterface { - /** - * Returns a typeorm database connection. - */ - getConnection(): Connection; - /** - * Sets the typeorm database connection. - */ - setConnection(connection: Connection): void; - /** - * Runs the given seed class - */ - runSeed(seedClass: SeedsConstructorInterface): Promise; - /** - * Returns an EntityFactoryInterface - */ - get(entityClass: ObjectType, value?: any): EntityFactoryInterface; - /** - * Define an entity faker - */ - define(entityClass: ObjectType, fakerFunction: (faker: typeof Faker, value?: any) => Entity): void; -} diff --git a/src/lib/seeds/SeedsInterface.ts b/src/lib/seeds/SeedsInterface.ts deleted file mode 100644 index b5efa458..00000000 --- a/src/lib/seeds/SeedsInterface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Factory } from './Factory'; - -/** - * Seeds should implement this interface and all its methods. - */ -export interface SeedsInterface { - /** - * Seed data into the databas. - */ - seed(factory: Factory): Promise; -} - -export interface SeedsConstructorInterface { - new(): SeedsInterface; -} diff --git a/src/lib/seeds/cli.ts b/src/lib/seeds/cli.ts deleted file mode 100644 index 44a71015..00000000 --- a/src/lib/seeds/cli.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as Chalk from 'chalk'; -import * as commander from 'commander'; -import * as glob from 'glob'; -import * as path from 'path'; -import 'reflect-metadata'; -import { Connection } from 'typeorm'; - -import { getConnection } from './connection'; -import { Factory } from './Factory'; - -// Get executiuon path to look from there for seeds and factories -const runDir = process.cwd(); - -// Cli helper -commander - .version('0.0.0') - .description('Run database seeds of your project') - .option('-L, --logging', 'enable sql query logging') - .option('--factories ', 'add filepath for your factories') - .option('--seeds ', 'add filepath for your seeds') - .option('--config ', 'add filepath to your database config (must be a json)') - .parse(process.argv); - -// Get cli parameter for a different factory path -const factoryPath = (commander.factories) - ? commander.factories - : 'src/database/'; - -// Get cli parameter for a different seeds path -const seedsPath = (commander.seeds) - ? commander.seeds - : 'src/database/seeds/'; - -// Search for seeds and factories -glob(path.join(runDir, factoryPath, '**/*Factory{.js,.ts}'), (errFactories: any, factories: string[]) => { - glob(path.join(runDir, seedsPath, '*{.js,.ts}'), (errSeeds: any, seeds: string[]) => { - const log = console.log; - const chalk = Chalk.default; - - // Status logging to print out the amount of factories and seeds. - log(chalk.bold('seeds')); - log('šŸ”Ž ', chalk.gray.underline(`found:`), - chalk.blue.bold(`${factories.length} factories`, chalk.gray('&'), chalk.blue.bold(`${seeds.length} seeds`))); - - // Initialize all factories - for (const factory of factories) { - require(factory); - } - - // Get typeorm database connection and pass them to the factory instance - getConnection().then((connection: Connection) => { - const factory = Factory.getInstance(); - factory.setConnection(connection); - - // Initialize and seed all seeds. - const queue: Array> = []; - for (const seed of seeds) { - try { - const seedFile: any = require(seed); - let className = seed.split('/')[seed.split('/').length - 1]; - className = className.replace('.ts', '').replace('.js', ''); - className = className.split('-')[className.split('-').length - 1]; - log('\n' + chalk.gray.underline(`executing seed: `), chalk.green.bold(`${className}`)); - queue.push((new seedFile[className]()).seed(factory)); - } catch (error) { - console.error('Could not run seed ' + seed, error); - } - } - - // Promise to catch the end for termination and logging - Promise - .all(queue) - .then(() => { - log('\nšŸ‘ ', chalk.gray.underline(`finished seeding`)); - process.exit(0); - }) - .catch((error) => { - console.error('Could not run seed ' + error); - process.exit(1); - }); - - }).catch((error) => { - console.error('Could not connection to database ' + error); - process.exit(1); - }); - }); -}); diff --git a/src/lib/seeds/index.ts b/src/lib/seeds/index.ts deleted file mode 100644 index 97fa23d1..00000000 --- a/src/lib/seeds/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import 'reflect-metadata'; -import { Connection } from 'typeorm'; - -import { Factory } from './Factory'; - -// ------------------------------------------------------------------------- -// Handy Exports -// ------------------------------------------------------------------------- - -export * from './FactoryInterface'; -export * from './EntityFactoryInterface'; -export * from './SeedsInterface'; -export * from './Factory'; -export * from './utils'; - -// ------------------------------------------------------------------------- -// Facade functions -// ------------------------------------------------------------------------- - -export const getFactory = (connection: Connection) => { - const factory = Factory.getInstance(); - factory.setConnection(connection); - return factory; -}; diff --git a/src/lib/seeds/utils.ts b/src/lib/seeds/utils.ts deleted file mode 100644 index 5106607e..00000000 --- a/src/lib/seeds/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Times repeats a function n times - * @param n amount of loops - * @param iteratee this function gets n-times called - */ -export const times = async (n: number, iteratee: (index: number) => Promise): Promise => { - const rs = [] as TResult[]; - for (let i = 0; i < n; i++) { - const r = await iteratee(i); - rs.push(r); - } - return rs; -}; diff --git a/test/e2e/api/users.test.ts b/test/e2e/api/users.test.ts index 115b56a3..1290b549 100644 --- a/test/e2e/api/users.test.ts +++ b/test/e2e/api/users.test.ts @@ -3,34 +3,35 @@ import * as request from 'supertest'; import { User } from '../../../src/api/models/User'; import { CreateBruce } from '../../../src/database/seeds/CreateBruce'; -import { Factory } from '../../../src/lib/seeds/Factory'; -import { getFactory } from '../../../src/lib/seeds/index'; -import { closeDatabase, migrateDatabase } from '../../utils/database'; +import { runSeeder } from '../../../src/lib/seed'; +import { closeDatabase } from '../../utils/database'; import { fakeAuthenticationForUser } from '../utils/auth'; -import { bootstrapApp, BootstrapSettings } from '../utils/bootstrap'; +import { BootstrapSettings } from '../utils/bootstrap'; +import { prepareServer } from '../utils/server'; describe('/api/users', () => { + let bruce: User; + let settings: BootstrapSettings; + // ------------------------------------------------------------------------- // Setup up // ------------------------------------------------------------------------- - let settings: BootstrapSettings; - let factory: Factory; - let bruce: User; - let authServer: nock.Scope; - beforeAll(async () => settings = await bootstrapApp()); - beforeAll(async () => migrateDatabase(settings.connection)); - beforeAll(async () => factory = getFactory(settings.connection)); - beforeAll(async () => bruce = await factory.runSeed(CreateBruce)); - beforeAll(async () => authServer = fakeAuthenticationForUser(bruce, true)); + beforeAll(async () => { + settings = await prepareServer({ migrate: true }); + bruce = await runSeeder(CreateBruce); + fakeAuthenticationForUser(bruce, true); + }); // ------------------------------------------------------------------------- // Tear down // ------------------------------------------------------------------------- - afterAll(() => nock.cleanAll()); - afterAll(async () => closeDatabase(settings.connection)); + afterAll(async () => { + nock.cleanAll(); + await closeDatabase(settings.connection); + }); // ------------------------------------------------------------------------- // Test cases diff --git a/test/e2e/utils/server.ts b/test/e2e/utils/server.ts new file mode 100644 index 00000000..2150ebcb --- /dev/null +++ b/test/e2e/utils/server.ts @@ -0,0 +1,12 @@ +import { setConnection } from '../../../src/lib/seed'; +import { migrateDatabase } from '../../utils/database'; +import { bootstrapApp } from './bootstrap'; + +export const prepareServer = async (options?: { migrate: boolean }) => { + const settings = await bootstrapApp(); + if (options && options.migrate) { + await migrateDatabase(settings.connection); + } + setConnection(settings.connection); + return settings; +}; diff --git a/tslint.json b/tslint.json index 525f68e8..aae115af 100644 --- a/tslint.json +++ b/tslint.json @@ -20,7 +20,6 @@ "ordered-imports": false, "object-literal-sort-keys": false, "arrow-parens": false, - "no-string-literal": false, "member-ordering": [ true, { @@ -72,6 +71,58 @@ }, "singleline": "never" } + ], + "align": [ + true, + "parameters" + ], + "class-name": true, + "curly": true, + "eofline": true, + "jsdoc-format": true, + "member-access": true, + "no-arg": true, + "no-construct": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-internal-module": true, + "no-string-literal": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-var-keyword": true, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-finally", + "check-whitespace" + ], + "semicolon": true, + "switch-default": true, + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" ] } } diff --git a/yarn.lock b/yarn.lock index 2271b4b5..3a661e7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -101,8 +101,8 @@ "@types/node" "*" "@types/node@*": - version "9.4.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.6.tgz#d8176d864ee48753d053783e4e463aec86b8d82e" + version "9.4.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" "@types/reflect-metadata@0.0.5": version "0.0.5" @@ -252,12 +252,18 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0, ansi-styles@^3.2.0: +ansi-styles@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" dependencies: color-convert "^1.9.0" +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -302,8 +308,8 @@ are-we-there-yet@~1.1.2: readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" @@ -557,7 +563,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2: +babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -866,12 +872,12 @@ chalk@^1.1.1, chalk@^1.1.3: supports-color "^2.0.0" chalk@^2.0.1, chalk@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + version "2.3.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" dependencies: - ansi-styles "^3.2.0" + ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" - supports-color "^5.2.0" + supports-color "^5.3.0" check-error@^1.0.1: version "1.0.2" @@ -914,9 +920,9 @@ ci-info@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" -class-transformer@~0.1.6: - version "0.1.8" - resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.1.8.tgz#be04dd2afb7b301e4c8c79c5349fedaac3d5a7e1" +class-transformer@^0.1.9: + version "0.1.9" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.1.9.tgz#29977c528233ca014e6fd9523327ebd31d11ca54" class-utils@^0.3.5: version "0.3.6" @@ -927,12 +933,18 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -class-validator@^0.7.0, class-validator@^0.7.3: +class-validator@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.7.3.tgz#3c2821b8cf35fd8d5f4fcb8063bc57fb50049e7e" dependencies: validator "^7.0.0" +class-validator@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.8.1.tgz#f5efd5c613927e3c2f68692e8f14d53a2644fb2f" + dependencies: + validator "9.2.0" + cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" @@ -1015,18 +1027,18 @@ commander@2.6.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" commander@^2.11.0: - version "2.14.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" + version "2.15.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" commander@^2.12.1: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" common-tags@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0" + version "1.7.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.7.2.tgz#24d9768c63d253a56ecff93845b44b4df1d52771" dependencies: - babel-runtime "^6.18.0" + babel-runtime "^6.26.0" component-bind@1.0.0: version "1.0.0" @@ -1141,7 +1153,11 @@ copyfiles@^1.2.0: noms "0.0.0" through2 "^2.0.1" -core-js@^2.4.0, core-js@^2.5.0: +core-js@^2.4.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + +core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -1216,8 +1232,8 @@ cross-env@^3.1.4: is-windows "^1.0.0" cross-env@^5.1.1: - version "5.1.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.3.tgz#f8ae18faac87692b0a8b4d2f7000d4ec3a85dfd7" + version "5.1.4" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.4.tgz#f61c14291f7cc653bb86457002ea80a04699d022" dependencies: cross-spawn "^5.1.0" is-windows "^1.0.0" @@ -2142,8 +2158,8 @@ helmet-csp@2.7.0: platform "1.3.5" helmet@^3.9.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.11.0.tgz#5eacccc0b5b61d786e29aa3fc5650abf73e1824f" + version "3.12.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.12.0.tgz#2098e35cf4e51c64c2f1d38670b7d382a377d92c" dependencies: dns-prefetch-control "0.1.0" dont-sniff-mimetype "1.0.0" @@ -2156,7 +2172,7 @@ helmet@^3.9.0: ienoopen "1.0.0" nocache "2.0.0" referrer-policy "1.1.0" - x-xss-protection "1.0.0" + x-xss-protection "1.1.0" hide-powered-by@1.0.0: version "1.0.0" @@ -2188,8 +2204,8 @@ homedir-polyfill@^1.0.1: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + version "2.6.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" hpkp@2.0.0: version "2.0.0" @@ -2502,11 +2518,7 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -is-windows@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" - -is-windows@^1.0.2: +is-windows@^1.0.0, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -2836,13 +2848,20 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.7.0, js-yaml@^3.8.4, js-yaml@^3.9.0: +js-yaml@^3.7.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.8.4, js-yaml@^3.9.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -2995,26 +3014,10 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash.endswith@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" - -lodash.isfunction@^3.0.8: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.reduce@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" -lodash.startswith@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" - lodash@^4.14.0, lodash@^4.5.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3045,8 +3048,8 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" lru-cache@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + version "4.1.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" @@ -3354,8 +3357,8 @@ nocache@2.0.0: resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980" nock@^9.1.4: - version "9.2.1" - resolved "https://registry.yarnpkg.com/nock/-/nock-9.2.1.tgz#7e73561277c3e8f2287e385d3d04c7223a54faea" + version "9.2.3" + resolved "https://registry.yarnpkg.com/nock/-/nock-9.2.3.tgz#39738087d6a0497d3a165fb352612b38a2f9b92f" dependencies: chai "^4.1.2" debug "^3.1.0" @@ -3397,17 +3400,18 @@ node-pre-gyp@^0.6.39, node-pre-gyp@~0.6.38: tar-pack "^3.4.0" nodemon@^1.12.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.15.1.tgz#54daa72443d8d5a548f130866b92e65cded0ed58" + version "1.17.1" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.17.1.tgz#cdb4bc53d7a86d6162143a1a44d7adf927d8652f" dependencies: chokidar "^2.0.2" debug "^3.1.0" ignore-by-default "^1.0.1" minimatch "^3.0.4" pstree.remy "^1.1.0" - semver "^5.4.1" + semver "^5.5.0" + supports-color "^5.2.0" touch "^3.1.0" - undefsafe "^2.0.1" + undefsafe "^2.0.2" update-notifier "^2.3.0" noms@0.0.0: @@ -3476,8 +3480,8 @@ nps-utils@^1.5.0: rimraf "^2.6.1" nps@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/nps/-/nps-5.7.1.tgz#12cbfc635a6535ccb4f6eb516e6011f3af6bef42" + version "5.8.2" + resolved "https://registry.yarnpkg.com/nps/-/nps-5.8.2.tgz#17fc2e86f5f5577c54c49423996a63ef9299a889" dependencies: arrify "^1.0.1" chalk "^2.0.1" @@ -3969,8 +3973,8 @@ readable-stream@2.3.3, readable-stream@^2.0.5, readable-stream@^2.1.5: util-deprecate "~1.0.1" readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" + version "2.3.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -3999,8 +4003,8 @@ readdirp@^2.0.0: set-immediate-shim "^1.0.1" readline-sync@^1.4.7: - version "1.4.7" - resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.7.tgz#001bfdd4c06110c3c084c63bf7c6a56022213f30" + version "1.4.9" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.9.tgz#3eda8e65f23cd2a17e61301b1f0003396af5ecda" redent@^1.0.0: version "1.0.0" @@ -4013,7 +4017,7 @@ referrer-policy@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79" -reflect-metadata@^0.1.10: +reflect-metadata@^0.1.10, reflect-metadata@^0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" @@ -4158,14 +4162,14 @@ rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: glob "^7.0.5" routing-controllers@^0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.7.6.tgz#f025b6b1f011fb5973e94db4ffde86da8d48091e" + version "0.7.7" + resolved "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.7.7.tgz#55b3dd3e676ade2527e522aad2834ac891cce0ee" dependencies: - class-transformer "~0.1.6" - class-validator "^0.7.0" + class-transformer "^0.1.9" + class-validator "^0.8.1" cookie "^0.3.1" glob "^7.0.5" - reflect-metadata "^0.1.10" + reflect-metadata "^0.1.12" template-url "^1.0.0" rx@2.3.24: @@ -4206,7 +4210,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -4446,22 +4450,30 @@ spawn-command-with-kill@^1.0.0: spawn-command "^0.0.2-1" spawn-command@^0.0.2-1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -4659,9 +4671,9 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -supports-color@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" +supports-color@^5.2.0, supports-color@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" dependencies: has-flag "^3.0.0" @@ -4796,12 +4808,18 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: +tough-cookie@^2.3.2, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: punycode "^1.4.1" +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -4898,14 +4916,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0: +type-detect@^4.0.0, type-detect@^4.0.3: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" -type-detect@^4.0.3: - version "4.0.5" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" - type-is@^1.6.15, type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" @@ -4922,8 +4936,8 @@ typeorm-typedi-extensions@^0.1.1: resolved "https://registry.yarnpkg.com/typeorm-typedi-extensions/-/typeorm-typedi-extensions-0.1.1.tgz#e99a66dcf501fbd5837cf2f7a3dc75e13de89734" typeorm@^0.1.3: - version "0.1.12" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.1.12.tgz#adcd45f302d21ab4d5b02671bfa8c07cea044af7" + version "0.1.16" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.1.16.tgz#52a9251f4394c07ce1f4e14d2c8b6cfca89535fc" dependencies: app-root-path "^2.0.1" chalk "^2.0.1" @@ -4963,7 +4977,7 @@ ultron@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" -undefsafe@^2.0.1: +undefsafe@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" dependencies: @@ -5004,13 +5018,8 @@ unzip-response@^2.0.1: resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" upath@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.2.tgz#80aaae5395abc5fd402933ae2f58694f0860204c" - dependencies: - lodash.endswith "^4.2.1" - lodash.isfunction "^3.0.8" - lodash.isstring "^4.0.1" - lodash.startswith "^4.2.1" + version "1.0.4" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" update-notifier@^2.3.0: version "2.3.0" @@ -5077,11 +5086,15 @@ v8flags@^3.0.0: homedir-polyfill "^1.0.1" validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validator@9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-9.2.0.tgz#ad216eed5f37cac31a6fe00ceab1f6b88bded03e" validator@^7.0.0: version "7.2.0" @@ -5160,8 +5173,8 @@ window-size@0.1.0: resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" winston@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee" + version "2.4.1" + resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.1.tgz#a3a9265105564263c6785b4583b8c8aca26fded6" dependencies: async "~1.0.0" colors "1.0.x" @@ -5216,9 +5229,9 @@ ws@~3.3.1: safe-buffer "~5.1.0" ultron "~1.1.0" -x-xss-protection@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.0.0.tgz#898afb93869b24661cf9c52f9ee8db8ed0764dd9" +x-xss-protection@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.1.0.tgz#4f1898c332deb1e7f2be1280efb3e2c53d69c1a7" xdg-basedir@^3.0.0: version "3.0.0" @@ -5236,8 +5249,8 @@ xml2js@^0.4.17: xmlbuilder "~9.0.1" xmlbuilder@~9.0.1: - version "9.0.4" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" xmlhttprequest-ssl@~1.5.4: version "1.5.4"