diff --git a/.travis.yml b/.travis.yml index c79b4af4..5560e288 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,14 @@ language: node_js node_js: - - "8.2.1" + - "8.9.4" install: - yarn install -scripts: - - npm test +env: + - DB_TYPE="sqlite" DB_DATABASE="./mydb.sql" DB_LOGGING=false +script: + - npm start test + - npm start test.integration + - npm start test.e2e - npm start build notifications: email: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6ffc870d..485cb437 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,7 +2,6 @@ "recommendations": [ "streetsidesoftware.code-spell-checker", "eg2.tslint", - "steoates.autoimport", "EditorConfig.EditorConfig", "christian-kohler.path-intellisense", "mike-co.import-sorter", diff --git a/README.md b/README.md index 3537b8c7..d65c3455 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,42 @@ -# Express Typescript Boilerplate - -[![Dependency Status](https://david-dm.org/w3tecch/express-typescript-boilerplate/status.svg?style=flat)](https://david-dm.org/w3tecch/express-typescript-boilerplate) -[![Build Status](https://travis-ci.org/w3tecch/express-typescript-boilerplate.svg?branch=master)](https://travis-ci.org/w3tecch/express-typescript-boilerplate) -[![Build status](https://ci.appveyor.com/api/projects/status/f8e7jdm8v58hcwpq/branch/master?svg=true&passingText=Windows%20passing&pendingText=Windows%20pending&failingText=Windows%20failing)](https://ci.appveyor.com/project/dweber019/express-typescript-boilerplate/branch/master) - -> A delightful way to building a RESTful API Services with beautiful code written in TypeScript. -> An Node.js Web-Service boilerplate/skeleton/starter-kit featuring -> Inspired by the awesome framework [laravel](https://laravel.com/) in PHP and of the repositories from [pleerock](https://github.com/pleerock). -[TypeScript](https://www.typescriptlang.org/), -[Express](https://expressjs.com/), -[Winston](https://github.com/winstonjs/winston), -[Microframework](https://github.com/pleerock/microframework), -[TypeDI](https://github.com/pleerock/typedi), -[TypeORM](https://github.com/typeorm/typeorm), -[TsLint](http://palantir.github.io/tslint/), -[@types](https://www.npmjs.com/~types), -[Jest](https://facebook.github.io/jest/), -[Swagger](http://swagger.io/), -[validatejs](https://validatejs.org/), -[GraphQL](http://graphql.org/), -[DataLoaders](https://github.com/facebook/dataloader), -by [w3tech](https://github.com/w3tecch) - -## Why +

+ w3tec +

+ +

Express Typescript Boilerplate

+ +

+ + dependency + + + travis + + + appveyor + + + StackShare + +

+ +

+ A delightful way to building a Node.js RESTful API Services with beautiful code written in TypeScript.
+ Inspired by the awesome framework laravel in PHP and of the repositories from pleerock
+ Made with ❤️ by w3tech, Gery Hirschfeld and contributors +

+ +
+ +![divider](./w3tec-divider.png) + +## ❯ Why Our main goal with this project is a feature complete server application. We like you to be focused on your business and not spending hours in project configuration. Try it!! We are happy to hear your feedback or any kind of new features. -## Features +### Features - **Beautiful Code** thanks to the awesome annotations of the libraries from [pleerock](https://github.com/pleerock). - **Easy API Testing** with included e2e testing. @@ -50,23 +57,27 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **GraphQL** provides as a awesome query language for our api [GraphQL](http://graphql.org/). - **DataLoaders** helps with performance thanks to caching and batching [DataLoaders](https://github.com/facebook/dataloader). -## Table of Contents +![divider](./w3tec-divider.png) -- [Getting Started](#getting-started) -- [Scripts and Tasks](#scripts-and-tasks) -- [Debugger in VSCode](#debugger-in-vscode) -- [API Routes](#api-routes) -- [Project Structure](#project-structure) -- [Logging](#logging) -- [Event Dispatching](#event-dispatching) -- [Seeding](#seeding) -- [Further Documentations](#further-documentation) -- [Related Projects](#related-projects) -- [License](#license) +## ❯ Table of Contents -## Getting Started +- [Getting Started](#-getting-started) +- [Scripts and Tasks](#-scripts-and-tasks) +- [Debugger in VSCode](#-debugger-in-vscode) +- [API Routes](#-api-routes) +- [Project Structure](#-project-structure) +- [Logging](#-logging) +- [Event Dispatching](#-event-dispatching) +- [Seeding](#-seeding) +- [Further Documentations](#-further-documentation) +- [Related Projects](#-related-projects) +- [License](#-license) -### Step 1: Set up the Development Environment +![divider](./w3tec-divider.png) + +## ❯ Getting Started + +### Step 1: Set up the Development Environment You need to set up your development environment before you can do anything. @@ -112,9 +123,11 @@ npm start serve > This starts a local server using `nodemon`, which will watch for any file changes and will restart the sever according to these changes. > The server address will be displayed to you as `http://0.0.0.0:3000`. -## Scripts and Tasks +![divider](./w3tec-divider.png) + +## ❯ Scripts and Tasks -All script are defined in the package.json file, but the most important ones are listed here. +All script are defined in the `package-scripts.js` file, but the most important ones are listed here. ### Install @@ -153,12 +166,16 @@ All script are defined in the package.json file, but the most important ones are - Run `npm start db.seed` to seed your seeds into the database. -## Debugger in VSCode +![divider](./w3tec-divider.png) + +## ❯ Debugger in VSCode + +To debug your code run `npm start build` or hit cmd + b to build your app. +Then, just set a breakpoint and hit F5 in your Visual Studio Code. -To debug your code run `npm start build` or hit `cmd + b` to build your app. -Then, just set a breakpoint and hit `F5` in your Visual Studio Code. +![divider](./w3tec-divider.png) -## API Routes +## ❯ API Routes The route prefix is `/api` by default, but you can change this in the .env file. The swagger and the monitor route can be altered in the `.env` file. @@ -172,7 +189,9 @@ The swagger and the monitor route can be altered in the `.env` file. | **/api/users** | Example entity endpoint | | **/api/pets** | Example entity endpoint | -## Project Structure +![divider](./w3tec-divider.png) + +## ❯ Project Structure | Name | Description | | --------------------------------- | ----------- | @@ -212,7 +231,9 @@ The swagger and the monitor route can be altered in the `.env` file. | ormconfig.json | TypeORM configuration for the database. Used by seeds and the migration. (generated file) | | mydb.sql | SQLite database for integration tests. Ignored by git and only available after integration tests | -## Logging +![divider](./w3tec-divider.png) + +## ❯ Logging Our logger is [winston](https://github.com/winstonjs/winston). To log http request we use the express middleware [morgan](https://github.com/expressjs/morgan). We created a simple annotation to inject the logger in your service (see example below). @@ -230,9 +251,11 @@ export class UserService { ... ``` -## Event Dispatching +![divider](./w3tec-divider.png) -Our we use this awesome repository [event-dispatch](https://github.com/pleerock/event-dispatch) for event dispatching. +## ❯ Event Dispatching + +We use this awesome repository [event-dispatch](https://github.com/pleerock/event-dispatch) for event dispatching. We created a simple annotation to inject the EventDispatcher in your service (see example below). All events are listed in the `events.ts` file. ```typescript @@ -253,17 +276,22 @@ export class UserService { } ``` -## Seeding +![divider](./w3tec-divider.png) + +## ❯ 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 +301,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. - -```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. +Handle relation in the entity factory 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,7 +381,20 @@ The last step is the easiest, just hit the following command in your terminal, b npm start db.seed ``` -## Run in Docker container +#### 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 | + +![divider](./w3tec-divider.png) + +## ❯ Run in Docker container ### Install Docker @@ -450,7 +473,9 @@ DB_HOST=localhost DB_PORT=3306 ``` -## Further Documentations +![divider](./w3tec-divider.png) + +## ❯ Further Documentations | Name & Link | Description | | --------------------------------- | --------------------------------- | @@ -472,16 +497,17 @@ DB_PORT=3306 | [GraphQL Documentation](http://graphql.org/graphql-js/) | A query language for your API. | | [DataLoader Documentation](https://github.com/facebook/dataloader) | DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a consistent API over various backends and reduce requests to those backends via batching and caching. | -## Related Projects +![divider](./w3tec-divider.png) + +## ❯ Related Projects - [Microsoft/TypeScript-Node-Starter](https://github.com/Microsoft/TypeScript-Node-Starter) - A starter template for TypeScript and Node with a detailed README describing how to use the two together. - [express-graphql-typescript-boilerplate](https://github.com/w3tecch/express-graphql-typescript-boilerplate) - A starter kit for building amazing GraphQL API's with TypeScript and express by @w3tecch - [aurelia-typescript-boilerplate](https://github.com/w3tecch/aurelia-typescript-boilerplate) - An Aurelia starter kit with TypeScript - [Auth0 Mock Server](https://github.com/hirsch88/auth0-mock-server) - Useful for e2e testing or faking an oAuth server -## License +![divider](./w3tec-divider.png) -[MIT](/LICENSE) +## ❯ License ---- -Made with ♥ by w3tech ([w3tech](https://github.com/w3tecch)), Gery Hirschfeld ([@GeryHirschfeld1](https://twitter.com/GeryHirschfeld1)) and [contributors](https://github.com/w3tecch/express-typescript-boilerplate/graphs/contributors) \ No newline at end of file +[MIT](/LICENSE) diff --git a/appveyor.yml b/appveyor.yml index 3bc1c57b..d9d00e55 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,8 @@ environment: nodejs_version: "8" + DB_TYPE: "sqlite" + DB_DATABASE: "./mydb.sql" + DB_LOGGING: false install: - ps: Install-Product node $env:nodejs_version @@ -9,4 +12,6 @@ build_script: - npm start build test_script: - - npm test + - npm start test + - npm start test.integration + - npm start test.e2e 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/package.json b/package.json index 13250bce..b04d8e18 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "express-typescript-boilerplate", - "version": "3.0.0-rc.2", - "description": "A delightful way to building a RESTful API with NodeJs & TypeScript", + "version": "3.0.0", + "description": "A delightful way to building a Node.js RESTful API Services with beautiful code written in TypeScript", "main": "src/app.ts", "scripts": { "start": "nps", "test": "npm start test", "build": "npm start build", "presetup": "yarn install", - "setup": "npm start setup.db" + "setup": "npm start config && npm start setup.script" }, "engines": { "node": ">=8.0.0" 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/api/types/PetType.ts b/src/api/types/PetType.ts index 3ae159ad..00be2b8a 100644 --- a/src/api/types/PetType.ts +++ b/src/api/types/PetType.ts @@ -35,7 +35,7 @@ export const PetType = new GraphQLObjectType({ type: OwnerType, description: 'The owner of the pet', resolve: (pet: Pet, args: any, context: GraphQLContext) => - context.dataLoaders.users.load(pet.userId), + context.dataLoaders.user.load(pet.userId), }, } }), }); diff --git a/src/api/types/UserType.ts b/src/api/types/UserType.ts index 1da349e4..8aab22cc 100644 --- a/src/api/types/UserType.ts +++ b/src/api/types/UserType.ts @@ -34,7 +34,7 @@ export const UserType = new GraphQLObjectType({ description: 'The pets of a user', resolve: async (user: User, args: any, context: GraphQLContext) => // We use data-loaders to save db queries - context.dataLoaders.petByUserIds.loadMany([user.id]), + context.dataLoaders.petsByUserIds.load(user.id), // This would be the case with a normal service, but not very fast // context.container.get(PetService).findByUser(user), }, 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..9a0bd2c5 100644 --- a/src/database/seeds/CreatePets.ts +++ b/src/database/seeds/CreatePets.ts @@ -1,18 +1,18 @@ +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); + return 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/graphql/dataloader.ts b/src/lib/graphql/dataloader.ts deleted file mode 100644 index 7482ebd0..00000000 --- a/src/lib/graphql/dataloader.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface Identifiable { - id?: number | number; -} - -export function ensureInputOrder(ids: number[] | string[], result: T[], key: string): T[] { - // For the dataloader batching to work, the results must be in the same order and of the - // same length as the ids. See: https://github.com/facebook/dataloader#batch-function - const orderedResult: T[] = []; - for (const id of ids) { - const item = result.find(t => t[key] === id); - if (item) { - orderedResult.push(item); - } else { - /* tslint:disable */ - // @ts-ignore - orderedResult.push(null); - /* tslint:enable */ - } - } - return orderedResult; -} diff --git a/src/lib/graphql/index.ts b/src/lib/graphql/index.ts index 82743967..f50a9a1d 100644 --- a/src/lib/graphql/index.ts +++ b/src/lib/graphql/index.ts @@ -6,7 +6,6 @@ import { Container as container, ObjectType } from 'typedi'; import { getCustomRepository, getRepository, Repository } from 'typeorm'; import { getFromContainer } from './container'; -import { ensureInputOrder } from './dataloader'; import { getErrorCode, getErrorMessage, handlingErrors } from './graphql-error-handling'; import { GraphQLContext, GraphQLContextDataLoader } from './GraphQLContext'; import { importClassesFromDirectories } from './importClassesFromDirectories'; @@ -29,10 +28,16 @@ export * from './container'; // Main Functions // ------------------------------------------------------------------------- +export interface CreateDataLoaderOptions { + method?: string; + key?: string; + multiple?: boolean; +} + /** * Creates a new dataloader with the typorm repository */ -export function createDataLoader(obj: ObjectType, method?: string, key?: string): DataLoader { +export function createDataLoader(obj: ObjectType, options: CreateDataLoaderOptions = {}): DataLoader { let repository; try { repository = getCustomRepository>(obj); @@ -46,13 +51,14 @@ export function createDataLoader(obj: ObjectType, method?: string, key?: s return new DataLoader(async (ids: number[]) => { let items = []; - if (method) { - items = await repository[method](ids); + if (options.method) { + items = await repository[options.method](ids); } else { items = await repository.findByIds(ids); } - return ensureInputOrder(ids, items, key || 'id'); + const handleBatch = (arr: any[]) => options.multiple === true ? arr : arr[0]; + return ids.map(id => handleBatch(items.filter(item => item[options.key || 'id'] === id))); }); } 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/seeds/utils.ts b/src/lib/seed/utils.ts similarity index 50% rename from src/lib/seeds/utils.ts rename to src/lib/seed/utils.ts index 5106607e..88b05037 100644 --- a/src/lib/seeds/utils.ts +++ b/src/lib/seed/utils.ts @@ -1,7 +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 - * @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[]; 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/loaders/graphqlLoader.ts b/src/loaders/graphqlLoader.ts index 61abca0e..4a790e32 100644 --- a/src/loaders/graphqlLoader.ts +++ b/src/loaders/graphqlLoader.ts @@ -16,9 +16,13 @@ export const graphqlLoader: MicroframeworkLoader = (settings: MicroframeworkSett queries: env.app.dirs.queries, mutations: env.app.dirs.mutations, dataLoaders: { - users: createDataLoader(UserRepository), - pets: createDataLoader(Pet), - petByUserIds: createDataLoader(PetRepository, 'findByUserIds', 'userId'), + user: createDataLoader(UserRepository), + pet: createDataLoader(Pet), + petsByUserIds: createDataLoader(PetRepository, { + method: 'findByUserIds', + key: 'userId', + multiple: true, + }), }, }); diff --git a/test/e2e/api/users.test.ts b/test/e2e/api/users.test.ts index 115b56a3..51e4b2e1 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 { runSeed } 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 runSeed(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/test/unit/lib/RepositoryMock.ts b/test/unit/lib/RepositoryMock.ts index 90f72625..5ae85a33 100644 --- a/test/unit/lib/RepositoryMock.ts +++ b/test/unit/lib/RepositoryMock.ts @@ -8,24 +8,24 @@ export class RepositoryMock { public saveMock = jest.fn(); public deleteMock = jest.fn(); - public find(...args: any[]): T[] { + public find(...args: any[]): Promise { this.findMock(args); - return this.list; + return Promise.resolve(this.list); } - public findOne(...args: any[]): T { + public findOne(...args: any[]): Promise { this.findOneMock(args); - return this.one; + return Promise.resolve(this.one); } - public save(value: T, ...args: any[]): T { + public save(value: T, ...args: any[]): Promise { this.saveMock(value, args); - return value; + return Promise.resolve(value); } - public delete(value: T, ...args: any[]): T { + public delete(value: T, ...args: any[]): Promise { this.deleteMock(value, args); - return value; + return Promise.resolve(value); } } 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/w3tec-divider.png b/w3tec-divider.png new file mode 100644 index 00000000..209b5404 Binary files /dev/null and b/w3tec-divider.png differ diff --git a/w3tec-logo.png b/w3tec-logo.png new file mode 100644 index 00000000..b9d594f7 Binary files /dev/null and b/w3tec-logo.png differ 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"