├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md └── nodejs-madrid-meetup ├── demo1 ├── package.json ├── src │ ├── example1.js │ ├── example1.ts │ ├── example2.js │ ├── example2.ts │ ├── example3.js │ └── example3.ts └── tsconfig.json ├── demo2 ├── package.json ├── src │ ├── constants │ │ └── types.ts │ ├── entities │ │ ├── katana.ts │ │ ├── ninja.ts │ │ ├── samurai.ts │ │ └── shuriken.ts │ ├── index.ts │ ├── interfaces │ │ └── interfaces.ts │ └── inversify.config.ts └── tsconfig.json └── demo3 ├── README.md ├── bin ├── demo.tar.gz ├── run.sh └── test.sh ├── package-lock.json ├── package.json ├── src ├── domain │ ├── constants │ │ ├── decorators.ts │ │ └── types.ts │ ├── interfaces │ │ ├── repositories.ts │ │ └── services.ts │ ├── model │ │ ├── account.ts │ │ ├── actor.ts │ │ ├── director.ts │ │ └── movie.ts │ └── services │ │ └── search_service.ts ├── index.ts ├── infrastructure │ ├── bootstrapping │ │ ├── bootstrap.ts │ │ └── middleware.ts │ ├── data_access │ │ ├── db_client.ts │ │ └── repositories │ │ │ ├── account_repository.ts │ │ │ ├── actor_repository.ts │ │ │ ├── director_repository.ts │ │ │ ├── generic_repository.ts │ │ │ └── movie_repository.ts │ └── ioc │ │ ├── ioc_container.ts │ │ └── utils.ts ├── inversify.config.ts └── ui │ └── rest_api │ ├── controllers │ ├── actor_controller.ts │ ├── director_controller.ts │ ├── movie_controller.ts │ ├── search_controller.ts │ └── secure_controller.ts │ └── middleware │ └── auth_middleware.ts ├── tests ├── actor_controller.test.ts ├── search_controller.test.ts └── test_utils.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | private 40 | dump -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.svn": true, 6 | "**/.hg": true, 7 | "**/CVS": true, 8 | "**/.DS_Store": true, 9 | "**/dump": true, 10 | "**/coverage": true, 11 | "**/.nyc_output": true, 12 | "**/private": true 13 | } 14 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Stellwagen Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # public-tech-demos 2 | 3 | This repository contains the source code from all our public tech demos (e.g. meetups, conferences, etc.) 4 | 5 | The following list contains links to the source code for each of the demos: 6 | 7 | - [Node.js Madrid Meetup (May 31, 2017)](./nodejs-madrid-meetup/) Writing `Node.js` applications using the onion architecture with `Express`, `TypeScript`, `Mongoose`, `InversifyJS` & `inversify-express-utils`. The event details are available [here](https://www.meetup.com/Node-js-Madrid/events/239855183/). The slides (in Spanish) are available [here](https://docs.google.com/presentation/d/12hK5z0wt4BlyOFhJBxVgQBTdvwTznKj3XttlvMwYgrY/present). 8 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Remo H. Jansen (http://www.remojansen.com)", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/src/example1.js: -------------------------------------------------------------------------------- 1 | function add(a, b) { 2 | return a + b; 3 | } 4 | 5 | var result1 = add(5, 5); // 10 6 | var result2 = add(5, "5"); // "55" 7 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/src/example1.ts: -------------------------------------------------------------------------------- 1 | function add(a: number, b: number) { 2 | return a + b; 3 | } 4 | 5 | var result1 = add(5, 5); // 10 6 | 7 | // Error: Argument is not assignable to parameter of type 'number'. 8 | // var result2 = add(5, "5"); 9 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/src/example2.js: -------------------------------------------------------------------------------- 1 | function filterUserBySurname(users, surname) { 2 | return users.filter(u => u.surname === surname); 3 | } 4 | 5 | var users1 = [ 6 | { surname: "Smith", age: 28 }, 7 | { surname: "Johnson", age: 55 }, 8 | { surname: "Williams", age: 14 } 9 | ]; 10 | 11 | filterUserBySurname(users1, "Smith"); // [{ surname: "Smith", age: 28 }] 12 | 13 | var users2 = [ 14 | { familyName: "Smith", age: 28 }, 15 | { familyName: "Johnson", age: 55 }, 16 | { familyName: "Williams", age: 14 } 17 | ]; 18 | 19 | filterUserBySurname(users2, "Smith"); // [] 20 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/src/example2.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | surname: string; 3 | age: number; 4 | } 5 | 6 | function filterUserBySurname(users: User[], surname: string) { 7 | return users.filter(u => u.surname === surname); 8 | } 9 | 10 | const users1 = [ 11 | { surname: "Smith", age: 28 }, 12 | { surname: "Johnson", age: 55 }, 13 | { surname: "Williams", age: 14 } 14 | ]; 15 | 16 | filterUserBySurname(users1, "Smith"); // [{ surname: "Smith", age: 28 }] 17 | 18 | const users2 = [ 19 | { familyName: "Smith", age: 28 }, 20 | { familyName: "Johnson", age: 55 }, 21 | { familyName: "Williams", age: 14 } 22 | ]; 23 | 24 | // Error: Property 'surname' is missing in type '{ familyName: string; age: string; }' 25 | filterUserBySurname(users2, "Smith"); 26 | 27 | var a = null; 28 | export { a }; 29 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/src/example3.js: -------------------------------------------------------------------------------- 1 | const filterByProperty = (property) => (entities, value) => { 2 | return entities.filter(e => e[property] === value); 3 | } 4 | 5 | const filterUserByAge = filterByProperty("age"); 6 | const filterUserBySurname = filterByProperty("surname"); 7 | 8 | const users1 = [ 9 | { surname: "Smith", age: 28 }, 10 | { surname: "Johnson", age: 55 }, 11 | { surname: "Williams", age: 14 } 12 | ]; 13 | 14 | filterUserByAge(users1, 28); // [{ surname: "Smith", age: 28 }] 15 | filterUserBySurname(users1, "Smith"); // [{ surname: "Smith", age: 28 }] 16 | 17 | const users2 = [ 18 | { surname: "Smith", age: "28" }, 19 | { surname: "Johnson", age: "55" }, 20 | { surname: "Williams", age: "14" } 21 | ]; 22 | 23 | filterUserByAge(users2, 28); // [] 24 | 25 | const users3 = [ 26 | { familyName: "Smith", age: 28 }, 27 | { familyName: "Johnson", age: 55 }, 28 | { familyName: "Williams", age: 14 } 29 | ]; 30 | 31 | filterUserBySurname(users3, "Smith"); // [] 32 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/src/example3.ts: -------------------------------------------------------------------------------- 1 | // Example of lookup type T[K] 2 | // Learn more @ https://blog.mariusschulz.com/2017/01/06/typescript-2-1-keyof-and-lookup-types 3 | const filterByProperty = (property: K) => (entities: T[], value: T[K]) => { 4 | return entities.filter(e => e[property] === value); 5 | } 6 | 7 | interface User { 8 | surname: string; 9 | age: number; 10 | } 11 | 12 | const filterUserByAge = filterByProperty("age"); 13 | const filterUserBySurname = filterByProperty("surname"); 14 | 15 | const users1 = [ 16 | { surname: "Smith", age: 28 }, 17 | { surname: "Johnson", age: 55 }, 18 | { surname: "Williams", age: 14 } 19 | ]; 20 | 21 | filterUserByAge(users1, 28); // [{ surname: "Smith", age: 28 }] 22 | filterUserBySurname(users1, "Smith"); // [{ surname: "Smith", age: 28 }] 23 | 24 | const users2 = [ 25 | { surname: "Smith", age: "28" }, 26 | { surname: "Johnson", age: "55" }, 27 | { surname: "Williams", age: "14" } 28 | ]; 29 | 30 | // Error: Types of property 'age' are incompatible. Type 'string' is not assignable to type 'number' 31 | filterUserByAge(users2, 28); 32 | 33 | const users3 = [ 34 | { familyName: "Smith", age: 28 }, 35 | { familyName: "Johnson", age: 55 }, 36 | { familyName: "Williams", age: 14 } 37 | ]; 38 | 39 | // Error: Property 'surname' is missing in type '{ familyName: string; age: number; }'. 40 | filterUserBySurname(users3, "Smith"); 41 | 42 | var a = null; 43 | export { a }; 44 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["es6", "dom"], 5 | "types": ["reflect-metadata"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "strictNullChecks": true, 11 | "noImplicitAny": true, 12 | "noUnusedLocals": true, 13 | "allowUnreachableCode": false, 14 | "noImplicitThis": true 15 | } 16 | } -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node src/index.ts", 8 | "clean": "rm -r -f coverage .nyc_output private", 9 | "build": "tsc -p tsconfig.json -inlineSourceMap -outDir private" 10 | }, 11 | "keywords": [], 12 | "author": "Remo H. Jansen (http://www.remojansen.com)", 13 | "license": "MIT", 14 | "dependencies": { 15 | "inversify": "^4.1.0", 16 | "reflect-metadata": "^0.1.10" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/constants/types.ts: -------------------------------------------------------------------------------- 1 | let TYPES = { 2 | Warrior: Symbol("Warrior"), 3 | Weapon: Symbol("Weapon"), 4 | ThrowableWeapon: Symbol("ThrowableWeapon") 5 | }; 6 | 7 | export { TYPES }; 8 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/entities/katana.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Weapon } from "../interfaces/interfaces"; 3 | 4 | @injectable() 5 | class Katana implements Weapon { 6 | public hit() { 7 | return "cuts!"; 8 | } 9 | } 10 | 11 | export { Katana }; 12 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/entities/ninja.ts: -------------------------------------------------------------------------------- 1 | import { injectable, inject } from "inversify"; 2 | import { TYPES } from "../constants/types"; 3 | import { Warrior, ThrowableWeapon } from "../interfaces/interfaces"; 4 | 5 | @injectable() 6 | class Ninja implements Warrior { 7 | 8 | private _shuriken: ThrowableWeapon; 9 | 10 | public constructor( 11 | @inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon 12 | ) { 13 | this._shuriken = shuriken; 14 | } 15 | 16 | public fight() { return this._shuriken.throw(); }; 17 | 18 | } 19 | 20 | export { Ninja }; 21 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/entities/samurai.ts: -------------------------------------------------------------------------------- 1 | import { injectable, inject } from "inversify"; 2 | import { TYPES } from "../constants/types"; 3 | import { Warrior, Weapon } from "../interfaces/interfaces"; 4 | 5 | @injectable() 6 | class Samurai implements Warrior { 7 | 8 | private _katana: Weapon; 9 | 10 | public constructor( 11 | @inject(TYPES.Weapon) katana: Weapon 12 | ) { 13 | this._katana = katana; 14 | } 15 | 16 | public fight() { return this._katana.hit(); }; 17 | 18 | } 19 | 20 | export { Samurai }; 21 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/entities/shuriken.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { ThrowableWeapon } from "../interfaces/interfaces"; 3 | 4 | @injectable() 5 | class Shuriken implements ThrowableWeapon { 6 | public throw() { 7 | return "hits!"; 8 | } 9 | } 10 | 11 | export { Shuriken }; 12 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { container } from "./inversify.config"; 3 | import { TYPES } from "./constants/types"; 4 | import { Warrior } from "./interfaces/interfaces"; 5 | 6 | const ninja = container.getNamed(TYPES.Warrior, "ninja"); 7 | const samurai = container.getNamed(TYPES.Warrior, "samurai"); 8 | 9 | console.log(`The Ninja ${ninja.fight()}`); // The Ninja cuts! 10 | console.log(`The Samurai ${samurai.fight()}`); // The Samurai hits! 11 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Warrior { 2 | fight(): string; 3 | } 4 | 5 | export interface Weapon { 6 | hit(): string; 7 | } 8 | 9 | export interface ThrowableWeapon { 10 | throw(): string; 11 | } 12 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/src/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { Container, ContainerModule } from "inversify"; 2 | import { TYPES } from "./constants/types"; 3 | import { Ninja } from "./entities/ninja"; 4 | import { Samurai } from "./entities/samurai"; 5 | import { Katana } from "./entities/katana"; 6 | import { Shuriken } from "./entities/shuriken"; 7 | import { Warrior, Weapon, ThrowableWeapon } from "./interfaces/interfaces"; 8 | 9 | const weapons = new ContainerModule((bind) => { 10 | container.bind(TYPES.Weapon).to(Katana); 11 | container.bind(TYPES.ThrowableWeapon).to(Shuriken); 12 | }); 13 | 14 | const warriors = new ContainerModule((bind) => { 15 | container.bind(TYPES.Warrior).to(Ninja).whenTargetNamed("ninja") 16 | container.bind(TYPES.Warrior).to(Samurai).whenTargetNamed("samurai"); 17 | }); 18 | 19 | const container = new Container(); 20 | 21 | container.load(weapons, warriors); 22 | 23 | export { container }; 24 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["es6", "dom"], 5 | "types": ["reflect-metadata"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "strictNullChecks": true, 11 | "noImplicitAny": true, 12 | "noUnusedLocals": true, 13 | "allowUnreachableCode": false, 14 | "noImplicitThis": true 15 | } 16 | } -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/README.md: -------------------------------------------------------------------------------- 1 | # Demo: Node.js & the Onion Architecture powered by InversifyJS 2 | 3 | You can learn about the onion architecture [here](https://dzone.com/articles/onion-architecture-is-interesting). 4 | 5 | ## Running the demo 6 | 7 | > :warning: These instructions require MongoDB[MongoDB](https://docs.mongodb.com/manual/installation/) and [Node.js](https://nodejs.org/en/download/) to be installed in your environment. 8 | > 9 | > :warning: The commands`mongod` & `mongorestore` can be found at the MondoDB installation directory. 10 | 11 | Clone the repository: 12 | 13 | ```sh 14 | git clone https://github.com/stelltec/public-tech-demos.git 15 | ``` 16 | 17 | Move to the demo directory: 18 | 19 | ```sh 20 | cd public-tech-demos/nodejs-madrid-meetup/demo3/ 21 | ``` 22 | 23 | Run the MongoDB server: 24 | 25 | ```sh 26 | mongod 27 | ``` 28 | 29 | Open the MongoDB demo backup: 30 | 31 | ```sh 32 | tar -zxvf ./bin/demo.tar.gz 33 | ``` 34 | 35 | Create new database named `demo` using the backup: 36 | 37 | ```sh 38 | mongorestore -d demo dump/demo/ 39 | ``` 40 | 41 | Run the tests: 42 | 43 | ```sh 44 | ./bin/test.sh 45 | ``` 46 | 47 | Run the app: 48 | 49 | ```sh 50 | ./bin/run.sh 51 | ``` 52 | 53 | Open: 54 | 55 | ```sh 56 | open http://localhost:8080/api/movies 57 | ``` 58 | 59 | ## REST Services 60 | 61 | The application exposes a few REST endpoints: 62 | 63 | - HTTP GET `/api/actors` 64 | - HTTP GET `/api/actors:id` 65 | - HTTP GET `/api/directors` 66 | - HTTP GET `/api/directors/:id` 67 | - HTTP GET `/api/movies` 68 | - HTTP GET `/api/movies/:id` 69 | - HTTP GET `/api/search/:query` 70 | - HTTP GET `/api/secured` (Requieres a valid `x-auth-token` header) 71 | 72 | You can use the following code snippet to call the secured endpoint: 73 | 74 | ```js 75 | fetch("http://localhost:8080/api/secure", { 76 | method: "GET", 77 | headers: { 78 | "Content-Type": "application/json", 79 | "x-auth-token": "SOME_VALID_DEMO_CREDENTIAL" 80 | }, 81 | }).then((r) => { 82 | if (r.status === 200) { 83 | r.json().then((j) => console.log(j)); 84 | } else { 85 | console.log("ERROR", r.status); 86 | } 87 | }).catch(e => console.log(e)); 88 | ``` 89 | 90 | You can use the following code snippet to call the secured endpoint with an invalid `x-auth-token` header: 91 | 92 | ```js 93 | fetch("http://localhost:8080/api/secure", { 94 | method: "GET", 95 | headers: { 96 | "Content-Type": "application/json", 97 | "x-auth-token": "SOME_WRONG_DEMO_CREDENTIAL" 98 | }, 99 | }).then((r) => { 100 | if (r.status === 200) { 101 | r.json().then((j) => console.log(j)); 102 | } else { 103 | console.log("ERROR", r.status); 104 | } 105 | }).catch(e => console.log(e)); 106 | ``` 107 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/bin/demo.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stelltec/public-tech-demos/a9e0aee3602145583ee1117dd1d5db5f31bbc91a/nodejs-madrid-meetup/demo3/bin/demo.tar.gz -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/bin/run.sh: -------------------------------------------------------------------------------- 1 | rm -r -f coverage .nyc_output private 2 | tsc -p tsconfig.json -inlineSourceMap -outDir private 3 | node --inspect private/src/index.js 4 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/bin/test.sh: -------------------------------------------------------------------------------- 1 | rm -r -f coverage .nyc_output private 2 | nyc -r=text -r=html -i ts-node/register -e .ts mocha -t 5000 -r ts-node/register -r reflect-metadata/Reflect tests/**/*.test.ts -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo3", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@types/body-parser": { 7 | "version": "1.16.3", 8 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.3.tgz", 9 | "integrity": "sha1-vCuaGB8vqFyA8eys2KBc8UFLhaM=", 10 | "dev": true 11 | }, 12 | "@types/bson": { 13 | "version": "1.0.3", 14 | "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.3.tgz", 15 | "integrity": "sha1-bCbwh2v52Muwbt1AGeKTVL86A+A=", 16 | "dev": true 17 | }, 18 | "@types/chai": { 19 | "version": "3.5.2", 20 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-3.5.2.tgz", 21 | "integrity": "sha1-wRzSgX06QBt7oPWkIPNcVhObHB4=", 22 | "dev": true 23 | }, 24 | "@types/express": { 25 | "version": "4.0.35", 26 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.35.tgz", 27 | "integrity": "sha1-YmfHtgpR+sRzRns8SgLNHkQYBf4=", 28 | "dev": true 29 | }, 30 | "@types/express-serve-static-core": { 31 | "version": "4.0.45", 32 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.45.tgz", 33 | "integrity": "sha1-cbsfh9cYdILQ2IUfWylEWOHHhmc=", 34 | "dev": true 35 | }, 36 | "@types/helmet": { 37 | "version": "0.0.35", 38 | "resolved": "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.35.tgz", 39 | "integrity": "sha1-tfA1BjGAwsH0MSKromEsntlGbcA=", 40 | "dev": true 41 | }, 42 | "@types/mime": { 43 | "version": "0.0.29", 44 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-0.0.29.tgz", 45 | "integrity": "sha1-+8/TMFc7kS71nu7hRgK/rOYwdUs=", 46 | "dev": true 47 | }, 48 | "@types/mocha": { 49 | "version": "2.2.41", 50 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.41.tgz", 51 | "integrity": "sha1-4nzwgXFT658nE7LT9saPHhw8pgg=", 52 | "dev": true 53 | }, 54 | "@types/mongodb": { 55 | "version": "2.2.4", 56 | "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-2.2.4.tgz", 57 | "integrity": "sha512-dTGnvugu7Sg881Bj3ZC/nqgLcgc1uFbxzcWYrI7ET+NrQ+ub2up+rNih+Jioq7t/EOPUslufFt0o+I/FqXh6YA==", 58 | "dev": true 59 | }, 60 | "@types/mongoose": { 61 | "version": "4.7.17", 62 | "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-4.7.17.tgz", 63 | "integrity": "sha512-tMTBgFVdV8XM0KZGFjVHUqDUn43FIwFbDpKFaER5WAXhjrcFETRh5Pfn0y8JZ8aUszIkYVTgEr1ZHhl/dGQz/w==", 64 | "dev": true 65 | }, 66 | "@types/node": { 67 | "version": "7.0.29", 68 | "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.29.tgz", 69 | "integrity": "sha512-+8JrLZny/uR+d/jLK9eaV63buRM7X/gNzQk57q76NS4KNKLSKOmxJYFIlwuP2zDvA7wqZj05POPhSd9Z1hYQpQ==", 70 | "dev": true 71 | }, 72 | "@types/serve-static": { 73 | "version": "1.7.31", 74 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz", 75 | "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=", 76 | "dev": true 77 | }, 78 | "@types/superagent": { 79 | "version": "2.0.37", 80 | "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-2.0.37.tgz", 81 | "integrity": "sha512-seOSyYolzf9AuiD8dAj1ZzleZ2fcvtTyMkB00qZqzfZ+jdJmg7UqxhNNEHbvYrdu+AqCtebCB5fT3441lPHPiQ==", 82 | "dev": true 83 | }, 84 | "@types/supertest": { 85 | "version": "2.0.0", 86 | "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.0.tgz", 87 | "integrity": "sha1-Rofer/SVC2PkYRU21v1EZl7hG7c=", 88 | "dev": true 89 | }, 90 | "accepts": { 91 | "version": "1.3.3", 92 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 93 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 94 | }, 95 | "ansi-regex": { 96 | "version": "2.1.1", 97 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 98 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 99 | "dev": true 100 | }, 101 | "ansi-styles": { 102 | "version": "2.2.1", 103 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 104 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 105 | "dev": true 106 | }, 107 | "array-flatten": { 108 | "version": "1.1.1", 109 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 110 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 111 | }, 112 | "arrify": { 113 | "version": "1.0.1", 114 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 115 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 116 | "dev": true 117 | }, 118 | "assertion-error": { 119 | "version": "1.0.2", 120 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", 121 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", 122 | "dev": true 123 | }, 124 | "async": { 125 | "version": "2.1.4", 126 | "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz", 127 | "integrity": "sha1-LSFgx3iAMuTdbL4lAvH5osj2zeQ=" 128 | }, 129 | "asynckit": { 130 | "version": "0.4.0", 131 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 132 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 133 | "dev": true 134 | }, 135 | "balanced-match": { 136 | "version": "0.4.2", 137 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", 138 | "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", 139 | "dev": true 140 | }, 141 | "bluebird": { 142 | "version": "2.10.2", 143 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz", 144 | "integrity": "sha1-AkpVFylTCIV/FPkfEQb8O1VfRGs=" 145 | }, 146 | "body-parser": { 147 | "version": "1.17.2", 148 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 149 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=" 150 | }, 151 | "brace-expansion": { 152 | "version": "1.1.7", 153 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", 154 | "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", 155 | "dev": true 156 | }, 157 | "browser-stdout": { 158 | "version": "1.3.0", 159 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 160 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 161 | "dev": true 162 | }, 163 | "bson": { 164 | "version": "1.0.4", 165 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz", 166 | "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=" 167 | }, 168 | "buffer-shims": { 169 | "version": "1.0.0", 170 | "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", 171 | "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" 172 | }, 173 | "bytes": { 174 | "version": "2.4.0", 175 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 176 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 177 | }, 178 | "camelize": { 179 | "version": "1.0.0", 180 | "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", 181 | "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" 182 | }, 183 | "chai": { 184 | "version": "4.0.2", 185 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.0.2.tgz", 186 | "integrity": "sha1-L3MnxN5vOF3XeHmZ4qsCaXoyuDs=", 187 | "dev": true 188 | }, 189 | "chalk": { 190 | "version": "1.1.3", 191 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 192 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 193 | "dev": true, 194 | "dependencies": { 195 | "supports-color": { 196 | "version": "2.0.0", 197 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 198 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 199 | "dev": true 200 | } 201 | } 202 | }, 203 | "check-error": { 204 | "version": "1.0.2", 205 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 206 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 207 | "dev": true 208 | }, 209 | "combined-stream": { 210 | "version": "1.0.5", 211 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 212 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", 213 | "dev": true 214 | }, 215 | "commander": { 216 | "version": "2.9.0", 217 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 218 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 219 | "dev": true 220 | }, 221 | "component-emitter": { 222 | "version": "1.2.1", 223 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 224 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", 225 | "dev": true 226 | }, 227 | "concat-map": { 228 | "version": "0.0.1", 229 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 230 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 231 | "dev": true 232 | }, 233 | "connect": { 234 | "version": "3.6.2", 235 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.2.tgz", 236 | "integrity": "sha1-aU6NIGgb/kkCgsiriGvpjwn0L+c=" 237 | }, 238 | "content-disposition": { 239 | "version": "0.5.2", 240 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 241 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 242 | }, 243 | "content-security-policy-builder": { 244 | "version": "1.1.0", 245 | "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-1.1.0.tgz", 246 | "integrity": "sha1-2R8bB2I2wRmFDH3umSS/VeBXcrM=" 247 | }, 248 | "content-type": { 249 | "version": "1.0.2", 250 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 251 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 252 | }, 253 | "cookie": { 254 | "version": "0.3.1", 255 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 256 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 257 | }, 258 | "cookie-signature": { 259 | "version": "1.0.6", 260 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 261 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 262 | }, 263 | "cookiejar": { 264 | "version": "2.1.1", 265 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", 266 | "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=", 267 | "dev": true 268 | }, 269 | "core-util-is": { 270 | "version": "1.0.2", 271 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 272 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 273 | }, 274 | "dasherize": { 275 | "version": "2.0.0", 276 | "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", 277 | "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" 278 | }, 279 | "dashify": { 280 | "version": "0.2.2", 281 | "resolved": "https://registry.npmjs.org/dashify/-/dashify-0.2.2.tgz", 282 | "integrity": "sha1-agdBWgHJH69KMuONnfunH2HLIP4=" 283 | }, 284 | "debug": { 285 | "version": "2.6.7", 286 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 287 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 288 | }, 289 | "deep-eql": { 290 | "version": "2.0.2", 291 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz", 292 | "integrity": "sha1-sbrAblbwp2d3aG1Qyf63XC7XZ5o=", 293 | "dev": true, 294 | "dependencies": { 295 | "type-detect": { 296 | "version": "3.0.0", 297 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-3.0.0.tgz", 298 | "integrity": "sha1-RtDMhVOrt7E6NSsNbeov1Y8tm1U=", 299 | "dev": true 300 | } 301 | } 302 | }, 303 | "delayed-stream": { 304 | "version": "1.0.0", 305 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 306 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 307 | "dev": true 308 | }, 309 | "depd": { 310 | "version": "1.1.0", 311 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 312 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 313 | }, 314 | "destroy": { 315 | "version": "1.0.4", 316 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 317 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 318 | }, 319 | "diff": { 320 | "version": "3.2.0", 321 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 322 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 323 | "dev": true 324 | }, 325 | "dns-prefetch-control": { 326 | "version": "0.1.0", 327 | "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", 328 | "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" 329 | }, 330 | "dont-sniff-mimetype": { 331 | "version": "1.0.0", 332 | "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", 333 | "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" 334 | }, 335 | "ee-first": { 336 | "version": "1.1.1", 337 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 338 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 339 | }, 340 | "encodeurl": { 341 | "version": "1.0.1", 342 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 343 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 344 | }, 345 | "es6-promise": { 346 | "version": "3.2.1", 347 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", 348 | "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" 349 | }, 350 | "escape-html": { 351 | "version": "1.0.3", 352 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 353 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 354 | }, 355 | "escape-string-regexp": { 356 | "version": "1.0.5", 357 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 358 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 359 | "dev": true 360 | }, 361 | "etag": { 362 | "version": "1.8.0", 363 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 364 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 365 | }, 366 | "expect-ct": { 367 | "version": "0.1.0", 368 | "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.1.0.tgz", 369 | "integrity": "sha1-UnNWeN4YUwiQ2Ne5XwrGNkCVgJQ=" 370 | }, 371 | "express": { 372 | "version": "4.15.3", 373 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 374 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 375 | }, 376 | "extend": { 377 | "version": "3.0.1", 378 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 379 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", 380 | "dev": true 381 | }, 382 | "finalhandler": { 383 | "version": "1.0.3", 384 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 385 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 386 | }, 387 | "form-data": { 388 | "version": "2.2.0", 389 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.2.0.tgz", 390 | "integrity": "sha1-ml47kpX5gLJiPPZPojixTOvKcHs=", 391 | "dev": true 392 | }, 393 | "formidable": { 394 | "version": "1.1.1", 395 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", 396 | "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=", 397 | "dev": true 398 | }, 399 | "forwarded": { 400 | "version": "0.1.0", 401 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 402 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 403 | }, 404 | "frameguard": { 405 | "version": "3.0.0", 406 | "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.0.0.tgz", 407 | "integrity": "sha1-e8rUae57lukdEs6zlZx4I1qScuk=" 408 | }, 409 | "fresh": { 410 | "version": "0.5.0", 411 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 412 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 413 | }, 414 | "fs.realpath": { 415 | "version": "1.0.0", 416 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 417 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 418 | "dev": true 419 | }, 420 | "get-func-name": { 421 | "version": "2.0.0", 422 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 423 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 424 | "dev": true 425 | }, 426 | "glob": { 427 | "version": "7.1.1", 428 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 429 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 430 | "dev": true 431 | }, 432 | "graceful-readlink": { 433 | "version": "1.0.1", 434 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 435 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 436 | "dev": true 437 | }, 438 | "growl": { 439 | "version": "1.9.2", 440 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 441 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 442 | "dev": true 443 | }, 444 | "has-ansi": { 445 | "version": "2.0.0", 446 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 447 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 448 | "dev": true 449 | }, 450 | "has-flag": { 451 | "version": "1.0.0", 452 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 453 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 454 | "dev": true 455 | }, 456 | "helmet": { 457 | "version": "3.6.1", 458 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.6.1.tgz", 459 | "integrity": "sha1-kfOqf6TJRnFZX7Vo39jChImjiL4=" 460 | }, 461 | "helmet-csp": { 462 | "version": "2.4.0", 463 | "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.4.0.tgz", 464 | "integrity": "sha1-flOhVxZ6BkWq3XF30SrmxgXBhC4=" 465 | }, 466 | "hide-powered-by": { 467 | "version": "1.0.0", 468 | "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", 469 | "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" 470 | }, 471 | "hooks-fixed": { 472 | "version": "2.0.0", 473 | "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz", 474 | "integrity": "sha1-oB2JTVKsf2WZu7H2PfycQR33DLo=" 475 | }, 476 | "hpkp": { 477 | "version": "2.0.0", 478 | "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", 479 | "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" 480 | }, 481 | "hsts": { 482 | "version": "2.0.0", 483 | "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.0.0.tgz", 484 | "integrity": "sha1-pSI0xgcN7PIUsra3C7FE0H5Hdsc=" 485 | }, 486 | "http-errors": { 487 | "version": "1.6.1", 488 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 489 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 490 | }, 491 | "iconv-lite": { 492 | "version": "0.4.15", 493 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 494 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 495 | }, 496 | "ienoopen": { 497 | "version": "1.0.0", 498 | "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.0.0.tgz", 499 | "integrity": "sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms=" 500 | }, 501 | "inflight": { 502 | "version": "1.0.6", 503 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 504 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 505 | "dev": true 506 | }, 507 | "inherits": { 508 | "version": "2.0.3", 509 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 510 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 511 | }, 512 | "inversify": { 513 | "version": "4.1.1", 514 | "resolved": "https://registry.npmjs.org/inversify/-/inversify-4.1.1.tgz", 515 | "integrity": "sha1-qV7bNK4IK7EXUzbVHc1fARE0xiU=" 516 | }, 517 | "inversify-express-utils": { 518 | "version": "3.5.2", 519 | "resolved": "https://registry.npmjs.org/inversify-express-utils/-/inversify-express-utils-3.5.2.tgz", 520 | "integrity": "sha1-tdsyqwE/VxHLY048etdiwakSH/s=" 521 | }, 522 | "ipaddr.js": { 523 | "version": "1.3.0", 524 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 525 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 526 | }, 527 | "isarray": { 528 | "version": "1.0.0", 529 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 530 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 531 | }, 532 | "json3": { 533 | "version": "3.3.2", 534 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 535 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 536 | "dev": true 537 | }, 538 | "kareem": { 539 | "version": "1.4.1", 540 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.4.1.tgz", 541 | "integrity": "sha1-7XYgAET6BB7zK02oJh4lU/EXNTE=" 542 | }, 543 | "lodash": { 544 | "version": "4.17.4", 545 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 546 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 547 | }, 548 | "lodash._baseassign": { 549 | "version": "3.2.0", 550 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 551 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 552 | "dev": true 553 | }, 554 | "lodash._basecopy": { 555 | "version": "3.0.1", 556 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 557 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 558 | "dev": true 559 | }, 560 | "lodash._basecreate": { 561 | "version": "3.0.3", 562 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 563 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 564 | "dev": true 565 | }, 566 | "lodash._getnative": { 567 | "version": "3.9.1", 568 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 569 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 570 | "dev": true 571 | }, 572 | "lodash._isiterateecall": { 573 | "version": "3.0.9", 574 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 575 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 576 | "dev": true 577 | }, 578 | "lodash.create": { 579 | "version": "3.1.1", 580 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 581 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 582 | "dev": true 583 | }, 584 | "lodash.isarguments": { 585 | "version": "3.1.0", 586 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 587 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 588 | "dev": true 589 | }, 590 | "lodash.isarray": { 591 | "version": "3.0.4", 592 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 593 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 594 | "dev": true 595 | }, 596 | "lodash.keys": { 597 | "version": "3.1.2", 598 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 599 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 600 | "dev": true 601 | }, 602 | "lodash.reduce": { 603 | "version": "4.6.0", 604 | "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", 605 | "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" 606 | }, 607 | "make-error": { 608 | "version": "1.3.0", 609 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz", 610 | "integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=", 611 | "dev": true 612 | }, 613 | "media-typer": { 614 | "version": "0.3.0", 615 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 616 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 617 | }, 618 | "merge-descriptors": { 619 | "version": "1.0.1", 620 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 621 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 622 | }, 623 | "methods": { 624 | "version": "1.1.2", 625 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 626 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 627 | }, 628 | "mime": { 629 | "version": "1.3.4", 630 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 631 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 632 | }, 633 | "mime-db": { 634 | "version": "1.27.0", 635 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 636 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 637 | }, 638 | "mime-types": { 639 | "version": "2.1.15", 640 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 641 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 642 | }, 643 | "minimatch": { 644 | "version": "3.0.4", 645 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 646 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 647 | "dev": true 648 | }, 649 | "minimist": { 650 | "version": "0.0.8", 651 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 652 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 653 | "dev": true 654 | }, 655 | "mkdirp": { 656 | "version": "0.5.1", 657 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 658 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 659 | "dev": true 660 | }, 661 | "mocha": { 662 | "version": "3.4.2", 663 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", 664 | "integrity": "sha1-0O9NMyEm2/GNDWQMmzgt1IvpdZQ=", 665 | "dev": true, 666 | "dependencies": { 667 | "debug": { 668 | "version": "2.6.0", 669 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", 670 | "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", 671 | "dev": true 672 | }, 673 | "ms": { 674 | "version": "0.7.2", 675 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 676 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 677 | "dev": true 678 | } 679 | } 680 | }, 681 | "mongodb": { 682 | "version": "2.2.27", 683 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.27.tgz", 684 | "integrity": "sha1-NBIgNNtm2YO89qta2yaiSnD+9uY=" 685 | }, 686 | "mongodb-core": { 687 | "version": "2.1.11", 688 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.11.tgz", 689 | "integrity": "sha1-HDh3bOsXSZepnCiGDu2QKNqbPho=" 690 | }, 691 | "mongoose": { 692 | "version": "4.10.5", 693 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.10.5.tgz", 694 | "integrity": "sha1-fNUO441QV4Feg5YuOYX33Z2naa0=" 695 | }, 696 | "mpath": { 697 | "version": "0.3.0", 698 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.3.0.tgz", 699 | "integrity": "sha1-elj3iem1/TyUUgY0FXlg8mvV70Q=" 700 | }, 701 | "mpromise": { 702 | "version": "0.5.5", 703 | "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.5.tgz", 704 | "integrity": "sha1-9bJCWddjrMIlewoMjG2Gb9UXMuY=" 705 | }, 706 | "mquery": { 707 | "version": "2.3.1", 708 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-2.3.1.tgz", 709 | "integrity": "sha1-mrNnSXFIAP8LtTpoHOS8TV8HyHs=", 710 | "dependencies": { 711 | "debug": { 712 | "version": "2.6.8", 713 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 714 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" 715 | }, 716 | "sliced": { 717 | "version": "0.0.5", 718 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz", 719 | "integrity": "sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8=" 720 | } 721 | } 722 | }, 723 | "ms": { 724 | "version": "2.0.0", 725 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 726 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 727 | }, 728 | "muri": { 729 | "version": "1.2.1", 730 | "resolved": "https://registry.npmjs.org/muri/-/muri-1.2.1.tgz", 731 | "integrity": "sha1-7H6lzmympSPrGrNbrNpfqBbJqjw=" 732 | }, 733 | "negotiator": { 734 | "version": "0.6.1", 735 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 736 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 737 | }, 738 | "nocache": { 739 | "version": "2.0.0", 740 | "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz", 741 | "integrity": "sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA=" 742 | }, 743 | "nyc": { 744 | "version": "10.3.2", 745 | "resolved": "https://registry.npmjs.org/nyc/-/nyc-10.3.2.tgz", 746 | "integrity": "sha1-8n9NkfKp2zbCT1dP9cbv/wIz3kY=", 747 | "dev": true, 748 | "dependencies": { 749 | "align-text": { 750 | "version": "0.1.4", 751 | "bundled": true, 752 | "dev": true 753 | }, 754 | "amdefine": { 755 | "version": "1.0.1", 756 | "bundled": true, 757 | "dev": true 758 | }, 759 | "ansi-regex": { 760 | "version": "2.1.1", 761 | "bundled": true, 762 | "dev": true 763 | }, 764 | "ansi-styles": { 765 | "version": "2.2.1", 766 | "bundled": true, 767 | "dev": true 768 | }, 769 | "append-transform": { 770 | "version": "0.4.0", 771 | "bundled": true, 772 | "dev": true 773 | }, 774 | "archy": { 775 | "version": "1.0.0", 776 | "bundled": true, 777 | "dev": true 778 | }, 779 | "arr-diff": { 780 | "version": "2.0.0", 781 | "bundled": true, 782 | "dev": true 783 | }, 784 | "arr-flatten": { 785 | "version": "1.0.3", 786 | "bundled": true, 787 | "dev": true 788 | }, 789 | "array-unique": { 790 | "version": "0.2.1", 791 | "bundled": true, 792 | "dev": true 793 | }, 794 | "arrify": { 795 | "version": "1.0.1", 796 | "bundled": true, 797 | "dev": true 798 | }, 799 | "async": { 800 | "version": "1.5.2", 801 | "bundled": true, 802 | "dev": true 803 | }, 804 | "babel-code-frame": { 805 | "version": "6.22.0", 806 | "bundled": true, 807 | "dev": true 808 | }, 809 | "babel-generator": { 810 | "version": "6.24.1", 811 | "bundled": true, 812 | "dev": true 813 | }, 814 | "babel-messages": { 815 | "version": "6.23.0", 816 | "bundled": true, 817 | "dev": true 818 | }, 819 | "babel-runtime": { 820 | "version": "6.23.0", 821 | "bundled": true, 822 | "dev": true 823 | }, 824 | "babel-template": { 825 | "version": "6.24.1", 826 | "bundled": true, 827 | "dev": true 828 | }, 829 | "babel-traverse": { 830 | "version": "6.24.1", 831 | "bundled": true, 832 | "dev": true 833 | }, 834 | "babel-types": { 835 | "version": "6.24.1", 836 | "bundled": true, 837 | "dev": true 838 | }, 839 | "babylon": { 840 | "version": "6.17.0", 841 | "bundled": true, 842 | "dev": true 843 | }, 844 | "balanced-match": { 845 | "version": "0.4.2", 846 | "bundled": true, 847 | "dev": true 848 | }, 849 | "brace-expansion": { 850 | "version": "1.1.7", 851 | "bundled": true, 852 | "dev": true 853 | }, 854 | "braces": { 855 | "version": "1.8.5", 856 | "bundled": true, 857 | "dev": true 858 | }, 859 | "builtin-modules": { 860 | "version": "1.1.1", 861 | "bundled": true, 862 | "dev": true 863 | }, 864 | "caching-transform": { 865 | "version": "1.0.1", 866 | "bundled": true, 867 | "dev": true 868 | }, 869 | "camelcase": { 870 | "version": "1.2.1", 871 | "bundled": true, 872 | "dev": true, 873 | "optional": true 874 | }, 875 | "center-align": { 876 | "version": "0.1.3", 877 | "bundled": true, 878 | "dev": true, 879 | "optional": true 880 | }, 881 | "chalk": { 882 | "version": "1.1.3", 883 | "bundled": true, 884 | "dev": true 885 | }, 886 | "cliui": { 887 | "version": "2.1.0", 888 | "bundled": true, 889 | "dev": true, 890 | "optional": true, 891 | "dependencies": { 892 | "wordwrap": { 893 | "version": "0.0.2", 894 | "bundled": true, 895 | "dev": true, 896 | "optional": true 897 | } 898 | } 899 | }, 900 | "code-point-at": { 901 | "version": "1.1.0", 902 | "bundled": true, 903 | "dev": true 904 | }, 905 | "commondir": { 906 | "version": "1.0.1", 907 | "bundled": true, 908 | "dev": true 909 | }, 910 | "concat-map": { 911 | "version": "0.0.1", 912 | "bundled": true, 913 | "dev": true 914 | }, 915 | "convert-source-map": { 916 | "version": "1.5.0", 917 | "bundled": true, 918 | "dev": true 919 | }, 920 | "core-js": { 921 | "version": "2.4.1", 922 | "bundled": true, 923 | "dev": true 924 | }, 925 | "cross-spawn": { 926 | "version": "4.0.2", 927 | "bundled": true, 928 | "dev": true 929 | }, 930 | "debug": { 931 | "version": "2.6.6", 932 | "bundled": true, 933 | "dev": true 934 | }, 935 | "debug-log": { 936 | "version": "1.0.1", 937 | "bundled": true, 938 | "dev": true 939 | }, 940 | "decamelize": { 941 | "version": "1.2.0", 942 | "bundled": true, 943 | "dev": true 944 | }, 945 | "default-require-extensions": { 946 | "version": "1.0.0", 947 | "bundled": true, 948 | "dev": true 949 | }, 950 | "detect-indent": { 951 | "version": "4.0.0", 952 | "bundled": true, 953 | "dev": true 954 | }, 955 | "error-ex": { 956 | "version": "1.3.1", 957 | "bundled": true, 958 | "dev": true 959 | }, 960 | "escape-string-regexp": { 961 | "version": "1.0.5", 962 | "bundled": true, 963 | "dev": true 964 | }, 965 | "esutils": { 966 | "version": "2.0.2", 967 | "bundled": true, 968 | "dev": true 969 | }, 970 | "expand-brackets": { 971 | "version": "0.1.5", 972 | "bundled": true, 973 | "dev": true 974 | }, 975 | "expand-range": { 976 | "version": "1.8.2", 977 | "bundled": true, 978 | "dev": true 979 | }, 980 | "extglob": { 981 | "version": "0.3.2", 982 | "bundled": true, 983 | "dev": true 984 | }, 985 | "filename-regex": { 986 | "version": "2.0.1", 987 | "bundled": true, 988 | "dev": true 989 | }, 990 | "fill-range": { 991 | "version": "2.2.3", 992 | "bundled": true, 993 | "dev": true 994 | }, 995 | "find-cache-dir": { 996 | "version": "0.1.1", 997 | "bundled": true, 998 | "dev": true 999 | }, 1000 | "find-up": { 1001 | "version": "1.1.2", 1002 | "bundled": true, 1003 | "dev": true 1004 | }, 1005 | "for-in": { 1006 | "version": "1.0.2", 1007 | "bundled": true, 1008 | "dev": true 1009 | }, 1010 | "for-own": { 1011 | "version": "0.1.5", 1012 | "bundled": true, 1013 | "dev": true 1014 | }, 1015 | "foreground-child": { 1016 | "version": "1.5.6", 1017 | "bundled": true, 1018 | "dev": true 1019 | }, 1020 | "fs.realpath": { 1021 | "version": "1.0.0", 1022 | "bundled": true, 1023 | "dev": true 1024 | }, 1025 | "get-caller-file": { 1026 | "version": "1.0.2", 1027 | "bundled": true, 1028 | "dev": true 1029 | }, 1030 | "glob": { 1031 | "version": "7.1.1", 1032 | "bundled": true, 1033 | "dev": true 1034 | }, 1035 | "glob-base": { 1036 | "version": "0.3.0", 1037 | "bundled": true, 1038 | "dev": true 1039 | }, 1040 | "glob-parent": { 1041 | "version": "2.0.0", 1042 | "bundled": true, 1043 | "dev": true 1044 | }, 1045 | "globals": { 1046 | "version": "9.17.0", 1047 | "bundled": true, 1048 | "dev": true 1049 | }, 1050 | "graceful-fs": { 1051 | "version": "4.1.11", 1052 | "bundled": true, 1053 | "dev": true 1054 | }, 1055 | "handlebars": { 1056 | "version": "4.0.8", 1057 | "bundled": true, 1058 | "dev": true, 1059 | "dependencies": { 1060 | "source-map": { 1061 | "version": "0.4.4", 1062 | "bundled": true, 1063 | "dev": true 1064 | } 1065 | } 1066 | }, 1067 | "has-ansi": { 1068 | "version": "2.0.0", 1069 | "bundled": true, 1070 | "dev": true 1071 | }, 1072 | "has-flag": { 1073 | "version": "1.0.0", 1074 | "bundled": true, 1075 | "dev": true 1076 | }, 1077 | "hosted-git-info": { 1078 | "version": "2.4.2", 1079 | "bundled": true, 1080 | "dev": true 1081 | }, 1082 | "imurmurhash": { 1083 | "version": "0.1.4", 1084 | "bundled": true, 1085 | "dev": true 1086 | }, 1087 | "inflight": { 1088 | "version": "1.0.6", 1089 | "bundled": true, 1090 | "dev": true 1091 | }, 1092 | "inherits": { 1093 | "version": "2.0.3", 1094 | "bundled": true, 1095 | "dev": true 1096 | }, 1097 | "invariant": { 1098 | "version": "2.2.2", 1099 | "bundled": true, 1100 | "dev": true 1101 | }, 1102 | "invert-kv": { 1103 | "version": "1.0.0", 1104 | "bundled": true, 1105 | "dev": true 1106 | }, 1107 | "is-arrayish": { 1108 | "version": "0.2.1", 1109 | "bundled": true, 1110 | "dev": true 1111 | }, 1112 | "is-buffer": { 1113 | "version": "1.1.5", 1114 | "bundled": true, 1115 | "dev": true 1116 | }, 1117 | "is-builtin-module": { 1118 | "version": "1.0.0", 1119 | "bundled": true, 1120 | "dev": true 1121 | }, 1122 | "is-dotfile": { 1123 | "version": "1.0.2", 1124 | "bundled": true, 1125 | "dev": true 1126 | }, 1127 | "is-equal-shallow": { 1128 | "version": "0.1.3", 1129 | "bundled": true, 1130 | "dev": true 1131 | }, 1132 | "is-extendable": { 1133 | "version": "0.1.1", 1134 | "bundled": true, 1135 | "dev": true 1136 | }, 1137 | "is-extglob": { 1138 | "version": "1.0.0", 1139 | "bundled": true, 1140 | "dev": true 1141 | }, 1142 | "is-finite": { 1143 | "version": "1.0.2", 1144 | "bundled": true, 1145 | "dev": true 1146 | }, 1147 | "is-fullwidth-code-point": { 1148 | "version": "1.0.0", 1149 | "bundled": true, 1150 | "dev": true 1151 | }, 1152 | "is-glob": { 1153 | "version": "2.0.1", 1154 | "bundled": true, 1155 | "dev": true 1156 | }, 1157 | "is-number": { 1158 | "version": "2.1.0", 1159 | "bundled": true, 1160 | "dev": true 1161 | }, 1162 | "is-posix-bracket": { 1163 | "version": "0.1.1", 1164 | "bundled": true, 1165 | "dev": true 1166 | }, 1167 | "is-primitive": { 1168 | "version": "2.0.0", 1169 | "bundled": true, 1170 | "dev": true 1171 | }, 1172 | "is-utf8": { 1173 | "version": "0.2.1", 1174 | "bundled": true, 1175 | "dev": true 1176 | }, 1177 | "isarray": { 1178 | "version": "1.0.0", 1179 | "bundled": true, 1180 | "dev": true 1181 | }, 1182 | "isexe": { 1183 | "version": "2.0.0", 1184 | "bundled": true, 1185 | "dev": true 1186 | }, 1187 | "isobject": { 1188 | "version": "2.1.0", 1189 | "bundled": true, 1190 | "dev": true 1191 | }, 1192 | "istanbul-lib-coverage": { 1193 | "version": "1.1.0", 1194 | "bundled": true, 1195 | "dev": true 1196 | }, 1197 | "istanbul-lib-hook": { 1198 | "version": "1.0.6", 1199 | "bundled": true, 1200 | "dev": true 1201 | }, 1202 | "istanbul-lib-instrument": { 1203 | "version": "1.7.1", 1204 | "bundled": true, 1205 | "dev": true 1206 | }, 1207 | "istanbul-lib-report": { 1208 | "version": "1.1.0", 1209 | "bundled": true, 1210 | "dev": true, 1211 | "dependencies": { 1212 | "supports-color": { 1213 | "version": "3.2.3", 1214 | "bundled": true, 1215 | "dev": true 1216 | } 1217 | } 1218 | }, 1219 | "istanbul-lib-source-maps": { 1220 | "version": "1.2.0", 1221 | "bundled": true, 1222 | "dev": true 1223 | }, 1224 | "istanbul-reports": { 1225 | "version": "1.1.0", 1226 | "bundled": true, 1227 | "dev": true 1228 | }, 1229 | "js-tokens": { 1230 | "version": "3.0.1", 1231 | "bundled": true, 1232 | "dev": true 1233 | }, 1234 | "jsesc": { 1235 | "version": "1.3.0", 1236 | "bundled": true, 1237 | "dev": true 1238 | }, 1239 | "kind-of": { 1240 | "version": "3.2.0", 1241 | "bundled": true, 1242 | "dev": true 1243 | }, 1244 | "lazy-cache": { 1245 | "version": "1.0.4", 1246 | "bundled": true, 1247 | "dev": true, 1248 | "optional": true 1249 | }, 1250 | "lcid": { 1251 | "version": "1.0.0", 1252 | "bundled": true, 1253 | "dev": true 1254 | }, 1255 | "load-json-file": { 1256 | "version": "1.1.0", 1257 | "bundled": true, 1258 | "dev": true 1259 | }, 1260 | "lodash": { 1261 | "version": "4.17.4", 1262 | "bundled": true, 1263 | "dev": true 1264 | }, 1265 | "longest": { 1266 | "version": "1.0.1", 1267 | "bundled": true, 1268 | "dev": true 1269 | }, 1270 | "loose-envify": { 1271 | "version": "1.3.1", 1272 | "bundled": true, 1273 | "dev": true 1274 | }, 1275 | "lru-cache": { 1276 | "version": "4.0.2", 1277 | "bundled": true, 1278 | "dev": true 1279 | }, 1280 | "md5-hex": { 1281 | "version": "1.3.0", 1282 | "bundled": true, 1283 | "dev": true 1284 | }, 1285 | "md5-o-matic": { 1286 | "version": "0.1.1", 1287 | "bundled": true, 1288 | "dev": true 1289 | }, 1290 | "merge-source-map": { 1291 | "version": "1.0.3", 1292 | "bundled": true, 1293 | "dev": true 1294 | }, 1295 | "micromatch": { 1296 | "version": "2.3.11", 1297 | "bundled": true, 1298 | "dev": true 1299 | }, 1300 | "minimatch": { 1301 | "version": "3.0.3", 1302 | "bundled": true, 1303 | "dev": true 1304 | }, 1305 | "minimist": { 1306 | "version": "0.0.8", 1307 | "bundled": true, 1308 | "dev": true 1309 | }, 1310 | "mkdirp": { 1311 | "version": "0.5.1", 1312 | "bundled": true, 1313 | "dev": true 1314 | }, 1315 | "ms": { 1316 | "version": "0.7.3", 1317 | "bundled": true, 1318 | "dev": true 1319 | }, 1320 | "normalize-package-data": { 1321 | "version": "2.3.8", 1322 | "bundled": true, 1323 | "dev": true 1324 | }, 1325 | "normalize-path": { 1326 | "version": "2.1.1", 1327 | "bundled": true, 1328 | "dev": true 1329 | }, 1330 | "number-is-nan": { 1331 | "version": "1.0.1", 1332 | "bundled": true, 1333 | "dev": true 1334 | }, 1335 | "object-assign": { 1336 | "version": "4.1.1", 1337 | "bundled": true, 1338 | "dev": true 1339 | }, 1340 | "object.omit": { 1341 | "version": "2.0.1", 1342 | "bundled": true, 1343 | "dev": true 1344 | }, 1345 | "once": { 1346 | "version": "1.4.0", 1347 | "bundled": true, 1348 | "dev": true 1349 | }, 1350 | "optimist": { 1351 | "version": "0.6.1", 1352 | "bundled": true, 1353 | "dev": true 1354 | }, 1355 | "os-homedir": { 1356 | "version": "1.0.2", 1357 | "bundled": true, 1358 | "dev": true 1359 | }, 1360 | "os-locale": { 1361 | "version": "1.4.0", 1362 | "bundled": true, 1363 | "dev": true 1364 | }, 1365 | "parse-glob": { 1366 | "version": "3.0.4", 1367 | "bundled": true, 1368 | "dev": true 1369 | }, 1370 | "parse-json": { 1371 | "version": "2.2.0", 1372 | "bundled": true, 1373 | "dev": true 1374 | }, 1375 | "path-exists": { 1376 | "version": "2.1.0", 1377 | "bundled": true, 1378 | "dev": true 1379 | }, 1380 | "path-is-absolute": { 1381 | "version": "1.0.1", 1382 | "bundled": true, 1383 | "dev": true 1384 | }, 1385 | "path-parse": { 1386 | "version": "1.0.5", 1387 | "bundled": true, 1388 | "dev": true 1389 | }, 1390 | "path-type": { 1391 | "version": "1.1.0", 1392 | "bundled": true, 1393 | "dev": true 1394 | }, 1395 | "pify": { 1396 | "version": "2.3.0", 1397 | "bundled": true, 1398 | "dev": true 1399 | }, 1400 | "pinkie": { 1401 | "version": "2.0.4", 1402 | "bundled": true, 1403 | "dev": true 1404 | }, 1405 | "pinkie-promise": { 1406 | "version": "2.0.1", 1407 | "bundled": true, 1408 | "dev": true 1409 | }, 1410 | "pkg-dir": { 1411 | "version": "1.0.0", 1412 | "bundled": true, 1413 | "dev": true 1414 | }, 1415 | "preserve": { 1416 | "version": "0.2.0", 1417 | "bundled": true, 1418 | "dev": true 1419 | }, 1420 | "pseudomap": { 1421 | "version": "1.0.2", 1422 | "bundled": true, 1423 | "dev": true 1424 | }, 1425 | "randomatic": { 1426 | "version": "1.1.6", 1427 | "bundled": true, 1428 | "dev": true 1429 | }, 1430 | "read-pkg": { 1431 | "version": "1.1.0", 1432 | "bundled": true, 1433 | "dev": true 1434 | }, 1435 | "read-pkg-up": { 1436 | "version": "1.0.1", 1437 | "bundled": true, 1438 | "dev": true 1439 | }, 1440 | "regenerator-runtime": { 1441 | "version": "0.10.5", 1442 | "bundled": true, 1443 | "dev": true 1444 | }, 1445 | "regex-cache": { 1446 | "version": "0.4.3", 1447 | "bundled": true, 1448 | "dev": true 1449 | }, 1450 | "remove-trailing-separator": { 1451 | "version": "1.0.1", 1452 | "bundled": true, 1453 | "dev": true 1454 | }, 1455 | "repeat-element": { 1456 | "version": "1.1.2", 1457 | "bundled": true, 1458 | "dev": true 1459 | }, 1460 | "repeat-string": { 1461 | "version": "1.6.1", 1462 | "bundled": true, 1463 | "dev": true 1464 | }, 1465 | "repeating": { 1466 | "version": "2.0.1", 1467 | "bundled": true, 1468 | "dev": true 1469 | }, 1470 | "require-directory": { 1471 | "version": "2.1.1", 1472 | "bundled": true, 1473 | "dev": true 1474 | }, 1475 | "require-main-filename": { 1476 | "version": "1.0.1", 1477 | "bundled": true, 1478 | "dev": true 1479 | }, 1480 | "resolve-from": { 1481 | "version": "2.0.0", 1482 | "bundled": true, 1483 | "dev": true 1484 | }, 1485 | "right-align": { 1486 | "version": "0.1.3", 1487 | "bundled": true, 1488 | "dev": true, 1489 | "optional": true 1490 | }, 1491 | "rimraf": { 1492 | "version": "2.6.1", 1493 | "bundled": true, 1494 | "dev": true 1495 | }, 1496 | "semver": { 1497 | "version": "5.3.0", 1498 | "bundled": true, 1499 | "dev": true 1500 | }, 1501 | "set-blocking": { 1502 | "version": "2.0.0", 1503 | "bundled": true, 1504 | "dev": true 1505 | }, 1506 | "signal-exit": { 1507 | "version": "3.0.2", 1508 | "bundled": true, 1509 | "dev": true 1510 | }, 1511 | "slide": { 1512 | "version": "1.1.6", 1513 | "bundled": true, 1514 | "dev": true 1515 | }, 1516 | "source-map": { 1517 | "version": "0.5.6", 1518 | "bundled": true, 1519 | "dev": true 1520 | }, 1521 | "spawn-wrap": { 1522 | "version": "1.2.4", 1523 | "bundled": true, 1524 | "dev": true, 1525 | "dependencies": { 1526 | "signal-exit": { 1527 | "version": "2.1.2", 1528 | "bundled": true, 1529 | "dev": true 1530 | } 1531 | } 1532 | }, 1533 | "spdx-correct": { 1534 | "version": "1.0.2", 1535 | "bundled": true, 1536 | "dev": true 1537 | }, 1538 | "spdx-expression-parse": { 1539 | "version": "1.0.4", 1540 | "bundled": true, 1541 | "dev": true 1542 | }, 1543 | "spdx-license-ids": { 1544 | "version": "1.2.2", 1545 | "bundled": true, 1546 | "dev": true 1547 | }, 1548 | "string-width": { 1549 | "version": "1.0.2", 1550 | "bundled": true, 1551 | "dev": true 1552 | }, 1553 | "strip-ansi": { 1554 | "version": "3.0.1", 1555 | "bundled": true, 1556 | "dev": true 1557 | }, 1558 | "strip-bom": { 1559 | "version": "2.0.0", 1560 | "bundled": true, 1561 | "dev": true 1562 | }, 1563 | "supports-color": { 1564 | "version": "2.0.0", 1565 | "bundled": true, 1566 | "dev": true 1567 | }, 1568 | "test-exclude": { 1569 | "version": "4.1.0", 1570 | "bundled": true, 1571 | "dev": true 1572 | }, 1573 | "to-fast-properties": { 1574 | "version": "1.0.3", 1575 | "bundled": true, 1576 | "dev": true 1577 | }, 1578 | "trim-right": { 1579 | "version": "1.0.1", 1580 | "bundled": true, 1581 | "dev": true 1582 | }, 1583 | "uglify-js": { 1584 | "version": "2.8.22", 1585 | "bundled": true, 1586 | "dev": true, 1587 | "optional": true, 1588 | "dependencies": { 1589 | "yargs": { 1590 | "version": "3.10.0", 1591 | "bundled": true, 1592 | "dev": true, 1593 | "optional": true 1594 | } 1595 | } 1596 | }, 1597 | "uglify-to-browserify": { 1598 | "version": "1.0.2", 1599 | "bundled": true, 1600 | "dev": true, 1601 | "optional": true 1602 | }, 1603 | "validate-npm-package-license": { 1604 | "version": "3.0.1", 1605 | "bundled": true, 1606 | "dev": true 1607 | }, 1608 | "which": { 1609 | "version": "1.2.14", 1610 | "bundled": true, 1611 | "dev": true 1612 | }, 1613 | "which-module": { 1614 | "version": "1.0.0", 1615 | "bundled": true, 1616 | "dev": true 1617 | }, 1618 | "window-size": { 1619 | "version": "0.1.0", 1620 | "bundled": true, 1621 | "dev": true, 1622 | "optional": true 1623 | }, 1624 | "wordwrap": { 1625 | "version": "0.0.3", 1626 | "bundled": true, 1627 | "dev": true 1628 | }, 1629 | "wrap-ansi": { 1630 | "version": "2.1.0", 1631 | "bundled": true, 1632 | "dev": true 1633 | }, 1634 | "wrappy": { 1635 | "version": "1.0.2", 1636 | "bundled": true, 1637 | "dev": true 1638 | }, 1639 | "write-file-atomic": { 1640 | "version": "1.3.4", 1641 | "bundled": true, 1642 | "dev": true 1643 | }, 1644 | "y18n": { 1645 | "version": "3.2.1", 1646 | "bundled": true, 1647 | "dev": true 1648 | }, 1649 | "yallist": { 1650 | "version": "2.1.2", 1651 | "bundled": true, 1652 | "dev": true 1653 | }, 1654 | "yargs": { 1655 | "version": "7.1.0", 1656 | "bundled": true, 1657 | "dev": true, 1658 | "dependencies": { 1659 | "camelcase": { 1660 | "version": "3.0.0", 1661 | "bundled": true, 1662 | "dev": true 1663 | }, 1664 | "cliui": { 1665 | "version": "3.2.0", 1666 | "bundled": true, 1667 | "dev": true 1668 | } 1669 | } 1670 | }, 1671 | "yargs-parser": { 1672 | "version": "5.0.0", 1673 | "bundled": true, 1674 | "dev": true, 1675 | "dependencies": { 1676 | "camelcase": { 1677 | "version": "3.0.0", 1678 | "bundled": true, 1679 | "dev": true 1680 | } 1681 | } 1682 | } 1683 | } 1684 | }, 1685 | "on-finished": { 1686 | "version": "2.3.0", 1687 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1688 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 1689 | }, 1690 | "once": { 1691 | "version": "1.4.0", 1692 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1693 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1694 | "dev": true 1695 | }, 1696 | "parseurl": { 1697 | "version": "1.3.1", 1698 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 1699 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 1700 | }, 1701 | "path-is-absolute": { 1702 | "version": "1.0.1", 1703 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1704 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1705 | "dev": true 1706 | }, 1707 | "path-to-regexp": { 1708 | "version": "0.1.7", 1709 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1710 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1711 | }, 1712 | "pathval": { 1713 | "version": "1.1.0", 1714 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1715 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1716 | "dev": true 1717 | }, 1718 | "platform": { 1719 | "version": "1.3.3", 1720 | "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.3.tgz", 1721 | "integrity": "sha1-ZGx3ARiZhwtqCQPnXpl+jlHadGE=" 1722 | }, 1723 | "process-nextick-args": { 1724 | "version": "1.0.7", 1725 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 1726 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 1727 | }, 1728 | "proxy-addr": { 1729 | "version": "1.1.4", 1730 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 1731 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 1732 | }, 1733 | "qs": { 1734 | "version": "6.4.0", 1735 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 1736 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 1737 | }, 1738 | "range-parser": { 1739 | "version": "1.2.0", 1740 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1741 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1742 | }, 1743 | "raw-body": { 1744 | "version": "2.2.0", 1745 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 1746 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 1747 | }, 1748 | "readable-stream": { 1749 | "version": "2.2.7", 1750 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", 1751 | "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=" 1752 | }, 1753 | "referrer-policy": { 1754 | "version": "1.1.0", 1755 | "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz", 1756 | "integrity": "sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk=" 1757 | }, 1758 | "reflect-metadata": { 1759 | "version": "0.1.10", 1760 | "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", 1761 | "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=" 1762 | }, 1763 | "regexp-clone": { 1764 | "version": "0.0.1", 1765 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", 1766 | "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" 1767 | }, 1768 | "require_optional": { 1769 | "version": "1.0.0", 1770 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.0.tgz", 1771 | "integrity": "sha1-UqhhN6hJco62ClVTNhf4+RT1mr8=" 1772 | }, 1773 | "resolve-from": { 1774 | "version": "2.0.0", 1775 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 1776 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 1777 | }, 1778 | "safe-buffer": { 1779 | "version": "5.0.1", 1780 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", 1781 | "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" 1782 | }, 1783 | "semver": { 1784 | "version": "5.3.0", 1785 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 1786 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" 1787 | }, 1788 | "send": { 1789 | "version": "0.15.3", 1790 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 1791 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 1792 | }, 1793 | "serve-static": { 1794 | "version": "1.12.3", 1795 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 1796 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 1797 | }, 1798 | "setprototypeof": { 1799 | "version": "1.0.3", 1800 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1801 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 1802 | }, 1803 | "sliced": { 1804 | "version": "1.0.1", 1805 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 1806 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 1807 | }, 1808 | "source-map": { 1809 | "version": "0.5.6", 1810 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 1811 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", 1812 | "dev": true 1813 | }, 1814 | "source-map-support": { 1815 | "version": "0.4.15", 1816 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", 1817 | "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", 1818 | "dev": true 1819 | }, 1820 | "statuses": { 1821 | "version": "1.3.1", 1822 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 1823 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 1824 | }, 1825 | "string_decoder": { 1826 | "version": "1.0.2", 1827 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", 1828 | "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=" 1829 | }, 1830 | "strip-ansi": { 1831 | "version": "3.0.1", 1832 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1833 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1834 | "dev": true 1835 | }, 1836 | "strip-bom": { 1837 | "version": "3.0.0", 1838 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1839 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1840 | "dev": true 1841 | }, 1842 | "strip-json-comments": { 1843 | "version": "2.0.1", 1844 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1845 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1846 | "dev": true 1847 | }, 1848 | "superagent": { 1849 | "version": "3.5.2", 1850 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.5.2.tgz", 1851 | "integrity": "sha1-M2GjlxVnUEw1EGOr6q4PqiPb8/g=", 1852 | "dev": true 1853 | }, 1854 | "supertest": { 1855 | "version": "3.0.0", 1856 | "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.0.0.tgz", 1857 | "integrity": "sha1-jUu2j9GDDuBwM7HFpamkAhyWUpY=", 1858 | "dev": true 1859 | }, 1860 | "supports-color": { 1861 | "version": "3.1.2", 1862 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 1863 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 1864 | "dev": true 1865 | }, 1866 | "ts-node": { 1867 | "version": "3.0.6", 1868 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.0.6.tgz", 1869 | "integrity": "sha1-VRJ/95DH7r9rpowebd6UsJqqIeA=", 1870 | "dev": true, 1871 | "dependencies": { 1872 | "minimist": { 1873 | "version": "1.2.0", 1874 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1875 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1876 | "dev": true 1877 | } 1878 | } 1879 | }, 1880 | "tsconfig": { 1881 | "version": "6.0.0", 1882 | "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", 1883 | "integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=", 1884 | "dev": true 1885 | }, 1886 | "type-detect": { 1887 | "version": "4.0.3", 1888 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", 1889 | "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", 1890 | "dev": true 1891 | }, 1892 | "type-is": { 1893 | "version": "1.6.15", 1894 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 1895 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 1896 | }, 1897 | "typescript": { 1898 | "version": "2.3.4", 1899 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.3.4.tgz", 1900 | "integrity": "sha1-PTgyGCgjHkNPKHUUlZw3qCtin0I=", 1901 | "dev": true 1902 | }, 1903 | "unpipe": { 1904 | "version": "1.0.0", 1905 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1906 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1907 | }, 1908 | "user-home": { 1909 | "version": "1.1.1", 1910 | "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", 1911 | "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", 1912 | "dev": true 1913 | }, 1914 | "util-deprecate": { 1915 | "version": "1.0.2", 1916 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1917 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1918 | }, 1919 | "utils-merge": { 1920 | "version": "1.0.0", 1921 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 1922 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 1923 | }, 1924 | "v8flags": { 1925 | "version": "2.1.1", 1926 | "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", 1927 | "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", 1928 | "dev": true 1929 | }, 1930 | "vary": { 1931 | "version": "1.1.1", 1932 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 1933 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 1934 | }, 1935 | "wrappy": { 1936 | "version": "1.0.2", 1937 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1938 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1939 | "dev": true 1940 | }, 1941 | "x-xss-protection": { 1942 | "version": "1.0.0", 1943 | "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.0.0.tgz", 1944 | "integrity": "sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk=" 1945 | }, 1946 | "yn": { 1947 | "version": "2.0.0", 1948 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 1949 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", 1950 | "dev": true 1951 | } 1952 | } 1953 | } 1954 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo3", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "Remo H. Jansen (http://www.remojansen.com)", 8 | "license": "MIT", 9 | "dependencies": { 10 | "body-parser": "^1.17.2", 11 | "express": "^4.15.3", 12 | "helmet": "^3.6.0", 13 | "inversify": "^4.1.0", 14 | "inversify-express-utils": "^3.5.1", 15 | "mongoose": "^4.10.0", 16 | "reflect-metadata": "^0.1.10" 17 | }, 18 | "devDependencies": { 19 | "@types/body-parser": "^1.16.3", 20 | "@types/chai": "^3.5.2", 21 | "@types/express": "^4.0.35", 22 | "@types/helmet": "0.0.35", 23 | "@types/mocha": "^2.2.41", 24 | "@types/mongoose": "^4.7.13", 25 | "@types/supertest": "^2.0.0", 26 | "chai": "^4.0.0", 27 | "mocha": "^3.4.2", 28 | "nyc": "^10.3.2", 29 | "supertest": "^3.0.0", 30 | "ts-node": "^3.0.4", 31 | "typescript": "^2.3.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/constants/decorators.ts: -------------------------------------------------------------------------------- 1 | import { inject } from "inversify"; 2 | import { TYPES } from "./types"; 3 | 4 | export const dbClient = inject(TYPES.DbClient); 5 | export const movieRepository = inject(TYPES.MovieRepository); 6 | export const actorRepository = inject(TYPES.ActorRepository); 7 | export const directorRepository = inject(TYPES.DirectorRepository); 8 | export const searchService = inject(TYPES.SearchService); 9 | export const accountRepository = inject(TYPES.AccountRepository); 10 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/constants/types.ts: -------------------------------------------------------------------------------- 1 | export const TYPES = { 2 | App: Symbol("App"), 3 | DbClient: Symbol("DbClient"), 4 | MovieRepository: Symbol("MovieRepository"), 5 | DirectorRepository: Symbol("DirectorRepository"), 6 | ActorRepository: Symbol("ActorRepository"), 7 | AccountRepository: Symbol("AccountRepository"), 8 | SearchService: Symbol("SearchService") 9 | }; 10 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/interfaces/repositories.ts: -------------------------------------------------------------------------------- 1 | import { Actor } from "../model/actor"; 2 | import { Director } from "../model/director"; 3 | import { Movie } from "../model/movie"; 4 | import { Account } from "../model/account"; 5 | import { Repository } from "../interfaces/repositories"; 6 | 7 | export type Query = { 8 | [P in keyof T]?: T[P] | { $regex: RegExp }; 9 | }; 10 | 11 | export interface Repository { 12 | save(doc: T): Promise; 13 | findAll(): Promise; 14 | findById(id: string): Promise; 15 | findManyById(ids: string[]): Promise; 16 | findManyByQuery(query?: Query): Promise; 17 | } 18 | 19 | export type MovieRepository = Repository; 20 | export type ActorRepository = Repository; 21 | export type DirectorRepository = Repository; 22 | export type AccountRepository = Repository; 23 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/interfaces/services.ts: -------------------------------------------------------------------------------- 1 | import { Movie } from "../model/movie"; 2 | 3 | export interface SearchService { 4 | 5 | search(query: string): Promise; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/model/account.ts: -------------------------------------------------------------------------------- 1 | export interface Account { 2 | id?: string; 3 | username: string; 4 | email: string; 5 | password: string; 6 | roles: string[] 7 | } 8 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/model/actor.ts: -------------------------------------------------------------------------------- 1 | export interface Actor { 2 | id?: string; 3 | name: string; 4 | yearBorn: number; 5 | nationality: string; 6 | movies: string[]; 7 | } 8 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/model/director.ts: -------------------------------------------------------------------------------- 1 | export interface Director { 2 | id?: string; 3 | name: string; 4 | yearBorn: number; 5 | nationality: string; 6 | movies: string[]; 7 | } 8 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/model/movie.ts: -------------------------------------------------------------------------------- 1 | export interface Movie { 2 | id?: string; 3 | title: string; 4 | releaseYear: number; 5 | releaseMonth: number; 6 | releaseDay: number; 7 | summary: string; 8 | directors: string[]; 9 | actors: string[]; 10 | } 11 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/domain/services/search_service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { SearchService as SearchServiceInterface } from "../interfaces/services"; 3 | import { Movie } from "../model/movie"; 4 | import { Actor } from "../model/actor"; 5 | import { Director } from "../model/director"; 6 | import { movieRepository, actorRepository, directorRepository } from "../constants/decorators"; 7 | import { MovieRepository, DirectorRepository, ActorRepository } from "../interfaces/repositories"; 8 | 9 | @injectable() 10 | export class SearchService implements SearchServiceInterface { 11 | 12 | @movieRepository private _movieRepository: MovieRepository; 13 | @actorRepository private _actorRepository: ActorRepository; 14 | @directorRepository private _directorRepository: DirectorRepository; 15 | 16 | public async search(query: string): Promise { 17 | 18 | const moviesWithMatchingTitle = await this._movieRepository.findManyByQuery( 19 | { 20 | title: { 21 | $regex: new RegExp(query, "ig") 22 | } 23 | } 24 | ); 25 | 26 | const matchingActors = await this._actorRepository.findManyByQuery( 27 | { 28 | name: { 29 | $regex: new RegExp(query, "ig") 30 | } 31 | } 32 | ); 33 | 34 | const matchingDirectors = await this._directorRepository.findManyByQuery( 35 | { 36 | name: { 37 | $regex: new RegExp(query, "ig") 38 | } 39 | } 40 | ); 41 | 42 | const getMovieIds = (arr: Actor[] | Director[]) => { 43 | return arr.map(i => i.movies).reduce((p, c) => [...p, ...c], []); 44 | } 45 | 46 | const moviesIdsWithMatchingDirector = getMovieIds(matchingDirectors); 47 | 48 | const movieIdsWithMatchingActor = getMovieIds(matchingActors); 49 | 50 | const movieIdsWithMatchingActorOrDirector = [ 51 | ...moviesIdsWithMatchingDirector, 52 | ...movieIdsWithMatchingActor 53 | ]; 54 | 55 | const moviesWithMatchingActorOrDirector = await this._movieRepository.findManyById(movieIdsWithMatchingActorOrDirector); 56 | 57 | const matchingMovies = [ 58 | ...moviesWithMatchingTitle, 59 | ...moviesWithMatchingActorOrDirector 60 | ]; 61 | 62 | return matchingMovies; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { bootstrap } from "./infrastructure/bootstrapping/bootstrap"; 3 | import { container } from "./infrastructure/ioc/ioc_container"; 4 | import { referenceDataIoCModule } from "./inversify.config"; 5 | 6 | async function runApp() { 7 | const app = await bootstrap( 8 | container, 9 | process.env.APP_PORT || 8080, 10 | process.env.DB_HOST || "localhost", 11 | process.env.DB_NAME || "demo", 12 | referenceDataIoCModule 13 | ); 14 | return app; 15 | } 16 | 17 | (async () => { 18 | await runApp(); 19 | })(); 20 | 21 | export { runApp }; 22 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/bootstrapping/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { Container, ContainerModule } from "inversify"; 2 | import * as express from "express"; 3 | import { InversifyExpressServer } from "inversify-express-utils"; 4 | import * as bodyParser from "body-parser"; 5 | import * as helmet from "helmet"; 6 | import { reqMiddleware, exceptionLoggerMiddleware } from "./middleware"; 7 | import { DbClient, getDatabaseClient } from "../data_access/db_client"; 8 | import { TYPES } from "../../domain/constants/types"; 9 | 10 | export async function bootstrap( 11 | container: Container, 12 | appPort: number, 13 | dbHost: string, 14 | dbName: string, 15 | ...modules: ContainerModule[] 16 | ) { 17 | 18 | if (container.isBound(TYPES.App) === false) { 19 | 20 | const dbClient = await getDatabaseClient(dbHost, dbName); 21 | container.bind(TYPES.DbClient).toConstantValue(dbClient); 22 | container.load(...modules); 23 | 24 | // Configure express server 25 | const server = new InversifyExpressServer(container); 26 | 27 | server.setConfig((app) => { 28 | 29 | // Disable default cache 30 | app.set("etag", false); 31 | 32 | // Configure requests body parsing 33 | app.use(bodyParser.urlencoded({ extended: true })); 34 | app.use(bodyParser.json()); 35 | 36 | // Adds some decurity defaults 37 | app.use(helmet()); 38 | 39 | // Log all requets that hit the server 40 | app.use(reqMiddleware); 41 | 42 | }); 43 | 44 | server.setErrorConfig((app) => { 45 | // Catch and log all exceptions 46 | app.use(exceptionLoggerMiddleware); 47 | }); 48 | 49 | const app = server.build(); 50 | 51 | // Run express server 52 | console.log(`Application listening on port ${appPort}...`); 53 | app.listen(appPort); 54 | 55 | container.bind(TYPES.App).toConstantValue(app); 56 | 57 | return app; 58 | } else { 59 | return container.get(TYPES.App); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/bootstrapping/middleware.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | 3 | export function reqMiddleware(req: express.Request, res: express.Response, next: () => void) { 4 | console.log(` 5 | ---------------------------------- 6 | REQUEST MIDDLEWARE 7 | HTTP ${req.method} ${req.url} 8 | ---------------------------------- 9 | `); 10 | next(); 11 | } 12 | 13 | export function exceptionLoggerMiddleware(error: Error, req: express.Request, res: express.Response, next: () => void) { 14 | 15 | // Log exception 16 | console.error(` 17 | ---------------------------------- 18 | EXCEPTION MIDDLEWARE 19 | HTTP ${req.method} ${req.url} 20 | ${error.message} 21 | ${error.stack} 22 | ---------------------------------- 23 | `); 24 | 25 | // Hide stack from client for security reasons 26 | const e = { error: "Internal server error" }; 27 | res.status(500).json(e); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/data_access/db_client.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from "mongoose"; 2 | 3 | export type DbClient = mongoose.Mongoose; 4 | 5 | export async function getDatabaseClient(dbHost: string, dbName: string) { 6 | return new Promise((resolve, reject) => { 7 | const connString = `mongodb://${dbHost}/${dbName}`; 8 | mongoose.connect(connString); 9 | const db = mongoose.connection; 10 | db.on("error", (e: Error) => { 11 | console.error("Db conenction error:", e); 12 | reject(e); 13 | }); 14 | db.once("open", () => { 15 | console.log("Db conenction success:", connString); 16 | resolve(mongoose); 17 | }); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/data_access/repositories/account_repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Document } from "mongoose"; 3 | import { DbClient } from "../db_client"; 4 | import { dbClient } from "../../../domain/constants/decorators"; 5 | import { GenericRepository } from "../repositories/generic_repository"; 6 | import { Account } from "../../../domain/model/account"; 7 | import { AccountRepository as AccountRepositoryInterface } from "../../../domain/interfaces/repositories"; 8 | 9 | export interface AccountModel extends Account, Document {} 10 | 11 | @injectable() 12 | export class AccountRepository 13 | extends GenericRepository 14 | implements AccountRepositoryInterface { 15 | 16 | public constructor( 17 | @dbClient dbClient: DbClient 18 | ) { 19 | super( 20 | dbClient, 21 | "Accounts", 22 | { 23 | username: String, 24 | email: String, 25 | password: String, 26 | roles: [String] 27 | } 28 | ); 29 | 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/data_access/repositories/actor_repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Document } from "mongoose"; 3 | import { DbClient } from "../db_client"; 4 | import { dbClient } from "../../../domain/constants/decorators"; 5 | import { GenericRepository } from "../repositories/generic_repository"; 6 | import { Actor } from "../../../domain/model/actor"; 7 | import { ActorRepository as ActorRepositoryInterface } from "../../../domain/interfaces/repositories"; 8 | 9 | export interface ActorModel extends Actor, Document {} 10 | 11 | @injectable() 12 | export class ActorRepository 13 | extends GenericRepository 14 | implements ActorRepositoryInterface { 15 | 16 | public constructor( 17 | @dbClient dbClient: DbClient 18 | ) { 19 | super( 20 | dbClient, 21 | "Actors", 22 | { 23 | name: String, 24 | yearBorn: Number, 25 | nationality: String, 26 | movies: [String] 27 | } 28 | ); 29 | 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/data_access/repositories/director_repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Document } from "mongoose"; 3 | import { DbClient } from "../db_client"; 4 | import { dbClient } from "../../../domain/constants/decorators"; 5 | import { GenericRepository } from "../repositories/generic_repository"; 6 | import { Director } from "../../../domain/model/director"; 7 | import { DirectorRepository as DirectorRepositoryInterface } from "../../../domain/interfaces/repositories"; 8 | 9 | export interface DirectorModel extends Director, Document {} 10 | 11 | @injectable() 12 | export class DirectorRepository 13 | extends GenericRepository 14 | implements DirectorRepositoryInterface { 15 | 16 | public constructor( 17 | @dbClient dbClient: DbClient 18 | ) { 19 | super( 20 | dbClient, 21 | "Directors", 22 | { 23 | name: String, 24 | yearBorn: Number, 25 | nationality: String, 26 | movies: [String] 27 | } 28 | ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/data_access/repositories/generic_repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable, unmanaged } from "inversify"; 2 | import { Schema, Document, Model, SchemaDefinition } from "mongoose"; 3 | import { DbClient } from "../db_client"; 4 | import { dbClient } from "../../../domain/constants/decorators"; 5 | import { Repository, Query } from "../../../domain/interfaces/repositories"; 6 | 7 | @injectable() 8 | export class GenericRepository 9 | implements Repository { 10 | 11 | private _name: string; 12 | protected Model: Model; 13 | 14 | public constructor ( 15 | @dbClient dbClient: DbClient, 16 | @unmanaged() name: string, 17 | @unmanaged() schemaDefinition: SchemaDefinition 18 | ) { 19 | this._name = name; 20 | const schema = new Schema(schemaDefinition, { collection: this._name }); 21 | this.Model = dbClient.model(this._name, schema); 22 | } 23 | 24 | // We wrap the mongoose API here so we can use async / await 25 | 26 | public async findAll() { 27 | return new Promise((resolve, reject) => { 28 | this.Model.find((err, res) => { 29 | if (err) { 30 | reject(err); 31 | } 32 | const result = res.map((r) => this._readMapper(r)); 33 | resolve(result); 34 | }); 35 | }); 36 | } 37 | 38 | public async findById(id: string) { 39 | return new Promise((resolve, reject) => { 40 | this.Model.findById(id, (err, res) => { 41 | if (err) { 42 | reject(err); 43 | } 44 | if (res === null) { 45 | reject(); 46 | } else { 47 | const result = this._readMapper(res); 48 | resolve(result); 49 | } 50 | }); 51 | }); 52 | } 53 | 54 | public async save(doc: TEntity) { 55 | return new Promise((resolve, reject) => { 56 | const instance = new this.Model(doc); 57 | instance.save((err, res) => { 58 | if (err) { 59 | reject(err); 60 | } 61 | resolve(this._readMapper(res)); 62 | }); 63 | }); 64 | } 65 | 66 | public findManyById(ids: string[]) { 67 | return new Promise((resolve, reject) => { 68 | const query = { _id: { $in : ids } }; 69 | this.Model.find(query, (err, res) => { 70 | if (err) { 71 | reject(err); 72 | } 73 | resolve(res.map((r) => this._readMapper(r))); 74 | }); 75 | }); 76 | } 77 | 78 | public findManyByQuery( 79 | query: Query, 80 | ) { 81 | return new Promise((resolve, reject) => { 82 | this.Model.find(query as any, (err, res) => { 83 | if (err) { 84 | reject(err); 85 | } 86 | resolve(res.map((r) => this._readMapper(r))); 87 | }); 88 | }); 89 | } 90 | 91 | private _readMapper(model: TModel) { 92 | const obj: any = model.toJSON(); 93 | Object.defineProperty(obj, "id", Object.getOwnPropertyDescriptor(obj, "_id")); 94 | delete obj["_id"]; 95 | return obj as TEntity; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/data_access/repositories/movie_repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Document } from "mongoose"; 3 | import { DbClient } from "../db_client"; 4 | import { dbClient } from "../../../domain/constants/decorators"; 5 | import { GenericRepository } from "../repositories/generic_repository"; 6 | import { Movie } from "../../../domain/model/movie"; 7 | import { MovieRepository as MovieRepositoryInterface } from "../../../domain/interfaces/repositories"; 8 | 9 | export interface MovieModel extends Movie, Document {} 10 | 11 | @injectable() 12 | export class MovieRepository 13 | extends GenericRepository 14 | implements MovieRepositoryInterface { 15 | 16 | public constructor( 17 | @dbClient dbClient: DbClient 18 | ) { 19 | super( 20 | dbClient, 21 | "Movies", 22 | { 23 | title: String, 24 | releaseYear: Number, 25 | releaseMonth: Number, 26 | releaseDay: Number, 27 | summary: String, 28 | actors: [String], 29 | directors: [String] 30 | } 31 | ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/ioc/ioc_container.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "inversify"; 2 | 3 | const container = new Container(); 4 | 5 | export { container }; 6 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/infrastructure/ioc/utils.ts: -------------------------------------------------------------------------------- 1 | import { interfaces } from "inversify"; 2 | import { TYPE } from "inversify-express-utils"; 3 | 4 | // Decorators 5 | 6 | // Utils 7 | export function registerController( 8 | bind: interfaces.Bind, 9 | constructor: interfaces.Newable 10 | ) { 11 | bind(TYPE.Controller) 12 | .to(constructor) 13 | .whenTargetNamed(constructor.name); 14 | } 15 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule } from "inversify"; 2 | 3 | // Interfaces & Types 4 | import { TYPES } from "./domain/constants/types"; 5 | import { 6 | MovieRepository as MovieRepositoryInterface, 7 | ActorRepository as ActorRepositoryInterface, 8 | DirectorRepository as DirectorRepositoryInterface, 9 | AccountRepository as AccountRepositoryInterface 10 | } from "./domain/interfaces/repositories"; 11 | 12 | import { SearchService as SearchServiceInterface } from "./domain/interfaces/services"; 13 | 14 | // Controllers 15 | import { MovieController } from "./ui/rest_api/controllers/movie_controller"; 16 | import { DirectorController } from "./ui/rest_api/controllers/director_controller"; 17 | import { ActorController } from "./ui/rest_api/controllers/actor_controller"; 18 | import { SecureController } from "./ui/rest_api/controllers/secure_controller"; 19 | import { SearchController } from "./ui/rest_api/controllers/search_controller"; 20 | 21 | // Repositories 22 | import { MovieRepository } from "./infrastructure/data_access/repositories/movie_repository"; 23 | import { DirectorRepository } from "./infrastructure/data_access/repositories/director_repository"; 24 | import { AccountRepository } from "./infrastructure/data_access/repositories/account_repository"; 25 | import { ActorRepository } from "./infrastructure/data_access/repositories/actor_repository"; 26 | 27 | // Services 28 | import { SearchService } from "./domain/services/search_service"; 29 | 30 | // Infrastructure & Utils 31 | import { registerController } from "./infrastructure/ioc/utils"; 32 | 33 | export const referenceDataIoCModule = new ContainerModule((bind) => { 34 | 35 | // Controllers 36 | registerController(bind, MovieController); 37 | registerController(bind, DirectorController); 38 | registerController(bind, ActorController); 39 | registerController(bind, SecureController); 40 | registerController(bind, SearchController); 41 | 42 | // Repositories 43 | bind(TYPES.MovieRepository) 44 | .to(MovieRepository).inSingletonScope(); 45 | 46 | bind(TYPES.DirectorRepository) 47 | .to(DirectorRepository).inSingletonScope(); 48 | 49 | bind(TYPES.ActorRepository) 50 | .to(ActorRepository).inSingletonScope(); 51 | 52 | bind(TYPES.AccountRepository) 53 | .to(AccountRepository).inSingletonScope(); 54 | 55 | // Services 56 | bind(TYPES.SearchService) 57 | .to(SearchService).inSingletonScope(); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/ui/rest_api/controllers/actor_controller.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Controller, Get, RequestParam } from "inversify-express-utils"; 3 | import { actorRepository } from "../../../domain/constants/decorators"; 4 | import { ActorRepository } from "../../../domain/interfaces/repositories"; 5 | 6 | @injectable() 7 | @Controller("/api/actors") 8 | export class ActorController { 9 | 10 | @actorRepository public _actorRepository: ActorRepository; 11 | 12 | @Get("/") 13 | public async get() { 14 | return await this._actorRepository.findAll(); 15 | } 16 | 17 | @Get("/:id") 18 | public async getById( 19 | @RequestParam("id") id: string, 20 | ) { 21 | return await this._actorRepository.findById(id); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/ui/rest_api/controllers/director_controller.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Controller, Get, RequestParam } from "inversify-express-utils"; 3 | import { directorRepository } from "../../../domain/constants/decorators"; 4 | import { DirectorRepository } from "../../../domain/interfaces/repositories"; 5 | 6 | @injectable() 7 | @Controller("/api/directors") 8 | export class DirectorController { 9 | 10 | @directorRepository public _directorRepository: DirectorRepository; 11 | 12 | @Get("/") 13 | public async get() { 14 | return await this._directorRepository.findAll(); 15 | } 16 | 17 | @Get("/:id") 18 | public async getById( 19 | @RequestParam("id") id: string, 20 | ) { 21 | return await this._directorRepository.findById(id); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/ui/rest_api/controllers/movie_controller.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Controller, Get, RequestParam } from "inversify-express-utils"; 3 | import { movieRepository } from "../../../domain/constants/decorators"; 4 | import { MovieRepository } from "../../../domain/interfaces/repositories"; 5 | 6 | @injectable() 7 | @Controller("/api/movies") 8 | export class MovieController { 9 | 10 | @movieRepository public _movieRepository: MovieRepository; 11 | 12 | @Get("/") 13 | public async get() { 14 | return await this._movieRepository.findAll(); 15 | } 16 | 17 | @Get("/:id") 18 | public async getById( 19 | @RequestParam("id") id: string, 20 | ) { 21 | return await this._movieRepository.findById(id); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/ui/rest_api/controllers/search_controller.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Controller, Get, RequestParam } from "inversify-express-utils"; 3 | import { SearchService } from "../../../domain/interfaces/services"; 4 | import { searchService } from "../../../domain/constants/decorators"; 5 | 6 | @injectable() 7 | @Controller("/api/search") 8 | export class SearchController { 9 | 10 | @searchService private _searchService: SearchService; 11 | 12 | @Get("/:query") 13 | public async get( 14 | @RequestParam("query") query: string, 15 | ) { 16 | return this._searchService.search(query); 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/ui/rest_api/controllers/secure_controller.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { Controller, Get } from "inversify-express-utils"; 3 | import { authMiddleware } from "../middleware/auth_middleware"; 4 | 5 | // This is an example of a controller protected by the auth middleware 6 | 7 | @injectable() 8 | @Controller("/api/secure", authMiddleware({ role: "admin" })) 9 | export class SecureController { 10 | 11 | @Get("/") 12 | public async get() { 13 | return Promise.resolve(["This", "data", "is", "secure!"]); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/src/ui/rest_api/middleware/auth_middleware.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { Container } from "inversify"; 3 | import { AccountRepository } from "../../../domain/interfaces/repositories"; 4 | import { TYPES } from "../../../domain/constants/types"; 5 | import { container } from "../../../infrastructure/ioc/ioc_container"; 6 | 7 | async function getEmailFromToken(token: string) { 8 | // This is a fake implementation to simplify this example 9 | // in real life you want to use something like JWT 10 | // https://github.com/auth0/node-jsonwebtoken 11 | return new Promise((resolve, reject) => { 12 | if (token === "SOME_VALID_DEMO_CREDENTIAL") { 13 | resolve("test.test@test.com"); 14 | } else { 15 | resolve(null); 16 | } 17 | }); 18 | } 19 | 20 | function authMiddlewareFactory(container: Container) { 21 | return (config: { role: string }) => { 22 | return (req: express.Request, res: express.Response, next: express.NextFunction) => { 23 | 24 | const accountRepository = container.get(TYPES.AccountRepository); 25 | 26 | (async () => { 27 | 28 | // get email using auth token 29 | const token = req.headers["x-auth-token"]; 30 | const email = await getEmailFromToken(token); 31 | 32 | if (email !== null) { 33 | 34 | // find user with matching email 35 | const matched = await accountRepository.findManyByQuery({ email: email }); 36 | 37 | // Check user has required role 38 | if (matched.length === 1 && matched[0].roles.indexOf(config.role) !== -1 ) { 39 | next(); 40 | } else { 41 | res.status(403).end("Forbidden"); 42 | } 43 | 44 | } else { 45 | res.status(401).end("Unauthorized"); 46 | } 47 | 48 | })(); 49 | }; 50 | }; 51 | } 52 | 53 | const authMiddleware = authMiddlewareFactory(container); 54 | 55 | export { authMiddleware }; 56 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/tests/actor_controller.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Actor } from "../src/domain/model/actor"; 3 | import { httpGet } from "./test_utils"; 4 | 5 | describe("ActorController", () => { 6 | 7 | const expectedActor1: Actor = { 8 | id: "5921e3954c2ac9f6162a780a", 9 | name: "Zoe Saldana", 10 | yearBorn: 1978, 11 | nationality: "US", 12 | movies: [ "5921ecb14c2ac9f6162a7811" ] 13 | }; 14 | 15 | function assertActorIsEqual(expectedActor: Actor, actualActor: Actor) { 16 | expect(expectedActor.id).to.eq(actualActor.id); 17 | expect(expectedActor.name).to.eq(actualActor.name); 18 | expect(expectedActor.yearBorn).to.eq(actualActor.yearBorn); 19 | expect(expectedActor.nationality).to.eq(actualActor.nationality); 20 | expect(expectedActor.movies[0]).to.eq(actualActor.movies[0]); 21 | } 22 | 23 | it("GET /api/actors", async () => { 24 | const body = await httpGet("/api/actors"); 25 | assertActorIsEqual(expectedActor1, body[0]); 26 | }); 27 | 28 | it("GET /api/actors/:id", async () => { 29 | const body = await httpGet("/api/actors/5921e3954c2ac9f6162a780a"); 30 | assertActorIsEqual(expectedActor1, body); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/tests/search_controller.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Movie } from "../src/domain/model/movie"; 3 | import { httpGet } from "./test_utils"; 4 | 5 | describe("SearchController", () => { 6 | 7 | const expectedMovie1: Movie = { 8 | title: "Star Trek Into Darkness", 9 | releaseYear: 2013, 10 | releaseMonth: 5, 11 | releaseDay: 16, 12 | summary: "After the crew of the Enterprise find an unstoppable force of terror from within their own organization, Captain Kirk leads a manhunt to a war-zone world to capture a one-man weapon of mass destruction.", 13 | directors: [ "5921e1b04c2ac9f6162a7808" ], 14 | actors: [ 15 | "5921e3954c2ac9f6162a780a", 16 | "5921e3db4c2ac9f6162a780b", 17 | "5921e3fa4c2ac9f6162a780c" 18 | ], 19 | id: "5921ecb14c2ac9f6162a7811" 20 | }; 21 | 22 | function assertMovieIsEqual(expectedActor: Movie, actualActor: Movie) { 23 | expect(expectedActor.id).to.eq(actualActor.id); 24 | expect(expectedActor.title).to.eq(actualActor.title); 25 | expect(expectedActor.releaseMonth).to.eq(actualActor.releaseMonth); 26 | expect(expectedActor.releaseDay).to.eq(actualActor.releaseDay); 27 | expect(expectedActor.directors[0]).to.eq(actualActor.directors[0]); 28 | expect(expectedActor.actors[0]).to.eq(actualActor.actors[0]); 29 | expect(expectedActor.actors[1]).to.eq(actualActor.actors[1]); 30 | expect(expectedActor.actors[2]).to.eq(actualActor.actors[2]); 31 | expect(expectedActor.id).to.eq(actualActor.id); 32 | } 33 | 34 | it("GET /api/search/:query", async () => { 35 | const body = await httpGet("/api/search/zoe"); 36 | assertMovieIsEqual(expectedMovie1, body[0]); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/tests/test_utils.ts: -------------------------------------------------------------------------------- 1 | import * as request from "supertest"; 2 | import { runApp } from "../src/index"; 3 | import { expect } from "chai"; 4 | 5 | export async function httpGet(url: string) { 6 | return new Promise((resolve, reject) => { 7 | runApp().then(app => { 8 | request(app) 9 | .get(url) 10 | .set("Accept", "application/json") 11 | .expect(200) 12 | .end((err, res) => { 13 | expect(err).to.eq(null); 14 | resolve(res.body); 15 | }); 16 | }).catch((e) => { 17 | expect(e).to.eq(null); 18 | }); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /nodejs-madrid-meetup/demo3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["es6", "dom"], 5 | "types": ["reflect-metadata", "mocha"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "strictNullChecks": true, 11 | "noImplicitAny": true, 12 | "noUnusedLocals": true, 13 | "allowUnreachableCode": false, 14 | "noImplicitThis": true 15 | } 16 | } --------------------------------------------------------------------------------