From ac9f2229f18fe874aa83e2ac971d12eeba5e07b0 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 11:09:47 +0100 Subject: [PATCH 001/104] Clean project for new deps --- README.md | 1 - knexfile.ts | 7 - package-scripts.js | 2 +- package.json | 35 +- src/api/controllers/UserController.ts | 60 - src/api/exceptions/DatabaseException.ts | 19 - src/api/exceptions/NotFoundException.ts | 17 - src/api/exceptions/ValidationException.ts | 20 - .../user/UserAuthenticatedListener.ts | 22 - src/api/listeners/user/UserCreatedListener.ts | 23 - src/api/middlewares/AuthenticateMiddleware.ts | 71 - src/api/middlewares/PopulateUserMiddleware.ts | 36 - src/api/models/User.ts | 60 - src/api/repositories/UserRepository.ts | 115 -- src/api/requests/user/UserCreateRequest.ts | 31 - src/api/requests/user/UserUpdateRequest.ts | 41 - src/api/services/UserService.ts | 119 -- src/api/validators/EndsWithValidator.ts | 21 - src/app.ts | 10 +- src/config/AppConfig.ts | 60 - src/config/CustomHeaderConfig.ts | 31 - src/config/Database.ts | 34 - src/config/IocConfig.ts | 47 - src/config/LoggerConfig.ts | 18 - src/config/logger/WinstonAdapter.ts | 58 - src/console/DatabaseResetCommand.ts | 49 - src/console/MakeApiTestCommand.ts | 21 - src/console/MakeControllerCommand.ts | 19 - src/console/MakeExceptionCommand.ts | 19 - src/console/MakeListenerCommand.ts | 19 - src/console/MakeMiddlewareCommand.ts | 19 - src/console/MakeMigrationCommand.ts | 56 - src/console/MakeModelCommand.ts | 85 - src/console/MakeRepoCommand.ts | 19 - src/console/MakeRequestCommand.ts | 25 - src/console/MakeResourceCommand.ts | 70 - src/console/MakeSeedCommand.ts | 26 - src/console/MakeServiceCommand.ts | 19 - src/console/MakeValidatorCommand.ts | 20 - src/console/UpdateTargetsCommand.ts | 89 - src/console/lib/AbstractCommand.ts | 36 - src/console/lib/AbstractMakeCommand.ts | 106 -- src/console/lib/console.ts | 69 - src/console/lib/template.ts | 56 - src/console/lib/utils.ts | 151 -- src/console/templates/api-test.hbs | 135 -- src/console/templates/controller.hbs | 52 - src/console/templates/exception.hbs | 8 - src/console/templates/listener.hbs | 22 - src/console/templates/middleware.hbs | 21 - src/console/templates/migration.hbs | 39 - src/console/templates/model.hbs | 28 - src/console/templates/repository.hbs | 64 - src/console/templates/request.hbs | 20 - src/console/templates/resource.hbs | 14 - src/console/templates/seed.hbs | 10 - src/console/templates/service.hbs | 82 - src/console/templates/targets.hbs | 18 - src/console/templates/validator.hbs | 17 - src/constants/Tables.ts | 12 - src/constants/Targets.ts | 34 - src/constants/Types.ts | 20 - src/constants/index.ts | 4 - src/core/ApiInfo.ts | 40 - src/core/ApiMonitor.ts | 19 - src/core/App.ts | 80 - src/core/BasicAuthentication.ts | 11 - src/core/Bootstrap.ts | 62 - src/core/IoC.ts | 219 --- src/core/Logger.ts | 90 - src/core/Server.ts | 114 -- src/core/SwaggerUI.ts | 32 - src/core/Targets.ts | 12 - src/core/api/Exception.ts | 30 - src/core/api/RequestBody.ts | 44 - src/core/api/Validate.ts | 57 - src/core/api/events.ts | 10 - src/core/api/exceptionHandler.ts | 24 - src/core/api/extendExpressResponse.ts | 116 -- src/core/database/BluePrint.ts | 13 - src/core/database/Factory.ts | 45 - src/core/database/ModelFactory.ts | 62 - src/core/database/index.ts | 3 - src/core/helpers/Environment.ts | 41 - src/database/factories/index.ts | 32 - .../20170120183349_create_users_table.ts | 25 - src/database/seeds/create_users.ts | 11 - .../AuthenticateMiddleware.test.ts | 64 - .../PopulateUserMiddleware.test.ts | 65 - test/unit/core/Environment.test.ts | 20 - test/unit/core/api/Exception.test.ts | 23 - test/unit/core/api/RequestBody.test.ts | 75 - test/unit/core/api/exceptionHandler.test.ts | 35 - .../core/api/extendExpressResponse.test.ts | 127 -- yarn.lock | 1475 ++++------------- 95 files changed, 289 insertions(+), 5318 deletions(-) delete mode 100644 knexfile.ts delete mode 100644 src/api/controllers/UserController.ts delete mode 100755 src/api/exceptions/DatabaseException.ts delete mode 100755 src/api/exceptions/NotFoundException.ts delete mode 100755 src/api/exceptions/ValidationException.ts delete mode 100644 src/api/listeners/user/UserAuthenticatedListener.ts delete mode 100644 src/api/listeners/user/UserCreatedListener.ts delete mode 100644 src/api/middlewares/AuthenticateMiddleware.ts delete mode 100644 src/api/middlewares/PopulateUserMiddleware.ts delete mode 100644 src/api/models/User.ts delete mode 100644 src/api/repositories/UserRepository.ts delete mode 100644 src/api/requests/user/UserCreateRequest.ts delete mode 100644 src/api/requests/user/UserUpdateRequest.ts delete mode 100644 src/api/services/UserService.ts delete mode 100644 src/api/validators/EndsWithValidator.ts delete mode 100644 src/config/AppConfig.ts delete mode 100644 src/config/CustomHeaderConfig.ts delete mode 100755 src/config/Database.ts delete mode 100644 src/config/IocConfig.ts delete mode 100644 src/config/LoggerConfig.ts delete mode 100644 src/config/logger/WinstonAdapter.ts delete mode 100644 src/console/DatabaseResetCommand.ts delete mode 100644 src/console/MakeApiTestCommand.ts delete mode 100644 src/console/MakeControllerCommand.ts delete mode 100644 src/console/MakeExceptionCommand.ts delete mode 100644 src/console/MakeListenerCommand.ts delete mode 100644 src/console/MakeMiddlewareCommand.ts delete mode 100644 src/console/MakeMigrationCommand.ts delete mode 100644 src/console/MakeModelCommand.ts delete mode 100644 src/console/MakeRepoCommand.ts delete mode 100644 src/console/MakeRequestCommand.ts delete mode 100644 src/console/MakeResourceCommand.ts delete mode 100644 src/console/MakeSeedCommand.ts delete mode 100644 src/console/MakeServiceCommand.ts delete mode 100644 src/console/MakeValidatorCommand.ts delete mode 100644 src/console/UpdateTargetsCommand.ts delete mode 100644 src/console/lib/AbstractCommand.ts delete mode 100644 src/console/lib/AbstractMakeCommand.ts delete mode 100644 src/console/lib/console.ts delete mode 100644 src/console/lib/template.ts delete mode 100644 src/console/lib/utils.ts delete mode 100644 src/console/templates/api-test.hbs delete mode 100644 src/console/templates/controller.hbs delete mode 100644 src/console/templates/exception.hbs delete mode 100644 src/console/templates/listener.hbs delete mode 100644 src/console/templates/middleware.hbs delete mode 100644 src/console/templates/migration.hbs delete mode 100644 src/console/templates/model.hbs delete mode 100644 src/console/templates/repository.hbs delete mode 100644 src/console/templates/request.hbs delete mode 100644 src/console/templates/resource.hbs delete mode 100644 src/console/templates/seed.hbs delete mode 100644 src/console/templates/service.hbs delete mode 100644 src/console/templates/targets.hbs delete mode 100644 src/console/templates/validator.hbs delete mode 100644 src/constants/Tables.ts delete mode 100644 src/constants/Targets.ts delete mode 100644 src/constants/Types.ts delete mode 100644 src/constants/index.ts delete mode 100644 src/core/ApiInfo.ts delete mode 100644 src/core/ApiMonitor.ts delete mode 100644 src/core/App.ts delete mode 100644 src/core/BasicAuthentication.ts delete mode 100644 src/core/Bootstrap.ts delete mode 100644 src/core/IoC.ts delete mode 100644 src/core/Logger.ts delete mode 100644 src/core/Server.ts delete mode 100644 src/core/SwaggerUI.ts delete mode 100644 src/core/Targets.ts delete mode 100755 src/core/api/Exception.ts delete mode 100644 src/core/api/RequestBody.ts delete mode 100644 src/core/api/Validate.ts delete mode 100644 src/core/api/events.ts delete mode 100644 src/core/api/exceptionHandler.ts delete mode 100644 src/core/api/extendExpressResponse.ts delete mode 100644 src/core/database/BluePrint.ts delete mode 100644 src/core/database/Factory.ts delete mode 100644 src/core/database/ModelFactory.ts delete mode 100644 src/core/database/index.ts delete mode 100755 src/core/helpers/Environment.ts delete mode 100755 src/database/factories/index.ts delete mode 100644 src/database/migrations/20170120183349_create_users_table.ts delete mode 100644 src/database/seeds/create_users.ts delete mode 100644 test/unit/api/middlewares/AuthenticateMiddleware.test.ts delete mode 100644 test/unit/api/middlewares/PopulateUserMiddleware.test.ts delete mode 100644 test/unit/core/Environment.test.ts delete mode 100644 test/unit/core/api/Exception.test.ts delete mode 100644 test/unit/core/api/RequestBody.test.ts delete mode 100644 test/unit/core/api/exceptionHandler.test.ts delete mode 100644 test/unit/core/api/extendExpressResponse.test.ts diff --git a/README.md b/README.md index de8af330..9f625fab 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,6 @@ All the templates for the commands are located in `src/console/templates`. * `npm run console make:listener ` - Generates a basic listener. * `npm run console make:exception ` - Generates a basic exception. * `npm run console make:validator ` - Generates a custom validator. -* `npm run console update:targets ` - Reads all the API files and generate a new `constants/Targets.ts` file out of it. **Example** ``` diff --git a/knexfile.ts b/knexfile.ts deleted file mode 100644 index e18a1a90..00000000 --- a/knexfile.ts +++ /dev/null @@ -1,7 +0,0 @@ -require('dotenv').config(); - -/** - * This is the database configuration for the migrations and - * the seeders. - */ -module.exports = require('./src/config/Database').DatabaseConfig; diff --git a/package-scripts.js b/package-scripts.js index cf7f22e4..fb13abcf 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -125,7 +125,7 @@ module.exports = { }, /** * This our scaffold api - * @example > nps "console update:targets" + * @example > nps "console make:controller" */ console: { default: { diff --git a/package.json b/package.json index ef4599c6..6dd2b947 100644 --- a/package.json +++ b/package.json @@ -42,67 +42,36 @@ "dependencies": { "@types/bluebird": "^3.5.18", "@types/body-parser": "^1.16.7", - "@types/bookshelf": "^0.9.6", - "@types/chalk": "^2.2.0", - "@types/commander": "^2.11.0", "@types/cors": "^2.8.1", "@types/dotenv": "^4.0.2", "@types/express": "^4.0.39", - "@types/faker": "^4.1.1", - "@types/glob": "^5.0.33", - "@types/handlebars": "^4.0.36", "@types/helmet": "^0.0.37", - "@types/inquirer": "^0.0.35", "@types/jest": "^21.1.5", - "@types/jsonwebtoken": "^7.2.3", - "@types/knex": "^0.0.64", "@types/lodash": "^4.14.80", "@types/morgan": "^1.7.35", - "@types/pluralize": "^0.0.28", "@types/reflect-metadata": "0.0.5", - "@types/request": "^2.0.7", - "@types/request-promise": "^4.1.39", "@types/serve-favicon": "^2.2.29", "@types/uuid": "^3.4.3", "@types/winston": "^2.3.7", "body-parser": "^1.18.2", - "bookshelf": "^0.10.4", - "bookshelf-camelcase": "^2.0.1", - "chalk": "^2.3.0", "class-validator": "^0.7.3", - "commander": "^2.11.0", "compression": "^1.7.1", - "copyfiles": "^1.2.0", "cors": "^2.8.4", "dotenv": "^4.0.0", "express": "^4.16.2", - "express-basic-auth": "^1.1.3", - "express-status-monitor": "^1.0.1", - "faker": "^4.1.0", "figlet": "^1.2.0", - "glob": "^7.1.2", - "handlebars": "^4.0.11", "helmet": "^3.9.0", - "inquirer": "^3.3.0", - "inversify": "^4.5.0", - "inversify-express-utils": "^4.1.0", - "jsonwebtoken": "^8.1.0", - "knex": "^0.13.0", "lodash": "^4.17.4", "morgan": "^1.9.0", - "mysql": "^2.15.0", "nodemon": "^1.12.1", "path": "^0.12.7", - "pluralize": "^7.0.0", "reflect-metadata": "^0.1.10", - "request": "^2.83.0", - "request-promise": "^4.2.2", + "routing-controllers": "^0.7.6", "serve-favicon": "^2.4.5", - "swagger-jsdoc": "^1.9.7", - "swagger-ui-express": "^2.0.9", "trash-cli": "^1.4.0", "ts-node": "^3.3.0", "tslint": "^5.8.0", + "typedi": "^0.5.2", "typescript": "^2.6.1", "uuid": "^3.1.0", "wait-on": "^2.0.2", diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts deleted file mode 100644 index 449fbe59..00000000 --- a/src/api/controllers/UserController.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * UserController - * ---------------------------------------- - * - * This controller is in charge of the user resource and should - * provide all crud actions. - */ - -import { inject, named } from 'inversify'; -import { controller, httpGet, httpPost, httpPut, httpDelete, response, request, requestBody, requestParam } from 'inversify-express-utils'; -import { app } from '../../app'; -import { Types, Targets } from '../../constants'; -import { UserService } from '../services/UserService'; - -// Get middlewares -const populateUser = app.IoC.getNamed(Types.Middleware, Targets.Middleware.PopulateUserMiddleware); -const authenticate = app.IoC.getNamed(Types.Middleware, Targets.Middleware.AuthenticateMiddleware); - - -@controller('/users', authenticate.use) -export class UserController { - - constructor( @inject(Types.Service) @named(Targets.Service.UserService) private userService: UserService) { } - - @httpGet('/') - public async findAll( @response() res: myExpress.Response): Promise { - const users = await this.userService.findAll(); - return res.found(users.toJSON()); - } - - @httpPost('/') - public async create( @response() res: myExpress.Response, @requestBody() body: any): Promise { - const user = await this.userService.create(body); - return res.created(user.toJSON()); - } - - @httpGet('/me', populateUser.use) - public async findMe( @request() req: myExpress.Request, @response() res: myExpress.Response): Promise { - return res.found(req.user); - } - - @httpGet('/:id') - public async findOne( @response() res: myExpress.Response, @requestParam('id') id: string): Promise { - const user = await this.userService.findOne(parseInt(id, 10)); - return res.found(user.toJSON()); - } - - @httpPut('/:id') - public async update( @response() res: myExpress.Response, @requestParam('id') id: string, @requestBody() body: any): Promise { - const user = await this.userService.update(parseInt(id, 10), body); - return res.updated(user.toJSON()); - } - - @httpDelete('/:id') - public async destroy( @response() res: myExpress.Response, @requestParam('id') id: string): Promise { - await this.userService.destroy(parseInt(id, 10)); - return res.destroyed(); - } - -} diff --git a/src/api/exceptions/DatabaseException.ts b/src/api/exceptions/DatabaseException.ts deleted file mode 100755 index 24393144..00000000 --- a/src/api/exceptions/DatabaseException.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * DatabaseException - * ---------------------------------------- - * - * This should be used for repository errors like - * entity with this id already exists and stuff like that. - */ - -import { Exception } from '../../core/api/Exception'; - - -export class DatabaseException extends Exception { - constructor(text: string, error: any) { - const value: string = error.stack.split('\n')[0]; - super(400, text, [ - value.substring(7) - ]); - } -} diff --git a/src/api/exceptions/NotFoundException.ts b/src/api/exceptions/NotFoundException.ts deleted file mode 100755 index 0b1d1430..00000000 --- a/src/api/exceptions/NotFoundException.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * NotFoundException - * ---------------------------------------- - * - * This should be used if a someone requests a - * entity with a id, but there is no entity with this id in the - * database, then we throw this exception. - */ - -import { Exception } from '../../core/api/Exception'; - - -export class NotFoundException extends Exception { - constructor(id?: number | string) { - super(404, `Entity with identifier ${id} does not exist`); - } -} diff --git a/src/api/exceptions/ValidationException.ts b/src/api/exceptions/ValidationException.ts deleted file mode 100755 index c77a3a1f..00000000 --- a/src/api/exceptions/ValidationException.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * ValidationException - * ---------------------------------------- - * - * This should be used when we validate - * the request payload, so we can response with a 400 (Bad Request) - */ - -import { Exception } from '../../core/api/Exception'; - - -export class ValidationException extends Exception { - constructor(text: string, errors: any) { - const info = errors.map((e) => ({ - property: e.property, - constraints: e.constraints - })); - super(400, text, info); - } -} diff --git a/src/api/listeners/user/UserAuthenticatedListener.ts b/src/api/listeners/user/UserAuthenticatedListener.ts deleted file mode 100644 index 21f04672..00000000 --- a/src/api/listeners/user/UserAuthenticatedListener.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { inject, named } from 'inversify'; -import { Types, Core } from '../../../constants'; -import { Logger as LoggerType } from '../../../core/Logger'; - - -export class UserAuthenticatedListener implements interfaces.Listener { - - public static Event = Symbol('UserAuthenticatedListener'); - - public log: LoggerType; - - constructor( - @inject(Types.Core) @named(Core.Logger) Logger: typeof LoggerType - ) { - this.log = new Logger(__filename); - } - - public act(user: any): void { - this.log.info('Receive event UserAuthenticatedListener', user); - } - -} diff --git a/src/api/listeners/user/UserCreatedListener.ts b/src/api/listeners/user/UserCreatedListener.ts deleted file mode 100644 index a926f121..00000000 --- a/src/api/listeners/user/UserCreatedListener.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { inject, named } from 'inversify'; -import { Types, Core } from '../../../constants'; -import { Logger as LoggerType } from '../../../core/Logger'; - - -export class UserCreatedListener implements interfaces.Listener { - - public static Event = Symbol('UserCreated'); - - public log: LoggerType; - - constructor( - @inject(Types.Core) @named(Core.Logger) Logger: typeof LoggerType - ) { - this.log = new Logger(__filename); - } - - public act(user: any): void { - this.log.info('Receive event UserCreated', user); - } - -} - diff --git a/src/api/middlewares/AuthenticateMiddleware.ts b/src/api/middlewares/AuthenticateMiddleware.ts deleted file mode 100644 index 4621d4b6..00000000 --- a/src/api/middlewares/AuthenticateMiddleware.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { inject, named } from 'inversify'; -import * as Request from 'request'; -import { Logger as LoggerType } from '../../core/Logger'; -import { Types, Core } from '../../constants'; -import { events } from '../../core/api/events'; -import { UserAuthenticatedListener } from '../listeners/user/UserAuthenticatedListener'; - - -export class AuthenticateMiddleware implements interfaces.Middleware { - - public log: LoggerType; - - constructor( - @inject(Types.Core) @named(Core.Logger) Logger: typeof LoggerType, - @inject(Types.Lib) @named('request') private request: typeof Request - ) { - this.log = new Logger(__filename); - } - - public use = (req: myExpress.Request, res: myExpress.Response, next: myExpress.NextFunction): void => { - const token = this.getToken(req); - - if (token === null) { - this.log.warn('No token given'); - return res.failed(403, 'You are not allowed to request this resource!'); - } - this.log.debug('Token is provided'); - - // Request user info at auth0 with the provided token - this.request({ - method: 'POST', - url: `${process.env.AUTH0_HOST}/tokeninfo`, - form: { - id_token: token - } - }, (error: any, response: Request.RequestResponse, body: any) => { - // Verify if the requests was successful and append user - // information to our extended express request object - if (!error && response.statusCode === 200) { - req.tokeninfo = JSON.parse(body); - this.log.info(`Retrieved user ${req.tokeninfo.email}`); - events.emit(UserAuthenticatedListener.Event, req.tokeninfo); - return next(); - } - - // Catch auth0 exception and return it as it is - this.log.warn(`Could not retrieve the user, because of`, body); - let statusCode = 401; - if (response && response.statusCode) { - statusCode = response.statusCode; - } else { - this.log.warn('It seems your oauth server is down!'); - } - res.failed(statusCode, body); - - }); - } - - private getToken(req: myExpress.Request): string | null { - const authorization = req.headers.authorization; - - // Retrieve the token form the Authorization header - if (authorization && authorization.split(' ')[0] === 'Bearer') { - return authorization.split(' ')[1]; - } - - // No token was provided by the client - return null; - } - -} diff --git a/src/api/middlewares/PopulateUserMiddleware.ts b/src/api/middlewares/PopulateUserMiddleware.ts deleted file mode 100644 index 1d028098..00000000 --- a/src/api/middlewares/PopulateUserMiddleware.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { inject, named } from 'inversify'; -import { Logger as LoggerType } from '../../core/Logger'; -import { UserService } from '../services/UserService'; -import { Types, Core, Targets } from '../../constants'; - - -export class PopulateUserMiddleware implements interfaces.Middleware { - - public log: LoggerType; - - constructor( - @inject(Types.Core) @named(Core.Logger) Logger: typeof LoggerType, - @inject(Types.Service) @named(Targets.Service.UserService) private userService: UserService - ) { - this.log = new Logger(__filename); - } - - public use = (req: myExpress.Request, res: myExpress.Response, next: myExpress.NextFunction): void => { - // Check if the authenticate middleware was successful - if (!req.tokeninfo || !req.tokeninfo.user_id) { - return res.failed(400, 'Missing token information!'); - } - // Find user from the token and store him in the request object - this.userService.findByUserId(req.tokeninfo.user_id) - .then((user) => { - req.user = user.toJSON(); - this.log.debug(`populated user with the id=${req.user.id} to the request object`); - next(); - }) - .catch((error) => { - this.log.warn(`could not populate user to the request object`); - next(error); - }); - } - -} diff --git a/src/api/models/User.ts b/src/api/models/User.ts deleted file mode 100644 index 8bda34d1..00000000 --- a/src/api/models/User.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * User Model - * ------------------------------ - */ - -import { Bookshelf } from '../../config/Database'; -import { Tables } from '../../constants'; - - -export class User extends Bookshelf.Model { - - public static async fetchById(id: number): Promise { - return User.where({ id }).fetch(); - } - - public static async fetchByUserId(userId: string): Promise { - return User.where({ auth_0_user_id: userId }).fetch(); - } - - /** - * Configurations - */ - public get tableName(): string { return Tables.Users; } - public get hasTimestamps(): boolean { return true; } - - /** - * Properties - */ - public get Id(): number { return this.get('id'); } - public set Id(value: number) { this.set('id', value); } - - public get FirstName(): string { return this.get('firstName'); } - public set FirstName(value: string) { this.set('firstName', value); } - - public get LastName(): string { return this.get('lastName'); } - public set LastName(value: string) { this.set('lastName', value); } - - public get Email(): string { return this.get('email'); } - public set Email(value: string) { this.set('email', value); } - - public get Picture(): string { return this.get('picture'); } - public set Picture(value: string) { this.set('picture', value); } - - public get Auth0UserId(): string { return this.get('auth0UserId'); } - public set Auth0UserId(value: string) { this.set('auth0UserId', value); } - - public get UpdatedAt(): Date { return this.get('updatedAt'); } - public set UpdatedAt(value: Date) { this.set('updatedAt', value); } - - public get CreatedAt(): Date { return this.get('createdAt'); } - public set CreatedAt(value: Date) { this.set('createdAt', value); } - - /** - * Helper methods - */ - public fullName(): string { - return this.FirstName + ' ' + this.LastName; - } - -} diff --git a/src/api/repositories/UserRepository.ts b/src/api/repositories/UserRepository.ts deleted file mode 100644 index ecdd168c..00000000 --- a/src/api/repositories/UserRepository.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * UserRepository - * ------------------------------ - */ - -import * as Bookshelf from 'bookshelf'; -import { inject, named } from 'inversify'; -import { Types, Targets } from '../../constants'; -import { User } from '../models/User'; -import { DatabaseException } from '../exceptions/DatabaseException'; -import { NotFoundException } from '../exceptions/NotFoundException'; - - -export class UserRepository { - - constructor( - @inject(Types.Model) @named(Targets.Model.User) public UserModel: typeof User - ) { } - - /** - * Retrieves all user data out of the database - * - * @static - * @returns {Promise>} - * - * @memberof UserRepository - */ - public async findAll(): Promise> { - return this.UserModel.fetchAll(); - } - - /** - * Retrieves one user entity of the database - * - * @static - * @param {number} id of the user - * @returns {Promise} - */ - public async findOne(id: number): Promise { - return this.UserModel.fetchById(id); - } - - /** - * Retrieves one user entity of the database - * - * @static - * @param {number} id of the user - * @returns {Promise} - */ - public async findByUserId(userId: string): Promise { - return this.UserModel.fetchByUserId(userId); - } - - /** - * Creates a new user entity in the database and returns - * the new created entity - * - * @static - * @param {*} data is the new user - * @returns {Promise} - */ - public async create(data: any): Promise { - const user = this.UserModel.forge(data); - try { - const createdUser = await user.save(); - return this.UserModel.fetchById(createdUser.id); - } catch (error) { - throw new DatabaseException('Could not create the user!', error); - } - } - - /** - * Updates a already existing entity and returns the new one - * - * @static - * @param {number} id - * @param {*} data - * @returns {Promise} - */ - public async update(id: number, data: any): Promise { - const user = this.UserModel.forge({ id }); - try { - const updatedUser = await user.save(data, { patch: true }); - return this.UserModel.fetchById(updatedUser.id); - - } catch (error) { - throw new DatabaseException('Could not update the user!', error); - } - } - - /** - * Removes a entity in the database, but if there is not user - * with the given id, we will throw a Not-Found exception - * - * @static - * @param {number} id - * @returns {Promise} - */ - public async destroy(id: number): Promise { - let user = this.UserModel.forge({ id }); - try { - user = await user.fetch({ require: true }); - } catch (error) { - throw new NotFoundException(id); - } - - try { - await user.destroy(); - return; - } catch (error) { - throw new DatabaseException('Could not delete the user!', error); - } - } - -} diff --git a/src/api/requests/user/UserCreateRequest.ts b/src/api/requests/user/UserCreateRequest.ts deleted file mode 100644 index 1d552fa7..00000000 --- a/src/api/requests/user/UserCreateRequest.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { IsEmail, IsNotEmpty, Validate } from 'class-validator'; -import { RequestBody } from '../../../core/api/RequestBody'; -import { EndsWithValidator } from '../../Validators/EndsWithValidator'; - -/** - * This class is used for create request. Create a new instance - * with the json body and than call .validate() to check if - * all criteria are given - * - * @export - * @class UserCreateRequest - * @extends {RequestBody} - */ -export class UserCreateRequest extends RequestBody { - - @IsNotEmpty() - public firstName: string; - - @IsNotEmpty() - public lastName: string; - - @IsNotEmpty() - @IsEmail() - @Validate(EndsWithValidator, ['@gmail.com', '@w3tec.ch']) - public email: string; - - public picture: string; - public auth0UserId: string; - -} - diff --git a/src/api/requests/user/UserUpdateRequest.ts b/src/api/requests/user/UserUpdateRequest.ts deleted file mode 100644 index 7042d48d..00000000 --- a/src/api/requests/user/UserUpdateRequest.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { IsEmail, IsNotEmpty, Validate } from 'class-validator'; -import { RequestBody } from '../../../core/api/RequestBody'; -import { EndsWithValidator } from '../../Validators/EndsWithValidator'; - -/** - * This class is used for update request. Create a new instance - * with the json body and than call .validate() to check if - * all criteria are given - * - * @export - * @class UserUpdateRequest - * @extends {RequestBody} - */ -export class UserUpdateRequest extends RequestBody { - - @IsNotEmpty() - public firstName: string; - - @IsNotEmpty() - public lastName: string; - - @IsEmail() - @Validate(EndsWithValidator, ['@gmail.com', '@w3tec.ch']) - public email: string; - - @IsNotEmpty() - public picture: string; - - @IsNotEmpty() - public auth0UserId: string; - - /** - * We override the validate method so we can skip the missing - * properties. - */ - public validate(): Promise { - return super.validate(true); - } - -} - diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts deleted file mode 100644 index fd743a54..00000000 --- a/src/api/services/UserService.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * UserService - * ------------------------------ - * - * This service is here to validate and call the repository layer for - * database actions. Furthermore you should throw events here if - * necessary. - */ - -import * as Bookshelf from 'bookshelf'; -import { inject, named } from 'inversify'; -import { Types, Core, Targets } from '../../constants'; -import { Logger as LoggerType } from '../../core/Logger'; -import { EventEmitter } from '../../core/api/events'; -import { validate, request } from '../../core/api/Validate'; -import { NotFoundException } from '../exceptions/NotFoundException'; -import { UserCreateRequest } from '../requests/user/UserCreateRequest'; -import { UserUpdateRequest } from '../requests/user/UserUpdateRequest'; -import { UserRepository } from '../repositories/UserRepository'; -import { User } from '../models/User'; -import { UserCreatedListener } from '../listeners/user/UserCreatedListener'; - - -export class UserService { - - private log: LoggerType; - - constructor( - @inject(Types.Repository) @named(Targets.Repository.UserRepository) public userRepo: UserRepository, - @inject(Types.Core) @named(Core.Logger) public Logger: typeof LoggerType, - @inject(Types.Core) @named(Core.Events) public events: EventEmitter - ) { - this.log = new Logger(__filename); - } - - /** - * This returns all user database objects - */ - public findAll(): Promise> { - return this.userRepo.findAll(); - } - - /** - * Returns the user with the given id or throws a Not-Found exception - * - * @param {number} id of the user - * @returns {Promise} - */ - public async findOne(id: number): Promise { - const user = await this.userRepo.findOne(id); - if (user === null) { - this.log.warn(`User with the id=${id} was not found!`); - throw new NotFoundException(id); - } - return user; - } - - /** - * Returns the user with the given user_id or throws a Not-Found exception - * - * @param {number} id of the user - * @returns {Promise} - */ - public async findByUserId(userId: string): Promise { - const user = await this.userRepo.findByUserId(userId); - if (user === null) { - this.log.warn(`User with the userId=${userId} was not found!`); - throw new NotFoundException(userId); - } - return user; - } - - /** - * We will validate the data and create a new user and - * return it, so the client get its new id - * - * @param {*} data is the json body of the request - * @returns {Promise} - */ - @validate() - public async create( @request(UserCreateRequest) data: any): Promise { - // If the request body was valid we will create the user - const user = await this.userRepo.create(data); - this.events.emit(UserCreatedListener.Event, user.toJSON()); - return user; - } - - /** - * We will validate the data and update a user with the given id and - * return the new user - * - * @param {number} id of the user - * @param {*} newUser is the json body of the request - * @returns {Promise} - */ - @validate() - public async update(id: number, @request(UserUpdateRequest) newUser: any): Promise { - // Find or fail - const user = await this.findOne(id); - // Set new values - user.FirstName = newUser.firstName; - user.LastName = newUser.lastName; - user.Email = newUser.email; - // Update user record - const updatedUser = await this.userRepo.update(id, user.toJSON()); - return updatedUser; - } - - /** - * This will just delete a user - * - * @param {number} id of the user - * @returns {Promise} - */ - public destroy(id: number): Promise { - return this.userRepo.destroy(id); - } - -} diff --git a/src/api/validators/EndsWithValidator.ts b/src/api/validators/EndsWithValidator.ts deleted file mode 100644 index da51ad1c..00000000 --- a/src/api/validators/EndsWithValidator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as _ from 'lodash'; -import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; - - -@ValidatorConstraint({ name: 'endsWith', async: false }) -export class EndsWithValidator implements ValidatorConstraintInterface { - - public validate(text: string, args: ValidationArguments): boolean { - for (const ending of args.constraints) { - if (_.endsWith(text, ending)) { - return true; - } - } - return false; - } - - public defaultMessage(args: ValidationArguments): string { - return 'Incorrect suffix'; - } - -} diff --git a/src/app.ts b/src/app.ts index 5da4e7a4..049be2c7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,14 +11,6 @@ */ import 'reflect-metadata'; -import { App } from './core/App'; -import { CustomHeaderConfig } from './config/CustomHeaderConfig'; -export const app = new App(); - -// Here you can add more custom configurations -app.configure(new CustomHeaderConfig()); - -// Launch the server with all his awesome features. -app.bootstrap(); +console.log('adsf'); diff --git a/src/config/AppConfig.ts b/src/config/AppConfig.ts deleted file mode 100644 index 4bc0c928..00000000 --- a/src/config/AppConfig.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * APPLICATION CONFIGURATION - * ---------------------------------------- - * - * This is the place to add any other express module and register - * all your custom middlewares and routes. - */ - -import * as path from 'path'; -import * as cors from 'cors'; -import * as morgan from 'morgan'; -import * as helmet from 'helmet'; -import * as express from 'express'; -import * as favicon from 'serve-favicon'; -import * as bodyParser from 'body-parser'; -import * as compression from 'compression'; -import { Logger } from '../core/Logger'; -import { App, Configurable } from '../core/App'; - -export class AppConfig implements Configurable { - public configure(app: App): void { - - const logger = new Logger(); - - app.Express - // Enabling the cors headers - .options('*', cors()) - .use(cors()) - - // Helmet helps you secure your Express apps by setting various HTTP headers. It's not a silver bullet, but it can help! - .use(helmet()) - .use(helmet.noCache()) - .use(helmet.hsts({ - maxAge: 31536000, - includeSubdomains: true - })) - - // Compress response bodies for all request that traverse through the middleware - .use(compression()) - - // Parse incoming request bodies in a middleware before your handlers, available under the req.body property. - .use(bodyParser.json()) - .use(bodyParser.urlencoded({ - extended: true - })) - - // Serve static filles like images from the public folder - .use(express.static(path.join(__dirname, '..', 'public'), { maxAge: 31557600000 })) - - // A favicon is a visual cue that client software, like browsers, use to identify a site - .use(favicon(path.join(__dirname, '..', 'public', 'favicon.ico'))) - - // HTTP request logger middleware for node.js - .use(morgan('dev', { - stream: { - write: logger.info.bind(logger) - } - })); - } -} diff --git a/src/config/CustomHeaderConfig.ts b/src/config/CustomHeaderConfig.ts deleted file mode 100644 index b9d445f9..00000000 --- a/src/config/CustomHeaderConfig.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * config.Custom - * ------------------------------------ - * - * Define all log adapters for this application and chose one. - */ - -import * as express from 'express'; -import * as uuid from 'uuid'; - -import { Logger } from '../core/Logger'; -import { App, Configurable } from '../core/App'; -import { Environment } from '../core/helpers/Environment'; - - -export class CustomHeaderConfig implements Configurable { - - private log = new Logger(__filename); - - public configure(app: App): void { - this.log.debug('Add custom headers'); - - app.Express.use((req: express.Request, res: express.Response, next: express.NextFunction) => { - res.setHeader('X-API-VERSION', Environment.getPkg().version); - res.setHeader('X-Request-Id', uuid.v4()); - next(); - }); - - } -} - diff --git a/src/config/Database.ts b/src/config/Database.ts deleted file mode 100755 index eb7d95a1..00000000 --- a/src/config/Database.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * config.Database - * ------------------------------------ - * - * Here we configure our database connection and - * our ORM 'bookshelf'. - * - * Here would be the place to add more bookshelf plugins. - */ - -import * as knex from 'knex'; -import * as bookshelf from 'bookshelf'; - - -export const DatabaseConfig = { - client: process.env.DB_CLIENT, - connection: process.env.DB_CONNECTION, - pool: { - min: parseInt(process.env.DB_POOL_MIN, 10), - max: parseInt(process.env.DB_POOL_MAX, 10) - }, - migrations: { - directory: process.env.DB_MIGRATION_DIR, - tableName: process.env.DB_MIGRATION_TABLE - }, - seeds: { - directory: process.env.DB_SEEDS_DIR - } -}; - -export const Knex = (): knex => knex(DatabaseConfig); - -export const Bookshelf: bookshelf = bookshelf(Knex() as any); -Bookshelf.plugin(['bookshelf-camelcase']); diff --git a/src/config/IocConfig.ts b/src/config/IocConfig.ts deleted file mode 100644 index 09b0a187..00000000 --- a/src/config/IocConfig.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * IOC - CONTAINER - * ---------------------------------------- - * - * Bind every controller and service to the ioc container. All controllers - * will then be bonded to the express structure with their defined routes. - */ - -import { Container, decorate, injectable } from 'inversify'; -import { IoC } from '../core/IoC'; -import { Types } from '../constants'; - -import * as request from 'request'; - - -export class IocConfig { - public configure(ioc: IoC): void { - /** - * Here you can bind all your third-party libraries like - * request, lodash and so on. Those will be bound before - * everything else. - */ - ioc.configureLib((container: Container) => { - - decorate(injectable(), request); - - container - .bind(Types.Lib) - .toConstantValue(request) - .whenTargetNamed('request'); - - return container; - }); - - /** - * Bind custom classes here. This will be bound at the end - */ - ioc.configure((container: Container) => { - - // Add your custom class here - - return container; - }); - } -} - - diff --git a/src/config/LoggerConfig.ts b/src/config/LoggerConfig.ts deleted file mode 100644 index 320674fe..00000000 --- a/src/config/LoggerConfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * config.Logger - * ------------------------------------ - * - * Define all log adapters for this application and chose one. - */ - -import { Logger } from '../core/Logger'; -import { WinstonAdapter } from './logger/WinstonAdapter'; -import { Configurable } from '../core/App'; - - -export class LoggerConfig implements Configurable { - public configure(): void { - Logger.addAdapter('winston', WinstonAdapter); - Logger.setAdapter(process.env.LOG_ADAPTER); - } -} diff --git a/src/config/logger/WinstonAdapter.ts b/src/config/logger/WinstonAdapter.ts deleted file mode 100644 index a58076ec..00000000 --- a/src/config/logger/WinstonAdapter.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * core.log.WinstonAdapter - * ------------------------------------------------ - * - * This adapter uses the winston module to print all logs - * to the terminal. - * - * Remote logging can be added here to this adapter. - */ - -import * as winston from 'winston'; -import { Environment } from '../../core/helpers/Environment'; - - -export class WinstonAdapter implements interfaces.LoggerAdapter { - - private logger: winston.LoggerInstance; - - constructor(private scope: string) { - this.logger = new winston.Logger({ - transports: [ - new winston.transports.Console({ - level: process.env.LOG_LEVEL, - timestamp: Environment.isProduction(), - handleExceptions: Environment.isProduction(), - json: Environment.isProduction(), - colorize: !Environment.isProduction() - }) - ], - exitOnError: false - }); - } - - public debug(message: string, ...args: any[]): void { - this.logger.debug(`${this.formatScope()} ${message}`, this.parseArgs(args)); - } - - public info(message: string, ...args: any[]): void { - this.logger.info(`${this.formatScope()} ${message}`, this.parseArgs(args)); - } - - public warn(message: string, ...args: any[]): void { - this.logger.warn(`${this.formatScope()} ${message}`, this.parseArgs(args)); - } - - public error(message: string, ...args: any[]): void { - this.logger.error(`${this.formatScope()} ${message}`, this.parseArgs(args)); - } - - private parseArgs(args: any[]): any { - return (args && args[0] && args[0].length > 0) ? args : ''; - } - - private formatScope(): string { - return `[${this.scope}]`; - } - -} diff --git a/src/console/DatabaseResetCommand.ts b/src/console/DatabaseResetCommand.ts deleted file mode 100644 index 2f56ba15..00000000 --- a/src/console/DatabaseResetCommand.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Logger } from '../core/Logger'; - -import * as Knex from 'knex'; -import { AbstractCommand } from './lib/AbstractCommand'; -import { DatabaseConfig } from '../config/Database'; - -const log = new Logger(__filename); - - -/** - * DatabaseResetCommand rollback all current migrations and - * then migrate to the latest one. - * - * @export - * @class DatabaseResetCommand - */ -export class DatabaseResetCommand extends AbstractCommand { - - public static command = 'db:reset'; - public static description = 'Reverse all current migrations and migrate to latest.'; - - public async run(): Promise { - const knex = Knex(DatabaseConfig as Knex.Config); - - const migrate: any = knex.migrate; - - // Force unlock in case of bad state - await migrate.forceFreeMigrationsLock(); - - // Get completed migrations - log.info('Get completed migrations'); - const completedMigrations = await migrate._listCompleted(); - - // Rollback migrations - log.info('Rollback migrations'); - await migrate._waterfallBatch(0, completedMigrations.reverse(), 'down'); - - // Migrate to latest - log.info('Migrate to latest'); - await migrate.latest(); - - // Close connection to the database - await knex.destroy(); - log.info('Done'); - } - -} - - diff --git a/src/console/MakeApiTestCommand.ts b/src/console/MakeApiTestCommand.ts deleted file mode 100644 index 49fe7f6d..00000000 --- a/src/console/MakeApiTestCommand.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * MakeSeedCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeApiTestCommand extends AbstractMakeCommand { - - public static command = 'make:api-test'; - public static description = 'Generate new api test'; - - public target = '/e2e'; - public type = 'API Test'; - public suffix = ''; - public template = 'api-test.hbs'; - public updateTargets = false; - public isTest = true; - -} diff --git a/src/console/MakeControllerCommand.ts b/src/console/MakeControllerCommand.ts deleted file mode 100644 index b805c1d8..00000000 --- a/src/console/MakeControllerCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * MakeControllerCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeControllerCommand extends AbstractMakeCommand { - - public static command = 'make:controller'; - public static description = 'Generate new controller'; - - public type = 'Controller'; - public suffix = 'Controller'; - public template = 'controller.hbs'; - public target = 'api/controllers'; - -} diff --git a/src/console/MakeExceptionCommand.ts b/src/console/MakeExceptionCommand.ts deleted file mode 100644 index ff364c83..00000000 --- a/src/console/MakeExceptionCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * MakeExceptionCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeExceptionCommand extends AbstractMakeCommand { - - public static command = 'make:exception'; - public static description = 'Generate new exception'; - - public type = 'Exception'; - public suffix = 'Exception'; - public template = 'exception.hbs'; - public target = 'api/exceptions'; - -} diff --git a/src/console/MakeListenerCommand.ts b/src/console/MakeListenerCommand.ts deleted file mode 100644 index 440ccd1a..00000000 --- a/src/console/MakeListenerCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * MakeListenerCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeListenerCommand extends AbstractMakeCommand { - - public static command = 'make:listener'; - public static description = 'Generate new listener'; - - public type = 'Listener'; - public suffix = 'Listener'; - public template = 'listener.hbs'; - public target = 'api/listeners'; - -} diff --git a/src/console/MakeMiddlewareCommand.ts b/src/console/MakeMiddlewareCommand.ts deleted file mode 100644 index 22baf812..00000000 --- a/src/console/MakeMiddlewareCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * MakeMiddlewareCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeMiddlewareCommand extends AbstractMakeCommand { - - public static command = 'make:middleware'; - public static description = 'Generate new middleware'; - - public type = 'Middleware'; - public suffix = 'Middleware'; - public template = 'middleware.hbs'; - public target = 'api/middlewares'; - -} diff --git a/src/console/MakeMigrationCommand.ts b/src/console/MakeMigrationCommand.ts deleted file mode 100644 index 1c366fb4..00000000 --- a/src/console/MakeMigrationCommand.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * MakeMigrationCommand - * ------------------------------------- - * - */ -import * as _ from 'lodash'; -import * as inquirer from 'inquirer'; -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; -import { inputIsRequired } from './lib/utils'; - - -export class MakeMigrationCommand extends AbstractMakeCommand { - - public static command = 'make:migration'; - public static description = 'Generate new migration'; - - public target = 'database/migrations'; - public type = 'Migration'; - public suffix = ''; - public template = 'migration.hbs'; - public updateTargets = false; - - public async run(): Promise { - if (this.context && this.context.tableName) { - this.context.name = `${this.getTimestamp()}_create_${_.snakeCase(this.context.tableName)}_table`; - - } else { - const prompt = inquirer.createPromptModule(); - const prompts = await prompt([ - { - type: 'input', - name: 'name', - message: `Enter the name of the ${this.type}:`, - filter: v => _.snakeCase(v), - validate: inputIsRequired - } - ]); - this.context = { ...(this.context || {}), ...prompts }; - this.context.name = `${this.getTimestamp()}_${prompts.name}`; - } - - } - - private getTimestamp(): string { - const today = new Date(); - const formatNumber = (n: number) => (n < 10) ? `0${n}` : `${n}`; - let timestamp = `${today.getFullYear()}`; - timestamp += `${formatNumber(today.getMonth())}`; - timestamp += `${formatNumber(today.getDay())}`; - timestamp += `${formatNumber(today.getHours())}`; - timestamp += `${formatNumber(today.getMinutes())}`; - timestamp += `${formatNumber(today.getSeconds())}`; - return timestamp; - } - -} diff --git a/src/console/MakeModelCommand.ts b/src/console/MakeModelCommand.ts deleted file mode 100644 index e3f3c248..00000000 --- a/src/console/MakeModelCommand.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * MakeModelCommand - * ------------------------------------- - * - */ -import * as _ from 'lodash'; -import * as inquirer from 'inquirer'; -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; -import { MakeMigrationCommand } from './MakeMigrationCommand'; -import { askProperties, buildFilePath, existsFile } from './lib/utils'; -import { writeTemplate } from './lib/template'; - - -export class MakeModelCommand extends AbstractMakeCommand { - - public static command = 'make:model'; - public static description = 'Generate new model'; - - public type = 'Model'; - public suffix = ''; - public template = 'model.hbs'; - public target = 'api/models'; - public makeMigrationCommand: MakeMigrationCommand; - - public async run(): Promise { - await super.run(); - const metaData = await this.askMetaData(this.context); - this.context = { ...(this.context || {}), ...metaData }; - - if (this.context.hasProperties && !this.context.properties) { - this.context.properties = await askProperties(this.context); - } - - if (this.context.hasMigration) { - this.makeMigrationCommand = new MakeMigrationCommand(this.context); - await this.makeMigrationCommand.run(); - } - } - - public async write(): Promise { - // Create migration file - if (this.context.hasMigration) { - await this.makeMigrationCommand.write(); - } - - // Create model - await super.write(); - - // Create interface for this resource object - const filePath = buildFilePath('types/resources', this.context.name.camelCase, false, '.d.ts'); - await existsFile(filePath, true); - await writeTemplate('resource.hbs', filePath, this.context); - } - - private async askMetaData(context: any): Promise { - const prompt = inquirer.createPromptModule(); - const prompts = await prompt([ - { - type: 'input', - name: 'tableName', - message: 'Enter the table-name:', - filter: (value: any) => _.snakeCase(value), - validate: (value: any) => !!value - }, { - type: 'confirm', - name: 'hasTimestamps', - message: 'Has timestamps?', - default: true - }, { - type: 'confirm', - name: 'hasMigration', - message: 'Add migration?', - default: true - }, { - type: 'confirm', - name: 'hasProperties', - message: 'Do you want to add some properties?', - default: true, - when: () => !this.context.properties - } - ]); - return _.assign(context, prompts); - } - -} diff --git a/src/console/MakeRepoCommand.ts b/src/console/MakeRepoCommand.ts deleted file mode 100644 index 1f8f85a7..00000000 --- a/src/console/MakeRepoCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * MakeRepoCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeRepoCommand extends AbstractMakeCommand { - - public static command = 'make:repo'; - public static description = 'Generate new repository'; - - public type = 'Repository'; - public suffix = 'Repository'; - public template = 'repository.hbs'; - public target = 'api/repositories'; - -} diff --git a/src/console/MakeRequestCommand.ts b/src/console/MakeRequestCommand.ts deleted file mode 100644 index 406e69d6..00000000 --- a/src/console/MakeRequestCommand.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * MakeRequestCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeRequestCommand extends AbstractMakeCommand { - - public static command = 'make:request'; - public static description = 'Generate new request'; - - public type = 'Request'; - public suffix = 'Request'; - public prefix = ''; - public template = 'request.hbs'; - public target = 'api/requests'; - - constructor(context: any, prefix?: string) { - super(context); - this.prefix = prefix || ''; - } - -} diff --git a/src/console/MakeResourceCommand.ts b/src/console/MakeResourceCommand.ts deleted file mode 100644 index 49e6c249..00000000 --- a/src/console/MakeResourceCommand.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * MakeResourceCommand - * ------------------------------------- - * - */ -import { askProperties } from './lib/utils'; -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; -import { MakeModelCommand } from './MakeModelCommand'; -import { MakeRepoCommand } from './MakeRepoCommand'; -import { MakeServiceCommand } from './MakeServiceCommand'; -import { MakeControllerCommand } from './MakeControllerCommand'; -import { MakeRequestCommand } from './MakeRequestCommand'; -import { MakeApiTestCommand } from './MakeApiTestCommand'; - - -export class MakeResourceCommand extends AbstractMakeCommand { - - public static command = 'make:resource'; - public static description = 'Generate a new resource'; - - public type = 'Resource'; - public suffix = ''; - public prefix = ''; - public context: any; - public properties: any[]; - - public makeModelCommand: AbstractMakeCommand; - public makeRepoCommand: AbstractMakeCommand; - public makeServiceCommand: AbstractMakeCommand; - public makeControllerCommand: AbstractMakeCommand; - public makeCreateRequestCommand: MakeRequestCommand; - public makeUpdateRequestCommand: MakeRequestCommand; - public makeApiTestCommand: MakeApiTestCommand; - - public async run(): Promise { - this.context = await this.askFileName(this.context, this.type, this.suffix, this.prefix); - this.context.properties = await askProperties(this.context.name); - this.context.hasProperties = true; - this.context.isResourceTemplate = true; - - // Get commands - this.makeModelCommand = new MakeModelCommand(this.context); - this.makeRepoCommand = new MakeRepoCommand(this.context); - this.makeServiceCommand = new MakeServiceCommand(this.context); - this.makeControllerCommand = new MakeControllerCommand(this.context); - this.makeCreateRequestCommand = new MakeRequestCommand(this.context, 'Create'); - this.makeUpdateRequestCommand = new MakeRequestCommand(this.context, 'Update'); - this.makeApiTestCommand = new MakeApiTestCommand(this.context); - - // Ask all meta-data - await this.makeModelCommand.run(); - await this.makeRepoCommand.run(); - await this.makeServiceCommand.run(); - await this.makeControllerCommand.run(); - await this.makeCreateRequestCommand.run(); - await this.makeUpdateRequestCommand.run(); - await this.makeApiTestCommand.run(); - } - - public async write(): Promise { - await this.makeModelCommand.write(); - await this.makeRepoCommand.write(); - await this.makeServiceCommand.write(); - await this.makeControllerCommand.write(); - await this.makeCreateRequestCommand.write(); - await this.makeUpdateRequestCommand.write(); - await this.makeApiTestCommand.write(); - } - -} diff --git a/src/console/MakeSeedCommand.ts b/src/console/MakeSeedCommand.ts deleted file mode 100644 index 0f579481..00000000 --- a/src/console/MakeSeedCommand.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * MakeSeedCommand - * ------------------------------------- - * - */ -import * as _ from 'lodash'; -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - -export class MakeSeedCommand extends AbstractMakeCommand { - - public static command = 'make:seed'; - public static description = 'Generate new seed'; - - public target = 'database/seeds'; - public type = 'Seed'; - public suffix = ''; - public template = 'seed.hbs'; - public updateTargets = false; - - public parseName(suffix: string = '', prefix: string = ''): (name: string) => string { - return (name: string) => { - return _.snakeCase(name); - }; - } - -} diff --git a/src/console/MakeServiceCommand.ts b/src/console/MakeServiceCommand.ts deleted file mode 100644 index 2c75bd1a..00000000 --- a/src/console/MakeServiceCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * MakeServiceCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeServiceCommand extends AbstractMakeCommand { - - public static command = 'make:service'; - public static description = 'Generate new service'; - - public type = 'Service'; - public suffix = 'Service'; - public template = 'service.hbs'; - public target = 'api/services'; - -} diff --git a/src/console/MakeValidatorCommand.ts b/src/console/MakeValidatorCommand.ts deleted file mode 100644 index 918dab6d..00000000 --- a/src/console/MakeValidatorCommand.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * MakeValidatorCommand - * ------------------------------------- - * - */ -import { AbstractMakeCommand } from './lib/AbstractMakeCommand'; - - -export class MakeValidatorCommand extends AbstractMakeCommand { - - public static command = 'make:validator'; - public static description = 'Generate new validator'; - - public type = 'Validator'; - public suffix = 'Validator'; - public template = 'validator.hbs'; - public target = 'api/validators'; - public updateTargets = false; - -} diff --git a/src/console/UpdateTargetsCommand.ts b/src/console/UpdateTargetsCommand.ts deleted file mode 100644 index 4dc0043a..00000000 --- a/src/console/UpdateTargetsCommand.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * UpdateTargetsCommand - * ------------------------------------- - * - * This script reads all the api files and generate the - * needed ioc targets file. - */ -import * as path from 'path'; -import * as _ from 'lodash'; -import * as glob from 'glob'; -import * as handlebars from 'handlebars'; -import { AbstractCommand } from './lib/AbstractCommand'; -import { writeTemplate } from './lib/template'; -import { existsFile } from './lib/utils'; - - -export class UpdateTargetsCommand extends AbstractCommand { - - public static command = 'update:targets'; - public static description = 'Generate new controller'; - - public template = 'targets.hbs'; - public targetFile = 'Targets.ts'; - - public async run(): Promise { - const files = await this.getFiles(); - - let context = {}; - files.forEach((filePath) => { - const obj = this.divideFilePath(filePath); - const tmpContext = {}; - tmpContext[obj.key] = this.parseFilePath(obj.path); - context = _.merge(context, tmpContext); - }); - - handlebars.registerHelper('object', (c) => { - let json = JSON.stringify(c, null, 4) || '{}'; - let jsonLines = json.split('\n'); - jsonLines = jsonLines.map(line => ` ${line}`); - json = jsonLines.join('\n'); - return json.replace(/\"([^(\")"]+)\":/g, '$1:').replace(/"/g, '\''); - }); - - const filepath = path.join(__dirname.replace('console', 'constants'), this.targetFile); - await existsFile(filepath, true); - await writeTemplate(this.template, filepath, context); - - } - - private async getFiles(): Promise { - return new Promise((resolve, reject) => { - const filepath = path.normalize(__dirname.replace('console', 'api')); - glob(`${path.join(filepath, '**', '*.ts')}`, (err: any, files: string[]) => { - if (err) { - return reject(err); - } - files = files - .map(f => path.normalize(f)) - .map((f) => f.replace(filepath + path.sep, '')) - .map((f) => f.replace('.ts', '')); - resolve(files); - }); - }); - } - - private divideFilePath(filePath: string): any { - const fs = filePath.split(path.sep); - const key = fs[0]; - fs.splice(0, 1); - return { - key, - path: fs.join(path.sep) - }; - } - - private parseFilePath(filePath: string): any { - if (filePath.indexOf(path.sep) !== -1) { - const obj = this.divideFilePath(filePath); - return { - [obj.key]: this.parseFilePath(obj.path) - }; - } else { - return { - [filePath]: filePath - }; - } - } - -} diff --git a/src/console/lib/AbstractCommand.ts b/src/console/lib/AbstractCommand.ts deleted file mode 100644 index b18fcc23..00000000 --- a/src/console/lib/AbstractCommand.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * AbstractCommand - * ------------------------------------- - * - */ -import * as _ from 'lodash'; - -export interface Command { - run(): Promise; -} - -export class AbstractCommand { - - public static command = 'make:command'; - public static description = 'description'; - - public static async action(command: Command): Promise { - try { - await command.run(); - process.exit(0); - } catch (e) { - process.exit(1); - } - } - - public context: any; - - constructor(context?: any) { - this.context = _.cloneDeep(context); - } - - public async run(): Promise { - console.log('You have to implement a run method!'); - } - -} diff --git a/src/console/lib/AbstractMakeCommand.ts b/src/console/lib/AbstractMakeCommand.ts deleted file mode 100644 index def8f216..00000000 --- a/src/console/lib/AbstractMakeCommand.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * AbstractMakeCommand - * ------------------------------------- - * - */ -import * as _ from 'lodash'; -import * as path from 'path'; -import * as inquirer from 'inquirer'; - -import { writeTemplate } from './template'; -import { existsFile, parseName, updateTargets, inputIsRequired } from './utils'; - -export interface MakeCommand { - context: any; - type: string; - suffix: string; - template: string; - target: string; - updateTargets: boolean; - - run(): Promise; - write(): Promise; -} - -export class AbstractMakeCommand { - - public static command = 'make:command'; - public static description = 'description'; - - public static async action(command: MakeCommand): Promise { - try { - await command.run(); - await command.write(); - if (command.updateTargets) { - await updateTargets(); - } - process.exit(0); - } catch (e) { - process.exit(1); - } - } - - public context: any; - public type = 'Type'; - public suffix = 'Suffix'; - public prefix = ''; - public template = 'template.hbs'; - public target = 'api/target/path'; - public updateTargets = true; - public isTest = false; - - constructor(context?: any) { - this.context = _.cloneDeep(context); - } - - public async run(): Promise { - this.context = await this.askFileName(this.context, this.type, this.suffix, this.prefix); - } - - public async write(): Promise { - const filePath = this.buildFilePath(this.target, this.context.name, this.isTest); - await existsFile(filePath, true, this.isTest); - this.context.name = parseName(this.context.name, this.suffix); - await writeTemplate(this.template, filePath, this.context); - } - - public buildFilePath = (targetPath: string, fileName: string, isTest = false, extension = '.ts') => { - if (isTest) { - return path.join(__dirname, `/../../../test${targetPath}`, `${fileName}${extension}`); - } else { - return path.join(__dirname, `/../../${targetPath}`, `${fileName}${extension}`); - } - } - - public parseName(suffix: string = '', prefix: string = ''): (name: string) => string { - return (name: string) => { - let ns = name.split('/'); - ns = ns.map((v) => _.camelCase(v)); - ns[ns.length - 1] = _.upperFirst(ns[ns.length - 1]); - return (ns.join('/')) + prefix + suffix; - }; - } - - public async askFileName(context: any, name: string, suffix: string, prefix: string): Promise { - const nameParser = this.parseName(suffix, prefix); - if (context === undefined || context.name === undefined) { - const prompt = inquirer.createPromptModule(); - context = await prompt([ - { - type: 'input', - name: 'name', - message: `Enter the name of the ${name}:`, - filter: nameParser, - validate: inputIsRequired - } - ]); - const amount = context.name.split('/').length - 1; - context.deepness = ''; - _.times(amount, () => context.deepness += '../'); - } else { - context.name = nameParser(context.name); - } - return context; - } - -} diff --git a/src/console/lib/console.ts b/src/console/lib/console.ts deleted file mode 100644 index e99d5220..00000000 --- a/src/console/lib/console.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * console.Commander - * ------------------------------------------------ - * - * Here you can define your console commands, so you are able - * to use them in the terminal with 'npm run console '. - * - * These console commands can also be accessed in the production - * environment. For example to import users. - */ - -import 'reflect-metadata'; -import * as glob from 'glob'; -import * as path from 'path'; -import * as commander from 'commander'; -import * as figlet from 'figlet'; -import chalk from 'chalk'; - -// It also loads the .env file into the 'process.env' variable. -import { config } from 'dotenv'; -config(); - -// Configures the logger -import { LoggerConfig } from '../../config/LoggerConfig'; -new LoggerConfig().configure(); - -figlet('console', (error: any, data: any) => { - console.log(chalk.blue(data)); - - // Find all command files - glob(path.join(__dirname, '../**/*Command.ts'), (err: any, matches: string[]) => { - if (err) { - console.log(err); - return; - } - - const files = matches - .filter(m => m.search(/\/lib/g) <= 0) - .map(m => ({ - path: m, - name: m.replace(__dirname.split(path.sep).join('/').replace('/lib', ''), '').replace('.ts', '').substring(1) - })); - - const commands = files.map(f => require(f.path)[f.name]); - const keys = commands.map(c => c.command); - const key = process.argv[2]; - - if (keys.indexOf(key) < 0 && key !== '--help') { - console.log(chalk.red('➜ ') + chalk.bold(`Command ${key} was not found!`)); - console.log(); - return; - } - - if (key !== '--help') { - console.log(chalk.green('➜ ') + chalk.bold(key)); - console.log(); - } - - commands.forEach((c) => { - commander - .command(c.command) - .description(c.description) - .action(() => c.action(new c())); - }); - - commander.parse(process.argv); - - }); -}); diff --git a/src/console/lib/template.ts b/src/console/lib/template.ts deleted file mode 100644 index 4de4ea6c..00000000 --- a/src/console/lib/template.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as mkdirp from 'mkdirp'; -import * as handlebars from 'handlebars'; - - -export const loadTemplate = async (file: string, stop: boolean = false): Promise => { - return new Promise((resolve, reject) => { - fs.readFile(path.join(__dirname, `../templates/${file}`), { encoding: 'utf-8' }, (err: any, content: any) => { - if (err) { - console.log(err); - if (stop) { - process.exit(1); - } - return reject(err); - } - resolve(content); - }); - }); -}; - -export const writeTemplate = async (tempFile: string, filePath: string, context: any): Promise => { - await syncFolder(filePath); - await syncTemplate(filePath, tempFile, context); - console.log('File created in: ' + filePath); -}; - -const syncFolder = (filePath: string) => { - return new Promise((resolve, reject) => { - mkdirp(path.dirname(filePath), (err) => { - if (err) { - if (stop) { - console.log(err); - process.exit(1); - } - return reject(err); - } - resolve(); - }); - }); -}; - -const syncTemplate = async (filePath: string, tempFile: string, context: any) => { - const template = await loadTemplate(tempFile); - const content = handlebars.compile(template)(context); - return new Promise((resolve, reject) => { - fs.writeFile(filePath, content, (err: any) => { - if (err) { - console.log(err); - return reject(err); - } - resolve(); - }); - - }); -}; diff --git a/src/console/lib/utils.ts b/src/console/lib/utils.ts deleted file mode 100644 index 6d33d344..00000000 --- a/src/console/lib/utils.ts +++ /dev/null @@ -1,151 +0,0 @@ -import * as fs from 'fs'; -import * as _ from 'lodash'; -import * as path from 'path'; -import * as inquirer from 'inquirer'; -import * as pluralize from 'pluralize'; -import { UpdateTargetsCommand } from '../UpdateTargetsCommand'; - - -export const parseName = (name: string, suffix: string) => ({ - camelCase: _.camelCase(removeSufix(suffix, name)), - snakeCase: _.snakeCase(removeSufix(suffix, name)), - capitalize: _.upperFirst(_.camelCase(removeSufix(suffix, name))), - lowerCase: _.lowerCase(removeSufix(suffix, name)), - kebabCase: _.kebabCase(removeSufix(suffix, name)), - pluralize: pluralize(_.kebabCase(removeSufix(suffix, name))), - normal: name -}); - -export const removeSufix = (suffix: string, value: string) => { - return value.replace(suffix, ''); -}; - -export const filterInput = (suffix: string, prefix = '') => (value: string) => { - if (value.indexOf('/') < 0) { - return value; - } - let vs = value.split('/'); - vs = vs.map((v) => _.camelCase(v)); - vs[vs.length - 1] = _.capitalize(vs[vs.length - 1]); - return (vs.join('/')) + prefix + suffix; -}; - -export const buildFilePath = (targetPath: string, fileName: string, isTest = false, extension = '.ts') => { - if (isTest) { - return path.join(__dirname, `/../../../test/${targetPath}`, `${fileName}.test${extension}`); - } else { - return path.join(__dirname, `/../../${targetPath}`, `${fileName}${extension}`); - } -}; - -export const inputIsRequired = (value: any) => !!value; - -export const existsFile = async (filePath: string, stop: boolean = false, isTest = false) => { - const prompt = inquirer.createPromptModule(); - return new Promise((resolve, reject) => { - fs.exists(filePath, async (exists) => { - - if (exists) { - let fileName = filePath.split(path.normalize('/src/'))[1]; - if (isTest) { - fileName = filePath.split(path.normalize('/test/'))[1]; - } - const answer = await prompt([ - { - type: 'confirm', - name: 'override', - message: `Override "${path.join(isTest ? 'test' : 'src', fileName)}"?`, - default: true - } - ]); - if (answer.override) { - return resolve(exists); - } - } else { - return resolve(exists); - } - - if (stop) { - process.exit(0); - } - reject(); - }); - }); -}; - -export const updateTargets = async () => { - console.log(''); - const prompt = inquirer.createPromptModule(); - const answer = await prompt([ - { - type: 'confirm', - name: 'generateTargets', - message: 'Update IoC targets?', - default: true - } - ]); - if (answer.generateTargets === true) { - const command = new UpdateTargetsCommand(); - await command.run(); - } -}; - -export const askProperties = async (name: string): Promise => { - console.log(''); - console.log(`Let\'s add some ${name} properties now.`); - console.log(`Enter an empty property name when done.`); - console.log(''); - - let askAgain = true; - const fieldPrompt = inquirer.createPromptModule(); - const properties: any[] = []; - while (askAgain) { - const property = await fieldPrompt([ - { - type: 'input', - name: 'name', - message: 'Property name:', - filter: (value: any) => _.camelCase(value) - }, { - type: 'list', - name: 'type', - message: 'Property type:', - when: (res: any) => { - askAgain = !!res['name']; - return askAgain; - }, - choices: [ - 'string (string)', - 'text (string)', - - 'boolean (boolean)', - - 'integer (number)', - 'bigInteger (number)', - 'float (number)', - 'decimal (number)', - 'binary (number)', - - 'date (Date)', - 'time (Date)', - 'dateTime (Date)' - ] - } - ]); - if (askAgain) { - console.log(''); - property.name = parseName(property.name, ''); - properties.push(property); - } - } - properties.map(p => { - const types = p.type.replace(/[()]/g, '').split(' '); - p.type = { - script: types[1], - database: types[0] - }; - return p; - }); - console.log(''); - return properties; -}; diff --git a/src/console/templates/api-test.hbs b/src/console/templates/api-test.hbs deleted file mode 100644 index e9ddf0cd..00000000 --- a/src/console/templates/api-test.hbs +++ /dev/null @@ -1,135 +0,0 @@ -import { api } from './lib/api'; -import { DatabaseResetCommand } from '../../src/console/DatabaseResetCommand'; -import { createAdminUser, getToken } from './lib/auth'; - - -describe('/{{name.pluralize}}', () => { - - {{#if isResourceTemplate}}const keys = [ - {{#each properties}}'{{name.camelCase}}'{{#unless @last}}, {{/unless}}{{/each}} - ]; - - const testData = { - {{#each properties}} - {{name.camelCase}}: undefined{{#unless @last}},{{/unless}} // TODO: Add test value - {{/each}} - }; - - const testDataUpdated = { - {{#each properties}} - {{name.camelCase}}: undefined{{#unless @last}},{{/unless}} // TODO: Add test value - {{/each}} - }; - - let token; - let auth; - let createdId; - beforeAll(async () => { - const command = new DatabaseResetCommand(); - await command.run(); - await createAdminUser(); - token = getToken(); - auth = { - token - }; - }); - - test('POST /{{name.pluralize}} Should create a new {{name.lowerCase}}', async () => { - const res = await api('POST', '/api/{{name.pluralize}}', { - token, - body: testData - }); - res.expectJson(); - res.expectStatusCode(201); - res.expectData(keys); - createdId = res.getData()['id']; - }); - - test('POST /{{name.pluralize}} Should fail because we want to create a empty {{name.lowerCase}}', async () => { - const res = await api('POST', '/api/{{name.pluralize}}', { - token, - body: {} - }); - res.expectJson(); - res.expectStatusCode(400); - }); - - test('GET /{{name.pluralize}} Should list of {{name.lowerCase}}s with our new create one', async () => { - const res = await api('GET', '/api/{{name.pluralize}}', auth); - res.expectJson(); - res.expectStatusCode(200); - res.expectData(keys); - const data = res.getData(); - expect(data.length).toBe(2); - - const result = data[1]; - {{#each properties}} - expect(result.{{name.camelCase}}).toBe(testData.{{name.camelCase}}); - {{/each}} - }); - - test('GET /{{name.pluralize}}/:id Should return one {{name.lowerCase}}', async () => { - const res = await api('GET', `/api/{{name.pluralize}}/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(200); - res.expectData(keys); - - const result: any = res.getData(); - {{#each properties}} - expect(result.{{name.camelCase}}).toBe(testData.{{name.camelCase}}); - {{/each}} - }); - - test('PUT /{{name.pluralize}}/:id Should update the {{name.lowerCase}}', async () => { - const res = await api('PUT', `/api/{{name.pluralize}}/${createdId}`, { - token, - body: testDataUpdated - }); - res.expectJson(); - res.expectStatusCode(200); - res.expectData(keys); - - const result: any = res.getData(); - {{#each properties}} - expect(result.{{name.camelCase}}).toBe(testDataUpdated.{{name.camelCase}}); - {{/each}} - }); - - test('PUT /{{name.pluralize}}/:id Should fail because we want to update the {{name.lowerCase}} with a invalid email', async () => { - const res = await api('PUT', `/api/{{name.pluralize}}/${createdId}`, { - token, - body: { - email: 'abc' - } - }); - res.expectJson(); - res.expectStatusCode(400); - }); - - test('DELETE /{{name.pluralize}}/:id Should delete the {{name.lowerCase}}', async () => { - const res = await api('DELETE', `/api/{{name.pluralize}}/${createdId}`, auth); - res.expectStatusCode(200); - }); - - /** - * 404 - NotFound Testing - */ - test('GET /{{name.pluralize}}/:id Should return with a 404, because we just deleted the {{name.lowerCase}}', async () => { - const res = await api('GET', `/api/{{name.pluralize}}/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(404); - }); - - test('DELETE /{{name.pluralize}}/:id Should return with a 404, because we just deleted the {{name.lowerCase}}', async () => { - const res = await api('DELETE', `/api/{{name.pluralize}}/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(404); - }); - - test('PUT /{{name.pluralize}}/:id Should return with a 404, because we just deleted the {{name.lowerCase}}', async () => { - const res = await api('PUT', `/api/{{name.pluralize}}/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(404); - });{{/if}} - -}); diff --git a/src/console/templates/controller.hbs b/src/console/templates/controller.hbs deleted file mode 100644 index bcde4ed7..00000000 --- a/src/console/templates/controller.hbs +++ /dev/null @@ -1,52 +0,0 @@ -{{#if isResourceTemplate}} -import { inject, named } from 'inversify'; -import { controller, httpGet, httpPost, httpPut, httpDelete, response, requestBody, requestParam } from 'inversify-express-utils'; -import { Types, Targets } from '../../{{deepness}}constants'; -import { app } from '../../{{deepness}}app'; -import { {{name.capitalize}}Service } from '../{{deepness}}services/{{name.capitalize}}Service'; -{{else}} -import { controller } from 'inversify-express-utils'; -import { app } from '../../{{deepness}}app'; -import { Types, Targets } from '../../{{deepness}}constants'; -{{/if}} - -// Get middlewares -const authenticate = app.IoC.getNamed(Types.Middleware, Targets.Middleware.AuthenticateMiddleware); - - -@controller('/{{name.pluralize}}', authenticate.use) -export class {{name.capitalize}}Controller { - - {{#if isResourceTemplate}}constructor( @inject(Types.Service) @named(Targets.Service.{{name.capitalize}}Service) private {{name.camelCase}}Service: {{name.capitalize}}Service) { } - - @httpGet('/') - public async findAll( @response() res: myExpress.Response): Promise { - const {{name.camelCase}}s = await this.{{name.camelCase}}Service.findAll(); - return res.found({{name.camelCase}}s.toJSON()); - } - - @httpPost('/') - public async create( @response() res: myExpress.Response, @requestBody() body: any): Promise { - const {{name.camelCase}} = await this.{{name.camelCase}}Service.create(body); - return res.created({{name.camelCase}}.toJSON()); - } - - @httpGet('/:id') - public async findOne( @response() res: myExpress.Response, @requestParam('id') id: string): Promise { - const {{name.camelCase}} = await this.{{name.camelCase}}Service.findOne(parseInt(id, 10)); - return res.found({{name.camelCase}}.toJSON()); - } - - @httpPut('/:id') - public async update( @response() res: myExpress.Response, @requestParam('id') id: string, @requestBody() body: any): Promise { - const {{name.camelCase}} = await this.{{name.camelCase}}Service.update(parseInt(id, 10), body); - return res.updated({{name.camelCase}}.toJSON()); - } - - @httpDelete('/:id') - public async destroy( @response() res: myExpress.Response, @requestParam('id') id: string): Promise { - await this.{{name.camelCase}}Service.destroy(parseInt(id, 10)); - return res.destroyed(); - }{{/if}} - // Implement your routes here -} diff --git a/src/console/templates/exception.hbs b/src/console/templates/exception.hbs deleted file mode 100644 index 532a8f51..00000000 --- a/src/console/templates/exception.hbs +++ /dev/null @@ -1,8 +0,0 @@ -import { Exception } from '../../{{deepness}}core/api/Exception'; - - -export class {{name.capitalize}}Exception extends Exception { - constructor(text: string, error: any) { - super(400, text, error); - } -} diff --git a/src/console/templates/listener.hbs b/src/console/templates/listener.hbs deleted file mode 100644 index a604ce26..00000000 --- a/src/console/templates/listener.hbs +++ /dev/null @@ -1,22 +0,0 @@ -import { inject, named } from 'inversify'; -import { Types, Core } from '../../{{deepness}}constants'; -import { Logger as LoggerType } from '../../{{deepness}}core/Logger'; - - -export class {{name.capitalize}}Listener implements interfaces.Listener { - - public static Event = Symbol('{{name.normal}}Listener'); - - public log: LoggerType; - - constructor( - @inject(Types.Core) @named(Core.Logger) Logger: typeof LoggerType - ) { - this.log = new Logger(__filename); - } - - public act(user: any): void { - this.log.info('Receive event {{name.capitalize}}Listener', user); - } - -} diff --git a/src/console/templates/middleware.hbs b/src/console/templates/middleware.hbs deleted file mode 100644 index ef2207d6..00000000 --- a/src/console/templates/middleware.hbs +++ /dev/null @@ -1,21 +0,0 @@ -import { inject, named } from 'inversify'; -import { Logger as LoggerType } from '../../{{deepness}}core/Logger'; -import { Types, Core } from '../../{{deepness}}constants'; - - -export class {{name.capitalize}}Middleware implements interfaces.Middleware { - - public log: LoggerType; - - constructor( - @inject(Types.Core) @named(Core.Logger) Logger: typeof LoggerType - ) { - this.log = new Logger(__filename); - } - - public use = (req: myExpress.Request, res: myExpress.Response, next: myExpress.NextFunction): void => { - // TODO add your middleware logic here - next(); - } - -} diff --git a/src/console/templates/migration.hbs b/src/console/templates/migration.hbs deleted file mode 100644 index cc1e0deb..00000000 --- a/src/console/templates/migration.hbs +++ /dev/null @@ -1,39 +0,0 @@ -import * as Knex from 'knex'; - - -{{#if hasProperties}} -exports.up = (db: Knex): Promise => { - return Promise.all([ - db.schema.createTable('{{tableName}}', (table: Knex.CreateTableBuilder) => { - table.increments('id').primary(); - - {{#each properties}} - table.{{type.database}}('{{name.camelCase}}').notNullable(); - {{/each}} - - {{#if hasTimestamps}} - table.timestamp('updated_at').defaultTo(db.fn.now()); - table.timestamp('created_at').defaultTo(db.fn.now()); - {{/if}} - }) - ]); -}; - -exports.down = (db: Knex): Promise => { - return Promise.all([ - db.schema.dropTable('{{tableName}}') - ]); -}; -{{else}} -exports.up = (db: Knex): Promise => { - return Promise.all([ - // TODO add your migration scripts here - ]); -}; - -exports.down = (db: Knex): Promise => { - return Promise.all([ - // TODO add your migration scripts here - ]); -}; -{{/if}} diff --git a/src/console/templates/model.hbs b/src/console/templates/model.hbs deleted file mode 100644 index 0b3bc1cf..00000000 --- a/src/console/templates/model.hbs +++ /dev/null @@ -1,28 +0,0 @@ -import { Bookshelf } from '../../{{deepness}}config/Database'; - - -export class {{name.capitalize}} extends Bookshelf.Model<{{name.capitalize}}> { - - public static async fetchById(value: number): Promise<{{name.capitalize}}> { - return await {{name.capitalize}}.where<{{name.capitalize}}>({ id: value }).fetch(); - } - - public get tableName(): string { return '{{tableName}}'; } - public get hasTimestamps(): boolean { return {{hasTimestamps}}; } - - public get Id(): number { return this.get('id'); } - public set Id(value: number) { this.set('id', value); } - {{#each properties}} - - public get {{name.capitalize}}(): {{type.script}} { return this.get('{{name.camelCase}}'); } - public set {{name.capitalize}}(value: {{type.script}}) { this.set('{{name.camelCase}}', value); } - {{/each}} - - {{#if hasTimestamps}} - public get UpdatedAt(): Date { return this.get('updatedAt'); } - public set UpdatedAt(value: Date) { this.set('updatedAt', value); } - - public get CreatedAt(): Date { return this.get('createdAt'); } - public set CreatedAt(value: Date) { this.set('createdAt', value); } - {{/if}} -} diff --git a/src/console/templates/repository.hbs b/src/console/templates/repository.hbs deleted file mode 100644 index 7eafd24d..00000000 --- a/src/console/templates/repository.hbs +++ /dev/null @@ -1,64 +0,0 @@ -{{#if isResourceTemplate}} -import * as Bookshelf from 'bookshelf'; -import { inject, named } from 'inversify'; -import { Types, Targets } from '../../{{deepness}}constants'; -import { {{name.capitalize}} } from '../{{deepness}}models/{{name.capitalize}}'; -import { DatabaseException } from '../{{deepness}}exceptions/DatabaseException'; -import { NotFoundException } from '../{{deepness}}exceptions/NotFoundException'; -{{else}} -// import { Knex } from '../../config/Database'; -{{/if}} - - -export class {{name.capitalize}}Repository { - - {{#if isResourceTemplate}}constructor( - @inject(Types.Model) @named(Targets.Model.{{name.capitalize}}) public {{name.capitalize}}Model: typeof {{name.capitalize}} - ) { } - - public async findAll(): Promise> { - const list = await this.{{name.capitalize}}Model.fetchAll(); - return list as Bookshelf.Collection<{{name.capitalize}}>; - } - - public async findOne(id: number): Promise<{{name.capitalize}}> { - return this.{{name.capitalize}}Model.fetchById(id); - } - - public async create(data: any): Promise<{{name.capitalize}}> { - const {{name.camelCase}} = this.{{name.capitalize}}Model.forge<{{name.capitalize}}>(data); - try { - const {{name.camelCase}}Created = await {{name.camelCase}}.save(); - return this.{{name.capitalize}}Model.fetchById({{name.camelCase}}Created.id); - } catch (error) { - throw new DatabaseException('Could not create the {{name.camelCase}}!', error); - } - } - - public async update(id: number, data: any): Promise<{{name.capitalize}}> { - const {{name.camelCase}} = this.{{name.capitalize}}Model.forge<{{name.capitalize}}>({ id }); - try { - const {{name.camelCase}}Updated = await {{name.camelCase}}.save(data, { patch: true }); - return this.{{name.capitalize}}Model.fetchById({{name.camelCase}}Updated.id); - } catch (error) { - throw new DatabaseException('Could not update the {{name.camelCase}}!', error); - } - } - - public async destroy(id: number): Promise { - let {{name.camelCase}} = this.{{name.capitalize}}Model.forge<{{name.capitalize}}>({ id }); - try { - {{name.camelCase}} = await {{name.camelCase}}.fetch({ require: true }); - } catch (error) { - throw new NotFoundException(id); - } - - try { - await {{name.camelCase}}.destroy(); - return; - } catch (error) { - throw new DatabaseException('Could not delete the {{name.camelCase}}!', error); - } - }{{/if}} - -} diff --git a/src/console/templates/request.hbs b/src/console/templates/request.hbs deleted file mode 100644 index 2c4944f2..00000000 --- a/src/console/templates/request.hbs +++ /dev/null @@ -1,20 +0,0 @@ -import { IsNotEmpty } from 'class-validator'; -import { RequestBody } from '../../{{deepness}}core/api/RequestBody'; - - -export class {{name.capitalize}}Request extends RequestBody { - {{#if hasProperties}} - {{#each properties}} - - @IsNotEmpty() - public {{name.camelCase}}: {{type.script}}; - {{/each}} - {{else}} - - // TODO Define your props here and add your needed validator annotations - @IsNotEmpty() - public propName: string; - {{/if}} - -} - diff --git a/src/console/templates/resource.hbs b/src/console/templates/resource.hbs deleted file mode 100644 index 24f7601f..00000000 --- a/src/console/templates/resource.hbs +++ /dev/null @@ -1,14 +0,0 @@ -declare module 'resources' { - - interface {{name.capitalize}} { - id: int; - {{#each properties}} - {{name.camelCase}}: {{type.script}}; - {{/each}} - {{#if hasTimestamps}} - createdAt: Date; - updatedAt: Date; - {{/if}} - } - -} diff --git a/src/console/templates/seed.hbs b/src/console/templates/seed.hbs deleted file mode 100644 index 6282d301..00000000 --- a/src/console/templates/seed.hbs +++ /dev/null @@ -1,10 +0,0 @@ -import * as Knex from 'knex'; - -import { Factory } from '../factories'; - - -exports.seed = async (db: Knex) => { - const factory = Factory.getInstance(); - // TODO: create new data with your factory - // await factory.get(User).create(10); -}; diff --git a/src/console/templates/service.hbs b/src/console/templates/service.hbs deleted file mode 100644 index 23fd6533..00000000 --- a/src/console/templates/service.hbs +++ /dev/null @@ -1,82 +0,0 @@ -{{#if isResourceTemplate}} -import * as Bookshelf from 'bookshelf'; -{{/if}} -import { inject, named } from 'inversify'; -import { Logger as LoggerType } from '../../{{deepness}}core/Logger'; -{{#unless isResourceTemplate}} -import { Types, Core } from '../../{{deepness}}constants'; -{{/unless}} -{{#if isResourceTemplate}} -import { Types, Core, Targets } from '../../{{deepness}}constants'; -import { validate, request } from '../../{{deepness}}core/api/Validate'; -import { NotFoundException } from '../{{deepness}}exceptions/NotFoundException'; -import { {{name.capitalize}}Repository } from '../{{deepness}}repositories/{{name.capitalize}}Repository'; -import { {{name.capitalize}} } from '../{{deepness}}models/{{name.capitalize}}'; -import { {{name.capitalize}}CreateRequest } from '../{{deepness}}requests/{{name.capitalize}}CreateRequest'; -import { {{name.capitalize}}UpdateRequest } from '../{{deepness}}requests/{{name.capitalize}}UpdateRequest'; -{{/if}} - - -export class {{name.capitalize}}Service { - - public log: LoggerType; - - {{#if isResourceTemplate}}constructor( - @inject(Types.Repository) @named(Targets.Repository.{{name.capitalize}}Repository) public {{name.camelCase}}Repo: {{name.capitalize}}Repository, - @inject(Types.Core) @named(Core.Logger) public Logger: typeof LoggerType - ) { - this.log = new Logger(__filename); - } - - public async findAll(): Promise> { - return this.{{name.camelCase}}Repo.findAll(); - } - - public async findOne(id: number): Promise<{{name.capitalize}}> { - const {{name.camelCase}} = await this.{{name.camelCase}}Repo.findOne(id); - if ({{name.camelCase}} === null) { - this.log.warn(`{{name.capitalize}} with the id=${id} was not found!`); - throw new NotFoundException(id); - } - return {{name.camelCase}}; - } - - @validate() - public async create( @request({{name.capitalize}}CreateRequest) body: any): Promise<{{name.capitalize}}> { - // If the request body was valid we will create the {{name.camelCase}} - const {{name.camelCase}} = await this.{{name.camelCase}}Repo.create(body); - return {{name.camelCase}}; - } - - @validate() - public async update(id: number, @request({{name.capitalize}}UpdateRequest) body: any): Promise<{{name.capitalize}}> { - // Find or fail - const {{name.camelCase}} = await this.findOne(id); - - // Set new values - {{#if hasProperties}} - {{#each properties}} - {{../name.camelCase}}.{{name.capitalize}} = body.{{name.camelCase}}; - {{/each}} - {{else}} - // TODO asign correct values - // {{name.camelCase}}.Value = body.value; - {{/if}} - - // Update {{name.camelCase}} record - const updated{{name.capitalize}} = await this.{{name.camelCase}}Repo.update(id, {{name.camelCase}}.toJSON()); - return updated{{name.capitalize}}; - } - - public async destroy(id: number): Promise { - await this.{{name.camelCase}}Repo.destroy(id); - } - {{else}} - constructor( - @inject(Types.Core) @named(Core.Logger) public Logger: typeof LoggerType - ) { - this.log = new Logger(__filename); - } - {{/if}} - -} diff --git a/src/console/templates/targets.hbs b/src/console/templates/targets.hbs deleted file mode 100644 index 37d60f9f..00000000 --- a/src/console/templates/targets.hbs +++ /dev/null @@ -1,18 +0,0 @@ -/** - * constants.Targets - * ------------------------------------------------ - * - * This is for our IOC so have a unique key/target for - * our controllers, services and repositories - * - * This file is generated with the task `$ npm run console update:targets`. - */ - -export const Targets = { - Model: {{{object models}}}, - Repository: {{{object repositories}}}, - Service: {{{object services}}}, - Middleware: {{{object middlewares}}}, - Listener: {{{object listeners}}}, - Controller: {{{object controllers}}} -}; diff --git a/src/console/templates/validator.hbs b/src/console/templates/validator.hbs deleted file mode 100644 index 5ab5f713..00000000 --- a/src/console/templates/validator.hbs +++ /dev/null @@ -1,17 +0,0 @@ -import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; - - -@ValidatorConstraint({ name: '{{name.normal}}', async: false }) -export class {{name.capitalize}}Validator implements ValidatorConstraintInterface { - - public validate(text: string, args: ValidationArguments): boolean { - // Place your validation here - return true; - } - - public defaultMessage(args: ValidationArguments): string { - // Error message if the validation fails - return 'Incorrect value'; - } - -} diff --git a/src/constants/Tables.ts b/src/constants/Tables.ts deleted file mode 100644 index 95525b24..00000000 --- a/src/constants/Tables.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * constants.Tables - * ------------------------------------------------ - * - * This variable has all the database table names, so - * it to refactor. However, it is very important that you - * do not use them in your migration scripts. - */ - -export const Tables = { - Users: 'users' -}; diff --git a/src/constants/Targets.ts b/src/constants/Targets.ts deleted file mode 100644 index 9fea3fed..00000000 --- a/src/constants/Targets.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * constants.Targets - * ------------------------------------------------ - * - * This is for our IOC so have a unique key/target for - * our controllers, services and repositories - * - * This file is generated with the task `$ npm run console update:targets`. - */ - -export const Targets = { - Model: { - User: 'User' - }, - Repository: { - UserRepository: 'UserRepository' - }, - Service: { - UserService: 'UserService' - }, - Middleware: { - AuthenticateMiddleware: 'AuthenticateMiddleware', - PopulateUserMiddleware: 'PopulateUserMiddleware' - }, - Listener: { - user: { - UserAuthenticatedListener: 'UserAuthenticatedListener', - UserCreatedListener: 'UserCreatedListener' - } - }, - Controller: { - UserController: 'UserController' - } -}; diff --git a/src/constants/Types.ts b/src/constants/Types.ts deleted file mode 100644 index d49b0392..00000000 --- a/src/constants/Types.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * constants.Types - * ------------------------------------------------ - * - * We extend the TYPE variable of the 'inversify-express-utils' - * module with our service and repositories. - */ - -import { TYPE } from 'inversify-express-utils'; - -export const Types = { - ...TYPE, - Lib: Symbol('Lib'), - Core: Symbol('Core'), - Model: Symbol('Model'), - Service: Symbol('Service'), - Listener: Symbol('Listener'), - Repository: Symbol('Repository'), - Middleware: Symbol('Middleware') -}; diff --git a/src/constants/index.ts b/src/constants/index.ts deleted file mode 100644 index 0ee4dd80..00000000 --- a/src/constants/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './Tables'; -export * from './Types'; -export * from './Targets'; -export * from '../core/Targets'; diff --git a/src/core/ApiInfo.ts b/src/core/ApiInfo.ts deleted file mode 100644 index 23edecac..00000000 --- a/src/core/ApiInfo.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as express from 'express'; -import { Environment } from './helpers/Environment'; -import { SwaggerUI } from './SwaggerUI'; -import { ApiMonitor } from './ApiMonitor'; - - -export class ApiInfo { - - public static getRoute(): string { - return process.env.APP_URL_PREFIX + process.env.API_INFO_ROUTE; - } - - public setup(application: express.Application): void { - if (Environment.isTruthy(process.env.API_INFO_ENABLED)) { - application.get( - ApiInfo.getRoute(), - // @ts-ignore: False type definitions from express - (req: myExpress.Request, res: myExpress.Response) => { - const pkg = Environment.getPkg(); - const links = { - links: {} - }; - if (Environment.isTruthy(process.env.SWAGGER_ENABLED)) { - links.links['swagger'] = - `${application.get('host')}${SwaggerUI.getRoute()}`; - } - if (Environment.isTruthy(process.env.MONITOR_ENABLED)) { - links.links['monitor'] = - `${application.get('host')}${ApiMonitor.getRoute()}`; - } - return res.json({ - name: pkg.name, - version: pkg.version, - description: pkg.description, - ...links - }); - }); - } - } -} diff --git a/src/core/ApiMonitor.ts b/src/core/ApiMonitor.ts deleted file mode 100644 index c9dd020e..00000000 --- a/src/core/ApiMonitor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as express from 'express'; -import * as monitor from 'express-status-monitor'; -import { Environment } from './helpers/Environment'; -import { BasicAuthentication } from './BasicAuthentication'; - - -export class ApiMonitor { - - public static getRoute(): string { - return process.env.MONITOR_ROUTE; - } - - public setup(app: express.Application): void { - if (Environment.isTruthy(process.env.MONITOR_ENABLED)) { - app.use(monitor()); - app.get(ApiMonitor.getRoute(), BasicAuthentication(), monitor().pageRoute); - } - } -} diff --git a/src/core/App.ts b/src/core/App.ts deleted file mode 100644 index b6402b0c..00000000 --- a/src/core/App.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as express from 'express'; -import * as dotenv from 'dotenv'; -import { Container } from 'inversify'; -import { InversifyExpressServer } from 'inversify-express-utils'; -import { Logger } from './Logger'; -import { LoggerConfig } from '../config/LoggerConfig'; -import { Bootstrap } from './Bootstrap'; -import { Server } from './Server'; -import { IoC } from './IoC'; -import { AppConfig } from '../config/AppConfig'; - - -export interface Configurable { - configure(app: App): void; -} - -export class App { - - private express: express.Application = express(); - private server: Server; - private inversifyExpressServer: InversifyExpressServer; - private ioc: IoC = new IoC(); - private log: Logger = new Logger(__filename); - private bootstrapApp = new Bootstrap(); - private configurations: Configurable[] = []; - - constructor() { - // It also loads the .env file into the 'process.env' variable. - dotenv.config(); - // Configure the logger, because we need it already. - const loggerConfig = new LoggerConfig(); - loggerConfig.configure(); - // Create express app - this.log.info('Defining app...'); - this.bootstrapApp.defineExpressApp(this.express); - } - - get IoC(): Container { - return this.ioc.container; - } - - get Express(): express.Application { - return this.express; - } - - get Server(): Server { - return this.server; - } - - public Logger(scope: string): Logger { - return new Logger(scope || __filename); - } - - public configure(configurations: Configurable): void { - this.configurations.push(configurations); - } - - public async bootstrap(): Promise { - this.log.info('Configuring app...'); - // Add express monitor app - this.bootstrapApp.setupMonitor(this.express); - // Configure the app config for all the middlewares - const appConfig = new AppConfig(); - appConfig.configure(this); - // Configure all custom configurations - this.configurations.forEach((c) => c.configure(this)); - // Setup the ioc of inversify - this.log.info('Binding IoC modules...'); - await this.ioc.bindModules(); - this.log.info('Setting up IoC...'); - this.inversifyExpressServer = this.bootstrapApp.setupInversifyExpressServer(this.express, this.ioc); - this.express = this.bootstrapApp.bindInversifyExpressServer(this.express, this.inversifyExpressServer); - this.bootstrapApp.setupCoreTools(this.express); - this.log.info('Starting app...'); - this.server = new Server(this.bootstrapApp.startServer(this.express)); - this.server.use(this.express); - this.log.info('App is ready!'); - } - -} diff --git a/src/core/BasicAuthentication.ts b/src/core/BasicAuthentication.ts deleted file mode 100644 index 61584b89..00000000 --- a/src/core/BasicAuthentication.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as basicAuth from 'express-basic-auth'; - - -export const BasicAuthentication = (): any => { - return basicAuth({ - users: { - [process.env.APP_BASIC_USER]: process.env.APP_BASIC_PASSWORD - }, - challenge: true - }); -}; diff --git a/src/core/Bootstrap.ts b/src/core/Bootstrap.ts deleted file mode 100644 index 363d536c..00000000 --- a/src/core/Bootstrap.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as http from 'http'; -import * as express from 'express'; -import { InversifyExpressServer } from 'inversify-express-utils'; -import { Server } from './Server'; -import { Logger } from './Logger'; -import { ApiInfo } from './ApiInfo'; -import { ApiMonitor } from './ApiMonitor'; -import { exceptionHandler } from './api/exceptionHandler'; -import { extendExpressResponse } from './api/extendExpressResponse'; -import { SwaggerUI } from './SwaggerUI'; -import { IoC } from './IoC'; - - -export class Bootstrap { - - public log: Logger = new Logger(__filename); - - public defineExpressApp(app: express.Application): express.Application { - app.set('host', process.env.APP_HOST); - app.set('port', Server.normalizePort(process.env.PORT || process.env.APP_PORT || '3000')); - return app; - } - - public setupMonitor(app: express.Application): void { - const apiMonitor = new ApiMonitor(); - apiMonitor.setup(app); - } - - public setupCoreTools(app: express.Application): void { - const apiInfo = new ApiInfo(); - apiInfo.setup(app); - - const swaggerUI = new SwaggerUI(); - swaggerUI.setup(app); - } - - public startServer(app: express.Application): http.Server { - return app.listen(app.get('port')); - } - - public setupInversifyExpressServer(app: express.Application, ioc: IoC): InversifyExpressServer { - const inversifyExpressServer = new InversifyExpressServer(ioc.container, undefined, { - rootPath: process.env.APP_URL_PREFIX - }, app); - // @ts-ignore: False type definitions from express - inversifyExpressServer.setConfig((a) => a.use(extendExpressResponse)); - // @ts-ignore: False type definitions from express - inversifyExpressServer.setErrorConfig((a) => a.use(exceptionHandler)); - return inversifyExpressServer; - } - - public bindInversifyExpressServer(app: express.Application, inversifyExpressServer: InversifyExpressServer): express.Application { - try { - app = inversifyExpressServer.build(); - } catch (e) { - this.log.error(e.message); - process.exit(1); - } - return app; - } - -} diff --git a/src/core/IoC.ts b/src/core/IoC.ts deleted file mode 100644 index 25936d10..00000000 --- a/src/core/IoC.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * IOC - CONTAINER - * ---------------------------------------- - * - * Bind every controller and service to the ioc container. All controllers - * will then be bonded to the express structure with their defined routes. - */ - -import * as glob from 'glob'; -import * as path from 'path'; -import { Container, decorate, injectable } from 'inversify'; -import { Types, Core, Targets } from '../constants'; -import { events, EventEmitter } from './api/events'; -import { Logger } from './Logger'; -import { IocConfig } from '../config/IocConfig'; - - -export class IoC { - - public container: Container; - public libConfiguration: (container: Container) => Container; - public customConfiguration: (container: Container) => Container; - - private log: Logger = new Logger(__filename); - - constructor() { - this.container = new Container(); - const config = new IocConfig(); - config.configure(this); - } - - public configure(configuration: (container: Container) => Container): void { - this.customConfiguration = configuration; - } - - public configureLib(configuration: (container: Container) => Container): void { - this.libConfiguration = configuration; - } - - public async bindModules(): Promise { - this.bindCore(); - - if (this.libConfiguration) { - this.container = this.libConfiguration(this.container); - } - - await this.bindModels(); - await this.bindRepositories(); - await this.bindServices(); - - await this.bindListeners(); - await this.bindMiddlewares(); - await this.bindControllers(); - - if (this.customConfiguration) { - this.container = this.customConfiguration(this.container); - } - } - - private bindCore(): void { - this.container.bind(Types.Core).toConstantValue(Logger).whenTargetNamed(Core.Logger); - this.container.bind(Types.Core).toConstantValue(events).whenTargetNamed(Core.Events); - } - - private bindModels(): Promise { - return this.bindFiles('/models/**/*.ts', Targets.Model, (name: any, value: any) => { - decorate(injectable(), value); - this.container - .bind(Types.Model) - .toConstantValue(value) - .whenTargetNamed(name); - }); - } - - private bindRepositories(): Promise { - return this.bindFiles( - '/repositories/**/*Repository.ts', - Targets.Repository, - (name: any, value: any) => this.bindFile(Types.Repository, name, value)); - } - - private bindServices(): Promise { - return this.bindFiles( - '/services/**/*Service.ts', - Targets.Service, - (name: any, value: any) => this.bindFile(Types.Service, name, value)); - } - - private bindMiddlewares(): Promise { - return this.bindFiles( - '/middlewares/**/*Middleware.ts', - Targets.Middleware, - (name: any, value: any) => this.bindFile(Types.Middleware, name, value)); - } - - private bindControllers(): Promise { - return this.bindFiles( - '/controllers/**/*Controller.ts', - Targets.Controller, - (name: any, value: any) => this.bindFile(Types.Controller, name, value)); - } - - private bindListeners(): Promise { - return this.bindFiles('/listeners/**/*Listener.ts', Targets.Listener, (name: any, value: any) => { - decorate(injectable(), value); - this.container - .bind(Types.Listener) - .to(value) - .whenTargetNamed(name); - - const listener: interfaces.Listener = this.container.getNamed(Types.Listener, name); - events.on(value.Event, (...args) => listener.act(...args)); - }); - } - - private bindFile(type: any, name: string, value: any): void { - decorate(injectable(), value); - this.container - .bind(type) - .to(value) - .whenTargetNamed(name); - } - - private bindFiles(filePath: string, target: any, callback: (name: any, value: any) => void): Promise { - return new Promise((resolve) => { - this.getFiles(filePath, (files: string[]) => { - files.forEach((file: any) => { - let fileExport; - let fileClass; - let fileTarget; - const isRecursive = file.name.indexOf('.') > 0; - try { - fileExport = require(`${file.filePath}`); - } catch (e) { - this.log.warn(e.message); - return; - } - if (fileExport === undefined) { - this.log.warn(`Could not find the file ${file.name}!`); - return; - } - if (isRecursive) { - fileClass = this.getClassOfFileExport(file.name, fileExport); - fileTarget = this.getTargetOfFile(file.name, target); - - } else { - fileClass = fileExport[file.name]; - fileTarget = target && target[file.name]; - } - - if (fileClass === undefined) { - this.log.warn(`Name of the file '${file.name}' does not match to the class name!`); - return; - } - - if (fileTarget === undefined) { - this.log.warn(`Please define your '${file.name}' class is in the target constants.`); - return; - } - - callback(fileTarget, fileClass); - }); - resolve(); - }); - }); - } - - private getClassOfFileExport(name: string, fileExport: any): any { - const fileParts = name.split('.'); - let fileClass = fileExport; - fileParts.forEach((part) => { - if (fileClass.hasOwnProperty(part)) { - fileClass = fileClass[part]; - } - }); - return fileClass; - } - - private getTargetOfFile(name: string, target: any): any { - const fileParts = name.split('.'); - let fileTarget = target; - fileParts.forEach((part) => fileTarget = fileTarget[part]); - return fileTarget; - } - - private getBasePath(): string { - const baseFolder = __dirname.indexOf(`${path.sep}src${path.sep}`) >= 0 ? `${path.sep}src${path.sep}` : `${path.sep}dist${path.sep}`; - const baseRoot = __dirname.substring(0, __dirname.indexOf(baseFolder)); - return path.join(baseRoot, baseFolder, 'api'); - } - - private getFiles(filePath: string, done: (files: any[]) => void): void { - const isTypeScript = __dirname.indexOf(`${path.sep}src${path.sep}`) >= 0; - if (!isTypeScript) { - filePath = filePath.replace('.ts', '.js'); - } - glob(this.getBasePath() + filePath, (err: any, files: string[]) => { - if (err) { - this.log.warn(`Could not read the folder ${filePath}!`); - return; - } - done(files.map((p: string) => this.parseFilePath(p))); - }); - } - - private parseFilePath(filePath: string): any { - const p = filePath.substring(this.getBasePath().length + 1); - const dir = p.split('/')[0]; - const file = p.substr(dir.length + 1); - const name = file.replace('/', '.').substring(0, file.length - 3); - return { - filePath, - dir, - file, - name - }; - } - -} diff --git a/src/core/Logger.ts b/src/core/Logger.ts deleted file mode 100644 index 67865818..00000000 --- a/src/core/Logger.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as path from 'path'; - -/** - * core.log.Log - * ------------------------------------------------ - * - * This is the main Logger Object. You can create a scope logger - * or directly use the static log methods. - * - * By Default it uses the debug-adapter, but you are able to change - * this in the start up process in the core/index.ts file. - */ - -export class Logger { - - public static DEFAULT_SCOPE = 'app'; - - public static addAdapter(key: string, adapter: interfaces.LoggerAdapterConstructor): void { - Logger.Adapters.set(key, adapter); - } - - public static setAdapter(key: string): void { - const adapter = Logger.Adapters.get(key); - if (adapter !== undefined) { - Logger.Adapter = adapter; - } else { - console.log(`No log adapter with key ${key} was found!`); - } - } - - private static Adapter: interfaces.LoggerAdapterConstructor; - private static Adapters: Map = new Map(); - - private static parsePathToScope(filepath: string): string { - if (filepath.indexOf(path.sep) >= 0) { - filepath = filepath.replace(process.cwd(), ''); - filepath = filepath.replace(`${path.sep}src${path.sep}`, ''); - filepath = filepath.replace(`${path.sep}dist${path.sep}`, ''); - filepath = filepath.replace('.ts', ''); - filepath = filepath.replace('.js', ''); - filepath = filepath.replace(path.sep, ':'); - } - return filepath; - } - - private scope: string; - private adapter: interfaces.LoggerAdapter; - - constructor(scope?: string) { - this.scope = Logger.parsePathToScope((scope) ? scope : Logger.DEFAULT_SCOPE); - } - - public getAdapter(): interfaces.LoggerAdapter { - return this.adapter; - } - - public debug(message: string, ...args: any[]): void { - this.log('debug', message, args); - } - - public info(message: string, ...args: any[]): void { - this.log('info', message, args); - } - - public warn(message: string, ...args: any[]): void { - this.log('warn', message, args); - } - - public error(message: string, ...args: any[]): void { - this.log('error', message, args); - } - - private log(level: string, message: string, args: any[]): void { - this.lazyLoadAdapter(); - if (this.adapter) { - this.adapter[level](message, args); - } - } - - private lazyLoadAdapter(): void { - if (!this.adapter) { - if (Logger.Adapter) { - this.adapter = new Logger.Adapter(this.scope); - } else { - console.log('Please add a log adapter in the LoggerConfig!'); - } - } - } - -} diff --git a/src/core/Server.ts b/src/core/Server.ts deleted file mode 100644 index 2506dadc..00000000 --- a/src/core/Server.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * core.Server - * ------------------------------------ - * - * The Server class is responsible to listen to http server - * events and to react to it. - */ - -import * as http from 'http'; -import * as express from 'express'; -import { Logger } from './Logger'; -import { Environment } from './helpers/Environment'; -import { SwaggerUI } from './SwaggerUI'; -import { ApiMonitor } from './ApiMonitor'; -import { ApiInfo } from './ApiInfo'; - - -export class Server { - - /** - * Normalize port for the express application - * - * @param {string} port - * @returns {(number | string | boolean)} - * - * @memberof Server - */ - public static normalizePort(port: string): number | string | boolean { - const parsedPort = parseInt(port, 10); - if (isNaN(parsedPort)) { // named pipe - return port; - } - if (parsedPort >= 0) { // port number - return parsedPort; - } - return false; - } - - private log = new Logger(__filename); - - constructor(public httpServer: http.Server) { } - - /** - * Listen to the given http server - * - * @param {http.Server} httpServer - * @param {express.Application} app - * - * @memberof Server - */ - public use(app: express.Application): void { - this.httpServer.on('listening', () => { - this.onStartUp(app); - }); - this.httpServer.on('error', (error) => { - this.onError(error); - }); - } - - /** - * This is called when the server has started and is ready. - * - * - * @memberof Server - */ - public onStartUp(app: express.Application): void { - this.log.debug(``); - this.log.debug(`Aloha, your app is ready on ${app.get('host')}${process.env.APP_URL_PREFIX}`); - this.log.debug(`To shut it down, press + C at any time.`); - this.log.debug(``); - this.log.debug('-------------------------------------------------------'); - this.log.debug(`Environment : ${Environment.getNodeEnv()}`); - this.log.debug(`Version : ${Environment.getPkg().version}`); - this.log.debug(``); - if (Environment.isTruthy(process.env.API_INFO_ENABLED)) { - this.log.debug(`API Info : ${app.get('host')}${ApiInfo.getRoute()}`); - } - if (Environment.isTruthy(process.env.SWAGGER_ENABLED)) { - this.log.debug(`Swagger : ${app.get('host')}${SwaggerUI.getRoute()}`); - } - if (Environment.isTruthy(process.env.MONITOR_ENABLED)) { - this.log.debug(`Monitor : ${app.get('host')}${ApiMonitor.getRoute()}`); - } - this.log.debug('-------------------------------------------------------'); - this.log.debug(''); - } - - /** - * This is called when the server throws an error like the given - * port is already used - * - * @param {*} error - * - * @memberof Server - */ - public onError(error: any): void { - if (error.syscall !== 'listen') { - throw error; - } - switch (error.code) { - case 'EACCES': - this.log.error(`The Server requires elevated privileges`); - process.exit(1); - break; - case 'EADDRINUSE': - this.log.error(`Port is already in use or blocked by the os`); - process.exit(1); - break; - default: - throw error; - } - } - -} diff --git a/src/core/SwaggerUI.ts b/src/core/SwaggerUI.ts deleted file mode 100644 index eac1eef9..00000000 --- a/src/core/SwaggerUI.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as express from 'express'; -import * as path from 'path'; -import * as swaggerUi from 'swagger-ui-express'; -import { Environment } from './helpers/Environment'; -import { BasicAuthentication } from './BasicAuthentication'; - - -export class SwaggerUI { - - public static getRoute(): string { - return process.env.SWAGGER_ROUTE; - } - - public setup(app: express.Application): void { - if (Environment.isTruthy(process.env.SWAGGER_ENABLED)) { - const baseFolder = __dirname.indexOf(`${path.sep}src${path.sep}`) >= 0 ? `${path.sep}src${path.sep}` : `${path.sep}dist${path.sep}`; - const basePath = __dirname.substring(0, __dirname.indexOf(baseFolder)); - const swaggerFile = require(path.join(basePath, process.env.SWAGGER_FILE)); - const packageJson = require(path.join(basePath, 'package.json')); - - // Add npm infos to the swagger doc - swaggerFile.info = { - title: packageJson.name, - description: packageJson.description, - version: packageJson.version - }; - - // Initialize swagger-jsdoc -> returns validated swagger spec in json format - app.use(SwaggerUI.getRoute(), BasicAuthentication(), swaggerUi.serve, swaggerUi.setup(swaggerFile)); - } - } -} diff --git a/src/core/Targets.ts b/src/core/Targets.ts deleted file mode 100644 index dd005253..00000000 --- a/src/core/Targets.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * core.Targets - * ------------------------------------------------ - * - * This is for our IOC so have a unique key/target for - * our core features. - */ - -export const Core = { - Events: 'Events', - Logger: 'Logger' -}; diff --git a/src/core/api/Exception.ts b/src/core/api/Exception.ts deleted file mode 100755 index 8027f5ca..00000000 --- a/src/core/api/Exception.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * core.api.Exception - * ------------------------------------------------ - * - * We use this extend error for our custom errors, which we - * call exceptions. They have a code property for the http-status, - * global message and a body, which we will return as a json. - */ - -export const isException = Symbol(); - -export class Exception extends Error { - - public code = 500; - public body; - - constructor(code: number, ...args: any[]) { - super(args[0]); - this.code = code; - this.name = this.constructor.name; - this.message = args[0] || 'Unknown error'; - this.body = args[1] || null; - this[isException] = true; - Error.captureStackTrace(this); - } - - public toString(): string { - return `${this.code} - ${this.constructor.name}:${this.message}`; - } -} diff --git a/src/core/api/RequestBody.ts b/src/core/api/RequestBody.ts deleted file mode 100644 index ada73028..00000000 --- a/src/core/api/RequestBody.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * core.api.RequestBody - * ------------------------------------------------ - * - * This class is used to verify a valid payload an prepare - * it for further actions in the services. To validate we - * use the module 'class-validator'. - * - * If you want to skip missing properties just override the - * validate method in your extended request class. - */ - -import 'reflect-metadata'; -import { validate } from 'class-validator'; -import { ValidationException } from '../../api/exceptions/ValidationException'; - - -export class RequestBody { - - /** - * Creates an instance of RequestBody and if a input is given - * we store the values into the correct property - */ - constructor(input?: any) { - if (input) { - const keys = Object.keys(input); - keys.forEach((key) => { - this[key] = input[key]; - }); - } - } - - /** - * Validates the body on the basis of the validator-annotations - */ - public async validate(skipMissingProperties: boolean = false): Promise { - const errors = await validate(this, { skipMissingProperties }); - if (errors && errors.length > 0) { - throw new ValidationException('Request body is not valid', errors); - } - return; - } - -} diff --git a/src/core/api/Validate.ts b/src/core/api/Validate.ts deleted file mode 100644 index cd0fca53..00000000 --- a/src/core/api/Validate.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * core.api.Validate - * ------------------------------------------------ - * - * Those annotations are used to simplify the use of request (payload) - * validation. The '@Request(RequestBodyClass)' annotation defines the - * the validation rules with his parameter and the '@Validate' runs all - * the given validation classes. - */ - -import 'reflect-metadata'; -import { RequestBody } from './RequestBody'; - - -const requestMetadataKey = Symbol('ValidateRequest'); - -interface RequestParameter { - request: typeof RequestBody; - index: number; -} - -/** - * Request annotation marks the parameters, which should be validated as a RequestBody. - * - * @param request - */ -export const request = (requestBody: typeof RequestBody) => (target: object, propertyKey: string | symbol, parameterIndex: number): any => { - const existingRequestParameters: RequestParameter[] = Reflect.getOwnMetadata(requestMetadataKey, target, propertyKey) || []; - existingRequestParameters.push({ - request: requestBody, - index: parameterIndex - }); - Reflect.defineMetadata(requestMetadataKey, existingRequestParameters, target, propertyKey); -}; - -/** - * Validate annotation builds the given RequestBodies and validates them - * - * @param target - * @param propertyName - * @param descriptor - */ -export const validate = () => (target: any, propertyName: string, descriptor: TypedPropertyDescriptor): any => { - const method = descriptor.value; - descriptor.value = async function(...args: any[]): Promise { - const requestParameters: RequestParameter[] = Reflect.getOwnMetadata(requestMetadataKey, target, propertyName); - if (requestParameters.length > 0) { - for (const requestParameter of requestParameters) { - const requestBody = new requestParameter.request(args[requestParameter.index]); - await requestBody.validate(); - } - } - return method && method.apply(this, args); - }; - - return descriptor; -}; diff --git a/src/core/api/events.ts b/src/core/api/events.ts deleted file mode 100644 index 04212723..00000000 --- a/src/core/api/events.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * core.api.events - * ----------------------------------- - * - * TODO - */ - -import { EventEmitter } from 'events'; -export { EventEmitter } from 'events'; -export const events = new EventEmitter(); diff --git a/src/core/api/exceptionHandler.ts b/src/core/api/exceptionHandler.ts deleted file mode 100644 index a61e2b64..00000000 --- a/src/core/api/exceptionHandler.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * core.api.exceptionHandler - * ------------------------------------------------ - * - * This handler catches all thrown exceptions from the api layer. Afterwards it - * send them directly to the client or otherwise it calls the next middleware. - */ - -import { Environment } from '../helpers/Environment'; -import { Exception, isException } from '../api/Exception'; - - -export const exceptionHandler = (error: Exception | Error, req: myExpress.Request, res: myExpress.Response, next: myExpress.NextFunction) => { - if (error instanceof Exception || error[isException]) { - res.failed(error['code'], error.message, error['body'] || null); - next(); - } else { - if (Environment.isDevelopment()) { - console.error(error.stack); - } - res.failed(500, 'Something broke!', error['body'] || null); - next(error); - } -}; diff --git a/src/core/api/extendExpressResponse.ts b/src/core/api/extendExpressResponse.ts deleted file mode 100644 index 2788d32b..00000000 --- a/src/core/api/extendExpressResponse.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * core.api.extendExpressResponse - * ------------------------------------------------ - * - * We use this middleware to extend the express response object, so - * we can access the new functionality in our controllers. The extension - * should simplify common responses. - */ - -import * as express from 'express'; - - -export const extendExpressResponse = (req: myExpress.Request, res: myExpress.Response, next: express.NextFunction) => { - - /** - * 200 - OK - * This is used for successful responses and a json body - */ - res.ok = (data: T, options: myExpress.ResponseOptions = {}) => { - res.status(200); - return res.json(bodySuccessful(data, options)); - }; - - /** - * 201 - Created - * This is used for created resources - */ - res.created = (data: T, options: myExpress.ResponseOptions = {}) => { - res.status(201); - return res.json(bodySuccessful(data, options)); - }; - - /** - * 200 - Found - * Like the ok function - */ - res.found = (data: T, options: myExpress.ResponseOptions = {}) => { - return res.ok(data, options); - }; - - /** - * 200 - Updated - * Like the ok function - */ - res.updated = (data: T, options: myExpress.ResponseOptions = {}) => { - return res.ok(data, options); - }; - - /** - * 200 - Destroyed - * This is the response after a resource has been removed - */ - res.destroyed = (options: myExpress.ResponseOptions = {}) => { - res.status(200); - return res.json(bodySuccessful(null)); - }; - - /** - * 400-500 - Failed - * This is used when a request has failed - */ - res.failed = (status: number, message: string, error?: any) => { - res.status(status); - return res.json(bodyFailed(message, error)); - }; - - /** - * 503 - Service Unavailable - * This is used when a service is unavailable - */ - res.unavailable = () => { - res.status(503); - return res.json(bodyFailed('Service unavailable')); - }; - - next(); -}; - - -/** - * This body parser is used to show successful responses to the client - */ -export function bodySuccessful(data: T, options: myExpress.ResponseOptions = {}): any { - return { - success: true, - ...prepareMessage(options.message), - ...prepareLinks(options.links), - data - }; -} - -/** - * This body parse is used for error messages to the client - */ -export function bodyFailed(message: string, error?: any): any { - return { - success: false, - message, - ...{ error } - }; -} - -/////////////////////////////////////////////////////// -function prepareMessage(value?: string): any { - if (value) { - return { message: value }; - } - return; -} - -function prepareLinks(values?: myExpress.ResponseLinks[]): any { - if (values) { - return { links: values }; - } - return; -} diff --git a/src/core/database/BluePrint.ts b/src/core/database/BluePrint.ts deleted file mode 100644 index 433c40ff..00000000 --- a/src/core/database/BluePrint.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * core.database.BluePrint - * ------------------------------------------------ - */ - -import * as bookshelf from 'bookshelf'; - - -export class BluePrint { - constructor( - public Model: typeof bookshelf.Model, - public callback: (faker: Faker.FakerStatic, args: any[]) => any) { } -} diff --git a/src/core/database/Factory.ts b/src/core/database/Factory.ts deleted file mode 100644 index 2e826303..00000000 --- a/src/core/database/Factory.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * core.database.Factory - * ------------------------------------------------ - */ - -import * as Faker from 'faker'; -import * as bookshelf from 'bookshelf'; -import { BluePrint } from './BluePrint'; -import { ModelFactory } from './ModelFactory'; - - -export class Factory { - - public static getInstance(): Factory { - if (!Factory.instance) { - Factory.instance = new Factory(Faker); - } - return Factory.instance; - } - - private static instance: Factory; - - private blueprints: { [key: string]: BluePrint }; - - constructor(private faker: Faker.FakerStatic) { - this.blueprints = {}; - } - - public define(ModelStatic: any, callback: (faker: Faker.FakerStatic, args: any[]) => any): void { - this.blueprints[this.getNameOfModel(ModelStatic)] = new BluePrint(ModelStatic, callback); - } - - public get(ModelStatic: any, ...args: any[]): ModelFactory { - return new ModelFactory( - this.faker, - this.blueprints[this.getNameOfModel(ModelStatic)], - args - ); - } - - private getNameOfModel(Model: typeof bookshelf.Model): string { - return new Model().constructor.name; - } - -} diff --git a/src/core/database/ModelFactory.ts b/src/core/database/ModelFactory.ts deleted file mode 100644 index b0a49a3b..00000000 --- a/src/core/database/ModelFactory.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * core.database.ModelFactory - * ------------------------------------------------ - */ - -import { BluePrint } from './BluePrint'; - - -export class ModelFactory { - - private identifier = 'id'; - private eachFn: (obj: any, faker: Faker.FakerStatic) => Promise; - - constructor( - private faker: Faker.FakerStatic, - private blueprint: BluePrint, - private args: any[]) { - } - - public returning(identifier: string): ModelFactory { - this.identifier = identifier; - return this; - } - - public each(iterator: (obj: any) => Promise): ModelFactory { - this.eachFn = iterator; - return this; - } - - public async create(amount: number = 1): Promise { - const results = [] as any; - for (let i = 0; i < amount; i++) { - const obj = await this.build(); - results.push(obj); - if (typeof this.eachFn === 'function') { - await this.eachFn(obj, this.faker); - } - } - if (amount === 1) { - return results[0]; - } - return results; - } - - private async build(): Promise { - const obj = await this.makeEntity(this.blueprint.callback(this.faker, this.args)); - return await new this.blueprint.Model(obj).save(); - } - - private async makeEntity(entity: any): Promise { - for (const attribute in entity) { - if (entity.hasOwnProperty(attribute)) { - if (typeof entity[attribute] === 'object' && entity[attribute] instanceof ModelFactory) { - const modelFactory: ModelFactory = entity[attribute]; - const subEntity = await modelFactory.build(); - entity[attribute] = subEntity[this.identifier]; - } - } - } - return entity; - } -} diff --git a/src/core/database/index.ts b/src/core/database/index.ts deleted file mode 100644 index 69f4ef33..00000000 --- a/src/core/database/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './BluePrint'; -export * from './Factory'; -export * from './ModelFactory'; diff --git a/src/core/helpers/Environment.ts b/src/core/helpers/Environment.ts deleted file mode 100755 index fa9640d6..00000000 --- a/src/core/helpers/Environment.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * core.Environment - * ------------------------------------ - * - * Helps us to simplify 'process.env' and also provide - * the content of the package.json. - */ -import * as packageInfo from '../../../package.json'; - - -export class Environment { - - public static getNodeEnv(): string { - return process.env.NODE_ENV || 'development'; - } - - public static isTest(): boolean { - return this.getNodeEnv() === 'test'; - } - - public static isDevelopment(): boolean { - return this.getNodeEnv() === 'development'; - } - - public static isProduction(): boolean { - return this.getNodeEnv() === 'production'; - } - - public static getPkg(): any { - return packageInfo; - } - - public static isTruthy(bool: string): boolean { - try { - return bool.toLowerCase() === 'true'; - } catch (e) { - return false; - } - } - -} diff --git a/src/database/factories/index.ts b/src/database/factories/index.ts deleted file mode 100755 index f86b9d56..00000000 --- a/src/database/factories/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * database.factories - * ---------------------------------------- - * - * Define all your model-factories here. These model-factories are used to seed - * data very easy into your database. - */ - -import { Factory } from '../../core/database/Factory'; -import { User } from '../../api/models/User'; - -export * from '../../core/database/Factory'; - - -const factory = Factory.getInstance(); - -/** - * USER - Factory - */ -factory.define(User, (faker: Faker.FakerStatic) => { - const gender = faker.random.number(1); - const fn = faker.name.firstName(gender); - const ln = faker.name.lastName(gender); - const e = faker.internet.email(fn, ln); - return { - firstName: fn, - lastName: ln, - email: e, - auth0UserId: 'auth0|' + e, - picture: faker.internet.avatar() - }; -}); diff --git a/src/database/migrations/20170120183349_create_users_table.ts b/src/database/migrations/20170120183349_create_users_table.ts deleted file mode 100644 index 23ff7820..00000000 --- a/src/database/migrations/20170120183349_create_users_table.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as Knex from 'knex'; - - -exports.up = (db: Knex): Promise => { - return Promise.all([ - db.schema.createTable('users', (table: Knex.CreateTableBuilder) => { - table.increments('id').primary(); - - table.string('first_name').notNullable(); - table.string('last_name').notNullable(); - table.string('email').notNullable().unique(); - table.string('auth_0_user_id').unique(); - table.string('picture'); - - table.timestamp('updated_at').defaultTo(db.fn.now()); - table.timestamp('created_at').defaultTo(db.fn.now()); - }) - ]); -}; - -exports.down = (db: Knex): Promise => { - return Promise.all([ - db.schema.dropTable('users') - ]); -}; diff --git a/src/database/seeds/create_users.ts b/src/database/seeds/create_users.ts deleted file mode 100644 index f775cc0c..00000000 --- a/src/database/seeds/create_users.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as Knex from 'knex'; - -import { Factory } from '../factories'; -import { User } from '../../api/models/User'; - - -exports.seed = async (db: Knex) => { - const factory = Factory.getInstance(); - await factory.get(User) - .create(10); -}; diff --git a/test/unit/api/middlewares/AuthenticateMiddleware.test.ts b/test/unit/api/middlewares/AuthenticateMiddleware.test.ts deleted file mode 100644 index 26f58c9b..00000000 --- a/test/unit/api/middlewares/AuthenticateMiddleware.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { AuthenticateMiddleware } from '../../../../src/api/middlewares/AuthenticateMiddleware'; -import { LogMock } from '../../lib/LogMock'; - -describe('AuthenticateMiddleware', () => { - - let authenticate; - let request; - let res; - let req; - let next; - beforeEach(() => { - process.env.AUTH0_HOST = 'test'; - request = jest.fn(); - authenticate = new AuthenticateMiddleware(LogMock, request); - res = { - failed: jest.fn() - }; - req = { - headers: { authorization: 'Bearer 1234' } - }; - next = jest.fn(); - }); - - test('Should fail because no token was given', () => { - req.headers.authorization = undefined; - authenticate.use(req, res, next); - expect(res.failed).toHaveBeenCalledWith(403, 'You are not allowed to request this resource!'); - - req.headers.authorization = ''; - authenticate.use(req, res, next); - expect(res.failed).toHaveBeenCalledWith(403, 'You are not allowed to request this resource!'); - - }); - - test('Should set the correct request options', () => { - request = (options, done) => { - expect(options.method).toBe('POST'); - expect(options.url).toBe('test/tokeninfo'); - expect(options.form.id_token).toBe('1234'); - }; - const auth = new AuthenticateMiddleware(LogMock, request); - auth.use(req, res, next); - }); - - test('Should pass and add the token info to the request object', () => { - request = (options, done) => { - done(null, { statusCode: 200 }, '{ "user_id": 77, "email": "test@jest.org" }'); - expect(req.tokeninfo.user_id).toBe(77); - expect(next).toHaveBeenCalled(); - }; - const auth = new AuthenticateMiddleware(LogMock, request); - auth.use(req, res, next); - }); - - test('Should fail and respond with a 401 error', () => { - request = (options, done) => { - done(null, { statusCode: 401 }, 'Bad message :-)'); - expect(res.failed).toHaveBeenCalledWith(401, 'Bad message :-)'); - }; - const auth = new AuthenticateMiddleware(LogMock, request); - auth.use(req, res, next); - }); - -}); diff --git a/test/unit/api/middlewares/PopulateUserMiddleware.test.ts b/test/unit/api/middlewares/PopulateUserMiddleware.test.ts deleted file mode 100644 index 32ad5ac3..00000000 --- a/test/unit/api/middlewares/PopulateUserMiddleware.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PopulateUserMiddleware } from '../../../../src/api/middlewares/PopulateUserMiddleware'; -import { LogMock } from '../../lib/LogMock'; - - -describe('PopulateUserMiddleware', () => { - - let populateUser; - let userService; - let res; - let req; - let next; - beforeEach(() => { - process.env.AUTH0_HOST = 'test'; - populateUser = new PopulateUserMiddleware(LogMock, userService); - res = { - failed: jest.fn() - }; - req = { - tokeninfo: { user_id: 77 } - }; - next = jest.fn(); - }); - - test('Should fail because no tokeninfo or user_id is given', () => { - req.tokeninfo.user_id = undefined; - populateUser.use(req, res, next); - expect(res.failed).toBeCalledWith(400, 'Missing token information!'); - req.tokeninfo = undefined; - populateUser.use(req, res, next); - expect(res.failed).toBeCalledWith(400, 'Missing token information!'); - }); - - test('Should pass the database query and attache the user to the request object', () => { - userService = { - findByUserId: jest.fn().mockImplementation(() => { - return new Promise((resolve, reject) => { - resolve({ - toJSON: () => ({ - id: 88 - }) - }); - expect(req.user.id).toBe(88); - expect(next).toBeCalled(); - }); - }) - }; - const pop = new PopulateUserMiddleware(LogMock, userService); - pop.use(req, res, next); - }); - - test('Should behave...', () => { - userService = { - findByUserId: jest.fn().mockImplementation(() => { - return new Promise((resolve, reject) => { - reject(new Error('test')); - expect(req.user).toBeUndefined(); - expect(next).toBeCalled(); - }); - }) - }; - const pop = new PopulateUserMiddleware(LogMock, userService); - pop.use(req, res, next); - }); - -}); diff --git a/test/unit/core/Environment.test.ts b/test/unit/core/Environment.test.ts deleted file mode 100644 index c80fa0e1..00000000 --- a/test/unit/core/Environment.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Environment } from '../../../src/core/helpers/Environment'; - - -describe('Environment', () => { - test('getName() should return the test env', () => { - expect(Environment.getNodeEnv()).toBe('test'); - }); - - test('isTest() should be true', () => { - expect(Environment.isTest()).toBeTruthy(); - }); - - test('isDevelopment() should be false', () => { - expect(Environment.isDevelopment()).toBeFalsy(); - }); - - test('isProduction() should be false', () => { - expect(Environment.isProduction()).toBeFalsy(); - }); -}); diff --git a/test/unit/core/api/Exception.test.ts b/test/unit/core/api/Exception.test.ts deleted file mode 100644 index 960fe771..00000000 --- a/test/unit/core/api/Exception.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Exception, isException } from '../../../../src/core/api/Exception'; - - -describe('Exception', () => { - test('Should have the correct properties', () => { - const exception = new Exception(400, 'message', { success: false }); - expect(exception.code).toBe(400); - expect(exception.message).toBe('message'); - expect(exception.body.success).toBe(false); - expect(exception[isException]).toBeTruthy(); - }); - test('Should have the correct properties', () => { - const exception = new Exception(400, 'message', { success: false }); - expect(exception.code).toBe(400); - expect(exception.message).toBe('message'); - expect(exception.body.success).toBe(false); - expect(exception[isException]).toBeTruthy(); - }); - test('Should return a string with the code, name and message of the exception', () => { - const exception = new Exception(400, 'message', { success: false }); - expect(exception.toString()).toBe(`400 - Exception:message`); - }); -}); diff --git a/test/unit/core/api/RequestBody.test.ts b/test/unit/core/api/RequestBody.test.ts deleted file mode 100644 index 6e88f6b7..00000000 --- a/test/unit/core/api/RequestBody.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { RequestBody } from '../../../../src/core/api/RequestBody'; -import { IsNotEmpty } from 'class-validator'; - - -describe('RequestBody', () => { - describe('constructor', () => { - test('Should construct with the given input and set all values', () => { - const r = new RequestBody({ a: 1 }); - expect(r['a']).toBe(1); - }); - test('Should construct with the given input and set all values', () => { - const r = new RequestBody({ a: 1 }); - expect(r['a']).toBe(1); - }); - }); - describe('validate', () => { - test('Should pass if no validators are defined', async () => { - const r = new RequestBody(); - const e = await r.validate(); - expect(e).toBe(undefined); - }); - test('Should pass if no validators are defined', async () => { - class TestBody extends RequestBody { - @IsNotEmpty() public value: string; - } - const r = new TestBody(); - try { - await r.validate(); - } catch (error) { - expect(error.name).toBe('ValidationException'); - } - }); - }); - // describe('toJSON', () => { - // test('Should return the set values as new json object', () => { - // const r = new RequestBody({ a: 1 }); - // const o = r.toJSON(); - // expect(o.a).toBe(1); - // }); - // }); - // describe('set', () => { - // test('Should set value the key property', () => { - // class TestBody extends RequestBody { - // @IsNotEmpty() value: string; - - // setValue(value: string): void { - // this.set('value', value); - // } - // } - // const tb = new TestBody(); - // tb.setValue('yes'); - // expect(tb.value).toBe('yes'); - // }); - // }); - // describe('update', () => { - // test('Should set value the key property', () => { - // class TestBody extends RequestBody { - // @IsNotEmpty() valueA: string; - // @IsNotEmpty() valueB: string; - - // updateValue(key: string, value: any): void { - // this.update(key, value); - // } - // } - // const tb = new TestBody({ - // valueA: 'no', - // valueB: 'no' - // }); - // tb.updateValue('valueA', 'yes'); - // tb.updateValue('valueB', undefined); - // expect(tb.valueA).toBe('yes'); - // expect(tb.valueB).toBe('no'); - // }); - // }); -}); diff --git a/test/unit/core/api/exceptionHandler.test.ts b/test/unit/core/api/exceptionHandler.test.ts deleted file mode 100644 index be935332..00000000 --- a/test/unit/core/api/exceptionHandler.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { exceptionHandler } from '../../../../src/core/api/exceptionHandler'; -import { Exception } from '../../../../src/core/api/Exception'; - - -describe('exceptionHandler', () => { - - let req; - let res; - let next; - beforeEach(() => { - req = {}; - res = { - failed: jest.fn() - }; - next = jest.fn(); - }); - - test('Should fail, because it is a exception with the given error code and body', () => { - const e = new Exception(400, 'message', { success: false }); - exceptionHandler(e, req, res, next); - expect(res.failed).toHaveBeenCalledWith(400, 'message', { success: false }); - }); - - test('Should fail, because it is a exception with the given error code', () => { - const e = new Exception(400, 'message'); - exceptionHandler(e, req, res, next); - expect(res.failed).toHaveBeenCalledWith(400, 'message', null); - }); - - test('Should fail, because it is a system error with 500', () => { - const e = new Error('message'); - exceptionHandler(e, req, res, next); - expect(res.failed).toHaveBeenCalledWith(500, 'Something broke!', null); - }); -}); diff --git a/test/unit/core/api/extendExpressResponse.test.ts b/test/unit/core/api/extendExpressResponse.test.ts deleted file mode 100644 index 6cc13166..00000000 --- a/test/unit/core/api/extendExpressResponse.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { extendExpressResponse } from '../../../../src/core/api/extendExpressResponse'; - - -describe('extendExpressResponse', () => { - - let req; - let res; - let next; - beforeEach(() => { - req = {}; - res = { - status: jest.fn(), - json: jest.fn() - }; - next = jest.fn(); - }); - - test('Should have all the response helpers', () => { - extendExpressResponse(req, res, next); - expect(typeof res.ok).toBe('function'); - expect(typeof res.created).toBe('function'); - expect(typeof res.found).toBe('function'); - expect(typeof res.updated).toBe('function'); - expect(typeof res.destroyed).toBe('function'); - expect(typeof res.failed).toBe('function'); - }); - - describe('ok', () => { - test('Should create to correct response format', () => { - extendExpressResponse(req, res, next); - res.ok({ name: 'hans' }); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - success: true, - data: { name: 'hans' } - }); - }); - test('Should create to correct response format with a message', () => { - extendExpressResponse(req, res, next); - res.ok({ name: 'hans' }, { message: 'message' }); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - success: true, - message: 'message', - data: { name: 'hans' } - }); - }); - test('Should create to correct response format with a links', () => { - extendExpressResponse(req, res, next); - res.ok({ name: 'hans' }, { - links: { - self: 'link' - } - }); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - success: true, - data: { name: 'hans' }, - links: { - self: 'link' - } - }); - }); - }); - - describe('created', () => { - test('Should create to correct response format', () => { - extendExpressResponse(req, res, next); - res.created({ name: 'hans' }); - expect(res.status).toHaveBeenCalledWith(201); - expect(res.json).toHaveBeenCalledWith({ - success: true, - data: { name: 'hans' } - }); - }); - }); - - describe('found', () => { - test('Should create to correct response format', () => { - extendExpressResponse(req, res, next); - res.found([{ name: 'hans' }]); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - success: true, - data: [{ name: 'hans' }] - }); - }); - }); - - describe('destroyed', () => { - test('Should create to correct response format', () => { - extendExpressResponse(req, res, next); - res.destroyed(); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - success: true, - data: null - }); - }); - }); - - describe('failed', () => { - test('Should create to correct response format', () => { - extendExpressResponse(req, res, next); - res.failed(400, 'message', { name: 'hans' }); - expect(res.status).toHaveBeenCalledWith(400); - expect(res.json).toHaveBeenCalledWith({ - success: false, - message: 'message', - error: { name: 'hans' } - }); - }); - }); - - describe('updated', () => { - test('Should create to correct response format', () => { - extendExpressResponse(req, res, next); - res.updated({ name: 'hans' }); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - success: true, - data: { name: 'hans' } - }); - }); - }); - -}); diff --git a/yarn.lock b/yarn.lock index d962c7fa..c3257d7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,7 +6,7 @@ version "1.0.1" resolved "https://registry.yarnpkg.com/@sindresorhus/df/-/df-1.0.1.tgz#c69b66f52f6fcdd287c807df210305dbaf78500d" -"@types/bluebird@*", "@types/bluebird@^3.5.18": +"@types/bluebird@^3.5.18": version "3.5.18" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6" @@ -17,37 +17,12 @@ "@types/express" "*" "@types/node" "*" -"@types/bookshelf@^0.9.6": - version "0.9.7" - resolved "https://registry.yarnpkg.com/@types/bookshelf/-/bookshelf-0.9.7.tgz#6911d62992b095b9088fa8254f7d0bf6b4547666" - dependencies: - "@types/bluebird" "*" - "@types/create-error" "*" - "@types/knex" "*" - "@types/lodash" "*" - -"@types/chalk@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" - dependencies: - chalk "*" - -"@types/commander@^2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.11.0.tgz#7fc765ccad14827e2babd6a99583359ff3e40563" - dependencies: - "@types/node" "*" - "@types/cors@^2.8.1": version "2.8.3" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.3.tgz#eaf6e476da0d36bee6b061a24d57e343ddce86d6" dependencies: "@types/express" "*" -"@types/create-error@*": - version "0.3.30" - resolved "https://registry.yarnpkg.com/@types/create-error/-/create-error-0.3.30.tgz#dc692a2b0a5d70d85dbf01fef2f6d41232255df3" - "@types/dotenv@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-4.0.2.tgz#f2983f0afe9fd7f6eb8aef515a4d77f4cbaa0597" @@ -68,65 +43,17 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" -"@types/faker@^4.1.1": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@types/faker/-/faker-4.1.2.tgz#f8ab50c9f9af68c160dd71b63f83e24b712d0df5" - -"@types/form-data@*": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" - dependencies: - "@types/node" "*" - -"@types/glob@^5.0.33": - version "5.0.33" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.33.tgz#3dff7c6ce09d65abe919c7961dc3dee016f36ad7" - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/handlebars@^4.0.36": - version "4.0.36" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.36.tgz#ff57c77fa1ab6713bb446534ddc4d979707a3a79" - "@types/helmet@^0.0.37": version "0.0.37" resolved "https://registry.yarnpkg.com/@types/helmet/-/helmet-0.0.37.tgz#6b150f30219c0d6563e8e0a3fbcad8dd90fa4f68" dependencies: "@types/express" "*" -"@types/inquirer@^0.0.35": - version "0.0.35" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.35.tgz#e054657cf2d10963823957d4d06ec244f05c65be" - dependencies: - "@types/rx" "*" - "@types/through" "*" - "@types/jest@^21.1.5": version "21.1.6" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.6.tgz#9467945ce33261e4fdd14276576951aa130515aa" -"@types/jsonwebtoken@^7.2.3": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.3.tgz#483c8f39945e1e6d308dcc51fd4aeca5208d4dca" - dependencies: - "@types/node" "*" - -"@types/knex@*": - version "0.0.65" - resolved "https://registry.yarnpkg.com/@types/knex/-/knex-0.0.65.tgz#e65a70b5bb617f11e65aac301070ef7f1eeddfd8" - dependencies: - "@types/bluebird" "*" - "@types/node" "*" - -"@types/knex@^0.0.64": - version "0.0.64" - resolved "https://registry.yarnpkg.com/@types/knex/-/knex-0.0.64.tgz#919d91cec9ddb9e9c44794425068a256b4a72b7b" - dependencies: - "@types/bluebird" "*" - "@types/node" "*" - -"@types/lodash@*", "@types/lodash@^4.14.80": +"@types/lodash@^4.14.80": version "4.14.85" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.85.tgz#a16fbf942422f6eca5622b6910492c496c35069b" @@ -134,10 +61,6 @@ version "2.0.0" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" -"@types/minimatch@*": - version "2.0.29" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" - "@types/morgan@^1.7.35": version "1.7.35" resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.7.35.tgz#6358f502931cc2583d7a94248c41518baa688494" @@ -148,123 +71,10 @@ version "8.0.53" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8" -"@types/pluralize@^0.0.28": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.28.tgz#daf46f90fdaa6172600bfeac81e46065177f03ea" - "@types/reflect-metadata@0.0.5": version "0.0.5" resolved "https://registry.yarnpkg.com/@types/reflect-metadata/-/reflect-metadata-0.0.5.tgz#9c042bfa9803d577aad4f57dfbca4b7cae4286fe" -"@types/request-promise@^4.1.39": - version "4.1.39" - resolved "https://registry.yarnpkg.com/@types/request-promise/-/request-promise-4.1.39.tgz#5c082bd20ae0a0caff83ba3a13e589ddacec5326" - dependencies: - "@types/bluebird" "*" - "@types/request" "*" - -"@types/request@*": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.0.7.tgz#a2aa5a57317c21971d9b024e393091ab2c99ab98" - dependencies: - "@types/form-data" "*" - "@types/node" "*" - -"@types/request@^2.0.7": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.0.8.tgz#424d3de255868107ed4dd6695c65c5f1766aba80" - dependencies: - "@types/form-data" "*" - "@types/node" "*" - -"@types/rx-core-binding@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3" - dependencies: - "@types/rx-core" "*" - -"@types/rx-core@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60" - -"@types/rx-lite-aggregates@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-async@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-backpressure@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-coincidence@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-experimental@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-joinpatterns@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-testing@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9" - dependencies: - "@types/rx-lite-virtualtime" "*" - -"@types/rx-lite-time@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-virtualtime@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.4.tgz#710ebf89d0a2d596c21047d91b1242bcef51c30b" - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - -"@types/rx@*": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48" - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - "@types/rx-lite" "*" - "@types/rx-lite-aggregates" "*" - "@types/rx-lite-async" "*" - "@types/rx-lite-backpressure" "*" - "@types/rx-lite-coincidence" "*" - "@types/rx-lite-experimental" "*" - "@types/rx-lite-joinpatterns" "*" - "@types/rx-lite-testing" "*" - "@types/rx-lite-time" "*" - "@types/rx-lite-virtualtime" "*" - "@types/serve-favicon@^2.2.29": version "2.2.30" resolved "https://registry.yarnpkg.com/@types/serve-favicon/-/serve-favicon-2.2.30.tgz#5bea4ead966a9ad5e0f409141edba0cebf20da73" @@ -278,12 +88,6 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/through@*": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.28.tgz#1effa9a6d00fb48572b4cc9f44df25b0100db7fc" - dependencies: - "@types/node" "*" - "@types/uuid@^3.4.3": version "3.4.3" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" @@ -301,15 +105,8 @@ abab@^1.0.3: resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" abbrev@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" - -accepts@1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" - dependencies: - mime-types "~2.1.11" - negotiator "0.6.1" + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" accepts@~1.3.4: version "1.3.4" @@ -328,9 +125,12 @@ acorn@^4.0.4: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" ajv@^5.1.0: version "5.3.0" @@ -400,11 +200,11 @@ any-shell-escape@^0.1.1: resolved "https://registry.yarnpkg.com/any-shell-escape/-/any-shell-escape-0.1.1.tgz#d55ab972244c71a9a5e1ab0879f30bf110806959" anymatch@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" dependencies: - arrify "^1.0.0" micromatch "^2.1.5" + normalize-path "^2.0.0" append-transform@^0.4.0: version "0.4.0" @@ -413,8 +213,8 @@ append-transform@^0.4.0: default-require-extensions "^1.0.0" aproba@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.1.tgz#95d3600f07710aa0e9298c726ad5ecf2eacbabab" + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" are-we-there-yet@~1.1.2: version "1.1.4" @@ -477,10 +277,6 @@ array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" -arraybuffer.slice@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" - arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -493,6 +289,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -506,8 +306,8 @@ async@^1.4.0: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.1.4: - version "2.4.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" dependencies: lodash "^4.14.0" @@ -519,11 +319,15 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws4@^1.6.0: +aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" @@ -535,31 +339,7 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^6.0.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.24.1.tgz#8c428564dce1e1f41fb337ec34f4c3b022b5ad83" - dependencies: - babel-code-frame "^6.22.0" - babel-generator "^6.24.1" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - babylon "^6.11.0" - convert-source-map "^1.1.0" - debug "^2.1.1" - json5 "^0.5.0" - lodash "^4.2.0" - minimatch "^3.0.2" - path-is-absolute "^1.0.0" - private "^0.1.6" - slash "^1.0.0" - source-map "^0.5.0" - -babel-core@^6.24.1, babel-core@^6.26.0: +babel-core@^6.0.0, babel-core@^6.24.1, babel-core@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" dependencies: @@ -583,7 +363,7 @@ babel-core@^6.24.1, babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.6" -babel-generator@^6.18.0, babel-generator@^6.24.1, babel-generator@^6.26.0: +babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" dependencies: @@ -616,15 +396,7 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-istanbul@^4.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.4.tgz#18dde84bf3ce329fddf3f4103fae921456d8e587" - dependencies: - find-up "^2.1.0" - istanbul-lib-instrument "^1.7.2" - test-exclude "^4.1.1" - -babel-plugin-istanbul@^4.1.4: +babel-plugin-istanbul@^4.0.0, babel-plugin-istanbul@^4.1.4: version "4.1.5" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" dependencies: @@ -663,7 +435,7 @@ babel-preset-jest@^21.2.0: babel-plugin-jest-hoist "^21.2.0" babel-plugin-syntax-object-rest-spread "^6.13.0" -babel-register@^6.24.1, babel-register@^6.26.0: +babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -675,14 +447,7 @@ babel-register@^6.24.1, babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.6.1: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.10.0" - -babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2: +babel-runtime@^6.18.0, 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: @@ -699,7 +464,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.18.0, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -722,34 +487,14 @@ babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.0: lodash "^4.17.4" to-fast-properties "^1.0.3" -babylon@^6.11.0, babylon@^6.13.0, babylon@^6.18.0: +babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - -base64id@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" - -base64url@2.0.0, base64url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" - -basic-auth@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" - basic-auth@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" @@ -762,23 +507,9 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - dependencies: - callsite "1.0.0" - -bignumber.js@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.0.4.tgz#7c40f5abcd2d6623ab7b99682ee7db81b11889a4" - binary-extensions@^1.0.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" - -blob@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + version "1.10.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" block-stream@*: version "0.0.9" @@ -786,10 +517,6 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.4.3, bluebird@^3.4.6, bluebird@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - body-parser@1.18.2, body-parser@^1.18.2: version "1.18.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" @@ -805,24 +532,11 @@ body-parser@1.18.2, body-parser@^1.18.2: raw-body "2.3.2" type-is "~1.6.15" -bookshelf-camelcase@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/bookshelf-camelcase/-/bookshelf-camelcase-2.0.1.tgz#0d7791f2aabee6d98d56c87ce10ccb0f81a6553c" - dependencies: - lodash.camelcase "~4.3.0" - lodash.snakecase "~4.1.0" - -bookshelf@^0.10.4: - version "0.10.4" - resolved "https://registry.yarnpkg.com/bookshelf/-/bookshelf-0.10.4.tgz#fe06984456740417aba13d0d76c7b065d1f1774d" +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" dependencies: - babel-runtime "^6.6.1" - bluebird "^3.4.3" - chalk "^1.0.0" - create-error "~0.3.1" - inflection "^1.5.1" - inherits "~2.0.1" - lodash "^4.13.1" + hoek "2.x.x" boom@4.x.x: version "4.3.1" @@ -889,10 +603,6 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -901,14 +611,6 @@ bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" @@ -951,14 +653,6 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@*, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - chalk@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" @@ -979,6 +673,14 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + chokidar@^1.6.0, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -995,10 +697,14 @@ chokidar@^1.6.0, chokidar@^1.7.0: fsevents "^1.0.0" ci-info@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + 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-validator@^0.7.3: +class-validator@^0.7.0, class-validator@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.7.3.tgz#3c2821b8cf35fd8d5f4fcb8063bc57fb50049e7e" dependencies: @@ -1008,16 +714,6 @@ cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - dependencies: - restore-cursor "^2.0.0" - -cli-width@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" - cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -1066,7 +762,7 @@ commander@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" -commander@^2.11.0, commander@^2.2.0, commander@^2.7.1, commander@^2.9.0: +commander@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -1076,18 +772,6 @@ common-tags@^1.4.0: dependencies: babel-runtime "^6.18.0" -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - -component-emitter@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - compressible@~2.0.11: version "2.0.12" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" @@ -1170,7 +854,7 @@ content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" -convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0: +convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -1178,29 +862,14 @@ cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" -cookie@0.3.1: +cookie@0.3.1, cookie@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -copyfiles@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" - dependencies: - glob "^7.0.5" - ltcdr "^2.2.1" - minimatch "^3.0.3" - mkdirp "^0.5.1" - noms "0.0.0" - through2 "^2.0.1" - -core-js@^2.4.0, core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" -core-js@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1264,10 +933,6 @@ create-error-class@^3.0.0, create-error-class@^3.0.1: dependencies: capture-stack-trace "^1.0.0" -create-error@~0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/create-error/-/create-error-0.3.1.tgz#69810245a629e654432bf04377360003a5351a23" - cross-env@^3.1.4: version "3.2.4" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.2.4.tgz#9e0585f277864ed421ce756f81a980ff0d698aba" @@ -1297,6 +962,12 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + cryptiles@3.x.x: version "3.1.2" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" @@ -1345,12 +1016,18 @@ date-fns@^1.23.0: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" -debug@2.6.9, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: ms "2.0.0" +debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1385,23 +1062,17 @@ destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" -detect-file@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" - dependencies: - fs-exists-sync "^0.1.0" - detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" dependencies: repeating "^2.0.0" -diff@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" +detect-libc@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.2.tgz#71ad5d204bf17a6a6ca8f450c61454066ef461e1" -diff@^3.2.0: +diff@^3.1.0, diff@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" @@ -1409,13 +1080,6 @@ dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" -doctrine@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - dont-sniff-mimetype@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58" @@ -1456,13 +1120,6 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -ecdsa-sig-formatter@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" - dependencies: - base64url "^2.0.0" - safe-buffer "^5.0.1" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1471,46 +1128,7 @@ encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" -engine.io-client@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.1.3.tgz#d705e48985dfe8b54a98c9f77052b8b08258be05" - dependencies: - component-emitter "1.2.1" - component-inherit "0.0.3" - debug "~2.6.9" - engine.io-parser "~2.1.1" - has-cors "1.1.0" - indexof "0.0.1" - parseqs "0.0.5" - parseuri "0.0.5" - ws "~2.3.1" - xmlhttprequest-ssl "~1.5.4" - yeast "0.1.2" - -engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.1.tgz#e0fb3f0e0462f7f58bb77c1a52e9f5a7e26e4668" - dependencies: - after "0.8.2" - arraybuffer.slice "0.0.6" - base64-arraybuffer "0.1.5" - blob "0.0.4" - has-binary2 "~1.0.2" - -engine.io@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.1.3.tgz#7aecf71bf8a310f9fa21461999c4fcc035f8a877" - dependencies: - accepts "1.3.3" - base64id "1.0.0" - cookie "0.3.1" - debug "~2.6.9" - engine.io-parser "~2.1.0" - ws "~2.3.1" - optionalDependencies: - uws "~0.14.4" - -"errno@>=0.1.1 <0.2.0-0": +errno@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" dependencies: @@ -1522,7 +1140,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es6-promise@^3.0.2, es6-promise@^3.3.1: +es6-promise@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" @@ -1530,9 +1148,9 @@ escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-applescript@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/escape-string-applescript/-/escape-string-applescript-1.0.0.tgz#6f1c2294245d82c63bc03338dc19a94aa8428892" +escape-string-applescript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-applescript/-/escape-string-applescript-2.0.0.tgz#760bca838668e408fe5ee52ce42caf7cb46c5273" escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" @@ -1582,8 +1200,8 @@ event-stream@~3.3.0: through "~2.3.1" exec-sh@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10" + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" dependencies: merge "^1.1.3" @@ -1622,12 +1240,6 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" -expand-tilde@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - dependencies: - os-homedir "^1.0.1" - expect-ct@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.0.tgz#52735678de18530890d8d7b95f0ac63640958094" @@ -1643,22 +1255,7 @@ expect@^21.2.1: jest-message-util "^21.2.1" jest-regex-util "^21.2.0" -express-basic-auth@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.1.3.tgz#18924c02fef18d9efe58e22847ee31e240749f33" - dependencies: - basic-auth "^1.1.0" - -express-status-monitor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/express-status-monitor/-/express-status-monitor-1.0.1.tgz#311288347b7aabfeaec0a01547e55c77652bb298" - dependencies: - debug "^2.6.9" - on-headers "^1.0.1" - pidusage "^1.1.6" - socket.io "^2.0.3" - -express@4.16.2, express@^4.16.2: +express@^4.16.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: @@ -1693,18 +1290,10 @@ express@4.16.2, express@^4.16.2: utils-merge "1.0.1" vary "~1.1.2" -extend@^3.0.0, extend@~3.0.1: +extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" - dependencies: - iconv-lite "^0.4.17" - jschardet "^1.4.2" - tmp "^0.0.31" - extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -1719,10 +1308,6 @@ eyes@0.1.x: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" -faker@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" - fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" @@ -1745,12 +1330,6 @@ figlet@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410" -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - dependencies: - escape-string-regexp "^1.0.5" - file-type@^3.6.0: version "3.9.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" @@ -1809,19 +1388,6 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -findup-sync@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" - dependencies: - detect-file "^0.1.0" - is-glob "^2.0.1" - micromatch "^2.3.7" - resolve-dir "^0.1.0" - -flagged-respawn@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" - for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -1836,6 +1402,14 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + form-data@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -1860,10 +1434,6 @@ from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -1887,11 +1457,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" fsevents@^1.0.0, fsevents@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.1.tgz#f19fd28f43eeaf761680e519a203c4d0b3d31aff" + version "1.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" dependencies: nan "^2.3.0" - node-pre-gyp "^0.6.29" + node-pre-gyp "^0.6.39" fstream-ignore@^1.0.5: version "1.0.5" @@ -1923,10 +1493,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generic-pool@^2.4.2: - version "2.5.4" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.5.4.tgz#38c6188513e14030948ec6e5cf65523d9779299b" - get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -1995,22 +1561,6 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" -global-modules@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" - dependencies: - global-prefix "^0.1.4" - is-windows "^0.2.0" - -global-prefix@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" - dependencies: - homedir-polyfill "^1.0.0" - ini "^1.3.4" - is-windows "^0.2.0" - which "^1.2.12" - globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -2080,7 +1630,7 @@ growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" -handlebars@^4.0.11, handlebars@^4.0.3: +handlebars@^4.0.3: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: @@ -2090,10 +1640,21 @@ handlebars@^4.0.11, handlebars@^4.0.3: optionalDependencies: uglify-js "^2.6" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" @@ -2113,16 +1674,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-binary2@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98" - dependencies: - isarray "2.0.1" - -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -2135,6 +1686,15 @@ has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + hawk@~6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" @@ -2175,6 +1735,10 @@ hide-powered-by@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b" +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" @@ -2186,7 +1750,7 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: +homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" dependencies: @@ -2219,6 +1783,14 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2227,7 +1799,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.19, iconv-lite@^0.4.17: +iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -2253,14 +1825,6 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - -inflection@^1.5.1: - version "1.12.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2280,44 +1844,11 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" -inquirer@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - -interpret@^0.6.5: - version "0.6.6" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" - invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: - loose-envify "^1.0.0" - -inversify-express-utils@^4.1.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/inversify-express-utils/-/inversify-express-utils-4.2.2.tgz#dd0a733cfdc7250a09132f7e694afd928ede6583" - dependencies: - express "4.16.2" - -inversify@^4.5.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.5.1.tgz#2f8a249e1fc5346e4f28b4b86dfae5af1b673178" + loose-envify "^1.0.0" invert-kv@^1.0.0: version "1.0.0" @@ -2438,10 +1969,6 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" @@ -2462,26 +1989,14 @@ 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@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - is-windows@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - isemail@2.x.x: version "2.2.1" resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" @@ -2501,17 +2016,17 @@ isstream@0.1.x, isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.1.1: - version "1.1.9" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.9.tgz#2827920d380d4286d857d57a2968a841db8a7ec8" + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" dependencies: async "^2.1.4" fileset "^2.0.2" istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.0.7" - istanbul-lib-instrument "^1.7.2" - istanbul-lib-report "^1.1.1" - istanbul-lib-source-maps "^1.2.1" - istanbul-reports "^1.1.1" + istanbul-lib-hook "^1.1.0" + istanbul-lib-instrument "^1.9.1" + istanbul-lib-report "^1.1.2" + istanbul-lib-source-maps "^1.2.2" + istanbul-reports "^1.1.3" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" @@ -2520,25 +2035,13 @@ istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" -istanbul-lib-hook@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" +istanbul-lib-hook@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.4.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz#6014b03d3470fb77638d5802508c255c06312e56" - dependencies: - babel-generator "^6.18.0" - babel-template "^6.16.0" - babel-traverse "^6.18.0" - babel-types "^6.18.0" - babylon "^6.13.0" - istanbul-lib-coverage "^1.1.1" - semver "^5.3.0" - -istanbul-lib-instrument@^1.7.2, istanbul-lib-instrument@^1.7.5: +istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" dependencies: @@ -2550,28 +2053,28 @@ istanbul-lib-instrument@^1.7.2, istanbul-lib-instrument@^1.7.5: istanbul-lib-coverage "^1.1.1" semver "^5.3.0" -istanbul-lib-report@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" +istanbul-lib-report@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" dependencies: istanbul-lib-coverage "^1.1.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" dependencies: - debug "^2.6.3" + debug "^3.1.0" istanbul-lib-coverage "^1.1.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e" +istanbul-reports@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" dependencies: handlebars "^4.0.3" @@ -2818,7 +2321,7 @@ 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.4.6, js-yaml@^3.7.0, js-yaml@^3.8.4, js-yaml@^3.9.0: +js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -2829,10 +2332,6 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" -jschardet@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.4.2.tgz#2aa107f142af4121d145659d44f50830961e699a" - jsdom@^9.12.0: version "9.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" @@ -2861,16 +2360,6 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" -json-schema-ref-parser@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-1.4.1.tgz#c0c2e438bf0796723b02451bae8bc7dd0b37fed0" - dependencies: - call-me-maybe "^1.0.1" - debug "^2.2.0" - es6-promise "^3.0.2" - js-yaml "^3.4.6" - ono "^2.0.1" - json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -2889,7 +2378,7 @@ json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json5@^0.5.0, json5@^0.5.1: +json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -2909,21 +2398,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonwebtoken@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz#c6397cd2e5fd583d65c007a83dc7bb78e6982b83" - dependencies: - jws "^3.1.4" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.0.0" - xtend "^4.0.1" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2933,23 +2407,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jwa@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" - dependencies: - base64url "2.0.0" - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.9" - safe-buffer "^5.0.1" - -jws@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" - dependencies: - base64url "^2.0.0" - jwa "^1.1.4" - safe-buffer "^5.0.1" - kind-of@^3.0.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -2968,29 +2425,6 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -knex@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/knex/-/knex-0.13.0.tgz#08dd494f6bb64928934eec9dac34787a14ca5fa4" - dependencies: - babel-runtime "^6.11.6" - bluebird "^3.4.6" - chalk "^1.0.0" - commander "^2.2.0" - debug "^2.1.3" - generic-pool "^2.4.2" - inherits "~2.0.1" - interpret "^0.6.5" - liftoff "~2.2.0" - lodash "^4.6.0" - minimist "~1.1.0" - mkdirp "^0.5.0" - pg-connection-string "^0.1.3" - readable-stream "^1.1.12" - safe-buffer "^5.0.1" - tildify "~1.0.0" - uuid "^3.0.0" - v8flags "^2.0.2" - latest-version@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" @@ -3028,16 +2462,6 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -liftoff@~2.2.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.2.5.tgz#998c2876cff484b103e4423b93d356da44734c91" - dependencies: - extend "^3.0.0" - findup-sync "^0.4.2" - flagged-respawn "^0.3.2" - rechoir "^0.6.2" - resolve "^1.1.7" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -3103,10 +2527,6 @@ lodash.assign@^3.0.0: lodash._createassigner "^3.0.0" lodash.keys "^3.0.0" -lodash.camelcase@~4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - lodash.defaults@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz#c7308b18dbf8bc9372d701a73493c61192bd2e2c" @@ -3114,14 +2534,6 @@ lodash.defaults@^3.1.2: lodash.assign "^3.0.0" lodash.restparam "^3.0.0" -lodash.get@^4.1.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -3130,30 +2542,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - -lodash.isequal@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -3162,10 +2550,6 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - lodash.reduce@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" @@ -3174,11 +2558,7 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" -lodash.snakecase@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" - -lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.0: +lodash@^4.14.0, lodash@^4.17.4, lodash@^4.5.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3203,24 +2583,13 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - -lru-cache@^4.0.1: +lru-cache@^4.0.0, lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" -ltcdr@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ltcdr/-/ltcdr-2.2.1.tgz#5ab87ad1d4c1dab8e8c08bbf037ee0c1902287cf" - make-dir@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" @@ -3286,7 +2655,7 @@ methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: +micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" dependencies: @@ -3312,7 +2681,7 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17: +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: @@ -3332,7 +2701,7 @@ mimic-fn@^1.0.0: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8, minimist@~0.0.1: +minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -3340,9 +2709,9 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -minimist@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" @@ -3351,8 +2720,8 @@ minimist@~1.1.0: minimist "0.0.8" moment@2.x.x: - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + version "2.19.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" morgan@^1.9.0: version "1.9.0" @@ -3370,26 +2739,13 @@ mount-point@^1.0.0: dependencies: "@sindresorhus/df" "^1.0.1" -ms@2.0.0, ms@^2.0.0: +ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - -mysql@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.15.0.tgz#ea16841156343e8f2e47fc8985ec41cdd9573b5c" - dependencies: - bignumber.js "4.0.4" - readable-stream "2.3.3" - safe-buffer "5.1.1" - sqlstring "2.3.0" - nan@^2.3.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + version "2.8.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" natural-compare@^1.4.0: version "1.4.0" @@ -3426,15 +2782,17 @@ node-notifier@^5.0.2: shellwords "^0.1.0" which "^1.2.12" -node-pre-gyp@^0.6.29: - version "0.6.34" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz#94ad1c798a11d7fc67381b50d47f8cc18d9799f7" +node-pre-gyp@^0.6.39: + version "0.6.39" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" dependencies: + detect-libc "^1.0.2" + hawk "3.1.3" mkdirp "^0.5.1" nopt "^4.0.1" npmlog "^4.0.2" rc "^1.1.7" - request "^2.81.0" + request "2.81.0" rimraf "^2.6.1" semver "^5.3.0" tar "^2.2.1" @@ -3459,13 +2817,6 @@ nodemon@^1.12.1: undefsafe "0.0.3" update-notifier "^2.2.0" -noms@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" - dependencies: - inherits "^2.0.1" - readable-stream "~1.0.31" - nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -3479,7 +2830,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.3.2: +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" dependencies: @@ -3488,16 +2839,7 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@^2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -3516,8 +2858,8 @@ npm-run-path@^2.0.0: path-key "^2.0.0" npmlog@^4.0.2: - version "4.1.0" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5" + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" @@ -3564,7 +2906,7 @@ number-is-nan@^1.0.0: version "1.4.3" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" -oauth-sign@~0.8.2: +oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -3572,10 +2914,6 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -3589,7 +2927,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@^1.0.1, on-headers@~1.0.1: +on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" @@ -3599,16 +2937,6 @@ once@^1.3.0, once@^1.3.3, once@^1.4.0: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - dependencies: - mimic-fn "^1.0.0" - -ono@^2.0.1: - version "2.2.5" - resolved "https://registry.yarnpkg.com/ono/-/ono-2.2.5.tgz#daf09488b51174da7a7e4275dfab31b438ffa0e3" - opn-cli@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/opn-cli/-/opn-cli-3.1.0.tgz#f819ae6cae0b411bd0149b8560fe6c88adad20f8" @@ -3644,7 +2972,7 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -3656,7 +2984,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3726,18 +3054,6 @@ parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - dependencies: - better-assert "~1.0.0" - -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - dependencies: - better-assert "~1.0.0" - parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -3803,18 +3119,14 @@ pause-stream@0.0.11: dependencies: through "~2.3" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" -pg-connection-string@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" - -pidusage@^1.1.6: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.2.0.tgz#65ee96ace4e08a4cd3f9240996c85b367171ee92" - pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -3853,10 +3165,6 @@ platform@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - prefix-matches@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prefix-matches/-/prefix-matches-1.0.1.tgz#02e34ce27f33af48e68bbfce2aac2a004bc2b76c" @@ -3883,7 +3191,7 @@ pretty-format@^21.2.1: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -private@^0.1.6, private@^0.1.7: +private@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -3912,7 +3220,7 @@ ps-tree@^1.1.0: dependencies: event-stream "~3.3.0" -pseudomap@^1.0.1, pseudomap@^1.0.2: +pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -3924,6 +3232,10 @@ qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -3945,8 +3257,8 @@ raw-body@2.3.2: unpipe "1.0.0" rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.1" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + version "1.2.2" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -3990,7 +3302,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -4002,24 +3314,6 @@ readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable- string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@^1.1.12: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@~1.0.31: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -4033,16 +3327,6 @@ readline-sync@^1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.7.tgz#001bfdd4c06110c3c084c63bf7c6a56022213f30" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - dependencies: - resolve "^1.1.6" - -recursive-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/recursive-iterator/-/recursive-iterator-2.0.3.tgz#d0e0d2c7e7a83109d73091cf043fc509e5a76dc3" - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -4058,10 +3342,6 @@ reflect-metadata@^0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.10.tgz#b4f83704416acad89988c9b15635d47e03b9344a" -regenerator-runtime@^0.10.0: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - regenerator-runtime@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" @@ -4103,22 +3383,34 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request-promise-core@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" - dependencies: - lodash "^4.13.1" - -request-promise@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.2.tgz#d1ea46d654a6ee4f8ee6a4fea1018c22911904b4" +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: - bluebird "^3.5.0" - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.3" + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" -request@^2.78.0, request@^2.79.0, request@^2.81.0, request@^2.83.0: +request@^2.78.0, request@^2.79.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -4153,30 +3445,16 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" -resolve-dir@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" - dependencies: - expand-tilde "^1.2.2" - global-modules "^0.2.3" - resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" +resolve@^1.1.7, resolve@^1.3.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" dependencies: path-parse "^1.0.5" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -4184,10 +3462,21 @@ right-align@^0.1.1: align-text "^0.1.1" rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + 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" dependencies: + class-transformer "~0.1.6" + class-validator "^0.7.0" + cookie "^0.3.1" glob "^7.0.5" + reflect-metadata "^0.1.10" + template-url "^1.0.0" run-applescript@^3.0.0: version "3.0.0" @@ -4195,22 +3484,6 @@ run-applescript@^3.0.0: dependencies: execa "^0.4.0" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - dependencies: - is-promise "^2.1.0" - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - rx@2.3.24: version "2.3.24" resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" @@ -4223,10 +3496,6 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, s version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" -safe-buffer@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" - sane@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" @@ -4251,14 +3520,10 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" -semver@^5.0.3, semver@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - send@0.16.1: version "0.16.1" resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" @@ -4332,8 +3597,8 @@ shell-quote@^1.6.1: jsonify "~0.0.0" shellwords@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14" + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" @@ -4347,60 +3612,19 @@ slide@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + sntp@2.x.x: version "2.1.0" resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" dependencies: hoek "4.x.x" -socket.io-adapter@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" - -socket.io-client@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.0.4.tgz#0918a552406dc5e540b380dcd97afc4a64332f8e" - dependencies: - backo2 "1.0.2" - base64-arraybuffer "0.1.5" - component-bind "1.0.0" - component-emitter "1.2.1" - debug "~2.6.4" - engine.io-client "~3.1.0" - has-cors "1.1.0" - indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" - socket.io-parser "~3.1.1" - to-array "0.1.4" - -socket.io-parser@~3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.1.2.tgz#dbc2282151fc4faebbe40aeedc0772eba619f7f2" - dependencies: - component-emitter "1.2.1" - debug "~2.6.4" - has-binary2 "~1.0.2" - isarray "2.0.1" - -socket.io@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.0.4.tgz#c1a4590ceff87ecf13c72652f046f716b29e6014" - dependencies: - debug "~2.6.6" - engine.io "~3.1.0" - socket.io-adapter "~1.1.0" - socket.io-client "2.0.4" - socket.io-parser "~3.1.1" - -source-map-support@^0.4.0: - version "0.4.15" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" - dependencies: - source-map "^0.5.6" - -source-map-support@^0.4.15: +source-map-support@^0.4.0, source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: @@ -4418,14 +3642,10 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.6: +source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@^0.5.3, source-map@~0.5.1: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -4465,10 +3685,6 @@ sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" -sqlstring@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.0.tgz#525b8a4fd26d6f71aa61e822a6caf976d31ad2a8" - sshpk@^1.7.0: version "1.13.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" @@ -4499,10 +3715,6 @@ statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" -stealthy-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -4531,24 +3743,13 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - string_decoder@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" dependencies: safe-buffer "~5.1.0" -stringstream@~0.0.5: +stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -4620,50 +3821,13 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -swagger-jsdoc@^1.9.7: - version "1.9.7" - resolved "https://registry.yarnpkg.com/swagger-jsdoc/-/swagger-jsdoc-1.9.7.tgz#7a761d4d7ef4a54bf457cea5c67ec316bb82f8b9" - dependencies: - chokidar "^1.7.0" - commander "^2.11.0" - doctrine "^2.0.0" - glob "^7.1.2" - js-yaml "^3.8.4" - recursive-iterator "^2.0.3" - swagger-parser "^3.4.1" - -swagger-methods@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/swagger-methods/-/swagger-methods-1.0.0.tgz#b39c77957d305a6535c0a1e015081185b99d61fc" - -swagger-parser@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-3.4.1.tgz#0290529dbae254d178b442a95df60d23d142301d" - dependencies: - call-me-maybe "^1.0.1" - debug "^2.2.0" - es6-promise "^3.0.2" - json-schema-ref-parser "^1.4.1" - ono "^2.0.1" - swagger-methods "^1.0.0" - swagger-schema-official "2.0.0-bab6bed" - z-schema "^3.16.1" - -swagger-schema-official@2.0.0-bab6bed: - version "2.0.0-bab6bed" - resolved "https://registry.yarnpkg.com/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz#70070468d6d2977ca5237b2e519ca7d06a2ea3fd" - -swagger-ui-express@^2.0.9: - version "2.0.10" - resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-2.0.10.tgz#a7beb3e44ad0abd3c9915afc6b41cb87a91aa3e7" - symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" tar-pack@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" dependencies: debug "^2.2.0" fstream "^1.0.10" @@ -4693,6 +3857,10 @@ temp-write@^2.1.0: pinkie-promise "^2.0.0" uuid "^2.0.1" +template-url@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/template-url/-/template-url-1.0.0.tgz#d9456bee70cac6617b462a7b08db29fb813a0b09" + term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" @@ -4713,23 +3881,10 @@ throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" -through2@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -through@2, through@^2.3.6, through@~2.3, through@~2.3.1: +through@2, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -tildify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.0.0.tgz#2a021db5e8fbde0a8f8b4df37adaa8fb1d39d7dd" - dependencies: - user-home "^1.0.0" - timed-out@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" @@ -4738,20 +3893,10 @@ timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" -tmp@^0.0.31: - version "0.0.31" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" - dependencies: - os-tmpdir "~1.0.1" - tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -4768,7 +3913,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@>=2.3.3, tough-cookie@^2.3.2, tough-cookie@~2.3.3: +tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: @@ -4787,16 +3932,15 @@ trash-cli@^1.4.0: update-notifier "^1.0.2" trash@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/trash/-/trash-4.0.1.tgz#b3ce5a4d862b1c655bef818ae7e084efce6e4c2d" + version "4.1.0" + resolved "https://registry.yarnpkg.com/trash/-/trash-4.1.0.tgz#edddcf2c26236d4942e8b27c80d1bd746b7acc77" dependencies: - escape-string-applescript "^1.0.0" + escape-string-applescript "^2.0.0" fs-extra "^0.30.0" globby "^6.0.0" - path-exists "^3.0.0" - pify "^2.3.0" + pify "^3.0.0" run-applescript "^3.0.0" - uuid "^2.0.1" + uuid "^3.1.0" xdg-trashdir "^2.0.0" tree-kill@^1.1.0: @@ -4850,8 +3994,8 @@ tsconfig@^6.0.0: strip-json-comments "^2.0.0" tslib@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec" + version "1.8.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" tslint@^5.8.0: version "5.8.0" @@ -4870,8 +4014,8 @@ tslint@^5.8.0: tsutils "^2.12.1" tsutils@^2.12.1: - version "2.12.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.12.1.tgz#f4d95ce3391c8971e46e54c4cf0edb0a21dd5b24" + version "2.12.2" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.12.2.tgz#ad58a4865d17ec3ddb6631b6ca53be14a5656ff3" dependencies: tslib "^1.7.1" @@ -4892,8 +4036,8 @@ type-check@~0.3.2: prelude-ls "~1.1.2" type-detect@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea" + version "4.0.5" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" type-is@~1.6.15: version "1.6.15" @@ -4902,13 +4046,17 @@ type-is@~1.6.15: media-typer "0.3.0" mime-types "~2.1.15" +typedi@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/typedi/-/typedi-0.5.2.tgz#e1f67409bf0d19be86f072ff88ae30db15f61b74" + typescript@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" uglify-js@^2.6: - version "2.8.27" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c" + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: source-map "~0.5.1" yargs "~3.10.0" @@ -4923,10 +4071,6 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" -ultron@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" - undefsafe@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f" @@ -4986,7 +4130,7 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" -user-home@^1.0.0, user-home@^1.1.0, user-home@^1.1.1: +user-home@^1.0.0, user-home@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" @@ -5008,24 +4152,10 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" - -uuid@^3.1.0: +uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -uws@~0.14.4: - version "0.14.5" - resolved "https://registry.yarnpkg.com/uws/-/uws-0.14.5.tgz#67aaf33c46b2a587a5f6666d00f7691328f149dc" - -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - dependencies: - user-home "^1.1.1" - v8flags@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.1.tgz#dce8fc379c17d9f2c9e9ed78d89ce00052b1b76b" @@ -5039,13 +4169,9 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -validator@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-6.3.0.tgz#47ce23ed8d4eaddfa9d4b8ef0071b6cf1078d7c8" - validator@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-7.0.0.tgz#c74deb8063512fac35547938e6f0b1504a282fd2" + version "7.2.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-7.2.0.tgz#a63dcbaba51d4350bf8df20988e0d5a54d711791" vary@^1, vary@~1.1.2: version "1.1.2" @@ -5107,13 +4233,7 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@^1.2.12, which@^1.2.8: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" - dependencies: - isexe "^2.0.0" - -which@^1.2.9: +which@^1.2.12, which@^1.2.8, which@^1.2.9: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: @@ -5159,11 +4279,11 @@ wordwrap@~1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" worker-farm@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.3.1.tgz#4333112bb49b17aa050b87895ca6b2cacf40e5ff" + version "1.5.2" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae" dependencies: - errno ">=0.1.1 <0.2.0-0" - xtend ">=4.0.0 <4.1.0-0" + errno "^0.1.4" + xtend "^4.0.1" wrap-ansi@^2.0.0: version "2.1.0" @@ -5192,13 +4312,6 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" -ws@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80" - dependencies: - safe-buffer "~5.0.1" - 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" @@ -5234,11 +4347,7 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" -xmlhttprequest-ssl@~1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz#04f560915724b389088715cc0ed7813e9677bf57" - -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -5246,7 +4355,7 @@ y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" -yallist@^2.0.0, yallist@^2.1.2: +yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -5324,20 +4433,6 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - yn@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" - -z-schema@^3.16.1: - version "3.18.2" - resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.18.2.tgz#e422196b5efe60b46adef3c3f2aef2deaa911161" - dependencies: - lodash.get "^4.1.2" - lodash.isequal "^4.4.0" - validator "^6.0.0" - optionalDependencies: - commander "^2.7.1" From fdda1dbcbb51b9b89b7728367e8a374b3eab5cf1 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 12:00:07 +0100 Subject: [PATCH 002/104] Add microframework --- package.json | 4 + src/api/routes.ts | 17 ++ src/app.ts | 23 +- src/env.ts | 36 ++++ src/modules/expressLoader.ts | 22 ++ src/modules/typeormLoader.ts | 22 ++ src/modules/winstonLoader.ts | 23 ++ yarn.lock | 393 ++++++++++++++++++++++++++++++++++- 8 files changed, 528 insertions(+), 12 deletions(-) create mode 100644 src/api/routes.ts create mode 100644 src/env.ts create mode 100644 src/modules/expressLoader.ts create mode 100644 src/modules/typeormLoader.ts create mode 100644 src/modules/winstonLoader.ts diff --git a/package.json b/package.json index 6dd2b947..4b673f57 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@types/serve-favicon": "^2.2.29", "@types/uuid": "^3.4.3", "@types/winston": "^2.3.7", + "ascii-art": "^1.4.2", "body-parser": "^1.18.2", "class-validator": "^0.7.3", "compression": "^1.7.1", @@ -62,7 +63,9 @@ "figlet": "^1.2.0", "helmet": "^3.9.0", "lodash": "^4.17.4", + "microframework": "^0.6.4", "morgan": "^1.9.0", + "mysql": "^2.15.0", "nodemon": "^1.12.1", "path": "^0.12.7", "reflect-metadata": "^0.1.10", @@ -72,6 +75,7 @@ "ts-node": "^3.3.0", "tslint": "^5.8.0", "typedi": "^0.5.2", + "typeorm": "^0.1.3", "typescript": "^2.6.1", "uuid": "^3.1.0", "wait-on": "^2.0.2", diff --git a/src/api/routes.ts b/src/api/routes.ts new file mode 100644 index 00000000..a45dc86e --- /dev/null +++ b/src/api/routes.ts @@ -0,0 +1,17 @@ +import { Request, Response } from 'express'; + +/** + * Just dummy routes as example how configuration can be separated from business logic. + * Try routing-controllers library if you want to have structured routes in your application. + */ +export const Routes = { + '/': (req: Request, res: Response) => { + res.send('Hello World'); + }, + '/posts': (req: Request, res: Response) => { + res.send('Hello posts'); + }, + '/users': (req: Request, res: Response) => { + res.send('Hello users'); + } +}; diff --git a/src/app.ts b/src/app.ts index 049be2c7..e0dac190 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,8 +9,27 @@ * To add express modules go to the 'config/AppConfig.ts' file. All the IOC registrations * are in the 'config/IocConfig.ts' file. */ +import * as dotenv from 'dotenv'; +dotenv.config(); -import 'reflect-metadata'; +import { bootstrapMicroframework } from 'microframework'; +import { expressLoader } from './modules/expressLoader'; +import { winstonLoader } from './modules/winstonLoader'; +import { typeormLoader } from './modules/typeormLoader'; +import { env } from './env'; -console.log('adsf'); +bootstrapMicroframework({ + config: { + logo: env.app.name, + showBootstrapTime: true + }, + loaders: [ + expressLoader, + winstonLoader, + typeormLoader + // here we can setup other databases, any other lib we want to setup in our application + ] +}) + .then(() => console.log('Application is up and running.')) + .catch(error => console.log('Application is crashed: ' + error)); diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 00000000..9b83b9c5 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,36 @@ +/** + * Environment variables + */ +export const env = { + app: { + name: getOsEnv('APP_NAME'), + host: getOsEnv('APP_HOST'), + port: toNumber(getOsEnv('APP_PORT')), + urlPrefix: getOsEnv('APP_URL_PREFIX') + }, + log: { + level: getOsEnv('LOG_LEVEL') + }, + db: { + type: getOsEnv('DB_TYPE'), + host: getOsEnv('DB_HOST'), + port: toNumber(getOsEnv('DB_PORT')), + username: getOsEnv('DB_USERNAME'), + password: getOsEnv('DB_PASSWORD'), + database: getOsEnv('DB_DATABASE'), + synchronize: toBool(getOsEnv('DB_SYNCHRONIZE')), + logging: toBool(getOsEnv('DB_LOGGING')) + } +}; + +function getOsEnv(key: string): string { + return `${process.env[key]}`; +} + +function toNumber(value: string): number { + return parseInt(value, 10); +} + +function toBool(value: string): boolean { + return value === 'true'; +} diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts new file mode 100644 index 00000000..2c01c60d --- /dev/null +++ b/src/modules/expressLoader.ts @@ -0,0 +1,22 @@ +import * as express from 'express'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { Routes } from '../api/routes'; + + +export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + + // create express app + const app = express(); + + // register all routes + const routes: any = Routes; + Object.keys(routes).forEach(routePath => app.get(routePath, routes[routePath])); + + // run application to listen on given port + app.listen(3000); + + // here we can set the data for other loaders + if (settings) { + settings.setData('express_app', app); + } +}; diff --git a/src/modules/typeormLoader.ts b/src/modules/typeormLoader.ts new file mode 100644 index 00000000..915d6496 --- /dev/null +++ b/src/modules/typeormLoader.ts @@ -0,0 +1,22 @@ +import { createConnection } from 'typeorm'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { env } from '../env'; + + +export const typeormLoader: MicroframeworkLoader = async (settings: MicroframeworkSettings | undefined) => { + // @ts-ignore this + const connection = await createConnection({ + type: env.db.type, + host: env.db.host, + port: env.db.port, + username: env.db.username, + password: env.db.password, + database: env.db.database, + synchronize: env.db.synchronize, + logging: env.db.logging + }); + + if (settings) { + settings.onShutdown(() => connection.close()); + } +}; diff --git a/src/modules/winstonLoader.ts b/src/modules/winstonLoader.ts new file mode 100644 index 00000000..8b9404e4 --- /dev/null +++ b/src/modules/winstonLoader.ts @@ -0,0 +1,23 @@ +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import * as winston from 'winston'; + + +export const winstonLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + winston.configure({ + transports: [ + new winston.transports.Console({ + level: process.env.LOG_LEVEL, + timestamp: true, + handleExceptions: true, + json: true, + colorize: true + }) + ] + }); + + + // btw we can retrieve express app instance here to make some winston-specific manipulations on it + // if (settings) { + // const expressApp = settings.getData('express_app'); + // } +}; diff --git a/yarn.lock b/yarn.lock index c3257d7f..27c297a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -195,6 +195,10 @@ ansi-styles@^3.1.0, ansi-styles@^3.2.0: 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" + any-shell-escape@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/any-shell-escape/-/any-shell-escape-0.1.1.tgz#d55ab972244c71a9a5e1ab0879f30bf110806959" @@ -206,6 +210,10 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +app-root-path@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" + append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -281,6 +289,18 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" +ascii-art@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/ascii-art/-/ascii-art-1.4.2.tgz#96f5f471b095e9702afc3cb29418486450cd17a1" + dependencies: + browser-request "0.3.3" + dirname-shim "1.0.0" + request "2.79.0" + yargs "*" + optionalDependencies: + canvas "*" + jsftp "*" + asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -507,6 +527,10 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bignumber.js@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.0.4.tgz#7c40f5abcd2d6623ab7b99682ee7db81b11889a4" + binary-extensions@^1.0.0: version "1.10.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" @@ -591,6 +615,10 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +browser-request@0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" + browser-resolve@^1.11.2: version "1.11.2" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" @@ -630,6 +658,10 @@ camelcase@^2.0.0, camelcase@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -638,10 +670,22 @@ camelize@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" +canvas@*: + version "1.6.7" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-1.6.7.tgz#2d8a04b453ec5d6510727cfc697e236dc4ae85dc" + dependencies: + nan "^2.4.0" + parse-css-font "^2.0.2" + units-css "^0.4.0" + capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" +caseless@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -714,6 +758,16 @@ cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" +cli-highlight@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-1.1.4.tgz#e45590c14fb18e13865e3899e824c5592cc22926" + dependencies: + chalk "^1.1.3" + he "^1.1.0" + highlight.js "^9.6.0" + mz "^2.4.0" + yargs "^4.8.1" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -978,6 +1032,36 @@ crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" +css-font-size-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz#854875ace9aca6a8d2ee0d345a44aae9bb6db6cb" + +css-font-stretch-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz#50cee9b9ba031fb5c952d4723139f1e107b54b10" + +css-font-style-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz#5c3532813f63b4a1de954d13cea86ab4333409e4" + +css-font-weight-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz#9bc04671ac85bc724b574ef5d3ac96b0d604fd97" + +css-global-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69" + +css-list-helpers@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-1.0.1.tgz#fff57192202db83240c41686f919e449a7024f7d" + dependencies: + tcomb "^2.5.0" + +css-system-font-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" + cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.2" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" @@ -1016,7 +1100,7 @@ date-fns@^1.23.0: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" -debug@2.6.9, debug@^2.2.0, debug@^2.6.8: +debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1076,6 +1160,10 @@ diff@^3.1.0, diff@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" +dirname-shim@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dirname-shim/-/dirname-shim-1.0.0.tgz#950c15ec411f5c785aa0972070531ed78b16e0e0" + dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" @@ -1326,7 +1414,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -figlet@^1.2.0: +figlet@^1.1.1, figlet@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410" @@ -1480,6 +1568,12 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +ftp-response-parser@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ftp-response-parser/-/ftp-response-parser-1.0.1.tgz#3b9d33f8edd5fb8e4700b8f778c462e5b1581f89" + dependencies: + readable-stream "^1.0.31" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -1493,6 +1587,16 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -1648,6 +1752,15 @@ har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" +har-validator@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" + dependencies: + chalk "^1.1.1" + commander "^2.9.0" + is-my-json-valid "^2.12.4" + pinkie-promise "^2.0.0" + har-validator@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" @@ -1704,6 +1817,10 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" +he@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + helmet-csp@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.6.0.tgz#c1f5595afbc5f83e5f1e6c15f842f07a10f6ea04" @@ -1735,6 +1852,10 @@ hide-powered-by@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b" +highlight.js@^9.6.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" + hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" @@ -1931,6 +2052,15 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-my-json-valid@^2.12.4: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -1969,6 +2099,10 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" @@ -1993,6 +2127,10 @@ is-windows@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2005,6 +2143,10 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" +isnumeric@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64" + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -2321,7 +2463,7 @@ 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.9.0: +js-yaml@^3.7.0, js-yaml@^3.8.4, js-yaml@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -2360,6 +2502,17 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" +jsftp@*: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jsftp/-/jsftp-2.0.0.tgz#720b1d07af22ce978f8967342da9d33a249b3f4b" + dependencies: + debug "^2.6.0" + ftp-response-parser "^1.0.1" + once "^1.3.3" + parse-listing "^1.1.3" + stream-combiner "^0.2.2" + unorm "^1.4.1" + json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -2398,6 +2551,10 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2527,6 +2684,10 @@ lodash.assign@^3.0.0: lodash._createassigner "^3.0.0" lodash.keys "^3.0.0" +lodash.assign@^4.0.3, lodash.assign@^4.0.6: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + lodash.defaults@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz#c7308b18dbf8bc9372d701a73493c61192bd2e2c" @@ -2655,6 +2816,12 @@ methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" +microframework@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/microframework/-/microframework-0.6.4.tgz#668ad0a8f5d7acdfec1bbdc3c01d430ef70021fd" + dependencies: + app-root-path "^2.0.1" + micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -2743,7 +2910,24 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -nan@^2.3.0: +mysql@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.15.0.tgz#ea16841156343e8f2e47fc8985ec41cdd9573b5c" + dependencies: + bignumber.js "4.0.4" + readable-stream "2.3.3" + safe-buffer "5.1.1" + sqlstring "2.3.0" + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nan@^2.3.0, nan@^2.4.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" @@ -2976,6 +3160,12 @@ os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + os-locale@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" @@ -3031,6 +3221,24 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" +parent-require@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" + +parse-css-font@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/parse-css-font/-/parse-css-font-2.0.2.tgz#7b60b060705a25a9b90b7f0ed493e5823248a652" + dependencies: + css-font-size-keywords "^1.0.0" + css-font-stretch-keywords "^1.0.1" + css-font-style-keywords "^1.0.1" + css-font-weight-keywords "^1.0.0" + css-global-keywords "^1.0.1" + css-list-helpers "^1.0.1" + css-system-font-keywords "^1.0.0" + tcomb "^2.5.0" + unquote "^1.1.0" + parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -3046,6 +3254,10 @@ parse-json@^2.1.0, parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-listing@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/parse-listing/-/parse-listing-1.1.3.tgz#aa546f57fdc129cfbf9945cd4b757b14b06182dd" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -3232,6 +3444,10 @@ qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@~6.3.0: + version "6.3.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" + qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" @@ -3302,7 +3518,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4: +readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -3314,6 +3530,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@^1.0.31: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -3383,6 +3608,31 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +request@2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~2.0.6" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + qs "~6.3.0" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "~0.4.1" + uuid "^3.0.0" + request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -3510,7 +3760,7 @@ sane@^2.0.0: optionalDependencies: fsevents "^1.1.1" -sax@^1.2.1: +sax@>=0.6.0, sax@^1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -3685,6 +3935,10 @@ sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" +sqlstring@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.0.tgz#525b8a4fd26d6f71aa61e822a6caf976d31ad2a8" + sshpk@^1.7.0: version "1.13.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" @@ -3715,6 +3969,13 @@ statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +stream-combiner@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" + dependencies: + duplexer "~0.1.1" + through "~2.3.4" + stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -3743,6 +4004,10 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + string_decoder@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" @@ -3846,6 +4111,10 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" +tcomb@^2.5.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" + temp-write@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-2.1.0.tgz#59890918e0ef09d548aaa342f4bd3409d8404e96" @@ -3877,11 +4146,23 @@ test-exclude@^4.1.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + dependencies: + any-promise "^1.0.0" + throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" -through@2, through@~2.3, through@~2.3.1: +through@2, through@~2.3, through@~2.3.1, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4025,6 +4306,10 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel-agent@~0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -4050,6 +4335,22 @@ typedi@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/typedi/-/typedi-0.5.2.tgz#e1f67409bf0d19be86f072ff88ae30db15f61b74" +typeorm@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.1.3.tgz#b8ae44bc7fbebbedde1350bd3bb72fc4c99833a1" + dependencies: + app-root-path "^2.0.1" + chalk "^2.0.1" + cli-highlight "^1.1.4" + dotenv "^4.0.0" + glob "^7.1.2" + js-yaml "^3.8.4" + mkdirp "^0.5.1" + reflect-metadata "^0.1.10" + xml2js "^0.4.17" + yargonaut "^1.1.2" + yargs "^9.0.1" + typescript@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" @@ -4081,14 +4382,29 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +units-css@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07" + dependencies: + isnumeric "^0.2.0" + viewport-dimensions "^0.2.0" + universalify@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" +unorm@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +unquote@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.0.tgz#98e1fc608b6b854c75afb1b95afc099ba69d942f" + unzip-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" @@ -4185,6 +4501,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viewport-dimensions@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c" + wait-on@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-2.0.2.tgz#0a84fd07024c6fc268cb0eabe585be217aaf2baa" @@ -4229,6 +4549,10 @@ whatwg-url@^4.3.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -4255,6 +4579,10 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +window-size@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" + winston@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee" @@ -4347,7 +4675,18 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" -xtend@^4.0.1: +xml2js@^0.4.17: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.4" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + +xtend@^4.0.0, xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -4359,6 +4698,21 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yargonaut@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.2.tgz#ee7b89e98121a3f241fa926a2a6e1b6641c81b3f" + dependencies: + chalk "^1.1.1" + figlet "^1.1.1" + parent-require "^1.0.0" + +yargs-parser@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" + dependencies: + camelcase "^3.0.0" + lodash.assign "^4.0.6" + yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" @@ -4371,7 +4725,7 @@ yargs-parser@^8.0.0: dependencies: camelcase "^4.1.0" -yargs@^10.0.3: +yargs@*, yargs@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae" dependencies: @@ -4388,6 +4742,25 @@ yargs@^10.0.3: y18n "^3.2.1" yargs-parser "^8.0.0" +yargs@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" + dependencies: + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + lodash.assign "^4.0.3" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.1" + which-module "^1.0.0" + window-size "^0.2.0" + y18n "^3.2.1" + yargs-parser "^2.4.1" + yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" @@ -4406,7 +4779,7 @@ yargs@^8.0.2: y18n "^3.2.1" yargs-parser "^7.0.0" -yargs@^9.0.0: +yargs@^9.0.0, yargs@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" dependencies: From 29aa799e1edeee64256d666bc5aaaff8fbfce7a5 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 12:18:24 +0100 Subject: [PATCH 003/104] Add logger --- src/app.ts | 12 +++---- src/core/Log.ts | 68 ++++++++++++++++++++++++++++++++++++ src/{ => core}/env.ts | 4 ++- src/modules/typeormLoader.ts | 2 +- src/modules/winstonLoader.ts | 15 +++----- 5 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 src/core/Log.ts rename src/{ => core}/env.ts (86%) diff --git a/src/app.ts b/src/app.ts index e0dac190..22c38a64 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,18 +12,16 @@ import * as dotenv from 'dotenv'; dotenv.config(); +import { Log } from './core/Log'; +const log = new Log(__filename); + import { bootstrapMicroframework } from 'microframework'; import { expressLoader } from './modules/expressLoader'; import { winstonLoader } from './modules/winstonLoader'; import { typeormLoader } from './modules/typeormLoader'; -import { env } from './env'; bootstrapMicroframework({ - config: { - logo: env.app.name, - showBootstrapTime: true - }, loaders: [ expressLoader, winstonLoader, @@ -31,5 +29,5 @@ bootstrapMicroframework({ // here we can setup other databases, any other lib we want to setup in our application ] }) - .then(() => console.log('Application is up and running.')) - .catch(error => console.log('Application is crashed: ' + error)); + .then(() => log.info('Application is up and running.')) + .catch(error => log.error('Application is crashed: ' + error)); diff --git a/src/core/Log.ts b/src/core/Log.ts new file mode 100644 index 00000000..5761c808 --- /dev/null +++ b/src/core/Log.ts @@ -0,0 +1,68 @@ +import * as path from 'path'; +import * as winston from 'winston'; + +/** + * core.Log + * ------------------------------------------------ + * + * This is the main Logger Object. You can create a scope logger + * or directly use the static log methods. + * + * By Default it uses the debug-adapter, but you are able to change + * this in the start up process in the core/index.ts file. + */ + +export class Log { + + public static DEFAULT_SCOPE = 'app'; + + private static parsePathToScope(filepath: string): string { + if (filepath.indexOf(path.sep) >= 0) { + filepath = filepath.replace(process.cwd(), ''); + filepath = filepath.replace(`${path.sep}src${path.sep}`, ''); + filepath = filepath.replace(`${path.sep}dist${path.sep}`, ''); + filepath = filepath.replace('.ts', ''); + filepath = filepath.replace('.js', ''); + filepath = filepath.replace(path.sep, ':'); + } + return filepath; + } + + private scope: string; + private adapter: interfaces.LoggerAdapter; + + constructor(scope?: string) { + this.scope = Log.parsePathToScope((scope) ? scope : Log.DEFAULT_SCOPE); + } + + public getAdapter(): interfaces.LoggerAdapter { + return this.adapter; + } + + public debug(message: string, ...args: any[]): void { + this.log('debug', message, args); + } + + public info(message: string, ...args: any[]): void { + this.log('info', message, args); + } + + public warn(message: string, ...args: any[]): void { + this.log('warn', message, args); + } + + public error(message: string, ...args: any[]): void { + this.log('error', message, args); + } + + private log(level: string, message: string, args: any[]): void { + if (winston) { + winston[level](`${this.formatScope()} ${message}`, args); + } + } + + private formatScope(): string { + return `[${this.scope}]`; + } + +} diff --git a/src/env.ts b/src/core/env.ts similarity index 86% rename from src/env.ts rename to src/core/env.ts index 9b83b9c5..0d41531b 100644 --- a/src/env.ts +++ b/src/core/env.ts @@ -2,6 +2,7 @@ * Environment variables */ export const env = { + node: process.env.NODE_ENV || 'development', app: { name: getOsEnv('APP_NAME'), host: getOsEnv('APP_HOST'), @@ -9,7 +10,8 @@ export const env = { urlPrefix: getOsEnv('APP_URL_PREFIX') }, log: { - level: getOsEnv('LOG_LEVEL') + level: getOsEnv('LOG_LEVEL'), + json: toBool(getOsEnv('LOG_JSON')) }, db: { type: getOsEnv('DB_TYPE'), diff --git a/src/modules/typeormLoader.ts b/src/modules/typeormLoader.ts index 915d6496..201c3717 100644 --- a/src/modules/typeormLoader.ts +++ b/src/modules/typeormLoader.ts @@ -1,6 +1,6 @@ import { createConnection } from 'typeorm'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; -import { env } from '../env'; +import { env } from '../core/env'; export const typeormLoader: MicroframeworkLoader = async (settings: MicroframeworkSettings | undefined) => { diff --git a/src/modules/winstonLoader.ts b/src/modules/winstonLoader.ts index 8b9404e4..449b260d 100644 --- a/src/modules/winstonLoader.ts +++ b/src/modules/winstonLoader.ts @@ -1,23 +1,18 @@ import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import * as winston from 'winston'; +import { env } from '../core/env'; export const winstonLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { winston.configure({ transports: [ new winston.transports.Console({ - level: process.env.LOG_LEVEL, - timestamp: true, + level: env.log.level, handleExceptions: true, - json: true, - colorize: true + json: env.log.json, + timestamp: env.node !== 'development', + colorize: env.node === 'development' }) ] }); - - - // btw we can retrieve express app instance here to make some winston-specific manipulations on it - // if (settings) { - // const expressApp = settings.getData('express_app'); - // } }; From 24ca5efd7ec4d05bd0f101708ad89f2b82af6584 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 13:51:00 +0100 Subject: [PATCH 004/104] Add basic mvc layers --- src/api/controllers/UserController.ts | 33 +++++++++++++++++ src/api/models/User.ts | 18 ++++++++++ src/api/repositories/UserRepository.ts | 50 ++++++++++++++++++++++++++ src/app.ts | 1 + src/modules/expressLoader.ts | 35 ++++++++++++++---- tslint.json | 1 + 6 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 src/api/controllers/UserController.ts create mode 100644 src/api/models/User.ts create mode 100644 src/api/repositories/UserRepository.ts diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts new file mode 100644 index 00000000..31b812af --- /dev/null +++ b/src/api/controllers/UserController.ts @@ -0,0 +1,33 @@ +import { JsonController, Get, Post as HttpPost, Param, Delete, Body } from 'routing-controllers'; +import { Service } from 'typedi'; +import { UserRepository } from '../repositories/UserRepository'; +import { User } from '../models/User'; + +@Service() +@JsonController() +export class UserController { + + constructor(private userRepository: UserRepository) { + } + + @Get('/users') + public all(): Promise { + return this.userRepository.findAll(); + } + + @Get('/users/:id') + public one( @Param('id') id: number): User | undefined { + return this.userRepository.findOne(id); + } + + @HttpPost('/users') + public post( @Body() user: User): User { + return this.userRepository.save(user); + } + + @Delete('/users/:id') + public delete( @Param('id') id: number): User | undefined { + return this.userRepository.remove(id); + } + +} diff --git a/src/api/models/User.ts b/src/api/models/User.ts new file mode 100644 index 00000000..aff4f1e0 --- /dev/null +++ b/src/api/models/User.ts @@ -0,0 +1,18 @@ +export class User { + + public id: number; + public firstName: string; + public lastName: string; + + constructor( + id: number, + firstName: string, + lastName: string + ) { + + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + +} diff --git a/src/api/repositories/UserRepository.ts b/src/api/repositories/UserRepository.ts new file mode 100644 index 00000000..c5780db8 --- /dev/null +++ b/src/api/repositories/UserRepository.ts @@ -0,0 +1,50 @@ + +import { Service } from 'typedi'; +import { User } from '../models/User'; + +@Service() +export class UserRepository { + + private users = [ + new User(1, 'Hans', 'Muster'), + new User(2, 'Horst', 'Schlemmer'), + new User(3, 'David', 'Weber'), + new User(4, 'Gery', 'Hirsch'), + new User(5, 'Irene', 'Sofi') + ]; + + public findAll(): Promise { + // here, for example you can load categories using mongoose + // you can also return a promise here + // simulate async with creating an empty promise + return Promise.resolve(this.users); + } + + public findOne(id: number): User | undefined { + // here, for example you can load category id using mongoose + // you can also return a promise here + let foundUser: User | undefined = undefined; + this.users.forEach(u => { + if (u.id === id) { + foundUser = u; + } + }); + return foundUser; + } + + public save(user: User): User { + // here, for example you can save a user to mongodb using mongoose + this.users.push(user); + return user; + } + + public remove(id: number): User | undefined { + // here, for example you can save a category to mongodb using mongoose + const user = this.findOne(id); + if (user) { + this.users.splice(this.users.indexOf(user), 1); + } + return user; + } + +} diff --git a/src/app.ts b/src/app.ts index 22c38a64..ef3713d2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,6 +9,7 @@ * To add express modules go to the 'config/AppConfig.ts' file. All the IOC registrations * are in the 'config/IocConfig.ts' file. */ +import 'reflect-metadata'; import * as dotenv from 'dotenv'; dotenv.config(); diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index 2c01c60d..236df0d0 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -1,16 +1,37 @@ -import * as express from 'express'; +import { Container } from 'typedi'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; -import { Routes } from '../api/routes'; +import { createExpressServer, useContainer } from 'routing-controllers'; + +import { UserController } from '../api/controllers/UserController'; export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { - // create express app - const app = express(); + /** + * Setup routing-controllers to use typedi container. + */ + useContainer(Container); + + /** + * We create a new express server instance. + * We could have also use useExpressServer here to attach controllers to an existing express instance. + */ + const app = createExpressServer({ + /** + * We can add options about how routing-controllers should configure itself. + * Here we specify what controllers should be registered in our express server. + */ + controllers: [ + UserController + ] + }); + + // // create express app + // const app = express(); - // register all routes - const routes: any = Routes; - Object.keys(routes).forEach(routePath => app.get(routePath, routes[routePath])); + // // register all routes + // const routes: any = Routes; + // Object.keys(routes).forEach(routePath => app.get(routePath, routes[routePath])); // run application to listen on given port app.listen(3000); diff --git a/tslint.json b/tslint.json index 14a2b117..c9689d23 100644 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ true, 160 ], + "no-unnecessary-initializer": false, "no-consecutive-blank-lines": false, "quotemark": [true, "single", "avoid-escape"], "interface-name": false, From 705540184cc21765ac8af9c0958852cb67e9a5d7 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 13:56:38 +0100 Subject: [PATCH 005/104] Update version tag --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b673f57..8227948d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-typescript-boilerplate", - "version": "2.0.0-beta.6", + "version": "3.0.0-alpha", "description": "A delightful way to building a RESTful API with NodeJs & TypeScript", "main": "src/app.ts", "engines": { From fe1ad4e6d5be467c9118166bef59b7f910a1790d Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 14:31:13 +0100 Subject: [PATCH 006/104] Add repos --- package.json | 1 + src/api/controllers/UserController.ts | 22 +++---- src/api/models/User.ts | 24 ++++--- src/api/repositories/UserRepository.ts | 86 +++++++++++++------------- src/modules/expressLoader.ts | 24 +++---- src/modules/typeormLoader.ts | 6 +- yarn.lock | 4 ++ 7 files changed, 85 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index 8227948d..b491892f 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "tslint": "^5.8.0", "typedi": "^0.5.2", "typeorm": "^0.1.3", + "typeorm-typedi-extensions": "^0.1.1", "typescript": "^2.6.1", "uuid": "^3.1.0", "wait-on": "^2.0.2", diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index 31b812af..9dd627f1 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -1,5 +1,5 @@ import { JsonController, Get, Post as HttpPost, Param, Delete, Body } from 'routing-controllers'; -import { Service } from 'typedi'; +import { Service, Inject } from 'typedi'; import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; @@ -7,27 +7,27 @@ import { User } from '../models/User'; @JsonController() export class UserController { - constructor(private userRepository: UserRepository) { - } + @Inject() + private userRepository: UserRepository; @Get('/users') - public all(): Promise { - return this.userRepository.findAll(); + public async all(): Promise { + return await this.userRepository.find(); } @Get('/users/:id') - public one( @Param('id') id: number): User | undefined { - return this.userRepository.findOne(id); + public async one( @Param('id') id: string): Promise { + return await this.userRepository.findOne({ id }); } @HttpPost('/users') - public post( @Body() user: User): User { - return this.userRepository.save(user); + public async post( @Body() user: User): Promise { + return await this.userRepository.save(user); } @Delete('/users/:id') - public delete( @Param('id') id: number): User | undefined { - return this.userRepository.remove(id); + public async delete( @Param('id') id: string): Promise { + return await this.userRepository.removeById(id); } } diff --git a/src/api/models/User.ts b/src/api/models/User.ts index aff4f1e0..0dc80c50 100644 --- a/src/api/models/User.ts +++ b/src/api/models/User.ts @@ -1,18 +1,16 @@ -export class User { +import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm'; - public id: number; - public firstName: string; - public lastName: string; - constructor( - id: number, - firstName: string, - lastName: string - ) { +@Entity() +export class User extends BaseEntity { + + @PrimaryGeneratedColumn('uuid') + public id: string; - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } + @Column({ name: 'first_name' }) + public firstName: string; + + @Column({ name: 'last_name' }) + public lastName: string; } diff --git a/src/api/repositories/UserRepository.ts b/src/api/repositories/UserRepository.ts index c5780db8..37fd6c78 100644 --- a/src/api/repositories/UserRepository.ts +++ b/src/api/repositories/UserRepository.ts @@ -1,50 +1,50 @@ import { Service } from 'typedi'; +import { Repository, EntityRepository } from 'typeorm'; import { User } from '../models/User'; + @Service() -export class UserRepository { - - private users = [ - new User(1, 'Hans', 'Muster'), - new User(2, 'Horst', 'Schlemmer'), - new User(3, 'David', 'Weber'), - new User(4, 'Gery', 'Hirsch'), - new User(5, 'Irene', 'Sofi') - ]; - - public findAll(): Promise { - // here, for example you can load categories using mongoose - // you can also return a promise here - // simulate async with creating an empty promise - return Promise.resolve(this.users); - } - - public findOne(id: number): User | undefined { - // here, for example you can load category id using mongoose - // you can also return a promise here - let foundUser: User | undefined = undefined; - this.users.forEach(u => { - if (u.id === id) { - foundUser = u; - } - }); - return foundUser; - } - - public save(user: User): User { - // here, for example you can save a user to mongodb using mongoose - this.users.push(user); - return user; - } - - public remove(id: number): User | undefined { - // here, for example you can save a category to mongodb using mongoose - const user = this.findOne(id); - if (user) { - this.users.splice(this.users.indexOf(user), 1); - } - return user; - } +@EntityRepository(User) +export class UserRepository extends Repository { + + + + // @OrmConnection() + // private connection: Connection; + + // public findAll(): Promise { + // // here, for example you can load categories using mongoose + // // you can also return a promise here + // // simulate async with creating an empty promise + // return Promise.resolve(this.users); + // } + + // public findOne(id: number): User | undefined { + // // here, for example you can load category id using mongoose + // // you can also return a promise here + // let foundUser: User | undefined = undefined; + // this.users.forEach(u => { + // if (u.id === id) { + // foundUser = u; + // } + // }); + // return foundUser; + // } + + // public save(user: User): User { + // // here, for example you can save a user to mongodb using mongoose + // this.users.push(user); + // return user; + // } + + // public remove(id: number): User | undefined { + // // here, for example you can save a category to mongodb using mongoose + // const user = this.findOne(id); + // if (user) { + // this.users.splice(this.users.indexOf(user), 1); + // } + // return user; + // } } diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index 236df0d0..833f4b7c 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -1,37 +1,33 @@ import { Container } from 'typedi'; +import { useContainer as ormUseContainer } from 'typeorm'; +import { useContainer as routingUseContainer, createExpressServer } from 'routing-controllers'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; -import { createExpressServer, useContainer } from 'routing-controllers'; - -import { UserController } from '../api/controllers/UserController'; export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + // base directory. we use it because file in "required" in another module + const baseDir = __dirname; /** * Setup routing-controllers to use typedi container. */ - useContainer(Container); + routingUseContainer(Container); + ormUseContainer(Container); /** * We create a new express server instance. * We could have also use useExpressServer here to attach controllers to an existing express instance. */ const app = createExpressServer({ + cors: true, /** * We can add options about how routing-controllers should configure itself. * Here we specify what controllers should be registered in our express server. */ - controllers: [ - UserController - ] - }); + controllers: [baseDir + '**/api/controllers/*{.js,.ts}'], + middlewares: [baseDir + '**/api/middlewares/*{.js,.ts}'] - // // create express app - // const app = express(); - - // // register all routes - // const routes: any = Routes; - // Object.keys(routes).forEach(routePath => app.get(routePath, routes[routePath])); + }); // run application to listen on given port app.listen(3000); diff --git a/src/modules/typeormLoader.ts b/src/modules/typeormLoader.ts index 201c3717..43450704 100644 --- a/src/modules/typeormLoader.ts +++ b/src/modules/typeormLoader.ts @@ -1,6 +1,7 @@ import { createConnection } from 'typeorm'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; +import { User } from '../api/models/User'; export const typeormLoader: MicroframeworkLoader = async (settings: MicroframeworkSettings | undefined) => { @@ -13,7 +14,10 @@ export const typeormLoader: MicroframeworkLoader = async (settings: Microframewo password: env.db.password, database: env.db.database, synchronize: env.db.synchronize, - logging: env.db.logging + logging: env.db.logging, + entities: [ + User + ] }); if (settings) { diff --git a/yarn.lock b/yarn.lock index 27c297a0..455ef149 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4335,6 +4335,10 @@ typedi@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/typedi/-/typedi-0.5.2.tgz#e1f67409bf0d19be86f072ff88ae30db15f61b74" +typeorm-typedi-extensions@^0.1.1: + version "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.3" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.1.3.tgz#b8ae44bc7fbebbedde1350bd3bb72fc4c99833a1" From 65f504668bf13fbba3eace3f116554e97892c4a7 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 14:41:12 +0100 Subject: [PATCH 007/104] Fix express settings --- src/api/controllers/UserController.ts | 2 ++ src/api/repositories/UserRepository.ts | 40 -------------------------- src/api/routes.ts | 17 ----------- src/core/env.ts | 2 +- src/modules/expressLoader.ts | 14 +++++---- 5 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 src/api/routes.ts diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index 9dd627f1..655f1e30 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -3,6 +3,7 @@ import { Service, Inject } from 'typedi'; import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; + @Service() @JsonController() export class UserController { @@ -12,6 +13,7 @@ export class UserController { @Get('/users') public async all(): Promise { + console.log(this.userRepository); return await this.userRepository.find(); } diff --git a/src/api/repositories/UserRepository.ts b/src/api/repositories/UserRepository.ts index 37fd6c78..2b0fee1a 100644 --- a/src/api/repositories/UserRepository.ts +++ b/src/api/repositories/UserRepository.ts @@ -1,4 +1,3 @@ - import { Service } from 'typedi'; import { Repository, EntityRepository } from 'typeorm'; import { User } from '../models/User'; @@ -8,43 +7,4 @@ import { User } from '../models/User'; @EntityRepository(User) export class UserRepository extends Repository { - - - // @OrmConnection() - // private connection: Connection; - - // public findAll(): Promise { - // // here, for example you can load categories using mongoose - // // you can also return a promise here - // // simulate async with creating an empty promise - // return Promise.resolve(this.users); - // } - - // public findOne(id: number): User | undefined { - // // here, for example you can load category id using mongoose - // // you can also return a promise here - // let foundUser: User | undefined = undefined; - // this.users.forEach(u => { - // if (u.id === id) { - // foundUser = u; - // } - // }); - // return foundUser; - // } - - // public save(user: User): User { - // // here, for example you can save a user to mongodb using mongoose - // this.users.push(user); - // return user; - // } - - // public remove(id: number): User | undefined { - // // here, for example you can save a category to mongodb using mongoose - // const user = this.findOne(id); - // if (user) { - // this.users.splice(this.users.indexOf(user), 1); - // } - // return user; - // } - } diff --git a/src/api/routes.ts b/src/api/routes.ts deleted file mode 100644 index a45dc86e..00000000 --- a/src/api/routes.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Request, Response } from 'express'; - -/** - * Just dummy routes as example how configuration can be separated from business logic. - * Try routing-controllers library if you want to have structured routes in your application. - */ -export const Routes = { - '/': (req: Request, res: Response) => { - res.send('Hello World'); - }, - '/posts': (req: Request, res: Response) => { - res.send('Hello posts'); - }, - '/users': (req: Request, res: Response) => { - res.send('Hello users'); - } -}; diff --git a/src/core/env.ts b/src/core/env.ts index 0d41531b..bd578940 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -7,7 +7,7 @@ export const env = { name: getOsEnv('APP_NAME'), host: getOsEnv('APP_HOST'), port: toNumber(getOsEnv('APP_PORT')), - urlPrefix: getOsEnv('APP_URL_PREFIX') + routePrefix: getOsEnv('APP_ROUTE_PREFIX') }, log: { level: getOsEnv('LOG_LEVEL'), diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index 833f4b7c..ec8597ba 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -2,11 +2,14 @@ import { Container } from 'typedi'; import { useContainer as ormUseContainer } from 'typeorm'; import { useContainer as routingUseContainer, createExpressServer } from 'routing-controllers'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { env } from '../core/env'; export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { - // base directory. we use it because file in "required" in another module - const baseDir = __dirname; + /** + * Base directory. we use it because file in "required" in another module + */ + const baseDir = __dirname.replace('modules', ''); /** * Setup routing-controllers to use typedi container. @@ -20,16 +23,17 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett */ const app = createExpressServer({ cors: true, + routePrefix: env.app.routePrefix, /** * We can add options about how routing-controllers should configure itself. * Here we specify what controllers should be registered in our express server. */ - controllers: [baseDir + '**/api/controllers/*{.js,.ts}'], - middlewares: [baseDir + '**/api/middlewares/*{.js,.ts}'] + controllers: [baseDir + '/api/controllers/*{.js,.ts}'], + middlewares: [baseDir + '/api/middlewares/*{.js,.ts}'] }); - // run application to listen on given port + // TODO: run application to listen on given port app.listen(3000); // here we can set the data for other loaders From 0ac35bd44424c8470ae3801d0f59e8f0cbc1aa55 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 14:51:36 +0100 Subject: [PATCH 008/104] Add orm repo --- src/api/controllers/UserController.ts | 10 +++++----- src/api/repositories/UserRepository.ts | 1 + src/modules/expressLoader.ts | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index 655f1e30..fdaede3d 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -1,6 +1,7 @@ -import { JsonController, Get, Post as HttpPost, Param, Delete, Body } from 'routing-controllers'; -import { Service, Inject } from 'typedi'; +import { JsonController, Get, Post, Param, Delete, Body } from 'routing-controllers'; +import { Service } from 'typedi'; import { UserRepository } from '../repositories/UserRepository'; +import { OrmRepository } from 'typeorm-typedi-extensions'; import { User } from '../models/User'; @@ -8,12 +9,11 @@ import { User } from '../models/User'; @JsonController() export class UserController { - @Inject() + @OrmRepository() private userRepository: UserRepository; @Get('/users') public async all(): Promise { - console.log(this.userRepository); return await this.userRepository.find(); } @@ -22,7 +22,7 @@ export class UserController { return await this.userRepository.findOne({ id }); } - @HttpPost('/users') + @Post('/users') public async post( @Body() user: User): Promise { return await this.userRepository.save(user); } diff --git a/src/api/repositories/UserRepository.ts b/src/api/repositories/UserRepository.ts index 2b0fee1a..1692c909 100644 --- a/src/api/repositories/UserRepository.ts +++ b/src/api/repositories/UserRepository.ts @@ -7,4 +7,5 @@ import { User } from '../models/User'; @EntityRepository(User) export class UserRepository extends Repository { + } diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index ec8597ba..44a3184e 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -29,7 +29,8 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett * Here we specify what controllers should be registered in our express server. */ controllers: [baseDir + '/api/controllers/*{.js,.ts}'], - middlewares: [baseDir + '/api/middlewares/*{.js,.ts}'] + middlewares: [baseDir + '/api/middlewares/*{.js,.ts}'], + interceptors: [baseDir + '/api/interceptors/*{.js,.ts}'] }); From 44b74ec45186437144e6e43a00af186868c765da Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 14:56:26 +0100 Subject: [PATCH 009/104] Improve express settings --- .env.example | 57 +++++++----------------------------- src/core/env.ts | 13 +++++++- src/modules/expressLoader.ts | 2 +- 3 files changed, 23 insertions(+), 49 deletions(-) diff --git a/.env.example b/.env.example index 91427e1f..a71b425d 100644 --- a/.env.example +++ b/.env.example @@ -2,61 +2,24 @@ # APPLICATION # APP_NAME="express-typescript-boilerplate" -APP_ENV="local" APP_HOST="http://localhost:3000" -APP_URL_PREFIX="/api" +APP_ROUTE_PREFIX="/api" APP_PORT=3000 # # LOGGING # LOG_LEVEL="debug" -LOG_ADAPTER="winston" - -# -# APPLICATION -# -APP_BASIC_USER="admin" -APP_BASIC_PASSWORD="1234" - -# -# API Info -# -API_INFO_ENABLED=true -API_INFO_ROUTE="/info" - -# -# Swagger Documentation -# -SWAGGER_ENABLED=true -SWAGGER_ROUTE="/swagger" -SWAGGER_FILE="/src/api/swagger.json" - -# -# Monitor -# -MONITOR_ENABLED=true -MONITOR_ROUTE="/monitor" +LOG_JSON=false # # DATABASE # -# MySql -DB_CLIENT="mysql" -DB_CONNECTION="mysql://root@localhost:3306/my_database" - -# PostGres -#DB_CLIENT=pg -#DB_CONNECTION=postgres://root:root@localhost:5432/my_database - -# Knex -DB_POOL_MIN=0 -DB_POOL_MAX=7 -DB_MIGRATION_DIR="./src/database/migrations" -DB_MIGRATION_TABLE="version" -DB_SEEDS_DIR="./src/database/seeds" - -# -# Auth0 -# -AUTH0_HOST="http://localhost:3333" +DB_TYPE="mysql" +DB_HOST="localhost" +DB_PORT=3306 +DB_USERNAME="root" +DB_PASSWORD="" +DB_DATABASE="my_database" +DB_SYNCHRONIZE=true +DB_LOGGING=true diff --git a/src/core/env.ts b/src/core/env.ts index bd578940..6258130f 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -6,7 +6,7 @@ export const env = { app: { name: getOsEnv('APP_NAME'), host: getOsEnv('APP_HOST'), - port: toNumber(getOsEnv('APP_PORT')), + port: normalizePort(getOsEnv('PORT') || getOsEnv('APP_PORT') || '3000'), routePrefix: getOsEnv('APP_ROUTE_PREFIX') }, log: { @@ -36,3 +36,14 @@ function toNumber(value: string): number { function toBool(value: string): boolean { return value === 'true'; } + +function normalizePort(port: string): number | string | boolean { + const parsedPort = parseInt(port, 10); + if (isNaN(parsedPort)) { // named pipe + return port; + } + if (parsedPort >= 0) { // port number + return parsedPort; + } + return false; +} diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index 44a3184e..61a76ce0 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -35,7 +35,7 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett }); // TODO: run application to listen on given port - app.listen(3000); + app.listen(env.app.port); // here we can set the data for other loaders if (settings) { From a19729299be51fe5bffe4e509cb806e48dee00e5 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 15:31:22 +0100 Subject: [PATCH 010/104] Add global middlewares --- .env.example | 4 ++-- src/api/middlewares/CompressionMiddleware.ts | 14 ++++++++++++ src/api/middlewares/LogMiddleware.ts | 22 +++++++++++++++++++ src/api/middlewares/SecurityHstsMiddleware.ts | 17 ++++++++++++++ src/api/middlewares/SecurityMiddleware.ts | 14 ++++++++++++ .../middlewares/SecurityNoCacheMiddleware.ts | 14 ++++++++++++ src/app.ts | 2 +- src/core/env.ts | 5 +++-- src/modules/expressLoader.ts | 21 ++++++++---------- 9 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 src/api/middlewares/CompressionMiddleware.ts create mode 100644 src/api/middlewares/LogMiddleware.ts create mode 100644 src/api/middlewares/SecurityHstsMiddleware.ts create mode 100644 src/api/middlewares/SecurityMiddleware.ts create mode 100644 src/api/middlewares/SecurityNoCacheMiddleware.ts diff --git a/.env.example b/.env.example index a71b425d..ab3820bb 100644 --- a/.env.example +++ b/.env.example @@ -2,15 +2,15 @@ # APPLICATION # APP_NAME="express-typescript-boilerplate" -APP_HOST="http://localhost:3000" +APP_HOST="http://localhost" APP_ROUTE_PREFIX="/api" -APP_PORT=3000 # # LOGGING # LOG_LEVEL="debug" LOG_JSON=false +LOG_OUTPUT="dev" # # DATABASE diff --git a/src/api/middlewares/CompressionMiddleware.ts b/src/api/middlewares/CompressionMiddleware.ts new file mode 100644 index 00000000..4a8e99da --- /dev/null +++ b/src/api/middlewares/CompressionMiddleware.ts @@ -0,0 +1,14 @@ + +import * as express from 'express'; +import * as compression from 'compression'; +import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; + + +@Middleware({ type: 'before' }) +export class SecurityMiddleware implements ExpressMiddlewareInterface { + + public use(req: any, res: any, next: express.NextFunction): any { + return compression()(req, res, next); + } + +} diff --git a/src/api/middlewares/LogMiddleware.ts b/src/api/middlewares/LogMiddleware.ts new file mode 100644 index 00000000..fb1b5781 --- /dev/null +++ b/src/api/middlewares/LogMiddleware.ts @@ -0,0 +1,22 @@ + +import * as express from 'express'; +import * as morgan from 'morgan'; +import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; +import { Log } from '../../core/Log'; +import { env } from '../../core/env'; + + +@Middleware({ type: 'before' }) +export class SecurityMiddleware implements ExpressMiddlewareInterface { + + private log = new Log(__dirname); + + public use(req: any, res: any, next: express.NextFunction): any { + return morgan(env.log.output, { + stream: { + write: this.log.info.bind(this.log) + } + })(req, res, next); + } + +} diff --git a/src/api/middlewares/SecurityHstsMiddleware.ts b/src/api/middlewares/SecurityHstsMiddleware.ts new file mode 100644 index 00000000..fcc7ba47 --- /dev/null +++ b/src/api/middlewares/SecurityHstsMiddleware.ts @@ -0,0 +1,17 @@ + +import * as express from 'express'; +import * as helmet from 'helmet'; +import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; + + +@Middleware({ type: 'before' }) +export class SecurityMiddleware implements ExpressMiddlewareInterface { + + public use(req: any, res: any, next: express.NextFunction): any { + return helmet.hsts({ + maxAge: 31536000, + includeSubdomains: true + })(req, res, next); + } + +} diff --git a/src/api/middlewares/SecurityMiddleware.ts b/src/api/middlewares/SecurityMiddleware.ts new file mode 100644 index 00000000..29bb8229 --- /dev/null +++ b/src/api/middlewares/SecurityMiddleware.ts @@ -0,0 +1,14 @@ + +import * as express from 'express'; +import * as helmet from 'helmet'; +import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; + + +@Middleware({ type: 'before' }) +export class SecurityMiddleware implements ExpressMiddlewareInterface { + + public use(req: any, res: any, next: express.NextFunction): any { + return helmet()(req, res, next); + } + +} diff --git a/src/api/middlewares/SecurityNoCacheMiddleware.ts b/src/api/middlewares/SecurityNoCacheMiddleware.ts new file mode 100644 index 00000000..16eea31a --- /dev/null +++ b/src/api/middlewares/SecurityNoCacheMiddleware.ts @@ -0,0 +1,14 @@ + +import * as express from 'express'; +import * as helmet from 'helmet'; +import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; + + +@Middleware({ type: 'before' }) +export class SecurityMiddleware implements ExpressMiddlewareInterface { + + public use(req: any, res: any, next: express.NextFunction): any { + return helmet.noCache()(req, res, next); + } + +} diff --git a/src/app.ts b/src/app.ts index ef3713d2..750e7998 100644 --- a/src/app.ts +++ b/src/app.ts @@ -24,8 +24,8 @@ import { typeormLoader } from './modules/typeormLoader'; bootstrapMicroframework({ loaders: [ - expressLoader, winstonLoader, + expressLoader, typeormLoader // here we can setup other databases, any other lib we want to setup in our application ] diff --git a/src/core/env.ts b/src/core/env.ts index 6258130f..5bbdfa4d 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -6,12 +6,13 @@ export const env = { app: { name: getOsEnv('APP_NAME'), host: getOsEnv('APP_HOST'), - port: normalizePort(getOsEnv('PORT') || getOsEnv('APP_PORT') || '3000'), + port: normalizePort(process.env.PORT || '3000'), routePrefix: getOsEnv('APP_ROUTE_PREFIX') }, log: { level: getOsEnv('LOG_LEVEL'), - json: toBool(getOsEnv('LOG_JSON')) + json: toBool(getOsEnv('LOG_JSON')), + output: getOsEnv('LOG_OUTPUT') }, db: { type: getOsEnv('DB_TYPE'), diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index 61a76ce0..dc16eba8 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import { Container } from 'typedi'; import { useContainer as ormUseContainer } from 'typeorm'; import { useContainer as routingUseContainer, createExpressServer } from 'routing-controllers'; @@ -6,10 +7,6 @@ import { env } from '../core/env'; export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { - /** - * Base directory. we use it because file in "required" in another module - */ - const baseDir = __dirname.replace('modules', ''); /** * Setup routing-controllers to use typedi container. @@ -21,24 +18,24 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett * We create a new express server instance. * We could have also use useExpressServer here to attach controllers to an existing express instance. */ - const app = createExpressServer({ + const expressApp = createExpressServer({ cors: true, routePrefix: env.app.routePrefix, /** * We can add options about how routing-controllers should configure itself. * Here we specify what controllers should be registered in our express server. */ - controllers: [baseDir + '/api/controllers/*{.js,.ts}'], - middlewares: [baseDir + '/api/middlewares/*{.js,.ts}'], - interceptors: [baseDir + '/api/interceptors/*{.js,.ts}'] + controllers: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')], + middlewares: [path.join(__dirname, '..', 'api/middlewares/*{.js,.ts}')], + interceptors: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')] }); - // TODO: run application to listen on given port - app.listen(env.app.port); + // Run application to listen on given port + expressApp.listen(env.app.port); - // here we can set the data for other loaders + // Here we can set the data for other loaders if (settings) { - settings.setData('express_app', app); + settings.setData('express_app', expressApp); } }; From 6ad246c6d64be191876a88198ec6f0fef5843b7d Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 15:34:19 +0100 Subject: [PATCH 011/104] Add basic validation --- src/api/models/User.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api/models/User.ts b/src/api/models/User.ts index 0dc80c50..151a85e4 100644 --- a/src/api/models/User.ts +++ b/src/api/models/User.ts @@ -1,4 +1,5 @@ import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm'; +import { IsNotEmpty } from 'class-validator'; @Entity() @@ -7,9 +8,11 @@ export class User extends BaseEntity { @PrimaryGeneratedColumn('uuid') public id: string; + @IsNotEmpty() @Column({ name: 'first_name' }) public firstName: string; + @IsNotEmpty() @Column({ name: 'last_name' }) public lastName: string; From f9e679d937acbf221098a129b3d6a1ada005e096 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 17:20:19 +0100 Subject: [PATCH 012/104] Add error and service layer --- src/api/controllers/UserController.ts | 45 +++++++++++++++------------ src/api/errors/UserNotFoundError.ts | 7 +++++ src/api/services/UserService.ts | 34 ++++++++++++++++++++ src/modules/expressLoader.ts | 2 +- 4 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 src/api/errors/UserNotFoundError.ts create mode 100644 src/api/services/UserService.ts diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index fdaede3d..67f612d9 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -1,35 +1,40 @@ -import { JsonController, Get, Post, Param, Delete, Body } from 'routing-controllers'; -import { Service } from 'typedi'; -import { UserRepository } from '../repositories/UserRepository'; -import { OrmRepository } from 'typeorm-typedi-extensions'; +import { JsonController, Get, Post, Put, Param, Delete, Body, OnUndefined } from 'routing-controllers'; +import { Inject } from 'typedi'; +import { UserService } from '../services/UserService'; import { User } from '../models/User'; +import { UserNotFoundError } from '../errors/UserNotFoundError'; -@Service() -@JsonController() +@JsonController('/users') export class UserController { - @OrmRepository() - private userRepository: UserRepository; + @Inject() + private userService: UserService; - @Get('/users') - public async all(): Promise { - return await this.userRepository.find(); + @Get() + public find(): Promise { + return this.userService.find(); } - @Get('/users/:id') - public async one( @Param('id') id: string): Promise { - return await this.userRepository.findOne({ id }); + @Get('/:id') + @OnUndefined(UserNotFoundError) + public one( @Param('id') id: string): Promise { + return this.userService.findOne(id); } - @Post('/users') - public async post( @Body() user: User): Promise { - return await this.userRepository.save(user); + @Post() + public create( @Body() user: User): Promise { + return this.userService.create(user); } - @Delete('/users/:id') - public async delete( @Param('id') id: string): Promise { - return await this.userRepository.removeById(id); + @Put('/:id') + public update( @Param('id') id: string, @Body() user: User): Promise { + return this.userService.update(id, user); + } + + @Delete('/:id') + public delete( @Param('id') id: string): Promise { + return this.userService.delete(id); } } diff --git a/src/api/errors/UserNotFoundError.ts b/src/api/errors/UserNotFoundError.ts new file mode 100644 index 00000000..2a2b1371 --- /dev/null +++ b/src/api/errors/UserNotFoundError.ts @@ -0,0 +1,7 @@ +import { HttpError } from 'routing-controllers'; + +export class UserNotFoundError extends HttpError { + constructor() { + super(404, 'User not found!'); + } +} diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts new file mode 100644 index 00000000..b0b346dd --- /dev/null +++ b/src/api/services/UserService.ts @@ -0,0 +1,34 @@ +import { Service } from 'typedi'; +import { OrmRepository } from 'typeorm-typedi-extensions'; +import { UserRepository } from '../repositories/UserRepository'; +import { User } from '../models/User'; + + +@Service() +export class UserService { + + @OrmRepository() + private userRepository: UserRepository; + + public find(): Promise { + return this.userRepository.find(); + } + + public findOne(id: string): Promise { + return this.userRepository.findOne({ id }); + } + + public create(user: User): Promise { + return this.userRepository.save(user); + } + + public update(id: string, user: User): Promise { + user.id = id; + return this.userRepository.save(user); + } + + public delete(id: string): Promise { + return this.userRepository.removeById(id); + } + +} diff --git a/src/modules/expressLoader.ts b/src/modules/expressLoader.ts index dc16eba8..e886c400 100644 --- a/src/modules/expressLoader.ts +++ b/src/modules/expressLoader.ts @@ -27,7 +27,7 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett */ controllers: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')], middlewares: [path.join(__dirname, '..', 'api/middlewares/*{.js,.ts}')], - interceptors: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')] + interceptors: [path.join(__dirname, '..', 'api/interceptors/*{.js,.ts}')] }); From 8747d13bb7fbea8ac6e35d8f198a1feccb38ab5e Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 17:38:52 +0100 Subject: [PATCH 013/104] Add swagger ui --- .env.example | 9 +++++++++ package.json | 2 ++ src/app.ts | 4 +++- src/core/env.ts | 11 +++++++++++ src/modules/swaggerLoader.ts | 33 +++++++++++++++++++++++++++++++++ tslint.json | 1 + yarn.lock | 14 ++++++++++++++ 7 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/modules/swaggerLoader.ts diff --git a/.env.example b/.env.example index ab3820bb..af73438d 100644 --- a/.env.example +++ b/.env.example @@ -23,3 +23,12 @@ DB_PASSWORD="" DB_DATABASE="my_database" DB_SYNCHRONIZE=true DB_LOGGING=true + +# +# Swagger +# +SWAGGER_ENABLED=true +SWAGGER_ROUTE="/swagger" +SWAGGER_FILE="api/swagger.json" +SWAGGER_USERNAME="admin" +SWAGGER_PASSWORD="1234" diff --git a/package.json b/package.json index b491892f..13fa2982 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "cors": "^2.8.4", "dotenv": "^4.0.0", "express": "^4.16.2", + "express-basic-auth": "^1.1.3", "figlet": "^1.2.0", "helmet": "^3.9.0", "lodash": "^4.17.4", @@ -71,6 +72,7 @@ "reflect-metadata": "^0.1.10", "routing-controllers": "^0.7.6", "serve-favicon": "^2.4.5", + "swagger-ui-express": "^2.0.10", "trash-cli": "^1.4.0", "ts-node": "^3.3.0", "tslint": "^5.8.0", diff --git a/src/app.ts b/src/app.ts index 750e7998..4af0ecee 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,13 +20,15 @@ import { bootstrapMicroframework } from 'microframework'; import { expressLoader } from './modules/expressLoader'; import { winstonLoader } from './modules/winstonLoader'; import { typeormLoader } from './modules/typeormLoader'; +import { swaggerLoader } from './modules/swaggerLoader'; bootstrapMicroframework({ loaders: [ winstonLoader, expressLoader, - typeormLoader + typeormLoader, + swaggerLoader // here we can setup other databases, any other lib we want to setup in our application ] }) diff --git a/src/core/env.ts b/src/core/env.ts index 5bbdfa4d..09f8dc22 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -1,3 +1,5 @@ +const pkg = require('../../package.json'); + /** * Environment variables */ @@ -5,6 +7,8 @@ export const env = { node: process.env.NODE_ENV || 'development', app: { name: getOsEnv('APP_NAME'), + version: pkg.version, + description: pkg.description, host: getOsEnv('APP_HOST'), port: normalizePort(process.env.PORT || '3000'), routePrefix: getOsEnv('APP_ROUTE_PREFIX') @@ -23,6 +27,13 @@ export const env = { database: getOsEnv('DB_DATABASE'), synchronize: toBool(getOsEnv('DB_SYNCHRONIZE')), logging: toBool(getOsEnv('DB_LOGGING')) + }, + swagger: { + enabled: toBool(getOsEnv('SWAGGER_ENABLED')), + route: getOsEnv('SWAGGER_ROUTE'), + file: getOsEnv('SWAGGER_FILE'), + username: getOsEnv('SWAGGER_USERNAME'), + password: getOsEnv('SWAGGER_PASSWORD') } }; diff --git a/src/modules/swaggerLoader.ts b/src/modules/swaggerLoader.ts new file mode 100644 index 00000000..dc7d1818 --- /dev/null +++ b/src/modules/swaggerLoader.ts @@ -0,0 +1,33 @@ +import * as path from 'path'; +import * as swaggerUi from 'swagger-ui-express'; +import * as basicAuth from 'express-basic-auth'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { env } from '../core/env'; + + +export const swaggerLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + if (settings && env.swagger.enabled) { + const expressApp = settings.getData('express_app'); + const swaggerFile = require(path.join(__dirname, '..', env.swagger.file)); + + // Add npm infos to the swagger doc + swaggerFile.info = { + title: env.app.name, + description: env.app.description, + version: env.app.version + }; + + expressApp.use( + env.swagger.route, + basicAuth({ + users: { + [`${env.swagger.username}`]: env.swagger.password + }, + challenge: true + }), + swaggerUi.serve, + swaggerUi.setup(swaggerFile) + ); + + } +}; diff --git a/tslint.json b/tslint.json index c9689d23..eee75561 100644 --- a/tslint.json +++ b/tslint.json @@ -6,6 +6,7 @@ 160 ], "no-unnecessary-initializer": false, + "no-var-requires": false, "no-consecutive-blank-lines": false, "quotemark": [true, "single", "avoid-escape"], "interface-name": false, diff --git a/yarn.lock b/yarn.lock index 455ef149..f5deee21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -515,6 +515,10 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +basic-auth@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" + basic-auth@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" @@ -1343,6 +1347,12 @@ expect@^21.2.1: jest-message-util "^21.2.1" jest-regex-util "^21.2.0" +express-basic-auth@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.1.3.tgz#18924c02fef18d9efe58e22847ee31e240749f33" + dependencies: + basic-auth "^1.1.0" + express@^4.16.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" @@ -4086,6 +4096,10 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" +swagger-ui-express@^2.0.10: + version "2.0.10" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-2.0.10.tgz#a7beb3e44ad0abd3c9915afc6b41cb87a91aa3e7" + symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" From e5bec244efb022d4a4e2516225d76e1c312229f6 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 17:50:09 +0100 Subject: [PATCH 014/104] Add status monitor --- .env.example | 8 ++ package.json | 1 + src/app.ts | 4 +- src/core/env.ts | 6 + src/modules/monitorLoader.ts | 33 ++++++ yarn.lock | 218 ++++++++++++++++++++++++++++++++++- 6 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 src/modules/monitorLoader.ts diff --git a/.env.example b/.env.example index af73438d..e434bd7e 100644 --- a/.env.example +++ b/.env.example @@ -32,3 +32,11 @@ SWAGGER_ROUTE="/swagger" SWAGGER_FILE="api/swagger.json" SWAGGER_USERNAME="admin" SWAGGER_PASSWORD="1234" + +# +# Status Monitor +# +MONITOR_ENABLED=true +MONITOR_ROUTE="/monitor" +MONITOR_USERNAME="admin" +MONITOR_PASSWORD="1234" diff --git a/package.json b/package.json index 13fa2982..6791fc99 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "dotenv": "^4.0.0", "express": "^4.16.2", "express-basic-auth": "^1.1.3", + "express-status-monitor": "^1.0.1", "figlet": "^1.2.0", "helmet": "^3.9.0", "lodash": "^4.17.4", diff --git a/src/app.ts b/src/app.ts index 4af0ecee..3e54a7e9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,6 +21,7 @@ import { expressLoader } from './modules/expressLoader'; import { winstonLoader } from './modules/winstonLoader'; import { typeormLoader } from './modules/typeormLoader'; import { swaggerLoader } from './modules/swaggerLoader'; +import { monitorLoader } from './modules/monitorLoader'; bootstrapMicroframework({ @@ -28,7 +29,8 @@ bootstrapMicroframework({ winstonLoader, expressLoader, typeormLoader, - swaggerLoader + swaggerLoader, + monitorLoader // here we can setup other databases, any other lib we want to setup in our application ] }) diff --git a/src/core/env.ts b/src/core/env.ts index 09f8dc22..7c330124 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -34,6 +34,12 @@ export const env = { file: getOsEnv('SWAGGER_FILE'), username: getOsEnv('SWAGGER_USERNAME'), password: getOsEnv('SWAGGER_PASSWORD') + }, + monitor: { + enabled: toBool(getOsEnv('MONITOR_ENABLED')), + route: getOsEnv('MONITOR_ROUTE'), + username: getOsEnv('MONITOR_USERNAME'), + password: getOsEnv('MONITOR_PASSWORD') } }; diff --git a/src/modules/monitorLoader.ts b/src/modules/monitorLoader.ts new file mode 100644 index 00000000..a9eb2dec --- /dev/null +++ b/src/modules/monitorLoader.ts @@ -0,0 +1,33 @@ +import * as path from 'path'; +import * as monitor from 'express-status-monitor'; +import * as basicAuth from 'express-basic-auth'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { env } from '../core/env'; + + +export const monitorLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + if (settings && env.monitor.enabled) { + const expressApp = settings.getData('express_app'); + const swaggerFile = require(path.join(__dirname, '..', env.swagger.file)); + + // Add npm infos to the swagger doc + swaggerFile.info = { + title: env.app.name, + description: env.app.description, + version: env.app.version + }; + + expressApp.use(monitor()); + expressApp.get( + env.monitor.route, + basicAuth({ + users: { + [`${env.monitor.username}`]: env.monitor.password + }, + challenge: true + }), + monitor().pageRoute + ); + + } +}; diff --git a/yarn.lock b/yarn.lock index f5deee21..87dabafc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,6 +108,13 @@ abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" +accepts@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" + dependencies: + mime-types "~2.1.11" + negotiator "0.6.1" + accepts@~1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" @@ -125,6 +132,10 @@ acorn@^4.0.4: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" +after@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" @@ -285,6 +296,10 @@ array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" +arraybuffer.slice@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" + arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -321,6 +336,10 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -511,10 +530,22 @@ babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + +base64id@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" + basic-auth@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" @@ -531,6 +562,12 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + bignumber.js@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.0.4.tgz#7c40f5abcd2d6623ab7b99682ee7db81b11889a4" @@ -539,6 +576,10 @@ binary-extensions@^1.0.0: version "1.10.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" +blob@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -643,6 +684,10 @@ bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" @@ -830,6 +875,18 @@ common-tags@^1.4.0: dependencies: babel-runtime "^6.18.0" +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + compressible@~2.0.11: version "2.0.12" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" @@ -1104,7 +1161,7 @@ date-fns@^1.23.0: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" -debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.8: +debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1220,6 +1277,45 @@ encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" +engine.io-client@~3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.1.4.tgz#4fcf1370b47163bd2ce9be2733972430350d4ea1" + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "~2.6.9" + engine.io-parser "~2.1.1" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.5" + parseuri "0.0.5" + ws "~3.3.1" + xmlhttprequest-ssl "~1.5.4" + yeast "0.1.2" + +engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.1.tgz#e0fb3f0e0462f7f58bb77c1a52e9f5a7e26e4668" + dependencies: + after "0.8.2" + arraybuffer.slice "0.0.6" + base64-arraybuffer "0.1.5" + blob "0.0.4" + has-binary2 "~1.0.2" + +engine.io@~3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.1.4.tgz#3d0211b70a552ce841ffc7da8627b301a9a4162e" + dependencies: + accepts "1.3.3" + base64id "1.0.0" + cookie "0.3.1" + debug "~2.6.9" + engine.io-parser "~2.1.0" + ws "~3.3.1" + optionalDependencies: + uws "~0.14.4" + errno@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -1353,6 +1449,15 @@ express-basic-auth@^1.1.3: dependencies: basic-auth "^1.1.0" +express-status-monitor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/express-status-monitor/-/express-status-monitor-1.0.1.tgz#311288347b7aabfeaec0a01547e55c77652bb298" + dependencies: + debug "^2.6.9" + on-headers "^1.0.1" + pidusage "^1.1.6" + socket.io "^2.0.3" + express@^4.16.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" @@ -1797,6 +1902,16 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-binary2@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98" + dependencies: + isarray "2.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -1956,6 +2071,10 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2145,6 +2264,10 @@ isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isarray@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" + isemail@2.x.x: version "2.2.1" resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" @@ -2858,7 +2981,7 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: @@ -3108,6 +3231,10 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -3121,7 +3248,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.1: +on-headers@^1.0.1, on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" @@ -3276,6 +3403,18 @@ parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -3349,6 +3488,10 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" +pidusage@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.2.0.tgz#65ee96ace4e08a4cd3f9240996c85b367171ee92" + pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -3884,6 +4027,47 @@ sntp@2.x.x: dependencies: hoek "4.x.x" +socket.io-adapter@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" + +socket.io-client@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.0.4.tgz#0918a552406dc5e540b380dcd97afc4a64332f8e" + dependencies: + backo2 "1.0.2" + base64-arraybuffer "0.1.5" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "~2.6.4" + engine.io-client "~3.1.0" + has-cors "1.1.0" + indexof "0.0.1" + object-component "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + socket.io-parser "~3.1.1" + to-array "0.1.4" + +socket.io-parser@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.1.2.tgz#dbc2282151fc4faebbe40aeedc0772eba619f7f2" + dependencies: + component-emitter "1.2.1" + debug "~2.6.4" + has-binary2 "~1.0.2" + isarray "2.0.1" + +socket.io@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.0.4.tgz#c1a4590ceff87ecf13c72652f046f716b29e6014" + dependencies: + debug "~2.6.6" + engine.io "~3.1.0" + socket.io-adapter "~1.1.0" + socket.io-client "2.0.4" + socket.io-parser "~3.1.1" + source-map-support@^0.4.0, source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" @@ -4192,6 +4376,10 @@ tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -4390,6 +4578,10 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" +ultron@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" + undefsafe@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f" @@ -4490,6 +4682,10 @@ uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +uws@~0.14.4: + version "0.14.5" + resolved "https://registry.yarnpkg.com/uws/-/uws-0.14.5.tgz#67aaf33c46b2a587a5f6666d00f7691328f149dc" + v8flags@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.1.tgz#dce8fc379c17d9f2c9e9ed78d89ce00052b1b76b" @@ -4658,6 +4854,14 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +ws@~3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.1.tgz#d97e34dee06a1190c61ac1e95f43cb60b78cf939" + dependencies: + async-limiter "~1.0.0" + 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" @@ -4704,6 +4908,10 @@ xmlbuilder@~9.0.1: version "9.0.4" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" +xmlhttprequest-ssl@~1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz#04f560915724b389088715cc0ed7813e9677bf57" + xtend@^4.0.0, xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -4824,6 +5032,10 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" + yn@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" From 547d37bec29c49c380a6f5f0d673f51c9aca04e2 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 21:59:55 +0100 Subject: [PATCH 015/104] Add home loader --- .env.example | 2 +- src/app.ts | 4 +++- src/core/env.ts | 6 +++--- src/modules/homeLoader.ts | 22 ++++++++++++++++++++++ src/modules/monitorLoader.ts | 9 --------- 5 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 src/modules/homeLoader.ts diff --git a/.env.example b/.env.example index e434bd7e..66131808 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # APPLICATION # APP_NAME="express-typescript-boilerplate" -APP_HOST="http://localhost" +APP_ROUTE="http://localhost:3000" APP_ROUTE_PREFIX="/api" # diff --git a/src/app.ts b/src/app.ts index 3e54a7e9..5433bd77 100644 --- a/src/app.ts +++ b/src/app.ts @@ -22,6 +22,7 @@ import { winstonLoader } from './modules/winstonLoader'; import { typeormLoader } from './modules/typeormLoader'; import { swaggerLoader } from './modules/swaggerLoader'; import { monitorLoader } from './modules/monitorLoader'; +import { homeLoader } from './modules/homeLoader'; bootstrapMicroframework({ @@ -30,7 +31,8 @@ bootstrapMicroframework({ expressLoader, typeormLoader, swaggerLoader, - monitorLoader + monitorLoader, + homeLoader // here we can setup other databases, any other lib we want to setup in our application ] }) diff --git a/src/core/env.ts b/src/core/env.ts index 7c330124..ffb2906d 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -9,9 +9,9 @@ export const env = { name: getOsEnv('APP_NAME'), version: pkg.version, description: pkg.description, - host: getOsEnv('APP_HOST'), - port: normalizePort(process.env.PORT || '3000'), - routePrefix: getOsEnv('APP_ROUTE_PREFIX') + route: getOsEnv('APP_ROUTE'), + routePrefix: getOsEnv('APP_ROUTE_PREFIX'), + port: normalizePort(process.env.PORT || '3000') }, log: { level: getOsEnv('LOG_LEVEL'), diff --git a/src/modules/homeLoader.ts b/src/modules/homeLoader.ts new file mode 100644 index 00000000..b54ab0d9 --- /dev/null +++ b/src/modules/homeLoader.ts @@ -0,0 +1,22 @@ +import * as monitor from 'express-status-monitor'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { env } from '../core/env'; + + +export const homeLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + if (settings) { + const expressApp = settings.getData('express_app'); + expressApp.use(monitor()); + expressApp.get( + env.app.routePrefix, + (req: myExpress.Request, res: myExpress.Response) => { + return res.json({ + name: env.app.name, + version: env.app.version, + description: env.app.description + }); + } + ); + + } +}; diff --git a/src/modules/monitorLoader.ts b/src/modules/monitorLoader.ts index a9eb2dec..354be5b6 100644 --- a/src/modules/monitorLoader.ts +++ b/src/modules/monitorLoader.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import * as monitor from 'express-status-monitor'; import * as basicAuth from 'express-basic-auth'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; @@ -8,14 +7,6 @@ import { env } from '../core/env'; export const monitorLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { if (settings && env.monitor.enabled) { const expressApp = settings.getData('express_app'); - const swaggerFile = require(path.join(__dirname, '..', env.swagger.file)); - - // Add npm infos to the swagger doc - swaggerFile.info = { - title: env.app.name, - description: env.app.description, - version: env.app.version - }; expressApp.use(monitor()); expressApp.get( From 9c92a17d6dc53e426e7e88b1c5a438590c2b96cd Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 22:00:39 +0100 Subject: [PATCH 016/104] Rename module folder to loaders --- src/app.ts | 14 ++++++-------- src/{modules => loaders}/expressLoader.ts | 0 src/{modules => loaders}/homeLoader.ts | 0 src/{modules => loaders}/monitorLoader.ts | 0 src/{modules => loaders}/swaggerLoader.ts | 0 src/{modules => loaders}/typeormLoader.ts | 0 src/{modules => loaders}/winstonLoader.ts | 0 7 files changed, 6 insertions(+), 8 deletions(-) rename src/{modules => loaders}/expressLoader.ts (100%) rename src/{modules => loaders}/homeLoader.ts (100%) rename src/{modules => loaders}/monitorLoader.ts (100%) rename src/{modules => loaders}/swaggerLoader.ts (100%) rename src/{modules => loaders}/typeormLoader.ts (100%) rename src/{modules => loaders}/winstonLoader.ts (100%) diff --git a/src/app.ts b/src/app.ts index 5433bd77..5bbffe9b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,8 +6,6 @@ * The basic layer of this app is express. For further information visit * the 'README.md' file. * - * To add express modules go to the 'config/AppConfig.ts' file. All the IOC registrations - * are in the 'config/IocConfig.ts' file. */ import 'reflect-metadata'; import * as dotenv from 'dotenv'; @@ -17,12 +15,12 @@ import { Log } from './core/Log'; const log = new Log(__filename); import { bootstrapMicroframework } from 'microframework'; -import { expressLoader } from './modules/expressLoader'; -import { winstonLoader } from './modules/winstonLoader'; -import { typeormLoader } from './modules/typeormLoader'; -import { swaggerLoader } from './modules/swaggerLoader'; -import { monitorLoader } from './modules/monitorLoader'; -import { homeLoader } from './modules/homeLoader'; +import { expressLoader } from './loaders/expressLoader'; +import { winstonLoader } from './loaders/winstonLoader'; +import { typeormLoader } from './loaders/typeormLoader'; +import { swaggerLoader } from './loaders/swaggerLoader'; +import { monitorLoader } from './loaders/monitorLoader'; +import { homeLoader } from './loaders/homeLoader'; bootstrapMicroframework({ diff --git a/src/modules/expressLoader.ts b/src/loaders/expressLoader.ts similarity index 100% rename from src/modules/expressLoader.ts rename to src/loaders/expressLoader.ts diff --git a/src/modules/homeLoader.ts b/src/loaders/homeLoader.ts similarity index 100% rename from src/modules/homeLoader.ts rename to src/loaders/homeLoader.ts diff --git a/src/modules/monitorLoader.ts b/src/loaders/monitorLoader.ts similarity index 100% rename from src/modules/monitorLoader.ts rename to src/loaders/monitorLoader.ts diff --git a/src/modules/swaggerLoader.ts b/src/loaders/swaggerLoader.ts similarity index 100% rename from src/modules/swaggerLoader.ts rename to src/loaders/swaggerLoader.ts diff --git a/src/modules/typeormLoader.ts b/src/loaders/typeormLoader.ts similarity index 100% rename from src/modules/typeormLoader.ts rename to src/loaders/typeormLoader.ts diff --git a/src/modules/winstonLoader.ts b/src/loaders/winstonLoader.ts similarity index 100% rename from src/modules/winstonLoader.ts rename to src/loaders/winstonLoader.ts From 466a1408c40185f22e4115a82ab633fe9ab09d89 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 22:05:40 +0100 Subject: [PATCH 017/104] Add public folder --- src/app.ts | 4 +++- src/loaders/publicLoader.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/loaders/publicLoader.ts diff --git a/src/app.ts b/src/app.ts index 5bbffe9b..ee342882 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,6 +21,7 @@ import { typeormLoader } from './loaders/typeormLoader'; import { swaggerLoader } from './loaders/swaggerLoader'; import { monitorLoader } from './loaders/monitorLoader'; import { homeLoader } from './loaders/homeLoader'; +import { publicLoader } from './loaders/publicLoader'; bootstrapMicroframework({ @@ -30,7 +31,8 @@ bootstrapMicroframework({ typeormLoader, swaggerLoader, monitorLoader, - homeLoader + homeLoader, + publicLoader // here we can setup other databases, any other lib we want to setup in our application ] }) diff --git a/src/loaders/publicLoader.ts b/src/loaders/publicLoader.ts new file mode 100644 index 00000000..491ca11e --- /dev/null +++ b/src/loaders/publicLoader.ts @@ -0,0 +1,18 @@ +import * as path from 'path'; +import * as express from 'express'; +import * as favicon from 'serve-favicon'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; + + +export const publicLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + if (settings) { + const expressApp = settings.getData('express_app'); + expressApp + // Serve static filles like images from the public folder + .use(express.static(path.join(__dirname, '..', 'public'), { maxAge: 31557600000 })) + + // A favicon is a visual cue that client software, like browsers, use to identify a site + .use(favicon(path.join(__dirname, '..', 'public', 'favicon.ico'))); + + } +}; From 2f3e2cc175497a0fc21dd9aca2b8e72b8c9f2395 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 18 Nov 2017 23:41:10 +0100 Subject: [PATCH 018/104] Add auth --- .env.example | 5 +++ package.json | 4 ++ src/api/controllers/UserController.ts | 5 ++- src/api/models/User.ts | 4 ++ src/auth/AuthService.ts | 45 +++++++++++++++++++++ src/auth/ITokenInfo.ts | 3 ++ src/auth/authorizationChecker.ts | 33 ++++++++++++++++ src/auth/currentUserChecker.ts | 32 +++++++++++++++ src/core/env.ts | 3 ++ src/loaders/expressLoader.ts | 9 ++++- src/loaders/typeormLoader.ts | 4 +- yarn.lock | 57 +++++++++++++++++++++++++-- 12 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 src/auth/AuthService.ts create mode 100644 src/auth/ITokenInfo.ts create mode 100644 src/auth/authorizationChecker.ts create mode 100644 src/auth/currentUserChecker.ts diff --git a/.env.example b/.env.example index 66131808..a0786823 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,11 @@ LOG_LEVEL="debug" LOG_JSON=false LOG_OUTPUT="dev" +# +# AUTHORIZATION +# +AUTH_ROUTE="http://localhost:3333/tokeninfo" + # # DATABASE # diff --git a/package.json b/package.json index 6791fc99..80a6e606 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "scripts": { "start": "node dist/app.js", "test": "nps test", + "build": "nps build", "ts-node": "./node_modules/.bin/ts-node", "ts-node:fast": "./node_modules/.bin/ts-node -F", "console": "npm run ts-node:fast -- ./src/console/lib/console.ts", @@ -50,6 +51,7 @@ "@types/lodash": "^4.14.80", "@types/morgan": "^1.7.35", "@types/reflect-metadata": "0.0.5", + "@types/request": "^2.0.8", "@types/serve-favicon": "^2.2.29", "@types/uuid": "^3.4.3", "@types/winston": "^2.3.7", @@ -57,6 +59,7 @@ "body-parser": "^1.18.2", "class-validator": "^0.7.3", "compression": "^1.7.1", + "copyfiles": "^1.2.0", "cors": "^2.8.4", "dotenv": "^4.0.0", "express": "^4.16.2", @@ -71,6 +74,7 @@ "nodemon": "^1.12.1", "path": "^0.12.7", "reflect-metadata": "^0.1.10", + "request": "^2.83.0", "routing-controllers": "^0.7.6", "serve-favicon": "^2.4.5", "swagger-ui-express": "^2.0.10", diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index 67f612d9..ac2c0933 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -1,10 +1,11 @@ -import { JsonController, Get, Post, Put, Param, Delete, Body, OnUndefined } from 'routing-controllers'; +import { JsonController, Get, Post, Put, Param, Delete, Body, OnUndefined, Authorized, CurrentUser } from 'routing-controllers'; import { Inject } from 'typedi'; import { UserService } from '../services/UserService'; import { User } from '../models/User'; import { UserNotFoundError } from '../errors/UserNotFoundError'; +@Authorized() @JsonController('/users') export class UserController { @@ -12,7 +13,7 @@ export class UserController { private userService: UserService; @Get() - public find(): Promise { + public find( @CurrentUser() user?: User): Promise { return this.userService.find(); } diff --git a/src/api/models/User.ts b/src/api/models/User.ts index 151a85e4..364a38bc 100644 --- a/src/api/models/User.ts +++ b/src/api/models/User.ts @@ -16,4 +16,8 @@ export class User extends BaseEntity { @Column({ name: 'last_name' }) public lastName: string; + @IsNotEmpty() + @Column() + public email: string; + } diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts new file mode 100644 index 00000000..9f8648b1 --- /dev/null +++ b/src/auth/AuthService.ts @@ -0,0 +1,45 @@ +import * as request from 'request'; +import { Service } from 'typedi'; +import { env } from '../core/env'; +import { ITokenInfo } from './ITokenInfo'; + + +@Service() +export class AuthService { + + public parseTokenFromRequest(req: myExpress.Request): string | null { + const authorization = req.header('authorization'); + + // Retrieve the token form the Authorization header + if (authorization && authorization.split(' ')[0] === 'Bearer') { + return authorization.split(' ')[1]; + } + + // No token was provided by the client + return null; + } + + public getTokenInfo(token: string): Promise { + return new Promise((resolve, reject) => { + request({ + method: 'POST', + url: env.auth.route, + form: { + id_token: token + } + }, (error: any, response: request.RequestResponse, body: any) => { + // Verify if the requests was successful and append user + // information to our extended express request object + if (!error) { + if (response.statusCode === 200) { + const tokeninfo = JSON.parse(body); + return resolve(tokeninfo); + } + return reject(body); + } + return reject(error); + }); + }); + } + +} diff --git a/src/auth/ITokenInfo.ts b/src/auth/ITokenInfo.ts new file mode 100644 index 00000000..052d4546 --- /dev/null +++ b/src/auth/ITokenInfo.ts @@ -0,0 +1,3 @@ +export interface ITokenInfo { + user_id: string; +} diff --git a/src/auth/authorizationChecker.ts b/src/auth/authorizationChecker.ts new file mode 100644 index 00000000..0c739816 --- /dev/null +++ b/src/auth/authorizationChecker.ts @@ -0,0 +1,33 @@ +import { Action } from 'routing-controllers'; +import { Container } from 'typedi'; +import { AuthService } from './AuthService'; +import { Log } from '../core/Log'; + +const log = new Log(__filename); +const authService = Container.get(AuthService); + + +export async function authorizationChecker(action: Action, roles: string[]): Promise { + // here you can use request/response objects from action + // also if decorator defines roles it needs to access the action + // you can use them to provide granular access check + // checker must return either boolean (true or false) + // either promise that resolves a boolean value + // demo code: + const token = authService.parseTokenFromRequest(action.request); + + if (token === null) { + log.warn('No token given'); + return false; // res.failed(403, 'You are not allowed to request this resource!'); + } + + // Request user info at auth0 with the provided token + try { + action.request.tokeninfo = await authService.getTokenInfo(token); + log.info('Successfully checked token'); + return true; + } catch (e) { + log.warn(e); + return false; + } +} diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts new file mode 100644 index 00000000..0ef112c0 --- /dev/null +++ b/src/auth/currentUserChecker.ts @@ -0,0 +1,32 @@ +import { Action } from 'routing-controllers'; +import { User } from '../api/models/User'; +import { Log } from '../core/Log'; +import { ITokenInfo } from './ITokenInfo'; +// import { UserRepository } from '../api/repositories/UserRepository'; +// import { getConnection } from 'typeorm'; + +const log = new Log(__filename); + + +export async function currentUserChecker(action: Action): Promise { + // here you can use request/response objects from action + // you need to provide a user object that will be injected in controller actions + // demo code: + const tokeninfo: ITokenInfo = action.request.tokeninfo; + log.info('todo user checker', tokeninfo); + + // const connection = getConnection(); + + // const userRepository = connection.getRepository(UserRepository); + // console.log(connection); + // const user = await userRepository. + // findOne({ + // where: { + // email: tokeninfo.user_id + // } + // }); + // console.log(user); + + return Promise.resolve(new User()); +} + diff --git a/src/core/env.ts b/src/core/env.ts index ffb2906d..0b20b8bf 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -18,6 +18,9 @@ export const env = { json: toBool(getOsEnv('LOG_JSON')), output: getOsEnv('LOG_OUTPUT') }, + auth: { + route: getOsEnv('AUTH_ROUTE') + }, db: { type: getOsEnv('DB_TYPE'), host: getOsEnv('DB_HOST'), diff --git a/src/loaders/expressLoader.ts b/src/loaders/expressLoader.ts index e886c400..815de90d 100644 --- a/src/loaders/expressLoader.ts +++ b/src/loaders/expressLoader.ts @@ -4,6 +4,8 @@ import { useContainer as ormUseContainer } from 'typeorm'; import { useContainer as routingUseContainer, createExpressServer } from 'routing-controllers'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; +import { authorizationChecker } from '../auth/authorizationChecker'; +import { currentUserChecker } from '../auth/currentUserChecker'; export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { @@ -27,8 +29,13 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett */ controllers: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')], middlewares: [path.join(__dirname, '..', 'api/middlewares/*{.js,.ts}')], - interceptors: [path.join(__dirname, '..', 'api/interceptors/*{.js,.ts}')] + interceptors: [path.join(__dirname, '..', 'api/interceptors/*{.js,.ts}')], + /** + * Authorization features + */ + authorizationChecker, + currentUserChecker }); // Run application to listen on given port diff --git a/src/loaders/typeormLoader.ts b/src/loaders/typeormLoader.ts index 43450704..5e3b7b51 100644 --- a/src/loaders/typeormLoader.ts +++ b/src/loaders/typeormLoader.ts @@ -1,7 +1,7 @@ +import * as path from 'path'; import { createConnection } from 'typeorm'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; -import { User } from '../api/models/User'; export const typeormLoader: MicroframeworkLoader = async (settings: MicroframeworkSettings | undefined) => { @@ -16,7 +16,7 @@ export const typeormLoader: MicroframeworkLoader = async (settings: Microframewo synchronize: env.db.synchronize, logging: env.db.logging, entities: [ - User + path.join(__dirname, '..', 'api/models/*{.js,.ts}') ] }); diff --git a/yarn.lock b/yarn.lock index 87dabafc..5280aa7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,6 +43,12 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/form-data@*": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" + dependencies: + "@types/node" "*" + "@types/helmet@^0.0.37": version "0.0.37" resolved "https://registry.yarnpkg.com/@types/helmet/-/helmet-0.0.37.tgz#6b150f30219c0d6563e8e0a3fbcad8dd90fa4f68" @@ -75,6 +81,13 @@ version "0.0.5" resolved "https://registry.yarnpkg.com/@types/reflect-metadata/-/reflect-metadata-0.0.5.tgz#9c042bfa9803d577aad4f57dfbca4b7cae4286fe" +"@types/request@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.0.8.tgz#424d3de255868107ed4dd6695c65c5f1766aba80" + dependencies: + "@types/form-data" "*" + "@types/node" "*" + "@types/serve-favicon@^2.2.29": version "2.2.30" resolved "https://registry.yarnpkg.com/@types/serve-favicon/-/serve-favicon-2.2.30.tgz#5bea4ead966a9ad5e0f409141edba0cebf20da73" @@ -981,6 +994,17 @@ cookie@0.3.1, cookie@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" +copyfiles@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" + dependencies: + glob "^7.0.5" + ltcdr "^2.2.1" + minimatch "^3.0.3" + mkdirp "^0.5.1" + noms "0.0.0" + through2 "^2.0.1" + core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -2884,6 +2908,10 @@ lru-cache@^4.0.0, lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" +ltcdr@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ltcdr/-/ltcdr-2.2.1.tgz#5ab87ad1d4c1dab8e8c08bbf037ee0c1902287cf" + make-dir@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" @@ -3134,6 +3162,13 @@ nodemon@^1.12.1: undefsafe "0.0.3" update-notifier "^2.2.0" +noms@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" + dependencies: + inherits "^2.0.1" + readable-stream "~1.0.31" + nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -3671,7 +3706,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4: +readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -3692,6 +3727,15 @@ readable-stream@^1.0.31: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@~1.0.31: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -3813,7 +3857,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.78.0, request@^2.79.0: +request@^2.78.0, request@^2.79.0, request@^2.83.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -4360,6 +4404,13 @@ throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" +through2@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + through@2, through@~2.3, through@~2.3.1, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4912,7 +4963,7 @@ xmlhttprequest-ssl@~1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz#04f560915724b389088715cc0ed7813e9677bf57" -xtend@^4.0.0, xtend@^4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From 06ab5a98b2b42b56e54bbb45c372ecd4cfd9c0ff Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 09:12:27 +0100 Subject: [PATCH 019/104] Add auth checkers --- src/api/models/User.ts | 4 +++ src/app.ts | 4 ++- src/auth/authorizationChecker.ts | 49 +++++++++++++++------------- src/auth/currentUserChecker.ts | 43 ++++++++++++------------- src/loaders/expressLoader.ts | 55 ++++++++++++++------------------ src/loaders/iocLoader.ts | 15 +++++++++ src/loaders/typeormLoader.ts | 1 + 7 files changed, 94 insertions(+), 77 deletions(-) create mode 100644 src/loaders/iocLoader.ts diff --git a/src/api/models/User.ts b/src/api/models/User.ts index 364a38bc..057a626e 100644 --- a/src/api/models/User.ts +++ b/src/api/models/User.ts @@ -20,4 +20,8 @@ export class User extends BaseEntity { @Column() public email: string; + public toString(): string { + return `${this.firstName} ${this.lastName} (${this.email})`; + } + } diff --git a/src/app.ts b/src/app.ts index ee342882..958ce623 100644 --- a/src/app.ts +++ b/src/app.ts @@ -22,13 +22,15 @@ import { swaggerLoader } from './loaders/swaggerLoader'; import { monitorLoader } from './loaders/monitorLoader'; import { homeLoader } from './loaders/homeLoader'; import { publicLoader } from './loaders/publicLoader'; +import { iocLoader } from './loaders/iocLoader'; bootstrapMicroframework({ loaders: [ winstonLoader, - expressLoader, + iocLoader, typeormLoader, + expressLoader, swaggerLoader, monitorLoader, homeLoader, diff --git a/src/auth/authorizationChecker.ts b/src/auth/authorizationChecker.ts index 0c739816..a2dfde7c 100644 --- a/src/auth/authorizationChecker.ts +++ b/src/auth/authorizationChecker.ts @@ -1,33 +1,36 @@ import { Action } from 'routing-controllers'; import { Container } from 'typedi'; +import { Connection } from 'typeorm'; import { AuthService } from './AuthService'; import { Log } from '../core/Log'; -const log = new Log(__filename); -const authService = Container.get(AuthService); +export function authorizationChecker(connection: Connection): (action: Action, roles: any[]) => Promise | boolean { + const log = new Log(__filename); + const authService = Container.get(AuthService); -export async function authorizationChecker(action: Action, roles: string[]): Promise { - // here you can use request/response objects from action - // also if decorator defines roles it needs to access the action - // you can use them to provide granular access check - // checker must return either boolean (true or false) - // either promise that resolves a boolean value - // demo code: - const token = authService.parseTokenFromRequest(action.request); + return async function innerAuthorizationChecker(action: Action, roles: string[]): Promise { + // here you can use request/response objects from action + // also if decorator defines roles it needs to access the action + // you can use them to provide granular access check + // checker must return either boolean (true or false) + // either promise that resolves a boolean value + // demo code: + const token = authService.parseTokenFromRequest(action.request); - if (token === null) { - log.warn('No token given'); - return false; // res.failed(403, 'You are not allowed to request this resource!'); - } + if (token === null) { + log.warn('No token given'); + return false; // res.failed(403, 'You are not allowed to request this resource!'); + } - // Request user info at auth0 with the provided token - try { - action.request.tokeninfo = await authService.getTokenInfo(token); - log.info('Successfully checked token'); - return true; - } catch (e) { - log.warn(e); - return false; - } + // Request user info at auth0 with the provided token + try { + action.request.tokeninfo = await authService.getTokenInfo(token); + log.info('Successfully checked token'); + return true; + } catch (e) { + log.warn(e); + return false; + } + }; } diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts index 0ef112c0..91814a71 100644 --- a/src/auth/currentUserChecker.ts +++ b/src/auth/currentUserChecker.ts @@ -2,31 +2,30 @@ import { Action } from 'routing-controllers'; import { User } from '../api/models/User'; import { Log } from '../core/Log'; import { ITokenInfo } from './ITokenInfo'; -// import { UserRepository } from '../api/repositories/UserRepository'; -// import { getConnection } from 'typeorm'; +import { Connection } from 'typeorm'; -const log = new Log(__filename); +export function currentUserChecker(connection: Connection): (action: Action) => Promise { + const log = new Log(__filename); -export async function currentUserChecker(action: Action): Promise { - // here you can use request/response objects from action - // you need to provide a user object that will be injected in controller actions - // demo code: - const tokeninfo: ITokenInfo = action.request.tokeninfo; - log.info('todo user checker', tokeninfo); + return async function innerCurrentUserChecker(action: Action): Promise { + // here you can use request/response objects from action + // you need to provide a user object that will be injected in controller actions + // demo code: + const tokeninfo: ITokenInfo = action.request.tokeninfo; + const em = connection.createEntityManager(); + const user = await em.findOne(User, { + where: { + email: tokeninfo.user_id + } + }); + if (user) { + log.info('Current user is ', user.toString()); + } else { + log.info('Current user is undefined'); + } - // const connection = getConnection(); - - // const userRepository = connection.getRepository(UserRepository); - // console.log(connection); - // const user = await userRepository. - // findOne({ - // where: { - // email: tokeninfo.user_id - // } - // }); - // console.log(user); - - return Promise.resolve(new User()); + return Promise.resolve(user); + }; } diff --git a/src/loaders/expressLoader.ts b/src/loaders/expressLoader.ts index 815de90d..c646ed0d 100644 --- a/src/loaders/expressLoader.ts +++ b/src/loaders/expressLoader.ts @@ -1,7 +1,5 @@ import * as path from 'path'; -import { Container } from 'typedi'; -import { useContainer as ormUseContainer } from 'typeorm'; -import { useContainer as routingUseContainer, createExpressServer } from 'routing-controllers'; +import { createExpressServer } from 'routing-controllers'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; import { authorizationChecker } from '../auth/authorizationChecker'; @@ -9,40 +7,35 @@ import { currentUserChecker } from '../auth/currentUserChecker'; export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + if (settings) { + const connection = settings.getData('connection'); - /** - * Setup routing-controllers to use typedi container. - */ - routingUseContainer(Container); - ormUseContainer(Container); - - /** - * We create a new express server instance. - * We could have also use useExpressServer here to attach controllers to an existing express instance. - */ - const expressApp = createExpressServer({ - cors: true, - routePrefix: env.app.routePrefix, /** - * We can add options about how routing-controllers should configure itself. - * Here we specify what controllers should be registered in our express server. + * We create a new express server instance. + * We could have also use useExpressServer here to attach controllers to an existing express instance. */ - controllers: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')], - middlewares: [path.join(__dirname, '..', 'api/middlewares/*{.js,.ts}')], - interceptors: [path.join(__dirname, '..', 'api/interceptors/*{.js,.ts}')], + const expressApp = createExpressServer({ + cors: true, + routePrefix: env.app.routePrefix, + /** + * TODO: We can add options about how routing-controllers should configure itself. + * Here we specify what controllers should be registered in our express server. + */ + controllers: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')], + middlewares: [path.join(__dirname, '..', 'api/middlewares/*{.js,.ts}')], + interceptors: [path.join(__dirname, '..', 'api/interceptors/*{.js,.ts}')], - /** - * Authorization features - */ - authorizationChecker, - currentUserChecker - }); + /** + * Authorization features + */ + authorizationChecker: authorizationChecker(connection), + currentUserChecker: currentUserChecker(connection) + }); - // Run application to listen on given port - expressApp.listen(env.app.port); + // Run application to listen on given port + expressApp.listen(env.app.port); - // Here we can set the data for other loaders - if (settings) { + // Here we can set the data for other loaders settings.setData('express_app', expressApp); } }; diff --git a/src/loaders/iocLoader.ts b/src/loaders/iocLoader.ts new file mode 100644 index 00000000..c1d44ca0 --- /dev/null +++ b/src/loaders/iocLoader.ts @@ -0,0 +1,15 @@ +import { Container } from 'typedi'; +import { useContainer as ormUseContainer } from 'typeorm'; +import { useContainer as routingUseContainer } from 'routing-controllers'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; + + +export const iocLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + + /** + * Setup routing-controllers to use typedi container. + */ + routingUseContainer(Container); + ormUseContainer(Container); + +}; diff --git a/src/loaders/typeormLoader.ts b/src/loaders/typeormLoader.ts index 5e3b7b51..70746ba6 100644 --- a/src/loaders/typeormLoader.ts +++ b/src/loaders/typeormLoader.ts @@ -21,6 +21,7 @@ export const typeormLoader: MicroframeworkLoader = async (settings: Microframewo }); if (settings) { + settings.setData('connection', connection); settings.onShutdown(() => connection.close()); } }; From 5a1e446fb78db3d9bed9d0096c3d00866ef48199 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 19 Nov 2017 14:52:16 +0100 Subject: [PATCH 020/104] Fix package.json and package-scripts and make them more readable --- package-scripts.js | 55 ++++++++++++++++++++++++++++++++++------------ package.json | 9 +++----- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index fb13abcf..5f92efdd 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -2,6 +2,9 @@ const { series, crossEnv, concurrent, rimraf, runInNewWindow } = require('nps-ut module.exports = { scripts: { + default: { + script: 'nps start' + }, /** * Starts the builded app from the dist directory */ @@ -14,7 +17,7 @@ module.exports = { serve: { script: series( 'nps banner.serve', - '\"./node_modules/.bin/nodemon\" --watch src --watch .env' + '\"./node_modules/.bin/nodemon\" --watch src --watch .env', ) }, /** @@ -24,7 +27,7 @@ module.exports = { script: series( 'yarn install', 'nps db.migrate', - 'nps db.seed' + 'nps db.seed', ) }, /** @@ -36,7 +39,7 @@ module.exports = { 'nps lint', 'nps clean.dist', 'nps transpile', - 'nps copy' + 'nps copy', ) }, /** @@ -49,7 +52,7 @@ module.exports = { script: series( 'nps banner.test', 'nps test.unit.pretest', - 'nps test.unit.run' + 'nps test.unit.run', ) }, pretest: { @@ -71,7 +74,7 @@ module.exports = { 'nps banner.test', 'nps test.e2e.pretest', runInNewWindow(series('nps build', 'nps start')), - 'nps test.e2e.run' + 'nps test.e2e.run', ) }, pretest: { @@ -82,7 +85,7 @@ module.exports = { }, run: series( `wait-on --timeout 120000 http-get://localhost:3000/api/info`, - './node_modules/.bin/cross-env NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=e2e -i' + './node_modules/.bin/cross-env NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=e2e -i', ), } }, @@ -103,7 +106,10 @@ module.exports = { */ clean: { default: { - script: series(`nps banner.clean`, `nps clean.dist`) + script: series( + `nps banner.clean`, + `nps clean.dist`, + ) }, dist: { script: `./node_modules/.bin/trash './dist'` @@ -114,13 +120,22 @@ module.exports = { */ copy: { default: { - script: `nps copy.swagger && nps copy.public` + script: series( + `nps copy.swagger`, + `nps copy.public`, + ) }, swagger: { - script: copy('./src/api/swagger.json', './dist') + script: copy( + './src/api/swagger.json', + './dist', + ) }, public: { - script: copy('./src/public/*', './dist') + script: copy( + './src/public/*', + './dist', + ) } }, /** @@ -140,15 +155,27 @@ module.exports = { db: { migrate: { default: { - script: series('nps banner.migrate', '\"./node_modules/.bin/knex\" migrate:latest') + script: series( + 'nps banner.migrate', + '\"./node_modules/.bin/knex\" migrate:latest', + ) }, - rollback: series('nps banner.rollback', '\"./node_modules/.bin/knex\" migrate:rollback') + rollback: series( + 'nps banner.rollback', + '\"./node_modules/.bin/knex\" migrate:rollback', + ) }, seed: { - script: series('nps banner.seed', '\"./node_modules/.bin/knex\" seed:run') + script: series( + 'nps banner.seed', + '\"./node_modules/.bin/knex\" seed:run', + ) }, reset: { - script: series('nps banner.dbReset', 'nps console db:reset') + script: series( + 'nps banner.dbReset', + 'nps console db:reset', + ) } }, /** diff --git a/package.json b/package.json index 80a6e606..e10174e2 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,10 @@ "version": "3.0.0-alpha", "description": "A delightful way to building a RESTful API with NodeJs & TypeScript", "main": "src/app.ts", - "engines": { - "node": "8.2.1" - }, "scripts": { - "start": "node dist/app.js", - "test": "nps test", - "build": "nps build", + "start": "nps", + "test": "npm start test", + "build": "npm start build", "ts-node": "./node_modules/.bin/ts-node", "ts-node:fast": "./node_modules/.bin/ts-node -F", "console": "npm run ts-node:fast -- ./src/console/lib/console.ts", From dd412a2611640f45ba9bc468344e7e99cff4bd48 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 15:42:32 +0100 Subject: [PATCH 021/104] Add banner --- .env.example | 1 + src/app.ts | 9 ++++++--- src/core/banner.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/core/banner.ts diff --git a/.env.example b/.env.example index a0786823..5d93ef85 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ APP_NAME="express-typescript-boilerplate" APP_ROUTE="http://localhost:3000" APP_ROUTE_PREFIX="/api" +APP_BANNER=true # # LOGGING diff --git a/src/app.ts b/src/app.ts index 958ce623..0b6ef6dc 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,12 +5,12 @@ * This is a boilerplate for Node.js Application written in TypeScript. * The basic layer of this app is express. For further information visit * the 'README.md' file. - * */ import 'reflect-metadata'; import * as dotenv from 'dotenv'; dotenv.config(); +import { banner } from './core/banner'; import { Log } from './core/Log'; const log = new Log(__filename); @@ -26,6 +26,10 @@ import { iocLoader } from './loaders/iocLoader'; bootstrapMicroframework({ + /** + * Loader is a place where you can configure all your modules during microframework + * bootstrap. All loaders are executed one by one in a sequential order. + */ loaders: [ winstonLoader, iocLoader, @@ -35,8 +39,7 @@ bootstrapMicroframework({ monitorLoader, homeLoader, publicLoader - // here we can setup other databases, any other lib we want to setup in our application ] }) - .then(() => log.info('Application is up and running.')) + .then(() => banner(log)) .catch(error => log.error('Application is crashed: ' + error)); diff --git a/src/core/banner.ts b/src/core/banner.ts new file mode 100644 index 00000000..ddf00453 --- /dev/null +++ b/src/core/banner.ts @@ -0,0 +1,27 @@ +import { Log } from './Log'; +import { env } from './env'; + + +export function banner(log: Log): void { + if (env.app.banner) { + log.info(``); + log.info(`Aloha, your app is ready on ${env.app.route}${env.app.routePrefix}`); + log.info(`To shut it down, press + C at any time.`); + log.info(``); + log.info('-------------------------------------------------------'); + log.info(`Environment : ${env.node}`); + log.info(`Version : ${env.app.version}`); + log.info(``); + log.info(`API Info : ${env.app.route}${env.app.routePrefix}`); + if (env.swagger.enabled) { + log.info(`Swagger : ${env.app.route}${env.swagger.route}`); + } + if (env.monitor.enabled) { + log.info(`Monitor : ${env.app.route}${env.monitor.route}`); + } + log.info('-------------------------------------------------------'); + log.info(''); + } else { + log.info(`Application is up and running.`); + } +} From 1282a94fefea60c81b747ea6c4ff25eaaa71396c Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 15:42:58 +0100 Subject: [PATCH 022/104] Starts with documentation --- README.new.md | 197 ++++++++++++++++++++++++++++++++++ README.md => README.old.md | 0 src/api/interceptors/.gitkeep | 0 src/api/validators/.gitkeep | 0 src/auth/AuthService.ts | 3 +- src/constants/.gitkeep | 0 src/core/Log.ts | 5 - src/core/env.ts | 3 +- src/loaders/expressLoader.ts | 9 +- src/loaders/homeLoader.ts | 3 +- src/types/interfaces.d.ts | 24 +---- src/types/my-express.d.ts | 47 -------- src/types/resources/user.d.ts | 14 --- 13 files changed, 209 insertions(+), 96 deletions(-) create mode 100644 README.new.md rename README.md => README.old.md (100%) create mode 100644 src/api/interceptors/.gitkeep create mode 100644 src/api/validators/.gitkeep create mode 100644 src/constants/.gitkeep delete mode 100644 src/types/my-express.d.ts delete mode 100644 src/types/resources/user.d.ts diff --git a/README.new.md b/README.new.md new file mode 100644 index 00000000..1cac9b21 --- /dev/null +++ b/README.new.md @@ -0,0 +1,197 @@ +# 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 with NodeJs & TypeScript. +> An Node.js Web-Serice boilerplate/skeleton/starter-kit featuring +[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/), +by [w3tech](https://github.com/w3tecch) + +## 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 + +- **Beautiful Code** thanks to the awesome annotations of the libraries from [pleerock](https://github.com/pleerock). +- **Easy API Testing** with included e2e testing. +- **Dependency Injection** done with the nice framework from [TypeDI](https://github.com/pleerock/typedi). +- **Simplified Database Query** with the ORM [TypeOrm](https://github.com/typeorm/typeorm). +- **Clear Structure** with different layers such as controllers, services, repositories, models, middlewares... +- **Easy Exception Handling** thanks to [routing-controllers](https://github.com/pleerock/routing-controllers). +- **Smart Validation** thanks to [class-validator](https://github.com/pleerock/class-validator) with some nice annotations. +- **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes) +- **API Documentation** thanks to [swagger](http://swagger.io/). +- **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor). +- **Integrated Testing Tool** thanks to [Wallaby.js](https://wallabyjs.com/) +- **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/) + +### Comming soon + +- **Fast Database Building** with simple migration and seeding from [Knex](http://knexjs.org/). +- **Easy Data Seeding** with our own factories. +- **Custom Commands** are also available in our setup and really easy to use or even extend. +- **Scaffolding Commands** will speed up your development tremendously as you should focus on business code and not scaffolding. + +## Getting Started + +### Step 1: Set up the Development Environment + +You need to set up your development environment before you can do anything. + +Install [Node.js and NPM](https://nodejs.org/en/download/) + +- on OSX use [homebrew](http://brew.sh) `brew install node` +- on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs` + +Install yarn globally + +```bash +npm install yarn -g +``` + +Install a MySQL database. + +> If you work with a mac, we recommend to use homebrew for the installation. + +### Step 2: Create new Project + +Fork or download this project. Configure your package.json for your new project. + +Then copy the `.env.example` file and rename it to `.env`. In this file you have to add your database connection information. + +Create a new database with the name you have in your `.env`-file. + +Then setup your application environment. + +```bash +nps setup +``` + +> This installs all dependencies with yarn. After that it migrates the database and seeds some test data into it. So after that your development environment is ready to use. + +### Step 3: Serve your App + +Go to the project dir and start your app with this npm script. + +```bash +nps 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 / Tasks + +All script are defined in the package.json file, but the most important ones are listed here. + +### Install + +- Install all dependencies with `yarn install` + +### Linting + +- Run code quality analysis using `nps lint`. This runs tslint. +- There is also a vscode task for this called `lint`. + +### Tests + +- Run the unit tests using `nps test` (There is also a vscode task for this called `test`). +- Run the e2e tests using `nps test:e2e` and don't forget to start your application and your [Auth0 Mock Server](https://github.com/hirsch88/auth0-mock-server). + +### Running in dev mode + +- Run `nps serve` to start nodemon with ts-node, to serve the app. +- The server address will be displayed to you as `http://0.0.0.0:3000` + +### Building the project and run it + +- Run `nps build` to generated all JavaScript files from the TypeScript sources (There is also a vscode task for this called `build`). +- To start the builded app located in `dist` use `npm start`. + +## Using the debugger in VS Code + +Just set a breakpoint and hit `F5` in your Visual Studio Code. + +## 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. + +| Route | Description | +| ------------ | ----------- | +| **/api** | Shows us the name, description and the version of the package.json | +| **/swagger** | This is the Swagger UI with our API documentation | +| **/monitor** | Shows a small monitor page for the server | + +## Project Structure + +| Name | Description | +| ----------------------------- | ----------- | +| **.vscode/** | VSCode tasks, launch configuration and some other settings | +| **dist/** | Compiled source files will be placed here | +| **src/** | Source files | +| **src/api/controllers/** | REST API Controllers | +| **src/api/errors/** | Custom HttpErrors like 404 NotFound | +| **src/api/interceptors/** | Interceptors are used to change or replace the data returned to the client. | +| **src/api/middlewares/** | Express Middlewares like helmet security features | +| **src/api/models/** | Bookshelf Models | +| **src/api/repositories/** | Repository / DB layer | +| **src/api/services/** | Service layer | +| **src/api/validators/** | Custom validators, which can be used in the request classes | +| **src/api/** swagger.json | Swagger documentation | +| **src/console/** | Command line scripts | +| **src/constants/** | Global Constants | +| **src/core/** | The core features like logger and env variables | +| **src/public/** | Static assets (fonts, css, js, img). | +| **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | +| **test** | Tests | +| **test/e2e/** *.test.ts | End-2-End tests (like e2e) | +| **test/unit/** *.test.ts | Unit tests | +| .env.example | Environment configurations | + +## 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 + +## Documentations of our main dependencies + +| Name & Link | Description | +| --------------------------------- | --------------------------------- | +| [Express](https://expressjs.com/) | Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. | +| [Microframework](https://github.com/pleerock/microframework) | Microframework is a simple tool that allows you to execute your modules in a proper order, helping you to organize bootstrap code in your application. | +| [TypeDI](https://github.com/pleerock/typedi) | Dependency Injection for TypeScript. | +| [routing-controllers](https://github.com/pleerock/routing-controllers) | Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage in Express / Koa using TypeScript and Routing Controllers Framework. | +| [TypeORM](http://typeorm.io/#/) | TypeORM is highly influenced by other ORMs, such as Hibernate, Doctrine and Entity Framework. | +| [class-validator](https://github.com/pleerock/class-validator) | Validation made easy using TypeScript decorators. | +| [class-transformer](https://github.com/pleerock/class-transformer) | Proper decorator-based transformation / serialization / deserialization of plain javascript objects to class constructors | +| [event-dispatcher](https://github.com/pleerock/event-dispatch) | Dispatching and listening for application events in Typescript | +| [Helmet](https://helmetjs.github.io/) | Helmet helps you secure your Express apps by setting various HTTP headers. It’s not a silver bullet, but it can help! | +| [Auth0 API Documentation](https://auth0.com/docs/api/management/v2) | Authentification service | +| [Jest](http://facebook.github.io/jest/) | Delightful JavaScript Testing Library for unit and e2e tests | +| [swagger Documentation](http://swagger.io/) | API Tool to describe and document your api. | + +## License + +[MIT](/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 diff --git a/README.md b/README.old.md similarity index 100% rename from README.md rename to README.old.md diff --git a/src/api/interceptors/.gitkeep b/src/api/interceptors/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/api/validators/.gitkeep b/src/api/validators/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index 9f8648b1..2c304c85 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -1,4 +1,5 @@ import * as request from 'request'; +import * as express from 'express'; import { Service } from 'typedi'; import { env } from '../core/env'; import { ITokenInfo } from './ITokenInfo'; @@ -7,7 +8,7 @@ import { ITokenInfo } from './ITokenInfo'; @Service() export class AuthService { - public parseTokenFromRequest(req: myExpress.Request): string | null { + public parseTokenFromRequest(req: express.Request): string | null { const authorization = req.header('authorization'); // Retrieve the token form the Authorization header diff --git a/src/constants/.gitkeep b/src/constants/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/core/Log.ts b/src/core/Log.ts index 5761c808..dbb662e9 100644 --- a/src/core/Log.ts +++ b/src/core/Log.ts @@ -29,16 +29,11 @@ export class Log { } private scope: string; - private adapter: interfaces.LoggerAdapter; constructor(scope?: string) { this.scope = Log.parsePathToScope((scope) ? scope : Log.DEFAULT_SCOPE); } - public getAdapter(): interfaces.LoggerAdapter { - return this.adapter; - } - public debug(message: string, ...args: any[]): void { this.log('debug', message, args); } diff --git a/src/core/env.ts b/src/core/env.ts index 0b20b8bf..9403c9ed 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -11,7 +11,8 @@ export const env = { description: pkg.description, route: getOsEnv('APP_ROUTE'), routePrefix: getOsEnv('APP_ROUTE_PREFIX'), - port: normalizePort(process.env.PORT || '3000') + port: normalizePort(process.env.PORT || '3000'), + banner: toBool(getOsEnv('APP_BANNER')) }, log: { level: getOsEnv('LOG_LEVEL'), diff --git a/src/loaders/expressLoader.ts b/src/loaders/expressLoader.ts index c646ed0d..6b9e2341 100644 --- a/src/loaders/expressLoader.ts +++ b/src/loaders/expressLoader.ts @@ -16,14 +16,15 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett */ const expressApp = createExpressServer({ cors: true, + classTransformer: true, routePrefix: env.app.routePrefix, /** - * TODO: We can add options about how routing-controllers should configure itself. + * We can add options about how routing-controllers should configure itself. * Here we specify what controllers should be registered in our express server. */ - controllers: [path.join(__dirname, '..', 'api/controllers/*{.js,.ts}')], - middlewares: [path.join(__dirname, '..', 'api/middlewares/*{.js,.ts}')], - interceptors: [path.join(__dirname, '..', 'api/interceptors/*{.js,.ts}')], + controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')], + middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')], + interceptors: [path.join(__dirname, '..', 'api/**/*Interceptor{.js,.ts}')], /** * Authorization features diff --git a/src/loaders/homeLoader.ts b/src/loaders/homeLoader.ts index b54ab0d9..ed265066 100644 --- a/src/loaders/homeLoader.ts +++ b/src/loaders/homeLoader.ts @@ -1,3 +1,4 @@ +import * as express from 'express'; import * as monitor from 'express-status-monitor'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; @@ -9,7 +10,7 @@ export const homeLoader: MicroframeworkLoader = (settings: MicroframeworkSetting expressApp.use(monitor()); expressApp.get( env.app.routePrefix, - (req: myExpress.Request, res: myExpress.Response) => { + (req: express.Request, res: express.Response) => { return res.json({ name: env.app.name, version: env.app.version, diff --git a/src/types/interfaces.d.ts b/src/types/interfaces.d.ts index 3546d301..6dc46619 100644 --- a/src/types/interfaces.d.ts +++ b/src/types/interfaces.d.ts @@ -1,28 +1,6 @@ declare namespace interfaces { - interface Middleware { - use(req: myExpress.Request, res: myExpress.Response, next: myExpress.NextFunction): void; - } - - interface Listener { - act(value?: T): void; - act(...args: any[]): void; - } - - interface Configurable { - configure(instance: T): void; - } - - interface LoggerAdapter { - debug(message: string, ...args: any[]): void; - info(message: string, ...args: any[]): void; - warn(message: string, ...args: any[]): void; - error(message: string, ...args: any[]): void; - } - - interface LoggerAdapterConstructor { - new (scope: string): LoggerAdapter; - } + // Here you can declare global interfaces } diff --git a/src/types/my-express.d.ts b/src/types/my-express.d.ts deleted file mode 100644 index 41bce4ba..00000000 --- a/src/types/my-express.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * my-express - * ---------------------------------------- - * - * This type definitions is used to extend the express interfaces, so - * we can add new values and functions to those objects. - */ - -import * as expressLib from 'express'; -import * as resources from 'resources'; - -declare namespace myExpress { - - interface Application extends expressLib.Application { - } - - interface NextFunction extends expressLib.NextFunction { - } - - interface Request extends expressLib.Request { - tokeninfo: auth0.User; - user: resources.User; - } - - interface Response extends expressLib.Response { - ok(data: T, options?: ResponseOptions): void; - created(data: T, options?: ResponseOptions): void; - found(data: T, options?: ResponseOptions): void; - updated(data: T, options?: ResponseOptions): void; - destroyed(options?: ResponseOptions): void; - failed(status: number, message: string, error?: any): void; - unavailable(): void; - } - - interface ResponseOptions { - message?: string; - links?: ResponseLinks[]; - } - - interface ResponseLinks { - name: string; - url: string; - } -} - -export as namespace myExpress; -export = myExpress; diff --git a/src/types/resources/user.d.ts b/src/types/resources/user.d.ts deleted file mode 100644 index 28960c87..00000000 --- a/src/types/resources/user.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare module 'resources' { - - interface User { - id: string; - firstName: string; - lastName: string; - email: string; - auth0UserId: string; - picture: string; - createdAt: Date; - updatedAt: Date; - } - -} From 0b24037b227bada41354a1982f5dee3a09371ec8 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 15:43:14 +0100 Subject: [PATCH 023/104] Renames readme --- README.new.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.new.md => README.md (100%) diff --git a/README.new.md b/README.md similarity index 100% rename from README.new.md rename to README.md From cd7981c6ffda2f670722b26c7d225b6d16718545 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 16:11:33 +0100 Subject: [PATCH 024/104] Remove old commands --- package.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/package.json b/package.json index e10174e2..4907d895 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,7 @@ "scripts": { "start": "nps", "test": "npm start test", - "build": "npm start build", - "ts-node": "./node_modules/.bin/ts-node", - "ts-node:fast": "./node_modules/.bin/ts-node -F", - "console": "npm run ts-node:fast -- ./src/console/lib/console.ts", - "console:dev": "npm run ts-node -- ./src/console/lib/console.ts", - "console:help": "npm run ts-node:fast -- ./src/console/lib/console.ts --help" + "build": "npm start build" }, "repository": "git+ssh://git@github.com/w3tec/express-typescript-boilerplate.git", "keywords": [ From 9475dfd0155dfc2981deaf1efd60ea356f7b12ca Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 16:17:51 +0100 Subject: [PATCH 025/104] Format tslint rules --- tslint.json | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/tslint.json b/tslint.json index eee75561..7ecaba91 100644 --- a/tslint.json +++ b/tslint.json @@ -8,7 +8,11 @@ "no-unnecessary-initializer": false, "no-var-requires": false, "no-consecutive-blank-lines": false, - "quotemark": [true, "single", "avoid-escape"], + "quotemark": [ + true, + "single", + "avoid-escape" + ], "interface-name": false, "no-empty-interface": false, "no-namespace": false, @@ -46,12 +50,22 @@ "timeEnd", "trace" ], - "no-inferrable-types": [true, "ignore-params"], + "no-inferrable-types": [ + true, + "ignore-params" + ], "no-switch-case-fall-through": true, - "typedef": [true, "call-signature", "parameter"], - "trailing-comma": [true, { - "multiline": "never", - "singleline": "never" - }] + "typedef": [ + true, + "call-signature", + "parameter" + ], + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ] } } From 0bfa3659531c5c0d07b0c9d695df76f976384b3f Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 16:41:31 +0100 Subject: [PATCH 026/104] Add basic migration --- ormconfig.json | 17 +++++++++++++++++ .../1511105183653-CreateUserTable.ts | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 ormconfig.json create mode 100644 src/database/migrations/1511105183653-CreateUserTable.ts diff --git a/ormconfig.json b/ormconfig.json new file mode 100644 index 00000000..da2fff0a --- /dev/null +++ b/ormconfig.json @@ -0,0 +1,17 @@ +{ + "type": "mysql", + "host": "localhost", + "port": 3306, + "username": "root", + "password": "", + "database": "my_database", + "entities": [ + "dist/api/**/*Models.js" + ], + "migrations": [ + "dist/database/migrations/*.js" + ], + "cli": { + "migrationsDir": "dist/database/migrations" + } +} diff --git a/src/database/migrations/1511105183653-CreateUserTable.ts b/src/database/migrations/1511105183653-CreateUserTable.ts new file mode 100644 index 00000000..f53eed33 --- /dev/null +++ b/src/database/migrations/1511105183653-CreateUserTable.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateUserTable1511105183653 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE \`user\` ( + \`id\` varchar(255) NOT NULL PRIMARY KEY, + \`first_name\` varchar(255) NOT NULL, + \`last_name\` varchar(255) NOT NULL, + \`email\` varchar(255) NOT NULL) ENGINE=InnoDB` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE \`user\``); + } + +} From 14ff48edd2f79cdae81b433b1876cfcc17a9d190 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 19 Nov 2017 16:42:46 +0100 Subject: [PATCH 027/104] Some feedback --- src/api/controllers/UserController.ts | 6 +++--- src/api/middlewares/CompressionMiddleware.ts | 4 ++-- src/api/middlewares/LogMiddleware.ts | 2 +- src/api/middlewares/SecurityHstsMiddleware.ts | 4 ++-- src/api/middlewares/SecurityMiddleware.ts | 4 ++-- src/api/middlewares/SecurityNoCacheMiddleware.ts | 4 ++-- src/api/models/User.ts | 4 ++-- src/api/repositories/UserRepository.ts | 4 ---- src/api/services/UserService.ts | 5 +++-- src/auth/AuthService.ts | 4 ++-- src/auth/authorizationChecker.ts | 2 +- src/auth/currentUserChecker.ts | 2 +- src/core/env.ts | 8 ++++---- src/loaders/homeLoader.ts | 2 -- src/loaders/monitorLoader.ts | 4 ++-- src/loaders/swaggerLoader.ts | 4 ++-- src/loaders/typeormLoader.ts | 4 ++-- tslint.json | 3 ++- 18 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/api/controllers/UserController.ts b/src/api/controllers/UserController.ts index ac2c0933..35d3f548 100644 --- a/src/api/controllers/UserController.ts +++ b/src/api/controllers/UserController.ts @@ -1,5 +1,4 @@ import { JsonController, Get, Post, Put, Param, Delete, Body, OnUndefined, Authorized, CurrentUser } from 'routing-controllers'; -import { Inject } from 'typedi'; import { UserService } from '../services/UserService'; import { User } from '../models/User'; import { UserNotFoundError } from '../errors/UserNotFoundError'; @@ -9,8 +8,9 @@ import { UserNotFoundError } from '../errors/UserNotFoundError'; @JsonController('/users') export class UserController { - @Inject() - private userService: UserService; + constructor( + private userService: UserService + ) { } @Get() public find( @CurrentUser() user?: User): Promise { diff --git a/src/api/middlewares/CompressionMiddleware.ts b/src/api/middlewares/CompressionMiddleware.ts index 4a8e99da..0a37e9d1 100644 --- a/src/api/middlewares/CompressionMiddleware.ts +++ b/src/api/middlewares/CompressionMiddleware.ts @@ -4,10 +4,10 @@ import * as compression from 'compression'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'before' }) +@Middleware({ type: 'after' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { - public use(req: any, res: any, next: express.NextFunction): any { + public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return compression()(req, res, next); } diff --git a/src/api/middlewares/LogMiddleware.ts b/src/api/middlewares/LogMiddleware.ts index fb1b5781..73fb5e1f 100644 --- a/src/api/middlewares/LogMiddleware.ts +++ b/src/api/middlewares/LogMiddleware.ts @@ -11,7 +11,7 @@ export class SecurityMiddleware implements ExpressMiddlewareInterface { private log = new Log(__dirname); - public use(req: any, res: any, next: express.NextFunction): any { + public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return morgan(env.log.output, { stream: { write: this.log.info.bind(this.log) diff --git a/src/api/middlewares/SecurityHstsMiddleware.ts b/src/api/middlewares/SecurityHstsMiddleware.ts index fcc7ba47..2d827b9f 100644 --- a/src/api/middlewares/SecurityHstsMiddleware.ts +++ b/src/api/middlewares/SecurityHstsMiddleware.ts @@ -4,10 +4,10 @@ import * as helmet from 'helmet'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'before' }) +@Middleware({ type: 'after' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { - public use(req: any, res: any, next: express.NextFunction): any { + public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return helmet.hsts({ maxAge: 31536000, includeSubdomains: true diff --git a/src/api/middlewares/SecurityMiddleware.ts b/src/api/middlewares/SecurityMiddleware.ts index 29bb8229..b92dd13a 100644 --- a/src/api/middlewares/SecurityMiddleware.ts +++ b/src/api/middlewares/SecurityMiddleware.ts @@ -4,10 +4,10 @@ import * as helmet from 'helmet'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'before' }) +@Middleware({ type: 'after' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { - public use(req: any, res: any, next: express.NextFunction): any { + public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return helmet()(req, res, next); } diff --git a/src/api/middlewares/SecurityNoCacheMiddleware.ts b/src/api/middlewares/SecurityNoCacheMiddleware.ts index 16eea31a..5915da50 100644 --- a/src/api/middlewares/SecurityNoCacheMiddleware.ts +++ b/src/api/middlewares/SecurityNoCacheMiddleware.ts @@ -4,10 +4,10 @@ import * as helmet from 'helmet'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'before' }) +@Middleware({ type: 'after' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { - public use(req: any, res: any, next: express.NextFunction): any { + public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return helmet.noCache()(req, res, next); } diff --git a/src/api/models/User.ts b/src/api/models/User.ts index 057a626e..37cfebda 100644 --- a/src/api/models/User.ts +++ b/src/api/models/User.ts @@ -1,9 +1,9 @@ -import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { IsNotEmpty } from 'class-validator'; @Entity() -export class User extends BaseEntity { +export class User { @PrimaryGeneratedColumn('uuid') public id: string; diff --git a/src/api/repositories/UserRepository.ts b/src/api/repositories/UserRepository.ts index 1692c909..7a55ee26 100644 --- a/src/api/repositories/UserRepository.ts +++ b/src/api/repositories/UserRepository.ts @@ -1,11 +1,7 @@ -import { Service } from 'typedi'; import { Repository, EntityRepository } from 'typeorm'; import { User } from '../models/User'; - -@Service() @EntityRepository(User) export class UserRepository extends Repository { - } diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index b0b346dd..22b21504 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -7,8 +7,9 @@ import { User } from '../models/User'; @Service() export class UserService { - @OrmRepository() - private userRepository: UserRepository; + constructor( + @OrmRepository() private userRepository: UserRepository + ) { } public find(): Promise { return this.userRepository.find(); diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index 9f8648b1..cf06c180 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -7,7 +7,7 @@ import { ITokenInfo } from './ITokenInfo'; @Service() export class AuthService { - public parseTokenFromRequest(req: myExpress.Request): string | null { + public parseTokenFromRequest(req: myExpress.Request): string | undefined { const authorization = req.header('authorization'); // Retrieve the token form the Authorization header @@ -16,7 +16,7 @@ export class AuthService { } // No token was provided by the client - return null; + return; } public getTokenInfo(token: string): Promise { diff --git a/src/auth/authorizationChecker.ts b/src/auth/authorizationChecker.ts index a2dfde7c..bb850ef4 100644 --- a/src/auth/authorizationChecker.ts +++ b/src/auth/authorizationChecker.ts @@ -18,7 +18,7 @@ export function authorizationChecker(connection: Connection): (action: Action, r // demo code: const token = authService.parseTokenFromRequest(action.request); - if (token === null) { + if (token === undefined) { log.warn('No token given'); return false; // res.failed(403, 'You are not allowed to request this resource!'); } diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts index 91814a71..43b060ce 100644 --- a/src/auth/currentUserChecker.ts +++ b/src/auth/currentUserChecker.ts @@ -25,7 +25,7 @@ export function currentUserChecker(connection: Connection): (action: Action) => log.info('Current user is undefined'); } - return Promise.resolve(user); + return user; }; } diff --git a/src/core/env.ts b/src/core/env.ts index 0b20b8bf..0b9288ae 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -1,4 +1,4 @@ -const pkg = require('../../package.json'); +import * as pkg from '../../package.json'; /** * Environment variables @@ -7,8 +7,8 @@ export const env = { node: process.env.NODE_ENV || 'development', app: { name: getOsEnv('APP_NAME'), - version: pkg.version, - description: pkg.description, + version: (pkg as any).version, + description: (pkg as any).description, route: getOsEnv('APP_ROUTE'), routePrefix: getOsEnv('APP_ROUTE_PREFIX'), port: normalizePort(process.env.PORT || '3000') @@ -47,7 +47,7 @@ export const env = { }; function getOsEnv(key: string): string { - return `${process.env[key]}`; + return process.env[key] as string; } function toNumber(value: string): number { diff --git a/src/loaders/homeLoader.ts b/src/loaders/homeLoader.ts index b54ab0d9..f902e63c 100644 --- a/src/loaders/homeLoader.ts +++ b/src/loaders/homeLoader.ts @@ -1,4 +1,3 @@ -import * as monitor from 'express-status-monitor'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; @@ -6,7 +5,6 @@ import { env } from '../core/env'; export const homeLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { if (settings) { const expressApp = settings.getData('express_app'); - expressApp.use(monitor()); expressApp.get( env.app.routePrefix, (req: myExpress.Request, res: myExpress.Response) => { diff --git a/src/loaders/monitorLoader.ts b/src/loaders/monitorLoader.ts index 354be5b6..94507afd 100644 --- a/src/loaders/monitorLoader.ts +++ b/src/loaders/monitorLoader.ts @@ -11,12 +11,12 @@ export const monitorLoader: MicroframeworkLoader = (settings: MicroframeworkSett expressApp.use(monitor()); expressApp.get( env.monitor.route, - basicAuth({ + env.monitor.username ? basicAuth({ users: { [`${env.monitor.username}`]: env.monitor.password }, challenge: true - }), + }) : (req, res, next) => next(), monitor().pageRoute ); diff --git a/src/loaders/swaggerLoader.ts b/src/loaders/swaggerLoader.ts index dc7d1818..e86afcd2 100644 --- a/src/loaders/swaggerLoader.ts +++ b/src/loaders/swaggerLoader.ts @@ -19,12 +19,12 @@ export const swaggerLoader: MicroframeworkLoader = (settings: MicroframeworkSett expressApp.use( env.swagger.route, - basicAuth({ + env.swagger.username ? basicAuth({ users: { [`${env.swagger.username}`]: env.swagger.password }, challenge: true - }), + }) : (req, res, next) => next(), swaggerUi.serve, swaggerUi.setup(swaggerFile) ); diff --git a/src/loaders/typeormLoader.ts b/src/loaders/typeormLoader.ts index 70746ba6..bcf33b52 100644 --- a/src/loaders/typeormLoader.ts +++ b/src/loaders/typeormLoader.ts @@ -5,9 +5,9 @@ import { env } from '../core/env'; export const typeormLoader: MicroframeworkLoader = async (settings: MicroframeworkSettings | undefined) => { - // @ts-ignore this + const connection = await createConnection({ - type: env.db.type, + type: env.db.type as any, // See createConnection options for valid types host: env.db.host, port: env.db.port, username: env.db.username, diff --git a/tslint.json b/tslint.json index eee75561..fb99c6ab 100644 --- a/tslint.json +++ b/tslint.json @@ -6,7 +6,8 @@ 160 ], "no-unnecessary-initializer": false, - "no-var-requires": false, + "no-var-requires": true, + "no-null-keyword": true, "no-consecutive-blank-lines": false, "quotemark": [true, "single", "avoid-escape"], "interface-name": false, From 3b3fabeaf114668a11d9cfa23d7165d9ce6265b0 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 19 Nov 2017 16:53:36 +0100 Subject: [PATCH 028/104] Remove constants --- README.md | 1 - src/constants/.gitkeep | 0 2 files changed, 1 deletion(-) delete mode 100644 src/constants/.gitkeep diff --git a/README.md b/README.md index 1cac9b21..88e30da2 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,6 @@ The swagger and the monitor route can be altered in the `.env` file. | **src/api/validators/** | Custom validators, which can be used in the request classes | | **src/api/** swagger.json | Swagger documentation | | **src/console/** | Command line scripts | -| **src/constants/** | Global Constants | | **src/core/** | The core features like logger and env variables | | **src/public/** | Static assets (fonts, css, js, img). | | **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | diff --git a/src/constants/.gitkeep b/src/constants/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 8ea0b78a4276e91d41d254c9b157c267aef4e2ca Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 19 Nov 2017 17:00:03 +0100 Subject: [PATCH 029/104] Remove unused stuff --- favicon.ico | Bin 1150 -> 0 bytes src/types/interfaces.d.ts | 8 -------- wallaby.js | 40 -------------------------------------- 3 files changed, 48 deletions(-) delete mode 100644 favicon.ico delete mode 100644 src/types/interfaces.d.ts delete mode 100644 wallaby.js diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index a406e949abb2e41d66c394bb9559f27481beca83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmaLVO)ErU6u|K_gGNodBe^6+6JbbFvQVs)mzsP47FH&VB+5oc_yDr7P*$>`EEFqc zVPWAdO|w9ejY$zg2;+Y)Y3>~~{qA$mdCr;VWk$rs$LABC#S%&vF-4>tq{w{b8AO=N z@qV%5?}v|O!!TTo4d{Ru8E8c_(vjR^d)GPSUm9vf0aeg7 zU!eI>gEQ6nbH~nxt7;F!fzpF;z{4oJ;WFK~nueacADP(31q#uUW%!IK8E&zI)C+O+ zQ=>hq5H*; { - process.env.NODE_ENV = 'test'; - // View test statistics (for locally running wallaby): http://wallabyjs.com/app/#/tests - return { - - files: [ - 'tsconfig.json', - 'src/**/*.ts', - 'test/lib/**/*.ts', - 'src/**/*.json', - ], - - tests: ['test/unit/**/*.test.ts'], - - env: { - type: 'node', - runner: 'node', - }, - - testFramework: 'jest', - - // setup(wallaby) { - // wallaby.testFramework.configure(require('./package.json').jest); - // }, - - compilers: { - '**/*.ts': wallaby.compilers.typeScript({ module: 'commonjs' }) - } - - // // If you want to do database testing with async calls then - // // you have to set this options, so that wallaby uses only - // // one worker - // workers: { - // recycle: true, - // initial: 1, - // regular: 1 - // } - - }; -}; From 2a669218b01f0f441ae75444753a84a182f5a6b3 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 19 Nov 2017 17:04:07 +0100 Subject: [PATCH 030/104] Remove knex things --- package-scripts.js | 29 ----------------------------- tsconfig.json | 3 --- 2 files changed, 32 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index 5f92efdd..e71b4337 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -149,35 +149,6 @@ module.exports = { dev: run('./src/console/lib/console.ts'), help: runFast('./src/console/lib/console.ts --help') }, - /** - * All database related scripts are here - */ - db: { - migrate: { - default: { - script: series( - 'nps banner.migrate', - '\"./node_modules/.bin/knex\" migrate:latest', - ) - }, - rollback: series( - 'nps banner.rollback', - '\"./node_modules/.bin/knex\" migrate:rollback', - ) - }, - seed: { - script: series( - 'nps banner.seed', - '\"./node_modules/.bin/knex\" seed:run', - ) - }, - reset: { - script: series( - 'nps banner.dbReset', - 'nps console db:reset', - ) - } - }, /** * This creates pretty banner to the terminal */ diff --git a/tsconfig.json b/tsconfig.json index 80328b81..a4c179c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,8 +34,5 @@ }, "include": [ "src/**/*" - ], - "exclude": [ - "knexfile.ts" ] } From 5deb1045620f645268ff371177d1d910e118891f Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 17:12:58 +0100 Subject: [PATCH 031/104] Remove unused scripts --- package-scripts.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index e71b4337..a486bb7b 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -25,9 +25,7 @@ module.exports = { */ setup: { script: series( - 'yarn install', - 'nps db.migrate', - 'nps db.seed', + 'yarn install' ) }, /** @@ -138,17 +136,6 @@ module.exports = { ) } }, - /** - * This our scaffold api - * @example > nps "console make:controller" - */ - console: { - default: { - script: runFast('./src/console/lib/console.ts') - }, - dev: run('./src/console/lib/console.ts'), - help: runFast('./src/console/lib/console.ts --help') - }, /** * This creates pretty banner to the terminal */ From 12e41cecf8700a19f34f954d1c478fa712f10555 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 17:17:36 +0100 Subject: [PATCH 032/104] Add migration scripts --- package-scripts.js | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index a486bb7b..ca6169c0 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -40,6 +40,31 @@ module.exports = { 'nps copy', ) }, + /** + * Migrate the database with TypeORM + */ + migrate: { + default: { + script: series( + 'nps banner.migrate', + 'nps lint', + 'nps clean.dist', + 'nps transpile', + 'nps copy', + '\"./node_modules/.bin/typeorm\" migrations:run', + ) + }, + rollback: { + script: series( + 'nps banner.rollback', + 'nps lint', + 'nps clean.dist', + 'nps transpile', + 'nps copy', + '\"./node_modules/.bin/typeorm\" migrations:revert', + ) + } + }, /** * These run various kinds of tests. Default is unit. */ @@ -143,10 +168,8 @@ module.exports = { build: banner('build'), serve: banner('serve'), test: banner('test'), - migrate: banner('db.migrate'), - rollback: banner('db.migrate.rollback'), - dbReset: banner('db.reset'), - seed: banner('db.seed'), + migrate: banner('migrate'), + rollback: banner('rollback'), clean: banner('clean') } } From b3a4fbeea4aa2890bf7f934c2b0e25487da43a88 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 17:18:24 +0100 Subject: [PATCH 033/104] Change defaults of the .env --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 5d93ef85..a682af6b 100644 --- a/.env.example +++ b/.env.example @@ -27,8 +27,8 @@ DB_PORT=3306 DB_USERNAME="root" DB_PASSWORD="" DB_DATABASE="my_database" -DB_SYNCHRONIZE=true -DB_LOGGING=true +DB_SYNCHRONIZE=false +DB_LOGGING=false # # Swagger From 098abcb15f9330404a41d67a5680e5baa9dc99df Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 20:35:52 +0100 Subject: [PATCH 034/104] Add migration docu and config --- .env.example | 3 +++ README.md | 6 ++++++ ormconfig.json | 30 +++++++++++++++--------------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index a682af6b..38a40d5f 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,9 @@ DB_PASSWORD="" DB_DATABASE="my_database" DB_SYNCHRONIZE=false DB_LOGGING=false +DB_ENTITIES="src/api/**/*Models.ts" +DB_MIGRATIONS="src/database/migrations/*.ts" +DB_MIGRATIONS_DIR="src/database/migrations" # # Swagger diff --git a/README.md b/README.md index 88e30da2..0b3babaa 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,12 @@ All script are defined in the package.json file, but the most important ones are - Run `nps build` to generated all JavaScript files from the TypeScript sources (There is also a vscode task for this called `build`). - To start the builded app located in `dist` use `npm start`. +### Database + +- Run `./node_modules/.bin/typeorm create -n ` to create a new migration file. +- To migrate your database run `nps migrate`. +- To revert your latest migration run `nps migrate.revert`. + ## Using the debugger in VS Code Just set a breakpoint and hit `F5` in your Visual Studio Code. diff --git a/ormconfig.json b/ormconfig.json index da2fff0a..8b40a6a2 100644 --- a/ormconfig.json +++ b/ormconfig.json @@ -1,17 +1,17 @@ { - "type": "mysql", - "host": "localhost", - "port": 3306, - "username": "root", - "password": "", - "database": "my_database", - "entities": [ - "dist/api/**/*Models.js" - ], - "migrations": [ - "dist/database/migrations/*.js" - ], - "cli": { - "migrationsDir": "dist/database/migrations" - } + "type": "mysql", + "host": "localhost", + "port": 3306, + "username": "root", + "password": "", + "database": "my_database", + "entities": [ + "src/api/**/*Models.ts" + ], + "migrations": [ + "src/database/migrations/*.ts" + ], + "cli": { + "migrationsDir": "src/database/migrations" + } } From 3ba9350b73c500924ec0c97192c94cac31009219 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 20:37:41 +0100 Subject: [PATCH 035/104] Add pkg scripts --- package-scripts.js | 23 ++++++++++------------- package.json | 1 + 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index ca6169c0..eef3fe53 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -47,22 +47,19 @@ module.exports = { default: { script: series( 'nps banner.migrate', - 'nps lint', - 'nps clean.dist', - 'nps transpile', - 'nps copy', - '\"./node_modules/.bin/typeorm\" migrations:run', + 'nps migrate.config', + runFast('./node_modules/.bin/typeorm migrations:run'), ) }, - rollback: { + revert: { script: series( - 'nps banner.rollback', - 'nps lint', - 'nps clean.dist', - 'nps transpile', - 'nps copy', - '\"./node_modules/.bin/typeorm\" migrations:revert', + 'nps banner.revert', + 'nps migrate.config', + runFast('./node_modules/.bin/typeorm migrations:revert'), ) + }, + config: { + script: runFast('./lib/ormconfig.ts'), } }, /** @@ -169,7 +166,7 @@ module.exports = { serve: banner('serve'), test: banner('test'), migrate: banner('migrate'), - rollback: banner('rollback'), + revert: banner('revert'), clean: banner('clean') } } diff --git a/package.json b/package.json index 4907d895..b5fb4401 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "express-status-monitor": "^1.0.1", "figlet": "^1.2.0", "helmet": "^3.9.0", + "jsonfile": "^4.0.0", "lodash": "^4.17.4", "microframework": "^0.6.4", "morgan": "^1.9.0", From ef54a3236358c96f22d8c7ad732a118c12a83e8b Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 20:38:41 +0100 Subject: [PATCH 036/104] Add ormconfig generator --- lib/ormconfig.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lib/ormconfig.ts diff --git a/lib/ormconfig.ts b/lib/ormconfig.ts new file mode 100644 index 00000000..d788c24e --- /dev/null +++ b/lib/ormconfig.ts @@ -0,0 +1,30 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); + +import * as path from 'path'; +import * as jsonfile from 'jsonfile'; +import { env } from '../src/core/env'; + + +const content = { + type: env.db.type, + host: env.db.host, + port: env.db.port, + username: env.db.username, + password: env.db.password, + database: env.db.database, + entities: env.db.entities, + migrations: env.db.migrations, + cli: { + migrationsDir: env.db.migrationsDir + } +}; + +const filePath = path.join(__dirname, '../', 'ormconfig.json'); +jsonfile.writeFile(filePath, content, { spaces: 2 }, (err) => { + if (err === null) { + console.log('Successfully generated ormconfig.json form the .env file'); + } else { + console.error('Failed to generate the ormconfig.json', err); + } +}); From cc376f31f2163bfd25e342df06af0a15db01a599 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sun, 19 Nov 2017 22:17:30 +0100 Subject: [PATCH 037/104] Refactor and add TypeORM values to the env.ts --- src/app.ts | 3 --- src/core/env.ts | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app.ts b/src/app.ts index 0b6ef6dc..668446aa 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,9 +7,6 @@ * the 'README.md' file. */ import 'reflect-metadata'; -import * as dotenv from 'dotenv'; -dotenv.config(); - import { banner } from './core/banner'; import { Log } from './core/Log'; const log = new Log(__filename); diff --git a/src/core/env.ts b/src/core/env.ts index 105af78e..2877bd96 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -1,4 +1,7 @@ import * as pkg from '../../package.json'; +import * as dotenv from 'dotenv'; +dotenv.config(); + /** * Environment variables @@ -30,7 +33,10 @@ export const env = { password: getOsEnv('DB_PASSWORD'), database: getOsEnv('DB_DATABASE'), synchronize: toBool(getOsEnv('DB_SYNCHRONIZE')), - logging: toBool(getOsEnv('DB_LOGGING')) + logging: toBool(getOsEnv('DB_LOGGING')), + entities: toArray(getOsEnv('DB_ENTITIES')), + migrations: toArray(getOsEnv('DB_MIGRATIONS')), + migrationsDir: getOsEnv('DB_MIGRATIONS_DIR') }, swagger: { enabled: toBool(getOsEnv('SWAGGER_ENABLED')), @@ -59,6 +65,13 @@ function toBool(value: string): boolean { return value === 'true'; } +function toArray(value: string): string[] { + if (value && value.indexOf(',') >= 0) { + return value.split(','); + } + return [value]; +} + function normalizePort(port: string): number | string | boolean { const parsedPort = parseInt(port, 10); if (isNaN(parsedPort)) { // named pipe From fa044cb4e0c3fc7368fa615ac581f94edd82e42a Mon Sep 17 00:00:00 2001 From: David Weber Date: Mon, 20 Nov 2017 07:37:56 +0100 Subject: [PATCH 038/104] Use npm start instead of nps as nps isn't globally installed and will fail for most users --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0b3babaa..21c6f930 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Create a new database with the name you have in your `.env`-file. Then setup your application environment. ```bash -nps setup +npm start setup ``` > This installs all dependencies with yarn. After that it migrates the database and seeds some test data into it. So after that your development environment is ready to use. @@ -90,7 +90,7 @@ nps setup Go to the project dir and start your app with this npm script. ```bash -nps serve +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. @@ -106,29 +106,29 @@ All script are defined in the package.json file, but the most important ones are ### Linting -- Run code quality analysis using `nps lint`. This runs tslint. +- Run code quality analysis using `npm start lint`. This runs tslint. - There is also a vscode task for this called `lint`. ### Tests -- Run the unit tests using `nps test` (There is also a vscode task for this called `test`). -- Run the e2e tests using `nps test:e2e` and don't forget to start your application and your [Auth0 Mock Server](https://github.com/hirsch88/auth0-mock-server). +- Run the unit tests using `npm start test` (There is also a vscode task for this called `test`). +- Run the e2e tests using `npm start test:e2e` and don't forget to start your application and your [Auth0 Mock Server](https://github.com/hirsch88/auth0-mock-server). ### Running in dev mode -- Run `nps serve` to start nodemon with ts-node, to serve the app. +- Run `npm start serve` to start nodemon with ts-node, to serve the app. - The server address will be displayed to you as `http://0.0.0.0:3000` ### Building the project and run it -- Run `nps build` to generated all JavaScript files from the TypeScript sources (There is also a vscode task for this called `build`). +- Run `npm start build` to generated all JavaScript files from the TypeScript sources (There is also a vscode task for this called `build`). - To start the builded app located in `dist` use `npm start`. ### Database - Run `./node_modules/.bin/typeorm create -n ` to create a new migration file. -- To migrate your database run `nps migrate`. -- To revert your latest migration run `nps migrate.revert`. +- To migrate your database run `npm start migrate`. +- To revert your latest migration run `npm start migrate.revert`. ## Using the debugger in VS Code From 9e28d377451d7e13f6af5e680ffa2073fd249794 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 07:42:36 +0100 Subject: [PATCH 039/104] Add custom error handling --- .env.example | 1 + src/api/middlewares/ErrorHandlerMiddleware.ts | 26 +++++++++++++++++++ src/core/env.ts | 5 +++- src/loaders/expressLoader.ts | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/api/middlewares/ErrorHandlerMiddleware.ts diff --git a/.env.example b/.env.example index 38a40d5f..74ff1281 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ APP_BANNER=true LOG_LEVEL="debug" LOG_JSON=false LOG_OUTPUT="dev" +APP_ERROR_PRINTSTACK_CODE=500 # # AUTHORIZATION diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts new file mode 100644 index 00000000..c0ed481b --- /dev/null +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -0,0 +1,26 @@ +import * as express from 'express'; +import { Middleware, ExpressErrorMiddlewareInterface, HttpError } from 'routing-controllers'; +import { env } from '../../core/env'; +import { Log } from '../../core/Log'; +const log = new Log(__filename); + + +@Middleware({ type: 'after' }) +export class CustomErrorHandler implements ExpressErrorMiddlewareInterface { + + public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void { + + // Standard output of an error to the user. + res.status(error.httpCode || 500); + res.json({ + name: error.name, + message: error.message + }); + + // Print stack if the status code matches or is higher than the defined one in the .env file. + if (error.httpCode >= (env.app.error.printStackCode || 404)) { + log.error(error.stack as string); + } + } + +} diff --git a/src/core/env.ts b/src/core/env.ts index 2877bd96..8894cf88 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -15,7 +15,10 @@ export const env = { route: getOsEnv('APP_ROUTE'), routePrefix: getOsEnv('APP_ROUTE_PREFIX'), port: normalizePort(process.env.PORT || '3000'), - banner: toBool(getOsEnv('APP_BANNER')) + banner: toBool(getOsEnv('APP_BANNER')), + error: { + printStackCode: toNumber(getOsEnv('APP_ERROR_PRINTSTACK_CODE')) + } }, log: { level: getOsEnv('LOG_LEVEL'), diff --git a/src/loaders/expressLoader.ts b/src/loaders/expressLoader.ts index 6b9e2341..66e6f09a 100644 --- a/src/loaders/expressLoader.ts +++ b/src/loaders/expressLoader.ts @@ -18,6 +18,7 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett cors: true, classTransformer: true, routePrefix: env.app.routePrefix, + defaultErrorHandler: false, /** * We can add options about how routing-controllers should configure itself. * Here we specify what controllers should be registered in our express server. From c5f11f2fc14ea05759659bbf98d7af13daf38c52 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 07:47:06 +0100 Subject: [PATCH 040/104] Change tslint rules with trailing comma to make git diffs easier --- src/api/middlewares/ErrorHandlerMiddleware.ts | 2 +- src/api/middlewares/LogMiddleware.ts | 4 ++-- src/api/middlewares/SecurityHstsMiddleware.ts | 2 +- src/app.ts | 4 ++-- src/auth/AuthService.ts | 4 ++-- src/auth/currentUserChecker.ts | 4 ++-- src/core/env.ts | 16 ++++++++-------- src/loaders/expressLoader.ts | 2 +- src/loaders/homeLoader.ts | 2 +- src/loaders/monitorLoader.ts | 4 ++-- src/loaders/swaggerLoader.ts | 6 +++--- src/loaders/typeormLoader.ts | 4 ++-- src/loaders/winstonLoader.ts | 6 +++--- tslint.json | 7 ++++++- 14 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index c0ed481b..330b4ec8 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -14,7 +14,7 @@ export class CustomErrorHandler implements ExpressErrorMiddlewareInterface { res.status(error.httpCode || 500); res.json({ name: error.name, - message: error.message + message: error.message, }); // Print stack if the status code matches or is higher than the defined one in the .env file. diff --git a/src/api/middlewares/LogMiddleware.ts b/src/api/middlewares/LogMiddleware.ts index 73fb5e1f..30293edf 100644 --- a/src/api/middlewares/LogMiddleware.ts +++ b/src/api/middlewares/LogMiddleware.ts @@ -14,8 +14,8 @@ export class SecurityMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return morgan(env.log.output, { stream: { - write: this.log.info.bind(this.log) - } + write: this.log.info.bind(this.log), + }, })(req, res, next); } diff --git a/src/api/middlewares/SecurityHstsMiddleware.ts b/src/api/middlewares/SecurityHstsMiddleware.ts index 2d827b9f..84209d16 100644 --- a/src/api/middlewares/SecurityHstsMiddleware.ts +++ b/src/api/middlewares/SecurityHstsMiddleware.ts @@ -10,7 +10,7 @@ export class SecurityMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return helmet.hsts({ maxAge: 31536000, - includeSubdomains: true + includeSubdomains: true, })(req, res, next); } diff --git a/src/app.ts b/src/app.ts index 668446aa..89642e5c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -35,8 +35,8 @@ bootstrapMicroframework({ swaggerLoader, monitorLoader, homeLoader, - publicLoader - ] + publicLoader, + ], }) .then(() => banner(log)) .catch(error => log.error('Application is crashed: ' + error)); diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index b758863e..c6a8b5e1 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -26,8 +26,8 @@ export class AuthService { method: 'POST', url: env.auth.route, form: { - id_token: token - } + id_token: token, + }, }, (error: any, response: request.RequestResponse, body: any) => { // Verify if the requests was successful and append user // information to our extended express request object diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts index 43b060ce..fcb5fa2a 100644 --- a/src/auth/currentUserChecker.ts +++ b/src/auth/currentUserChecker.ts @@ -16,8 +16,8 @@ export function currentUserChecker(connection: Connection): (action: Action) => const em = connection.createEntityManager(); const user = await em.findOne(User, { where: { - email: tokeninfo.user_id - } + email: tokeninfo.user_id, + }, }); if (user) { log.info('Current user is ', user.toString()); diff --git a/src/core/env.ts b/src/core/env.ts index 8894cf88..deb2ba88 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -17,16 +17,16 @@ export const env = { port: normalizePort(process.env.PORT || '3000'), banner: toBool(getOsEnv('APP_BANNER')), error: { - printStackCode: toNumber(getOsEnv('APP_ERROR_PRINTSTACK_CODE')) - } + printStackCode: toNumber(getOsEnv('APP_ERROR_PRINTSTACK_CODE')), + }, }, log: { level: getOsEnv('LOG_LEVEL'), json: toBool(getOsEnv('LOG_JSON')), - output: getOsEnv('LOG_OUTPUT') + output: getOsEnv('LOG_OUTPUT'), }, auth: { - route: getOsEnv('AUTH_ROUTE') + route: getOsEnv('AUTH_ROUTE'), }, db: { type: getOsEnv('DB_TYPE'), @@ -39,21 +39,21 @@ export const env = { logging: toBool(getOsEnv('DB_LOGGING')), entities: toArray(getOsEnv('DB_ENTITIES')), migrations: toArray(getOsEnv('DB_MIGRATIONS')), - migrationsDir: getOsEnv('DB_MIGRATIONS_DIR') + migrationsDir: getOsEnv('DB_MIGRATIONS_DIR'), }, swagger: { enabled: toBool(getOsEnv('SWAGGER_ENABLED')), route: getOsEnv('SWAGGER_ROUTE'), file: getOsEnv('SWAGGER_FILE'), username: getOsEnv('SWAGGER_USERNAME'), - password: getOsEnv('SWAGGER_PASSWORD') + password: getOsEnv('SWAGGER_PASSWORD'), }, monitor: { enabled: toBool(getOsEnv('MONITOR_ENABLED')), route: getOsEnv('MONITOR_ROUTE'), username: getOsEnv('MONITOR_USERNAME'), - password: getOsEnv('MONITOR_PASSWORD') - } + password: getOsEnv('MONITOR_PASSWORD'), + }, }; function getOsEnv(key: string): string { diff --git a/src/loaders/expressLoader.ts b/src/loaders/expressLoader.ts index 66e6f09a..4933dba3 100644 --- a/src/loaders/expressLoader.ts +++ b/src/loaders/expressLoader.ts @@ -31,7 +31,7 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett * Authorization features */ authorizationChecker: authorizationChecker(connection), - currentUserChecker: currentUserChecker(connection) + currentUserChecker: currentUserChecker(connection), }); // Run application to listen on given port diff --git a/src/loaders/homeLoader.ts b/src/loaders/homeLoader.ts index 7af75aaf..0011d11d 100644 --- a/src/loaders/homeLoader.ts +++ b/src/loaders/homeLoader.ts @@ -12,7 +12,7 @@ export const homeLoader: MicroframeworkLoader = (settings: MicroframeworkSetting return res.json({ name: env.app.name, version: env.app.version, - description: env.app.description + description: env.app.description, }); } ); diff --git a/src/loaders/monitorLoader.ts b/src/loaders/monitorLoader.ts index 94507afd..0bf12aea 100644 --- a/src/loaders/monitorLoader.ts +++ b/src/loaders/monitorLoader.ts @@ -13,9 +13,9 @@ export const monitorLoader: MicroframeworkLoader = (settings: MicroframeworkSett env.monitor.route, env.monitor.username ? basicAuth({ users: { - [`${env.monitor.username}`]: env.monitor.password + [`${env.monitor.username}`]: env.monitor.password, }, - challenge: true + challenge: true, }) : (req, res, next) => next(), monitor().pageRoute ); diff --git a/src/loaders/swaggerLoader.ts b/src/loaders/swaggerLoader.ts index e86afcd2..c1f4516e 100644 --- a/src/loaders/swaggerLoader.ts +++ b/src/loaders/swaggerLoader.ts @@ -14,16 +14,16 @@ export const swaggerLoader: MicroframeworkLoader = (settings: MicroframeworkSett swaggerFile.info = { title: env.app.name, description: env.app.description, - version: env.app.version + version: env.app.version, }; expressApp.use( env.swagger.route, env.swagger.username ? basicAuth({ users: { - [`${env.swagger.username}`]: env.swagger.password + [`${env.swagger.username}`]: env.swagger.password, }, - challenge: true + challenge: true, }) : (req, res, next) => next(), swaggerUi.serve, swaggerUi.setup(swaggerFile) diff --git a/src/loaders/typeormLoader.ts b/src/loaders/typeormLoader.ts index bcf33b52..6dd6645a 100644 --- a/src/loaders/typeormLoader.ts +++ b/src/loaders/typeormLoader.ts @@ -16,8 +16,8 @@ export const typeormLoader: MicroframeworkLoader = async (settings: Microframewo synchronize: env.db.synchronize, logging: env.db.logging, entities: [ - path.join(__dirname, '..', 'api/models/*{.js,.ts}') - ] + path.join(__dirname, '..', 'api/models/*{.js,.ts}'), + ], }); if (settings) { diff --git a/src/loaders/winstonLoader.ts b/src/loaders/winstonLoader.ts index 449b260d..c61f563c 100644 --- a/src/loaders/winstonLoader.ts +++ b/src/loaders/winstonLoader.ts @@ -11,8 +11,8 @@ export const winstonLoader: MicroframeworkLoader = (settings: MicroframeworkSett handleExceptions: true, json: env.log.json, timestamp: env.node !== 'development', - colorize: env.node === 'development' - }) - ] + colorize: env.node === 'development', + }), + ], }); }; diff --git a/tslint.json b/tslint.json index ee9940d5..3a9f5ee3 100644 --- a/tslint.json +++ b/tslint.json @@ -64,7 +64,12 @@ "trailing-comma": [ true, { - "multiline": "never", + "multiline": { + "objects": "always", + "arrays": "always", + "functions": "never", + "typeLiterals": "ignore" + }, "singleline": "never" } ] From 17f010606c3addf1e8dc28f579f7106614c45ea1 Mon Sep 17 00:00:00 2001 From: David Weber Date: Mon, 20 Nov 2017 07:55:07 +0100 Subject: [PATCH 041/104] We don't have wallabyjs anymore, we use jest now --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21c6f930..d8a2cf4e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes) - **API Documentation** thanks to [swagger](http://swagger.io/). - **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor). -- **Integrated Testing Tool** thanks to [Wallaby.js](https://wallabyjs.com/) +- **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest) - **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/) ### Comming soon From 65620f9243fd4795480c0cdeda4044a579251039 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 08:44:21 +0100 Subject: [PATCH 042/104] Add event-dispatcher --- package.json | 2 ++ src/api/services/UserService.ts | 9 +++++++-- src/api/subscribers/UserEventSubscriber.ts | 16 ++++++++++++++++ src/api/subscribers/events.ts | 10 ++++++++++ src/app.ts | 2 ++ src/loaders/eventDispatchLoader.ts | 21 +++++++++++++++++++++ yarn.lock | 15 +++++++++++++++ 7 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/api/subscribers/UserEventSubscriber.ts create mode 100644 src/api/subscribers/events.ts create mode 100644 src/loaders/eventDispatchLoader.ts diff --git a/package.json b/package.json index b5fb4401..f51a2dbb 100644 --- a/package.json +++ b/package.json @@ -54,10 +54,12 @@ "copyfiles": "^1.2.0", "cors": "^2.8.4", "dotenv": "^4.0.0", + "event-dispatch": "^0.4.1", "express": "^4.16.2", "express-basic-auth": "^1.1.3", "express-status-monitor": "^1.0.1", "figlet": "^1.2.0", + "glob": "^7.1.2", "helmet": "^3.9.0", "jsonfile": "^4.0.0", "lodash": "^4.17.4", diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index 22b21504..02bfb2aa 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -1,7 +1,9 @@ import { Service } from 'typedi'; import { OrmRepository } from 'typeorm-typedi-extensions'; +import { EventDispatcher } from 'event-dispatch'; import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; +import { events } from '../subscribers/events'; @Service() @@ -19,8 +21,11 @@ export class UserService { return this.userRepository.findOne({ id }); } - public create(user: User): Promise { - return this.userRepository.save(user); + public async create(user: User): Promise { + const newUser = await this.userRepository.save(user); + const eventDispatcher = new EventDispatcher(); + eventDispatcher.dispatch(events.user.created, newUser); + return newUser; } public update(id: string, user: User): Promise { diff --git a/src/api/subscribers/UserEventSubscriber.ts b/src/api/subscribers/UserEventSubscriber.ts new file mode 100644 index 00000000..f7864d08 --- /dev/null +++ b/src/api/subscribers/UserEventSubscriber.ts @@ -0,0 +1,16 @@ +import { EventSubscriber, On } from 'event-dispatch'; +import { User } from '../models/User'; +import { events } from './events'; +import { Log } from '../../core/Log'; +const log = new Log(__filename); + + +@EventSubscriber() +export class UserEventSubscriber { + + @On(events.user.created) + public onUserCreate(user: User): void { + log.info('User ' + user.toString() + ' created!'); + } + +} diff --git a/src/api/subscribers/events.ts b/src/api/subscribers/events.ts new file mode 100644 index 00000000..2d236326 --- /dev/null +++ b/src/api/subscribers/events.ts @@ -0,0 +1,10 @@ +/** + * events + * --------------------- + * Define all your possible custom events here. + */ +export const events = { + user: { + created: 'onUserCreate', + }, +}; diff --git a/src/app.ts b/src/app.ts index 89642e5c..873e1880 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,6 +20,7 @@ import { monitorLoader } from './loaders/monitorLoader'; import { homeLoader } from './loaders/homeLoader'; import { publicLoader } from './loaders/publicLoader'; import { iocLoader } from './loaders/iocLoader'; +import { eventDispatchLoader } from './loaders/eventDispatchLoader'; bootstrapMicroframework({ @@ -30,6 +31,7 @@ bootstrapMicroframework({ loaders: [ winstonLoader, iocLoader, + eventDispatchLoader, typeormLoader, expressLoader, swaggerLoader, diff --git a/src/loaders/eventDispatchLoader.ts b/src/loaders/eventDispatchLoader.ts new file mode 100644 index 00000000..ec8bf077 --- /dev/null +++ b/src/loaders/eventDispatchLoader.ts @@ -0,0 +1,21 @@ +import * as path from 'path'; +import * as glob from 'glob'; +import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; + + +/** + * eventDispatchLoader + * ------------------------------ + * This loads all the created subscribers into the project, so we do not have to + * import them manually + */ +export const eventDispatchLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { + if (settings) { + const filePath = path.join(__dirname, '..', 'api/**/*Subscriber{.js,.ts}'); + glob(filePath, (err: any, files: string[]) => { + for (const file of files) { + require(file); + } + }); + } +}; diff --git a/yarn.lock b/yarn.lock index 5280aa7c..73b253d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1399,6 +1399,13 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" +event-dispatch@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/event-dispatch/-/event-dispatch-0.4.1.tgz#5f9c272f5080f79d3e22827303a9fbcccd2c433e" + dependencies: + fs "0.0.2" + path "^0.11.14" + event-stream@~3.3.0: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" @@ -1683,6 +1690,10 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" +fs@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.2.tgz#e1f244ef3933c1b2a64bd4799136060d0f5914f8" + fsevents@^1.0.0, fsevents@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" @@ -3502,6 +3513,10 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" +path@^0.11.14: + version "0.11.14" + resolved "https://registry.yarnpkg.com/path/-/path-0.11.14.tgz#cbc7569355cb3c83afeb4ace43ecff95231e5a7d" + path@^0.12.7: version "0.12.7" resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" From 07bd60230b6584c663997ae889068ccffae05083 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 08:44:36 +0100 Subject: [PATCH 043/104] Fix middlewares --- src/api/middlewares/CompressionMiddleware.ts | 2 +- src/api/middlewares/SecurityHstsMiddleware.ts | 2 +- src/api/middlewares/SecurityMiddleware.ts | 2 +- src/api/middlewares/SecurityNoCacheMiddleware.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/middlewares/CompressionMiddleware.ts b/src/api/middlewares/CompressionMiddleware.ts index 0a37e9d1..c508e675 100644 --- a/src/api/middlewares/CompressionMiddleware.ts +++ b/src/api/middlewares/CompressionMiddleware.ts @@ -4,7 +4,7 @@ import * as compression from 'compression'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'after' }) +@Middleware({ type: 'before' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { diff --git a/src/api/middlewares/SecurityHstsMiddleware.ts b/src/api/middlewares/SecurityHstsMiddleware.ts index 84209d16..430d3287 100644 --- a/src/api/middlewares/SecurityHstsMiddleware.ts +++ b/src/api/middlewares/SecurityHstsMiddleware.ts @@ -4,7 +4,7 @@ import * as helmet from 'helmet'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'after' }) +@Middleware({ type: 'before' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { diff --git a/src/api/middlewares/SecurityMiddleware.ts b/src/api/middlewares/SecurityMiddleware.ts index b92dd13a..02e4b2c2 100644 --- a/src/api/middlewares/SecurityMiddleware.ts +++ b/src/api/middlewares/SecurityMiddleware.ts @@ -4,7 +4,7 @@ import * as helmet from 'helmet'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'after' }) +@Middleware({ type: 'before' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { diff --git a/src/api/middlewares/SecurityNoCacheMiddleware.ts b/src/api/middlewares/SecurityNoCacheMiddleware.ts index 5915da50..f46b159b 100644 --- a/src/api/middlewares/SecurityNoCacheMiddleware.ts +++ b/src/api/middlewares/SecurityNoCacheMiddleware.ts @@ -4,7 +4,7 @@ import * as helmet from 'helmet'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -@Middleware({ type: 'after' }) +@Middleware({ type: 'before' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { From 9f57d7ad74033adaba6c339f1ca3afb03478c202 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 08:49:31 +0100 Subject: [PATCH 044/104] Custom error-handler reacts diffrent in production --- .env.example | 1 - src/api/middlewares/ErrorHandlerMiddleware.ts | 24 ++++++++++++------- src/core/env.ts | 4 +--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index 74ff1281..38a40d5f 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,6 @@ APP_BANNER=true LOG_LEVEL="debug" LOG_JSON=false LOG_OUTPUT="dev" -APP_ERROR_PRINTSTACK_CODE=500 # # AUTHORIZATION diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index 330b4ec8..23c916d8 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -9,18 +9,24 @@ const log = new Log(__filename); export class CustomErrorHandler implements ExpressErrorMiddlewareInterface { public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void { - - // Standard output of an error to the user. res.status(error.httpCode || 500); - res.json({ - name: error.name, - message: error.message, - }); - // Print stack if the status code matches or is higher than the defined one in the .env file. - if (error.httpCode >= (env.app.error.printStackCode || 404)) { - log.error(error.stack as string); + // Standard output of an error to the user. + if (env.isProduction) { + res.json({ + name: error.name, + message: error.message, + stack: error.stack, + }); + log.error(error.name, error.stack); + } else { + res.json({ + name: error.name, + message: error.message, + }); + log.error(error.name, error.message); } + } } diff --git a/src/core/env.ts b/src/core/env.ts index deb2ba88..d3f1520a 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -8,6 +8,7 @@ dotenv.config(); */ export const env = { node: process.env.NODE_ENV || 'development', + isProduction: process.env.NODE_ENV === 'production', app: { name: getOsEnv('APP_NAME'), version: (pkg as any).version, @@ -16,9 +17,6 @@ export const env = { routePrefix: getOsEnv('APP_ROUTE_PREFIX'), port: normalizePort(process.env.PORT || '3000'), banner: toBool(getOsEnv('APP_BANNER')), - error: { - printStackCode: toNumber(getOsEnv('APP_ERROR_PRINTSTACK_CODE')), - }, }, log: { level: getOsEnv('LOG_LEVEL'), From c9797624fc1eb4f7e58cb542c6d7b44abab6677d Mon Sep 17 00:00:00 2001 From: David Weber Date: Mon, 20 Nov 2017 08:50:54 +0100 Subject: [PATCH 045/104] Move app related directory into a central place for easy change, btw. they do not change mostly Don't track ormconfig.json as this can be different on any local setup as it's driven by .env --- .env.example | 3 --- .gitignore | 1 + lib/ormconfig.ts | 8 ++++---- ormconfig.json | 17 ----------------- src/core/env.ts | 19 +++++++++---------- src/loaders/expressLoader.ts | 7 +++---- src/loaders/typeormLoader.ts | 5 +---- 7 files changed, 18 insertions(+), 42 deletions(-) delete mode 100644 ormconfig.json diff --git a/.env.example b/.env.example index 74ff1281..b3dbc0b1 100644 --- a/.env.example +++ b/.env.example @@ -30,9 +30,6 @@ DB_PASSWORD="" DB_DATABASE="my_database" DB_SYNCHRONIZE=false DB_LOGGING=false -DB_ENTITIES="src/api/**/*Models.ts" -DB_MIGRATIONS="src/database/migrations/*.ts" -DB_MIGRATIONS_DIR="src/database/migrations" # # Swagger diff --git a/.gitignore b/.gitignore index 860ee89c..032e2e7e 100755 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ typings/ # Dist # dist/ +ormconfig.json # IDE # .idea/ diff --git a/lib/ormconfig.ts b/lib/ormconfig.ts index d788c24e..f1a43fad 100644 --- a/lib/ormconfig.ts +++ b/lib/ormconfig.ts @@ -13,11 +13,11 @@ const content = { username: env.db.username, password: env.db.password, database: env.db.database, - entities: env.db.entities, - migrations: env.db.migrations, + entities: env.app.dirs.entities, + migrations: env.app.dirs.migrations, cli: { - migrationsDir: env.db.migrationsDir - } + migrationsDir: env.app.dirs.migrationsDir, + }, }; const filePath = path.join(__dirname, '../', 'ormconfig.json'); diff --git a/ormconfig.json b/ormconfig.json deleted file mode 100644 index 8b40a6a2..00000000 --- a/ormconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "mysql", - "host": "localhost", - "port": 3306, - "username": "root", - "password": "", - "database": "my_database", - "entities": [ - "src/api/**/*Models.ts" - ], - "migrations": [ - "src/database/migrations/*.ts" - ], - "cli": { - "migrationsDir": "src/database/migrations" - } -} diff --git a/src/core/env.ts b/src/core/env.ts index deb2ba88..87ae1e6e 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import * as pkg from '../../package.json'; import * as dotenv from 'dotenv'; dotenv.config(); @@ -19,6 +20,14 @@ export const env = { error: { printStackCode: toNumber(getOsEnv('APP_ERROR_PRINTSTACK_CODE')), }, + dirs: { + entities: [path.join(__dirname, '..', 'api/models/*{.js,.ts}')], + migrations: [path.join(__dirname, '..', 'database/migrations/*.ts')], + migrationsDir: path.join(__dirname, '..', 'database/migrations'), + controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')], + middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')], + interceptors: [path.join(__dirname, '..', 'api/**/*Interceptor{.js,.ts}')], + }, }, log: { level: getOsEnv('LOG_LEVEL'), @@ -37,9 +46,6 @@ export const env = { database: getOsEnv('DB_DATABASE'), synchronize: toBool(getOsEnv('DB_SYNCHRONIZE')), logging: toBool(getOsEnv('DB_LOGGING')), - entities: toArray(getOsEnv('DB_ENTITIES')), - migrations: toArray(getOsEnv('DB_MIGRATIONS')), - migrationsDir: getOsEnv('DB_MIGRATIONS_DIR'), }, swagger: { enabled: toBool(getOsEnv('SWAGGER_ENABLED')), @@ -68,13 +74,6 @@ function toBool(value: string): boolean { return value === 'true'; } -function toArray(value: string): string[] { - if (value && value.indexOf(',') >= 0) { - return value.split(','); - } - return [value]; -} - function normalizePort(port: string): number | string | boolean { const parsedPort = parseInt(port, 10); if (isNaN(parsedPort)) { // named pipe diff --git a/src/loaders/expressLoader.ts b/src/loaders/expressLoader.ts index 4933dba3..9c53d3ca 100644 --- a/src/loaders/expressLoader.ts +++ b/src/loaders/expressLoader.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import { createExpressServer } from 'routing-controllers'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; @@ -23,9 +22,9 @@ export const expressLoader: MicroframeworkLoader = (settings: MicroframeworkSett * We can add options about how routing-controllers should configure itself. * Here we specify what controllers should be registered in our express server. */ - controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')], - middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')], - interceptors: [path.join(__dirname, '..', 'api/**/*Interceptor{.js,.ts}')], + controllers: env.app.dirs.controllers, + middlewares: env.app.dirs.middlewares, + interceptors: env.app.dirs.interceptors, /** * Authorization features diff --git a/src/loaders/typeormLoader.ts b/src/loaders/typeormLoader.ts index 6dd6645a..6d6871fc 100644 --- a/src/loaders/typeormLoader.ts +++ b/src/loaders/typeormLoader.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import { createConnection } from 'typeorm'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; @@ -15,9 +14,7 @@ export const typeormLoader: MicroframeworkLoader = async (settings: Microframewo database: env.db.database, synchronize: env.db.synchronize, logging: env.db.logging, - entities: [ - path.join(__dirname, '..', 'api/models/*{.js,.ts}'), - ], + entities: env.app.dirs.entities, }); if (settings) { From 3152fdaf4294e5ffa27c2d7819159ca20b219bce Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 08:53:52 +0100 Subject: [PATCH 046/104] Remove unused image --- console.png | Bin 101830 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 console.png diff --git a/console.png b/console.png deleted file mode 100644 index 7dbdb9c3a9a1c88ec498887881ca470d2ec7f2ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101830 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 From 111ec51759937c032b8e0ea4e32a5e4e034c2151 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 08:54:17 +0100 Subject: [PATCH 047/104] Add config to loader --- src/loaders/typeormLoader.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/loaders/typeormLoader.ts b/src/loaders/typeormLoader.ts index 6dd6645a..a66c1f32 100644 --- a/src/loaders/typeormLoader.ts +++ b/src/loaders/typeormLoader.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import { createConnection } from 'typeorm'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; import { env } from '../core/env'; @@ -15,9 +14,7 @@ export const typeormLoader: MicroframeworkLoader = async (settings: Microframewo database: env.db.database, synchronize: env.db.synchronize, logging: env.db.logging, - entities: [ - path.join(__dirname, '..', 'api/models/*{.js,.ts}'), - ], + entities: env.db.entities, }); if (settings) { From fcbda56eb386e325be0fe7c44ee5c9adec475c23 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 09:00:16 +0100 Subject: [PATCH 048/104] Add subscribers file pattern to the env --- src/core/env.ts | 3 ++- src/loaders/eventDispatchLoader.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/core/env.ts b/src/core/env.ts index 59f5b0b7..8348473f 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -19,9 +19,10 @@ export const env = { port: normalizePort(process.env.PORT || '3000'), banner: toBool(getOsEnv('APP_BANNER')), dirs: { - entities: [path.join(__dirname, '..', 'api/models/*{.js,.ts}')], migrations: [path.join(__dirname, '..', 'database/migrations/*.ts')], migrationsDir: path.join(__dirname, '..', 'database/migrations'), + entities: [path.join(__dirname, '..', 'api/**/*Model{.js,.ts}')], + subscribers: [ path.join(__dirname, '..', 'api/**/*Subscriber{.js,.ts}')], controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')], middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')], interceptors: [path.join(__dirname, '..', 'api/**/*Interceptor{.js,.ts}')], diff --git a/src/loaders/eventDispatchLoader.ts b/src/loaders/eventDispatchLoader.ts index ec8bf077..954e14a2 100644 --- a/src/loaders/eventDispatchLoader.ts +++ b/src/loaders/eventDispatchLoader.ts @@ -1,6 +1,6 @@ -import * as path from 'path'; import * as glob from 'glob'; import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; +import { env } from '../core/env'; /** @@ -11,11 +11,13 @@ import { MicroframeworkSettings, MicroframeworkLoader } from 'microframework'; */ export const eventDispatchLoader: MicroframeworkLoader = (settings: MicroframeworkSettings | undefined) => { if (settings) { - const filePath = path.join(__dirname, '..', 'api/**/*Subscriber{.js,.ts}'); - glob(filePath, (err: any, files: string[]) => { - for (const file of files) { - require(file); - } + const patterns = env.app.dirs.subscribers; + patterns.forEach((pattern) => { + glob(pattern, (err: any, files: string[]) => { + for (const file of files) { + require(file); + } + }); }); } }; From a4d514fb54bf0efb014e15070a8057d3bf0ff828 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 09:02:37 +0100 Subject: [PATCH 049/104] Add event-dispatch to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d8a2cf4e..92f8da2d 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor). - **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest) - **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/) +- **Easy event dispatching** thanks to [event-dispatch](https://github.com/pleerock/event-dispatch) ### Comming soon @@ -159,6 +160,7 @@ The swagger and the monitor route can be altered in the `.env` file. | **src/api/models/** | Bookshelf Models | | **src/api/repositories/** | Repository / DB layer | | **src/api/services/** | Service layer | +| **src/api/subscribers/** | Event subscribers | | **src/api/validators/** | Custom validators, which can be used in the request classes | | **src/api/** swagger.json | Swagger documentation | | **src/console/** | Command line scripts | From 870496b56384f8371976d0c03892c904b1438625 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 09:12:09 +0100 Subject: [PATCH 050/104] Fix auth-checker for auth0 --- src/auth/currentUserChecker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts index fcb5fa2a..3cb8b254 100644 --- a/src/auth/currentUserChecker.ts +++ b/src/auth/currentUserChecker.ts @@ -16,7 +16,7 @@ export function currentUserChecker(connection: Connection): (action: Action) => const em = connection.createEntityManager(); const user = await em.findOne(User, { where: { - email: tokeninfo.user_id, + email: tokeninfo.user_id.replace('auth0|', ''), }, }); if (user) { From 21e7003b513b22b71a02233cb62cb61e06f8ec89 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 09:24:49 +0100 Subject: [PATCH 051/104] Add user validation unit tests --- test/unit/validations/UserValidations.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/unit/validations/UserValidations.test.ts diff --git a/test/unit/validations/UserValidations.test.ts b/test/unit/validations/UserValidations.test.ts new file mode 100644 index 00000000..b6c0d399 --- /dev/null +++ b/test/unit/validations/UserValidations.test.ts @@ -0,0 +1,44 @@ +import { validate } from 'class-validator'; +import { User } from './../../../src/api/models/User'; + + +describe('UserValidations', () => { + + test('User should always have a first name', async (done) => { + const user = new User(); + const errorsOne = await validate(user); + user.firstName = 'TestName'; + const errorsTwo = await validate(user); + expect(errorsOne.length).toBeGreaterThan(errorsTwo.length); + done(); + }); + + test('User should always have a last name', async (done) => { + const user = new User(); + const errorsOne = await validate(user); + user.lastName = 'TestName'; + const errorsTwo = await validate(user); + expect(errorsOne.length).toBeGreaterThan(errorsTwo.length); + done(); + }); + + test('User should always have a email', async (done) => { + const user = new User(); + const errorsOne = await validate(user); + user.email = 'test@test.com'; + const errorsTwo = await validate(user); + expect(errorsOne.length).toBeGreaterThan(errorsTwo.length); + done(); + }); + + test('User validation should succeed with all required fields', async (done) => { + const user = new User(); + user.firstName = 'TestName'; + user.lastName = 'TestName'; + user.email = 'test@test.com'; + const errors = await validate(user); + expect(errors.length).toEqual(0); + done(); + }); + +}); From 88b6914deefb729140c8a5aa2c310486a6eadb30 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 09:47:42 +0100 Subject: [PATCH 052/104] Add basic auth-service unit-tests --- package.json | 1 + test/unit/auth/AuthService.test.ts | 41 ++++++++++++++++++++++++++++++ yarn.lock | 30 ++++++++++++++++++---- 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 test/unit/auth/AuthService.test.ts diff --git a/package.json b/package.json index f51a2dbb..2cdcf05c 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "jsonfile": "^4.0.0", "lodash": "^4.17.4", "microframework": "^0.6.4", + "mock-express-request": "^0.2.0", "morgan": "^1.9.0", "mysql": "^2.15.0", "nodemon": "^1.12.1", diff --git a/test/unit/auth/AuthService.test.ts b/test/unit/auth/AuthService.test.ts new file mode 100644 index 00000000..668a4f4b --- /dev/null +++ b/test/unit/auth/AuthService.test.ts @@ -0,0 +1,41 @@ +import { Request } from 'express'; +import * as MockExpressRequest from 'mock-express-request'; +import { AuthService } from './../../../src/auth/AuthService'; + + +describe('AuthService', () => { + + let authService: AuthService; + beforeEach(() => { + authService = new AuthService(); + }); + + describe('parseTokenFromRequest', () => { + test('Should return the token without Bearer', () => { + const req: Request = new MockExpressRequest({ + headers: { + Authorization: 'Bearer 1234', + }, + }); + const token = authService.parseTokenFromRequest(req); + expect(token).toBe('1234'); + }); + + test('Should return undefined if there is no Bearer', () => { + const req: Request = new MockExpressRequest({ + headers: { + Authorization: 'Basic 1234', + }, + }); + const token = authService.parseTokenFromRequest(req); + expect(token).toBeUndefined(); + }); + + test('Should return undefined if there is no "Authorization" header', () => { + const req: Request = new MockExpressRequest(); + const token = authService.parseTokenFromRequest(req); + expect(token).toBeUndefined(); + }); + }); + +}); diff --git a/yarn.lock b/yarn.lock index 73b253d6..252ab557 100644 --- a/yarn.lock +++ b/yarn.lock @@ -128,7 +128,7 @@ accepts@1.3.3: mime-types "~2.1.11" negotiator "0.6.1" -accepts@~1.3.4: +accepts@^1.3.2, accepts@~1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" dependencies: @@ -1664,6 +1664,10 @@ fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" +fresh@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" + from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" @@ -2887,7 +2891,7 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" -lodash@^4.14.0, lodash@^4.17.4, lodash@^4.5.1: +lodash@^4.14.0, lodash@^4.17.0, lodash@^4.17.4, lodash@^4.5.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3058,6 +3062,22 @@ minimist@~0.0.1: dependencies: minimist "0.0.8" +mock-express-request@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/mock-express-request/-/mock-express-request-0.2.0.tgz#fe24ccf43692bcb75bf971abd19aa23d8b42625c" + dependencies: + accepts "^1.3.2" + fresh "^0.3.0" + lodash "^4.17.0" + mock-req "^0.2.0" + parseurl "^1.3.1" + range-parser "^1.0.3" + type-is "^1.6.12" + +mock-req@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/mock-req/-/mock-req-0.2.0.tgz#749446804d2c006169342ee7be6bba1cffd534c2" + moment@2.x.x: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" @@ -3461,7 +3481,7 @@ parseuri@0.0.5: dependencies: better-assert "~1.0.0" -parseurl@~1.3.2: +parseurl@^1.3.1, parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -3662,7 +3682,7 @@ randomatic@^1.1.3: is-number "^3.0.0" kind-of "^4.0.0" -range-parser@~1.2.0: +range-parser@^1.0.3, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" @@ -4592,7 +4612,7 @@ 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.12, type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" dependencies: From 6db97bb0b411575f7757c95cbcb118e428982914 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 09:49:00 +0100 Subject: [PATCH 053/104] Get rid off old e2e tests --- test/e2e/.gitkeep | 0 test/e2e/API-Info.test.ts | 16 ---- test/e2e/User.test.ts | 134 --------------------------------- test/e2e/lib/ApiResponeTest.ts | 56 -------------- test/e2e/lib/api.ts | 39 ---------- test/e2e/lib/auth.ts | 29 ------- 6 files changed, 274 deletions(-) create mode 100644 test/e2e/.gitkeep delete mode 100644 test/e2e/API-Info.test.ts delete mode 100644 test/e2e/User.test.ts delete mode 100644 test/e2e/lib/ApiResponeTest.ts delete mode 100644 test/e2e/lib/api.ts delete mode 100644 test/e2e/lib/auth.ts diff --git a/test/e2e/.gitkeep b/test/e2e/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/e2e/API-Info.test.ts b/test/e2e/API-Info.test.ts deleted file mode 100644 index b54417fe..00000000 --- a/test/e2e/API-Info.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { api } from './lib/api'; - - -describe('API-Info', () => { - test('GET /info Should return the api info as a json', async () => { - const res = await api('GET', '/api/info'); - res.expectJson(); - res.expectStatusCode(200); - - const body = res.getBody(); - const pkg = require('../../package.json'); - expect(body.name).toBe(pkg.name); - expect(body.version).toBe(pkg.version); - expect(body.description).toBe(pkg.description); - }); -}); diff --git a/test/e2e/User.test.ts b/test/e2e/User.test.ts deleted file mode 100644 index 93dbdeb1..00000000 --- a/test/e2e/User.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { api } from './lib/api'; -import { DatabaseResetCommand } from '../../src/console/DatabaseResetCommand'; -import { createAdminUser, getToken } from './lib/auth'; - - -describe('/users', () => { - - const userKeys = ['id', 'firstName', 'lastName', 'email', 'picture', 'auth0UserId', 'updatedAt', 'createdAt']; - - const testUser = { - firstName: 'Hans', - lastName: 'Muster', - email: 'hans@gmail.com', - auth0UserId: '1234' - }; - - const testUserUpdated = { - firstName: 'Horst', - lastName: 'Maier', - email: 'horst@gmail.com' - }; - - let token; - let auth; - let createdId; - beforeAll(async () => { - const command = new DatabaseResetCommand(); - await command.run(); - await createAdminUser(); - token = getToken(); - auth = { - token - }; - }); - - test('POST /users Should create a new user', async () => { - const res = await api('POST', '/api/users', { - token, - body: testUser - }); - res.expectJson(); - res.expectStatusCode(201); - res.expectData(userKeys); - createdId = res.getData()['id']; - }); - - test('POST /users Should fail because we want to create a empty user', async () => { - const res = await api('POST', '/api/users', { - token, - body: {} - }); - res.expectJson(); - res.expectStatusCode(400); - }); - - test('GET /users Should list of users with our new create one', async () => { - const res = await api('GET', '/api/users', auth); - res.expectJson(); - res.expectStatusCode(200); - res.expectData(userKeys); - const data = res.getData(); - expect(data.length).toBe(2); - - const user = data[1]; - expect(user.firstName).toBe(testUser.firstName); - expect(user.lastName).toBe(testUser.lastName); - expect(user.email).toBe(testUser.email); - }); - - test('GET /users/:id Should return one user', async () => { - const res = await api('GET', `/api/users/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(200); - res.expectData(userKeys); - - const user: any = res.getData(); - expect(user.firstName).toBe(testUser.firstName); - expect(user.lastName).toBe(testUser.lastName); - expect(user.email).toBe(testUser.email); - }); - - test('PUT /users/:id Should update the user', async () => { - const res = await api('PUT', `/api/users/${createdId}`, { - token, - body: testUserUpdated - }); - res.expectJson(); - res.expectStatusCode(200); - res.expectData(userKeys); - - const user: any = res.getData(); - expect(user.firstName).toBe(testUserUpdated.firstName); - expect(user.lastName).toBe(testUserUpdated.lastName); - expect(user.email).toBe(testUserUpdated.email); - }); - - test('PUT /users/:id Should fail because we want to update the user with a invalid email', async () => { - const res = await api('PUT', `/api/users/${createdId}`, { - token, - body: { - email: 'abc' - } - }); - res.expectJson(); - res.expectStatusCode(400); - }); - - test('DELETE /users/:id Should delete the user', async () => { - const res = await api('DELETE', `/api/users/${createdId}`, auth); - res.expectStatusCode(200); - }); - - /** - * 404 - NotFound Testing - */ - test('GET /users/:id Should return with a 404, because we just deleted the user', async () => { - const res = await api('GET', `/api/users/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(404); - }); - - test('DELETE /users/:id Should return with a 404, because we just deleted the user', async () => { - const res = await api('DELETE', `/api/users/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(404); - }); - - test('PUT /users/:id Should return with a 404, because we just deleted the user', async () => { - const res = await api('PUT', `/api/users/${createdId}`, auth); - res.expectJson(); - res.expectStatusCode(404); - }); - -}); diff --git a/test/e2e/lib/ApiResponeTest.ts b/test/e2e/lib/ApiResponeTest.ts deleted file mode 100644 index b3a57a3c..00000000 --- a/test/e2e/lib/ApiResponeTest.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as _ from 'lodash'; - - -export class ApiResponeTest { - - constructor(private error: any, private res: any) { - } - - public getBody(): T { - return this.res['body']; - } - - public getData(): T { - return this.getBody()['data']; - } - - public getHeaders(): T { - if (this.res) { - return this.res['headers']; - } else { - return this.error['response']['headers']; - } - } - - public expectStatusCode(code: number): ApiResponeTest { - if (this.res) { - expect(this.res['statusCode']).toBe(code); - } else { - expect(this.error['statusCode']).toBe(code); - } - return this; - } - - public expectJson(): ApiResponeTest { - expect(this.getHeaders()['content-type']).toContain('json'); - return this; - } - - public expectData(keys: string[]): ApiResponeTest { - const a = keys.sort(); - const d = _.isArray(this.getData()) ? this.getData()[0] : this.getData(); - const b = Object.keys(d).sort(); - expect(_.isEqual(a, b)).toBeTruthy(); - expect(this.getBody()['success']).toBeTruthy(); - return this; - } - - public printResponse(): void { - console.log(this.res); - } - - public printError(): void { - console.log(this.error); - } - -} diff --git a/test/e2e/lib/api.ts b/test/e2e/lib/api.ts deleted file mode 100644 index 5ed531e6..00000000 --- a/test/e2e/lib/api.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as dotenv from 'dotenv'; -dotenv.config(); -import * as request from 'request-promise'; -import { Options } from 'request-promise'; -import { ApiResponeTest } from './ApiResponeTest'; - - -export interface ApiOptions { - token?: string; - body?: T; - headers?: any; -} - - -export const api = async (method: string, path: string, options: ApiOptions = {}) => { - const o: Options = { - method, - uri: `${process.env.APP_HOST}:${process.env.APP_PORT}${path}`, - resolveWithFullResponse: true, - headers: options.headers, - json: true, - body: options.body - }; - - if (options.token) { - o.headers = {}; - o.headers['authorization'] = `Bearer ${options.token}`; - } - - let res; - let error = null; - try { - res = await request(o); - } catch (e) { - error = e; - } - - return new ApiResponeTest(error, res); -}; diff --git a/test/e2e/lib/auth.ts b/test/e2e/lib/auth.ts deleted file mode 100644 index 4e3369bf..00000000 --- a/test/e2e/lib/auth.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as jwt from 'jsonwebtoken'; -import { Knex } from '../../../src/config/Database'; -import { Tables } from '../../../src/constants/Tables'; - - -export const USER = { - firstName: 'Bruce', - lastName: 'Wayne', - email: 'bw@enterprice.com', - auth0UserId: 'batman' -}; - -export const createAdminUser = async () => { - const knex = Knex(); - const user = await knex(Tables.Users).insert({ - first_name: USER.firstName, - last_name: USER.lastName, - email: USER.email, - auth_0_user_id: USER.auth0UserId - }); - await knex.destroy(); - return user; -}; - -export const getToken = (auth0UserId?: string) => { - return jwt.sign({ - user_id: `auth0|${auth0UserId || USER.auth0UserId}` - }, 'auth0-mock'); -}; From dca34890ffa79411ab2a490bf6d37c8e8ec12082 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 10:13:21 +0100 Subject: [PATCH 054/104] Fix model path --- src/core/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/env.ts b/src/core/env.ts index 8348473f..266dc10c 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -21,7 +21,7 @@ export const env = { dirs: { migrations: [path.join(__dirname, '..', 'database/migrations/*.ts')], migrationsDir: path.join(__dirname, '..', 'database/migrations'), - entities: [path.join(__dirname, '..', 'api/**/*Model{.js,.ts}')], + entities: [path.join(__dirname, '..', 'api/**/models/*{.js,.ts}')], subscribers: [ path.join(__dirname, '..', 'api/**/*Subscriber{.js,.ts}')], controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')], middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')], From 498896541a7a0d3f6312577a31686f49f12b2d69 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 10:39:52 +0100 Subject: [PATCH 055/104] Add AuthService test --- package.json | 1 + src/auth/AuthService.ts | 11 ++++-- test/unit/auth/AuthService.test.ts | 32 +++++++++++++++++- yarn.lock | 54 ++++++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2cdcf05c..1a600af5 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "mock-express-request": "^0.2.0", "morgan": "^1.9.0", "mysql": "^2.15.0", + "nock": "^9.1.0", "nodemon": "^1.12.1", "path": "^0.12.7", "reflect-metadata": "^0.1.10", diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index c6a8b5e1..72926554 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -1,6 +1,6 @@ import * as request from 'request'; import * as express from 'express'; -import { Service } from 'typedi'; +import { Service, Require } from 'typedi'; import { env } from '../core/env'; import { ITokenInfo } from './ITokenInfo'; @@ -8,6 +8,13 @@ import { ITokenInfo } from './ITokenInfo'; @Service() export class AuthService { + // private httpRequest = request; + private httpRequest: typeof request; + + constructor( @Require('request') r: any) { + this.httpRequest = r; // the same if you do this.logger = require("logger") + } + public parseTokenFromRequest(req: express.Request): string | undefined { const authorization = req.header('authorization'); @@ -22,7 +29,7 @@ export class AuthService { public getTokenInfo(token: string): Promise { return new Promise((resolve, reject) => { - request({ + this.httpRequest({ method: 'POST', url: env.auth.route, form: { diff --git a/test/unit/auth/AuthService.test.ts b/test/unit/auth/AuthService.test.ts index 668a4f4b..3a70b34a 100644 --- a/test/unit/auth/AuthService.test.ts +++ b/test/unit/auth/AuthService.test.ts @@ -1,13 +1,16 @@ import { Request } from 'express'; +import * as request from 'request'; import * as MockExpressRequest from 'mock-express-request'; +import * as nock from 'nock'; import { AuthService } from './../../../src/auth/AuthService'; +import { env } from './../../../src/core/env'; describe('AuthService', () => { let authService: AuthService; beforeEach(() => { - authService = new AuthService(); + authService = new AuthService(request); }); describe('parseTokenFromRequest', () => { @@ -38,4 +41,31 @@ describe('AuthService', () => { }); }); + describe('getTokenInfo', () => { + test('Should get the tokeninfo', async (done) => { + nock(env.auth.route) + .post('') + .reply(200, { + user_id: 'auth0|test@test.com', + }); + + const tokeninfo = await authService.getTokenInfo('1234'); + expect(tokeninfo.user_id).toBe('auth0|test@test.com'); + done(); + }); + + test('Should fail due to invalid token', async (done) => { + nock(env.auth.route) + .post('') + .reply(401, 'Invalid token'); + + try { + await authService.getTokenInfo('1234'); + } catch (error) { + expect(error).toBe('Invalid token'); + } + done(); + }); + }); + }); diff --git a/yarn.lock b/yarn.lock index 252ab557..a2a60fa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -341,6 +341,10 @@ assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -759,6 +763,14 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +"chai@>=1.9.2 <4.0.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" + dependencies: + assertion-error "^1.0.1" + deep-eql "^0.1.3" + type-detect "^1.0.0" + chalk@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" @@ -1201,6 +1213,16 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +deep-eql@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" + dependencies: + type-detect "0.1.1" + +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -2699,7 +2721,7 @@ json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -2891,7 +2913,7 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" -lodash@^4.14.0, lodash@^4.17.0, lodash@^4.17.4, lodash@^4.5.1: +lodash@^4.14.0, lodash@^4.17.0, lodash@^4.17.4, lodash@^4.5.1, lodash@~4.17.2: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3145,6 +3167,20 @@ nocache@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980" +nock@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/nock/-/nock-9.1.0.tgz#b6fd783abc1e774cb028058ea81207369a735747" + dependencies: + chai ">=1.9.2 <4.0.0" + debug "^2.2.0" + deep-equal "^1.0.0" + json-stringify-safe "^5.0.1" + lodash "~4.17.2" + mkdirp "^0.5.0" + propagate "0.4.0" + qs "^6.0.2" + semver "^5.3.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -3638,6 +3674,10 @@ process@^0.11.1: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" +propagate@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.4.0.tgz#f3fcca0a6fe06736a7ba572966069617c130b481" + proxy-addr@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" @@ -3663,7 +3703,7 @@ punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -qs@6.5.1, qs@~6.5.1: +qs@6.5.1, qs@^6.0.2, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -4608,6 +4648,14 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" + +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + type-detect@^4.0.3: version "4.0.5" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" From e4da305945002672a4b0b2b5420fd80d7241b6b0 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 10:59:57 +0100 Subject: [PATCH 056/104] Add logger decorater --- src/api/middlewares/ErrorHandlerMiddleware.ts | 4 ++-- src/api/middlewares/LogMiddleware.ts | 4 ++-- src/api/services/UserService.ts | 10 +++++++++- src/api/subscribers/UserEventSubscriber.ts | 4 ++-- src/app.ts | 4 ++-- src/auth/AuthService.ts | 13 +++++++++---- src/auth/authorizationChecker.ts | 4 ++-- src/auth/currentUserChecker.ts | 4 ++-- src/core/ILogger.ts | 6 ++++++ src/core/{Log.ts => Logger.ts} | 4 ++-- src/core/banner.ts | 4 ++-- src/decorators/Logger.ts | 10 ++++++++++ test/unit/auth/AuthService.test.ts | 9 ++++++++- 13 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 src/core/ILogger.ts rename src/core/{Log.ts => Logger.ts} (94%) create mode 100644 src/decorators/Logger.ts diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index 23c916d8..997b21e2 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -1,8 +1,8 @@ import * as express from 'express'; import { Middleware, ExpressErrorMiddlewareInterface, HttpError } from 'routing-controllers'; import { env } from '../../core/env'; -import { Log } from '../../core/Log'; -const log = new Log(__filename); +import { Logger } from '../../core/Logger'; +const log = new Logger(__filename); @Middleware({ type: 'after' }) diff --git a/src/api/middlewares/LogMiddleware.ts b/src/api/middlewares/LogMiddleware.ts index 30293edf..02bf97da 100644 --- a/src/api/middlewares/LogMiddleware.ts +++ b/src/api/middlewares/LogMiddleware.ts @@ -2,14 +2,14 @@ import * as express from 'express'; import * as morgan from 'morgan'; import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -import { Log } from '../../core/Log'; +import { Logger } from '../../core/Logger'; import { env } from '../../core/env'; @Middleware({ type: 'before' }) export class SecurityMiddleware implements ExpressMiddlewareInterface { - private log = new Log(__dirname); + private log = new Logger(__dirname); public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return morgan(env.log.output, { diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index 02bfb2aa..beac52b4 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -4,24 +4,30 @@ import { EventDispatcher } from 'event-dispatch'; import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; import { events } from '../subscribers/events'; +import { Logger } from '../../decorators/Logger'; +import { ILogger } from '../../core/ILogger'; @Service() export class UserService { constructor( - @OrmRepository() private userRepository: UserRepository + @OrmRepository() private userRepository: UserRepository, + @Logger(__filename) private log: ILogger ) { } public find(): Promise { + this.log.info('Find all users'); return this.userRepository.find(); } public findOne(id: string): Promise { + this.log.info('Find all users'); return this.userRepository.findOne({ id }); } public async create(user: User): Promise { + this.log.info('Create a new user => ', user.toString()); const newUser = await this.userRepository.save(user); const eventDispatcher = new EventDispatcher(); eventDispatcher.dispatch(events.user.created, newUser); @@ -29,11 +35,13 @@ export class UserService { } public update(id: string, user: User): Promise { + this.log.info('Update a user'); user.id = id; return this.userRepository.save(user); } public delete(id: string): Promise { + this.log.info('Delete a user'); return this.userRepository.removeById(id); } diff --git a/src/api/subscribers/UserEventSubscriber.ts b/src/api/subscribers/UserEventSubscriber.ts index f7864d08..c29a9cd8 100644 --- a/src/api/subscribers/UserEventSubscriber.ts +++ b/src/api/subscribers/UserEventSubscriber.ts @@ -1,8 +1,8 @@ import { EventSubscriber, On } from 'event-dispatch'; import { User } from '../models/User'; import { events } from './events'; -import { Log } from '../../core/Log'; -const log = new Log(__filename); +import { Logger } from '../../core/Logger'; +const log = new Logger(__filename); @EventSubscriber() diff --git a/src/app.ts b/src/app.ts index 873e1880..86846c6d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,8 +8,8 @@ */ import 'reflect-metadata'; import { banner } from './core/banner'; -import { Log } from './core/Log'; -const log = new Log(__filename); +import { Logger } from './core/Logger'; +const log = new Logger(__filename); import { bootstrapMicroframework } from 'microframework'; import { expressLoader } from './loaders/expressLoader'; diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index 72926554..7e0bcc53 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -3,16 +3,20 @@ import * as express from 'express'; import { Service, Require } from 'typedi'; import { env } from '../core/env'; import { ITokenInfo } from './ITokenInfo'; +import { Logger } from '../decorators/Logger'; +import { ILogger } from '../core/ILogger'; @Service() export class AuthService { - // private httpRequest = request; private httpRequest: typeof request; - constructor( @Require('request') r: any) { - this.httpRequest = r; // the same if you do this.logger = require("logger") + constructor( + @Require('request') r: any, + @Logger(__filename) private log: ILogger + ) { + this.httpRequest = r; } public parseTokenFromRequest(req: express.Request): string | undefined { @@ -20,10 +24,11 @@ export class AuthService { // Retrieve the token form the Authorization header if (authorization && authorization.split(' ')[0] === 'Bearer') { + this.log.info('Token provided by the client'); return authorization.split(' ')[1]; } - // No token was provided by the client + this.log.info('No Token provided by the client'); return; } diff --git a/src/auth/authorizationChecker.ts b/src/auth/authorizationChecker.ts index bb850ef4..58c96ad0 100644 --- a/src/auth/authorizationChecker.ts +++ b/src/auth/authorizationChecker.ts @@ -2,11 +2,11 @@ import { Action } from 'routing-controllers'; import { Container } from 'typedi'; import { Connection } from 'typeorm'; import { AuthService } from './AuthService'; -import { Log } from '../core/Log'; +import { Logger } from '../core/Logger'; export function authorizationChecker(connection: Connection): (action: Action, roles: any[]) => Promise | boolean { - const log = new Log(__filename); + const log = new Logger(__filename); const authService = Container.get(AuthService); return async function innerAuthorizationChecker(action: Action, roles: string[]): Promise { diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts index 3cb8b254..2cf978bd 100644 --- a/src/auth/currentUserChecker.ts +++ b/src/auth/currentUserChecker.ts @@ -1,12 +1,12 @@ import { Action } from 'routing-controllers'; import { User } from '../api/models/User'; -import { Log } from '../core/Log'; +import { Logger } from '../core/Logger'; import { ITokenInfo } from './ITokenInfo'; import { Connection } from 'typeorm'; export function currentUserChecker(connection: Connection): (action: Action) => Promise { - const log = new Log(__filename); + const log = new Logger(__filename); return async function innerCurrentUserChecker(action: Action): Promise { // here you can use request/response objects from action diff --git a/src/core/ILogger.ts b/src/core/ILogger.ts new file mode 100644 index 00000000..44617b39 --- /dev/null +++ b/src/core/ILogger.ts @@ -0,0 +1,6 @@ +export interface ILogger { + debug(message: string, ...args: any[]): void; + info(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + error(message: string, ...args: any[]): void; +} diff --git a/src/core/Log.ts b/src/core/Logger.ts similarity index 94% rename from src/core/Log.ts rename to src/core/Logger.ts index dbb662e9..ba26f213 100644 --- a/src/core/Log.ts +++ b/src/core/Logger.ts @@ -12,7 +12,7 @@ import * as winston from 'winston'; * this in the start up process in the core/index.ts file. */ -export class Log { +export class Logger { public static DEFAULT_SCOPE = 'app'; @@ -31,7 +31,7 @@ export class Log { private scope: string; constructor(scope?: string) { - this.scope = Log.parsePathToScope((scope) ? scope : Log.DEFAULT_SCOPE); + this.scope = Logger.parsePathToScope((scope) ? scope : Logger.DEFAULT_SCOPE); } public debug(message: string, ...args: any[]): void { diff --git a/src/core/banner.ts b/src/core/banner.ts index ddf00453..ef494f10 100644 --- a/src/core/banner.ts +++ b/src/core/banner.ts @@ -1,8 +1,8 @@ -import { Log } from './Log'; +import { Logger } from './Logger'; import { env } from './env'; -export function banner(log: Log): void { +export function banner(log: Logger): void { if (env.app.banner) { log.info(``); log.info(`Aloha, your app is ready on ${env.app.route}${env.app.routePrefix}`); diff --git a/src/decorators/Logger.ts b/src/decorators/Logger.ts new file mode 100644 index 00000000..d54bebee --- /dev/null +++ b/src/decorators/Logger.ts @@ -0,0 +1,10 @@ +import { Container } from 'typedi'; +import { Logger as WinstonLogger } from '../core/Logger'; + + +export function Logger(scope: string): any { + return (object: any, propertyName: string, index?: number): any => { + const logger = new WinstonLogger(scope); + Container.registerHandler({ object, propertyName, index, value: () => logger }); + }; +} diff --git a/test/unit/auth/AuthService.test.ts b/test/unit/auth/AuthService.test.ts index 3a70b34a..0e9c65b6 100644 --- a/test/unit/auth/AuthService.test.ts +++ b/test/unit/auth/AuthService.test.ts @@ -3,6 +3,7 @@ import * as request from 'request'; import * as MockExpressRequest from 'mock-express-request'; import * as nock from 'nock'; import { AuthService } from './../../../src/auth/AuthService'; +import { ILogger } from './../../../src/core/ILogger'; import { env } from './../../../src/core/env'; @@ -10,7 +11,13 @@ describe('AuthService', () => { let authService: AuthService; beforeEach(() => { - authService = new AuthService(request); + const log: ILogger = { + debug: () => void 0, + info: () => void 0, + warn: () => void 0, + error: () => void 0, + }; + authService = new AuthService(request, log); }); describe('parseTokenFromRequest', () => { From 752df7c0a36f5c945b3d23e1c023efdca13c5460 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 13:01:25 +0100 Subject: [PATCH 057/104] Improve typing --- src/auth/authorizationChecker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/authorizationChecker.ts b/src/auth/authorizationChecker.ts index 58c96ad0..946607d1 100644 --- a/src/auth/authorizationChecker.ts +++ b/src/auth/authorizationChecker.ts @@ -7,7 +7,7 @@ import { Logger } from '../core/Logger'; export function authorizationChecker(connection: Connection): (action: Action, roles: any[]) => Promise | boolean { const log = new Logger(__filename); - const authService = Container.get(AuthService); + const authService = Container.get(AuthService); return async function innerAuthorizationChecker(action: Action, roles: string[]): Promise { // here you can use request/response objects from action From fa2bed8433fe855af5645f6b164a63dd73d2c884 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 13:02:01 +0100 Subject: [PATCH 058/104] Add event dispacher decorator --- src/api/services/UserService.ts | 6 +++--- src/decorators/EventDispatcher.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 src/decorators/EventDispatcher.ts diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index beac52b4..aaaf0879 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -1,9 +1,9 @@ import { Service } from 'typedi'; import { OrmRepository } from 'typeorm-typedi-extensions'; -import { EventDispatcher } from 'event-dispatch'; import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; import { events } from '../subscribers/events'; +import { EventDispatcher, IEventDispatcher } from '../../decorators/EventDispatcher'; import { Logger } from '../../decorators/Logger'; import { ILogger } from '../../core/ILogger'; @@ -13,6 +13,7 @@ export class UserService { constructor( @OrmRepository() private userRepository: UserRepository, + @EventDispatcher() private eventDispatcher: IEventDispatcher, @Logger(__filename) private log: ILogger ) { } @@ -29,8 +30,7 @@ export class UserService { public async create(user: User): Promise { this.log.info('Create a new user => ', user.toString()); const newUser = await this.userRepository.save(user); - const eventDispatcher = new EventDispatcher(); - eventDispatcher.dispatch(events.user.created, newUser); + this.eventDispatcher.dispatch(events.user.created, newUser); return newUser; } diff --git a/src/decorators/EventDispatcher.ts b/src/decorators/EventDispatcher.ts new file mode 100644 index 00000000..50bac4cc --- /dev/null +++ b/src/decorators/EventDispatcher.ts @@ -0,0 +1,12 @@ +import { Container } from 'typedi'; +import { EventDispatcher as EventDispatcherClass } from 'event-dispatch'; + + +export function EventDispatcher(): any { + return (object: any, propertyName: string, index?: number): any => { + const eventDispatcher = new EventDispatcherClass(); + Container.registerHandler({ object, propertyName, index, value: () => eventDispatcher }); + }; +} + +export { EventDispatcher as IEventDispatcher } from 'event-dispatch'; From 51c3743230344772c0ed378463d5babeb6d6b2a7 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 13:03:54 +0100 Subject: [PATCH 059/104] Improve logger decorater --- src/api/services/UserService.ts | 3 +-- src/auth/AuthService.ts | 3 +-- src/decorators/Logger.ts | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index aaaf0879..b8916656 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -4,8 +4,7 @@ import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; import { events } from '../subscribers/events'; import { EventDispatcher, IEventDispatcher } from '../../decorators/EventDispatcher'; -import { Logger } from '../../decorators/Logger'; -import { ILogger } from '../../core/ILogger'; +import { Logger, ILogger } from '../../decorators/Logger'; @Service() diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index 7e0bcc53..39e9dec4 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -3,8 +3,7 @@ import * as express from 'express'; import { Service, Require } from 'typedi'; import { env } from '../core/env'; import { ITokenInfo } from './ITokenInfo'; -import { Logger } from '../decorators/Logger'; -import { ILogger } from '../core/ILogger'; +import { Logger, ILogger } from '../decorators/Logger'; @Service() diff --git a/src/decorators/Logger.ts b/src/decorators/Logger.ts index d54bebee..23729ef6 100644 --- a/src/decorators/Logger.ts +++ b/src/decorators/Logger.ts @@ -8,3 +8,5 @@ export function Logger(scope: string): any { Container.registerHandler({ object, propertyName, index, value: () => logger }); }; } + +export { ILogger } from '../core/ILogger'; From c8eb3f38d1ce9201a945bfe084a5be5e29f25763 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 13:38:44 +0100 Subject: [PATCH 060/104] Add service unit test --- test/unit/lib/EventDispatcherMock.ts | 9 ++++++ test/unit/lib/RepositoryMock.ts | 34 ++++++++++++++++++++ test/unit/services/UserService.test.ts | 43 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 test/unit/lib/EventDispatcherMock.ts create mode 100644 test/unit/lib/RepositoryMock.ts create mode 100644 test/unit/services/UserService.test.ts diff --git a/test/unit/lib/EventDispatcherMock.ts b/test/unit/lib/EventDispatcherMock.ts new file mode 100644 index 00000000..baa640a6 --- /dev/null +++ b/test/unit/lib/EventDispatcherMock.ts @@ -0,0 +1,9 @@ +export class EventDispatcherMock { + + public dispatchMock = jest.fn(); + + public dispatch(...args: any[]): void { + this.dispatchMock(args); + } + +} diff --git a/test/unit/lib/RepositoryMock.ts b/test/unit/lib/RepositoryMock.ts new file mode 100644 index 00000000..6b11d8de --- /dev/null +++ b/test/unit/lib/RepositoryMock.ts @@ -0,0 +1,34 @@ +import { validate } from 'class-validator'; + + +export class RepositoryMock { + + public one: T; + public list: T[]; + + public findMock = jest.fn(); + public findOneMock = jest.fn(); + public saveMock = jest.fn(); + public deleteMock = jest.fn(); + + public find(...args: any[]): T[] { + this.findMock(args); + return this.list; + } + + public findOne(...args: any[]): T { + this.findOneMock(args); + return this.one; + } + + public save(value: T, ...args: any[]): T { + this.saveMock(value, args); + return value; + } + + public delete(value: T, ...args: any[]): T { + this.deleteMock(value, args); + return value; + } + +} diff --git a/test/unit/services/UserService.test.ts b/test/unit/services/UserService.test.ts new file mode 100644 index 00000000..ef55afc1 --- /dev/null +++ b/test/unit/services/UserService.test.ts @@ -0,0 +1,43 @@ +import { UserService } from '../../../src/api/services/UserService'; +import { UserRepository } from '../../../src/api/repositories/UserRepository'; +import { User } from '../../../src/api/models/User'; +import { events } from '../../../src/api/subscribers/events'; +import { LogMock } from '../lib/LogMock'; +import { RepositoryMock } from '../lib/RepositoryMock'; +import { EventDispatcherMock } from '../lib/EventDispatcherMock'; + + +describe('UserService', () => { + + test('Find should return a list of users', async (done) => { + const log = new LogMock(); + const repo = new RepositoryMock(); + const ed = new EventDispatcherMock(); + const user = new User(); + user.id = '1'; + user.firstName = 'John'; + user.lastName = 'Doe'; + user.email = 'john.doe@test.com'; + repo.list = [user]; + const userService = new UserService(repo as any, ed as any, log); + const list = await userService.find(); + expect(list[0].firstName).toBe(user.firstName); + done(); + }); + + test('Create should dispatch subscribers', async (done) => { + const log = new LogMock(); + const repo = new RepositoryMock(); + const ed = new EventDispatcherMock(); + const user = new User(); + user.id = '1'; + user.firstName = 'John'; + user.lastName = 'Doe'; + user.email = 'john.doe@test.com'; + const userService = new UserService(repo as any, ed as any, log); + const newUser = await userService.create(user); + expect(ed.dispatchMock).toBeCalledWith([events.user.created, newUser]); + done(); + }); + +}); From ee6c0f09536183fbb076c9b4570a5dac6007f99d Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 14:16:20 +0100 Subject: [PATCH 061/104] Add middleware test --- package.json | 1 + src/api/middlewares/ErrorHandlerMiddleware.ts | 14 +- .../ErrorHandlerMiddleware.test.ts | 37 +++++ yarn.lock | 132 ++++++++++++++++-- 4 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 test/unit/middlewares/ErrorHandlerMiddleware.test.ts diff --git a/package.json b/package.json index 1a600af5..57f67a3e 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "lodash": "^4.17.4", "microframework": "^0.6.4", "mock-express-request": "^0.2.0", + "mock-express-response": "^0.2.1", "morgan": "^1.9.0", "mysql": "^2.15.0", "nock": "^9.1.0", diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index 997b21e2..76ad95c4 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -2,29 +2,31 @@ import * as express from 'express'; import { Middleware, ExpressErrorMiddlewareInterface, HttpError } from 'routing-controllers'; import { env } from '../../core/env'; import { Logger } from '../../core/Logger'; -const log = new Logger(__filename); @Middleware({ type: 'after' }) -export class CustomErrorHandler implements ExpressErrorMiddlewareInterface { +export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { + + public log = new Logger(__filename); + public isProduction = env.isProduction; public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void { res.status(error.httpCode || 500); // Standard output of an error to the user. - if (env.isProduction) { + if (this.isProduction) { res.json({ name: error.name, message: error.message, - stack: error.stack, }); - log.error(error.name, error.stack); + this.log.error(error.name, error.message); } else { res.json({ name: error.name, message: error.message, + stack: error.stack, }); - log.error(error.name, error.message); + this.log.error(error.name, error.stack); } } diff --git a/test/unit/middlewares/ErrorHandlerMiddleware.test.ts b/test/unit/middlewares/ErrorHandlerMiddleware.test.ts new file mode 100644 index 00000000..22d05c2b --- /dev/null +++ b/test/unit/middlewares/ErrorHandlerMiddleware.test.ts @@ -0,0 +1,37 @@ +import { HttpError } from 'routing-controllers'; +import * as MockExpressResponse from 'mock-express-response'; +import { ErrorHandlerMiddleware } from '../../../src/api/middlewares/ErrorHandlerMiddleware'; +import { LogMock } from '../lib/LogMock'; + + +describe('ErrorHandlerMiddleware', () => { + + test('Should not print stack out in production', () => { + const middleware = new ErrorHandlerMiddleware(); + middleware.log = new LogMock(); + middleware.isProduction = true; + + const res = new MockExpressResponse(); + const err = new HttpError(400, 'Test Error'); + middleware.error(err, undefined, res, undefined); + const json = res._getJSON(); + expect(json.name).toBe(err.name); + expect(json.message).toBe(err.message); + expect(json.stack).toBeUndefined(); + }); + + test('Should print stack out in production', () => { + const middleware = new ErrorHandlerMiddleware(); + middleware.log = new LogMock(); + middleware.isProduction = false; + + const res = new MockExpressResponse(); + const err = new HttpError(400, 'Test Error'); + middleware.error(err, undefined, res, undefined); + const json = res._getJSON(); + expect(json.name).toBe(err.name); + expect(json.message).toBe(err.message); + expect(json.stack).toBeDefined(); + }); + +}); diff --git a/yarn.lock b/yarn.lock index a2a60fa3..c0742a43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -976,7 +976,7 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -content-disposition@0.5.2: +content-disposition@0.5.2, content-disposition@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" @@ -990,7 +990,7 @@ content-type-parser@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" -content-type@~1.0.4: +content-type@^1.0.1, content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -998,7 +998,7 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" -cookie-signature@1.0.6: +cookie-signature@1.0.6, cookie-signature@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1006,6 +1006,10 @@ cookie@0.3.1, cookie@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" +cookie@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.5.tgz#6ab9948a4b1ae21952cd2588530a4722d4044d7c" + copyfiles@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" @@ -1078,6 +1082,10 @@ cpy@^4.0.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +crc@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.2.1.tgz#5d9c8fb77a245cd5eca291e5d2d005334bab0082" + create-error-class@^3.0.0, create-error-class@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" @@ -1209,6 +1217,12 @@ debug@^3.1.0: dependencies: ms "2.0.0" +debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1245,10 +1259,18 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.1, depd@~1.1.1: +depd@1.1.1, depd@^1.0.1, depd@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" +depd@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa" + +destroy@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.3.tgz#b433b4724e71fd8551d9885174851c5fc377e2c9" + destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -1315,6 +1337,10 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ee-first@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.0.tgz#6a0d7c6221e490feefd92ec3f441c9ce8cd097f4" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1378,7 +1404,11 @@ es6-promise@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" -escape-html@~1.0.3: +escape-html@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.1.tgz#181a286ead397a39a92857cfb1d43052e356bff0" + +escape-html@^1.0.1, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1417,10 +1447,16 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -etag@~1.8.1: +etag@^1.6.0, etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" +etag@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.6.0.tgz#8bcb2c6af1254c481dfc8b997c906ef4e442c207" + dependencies: + crc "3.2.1" + event-dispatch@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/event-dispatch/-/event-dispatch-0.4.1.tgz#5f9c272f5080f79d3e22827303a9fbcccd2c433e" @@ -1674,7 +1710,7 @@ form-data@~2.3.1: combined-stream "^1.0.5" mime-types "^2.1.12" -forwarded@~0.1.2: +forwarded@~0.1.0, forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -1682,6 +1718,10 @@ frameguard@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9" +fresh@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.2.4.tgz#3582499206c9723714190edd74b4604feb4a614c" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -2165,6 +2205,10 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ipaddr.js@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" + ipaddr.js@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" @@ -3052,6 +3096,10 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, dependencies: mime-db "~1.30.0" +mime@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" + mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -3096,10 +3144,34 @@ mock-express-request@^0.2.0: range-parser "^1.0.3" type-is "^1.6.12" +mock-express-response@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/mock-express-response/-/mock-express-response-0.2.1.tgz#bf0e23c2cd29889dab9d2bad66e6e4011d40d6ec" + dependencies: + content-disposition "^0.5.0" + content-type "^1.0.1" + cookie "^0.1.3" + cookie-signature "^1.0.6" + depd "^1.0.1" + escape-html "^1.0.1" + etag "^1.6.0" + mock-express-request "^0.2.0" + mock-res "^0.4.1" + on-finished "^2.2.1" + proxy-addr "^1.0.8" + qs "^2.4.2" + send "^0.12.3" + utils-merge "^1.0.0" + vary "^1.0.0" + mock-req@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/mock-req/-/mock-req-0.2.0.tgz#749446804d2c006169342ee7be6bba1cffd534c2" +mock-res@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/mock-res/-/mock-res-0.4.1.tgz#d3004ea770ae0faec45b6594cc9500709809ec61" + moment@2.x.x: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" @@ -3120,6 +3192,10 @@ mount-point@^1.0.0: dependencies: "@sindresorhus/df" "^1.0.1" +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3344,12 +3420,18 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -on-finished@~2.3.0: +on-finished@^2.2.1, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" dependencies: ee-first "1.1.1" +on-finished@~2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.2.1.tgz#5c85c1cc36299f78029653f667f27b6b99ebc029" + dependencies: + ee-first "1.1.0" + on-headers@^1.0.1, on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" @@ -3678,6 +3760,13 @@ propagate@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.4.0.tgz#f3fcca0a6fe06736a7ba572966069617c130b481" +proxy-addr@^1.0.8: + version "1.1.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" + dependencies: + forwarded "~0.1.0" + ipaddr.js "1.4.0" + proxy-addr@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" @@ -3707,6 +3796,10 @@ qs@6.5.1, qs@^6.0.2, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-2.4.2.tgz#f7ce788e5777df0b5010da7f7c4e73ba32470f5a" + qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -3726,6 +3819,10 @@ range-parser@^1.0.3, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" +range-parser@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.0.3.tgz#6872823535c692e2c2a0103826afd82c2e0ff175" + raw-body@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" @@ -4064,6 +4161,21 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" +send@^0.12.3: + version "0.12.3" + resolved "https://registry.yarnpkg.com/send/-/send-0.12.3.tgz#cd12dc58fde21e4f91902b39b2fda05a7a6d9bdc" + dependencies: + debug "~2.2.0" + depd "~1.0.1" + destroy "1.0.3" + escape-html "1.0.1" + etag "~1.6.0" + fresh "0.2.4" + mime "1.3.4" + ms "0.7.1" + on-finished "~2.2.1" + range-parser "~1.0.2" + serve-favicon@^2.4.5: version "2.4.5" resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.4.5.tgz#49d9a46863153a9240691c893d2b0e7d85d6d436" @@ -4804,7 +4916,7 @@ util@^0.10.3: dependencies: inherits "2.0.1" -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -4837,7 +4949,7 @@ validator@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/validator/-/validator-7.2.0.tgz#a63dcbaba51d4350bf8df20988e0d5a54d711791" -vary@^1, vary@~1.1.2: +vary@^1, vary@^1.0.0, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" From 455002ad39878eca9be8d5595135c5f7fee10ca7 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Mon, 20 Nov 2017 17:47:36 +0100 Subject: [PATCH 062/104] Improve our documentation --- README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 92f8da2d..b7ea59e6 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ [![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 with NodeJs & TypeScript. -> An Node.js Web-Serice boilerplate/skeleton/starter-kit featuring +> 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), @@ -35,20 +36,33 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **Clear Structure** with different layers such as controllers, services, repositories, models, middlewares... - **Easy Exception Handling** thanks to [routing-controllers](https://github.com/pleerock/routing-controllers). - **Smart Validation** thanks to [class-validator](https://github.com/pleerock/class-validator) with some nice annotations. -- **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes) +- **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes). - **API Documentation** thanks to [swagger](http://swagger.io/). - **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor). -- **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest) -- **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/) -- **Easy event dispatching** thanks to [event-dispatch](https://github.com/pleerock/event-dispatch) +- **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest). +- **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/). +- **Easy event dispatching** thanks to [event-dispatch](https://github.com/pleerock/event-dispatch). +- **Fast Database Building** with simple migration from [TypeOrm](https://github.com/typeorm/typeorm). ### Comming soon -- **Fast Database Building** with simple migration and seeding from [Knex](http://knexjs.org/). - **Easy Data Seeding** with our own factories. - **Custom Commands** are also available in our setup and really easy to use or even extend. - **Scaffolding Commands** will speed up your development tremendously as you should focus on business code and not scaffolding. +# Table of Contents + +- [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) +- [Further Documentations](#further-documentation) +- [Related Projects](#related-projects) +- [License](#license) + ## Getting Started ### Step 1: Set up the Development Environment @@ -97,7 +111,7 @@ 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 / Tasks +## Scripts and Tasks All script are defined in the package.json file, but the most important ones are listed here. @@ -131,7 +145,7 @@ All script are defined in the package.json file, but the most important ones are - To migrate your database run `npm start migrate`. - To revert your latest migration run `npm start migrate.revert`. -## Using the debugger in VS Code +## Debugger in VSCode Just set a breakpoint and hit `F5` in your Visual Studio Code. @@ -172,14 +186,48 @@ The swagger and the monitor route can be altered in the `.env` file. | **test/unit/** *.test.ts | Unit tests | | .env.example | Environment configurations | -## Related Projects +## Logging -- [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 +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). + +```typescript +import { Logger, ILogger } from '../../decorators/Logger'; + +@Service() +export class UserService { + + constructor( + @Logger(__filename) private log: ILogger + ) { } + + ... +``` + +## Event Dispatching -## Documentations of our main dependencies +Our 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 +import { events } from '../subscribers/events'; +import { EventDispatcher, IEventDispatcher } from '../../decorators/EventDispatcher'; + +@Service() +export class UserService { + + constructor( + @EventDispatcher() private eventDispatcher: IEventDispatcher + ) { } + + public async create(user: User): Promise { + ... + this.eventDispatcher.dispatch(events.user.created, newUser); + ... + } +``` + +## Further Documentations | Name & Link | Description | | --------------------------------- | --------------------------------- | @@ -196,6 +244,13 @@ The swagger and the monitor route can be altered in the `.env` file. | [Jest](http://facebook.github.io/jest/) | Delightful JavaScript Testing Library for unit and e2e tests | | [swagger Documentation](http://swagger.io/) | API Tool to describe and document your api. | +## 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 [MIT](/LICENSE) From 88d9106d872a0b021037e5db9dfadff89a2ea7a0 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 11:41:44 +0100 Subject: [PATCH 063/104] Fix ts-node issue --- tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index a4c179c2..af198af2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,9 @@ "typeRoots": [ "src/types" ], - "types": [], + "types": [ + "node" + ], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true From d7828e6091a5fd0b46de542be0acb9be844f9536 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 11:42:26 +0100 Subject: [PATCH 064/104] Add seeds --- lib/seeds/BluePrint.ts | 12 ++++ lib/seeds/EntityFactory.ts | 76 ++++++++++++++++++++++++++ lib/seeds/EntityFactoryInterface.ts | 21 +++++++ lib/seeds/Factory.ts | 41 ++++++++++++++ lib/seeds/FactoryInterface.ts | 17 ++++++ lib/seeds/SeedsInterface.ts | 11 ++++ lib/seeds/connection.ts | 39 +++++++++++++ lib/seeds/index.ts | 67 +++++++++++++++++++++++ package.json | 6 ++ src/database/factories/UserFactory.ts | 22 ++++++++ src/database/seeds/0000-CreateUsers.ts | 13 +++++ tslint.json | 2 +- yarn.lock | 38 ++++++++++--- 13 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 lib/seeds/BluePrint.ts create mode 100644 lib/seeds/EntityFactory.ts create mode 100644 lib/seeds/EntityFactoryInterface.ts create mode 100644 lib/seeds/Factory.ts create mode 100644 lib/seeds/FactoryInterface.ts create mode 100644 lib/seeds/SeedsInterface.ts create mode 100644 lib/seeds/connection.ts create mode 100644 lib/seeds/index.ts create mode 100644 src/database/factories/UserFactory.ts create mode 100644 src/database/seeds/0000-CreateUsers.ts diff --git a/lib/seeds/BluePrint.ts b/lib/seeds/BluePrint.ts new file mode 100644 index 00000000..a001b899 --- /dev/null +++ b/lib/seeds/BluePrint.ts @@ -0,0 +1,12 @@ +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 callback: (faker: typeof Faker, args: any[]) => any) { } +} diff --git a/lib/seeds/EntityFactory.ts b/lib/seeds/EntityFactory.ts new file mode 100644 index 00000000..0f2eeab0 --- /dev/null +++ b/lib/seeds/EntityFactory.ts @@ -0,0 +1,76 @@ +import * as Faker from 'faker'; +import { Connection } from 'typeorm'; +import { EntityFactoryInterface } from './EntityFactoryInterface'; +import { BluePrint } from './BluePrint'; +import { getConnection } from './connection'; + + +export class EntityFactory implements EntityFactoryInterface { + + private connection: Connection | undefined; + private identifier = 'id'; + private eachFn: (obj: any, faker: typeof Faker) => Promise; + + constructor( + private faker: typeof Faker, + private blueprint: BluePrint, + private args: any[]) { } + + public returning(identifier: string): EntityFactory { + this.identifier = identifier; + return this; + } + + public each(iterator: (entity: Entity, faker: typeof Faker) => Promise): EntityFactory { + this.eachFn = iterator; + return this; + } + + public async create(amount: number = 1): Promise { + this.connection = await getConnection(); + if (this.connection) { + const results: Entity[] = []; + for (let i = 0; i < amount; i++) { + const entity = await this.build(); + results.push(entity); + if (typeof this.eachFn === 'function') { + await this.eachFn(entity, this.faker); + } + } + await this.connection.close(); + if (amount === 1) { + return results[0]; + } + return results; + } + return; + } + + private async build(): Promise { + if (this.connection) { + const entity = await this.makeEntity(this.blueprint.callback(this.faker, this.args)); + 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 (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; + } + +} diff --git a/lib/seeds/EntityFactoryInterface.ts b/lib/seeds/EntityFactoryInterface.ts new file mode 100644 index 00000000..850342fa --- /dev/null +++ b/lib/seeds/EntityFactoryInterface.ts @@ -0,0 +1,21 @@ +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 an amount (default 1) of the defined entity. + */ + create(amount: number): Promise; + /** + * Returns the identifier of the created entity. + */ + returning(identifier: string): EntityFactoryInterface; + /** + * 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/lib/seeds/Factory.ts b/lib/seeds/Factory.ts new file mode 100644 index 00000000..0f39903d --- /dev/null +++ b/lib/seeds/Factory.ts @@ -0,0 +1,41 @@ +import { ObjectType } from 'typeorm'; +import * as Faker from 'faker'; +import { FactoryInterface } from './FactoryInterface'; +import { EntityFactory } from './EntityFactory'; +import { BluePrint } from './BluePrint'; + + +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 blueprints: { [key: string]: BluePrint }; + + constructor(private faker: typeof Faker) { + this.blueprints = {}; + } + + 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.blueprints[this.getNameOfEntity(entityClass)], + args + ); + } + + private getNameOfEntity(EntityClass: any): string { + return new EntityClass().constructor.name; + } + +} diff --git a/lib/seeds/FactoryInterface.ts b/lib/seeds/FactoryInterface.ts new file mode 100644 index 00000000..a37c5edf --- /dev/null +++ b/lib/seeds/FactoryInterface.ts @@ -0,0 +1,17 @@ +import * as Faker from 'faker'; +import { ObjectType } from 'typeorm'; +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 an EntityFactoryInterface + */ + get(entityClass: ObjectType): EntityFactoryInterface; + /** + * Define an entity faker + */ + define(entityClass: ObjectType, fakerFunction: (faker: typeof Faker, args: any[]) => Entity): void; +} diff --git a/lib/seeds/SeedsInterface.ts b/lib/seeds/SeedsInterface.ts new file mode 100644 index 00000000..80b26628 --- /dev/null +++ b/lib/seeds/SeedsInterface.ts @@ -0,0 +1,11 @@ +import { FactoryInterface } from './FactoryInterface'; +/** + * Seeds should implement this interface and all its methods. + */ +export interface SeedsInterface { + /** + * Seed data into the databas. + */ + seed(factory: FactoryInterface): Promise; + +} diff --git a/lib/seeds/connection.ts b/lib/seeds/connection.ts new file mode 100644 index 00000000..7d383b44 --- /dev/null +++ b/lib/seeds/connection.ts @@ -0,0 +1,39 @@ +import { createConnection, Connection } from 'typeorm'; + +const args = process.argv; +const runDir = process.cwd(); + +// Get cli parameter for logging +const logging = args.indexOf('--logging') >= 0 || args.indexOf('-L') >= 0 || false; + +// Get cli parameter for ormconfig.json or another json file +const configParam = '--config'; +const hasConfigPath = args.indexOf(configParam) >= 0 || false; +const indexOfConfigPath = args.indexOf(configParam) + 1; +const ormconfig = (hasConfigPath) + ? require(`${args[indexOfConfigPath]}`) + : require(`${runDir}/ormconfig.json`); + +/** + * Returns a TypeORM database connection for our entity-manager + */ +export const getConnection = async (): Promise => { + try { + const connection = 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 connection; + } catch (error) { + console.error(error); + return; + } +}; + + diff --git a/lib/seeds/index.ts b/lib/seeds/index.ts new file mode 100644 index 00000000..11984e50 --- /dev/null +++ b/lib/seeds/index.ts @@ -0,0 +1,67 @@ +import 'reflect-metadata'; + +export * from './FactoryInterface'; +export * from './EntityFactoryInterface'; +export * from './SeedsInterface'; +export * from './Factory'; + +import * as path from 'path'; +import * as glob from 'glob'; +import * as commander from 'commander'; +import * as Chalk from 'chalk'; +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; + + log(chalk.bold('seeds')); + log('🔎 ', chalk.gray.underline(`found:`), + chalk.blue.bold(`${factories.length} factories`, chalk.gray('&'), chalk.blue.bold(`${seeds.length} seeds`))); + + for (const factory of factories) { + require(factory); + } + + for (const seed of seeds) { + const seedFile: any = require(seed); + + try { + let className = seed.split('/')[seed.split('/').length - 1]; + className = className.replace('.ts', '').replace('.js', ''); + className = className.split('-')[className.split('-').length - 1]; + log(); // Add line break for better visibility in the terminal + log(chalk.gray.underline(`executing seed: `), chalk.blue.bold(`${className}`)); + (new seedFile[className]()).seed(Factory.getInstance()); + } catch (error) { + console.error('Could not run seed ' + seedFile, error); + } + } + }); +}); + diff --git a/package.json b/package.json index 57f67a3e..a10ac2a5 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,12 @@ "dependencies": { "@types/bluebird": "^3.5.18", "@types/body-parser": "^1.16.7", + "@types/chalk": "^2.2.0", + "@types/commander": "^2.11.0", "@types/cors": "^2.8.1", "@types/dotenv": "^4.0.2", "@types/express": "^4.0.39", + "@types/faker": "^4.1.2", "@types/helmet": "^0.0.37", "@types/jest": "^21.1.5", "@types/lodash": "^4.14.80", @@ -49,7 +52,9 @@ "@types/winston": "^2.3.7", "ascii-art": "^1.4.2", "body-parser": "^1.18.2", + "chalk": "^2.3.0", "class-validator": "^0.7.3", + "commander": "^2.11.0", "compression": "^1.7.1", "copyfiles": "^1.2.0", "cors": "^2.8.4", @@ -58,6 +63,7 @@ "express": "^4.16.2", "express-basic-auth": "^1.1.3", "express-status-monitor": "^1.0.1", + "faker": "^4.1.0", "figlet": "^1.2.0", "glob": "^7.1.2", "helmet": "^3.9.0", diff --git a/src/database/factories/UserFactory.ts b/src/database/factories/UserFactory.ts new file mode 100644 index 00000000..cda08786 --- /dev/null +++ b/src/database/factories/UserFactory.ts @@ -0,0 +1,22 @@ +import * as Faker from 'faker'; +import { Factory } from '../../../lib/seeds'; +import { User } from '../../../src/api/models/User'; + + +const factory = Factory.getInstance(); + +/** + * User factory + */ +factory.define(User, (faker: typeof Faker) => { + const gender = faker.random.number(1); + const fn = faker.name.firstName(gender); + const ln = faker.name.lastName(gender); + const e = faker.internet.email(fn, ln); + + const user = new User(); + user.firstName = fn; + user.lastName = ln; + user.email = e; + return user; +}); diff --git a/src/database/seeds/0000-CreateUsers.ts b/src/database/seeds/0000-CreateUsers.ts new file mode 100644 index 00000000..67dfcc59 --- /dev/null +++ b/src/database/seeds/0000-CreateUsers.ts @@ -0,0 +1,13 @@ +import { SeedsInterface, FactoryInterface } from '../../../lib/seeds'; +import { User } from '../../../src/api/models/User'; + + +export class CreateUsers implements SeedsInterface { + + public async seed(factory: FactoryInterface): Promise { + await factory + .get(User) + .create(1); + } + +} diff --git a/tslint.json b/tslint.json index 3a9f5ee3..689ca78a 100644 --- a/tslint.json +++ b/tslint.json @@ -6,7 +6,7 @@ 160 ], "no-unnecessary-initializer": false, - "no-var-requires": true, + "no-var-requires": false, "no-null-keyword": true, "no-consecutive-blank-lines": false, "quotemark": [ diff --git a/yarn.lock b/yarn.lock index c0742a43..414ab493 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,6 +17,18 @@ "@types/express" "*" "@types/node" "*" +"@types/chalk@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" + dependencies: + chalk "*" + +"@types/commander@^2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.11.0.tgz#7fc765ccad14827e2babd6a99583359ff3e40563" + dependencies: + "@types/node" "*" + "@types/cors@^2.8.1": version "2.8.3" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.3.tgz#eaf6e476da0d36bee6b061a24d57e343ddce86d6" @@ -43,6 +55,10 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/faker@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/faker/-/faker-4.1.2.tgz#f8ab50c9f9af68c160dd71b63f83e24b712d0df5" + "@types/form-data@*": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" @@ -771,6 +787,14 @@ center-align@^0.1.1: deep-eql "^0.1.3" type-detect "^1.0.0" +chalk@*, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + chalk@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" @@ -791,14 +815,6 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - chokidar@^1.6.0, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -890,7 +906,7 @@ commander@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" -commander@^2.9.0: +commander@^2.11.0, commander@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -1600,6 +1616,10 @@ eyes@0.1.x: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" +faker@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" From e23cb21a7d4939c4da942020ac0b4aee4e8e75ed Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 13:03:58 +0100 Subject: [PATCH 065/104] Refactor seeds --- lib/seeds/index.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/seeds/index.ts b/lib/seeds/index.ts index 11984e50..9aae518c 100644 --- a/lib/seeds/index.ts +++ b/lib/seeds/index.ts @@ -1,10 +1,4 @@ import 'reflect-metadata'; - -export * from './FactoryInterface'; -export * from './EntityFactoryInterface'; -export * from './SeedsInterface'; -export * from './Factory'; - import * as path from 'path'; import * as glob from 'glob'; import * as commander from 'commander'; @@ -40,28 +34,46 @@ glob(path.join(runDir, factoryPath, '**/*Factory{.js,.ts}'), (errFactories: any, 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); } + // Initialize and seed all seeds. + const queue: Array> = []; for (const seed of seeds) { - const seedFile: any = require(seed); - 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(); // Add line break for better visibility in the terminal - log(chalk.gray.underline(`executing seed: `), chalk.blue.bold(`${className}`)); - (new seedFile[className]()).seed(Factory.getInstance()); + log('\n' + chalk.gray.underline(`executing seed: `), chalk.green.bold(`${className}`)); + queue.push((new seedFile[className]()).seed(Factory.getInstance())); } catch (error) { - console.error('Could not run seed ' + seedFile, 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); + }); }); }); +export * from './FactoryInterface'; +export * from './EntityFactoryInterface'; +export * from './SeedsInterface'; +export * from './Factory'; From d0c9ed685d1e50a5d2ca07cc94b41080ae3e6fe5 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:10:13 +0100 Subject: [PATCH 066/104] Add seeds to nps --- package-scripts.js | 50 +++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index eef3fe53..8cc5e0bd 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -18,7 +18,7 @@ module.exports = { script: series( 'nps banner.serve', '\"./node_modules/.bin/nodemon\" --watch src --watch .env', - ) + ), }, /** * Setup's the development environment and the database @@ -26,7 +26,7 @@ module.exports = { setup: { script: series( 'yarn install' - ) + ), }, /** * Builds the app into the dist directory @@ -38,25 +38,32 @@ module.exports = { 'nps clean.dist', 'nps transpile', 'nps copy', - ) + ), }, /** * Migrate the database with TypeORM */ - migrate: { - default: { + db: { + migrate: { script: series( 'nps banner.migrate', 'nps migrate.config', runFast('./node_modules/.bin/typeorm migrations:run'), - ) + ), }, revert: { script: series( 'nps banner.revert', 'nps migrate.config', runFast('./node_modules/.bin/typeorm migrations:revert'), - ) + ), + }, + seed: { + script: series( + 'nps banner.seed', + 'nps migrate.config', + runFast('./lib/seeds.ts'), + ), }, config: { script: runFast('./lib/ormconfig.ts'), @@ -73,7 +80,7 @@ module.exports = { 'nps banner.test', 'nps test.unit.pretest', 'nps test.unit.run', - ) + ), }, pretest: { script: './node_modules/.bin/tslint -c ./tslint.json -t stylish "./test/unit/**/*.ts"' @@ -95,7 +102,7 @@ module.exports = { 'nps test.e2e.pretest', runInNewWindow(series('nps build', 'nps start')), 'nps test.e2e.run', - ) + ), }, pretest: { script: './node_modules/.bin/tslint -c ./tslint.json -t stylish "./test/e2e/**/*.ts"' @@ -129,7 +136,7 @@ module.exports = { script: series( `nps banner.clean`, `nps clean.dist`, - ) + ), }, dist: { script: `./node_modules/.bin/trash './dist'` @@ -143,19 +150,19 @@ module.exports = { script: series( `nps copy.swagger`, `nps copy.public`, - ) + ), }, swagger: { script: copy( './src/api/swagger.json', './dist', - ) + ), }, public: { script: copy( './src/public/*', './dist', - ) + ), } }, /** @@ -166,10 +173,11 @@ module.exports = { serve: banner('serve'), test: banner('test'), migrate: banner('migrate'), + seed: banner('seed'), revert: banner('revert'), - clean: banner('clean') - } - } + clean: banner('clean'), + }, + }, }; function banner(name) { @@ -178,18 +186,18 @@ function banner(name) { silent: true, logLevel: 'error', description: `Shows ${name} banners to the console`, - script: runFast(`./src/console/lib/banner.ts ${name}`) - } + script: runFast(`./src/console/lib/banner.ts ${name}`), + }; } function copy(source, target) { - return `./node_modules/.bin/copyup ${source} ${target}` + return `./node_modules/.bin/copyup ${source} ${target}`; } function run(path) { - return `./node_modules/.bin/ts-node ${path}` + return `./node_modules/.bin/ts-node ${path}`; } function runFast(path) { - return run(`-F ${path}`) + return run(`-F ${path}`); } From 7c605819ee3e25e3d40bd5a8ed05a469e7a51849 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:16:35 +0100 Subject: [PATCH 067/104] Moved some dep to dev-dep --- package.json | 18 ++-- yarn.lock | 265 ++------------------------------------------------- 2 files changed, 15 insertions(+), 268 deletions(-) diff --git a/package.json b/package.json index a10ac2a5..e908fe90 100644 --- a/package.json +++ b/package.json @@ -35,14 +35,11 @@ "dependencies": { "@types/bluebird": "^3.5.18", "@types/body-parser": "^1.16.7", - "@types/chalk": "^2.2.0", - "@types/commander": "^2.11.0", "@types/cors": "^2.8.1", "@types/dotenv": "^4.0.2", "@types/express": "^4.0.39", "@types/faker": "^4.1.2", "@types/helmet": "^0.0.37", - "@types/jest": "^21.1.5", "@types/lodash": "^4.14.80", "@types/morgan": "^1.7.35", "@types/reflect-metadata": "0.0.5", @@ -50,11 +47,8 @@ "@types/serve-favicon": "^2.2.29", "@types/uuid": "^3.4.3", "@types/winston": "^2.3.7", - "ascii-art": "^1.4.2", "body-parser": "^1.18.2", - "chalk": "^2.3.0", "class-validator": "^0.7.3", - "commander": "^2.11.0", "compression": "^1.7.1", "copyfiles": "^1.2.0", "cors": "^2.8.4", @@ -70,11 +64,8 @@ "jsonfile": "^4.0.0", "lodash": "^4.17.4", "microframework": "^0.6.4", - "mock-express-request": "^0.2.0", - "mock-express-response": "^0.2.1", "morgan": "^1.9.0", "mysql": "^2.15.0", - "nock": "^9.1.0", "nodemon": "^1.12.1", "path": "^0.12.7", "reflect-metadata": "^0.1.10", @@ -90,7 +81,6 @@ "typeorm-typedi-extensions": "^0.1.1", "typescript": "^2.6.1", "uuid": "^3.1.0", - "wait-on": "^2.0.2", "winston": "^2.4.0" }, "jest": { @@ -109,8 +99,16 @@ }, "license": "MIT", "devDependencies": { + "@types/chalk": "^2.2.0", + "@types/commander": "^2.11.0", + "@types/jest": "^21.1.5", + "chalk": "^2.3.0", + "commander": "^2.11.0", "cross-env": "^5.1.1", "jest": "^21.2.1", + "mock-express-request": "^0.2.0", + "mock-express-response": "^0.2.1", + "nock": "^9.1.0", "nps": "^5.7.1", "nps-utils": "^1.5.0", "ts-jest": "^21.1.4" diff --git a/yarn.lock b/yarn.lock index 414ab493..22ec7df0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -333,18 +333,6 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -ascii-art@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/ascii-art/-/ascii-art-1.4.2.tgz#96f5f471b095e9702afc3cb29418486450cd17a1" - dependencies: - browser-request "0.3.3" - dirname-shim "1.0.0" - request "2.79.0" - yargs "*" - optionalDependencies: - canvas "*" - jsftp "*" - asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -693,10 +681,6 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -browser-request@0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" - browser-resolve@^1.11.2: version "1.11.2" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" @@ -752,22 +736,10 @@ camelize@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" -canvas@*: - version "1.6.7" - resolved "https://registry.yarnpkg.com/canvas/-/canvas-1.6.7.tgz#2d8a04b453ec5d6510727cfc697e236dc4ae85dc" - dependencies: - nan "^2.4.0" - parse-css-font "^2.0.2" - units-css "^0.4.0" - capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1037,7 +1009,7 @@ copyfiles@^1.2.0: noms "0.0.0" through2 "^2.0.1" -core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -1153,36 +1125,6 @@ crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" -css-font-size-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz#854875ace9aca6a8d2ee0d345a44aae9bb6db6cb" - -css-font-stretch-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz#50cee9b9ba031fb5c952d4723139f1e107b54b10" - -css-font-style-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz#5c3532813f63b4a1de954d13cea86ab4333409e4" - -css-font-weight-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz#9bc04671ac85bc724b574ef5d3ac96b0d604fd97" - -css-global-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69" - -css-list-helpers@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-1.0.1.tgz#fff57192202db83240c41686f919e449a7024f7d" - dependencies: - tcomb "^2.5.0" - -css-system-font-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" - cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.2" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" @@ -1221,7 +1163,7 @@ date-fns@^1.23.0: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" -debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6, debug@~2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1305,10 +1247,6 @@ diff@^3.1.0, diff@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" -dirname-shim@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dirname-shim/-/dirname-shim-1.0.0.tgz#950c15ec411f5c785aa0972070531ed78b16e0e0" - dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" @@ -1804,12 +1742,6 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -ftp-response-parser@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ftp-response-parser/-/ftp-response-parser-1.0.1.tgz#3b9d33f8edd5fb8e4700b8f778c462e5b1581f89" - dependencies: - readable-stream "^1.0.31" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -1823,16 +1755,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - dependencies: - is-property "^1.0.0" - get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -1988,15 +1910,6 @@ har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - har-validator@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" @@ -2306,15 +2219,6 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -2353,10 +2257,6 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" @@ -2393,18 +2293,10 @@ isarray@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" -isemail@2.x.x: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isnumeric@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64" - isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -2478,10 +2370,6 @@ istanbul-reports@^1.1.3: dependencies: handlebars "^4.0.3" -items@2.x.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198" - jest-changed-files@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29" @@ -2707,16 +2595,6 @@ jest@^21.2.1: dependencies: jest-cli "^21.2.1" -joi@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-9.2.0.tgz#3385ac790192130cbe230e802ec02c9215bbfeda" - dependencies: - hoek "4.x.x" - isemail "2.x.x" - items "2.x.x" - moment "2.x.x" - topo "2.x.x" - 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" @@ -2760,17 +2638,6 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" -jsftp@*: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jsftp/-/jsftp-2.0.0.tgz#720b1d07af22ce978f8967342da9d33a249b3f4b" - dependencies: - debug "^2.6.0" - ftp-response-parser "^1.0.1" - once "^1.3.3" - parse-listing "^1.1.3" - stream-combiner "^0.2.2" - unorm "^1.4.1" - json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -2809,10 +2676,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3192,10 +3055,6 @@ mock-res@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/mock-res/-/mock-res-0.4.1.tgz#d3004ea770ae0faec45b6594cc9500709809ec61" -moment@2.x.x: - version "2.19.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" - morgan@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051" @@ -3237,7 +3096,7 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.3.0, nan@^2.4.0: +nan@^2.3.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" @@ -3566,20 +3425,6 @@ parent-require@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" -parse-css-font@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/parse-css-font/-/parse-css-font-2.0.2.tgz#7b60b060705a25a9b90b7f0ed493e5823248a652" - dependencies: - css-font-size-keywords "^1.0.0" - css-font-stretch-keywords "^1.0.1" - css-font-style-keywords "^1.0.1" - css-font-weight-keywords "^1.0.0" - css-global-keywords "^1.0.1" - css-list-helpers "^1.0.1" - css-system-font-keywords "^1.0.0" - tcomb "^2.5.0" - unquote "^1.1.0" - parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -3595,10 +3440,6 @@ parse-json@^2.1.0, parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-listing@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/parse-listing/-/parse-listing-1.1.3.tgz#aa546f57fdc129cfbf9945cd4b757b14b06182dd" - parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -3820,10 +3661,6 @@ qs@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/qs/-/qs-2.4.2.tgz#f7ce788e5777df0b5010da7f7c4e73ba32470f5a" -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" @@ -3910,15 +3747,6 @@ readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable- string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@^1.0.31: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@~1.0.31: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -3997,31 +3825,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -4049,7 +3852,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.78.0, request@^2.79.0, request@^2.83.0: +request@^2.79.0, request@^2.83.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -4127,10 +3930,6 @@ rx@2.3.24: version "2.3.24" resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" - safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -4414,13 +4213,6 @@ statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" -stream-combiner@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" - dependencies: - duplexer "~0.1.1" - through "~2.3.4" - stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -4560,10 +4352,6 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" -tcomb@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" - temp-write@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-2.1.0.tgz#59890918e0ef09d548aaa342f4bd3409d8404e96" @@ -4618,7 +4406,7 @@ through2@^2.0.1: readable-stream "^2.1.5" xtend "~4.0.1" -through@2, through@~2.3, through@~2.3.1, through@~2.3.4: +through@2, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4642,12 +4430,6 @@ to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" -topo@2.x.x: - version "2.0.2" - resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" - dependencies: - hoek "4.x.x" - touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -4766,10 +4548,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -4858,29 +4636,14 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -units-css@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07" - dependencies: - isnumeric "^0.2.0" - viewport-dimensions "^0.2.0" - universalify@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" -unorm@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -unquote@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.0.tgz#98e1fc608b6b854c75afb1b95afc099ba69d942f" - unzip-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" @@ -4981,20 +4744,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -viewport-dimensions@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c" - -wait-on@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-2.0.2.tgz#0a84fd07024c6fc268cb0eabe585be217aaf2baa" - dependencies: - core-js "^2.4.1" - joi "^9.2.0" - minimist "^1.2.0" - request "^2.78.0" - rx "^4.1.0" - walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -5178,7 +4927,7 @@ xmlhttprequest-ssl@~1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz#04f560915724b389088715cc0ed7813e9677bf57" -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -5217,7 +4966,7 @@ yargs-parser@^8.0.0: dependencies: camelcase "^4.1.0" -yargs@*, yargs@^10.0.3: +yargs@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae" dependencies: From b547b3174e1d2ea53073902328c74ad0bb813913 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:16:54 +0100 Subject: [PATCH 068/104] Adjust nps script comment --- package-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-scripts.js b/package-scripts.js index 8cc5e0bd..f1a2a1b6 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -41,7 +41,7 @@ module.exports = { ), }, /** - * Migrate the database with TypeORM + * Database scripts */ db: { migrate: { From 7bfdae95a6189544692561b0c6e7df77a0c52805 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:18:24 +0100 Subject: [PATCH 069/104] Update swagger ui --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 22ec7df0..51f5f1da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4324,8 +4324,8 @@ supports-color@^4.0.0: has-flag "^2.0.0" swagger-ui-express@^2.0.10: - version "2.0.10" - resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-2.0.10.tgz#a7beb3e44ad0abd3c9915afc6b41cb87a91aa3e7" + version "2.0.11" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-2.0.11.tgz#db331cf888640a93b7a5b06111b02907a4be9c35" symbol-tree@^3.2.1: version "3.2.2" From 51eb4cb9de1d25547163824594725a8026a14aad Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:45:21 +0100 Subject: [PATCH 070/104] Moved banner script --- {src/console/lib => lib}/banner.ts | 0 package-scripts.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {src/console/lib => lib}/banner.ts (100%) diff --git a/src/console/lib/banner.ts b/lib/banner.ts similarity index 100% rename from src/console/lib/banner.ts rename to lib/banner.ts diff --git a/package-scripts.js b/package-scripts.js index f1a2a1b6..bab8ec8c 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -186,7 +186,7 @@ function banner(name) { silent: true, logLevel: 'error', description: `Shows ${name} banners to the console`, - script: runFast(`./src/console/lib/banner.ts ${name}`), + script: runFast(`./lib/banner.ts ${name}`), }; } From 120ee07df091d3a47f62cae0ee21bca6ece4d9b0 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:45:47 +0100 Subject: [PATCH 071/104] Add seeding documentation --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b7ea59e6..89b00213 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,10 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/). - **Easy event dispatching** thanks to [event-dispatch](https://github.com/pleerock/event-dispatch). - **Fast Database Building** with simple migration from [TypeOrm](https://github.com/typeorm/typeorm). +- **Easy Data Seeding** with our own factories. ### Comming soon -- **Easy Data Seeding** with our own factories. - **Custom Commands** are also available in our setup and really easy to use or even extend. - **Scaffolding Commands** will speed up your development tremendously as you should focus on business code and not scaffolding. @@ -59,6 +59,7 @@ Try it!! We are happy to hear your feedback or any kind of new features. - [Project Structure](#project-structure) - [Logging](#logging) - [Event Dispatching](#event-dispatching) +- [Seeding](#seeding) - [Further Documentations](#further-documentation) - [Related Projects](#related-projects) - [License](#license) @@ -139,12 +140,16 @@ All script are defined in the package.json file, but the most important ones are - Run `npm start build` to generated all JavaScript files from the TypeScript sources (There is also a vscode task for this called `build`). - To start the builded app located in `dist` use `npm start`. -### Database +### Database Migration - Run `./node_modules/.bin/typeorm create -n ` to create a new migration file. - To migrate your database run `npm start migrate`. - To revert your latest migration run `npm start migrate.revert`. +### Database Seeding + +- Run `nps db.seed` to seed your seeds into the database. + ## Debugger in VSCode Just set a breakpoint and hit `F5` in your Visual Studio Code. @@ -177,14 +182,20 @@ The swagger and the monitor route can be altered in the `.env` file. | **src/api/subscribers/** | Event subscribers | | **src/api/validators/** | Custom validators, which can be used in the request classes | | **src/api/** swagger.json | Swagger documentation | -| **src/console/** | Command line scripts | +| **src/auth/** | Authentication checkers and services | | **src/core/** | The core features like logger and env variables | +| **src/database/factories** | Factory the generate fake entities | +| **src/database/migrations** | Database migration scripts | +| **src/database/seeds** | Seeds to create some data in the database | +| **src/decoratros/** | Custom decorators like @Logger & @EventDispatch | +| **src/loaders/** | Loader is a place where you can configure your app | | **src/public/** | Static assets (fonts, css, js, img). | | **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | | **test** | Tests | | **test/e2e/** *.test.ts | End-2-End tests (like e2e) | | **test/unit/** *.test.ts | Unit tests | | .env.example | Environment configurations | +| ormconfig.json | TypeORM configuration for the database. Used by seeds and the migration. (generated file) | ## Logging @@ -227,6 +238,92 @@ 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. + +### 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 awesome the [faker](https://github.com/marak/Faker.js/) library. The create a new "fake" entity and return it. Those file should be in the `src/database/factories` folder and suffixed with `Factory`. Example `src/database/factories/UserFactory.ts`. + +```typescript +factory.define(User, (faker: typeof Faker) => { + const gender = faker.random.number(1); + const firstName = faker.name.firstName(gender); + const lastName = faker.name.lastName(gender); + const email = faker.internet.email(firstName, lastName); + + const user = new User(); + user.firstName = firstName; + user.lastName = lastName; + user.email = email; + return user; +}); +``` + +This is a nested example for a factory to get the foreign key of the other entity. + +```typescript +factory.define(Pet, (faker: typeof Faker, args: any[]) => { + const type = args[0]; + return { + name: faker.name.firstName(), + type: type || 'dog', + userId: factory.get(User).returning('id') + }; +}); +``` + +### 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. + +```typescript +export class CreateUsers implements SeedsInterface { + + public async seed(factory: FactoryInterface): Promise { + await factory + .get(User) + .create(1); + } + +} +``` + +Another example for nested entities. For that we use the each function to so. + +```typescript +... +await factory.get(Tournament, 2) + .each(async (tournament: Tournament) => { + + const teams: Team[] = await factory.get(Team) + .each(async (team: Team) => { + + const users: User[] = await factory.get(User).create(2); + const userIds = users.map((u: User) => u.Id); + await team.users().attach(userIds); + + }) + .create(getRandomNumber(tournament.MaxSize)); + + const teamIds = teams.map((t: Team) => t.Id); + await tournament.teams().attach(teamIds); + + }) + .create(5); +... +``` + +### 3. Run the seeder + +The last step is the easiest, just hit the following command in your terminal, but be sure you are in the projects root folder. + +```bash +nps db.seed +``` + ## Further Documentations | Name & Link | Description | From fc451be213a270fefeaf368e7c63faa937f81a3d Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:46:05 +0100 Subject: [PATCH 072/104] Refactor UserFactory --- src/database/factories/UserFactory.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/database/factories/UserFactory.ts b/src/database/factories/UserFactory.ts index cda08786..3d371aca 100644 --- a/src/database/factories/UserFactory.ts +++ b/src/database/factories/UserFactory.ts @@ -2,21 +2,21 @@ import * as Faker from 'faker'; import { Factory } from '../../../lib/seeds'; import { User } from '../../../src/api/models/User'; - const factory = Factory.getInstance(); + /** * User factory */ factory.define(User, (faker: typeof Faker) => { const gender = faker.random.number(1); - const fn = faker.name.firstName(gender); - const ln = faker.name.lastName(gender); - const e = faker.internet.email(fn, ln); + const firstName = faker.name.firstName(gender); + const lastName = faker.name.lastName(gender); + const email = faker.internet.email(firstName, lastName); const user = new User(); - user.firstName = fn; - user.lastName = ln; - user.email = e; + user.firstName = firstName; + user.lastName = lastName; + user.email = email; return user; }); From 08ad60e45e5a040d97c6dd776dca0cd34d85f000 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 14:54:53 +0100 Subject: [PATCH 073/104] Refactoring naming --- README.md | 29 +++++++++++---------- src/api/services/UserService.ts | 8 +++--- src/auth/AuthService.ts | 8 +++--- src/auth/ITokenInfo.ts | 3 --- src/auth/TokenInfoInterface.ts | 3 +++ src/auth/currentUserChecker.ts | 4 +-- src/core/{ILogger.ts => LoggerInterface.ts} | 2 +- src/decorators/EventDispatcher.ts | 2 +- src/decorators/Logger.ts | 2 +- test/unit/auth/AuthService.test.ts | 4 +-- 10 files changed, 33 insertions(+), 32 deletions(-) delete mode 100644 src/auth/ITokenInfo.ts create mode 100644 src/auth/TokenInfoInterface.ts rename src/core/{ILogger.ts => LoggerInterface.ts} (85%) diff --git a/README.md b/README.md index 89b00213..f9bc3c2f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **Beautiful Code** thanks to the awesome annotations of the libraries from [pleerock](https://github.com/pleerock). - **Easy API Testing** with included e2e testing. - **Dependency Injection** done with the nice framework from [TypeDI](https://github.com/pleerock/typedi). -- **Simplified Database Query** with the ORM [TypeOrm](https://github.com/typeorm/typeorm). +- **Simplified Database Query** with the ORM [TypeORM](https://github.com/typeorm/typeorm). - **Clear Structure** with different layers such as controllers, services, repositories, models, middlewares... - **Easy Exception Handling** thanks to [routing-controllers](https://github.com/pleerock/routing-controllers). - **Smart Validation** thanks to [class-validator](https://github.com/pleerock/class-validator) with some nice annotations. @@ -42,7 +42,7 @@ Try it!! We are happy to hear your feedback or any kind of new features. - **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest). - **Basic Security Features** thanks to [Helmet](https://helmetjs.github.io/). - **Easy event dispatching** thanks to [event-dispatch](https://github.com/pleerock/event-dispatch). -- **Fast Database Building** with simple migration from [TypeOrm](https://github.com/typeorm/typeorm). +- **Fast Database Building** with simple migration from [TypeORM](https://github.com/typeorm/typeorm). - **Easy Data Seeding** with our own factories. ### Comming soon @@ -148,7 +148,7 @@ All script are defined in the package.json file, but the most important ones are ### Database Seeding -- Run `nps db.seed` to seed your seeds into the database. +- Run `npm start db.seed` to seed your seeds into the database. ## Debugger in VSCode @@ -159,11 +159,12 @@ Just set a breakpoint and hit `F5` in your Visual Studio Code. 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. -| Route | Description | -| ------------ | ----------- | -| **/api** | Shows us the name, description and the version of the package.json | -| **/swagger** | This is the Swagger UI with our API documentation | -| **/monitor** | Shows a small monitor page for the server | +| Route | Description | +| -------------- | ----------- | +| **/api** | Shows us the name, description and the version of the package.json | +| **/swagger** | This is the Swagger UI with our API documentation | +| **/monitor** | Shows a small monitor page for the server | +| **/api/users** | Example entity endpoint | ## Project Structure @@ -187,7 +188,7 @@ The swagger and the monitor route can be altered in the `.env` file. | **src/database/factories** | Factory the generate fake entities | | **src/database/migrations** | Database migration scripts | | **src/database/seeds** | Seeds to create some data in the database | -| **src/decoratros/** | Custom decorators like @Logger & @EventDispatch | +| **src/decorators/** | Custom decorators like @Logger & @EventDispatch | | **src/loaders/** | Loader is a place where you can configure your app | | **src/public/** | Static assets (fonts, css, js, img). | | **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | @@ -203,13 +204,13 @@ Our logger is [winston](https://github.com/winstonjs/winston). To log http reque We created a simple annotation to inject the logger in your service (see example below). ```typescript -import { Logger, ILogger } from '../../decorators/Logger'; +import { Logger, LoggerInterface } from '../../decorators/Logger'; @Service() export class UserService { constructor( - @Logger(__filename) private log: ILogger + @Logger(__filename) private log: LoggerInterface ) { } ... @@ -222,13 +223,13 @@ We created a simple annotation to inject the EventDispatcher in your service (se ```typescript import { events } from '../subscribers/events'; -import { EventDispatcher, IEventDispatcher } from '../../decorators/EventDispatcher'; +import { EventDispatcher, EventDispatcherInterface } from '../../decorators/EventDispatcher'; @Service() export class UserService { constructor( - @EventDispatcher() private eventDispatcher: IEventDispatcher + @EventDispatcher() private eventDispatcher: EventDispatcherInterface ) { } public async create(user: User): Promise { @@ -321,7 +322,7 @@ await factory.get(Tournament, 2) The last step is the easiest, just hit the following command in your terminal, but be sure you are in the projects root folder. ```bash -nps db.seed +npm start db.seed ``` ## Further Documentations diff --git a/src/api/services/UserService.ts b/src/api/services/UserService.ts index b8916656..addafc3d 100644 --- a/src/api/services/UserService.ts +++ b/src/api/services/UserService.ts @@ -3,8 +3,8 @@ import { OrmRepository } from 'typeorm-typedi-extensions'; import { UserRepository } from '../repositories/UserRepository'; import { User } from '../models/User'; import { events } from '../subscribers/events'; -import { EventDispatcher, IEventDispatcher } from '../../decorators/EventDispatcher'; -import { Logger, ILogger } from '../../decorators/Logger'; +import { EventDispatcher, EventDispatcherInterface } from '../../decorators/EventDispatcher'; +import { Logger, LoggerInterface } from '../../decorators/Logger'; @Service() @@ -12,8 +12,8 @@ export class UserService { constructor( @OrmRepository() private userRepository: UserRepository, - @EventDispatcher() private eventDispatcher: IEventDispatcher, - @Logger(__filename) private log: ILogger + @EventDispatcher() private eventDispatcher: EventDispatcherInterface, + @Logger(__filename) private log: LoggerInterface ) { } public find(): Promise { diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index 39e9dec4..ddba5fe1 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -2,8 +2,8 @@ import * as request from 'request'; import * as express from 'express'; import { Service, Require } from 'typedi'; import { env } from '../core/env'; -import { ITokenInfo } from './ITokenInfo'; -import { Logger, ILogger } from '../decorators/Logger'; +import { TokenInfoInterface } from './TokenInfoInterface'; +import { Logger, LoggerInterface } from '../decorators/Logger'; @Service() @@ -13,7 +13,7 @@ export class AuthService { constructor( @Require('request') r: any, - @Logger(__filename) private log: ILogger + @Logger(__filename) private log: LoggerInterface ) { this.httpRequest = r; } @@ -31,7 +31,7 @@ export class AuthService { return; } - public getTokenInfo(token: string): Promise { + public getTokenInfo(token: string): Promise { return new Promise((resolve, reject) => { this.httpRequest({ method: 'POST', diff --git a/src/auth/ITokenInfo.ts b/src/auth/ITokenInfo.ts deleted file mode 100644 index 052d4546..00000000 --- a/src/auth/ITokenInfo.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ITokenInfo { - user_id: string; -} diff --git a/src/auth/TokenInfoInterface.ts b/src/auth/TokenInfoInterface.ts new file mode 100644 index 00000000..4b14d6d4 --- /dev/null +++ b/src/auth/TokenInfoInterface.ts @@ -0,0 +1,3 @@ +export interface TokenInfoInterface { + user_id: string; +} diff --git a/src/auth/currentUserChecker.ts b/src/auth/currentUserChecker.ts index 2cf978bd..a63ee85d 100644 --- a/src/auth/currentUserChecker.ts +++ b/src/auth/currentUserChecker.ts @@ -1,7 +1,7 @@ import { Action } from 'routing-controllers'; import { User } from '../api/models/User'; import { Logger } from '../core/Logger'; -import { ITokenInfo } from './ITokenInfo'; +import { TokenInfoInterface } from './TokenInfoInterface'; import { Connection } from 'typeorm'; @@ -12,7 +12,7 @@ export function currentUserChecker(connection: Connection): (action: Action) => // here you can use request/response objects from action // you need to provide a user object that will be injected in controller actions // demo code: - const tokeninfo: ITokenInfo = action.request.tokeninfo; + const tokeninfo: TokenInfoInterface = action.request.tokeninfo; const em = connection.createEntityManager(); const user = await em.findOne(User, { where: { diff --git a/src/core/ILogger.ts b/src/core/LoggerInterface.ts similarity index 85% rename from src/core/ILogger.ts rename to src/core/LoggerInterface.ts index 44617b39..75a635c3 100644 --- a/src/core/ILogger.ts +++ b/src/core/LoggerInterface.ts @@ -1,4 +1,4 @@ -export interface ILogger { +export interface LoggerInterface { debug(message: string, ...args: any[]): void; info(message: string, ...args: any[]): void; warn(message: string, ...args: any[]): void; diff --git a/src/decorators/EventDispatcher.ts b/src/decorators/EventDispatcher.ts index 50bac4cc..d08cdf47 100644 --- a/src/decorators/EventDispatcher.ts +++ b/src/decorators/EventDispatcher.ts @@ -9,4 +9,4 @@ export function EventDispatcher(): any { }; } -export { EventDispatcher as IEventDispatcher } from 'event-dispatch'; +export { EventDispatcher as EventDispatcherInterface } from 'event-dispatch'; diff --git a/src/decorators/Logger.ts b/src/decorators/Logger.ts index 23729ef6..f5459650 100644 --- a/src/decorators/Logger.ts +++ b/src/decorators/Logger.ts @@ -9,4 +9,4 @@ export function Logger(scope: string): any { }; } -export { ILogger } from '../core/ILogger'; +export { LoggerInterface } from '../core/LoggerInterface'; diff --git a/test/unit/auth/AuthService.test.ts b/test/unit/auth/AuthService.test.ts index 0e9c65b6..b210e405 100644 --- a/test/unit/auth/AuthService.test.ts +++ b/test/unit/auth/AuthService.test.ts @@ -3,7 +3,7 @@ import * as request from 'request'; import * as MockExpressRequest from 'mock-express-request'; import * as nock from 'nock'; import { AuthService } from './../../../src/auth/AuthService'; -import { ILogger } from './../../../src/core/ILogger'; +import { LoggerInterface } from './../../../src/core/LoggerInterface'; import { env } from './../../../src/core/env'; @@ -11,7 +11,7 @@ describe('AuthService', () => { let authService: AuthService; beforeEach(() => { - const log: ILogger = { + const log: LoggerInterface = { debug: () => void 0, info: () => void 0, warn: () => void 0, From 3f7e261d1d9706317dec52d5a0a7a2f68ddb55bb Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:11:52 +0100 Subject: [PATCH 074/104] Fix build task --- package-scripts.js | 6 +++--- src/database/factories/UserFactory.ts | 2 +- src/database/seeds/0000-CreateUsers.ts | 2 +- {lib => src/lib}/banner.ts | 0 {lib => src/lib}/ormconfig.ts | 4 ++-- {lib => src/lib}/seeds/BluePrint.ts | 0 {lib => src/lib}/seeds/EntityFactory.ts | 0 {lib => src/lib}/seeds/EntityFactoryInterface.ts | 0 {lib => src/lib}/seeds/Factory.ts | 0 {lib => src/lib}/seeds/FactoryInterface.ts | 0 {lib => src/lib}/seeds/SeedsInterface.ts | 0 {lib => src/lib}/seeds/connection.ts | 0 {lib => src/lib}/seeds/index.ts | 0 tsconfig.json | 6 ++++++ 14 files changed, 13 insertions(+), 7 deletions(-) rename {lib => src/lib}/banner.ts (100%) rename {lib => src/lib}/ormconfig.ts (87%) rename {lib => src/lib}/seeds/BluePrint.ts (100%) rename {lib => src/lib}/seeds/EntityFactory.ts (100%) rename {lib => src/lib}/seeds/EntityFactoryInterface.ts (100%) rename {lib => src/lib}/seeds/Factory.ts (100%) rename {lib => src/lib}/seeds/FactoryInterface.ts (100%) rename {lib => src/lib}/seeds/SeedsInterface.ts (100%) rename {lib => src/lib}/seeds/connection.ts (100%) rename {lib => src/lib}/seeds/index.ts (100%) diff --git a/package-scripts.js b/package-scripts.js index bab8ec8c..7a03c700 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -62,11 +62,11 @@ module.exports = { script: series( 'nps banner.seed', 'nps migrate.config', - runFast('./lib/seeds.ts'), + runFast('./src/lib/seeds.ts'), ), }, config: { - script: runFast('./lib/ormconfig.ts'), + script: runFast('./src/lib/ormconfig.ts'), } }, /** @@ -186,7 +186,7 @@ function banner(name) { silent: true, logLevel: 'error', description: `Shows ${name} banners to the console`, - script: runFast(`./lib/banner.ts ${name}`), + script: runFast(`./src/lib/banner.ts ${name}`), }; } diff --git a/src/database/factories/UserFactory.ts b/src/database/factories/UserFactory.ts index 3d371aca..5ce3953b 100644 --- a/src/database/factories/UserFactory.ts +++ b/src/database/factories/UserFactory.ts @@ -1,5 +1,5 @@ import * as Faker from 'faker'; -import { Factory } from '../../../lib/seeds'; +import { Factory } from '../../lib/seeds'; import { User } from '../../../src/api/models/User'; const factory = Factory.getInstance(); diff --git a/src/database/seeds/0000-CreateUsers.ts b/src/database/seeds/0000-CreateUsers.ts index 67dfcc59..b8015321 100644 --- a/src/database/seeds/0000-CreateUsers.ts +++ b/src/database/seeds/0000-CreateUsers.ts @@ -1,4 +1,4 @@ -import { SeedsInterface, FactoryInterface } from '../../../lib/seeds'; +import { SeedsInterface, FactoryInterface } from '../../lib/seeds'; import { User } from '../../../src/api/models/User'; diff --git a/lib/banner.ts b/src/lib/banner.ts similarity index 100% rename from lib/banner.ts rename to src/lib/banner.ts diff --git a/lib/ormconfig.ts b/src/lib/ormconfig.ts similarity index 87% rename from lib/ormconfig.ts rename to src/lib/ormconfig.ts index f1a43fad..667e64f4 100644 --- a/lib/ormconfig.ts +++ b/src/lib/ormconfig.ts @@ -3,7 +3,7 @@ dotenv.config(); import * as path from 'path'; import * as jsonfile from 'jsonfile'; -import { env } from '../src/core/env'; +import { env } from '../core/env'; const content = { @@ -20,7 +20,7 @@ const content = { }, }; -const filePath = path.join(__dirname, '../', 'ormconfig.json'); +const filePath = path.join(process.cwd(), 'ormconfig.json'); jsonfile.writeFile(filePath, content, { spaces: 2 }, (err) => { if (err === null) { console.log('Successfully generated ormconfig.json form the .env file'); diff --git a/lib/seeds/BluePrint.ts b/src/lib/seeds/BluePrint.ts similarity index 100% rename from lib/seeds/BluePrint.ts rename to src/lib/seeds/BluePrint.ts diff --git a/lib/seeds/EntityFactory.ts b/src/lib/seeds/EntityFactory.ts similarity index 100% rename from lib/seeds/EntityFactory.ts rename to src/lib/seeds/EntityFactory.ts diff --git a/lib/seeds/EntityFactoryInterface.ts b/src/lib/seeds/EntityFactoryInterface.ts similarity index 100% rename from lib/seeds/EntityFactoryInterface.ts rename to src/lib/seeds/EntityFactoryInterface.ts diff --git a/lib/seeds/Factory.ts b/src/lib/seeds/Factory.ts similarity index 100% rename from lib/seeds/Factory.ts rename to src/lib/seeds/Factory.ts diff --git a/lib/seeds/FactoryInterface.ts b/src/lib/seeds/FactoryInterface.ts similarity index 100% rename from lib/seeds/FactoryInterface.ts rename to src/lib/seeds/FactoryInterface.ts diff --git a/lib/seeds/SeedsInterface.ts b/src/lib/seeds/SeedsInterface.ts similarity index 100% rename from lib/seeds/SeedsInterface.ts rename to src/lib/seeds/SeedsInterface.ts diff --git a/lib/seeds/connection.ts b/src/lib/seeds/connection.ts similarity index 100% rename from lib/seeds/connection.ts rename to src/lib/seeds/connection.ts diff --git a/lib/seeds/index.ts b/src/lib/seeds/index.ts similarity index 100% rename from lib/seeds/index.ts rename to src/lib/seeds/index.ts diff --git a/tsconfig.json b/tsconfig.json index af198af2..f6889749 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,5 +36,11 @@ }, "include": [ "src/**/*" + ], + "exclude": [ + "lib", + "node_modules", + "**/*.test.ts", + "**/*.spec.ts" ] } From 1ae3b086f5a652ddd3110f091d440f4992bede52 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:13:49 +0100 Subject: [PATCH 075/104] Improve setup task --- package-scripts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-scripts.js b/package-scripts.js index 7a03c700..48880987 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -25,7 +25,9 @@ module.exports = { */ setup: { script: series( - 'yarn install' + 'yarn install', + 'nps db.migrate', + 'nps db.seed' ), }, /** From b72dd3621bb5ed5a7f7c9935138ae4ed9f87fa70 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:22:16 +0100 Subject: [PATCH 076/104] Add db.drop script --- README.md | 5 +++-- package-scripts.js | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9bc3c2f..e1c9437e 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,9 @@ All script are defined in the package.json file, but the most important ones are ### Database Migration - Run `./node_modules/.bin/typeorm create -n ` to create a new migration file. -- To migrate your database run `npm start migrate`. -- To revert your latest migration run `npm start migrate.revert`. +- To migrate your database run `npm start db.migrate`. +- To revert your latest migration run `npm start db.revert`. +- Drops the complete database schema `npm start db.drop`. ### Database Seeding diff --git a/package-scripts.js b/package-scripts.js index 48880987..1c8eed66 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -69,6 +69,9 @@ module.exports = { }, config: { script: runFast('./src/lib/ormconfig.ts'), + }, + drop: { + script: runFast('./node_modules/.bin/typeorm schema:drop') } }, /** From 93b17e2551603ef33575649ee986b915c2d834fc Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:28:55 +0100 Subject: [PATCH 077/104] Remove unuesed settings --- tsconfig.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index f6889749..af198af2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,11 +36,5 @@ }, "include": [ "src/**/*" - ], - "exclude": [ - "lib", - "node_modules", - "**/*.test.ts", - "**/*.spec.ts" ] } From 4555a1604061cb8ceaef6206b6f033aff9c097af Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:29:23 +0100 Subject: [PATCH 078/104] Remove old readme --- README.old.md | 273 -------------------------------------------------- 1 file changed, 273 deletions(-) delete mode 100644 README.old.md diff --git a/README.old.md b/README.old.md deleted file mode 100644 index 9f625fab..00000000 --- a/README.old.md +++ /dev/null @@ -1,273 +0,0 @@ -# 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 with NodeJs & TypeScript. - -> An Node.js RESTful API boilerplate featuring -[Express](https://expressjs.com/), -[Inversify](http://inversify.io/), -[Winston](https://github.com/winstonjs/winston), -[TypeScript](https://www.typescriptlang.org/), -[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/), -[knex](http://knexjs.org/) and -[bookshelf](http://bookshelfjs.org/) -by [w3tech](https://github.com/w3tecch) - -## 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 -- **Beautiful Syntax** thanks to the awesome annotations from [Inversify Express Utils](https://github.com/inversify/inversify-express-utils). -- **Easy API Testing** with included e2e testing. -- **Dependency Injection** done with the nice framework from [Inversify](http://inversify.io/). -- **Fast Database Building** with simple migration and seeding from [Knex](http://knexjs.org/). -- **Simplified Database Query** with the ORM of [Knex](http://knexjs.org/) called [Bookshelf](http://bookshelfjs.org/). -- **Clear Structure** with controllers, services, repositories, models, middlewares... -- **Easy Exception Handling** with our own simple and easy to adopt logic. You will love it. -- **Easy Data Seeding** with our own factories. -- **Custom Commands** are also available in our setup and really easy to use or even extend. -- **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes) -- **Scaffolding Commands** will speed up your development tremendously as you should focus on business code and not scaffolding. -- **Smart Validation** thanks to [class-validator](https://github.com/pleerock/class-validator) with some nice annotations. -- **API Documentation** thanks to [swagger](http://swagger.io/). -- **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor). -- **Integrated Testing Tool** thanks to [Wallaby.js](https://wallabyjs.com/) - -## Getting Started -### Step 1: Set up the Development Environment -You need to set up your development environment before you can do anything. - -Install [Node.js and NPM](https://nodejs.org/en/download/) -* on OSX use [homebrew](http://brew.sh) `brew install node` -* on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs` - -Install yarn globally -``` -npm install yarn -g -``` - -Install a MySQL database. - -> If you work with a mac, we recommend to use homebrew for the installation. - -### Step 2: Create new Project -Fork or download this project. Configure your package.json for your new project. - -Then copy the `.env.example` file and rename it to `.env`. In this file you have to add your database connection information. - -Create a new database with the name you have in your `.env`-file. - -Then setup your application environment. -``` -nps setup -``` - -> This installs all dependencies with yarn. After that it migrates the database and seeds some test data into it. So after that your development environment is ready to use. - -### Step 3: Serve your App -Go to the project dir and start your app with this npm script. -``` -nps 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`. - -### Step 4: Create a new Resource -Go to the project dir and hit this command in your terminal. -``` -npm run console make:resource -``` - -Apply the same information like you see in the screenshot below. - -![console](console.png) - -> With that you just have created a complete new endpoint in your api for the resource pets. - -Normally a pet belogns to a user, so we have to add the relationship between users an pets. Open the created migration file and replace the user property with these lines. -``` -table.integer('user_id').unsigned(); -table.foreign('user_id').references('id').inTable('users').onDelete('cascade'); -``` - -Next we have to add this relationship also in the pets model. -``` -public user(): User { - return this.belongsTo(User); -} -``` - -> The relationship between the users and pets are set and ready. So you can migrate your database with `npm run db:migrate` - -### Step 5: Create a Seeder -To seed some cute pets we need a smart factory. So open the ./src/database/factories/index.ts and add this code. -``` -/** - * PET - Factory - */ -factory.define(Pet, (faker: Faker.FakerStatic, args: any[]) => { - const type = args[0]; - return { - name: faker.name.firstName(), - type: type || 'dog', - userId: factory.get(User).returning('id') - }; -}); -``` - -> This factory helps us to create a fake pet to seed to the database. - -Run this command in your terminal and call the new seeder `create pets`. -``` -npm run console make:seed -``` - -Open the file and place this code into it. -``` -await factory.get(Pet) - .create(10); -``` - -> Now we can seed some nice cats into the database with `npm run db:seed`. - -> That was easy! Now its your turn to make something great out of it. - -## Scripts / Tasks -All script are defined in the package.json file, but the most important ones are listed here. - -### Install -* Install all dependencies with `yarn install` - -### Linting -* Run code quality analysis using `nps lint`. This runs tslint. -* There is also a vscode task for this called `lint`. - -### Tests -* Run the unit tests using `nps test` (There is also a vscode task for this called `test`). -* Run the e2e tests using `nps test:e2e` and don't forget to start your application and your [Auth0 Mock Server](https://github.com/hirsch88/auth0-mock-server). - -### Running in dev mode -* Run `nps serve` to start nodemon with ts-node, to serve the app. -* The server address will be displayed to you as `http://0.0.0.0:3000` - -### Building the project and run it -* Run `nps build` to generated all JavaScript files from the TypeScript sources (There is also a vscode task for this called `build`). -* To start the builded app located in `dist` use `npm start`. - -### Database -* Run `nps db:migrate` to migrate schema changes to the database -* Run `nps db:migrate:rollback` to rollback one migration -* Run `nps db:seed` to seed sample data into the database -* Run `nps db:reset` to rollback all migrations and migrate any migration again - -### Console -* To run your own created command enter `npm run console `. -* This list all your created commands `npm run console:help`. - -### Scaffolding Commands -All the templates for the commands are located in `src/console/templates`. - -* `npm run console make:resource ` - Generates a controller, service, requests, repo, model and a migration with CRUD operations. -* `npm run console make:controller ` - Generates a controller. -* `npm run console make:service ` - Generates a service. -* `npm run console make:repo ` - Generates a repository. -* `npm run console make:model ` - Generates a model with the props and configurations. -* `npm run console make:middleware ` - Generates a basic middleware. -* `npm run console make:request ` - Generates a basic request. -* `npm run console make:listener ` - Generates a basic listener. -* `npm run console make:exception ` - Generates a basic exception. -* `npm run console make:validator ` - Generates a custom validator. - -**Example** -``` -$ npm run console make:controller auth/auth -// -> creates `api/controllers/auth/AuthController.ts - -$ npm run console make:model user -// -> creates `api/models/User.ts -``` - -## IoC -Our IoC automatically looks through the `controllers`, `listeners` , `middlewares`, `services`, -`repositories` and `models` folders in `src/api/` for files to bound automatically into the IoC - Container, so you have nothing to do. - -**However it is very important to keep the naming right, because otherwise our IoC will not find your -created files!!** - -## Using the debugger in VS Code -Just set a breakpoint and hit `F5` in your Visual Studio Code. - -## API Routes -The route prefix is `/api` by default, but you can change this in the .env file. - -| Route | Description | -| ----------- | ----------- | -| **/api/info** | Shows us the name, description and the version of the package.json | -| **/api/docs** | This is the Swagger UI with our API documentation | -| **/status** | Shows a small monitor page for the server | - -## Project Structure - -| Name | Description | -| ----------------------------- | ----------- | -| **.vscode/** | VSCode tasks, launch configuration and some other settings | -| **dist/** | Compiled source files will be placed here | -| **src/** | Source files | -| **src/api/controllers/** | REST API Controllers | -| **src/api/exceptions/** | Exceptions like 404 NotFound | -| **src/api/listeners/** | Event listeners | -| **src/api/middlewares/** | Express Middlewares like populateUser | -| **src/api/models/** | Bookshelf Models | -| **src/api/repositories/** | Repository / DB layer | -| **src/api/requests/** | Request bodys with validations | -| **src/api/services/** | Service layer | -| **src/api/validators/** | Custom validators, which can be used in the request classes | -| **src/api/** swagger.json | Swagger documentation | -| **src/console/** | Command line scripts | -| **src/config/** | Configurations like database or logger | -| **src/constants/** | Global Constants | -| **src/core/** | The core framework | -| **src/database/factories/** | Model factories to generate database records | -| **src/database/migrations/** | Migrations scripts to build up the database schema | -| **src/database/seeds/** | Seed scripts to fake sample data into the database | -| **src/public/** | Static assets (fonts, css, js, img). | -| **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | -| **test** | Tests | -| **test/e2e/** *.test.ts | End-2-End tests (like e2e) | -| **test/unit/** *.test.ts | Unit tests | -| .env.example | Environment configurations | -| knexfile.ts | This file is used for the migrations and seed task of knex | - -## 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 - -## Documentations of our main dependencies -* [Express](https://expressjs.com/) -* [Knex](http://knexjs.org/) -* [Bookshelf](http://bookshelfjs.org/) -* [Bookshelf Cheatsheet](http://ricostacruz.com/cheatsheets/bookshelf.html) -* [Inversify](http://inversify.io/) -* [Inversify Express Utils](https://github.com/inversify/inversify-express-utils) -* [class-validator](https://github.com/pleerock/class-validator) -* [Jest](http://facebook.github.io/jest/) -* [Auth0 API Documentation](https://auth0.com/docs/api/management/v2) -* [swagger Documentation](http://swagger.io/) - -## License - [MIT](/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) From 875a432ae4b58ecaecc13a97a7271d7251589689 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:31:45 +0100 Subject: [PATCH 079/104] Update CI options --- .travis.yml | 6 +++--- appveyor.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54dac591..c79b4af4 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: node_js node_js: - - "7.7.3" + - "8.2.1" install: - yarn install scripts: - - nps test - - nps build + - npm test + - npm start build notifications: email: false diff --git a/appveyor.yml b/appveyor.yml index 6f1e255d..3bc1c57b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,12 @@ environment: - nodejs_version: "7" + nodejs_version: "8" install: - ps: Install-Product node $env:nodejs_version - yarn install build_script: - - nps build + - npm start build test_script: - - nps test + - npm test From 4b1986ad3c37e8eff62e8ada52f34198d128063f Mon Sep 17 00:00:00 2001 From: David Weber Date: Tue, 21 Nov 2017 18:36:16 +0100 Subject: [PATCH 080/104] Review of seeder --- README.md | 23 ++++++++--------------- lib/seeds/connection.ts | 8 +++++--- tslint.json | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f9bc3c2f..d7125492 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ How does it work? Just, create a factory for your entities and a seeds 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 awesome the [faker](https://github.com/marak/Faker.js/) library. The create a new "fake" entity and return it. Those file should be in the `src/database/factories` folder and suffixed with `Factory`. Example `src/database/factories/UserFactory.ts`. +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`. ```typescript factory.define(User, (faker: typeof Faker) => { @@ -292,25 +292,18 @@ export class CreateUsers implements SeedsInterface { } ``` -Another example for nested entities. For that we use the each function to so. +Here an example with nested factories. ```typescript ... -await factory.get(Tournament, 2) - .each(async (tournament: Tournament) => { +await factory.get(User) + .each(async (user: User) => { - const teams: Team[] = await factory.get(Team) - .each(async (team: Team) => { + const pets: Pet[] = await factory.get(Pet) + .create(2); - const users: User[] = await factory.get(User).create(2); - const userIds = users.map((u: User) => u.Id); - await team.users().attach(userIds); - - }) - .create(getRandomNumber(tournament.MaxSize)); - - const teamIds = teams.map((t: Team) => t.Id); - await tournament.teams().attach(teamIds); + const petIds = pets.map((pet: Pet) => pet.Id); + await user.pets().attach(petIds); }) .create(5); diff --git a/lib/seeds/connection.ts b/lib/seeds/connection.ts index 7d383b44..37c1f526 100644 --- a/lib/seeds/connection.ts +++ b/lib/seeds/connection.ts @@ -10,14 +10,16 @@ const logging = args.indexOf('--logging') >= 0 || args.indexOf('-L') >= 0 || fal const configParam = '--config'; const hasConfigPath = args.indexOf(configParam) >= 0 || false; const indexOfConfigPath = args.indexOf(configParam) + 1; -const ormconfig = (hasConfigPath) - ? require(`${args[indexOfConfigPath]}`) - : require(`${runDir}/ormconfig.json`); /** * Returns a TypeORM database connection for our entity-manager */ export const getConnection = async (): Promise => { + + const ormconfig = (hasConfigPath) + ? require(`${args[indexOfConfigPath]}`) + : require(`${runDir}/ormconfig.json`); + try { const connection = await createConnection({ type: (ormconfig as any).type as any, diff --git a/tslint.json b/tslint.json index 689ca78a..3a9f5ee3 100644 --- a/tslint.json +++ b/tslint.json @@ -6,7 +6,7 @@ 160 ], "no-unnecessary-initializer": false, - "no-var-requires": false, + "no-var-requires": true, "no-null-keyword": true, "no-consecutive-blank-lines": false, "quotemark": [ From 6b3d6c983b77eaaeb978b93ec12f0791cf8bfebf Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:40:18 +0100 Subject: [PATCH 081/104] Add missed parameter to the factory interface --- src/lib/seeds/FactoryInterface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/seeds/FactoryInterface.ts b/src/lib/seeds/FactoryInterface.ts index a37c5edf..325bf023 100644 --- a/src/lib/seeds/FactoryInterface.ts +++ b/src/lib/seeds/FactoryInterface.ts @@ -9,7 +9,7 @@ export interface FactoryInterface { /** * Returns an EntityFactoryInterface */ - get(entityClass: ObjectType): EntityFactoryInterface; + get(entityClass: ObjectType, args: any[]): EntityFactoryInterface; /** * Define an entity faker */ From 0626de5f37f72fcee0e7da6c6c30d8c155aa4185 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 18:48:22 +0100 Subject: [PATCH 082/104] Explained second paramter of the factory.get --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index e44bf3de..1e85a687 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,20 @@ export class CreateUsers implements SeedsInterface { public async seed(factory: FactoryInterface): Promise { await factory .get(User) + .create(10); + } + +} +``` + +With the second parameter in the `.get(, )` you are able to create different variations of entities. + +```typescript +export class CreateUsers implements SeedsInterface { + + public async seed(factory: FactoryInterface): Promise { + await factory + .get(User, 'admin') .create(1); } From 796612d8e03bf6d1dfcc75824b4cc9b63f7a32b1 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 21:04:37 +0100 Subject: [PATCH 083/104] Fix travis ci --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c79b4af4..0aa607b1 100755 --- a/.travis.yml +++ b/.travis.yml @@ -8,3 +8,5 @@ scripts: - npm start build notifications: email: false +env: + - APP_ROUTE="http://localhost:3000" From e1d7892337891c085e31bf42e0c56c1f4768f394 Mon Sep 17 00:00:00 2001 From: David Weber Date: Tue, 21 Nov 2017 21:26:09 +0100 Subject: [PATCH 084/104] As middlewares are DI aware we should use the constructor to mimic the same behaviour as in e.g. controllers --- src/api/middlewares/ErrorHandlerMiddleware.ts | 7 +++++-- test/unit/middlewares/ErrorHandlerMiddleware.test.ts | 6 ++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index 76ad95c4..505e31a2 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -1,15 +1,18 @@ import * as express from 'express'; import { Middleware, ExpressErrorMiddlewareInterface, HttpError } from 'routing-controllers'; import { env } from '../../core/env'; -import { Logger } from '../../core/Logger'; +import { Logger, LoggerInterface } from '../../decorators/Logger'; @Middleware({ type: 'after' }) export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { - public log = new Logger(__filename); public isProduction = env.isProduction; + constructor( + @Logger(__filename) private log: LoggerInterface + ) { } + public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void { res.status(error.httpCode || 500); diff --git a/test/unit/middlewares/ErrorHandlerMiddleware.test.ts b/test/unit/middlewares/ErrorHandlerMiddleware.test.ts index 22d05c2b..e039c182 100644 --- a/test/unit/middlewares/ErrorHandlerMiddleware.test.ts +++ b/test/unit/middlewares/ErrorHandlerMiddleware.test.ts @@ -7,8 +7,7 @@ import { LogMock } from '../lib/LogMock'; describe('ErrorHandlerMiddleware', () => { test('Should not print stack out in production', () => { - const middleware = new ErrorHandlerMiddleware(); - middleware.log = new LogMock(); + const middleware = new ErrorHandlerMiddleware(new LogMock()); middleware.isProduction = true; const res = new MockExpressResponse(); @@ -21,8 +20,7 @@ describe('ErrorHandlerMiddleware', () => { }); test('Should print stack out in production', () => { - const middleware = new ErrorHandlerMiddleware(); - middleware.log = new LogMock(); + const middleware = new ErrorHandlerMiddleware(new LogMock()); middleware.isProduction = false; const res = new MockExpressResponse(); From bc636087090fa007363f0edf5e6cb19bd88596aa Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 21:33:07 +0100 Subject: [PATCH 085/104] Fix factory interface issue --- src/lib/seeds/FactoryInterface.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/seeds/FactoryInterface.ts b/src/lib/seeds/FactoryInterface.ts index 325bf023..b4796081 100644 --- a/src/lib/seeds/FactoryInterface.ts +++ b/src/lib/seeds/FactoryInterface.ts @@ -9,9 +9,9 @@ export interface FactoryInterface { /** * Returns an EntityFactoryInterface */ - get(entityClass: ObjectType, args: any[]): EntityFactoryInterface; + get(entityClass: ObjectType, value?: any): EntityFactoryInterface; /** * Define an entity faker */ - define(entityClass: ObjectType, fakerFunction: (faker: typeof Faker, args: any[]) => Entity): void; + define(entityClass: ObjectType, fakerFunction: (faker: typeof Faker, value?: any) => Entity): void; } From f73814bf72707d5571a926b0e29a403a30186f41 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 21:33:21 +0100 Subject: [PATCH 086/104] Add test env --- .env.test | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/core/env.ts | 9 ++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 .env.test diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..f08dec70 --- /dev/null +++ b/.env.test @@ -0,0 +1,48 @@ +# +# APPLICATION +# +APP_NAME="express-typescript-boilerplate" +APP_ROUTE="http://localhost:3000" +APP_ROUTE_PREFIX="/api" +APP_BANNER=false + +# +# LOGGING +# +LOG_LEVEL="none" +LOG_JSON=false +LOG_OUTPUT="dev" + +# +# AUTHORIZATION +# +AUTH_ROUTE="http://localhost:3333/tokeninfo" + +# +# DATABASE +# +DB_TYPE="mysql" +DB_HOST="localhost" +DB_PORT=3306 +DB_USERNAME="root" +DB_PASSWORD="" +DB_DATABASE="my_database" +DB_SYNCHRONIZE=false +DB_LOGGING=false + +# +# Swagger +# +SWAGGER_ENABLED=true +SWAGGER_ROUTE="/swagger" +SWAGGER_FILE="api/swagger.json" +SWAGGER_USERNAME="admin" +SWAGGER_PASSWORD="1234" + +# +# Status Monitor +# +MONITOR_ENABLED=true +MONITOR_ROUTE="/monitor" +MONITOR_USERNAME="admin" +MONITOR_PASSWORD="1234" diff --git a/src/core/env.ts b/src/core/env.ts index 266dc10c..d0a64622 100644 --- a/src/core/env.ts +++ b/src/core/env.ts @@ -1,8 +1,11 @@ import * as path from 'path'; -import * as pkg from '../../package.json'; import * as dotenv from 'dotenv'; -dotenv.config(); +import * as pkg from '../../package.json'; +/** + * Load .env file or for tests the .env.test file. + */ +dotenv.config({ path: path.join(process.cwd(), `.env${((process.env.NODE_ENV === 'test') ? '.test' : '')}`) }); /** * Environment variables @@ -22,7 +25,7 @@ export const env = { migrations: [path.join(__dirname, '..', 'database/migrations/*.ts')], migrationsDir: path.join(__dirname, '..', 'database/migrations'), entities: [path.join(__dirname, '..', 'api/**/models/*{.js,.ts}')], - subscribers: [ path.join(__dirname, '..', 'api/**/*Subscriber{.js,.ts}')], + subscribers: [path.join(__dirname, '..', 'api/**/*Subscriber{.js,.ts}')], controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')], middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')], interceptors: [path.join(__dirname, '..', 'api/**/*Interceptor{.js,.ts}')], From b712c20b5228d87482a000b21db0143e9574ee44 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 21:37:59 +0100 Subject: [PATCH 087/104] Remove travis ci env var --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0aa607b1..c79b4af4 100755 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,3 @@ scripts: - npm start build notifications: email: false -env: - - APP_ROUTE="http://localhost:3000" From aac3b338e3421b392824153f74d11808765d48cb Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 22:00:23 +0100 Subject: [PATCH 088/104] Fix nps db.seed task --- package-scripts.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index 1c8eed66..2f98d504 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -49,30 +49,30 @@ module.exports = { migrate: { script: series( 'nps banner.migrate', - 'nps migrate.config', + 'nps db.config', runFast('./node_modules/.bin/typeorm migrations:run'), ), }, revert: { script: series( 'nps banner.revert', - 'nps migrate.config', + 'nps db.config', runFast('./node_modules/.bin/typeorm migrations:revert'), ), }, seed: { script: series( 'nps banner.seed', - 'nps migrate.config', - runFast('./src/lib/seeds.ts'), + 'nps db.config', + runFast('./src/lib/seeds/'), ), }, config: { script: runFast('./src/lib/ormconfig.ts'), }, drop: { - script: runFast('./node_modules/.bin/typeorm schema:drop') - } + script: runFast('./node_modules/.bin/typeorm schema:drop'), + }, }, /** * These run various kinds of tests. Default is unit. From e44a08bdd6ada9179e51a53616922664f19591f5 Mon Sep 17 00:00:00 2001 From: David Weber Date: Tue, 21 Nov 2017 22:00:36 +0100 Subject: [PATCH 089/104] Use LogMock and check for logger infos to be called --- src/auth/AuthService.ts | 2 +- test/unit/auth/AuthService.test.ts | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index ddba5fe1..676b8e9e 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -27,7 +27,7 @@ export class AuthService { return authorization.split(' ')[1]; } - this.log.info('No Token provided by the client'); + this.log.info('No Token provided by the client'); return; } diff --git a/test/unit/auth/AuthService.test.ts b/test/unit/auth/AuthService.test.ts index b210e405..1d8f2c1c 100644 --- a/test/unit/auth/AuthService.test.ts +++ b/test/unit/auth/AuthService.test.ts @@ -2,21 +2,17 @@ import { Request } from 'express'; import * as request from 'request'; import * as MockExpressRequest from 'mock-express-request'; import * as nock from 'nock'; +import { LogMock } from './../lib/LogMock'; import { AuthService } from './../../../src/auth/AuthService'; -import { LoggerInterface } from './../../../src/core/LoggerInterface'; import { env } from './../../../src/core/env'; describe('AuthService', () => { let authService: AuthService; + let log: LogMock; beforeEach(() => { - const log: LoggerInterface = { - debug: () => void 0, - info: () => void 0, - warn: () => void 0, - error: () => void 0, - }; + log = new LogMock(); authService = new AuthService(request, log); }); @@ -39,12 +35,14 @@ describe('AuthService', () => { }); const token = authService.parseTokenFromRequest(req); expect(token).toBeUndefined(); + expect(log.infoMock).toBeCalledWith('info', 'No Token provided by the client', []); }); test('Should return undefined if there is no "Authorization" header', () => { const req: Request = new MockExpressRequest(); const token = authService.parseTokenFromRequest(req); expect(token).toBeUndefined(); + expect(log.infoMock).toBeCalledWith('info', 'No Token provided by the client', []); }); }); From a6389f9d4572bfeceef1c30f83bcc9980726a0ce Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Tue, 21 Nov 2017 22:03:36 +0100 Subject: [PATCH 090/104] Remove unnecessary tsconfig settings --- tsconfig.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index af198af2..9f803e59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,19 +17,6 @@ "noFallthroughCasesInSwitch": true, "moduleResolution": "node", "baseUrl": ".", - "paths": { - "*": [ - "node_modules/*", - "src/types/*" - ] - }, - "rootDirs": [], - "typeRoots": [ - "src/types" - ], - "types": [ - "node" - ], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true From 0c7e24352ad503adaa3f377aacbaf858cb809b58 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Wed, 22 Nov 2017 07:47:14 +0100 Subject: [PATCH 091/104] Adjust nps for windows --- package-scripts.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index 2f98d504..b9b53e47 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -17,7 +17,7 @@ module.exports = { serve: { script: series( 'nps banner.serve', - '\"./node_modules/.bin/nodemon\" --watch src --watch .env', + '\"./node_modules/.bin/nodemon\" --watch src --watch \".env\"', ), }, /** @@ -50,28 +50,28 @@ module.exports = { script: series( 'nps banner.migrate', 'nps db.config', - runFast('./node_modules/.bin/typeorm migrations:run'), + runFast('\"./node_modules/.bin/typeorm\" migrations:run'), ), }, revert: { script: series( 'nps banner.revert', 'nps db.config', - runFast('./node_modules/.bin/typeorm migrations:revert'), + runFast('\"./node_modules/.bin/typeorm\" migrations:revert'), ), }, seed: { script: series( 'nps banner.seed', 'nps db.config', - runFast('./src/lib/seeds/'), + runFast('\"./src/lib/seeds/\"'), ), }, config: { - script: runFast('./src/lib/ormconfig.ts'), + script: runFast('\"./src/lib/ormconfig.ts\"'), }, drop: { - script: runFast('./node_modules/.bin/typeorm schema:drop'), + script: runFast('\"./node_modules/.bin/typeorm\" schema:drop'), }, }, /** @@ -88,10 +88,10 @@ module.exports = { ), }, pretest: { - script: './node_modules/.bin/tslint -c ./tslint.json -t stylish "./test/unit/**/*.ts"' + script: '\"./node_modules/.bin/tslint\" -c \"./tslint.json\" -t stylish \"./test/unit/**/*.ts\"' }, run: { - script: './node_modules/.bin/cross-env NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=unit' + script: '\"./node_modules/.bin/cross-env\" NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=unit' }, verbose: { script: 'nps "test --verbose"' @@ -110,14 +110,14 @@ module.exports = { ), }, pretest: { - script: './node_modules/.bin/tslint -c ./tslint.json -t stylish "./test/e2e/**/*.ts"' + script: '\"./node_modules/.bin/tslint\" -c \"./tslint.json\" -t stylish \"./test/e2e/**/*.ts\"' }, verbose: { script: 'nps "test.e2e --verbose"' }, run: series( `wait-on --timeout 120000 http-get://localhost:3000/api/info`, - './node_modules/.bin/cross-env NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=e2e -i', + '\"./node_modules/.bin/cross-env\" NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=e2e -i', ), } }, @@ -125,13 +125,13 @@ module.exports = { * Runs TSLint over your project */ lint: { - script: `./node_modules/.bin/tslint -c ./tslint.json -p tsconfig.json 'src/**/*.ts' --format stylish` + script: `\"./node_modules/.bin/tslint\" -c \"./tslint.json\" -p tsconfig.json \"src/**/*.ts\" --format stylish` }, /** * Transpile your app into javascript */ transpile: { - script: `./node_modules/.bin/tsc` + script: `\"./node_modules/.bin/tsc\"` }, /** * Clean files and folders @@ -144,7 +144,7 @@ module.exports = { ), }, dist: { - script: `./node_modules/.bin/trash './dist'` + script: `\"./node_modules/.bin/trash\" \"./dist\"` } }, /** @@ -159,14 +159,14 @@ module.exports = { }, swagger: { script: copy( - './src/api/swagger.json', - './dist', + '\"./src/api/swagger.json\"', + '\"./dist\"', ), }, public: { script: copy( - './src/public/*', - './dist', + '\"./src/public/*\"', + '\"./dist\"', ), } }, @@ -191,16 +191,16 @@ function banner(name) { silent: true, logLevel: 'error', description: `Shows ${name} banners to the console`, - script: runFast(`./src/lib/banner.ts ${name}`), + script: runFast(`\"./src/lib/banner.ts\" ${name}`), }; } function copy(source, target) { - return `./node_modules/.bin/copyup ${source} ${target}`; + return `\"./node_modules/.bin/copyup\" ${source} ${target}`; } function run(path) { - return `./node_modules/.bin/ts-node ${path}`; + return `\"./node_modules/.bin/ts-node\" ${path}`; } function runFast(path) { From 2442dc67a96ccdc097e27882ec70e0e58699fa98 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:01:50 +0100 Subject: [PATCH 092/104] Remove comment --- src/auth/authorizationChecker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/authorizationChecker.ts b/src/auth/authorizationChecker.ts index 946607d1..2828c0d4 100644 --- a/src/auth/authorizationChecker.ts +++ b/src/auth/authorizationChecker.ts @@ -20,7 +20,7 @@ export function authorizationChecker(connection: Connection): (action: Action, r if (token === undefined) { log.warn('No token given'); - return false; // res.failed(403, 'You are not allowed to request this resource!'); + return false; } // Request user info at auth0 with the provided token From 1713df643a8b8e337b574692146b005b4e542858 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:02:02 +0100 Subject: [PATCH 093/104] Fix naming of the middlewares --- src/api/middlewares/LogMiddleware.ts | 2 +- src/api/middlewares/SecurityHstsMiddleware.ts | 2 +- src/api/middlewares/SecurityNoCacheMiddleware.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/middlewares/LogMiddleware.ts b/src/api/middlewares/LogMiddleware.ts index 02bf97da..9399b72b 100644 --- a/src/api/middlewares/LogMiddleware.ts +++ b/src/api/middlewares/LogMiddleware.ts @@ -7,7 +7,7 @@ import { env } from '../../core/env'; @Middleware({ type: 'before' }) -export class SecurityMiddleware implements ExpressMiddlewareInterface { +export class LogMiddleware implements ExpressMiddlewareInterface { private log = new Logger(__dirname); diff --git a/src/api/middlewares/SecurityHstsMiddleware.ts b/src/api/middlewares/SecurityHstsMiddleware.ts index 430d3287..76665e2c 100644 --- a/src/api/middlewares/SecurityHstsMiddleware.ts +++ b/src/api/middlewares/SecurityHstsMiddleware.ts @@ -5,7 +5,7 @@ import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; @Middleware({ type: 'before' }) -export class SecurityMiddleware implements ExpressMiddlewareInterface { +export class SecurityHstsMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return helmet.hsts({ diff --git a/src/api/middlewares/SecurityNoCacheMiddleware.ts b/src/api/middlewares/SecurityNoCacheMiddleware.ts index f46b159b..2278f3d7 100644 --- a/src/api/middlewares/SecurityNoCacheMiddleware.ts +++ b/src/api/middlewares/SecurityNoCacheMiddleware.ts @@ -5,7 +5,7 @@ import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; @Middleware({ type: 'before' }) -export class SecurityMiddleware implements ExpressMiddlewareInterface { +export class SecurityNoCacheMiddleware implements ExpressMiddlewareInterface { public use(req: express.Request, res: express.Response, next: express.NextFunction): any { return helmet.noCache()(req, res, next); From 62ba4cabcfaa59d46552c271bba9a437bb65de48 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:03:24 +0100 Subject: [PATCH 094/104] Fix nps --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e908fe90..2911317a 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,13 @@ "description": "A delightful way to building a RESTful API with NodeJs & TypeScript", "main": "src/app.ts", "scripts": { - "start": "nps", + "start": "\"./node_modules/.bin/nps\"", "test": "npm start test", "build": "npm start build" }, + "engines": { + "node": ">=8.0.0" + }, "repository": "git+ssh://git@github.com/w3tec/express-typescript-boilerplate.git", "keywords": [ "NodeJS", From 7000c029c6408ec66811ea4348b8dc8a04c299ef Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:05:14 +0100 Subject: [PATCH 095/104] Move production libs from devDependencies to dependencies --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2911317a..91e27dc6 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "@types/helmet": "^0.0.37", "@types/lodash": "^4.14.80", "@types/morgan": "^1.7.35", + "@types/chalk": "^2.2.0", + "@types/commander": "^2.11.0", "@types/reflect-metadata": "0.0.5", "@types/request": "^2.0.8", "@types/serve-favicon": "^2.2.29", @@ -84,6 +86,10 @@ "typeorm-typedi-extensions": "^0.1.1", "typescript": "^2.6.1", "uuid": "^3.1.0", + "nps": "^5.7.1", + "nps-utils": "^1.5.0", + "chalk": "^2.3.0", + "commander": "^2.11.0", "winston": "^2.4.0" }, "jest": { @@ -102,18 +108,12 @@ }, "license": "MIT", "devDependencies": { - "@types/chalk": "^2.2.0", - "@types/commander": "^2.11.0", "@types/jest": "^21.1.5", - "chalk": "^2.3.0", - "commander": "^2.11.0", "cross-env": "^5.1.1", "jest": "^21.2.1", "mock-express-request": "^0.2.0", "mock-express-response": "^0.2.1", "nock": "^9.1.0", - "nps": "^5.7.1", - "nps-utils": "^1.5.0", "ts-jest": "^21.1.4" } } From 0c0e0035b1b0b5d1b1ed40f81ac0b5af053efd6d Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:08:24 +0100 Subject: [PATCH 096/104] Adjust error handler middleware --- src/api/middlewares/ErrorHandlerMiddleware.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index 505e31a2..d30becdd 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -3,6 +3,10 @@ import { Middleware, ExpressErrorMiddlewareInterface, HttpError } from 'routing- import { env } from '../../core/env'; import { Logger, LoggerInterface } from '../../decorators/Logger'; +interface ErrorInterface extends HttpError { + errors: any[]; +} + @Middleware({ type: 'after' }) export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { @@ -13,22 +17,17 @@ export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { @Logger(__filename) private log: LoggerInterface ) { } - public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void { + public error(error: ErrorInterface, req: express.Request, res: express.Response, next: express.NextFunction): void { res.status(error.httpCode || 500); + res.json({ + name: error.name, + message: error.message, + errors: error.errors || [], + }); - // Standard output of an error to the user. if (this.isProduction) { - res.json({ - name: error.name, - message: error.message, - }); this.log.error(error.name, error.message); } else { - res.json({ - name: error.name, - message: error.message, - stack: error.stack, - }); this.log.error(error.name, error.stack); } From 655d175ed5879484763ea9a792146e935b7e48cc Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:10:20 +0100 Subject: [PATCH 097/104] Fix debug process --- .vscode/launch.json | 1 - README.md | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 41f20e83..12ada0bd 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,6 @@ "../dist/**/*.js" ], "protocol": "inspector", - "preLaunchTask": "build", "env": { "NODE_ENV": "development" } diff --git a/README.md b/README.md index 1e85a687..213b4a6b 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,8 @@ All script are defined in the package.json file, but the most important ones are ## Debugger in VSCode -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. ## API Routes From 6ff19fc75a5eeeea7045c203dde0bb0dae2fe54b Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:33:05 +0100 Subject: [PATCH 098/104] Fix unit test for the error handler middleware --- src/api/middlewares/ErrorHandlerMiddleware.ts | 8 ++---- test/unit/auth/AuthService.test.ts | 4 +-- test/unit/lib/LogMock.ts | 8 +++--- .../ErrorHandlerMiddleware.test.ts | 25 +++++++++++-------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/api/middlewares/ErrorHandlerMiddleware.ts b/src/api/middlewares/ErrorHandlerMiddleware.ts index d30becdd..ae89a3b4 100644 --- a/src/api/middlewares/ErrorHandlerMiddleware.ts +++ b/src/api/middlewares/ErrorHandlerMiddleware.ts @@ -3,10 +3,6 @@ import { Middleware, ExpressErrorMiddlewareInterface, HttpError } from 'routing- import { env } from '../../core/env'; import { Logger, LoggerInterface } from '../../decorators/Logger'; -interface ErrorInterface extends HttpError { - errors: any[]; -} - @Middleware({ type: 'after' }) export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { @@ -17,12 +13,12 @@ export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { @Logger(__filename) private log: LoggerInterface ) { } - public error(error: ErrorInterface, req: express.Request, res: express.Response, next: express.NextFunction): void { + public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void { res.status(error.httpCode || 500); res.json({ name: error.name, message: error.message, - errors: error.errors || [], + errors: error['errors'] || [], }); if (this.isProduction) { diff --git a/test/unit/auth/AuthService.test.ts b/test/unit/auth/AuthService.test.ts index 1d8f2c1c..309b9749 100644 --- a/test/unit/auth/AuthService.test.ts +++ b/test/unit/auth/AuthService.test.ts @@ -35,14 +35,14 @@ describe('AuthService', () => { }); const token = authService.parseTokenFromRequest(req); expect(token).toBeUndefined(); - expect(log.infoMock).toBeCalledWith('info', 'No Token provided by the client', []); + expect(log.infoMock).toBeCalledWith('No Token provided by the client', []); }); test('Should return undefined if there is no "Authorization" header', () => { const req: Request = new MockExpressRequest(); const token = authService.parseTokenFromRequest(req); expect(token).toBeUndefined(); - expect(log.infoMock).toBeCalledWith('info', 'No Token provided by the client', []); + expect(log.infoMock).toBeCalledWith('No Token provided by the client', []); }); }); diff --git a/test/unit/lib/LogMock.ts b/test/unit/lib/LogMock.ts index 94306ef8..08f98942 100644 --- a/test/unit/lib/LogMock.ts +++ b/test/unit/lib/LogMock.ts @@ -9,19 +9,19 @@ export class LogMock extends Logger { public errorMock = jest.fn(); public debug(message: string, ...args: any[]): void { - this.debugMock('debug', message, args); + this.debugMock(message, args); } public info(message: string, ...args: any[]): void { - this.infoMock('info', message, args); + this.infoMock(message, args); } public warn(message: string, ...args: any[]): void { - this.warnMock('warn', message, args); + this.warnMock(message, args); } public error(message: string, ...args: any[]): void { - this.errorMock('error', message, args); + this.errorMock(message, args); } } diff --git a/test/unit/middlewares/ErrorHandlerMiddleware.test.ts b/test/unit/middlewares/ErrorHandlerMiddleware.test.ts index e039c182..c7a4e709 100644 --- a/test/unit/middlewares/ErrorHandlerMiddleware.test.ts +++ b/test/unit/middlewares/ErrorHandlerMiddleware.test.ts @@ -6,30 +6,33 @@ import { LogMock } from '../lib/LogMock'; describe('ErrorHandlerMiddleware', () => { + let log; + let middleware; + let err; + let res; + beforeEach(() => { + log = new LogMock(); + middleware = new ErrorHandlerMiddleware(log); + res = new MockExpressResponse(); + err = new HttpError(400, 'Test Error'); + }); + test('Should not print stack out in production', () => { - const middleware = new ErrorHandlerMiddleware(new LogMock()); middleware.isProduction = true; - - const res = new MockExpressResponse(); - const err = new HttpError(400, 'Test Error'); middleware.error(err, undefined, res, undefined); const json = res._getJSON(); expect(json.name).toBe(err.name); expect(json.message).toBe(err.message); - expect(json.stack).toBeUndefined(); + expect(log.errorMock).toHaveBeenCalledWith(err.name, [err.message]); }); - test('Should print stack out in production', () => { - const middleware = new ErrorHandlerMiddleware(new LogMock()); + test('Should print stack out in development', () => { middleware.isProduction = false; - - const res = new MockExpressResponse(); - const err = new HttpError(400, 'Test Error'); middleware.error(err, undefined, res, undefined); const json = res._getJSON(); expect(json.name).toBe(err.name); expect(json.message).toBe(err.message); - expect(json.stack).toBeDefined(); + expect(log.errorMock).toHaveBeenCalled(); }); }); From 8942dd1a1968b3264b90f81d3cf50a260d2b492e Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Thu, 23 Nov 2017 17:38:13 +0100 Subject: [PATCH 099/104] Add requests and response folders to the app structure --- README.md | 60 +++++++++++++------------- src/api/controllers/requests/.gitkeep | 0 src/api/controllers/responses/.gitkeep | 0 3 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 src/api/controllers/requests/.gitkeep create mode 100644 src/api/controllers/responses/.gitkeep diff --git a/README.md b/README.md index 213b4a6b..4a966d99 100644 --- a/README.md +++ b/README.md @@ -170,35 +170,37 @@ The swagger and the monitor route can be altered in the `.env` file. ## Project Structure -| Name | Description | -| ----------------------------- | ----------- | -| **.vscode/** | VSCode tasks, launch configuration and some other settings | -| **dist/** | Compiled source files will be placed here | -| **src/** | Source files | -| **src/api/controllers/** | REST API Controllers | -| **src/api/errors/** | Custom HttpErrors like 404 NotFound | -| **src/api/interceptors/** | Interceptors are used to change or replace the data returned to the client. | -| **src/api/middlewares/** | Express Middlewares like helmet security features | -| **src/api/models/** | Bookshelf Models | -| **src/api/repositories/** | Repository / DB layer | -| **src/api/services/** | Service layer | -| **src/api/subscribers/** | Event subscribers | -| **src/api/validators/** | Custom validators, which can be used in the request classes | -| **src/api/** swagger.json | Swagger documentation | -| **src/auth/** | Authentication checkers and services | -| **src/core/** | The core features like logger and env variables | -| **src/database/factories** | Factory the generate fake entities | -| **src/database/migrations** | Database migration scripts | -| **src/database/seeds** | Seeds to create some data in the database | -| **src/decorators/** | Custom decorators like @Logger & @EventDispatch | -| **src/loaders/** | Loader is a place where you can configure your app | -| **src/public/** | Static assets (fonts, css, js, img). | -| **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | -| **test** | Tests | -| **test/e2e/** *.test.ts | End-2-End tests (like e2e) | -| **test/unit/** *.test.ts | Unit tests | -| .env.example | Environment configurations | -| ormconfig.json | TypeORM configuration for the database. Used by seeds and the migration. (generated file) | +| Name | Description | +| --------------------------------- | ----------- | +| **.vscode/** | VSCode tasks, launch configuration and some other settings | +| **dist/** | Compiled source files will be placed here | +| **src/** | Source files | +| **src/api/controllers/** | REST API Controllers | +| **src/api/controllers/requests** | Request classes with validation rules if the body is not equal with a model | +| **src/api/controllers/responses** | Response classes or interfaces to type json response bodies | +| **src/api/errors/** | Custom HttpErrors like 404 NotFound | +| **src/api/interceptors/** | Interceptors are used to change or replace the data returned to the client. | +| **src/api/middlewares/** | Express Middlewares like helmet security features | +| **src/api/models/** | Bookshelf Models | +| **src/api/repositories/** | Repository / DB layer | +| **src/api/services/** | Service layer | +| **src/api/subscribers/** | Event subscribers | +| **src/api/validators/** | Custom validators, which can be used in the request classes | +| **src/api/** swagger.json | Swagger documentation | +| **src/auth/** | Authentication checkers and services | +| **src/core/** | The core features like logger and env variables | +| **src/database/factories** | Factory the generate fake entities | +| **src/database/migrations** | Database migration scripts | +| **src/database/seeds** | Seeds to create some data in the database | +| **src/decorators/** | Custom decorators like @Logger & @EventDispatch | +| **src/loaders/** | Loader is a place where you can configure your app | +| **src/public/** | Static assets (fonts, css, js, img). | +| **src/types/** *.d.ts | Custom type definitions and files that aren't on DefinitelyTyped | +| **test** | Tests | +| **test/e2e/** *.test.ts | End-2-End tests (like e2e) | +| **test/unit/** *.test.ts | Unit tests | +| .env.example | Environment configurations | +| ormconfig.json | TypeORM configuration for the database. Used by seeds and the migration. (generated file) | ## Logging diff --git a/src/api/controllers/requests/.gitkeep b/src/api/controllers/requests/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/api/controllers/responses/.gitkeep b/src/api/controllers/responses/.gitkeep new file mode 100644 index 00000000..e69de29b From 4b9638c33b653b6e205f8ce49ab58376e027f4cb Mon Sep 17 00:00:00 2001 From: David Weber Date: Sat, 25 Nov 2017 11:39:00 +0100 Subject: [PATCH 100/104] Fix nps for windows - Remove trailing comma - cleanup the mess of direct node_module access - db section isn't working because of typeorm --- package-scripts.js | 90 ++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index b9b53e47..48985f01 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -1,3 +1,7 @@ +/** + * Windows: Please do not use trailing comma as windows will fail with token error + */ + const { series, crossEnv, concurrent, rimraf, runInNewWindow } = require('nps-utils'); module.exports = { @@ -17,8 +21,8 @@ module.exports = { serve: { script: series( 'nps banner.serve', - '\"./node_modules/.bin/nodemon\" --watch src --watch \".env\"', - ), + 'nodemon --watch src --watch .env' + ) }, /** * Setup's the development environment and the database @@ -28,7 +32,7 @@ module.exports = { 'yarn install', 'nps db.migrate', 'nps db.seed' - ), + ) }, /** * Builds the app into the dist directory @@ -39,8 +43,8 @@ module.exports = { 'nps lint', 'nps clean.dist', 'nps transpile', - 'nps copy', - ), + 'nps copy' + ) }, /** * Database scripts @@ -50,29 +54,29 @@ module.exports = { script: series( 'nps banner.migrate', 'nps db.config', - runFast('\"./node_modules/.bin/typeorm\" migrations:run'), - ), + runFast('\"./node_modules/.bin/typeorm\" migrations:run') + ) }, revert: { script: series( 'nps banner.revert', 'nps db.config', - runFast('\"./node_modules/.bin/typeorm\" migrations:revert'), - ), + runFast('\"./node_modules/.bin/typeorm\" migrations:revert') + ) }, seed: { script: series( 'nps banner.seed', 'nps db.config', - runFast('\"./src/lib/seeds/\"'), - ), + runFast('./src/lib/seeds/') + ) }, config: { - script: runFast('\"./src/lib/ormconfig.ts\"'), + script: runFast('./src/lib/ormconfig.ts') }, drop: { - script: runFast('\"./node_modules/.bin/typeorm\" schema:drop'), - }, + script: runFast('\"./node_modules/.bin/typeorm\" schema:drop') + } }, /** * These run various kinds of tests. Default is unit. @@ -84,21 +88,21 @@ module.exports = { script: series( 'nps banner.test', 'nps test.unit.pretest', - 'nps test.unit.run', - ), + 'nps test.unit.run' + ) }, pretest: { - script: '\"./node_modules/.bin/tslint\" -c \"./tslint.json\" -t stylish \"./test/unit/**/*.ts\"' + script: 'tslint -c ./tslint.json -t stylish ./test/unit/**/*.ts' }, run: { - script: '\"./node_modules/.bin/cross-env\" NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=unit' + script: 'cross-env NODE_ENV=test jest --testPathPattern=unit' }, verbose: { script: 'nps "test --verbose"' }, coverage: { script: 'nps "test --coverage"' - }, + } }, e2e: { default: { @@ -106,32 +110,32 @@ module.exports = { 'nps banner.test', 'nps test.e2e.pretest', runInNewWindow(series('nps build', 'nps start')), - 'nps test.e2e.run', - ), + 'nps test.e2e.run' + ) }, pretest: { - script: '\"./node_modules/.bin/tslint\" -c \"./tslint.json\" -t stylish \"./test/e2e/**/*.ts\"' + script: 'tslint -c ./tslint.json -t stylish ./test/e2e/**/*.ts' }, verbose: { script: 'nps "test.e2e --verbose"' }, run: series( `wait-on --timeout 120000 http-get://localhost:3000/api/info`, - '\"./node_modules/.bin/cross-env\" NODE_ENV=test \"./node_modules/.bin/jest\" --testPathPattern=e2e -i', - ), + 'cross-env NODE_ENV=test jest --testPathPattern=e2e -i' + ) } }, /** * Runs TSLint over your project */ lint: { - script: `\"./node_modules/.bin/tslint\" -c \"./tslint.json\" -p tsconfig.json \"src/**/*.ts\" --format stylish` + script: `tslint -c ./tslint.json -p tsconfig.json src/**/*.ts --format stylish` }, /** * Transpile your app into javascript */ transpile: { - script: `\"./node_modules/.bin/tsc\"` + script: `tsc` }, /** * Clean files and folders @@ -140,11 +144,11 @@ module.exports = { default: { script: series( `nps banner.clean`, - `nps clean.dist`, - ), + `nps clean.dist` + ) }, dist: { - script: `\"./node_modules/.bin/trash\" \"./dist\"` + script: rimraf('./dist') } }, /** @@ -154,20 +158,20 @@ module.exports = { default: { script: series( `nps copy.swagger`, - `nps copy.public`, - ), + `nps copy.public` + ) }, swagger: { script: copy( - '\"./src/api/swagger.json\"', - '\"./dist\"', - ), + './src/api/swagger.json', + './dist' + ) }, public: { script: copy( - '\"./src/public/*\"', - '\"./dist\"', - ), + './src/public/*', + './dist' + ) } }, /** @@ -180,9 +184,9 @@ module.exports = { migrate: banner('migrate'), seed: banner('seed'), revert: banner('revert'), - clean: banner('clean'), - }, - }, + clean: banner('clean') + } + } }; function banner(name) { @@ -191,16 +195,16 @@ function banner(name) { silent: true, logLevel: 'error', description: `Shows ${name} banners to the console`, - script: runFast(`\"./src/lib/banner.ts\" ${name}`), + script: runFast(`./src/lib/banner.ts ${name}`), }; } function copy(source, target) { - return `\"./node_modules/.bin/copyup\" ${source} ${target}`; + return `copyup ${source} ${target}`; } function run(path) { - return `\"./node_modules/.bin/ts-node\" ${path}`; + return `ts-node ${path}`; } function runFast(path) { diff --git a/package.json b/package.json index 91e27dc6..433c1c90 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A delightful way to building a RESTful API with NodeJs & TypeScript", "main": "src/app.ts", "scripts": { - "start": "\"./node_modules/.bin/nps\"", + "start": "nps", "test": "npm start test", "build": "npm start build" }, From f4f2b55a6ac917c5e2d07c5553a24d6bae3134fe Mon Sep 17 00:00:00 2001 From: David Weber Date: Sat, 25 Nov 2017 11:49:39 +0100 Subject: [PATCH 101/104] Fix typeorm commands --- package-scripts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-scripts.js b/package-scripts.js index 48985f01..749c6e23 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -54,14 +54,14 @@ module.exports = { script: series( 'nps banner.migrate', 'nps db.config', - runFast('\"./node_modules/.bin/typeorm\" migrations:run') + runFast('./node_modules/typeorm/cli.js migrations:run') ) }, revert: { script: series( 'nps banner.revert', 'nps db.config', - runFast('\"./node_modules/.bin/typeorm\" migrations:revert') + runFast('./node_modules/typeorm/cli.js migrations:revert') ) }, seed: { @@ -75,7 +75,7 @@ module.exports = { script: runFast('./src/lib/ormconfig.ts') }, drop: { - script: runFast('\"./node_modules/.bin/typeorm\" schema:drop') + script: runFast('./node_modules/typeorm/cli.js schema:drop') } }, /** From 54125908fe0a53ffbb60641b3a41ef2413fe6de7 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sat, 25 Nov 2017 12:00:28 +0100 Subject: [PATCH 102/104] Remove trash-cli as we have rimraf in nps --- package.json | 13 ++- yarn.lock | 290 ++------------------------------------------------- 2 files changed, 16 insertions(+), 287 deletions(-) diff --git a/package.json b/package.json index 433c1c90..6a64e623 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "dependencies": { "@types/bluebird": "^3.5.18", "@types/body-parser": "^1.16.7", + "@types/chalk": "^2.2.0", + "@types/commander": "^2.11.0", "@types/cors": "^2.8.1", "@types/dotenv": "^4.0.2", "@types/express": "^4.0.39", @@ -45,15 +47,15 @@ "@types/helmet": "^0.0.37", "@types/lodash": "^4.14.80", "@types/morgan": "^1.7.35", - "@types/chalk": "^2.2.0", - "@types/commander": "^2.11.0", "@types/reflect-metadata": "0.0.5", "@types/request": "^2.0.8", "@types/serve-favicon": "^2.2.29", "@types/uuid": "^3.4.3", "@types/winston": "^2.3.7", "body-parser": "^1.18.2", + "chalk": "^2.3.0", "class-validator": "^0.7.3", + "commander": "^2.11.0", "compression": "^1.7.1", "copyfiles": "^1.2.0", "cors": "^2.8.4", @@ -72,13 +74,14 @@ "morgan": "^1.9.0", "mysql": "^2.15.0", "nodemon": "^1.12.1", + "nps": "^5.7.1", + "nps-utils": "^1.5.0", "path": "^0.12.7", "reflect-metadata": "^0.1.10", "request": "^2.83.0", "routing-controllers": "^0.7.6", "serve-favicon": "^2.4.5", "swagger-ui-express": "^2.0.10", - "trash-cli": "^1.4.0", "ts-node": "^3.3.0", "tslint": "^5.8.0", "typedi": "^0.5.2", @@ -86,10 +89,6 @@ "typeorm-typedi-extensions": "^0.1.1", "typescript": "^2.6.1", "uuid": "^3.1.0", - "nps": "^5.7.1", - "nps-utils": "^1.5.0", - "chalk": "^2.3.0", - "commander": "^2.11.0", "winston": "^2.4.0" }, "jest": { diff --git a/yarn.lock b/yarn.lock index 51f5f1da..5c8650ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,6 @@ # yarn lockfile v1 -"@sindresorhus/df@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/df/-/df-1.0.1.tgz#c69b66f52f6fcdd287c807df210305dbaf78500d" - "@types/bluebird@^3.5.18": version "3.5.18" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6" @@ -193,12 +189,6 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -ansi-align@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" - dependencies: - string-width "^1.0.1" - ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -640,20 +630,6 @@ boom@5.x.x: dependencies: hoek "4.x.x" -boxen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" - dependencies: - ansi-align "^1.1.0" - camelcase "^2.1.0" - chalk "^1.1.1" - cli-boxes "^1.0.0" - filled-array "^1.0.0" - object-assign "^4.0.1" - repeating "^2.0.0" - string-width "^1.0.1" - widest-line "^1.0.0" - boxen@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.2.2.tgz#3f1d4032c30ffea9d4b02c322eaf2ea741dcbce5" @@ -720,7 +696,7 @@ camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" -camelcase@^2.0.0, camelcase@^2.1.0: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -777,7 +753,7 @@ chalk@0.5.1: strip-ansi "^0.3.0" supports-color "^0.2.0" -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -935,20 +911,6 @@ concurrently@^3.4.0: supports-color "^3.2.3" tree-kill "^1.1.0" -configstore@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" - dependencies: - dot-prop "^3.0.0" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" - configstore@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" @@ -1074,7 +1036,7 @@ crc@3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/crc/-/crc-3.2.1.tgz#5d9c8fb77a245cd5eca291e5d2d005334bab0082" -create-error-class@^3.0.0, create-error-class@^3.0.1: +create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" dependencies: @@ -1094,13 +1056,6 @@ cross-env@^5.1.1: cross-spawn "^5.1.0" is-windows "^1.0.0" -cross-spawn-async@^2.1.1: - version "2.2.5" - resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" - dependencies: - lru-cache "^4.0.0" - which "^1.2.8" - cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1255,12 +1210,6 @@ dont-sniff-mimetype@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58" -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" - dependencies: - is-obj "^1.0.0" - dot-prop@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" @@ -1271,12 +1220,6 @@ dotenv@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" -duplexer2@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -1366,10 +1309,6 @@ escape-html@^1.0.1, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-applescript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-applescript/-/escape-string-applescript-2.0.0.tgz#760bca838668e408fe5ee52ce42caf7cb46c5273" - escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1436,17 +1375,6 @@ exec-sh@^0.2.0: dependencies: merge "^1.1.3" -execa@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.4.0.tgz#4eb6467a36a095fabb2970ff9d5e3fb7bce6ebc3" - dependencies: - cross-spawn-async "^2.1.1" - is-stream "^1.1.0" - npm-run-path "^1.0.0" - object-assign "^4.0.1" - path-key "^1.0.0" - strip-eof "^1.0.0" - execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -1605,10 +1533,6 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" -filled-array@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" - finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -1692,16 +1616,6 @@ from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-extra@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" @@ -1838,36 +1752,6 @@ globby@^4.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -got@^5.0.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" - dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" - url-parse-lax "^1.0.0" - got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -1884,7 +1768,7 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2660,12 +2544,6 @@ json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -2697,18 +2575,6 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - optionalDependencies: - graceful-fs "^4.1.9" - -latest-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" - dependencies: - package-json "^2.0.0" - latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -2719,10 +2585,6 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-req@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -2865,7 +2727,7 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@^4.0.0, lru-cache@^4.0.1: +lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" dependencies: @@ -3065,12 +2927,6 @@ morgan@^1.9.0: on-finished "~2.3.0" on-headers "~1.0.1" -mount-point@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mount-point/-/mount-point-1.2.0.tgz#f7712295e1ecd8df2e42ecbe23e07a3660807851" - dependencies: - "@sindresorhus/df" "^1.0.1" - ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" @@ -3165,10 +3021,6 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - nodemon@^1.12.1: version "1.12.1" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.12.1.tgz#996a56dc49d9f16bbf1b78a4de08f13634b3878d" @@ -3219,12 +3071,6 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" -npm-run-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f" - dependencies: - path-key "^1.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3378,7 +3224,7 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@^0.1.0, osenv@^0.1.4: +osenv@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" dependencies: @@ -3403,15 +3249,6 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -package-json@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" - dependencies: - got "^5.0.0" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - package-json@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" @@ -3434,7 +3271,7 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -parse-json@^2.1.0, parse-json@^2.2.0: +parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" dependencies: @@ -3482,10 +3319,6 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" -path-key@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" - path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -3549,22 +3382,12 @@ pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" -pinkie-promise@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-1.0.0.tgz#d1da67f5482563bb7cf57f286ae2822ecfbf3670" - dependencies: - pinkie "^1.0.0" - pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" dependencies: pinkie "^2.0.0" -pinkie@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-1.0.0.tgz#5a47f28ba1015d0201bda7bf0f358e47bec8c7e4" - pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" @@ -3698,13 +3521,6 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -3735,7 +3551,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@2.3.3, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5: +readable-stream@2.3.3, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -3903,7 +3719,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: +rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -3920,12 +3736,6 @@ routing-controllers@^0.7.6: reflect-metadata "^0.1.10" template-url "^1.0.0" -run-applescript@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-3.0.0.tgz#08252093def5b42d1b58654b16c2df9888e27bd7" - dependencies: - execa "^0.4.0" - rx@2.3.24: version "2.3.24" resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" @@ -4061,10 +3871,6 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" -slide@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -4410,10 +4216,6 @@ through@2, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" - timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -4446,26 +4248,6 @@ tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" -trash-cli@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/trash-cli/-/trash-cli-1.4.0.tgz#3288d890c824a5cc978a6c448a9f329b06be069d" - dependencies: - meow "^3.7.0" - trash "^4.0.0" - update-notifier "^1.0.2" - -trash@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/trash/-/trash-4.1.0.tgz#edddcf2c26236d4942e8b27c80d1bd746b7acc77" - dependencies: - escape-string-applescript "^2.0.0" - fs-extra "^0.30.0" - globby "^6.0.0" - pify "^3.0.0" - run-applescript "^3.0.0" - uuid "^3.1.0" - xdg-trashdir "^2.0.0" - tree-kill@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" @@ -4644,27 +4426,10 @@ unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" -update-notifier@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" - dependencies: - boxen "^0.6.0" - chalk "^1.0.0" - configstore "^2.0.0" - is-npm "^1.0.0" - latest-version "^2.0.0" - lazy-req "^1.1.0" - semver-diff "^2.0.0" - xdg-basedir "^2.0.0" - update-notifier@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" @@ -4685,10 +4450,6 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" -user-home@^1.0.0, user-home@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4786,7 +4547,7 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@^1.2.12, which@^1.2.8, which@^1.2.9: +which@^1.2.12, which@^1.2.9: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: @@ -4853,14 +4614,6 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^1.1.2: - version "1.3.4" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - slide "^1.1.5" - write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" @@ -4881,33 +4634,10 @@ 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" -xdg-basedir@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-1.0.1.tgz#14ff8f63a4fdbcb05d5b6eea22b36f3033b9f04e" - dependencies: - user-home "^1.0.0" - -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - dependencies: - os-homedir "^1.0.0" - xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" -xdg-trashdir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/xdg-trashdir/-/xdg-trashdir-2.1.0.tgz#57e33efed7e68d68fd46b5d7344abab37107030c" - dependencies: - "@sindresorhus/df" "^1.0.1" - mount-point "^1.0.0" - pify "^2.2.0" - pinkie-promise "^1.0.0" - user-home "^1.1.0" - xdg-basedir "^1.0.0" - xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" From 16eec803ec49e5399db132ec66189e51340debb2 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sat, 25 Nov 2017 12:05:49 +0100 Subject: [PATCH 103/104] Update dependencies --- yarn.lock | 90 +++++++++++++++---------------------------------------- 1 file changed, 24 insertions(+), 66 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5c8650ec..f6ffd089 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,8 +68,8 @@ "@types/express" "*" "@types/jest@^21.1.5": - version "21.1.6" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.6.tgz#9467945ce33261e4fdd14276576951aa130515aa" + version "21.1.8" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.8.tgz#d497213725684f1e5a37900b17a47c9c018f1a97" "@types/lodash@^4.14.80": version "4.14.85" @@ -700,10 +700,6 @@ camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -797,14 +793,14 @@ cli-boxes@^1.0.0: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" cli-highlight@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-1.1.4.tgz#e45590c14fb18e13865e3899e824c5592cc22926" + version "1.2.1" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-1.2.1.tgz#c8a7cd603e075f7446c75fa60384a6faeab08483" dependencies: - chalk "^1.1.3" - he "^1.1.0" + chalk "^2.3.0" highlight.js "^9.6.0" mz "^2.4.0" - yargs "^4.8.1" + parse5 "^3.0.3" + yargs "^10.0.3" cliui@^2.1.0: version "2.1.0" @@ -854,7 +850,11 @@ commander@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" -commander@^2.11.0, commander@^2.9.0: +commander@^2.11.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.1.tgz#468635c4168d06145b9323356d1da84d14ac4a7a" + +commander@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -1860,10 +1860,6 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -he@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - helmet-csp@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.6.0.tgz#c1f5595afbc5f83e5f1e6c15f842f07a10f6ea04" @@ -2667,10 +2663,6 @@ lodash.assign@^3.0.0: lodash._createassigner "^3.0.0" lodash.keys "^3.0.0" -lodash.assign@^4.0.3, lodash.assign@^4.0.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - lodash.defaults@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz#c7308b18dbf8bc9372d701a73493c61192bd2e2c" @@ -2979,8 +2971,8 @@ nocache@2.0.0: resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980" nock@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/nock/-/nock-9.1.0.tgz#b6fd783abc1e774cb028058ea81207369a735747" + version "9.1.2" + resolved "https://registry.yarnpkg.com/nock/-/nock-9.1.2.tgz#58f7744d781c3bbc0f495dda1915f1abddfff10e" dependencies: chai ">=1.9.2 <4.0.0" debug "^2.2.0" @@ -2989,7 +2981,7 @@ nock@^9.1.0: lodash "~4.17.2" mkdirp "^0.5.0" propagate "0.4.0" - qs "^6.0.2" + qs "^6.5.1" semver "^5.3.0" node-int64@^0.4.0: @@ -3206,12 +3198,6 @@ os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - dependencies: - lcid "^1.0.0" - os-locale@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" @@ -3285,6 +3271,12 @@ parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" +parse5@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + dependencies: + "@types/node" "*" + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -3476,7 +3468,7 @@ punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -qs@6.5.1, qs@^6.0.2, qs@~6.5.1: +qs@6.5.1, qs@^6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -4368,8 +4360,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.3" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.1.3.tgz#b8ae44bc7fbebbedde1350bd3bb72fc4c99833a1" + version "0.1.6" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.1.6.tgz#77c385b4ef0a267a145da62989a369f741beb23d" dependencies: app-root-path "^2.0.1" chalk "^2.0.1" @@ -4539,10 +4531,6 @@ whatwg-url@^4.3.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -4569,10 +4557,6 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" -window-size@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" - winston@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee" @@ -4677,13 +4661,6 @@ yargonaut@^1.1.2: figlet "^1.1.1" parent-require "^1.0.0" -yargs-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" - dependencies: - camelcase "^3.0.0" - lodash.assign "^4.0.6" - yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" @@ -4713,25 +4690,6 @@ yargs@^10.0.3: y18n "^3.2.1" yargs-parser "^8.0.0" -yargs@^4.8.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" - dependencies: - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - lodash.assign "^4.0.3" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.1" - which-module "^1.0.0" - window-size "^0.2.0" - y18n "^3.2.1" - yargs-parser "^2.4.1" - yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" From 1548d253b2d450a30eddd1cdd83b3ab548cf9d39 Mon Sep 17 00:00:00 2001 From: hirsch88 Date: Sat, 25 Nov 2017 17:06:14 +0100 Subject: [PATCH 104/104] Bump new version 3.0.0-beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a64e623..8378c252 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-typescript-boilerplate", - "version": "3.0.0-alpha", + "version": "3.0.0-beta", "description": "A delightful way to building a RESTful API with NodeJs & TypeScript", "main": "src/app.ts", "scripts": {