├── .dockerignore ├── .editorconfig ├── .env-sample ├── .gitignore ├── .nvmrc ├── .sequelizerc ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── cluster.js ├── config ├── README.md ├── database.js ├── environments │ ├── development.js │ ├── production.js │ └── test.js └── index.js ├── docker-compose.yml ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── general │ ├── authentication.md │ ├── cli-commands.md │ └── coding-standard.md ├── index.html ├── organization-architecture │ ├── di-container.md │ ├── folder-structure.md │ ├── paths-require.md │ └── recommendation.md ├── setup │ ├── config.md │ ├── database.md │ └── logging.md └── testing │ ├── black-box.md │ ├── testing.md │ └── unit-testing.md ├── index.js ├── logs └── README.md ├── package.json ├── pnpm-lock.yaml ├── postgres-initdb.sh ├── public ├── README.md └── docs │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.html │ ├── oauth2-redirect.html │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ └── swagger-ui.js.map ├── src ├── app │ ├── README.md │ ├── company │ │ ├── delete.js │ │ ├── get.js │ │ ├── index.js │ │ ├── post.js │ │ └── put.js │ ├── index.js │ ├── token │ │ ├── index.js │ │ └── post.js │ └── user │ │ ├── delete.js │ │ ├── get.js │ │ ├── index.js │ │ ├── post.js │ │ └── put.js ├── container.js ├── domain │ ├── README.md │ ├── company │ │ ├── company.js │ │ └── index.js │ ├── helper.js │ ├── token │ │ └── index.js │ └── user │ │ ├── index.js │ │ └── user.js ├── infra │ ├── README.md │ ├── database │ │ ├── README.md │ │ ├── index.js │ │ └── models │ │ │ ├── company.js │ │ │ └── user.js │ ├── encryption │ │ └── index.js │ ├── health │ │ └── index.js │ ├── jwt │ │ └── index.js │ ├── logging │ │ └── logger.js │ ├── repositories │ │ ├── company │ │ │ ├── index.js │ │ │ └── transform.js │ │ ├── index.js │ │ └── user │ │ │ ├── index.js │ │ │ └── transform.js │ ├── sequelize │ │ ├── index.js │ │ ├── migrations │ │ │ ├── 001-users.js │ │ │ └── 002-company.js │ │ └── seeders │ │ │ ├── development │ │ │ ├── 001-users.js │ │ │ └── 002-company.js │ │ │ └── production │ │ │ ├── 001-users.js │ │ │ └── 002-company.js │ └── support │ │ ├── date.js │ │ ├── fakers │ │ ├── README.md │ │ ├── development │ │ │ ├── companies.js │ │ │ └── users.js │ │ └── index.js │ │ └── response.js └── interfaces │ ├── README.md │ └── http │ ├── auth.js │ ├── middlewares │ ├── error_handler.js │ └── http_logger.js │ ├── modules │ ├── company │ │ ├── index.js │ │ ├── instance.js │ │ └── router.js │ ├── index.js │ ├── token │ │ ├── index.js │ │ ├── instance.js │ │ └── router.js │ └── user │ │ ├── index.js │ │ ├── instance.js │ │ └── router.js │ ├── router.js │ ├── server.js │ └── utils │ └── create_controller.js └── test ├── api ├── companies │ ├── delete_companies.spec.js │ ├── get_companies.spec.js │ ├── post_companies.spec.js │ └── put_companies.spec.js ├── index.spec.js ├── token.spec.js └── users │ ├── delete_users.spec.js │ ├── get_users.spec.js │ ├── post_users.spec.js │ └── put_users.spec.js ├── factory.js ├── mocha.opts ├── setup.js └── unit └── app ├── company ├── delete.spec.js ├── get.spec.js ├── post.spec.js └── put.spec.js ├── index.spec.js └── user ├── delete.spec.js ├── get.spec.js ├── post.spec.js └── put.spec.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile* 4 | docker-compose* 5 | .dockerignore 6 | .git 7 | .gitignore 8 | README.md 9 | LICENSE 10 | .vscode 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env-sample: -------------------------------------------------------------------------------- 1 | APP_BASE_URL= 2 | APP_VERSION= 3 | API_SWAGGER= 4 | DATABASE_URL= 5 | DATABASE_URL_TEST= 6 | DATABASE_URL_STAGING= 7 | DATABASE_URL_PRODUCTION= 8 | PORT= 9 | TIMEZONE= 10 | SECRET= 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | .env 4 | npm-debug.* 5 | .DS_Store 6 | .vscode 7 | yarn-error.log 8 | debug.log 9 | coverage 10 | 11 | postgres-data 12 | redis-data 13 | 14 | package-lock.json 15 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.8.0 2 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('./config', 'database.js'), 5 | 'migrations-path': path.resolve('./src/infra/sequelize', 'migrations'), 6 | 'models-path': path.resolve('./src/infra/database', 'models'), 7 | 'seeders-path': path.resolve('./src/infra/sequelize', 'seeders') 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | before_install: 5 | - npm install -g pnpm 6 | - psql -c 'create database node_ddd_test;' -U postgres 7 | install: 8 | - pnpm install 9 | after_script: 10 | - npm run coveralls 11 | after_success: 12 | - npm run travis-deploy-once "npm run semantic-release" 13 | cache: 14 | yarn: true 15 | directories: 16 | - node_modules 17 | services: 18 | - postgresql 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.7.0-alpine 2 | 3 | RUN apk --no-cache add --virtual builds-deps build-base python git 4 | 5 | RUN mkdir -p /usr/src/app 6 | 7 | WORKDIR /usr/src/app 8 | 9 | COPY package.json /usr/src/app/ 10 | COPY .env /usr/src/app/ 11 | 12 | COPY . /usr/src/app/ 13 | 14 | RUN yarn install && \ 15 | npm rebuild bcrypt --build-from-source 16 | 17 | EXPOSE 4000 18 | 19 | CMD [ "npm", "run", "start:dev"] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Talysson de Oliveira Cassiano 4 | Modified work Copyright 2018 Joshua C Alpuerto 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node DDD Boilerplate 2 | > RESTful api with Domain Driven Design 3 | 4 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 5 | [![Coverage Status](https://coveralls.io/repos/github/joshuaalpuerto/node-ddd-boilerplate/badge.svg?branch=master)](https://coveralls.io/github/joshuaalpuerto/node-ddd-boilerplate?branch=master) 6 | 7 | ## Development Environment Setup 8 | 9 | 1. Make sure you have `nvm`, node latest `LTS` version of node installed 10 | 2. Install `pnpm` - `npm install -g pnpm`. 11 | 3. Use a smart `.npmrc`. By default, `npm` doesn’t save installed dependencies to package.json (and you should always track your dependencies!). 12 | 13 | ### NOTE: 14 | If you encounter installation issues with `bcrypt` you need to install [node-gyp](https://github.com/nodejs/node-gyp) first. 15 | If you update your node version to latest and you encountered compatibility issues: 16 | 1. Ensure that there is no `pm2` runnign `pm2 list` (if there is kill the process) 17 | 2. You have to rebuild your bcrypt running this command `npm rebuild bcrypt --update-binary` 18 | 19 | ## Docker support 20 | 21 | **Prerequisites** 22 | 1. [Docker](https://www.docker.com/products/docker-engine) Community Edition v17 or higher 23 | 24 | ```sh 25 | $ docker-compose up -d 26 | ``` 27 | Access `http://localhost:/api/` and you're ready to go! 28 | > http://localhost:4000/api/v1 29 | 30 | ### Docker CLI 31 | - `yarn docker:db:reset` - reset and run all migrations and seeders. 32 | - `yarn docker:db:refresh` - reset and run all migrations. 33 | - `yarn docker:db:refresh-test` - reset and run all migrations for test 34 | - `yarn docker:test` - refreshes test database and run unit and black-box testing. 35 | 36 | *...will add more* 37 | 38 | ## Quick Start 39 | 40 | 1. Clone the repository with `git clone --depth=1 https://github.com/joshuaalpuerto/node-ddd-boilerplate.git` 41 | 2. Install the dependencies with [Yarn](https://yarnpkg.com/en/docs/install/) 42 | 3. Install global dependencies [Application Setup](https://github.com/joshuaalpuerto/node-ddd-boilerplate#application-setup-development) 43 | 4. Create the development and test [Databases](https://github.com/joshuaalpuerto/node-ddd-boilerplate#database-setup-development) 44 | 5. Run database migrations and seed with `yarn db:refresh` 45 | 6. Run the application in development mode with `yarn start` 46 | 7. Access `http://localhost:/api/` and you're ready to go! 47 | > http://localhost:4000/api/v1 48 | 49 | ### Application Setup (Development) 50 | 51 | ```sh 52 | $ npm install -g standard # JavaScript Standard Style 53 | $ npm install -g babel-eslint # required by StandardJs 54 | $ npm install -g snazzy # Format JavaScript Standard Style as beautiful output 55 | $ npm install -g sequelize-cli # CLI for Sequelize 56 | ``` 57 | 58 | ### Database Setup (Development) 59 | 60 | 1. Install [PostgreSql](https://www.postgresql.org/) - v9.6. 61 | 2. Create an empty database named - `node_ddd` and `node_ddd_test` for test enviroment. 62 | 3. Rename the .env and populate it with the correct credentials and settings of your Postgresql databases 63 | 4. Enable SSL in the `postgresql.conf` configuration file of your Postgresql installation. 64 | 65 | Follow the the steps in the next section to enable Posgresql SSL connections. 66 | 67 | ```sh 68 | $ psql 69 | psql (9.6.0) 70 | Type "help" for help. 71 | 72 | $ CREATE DATABASE node_ddd; 73 | $ CREATE DATABASE node_ddd_test; 74 | ``` 75 | 76 | ## Overview 77 | 78 | - uses Node.js > v9 79 | - written using ES6 80 | - uses Yarn for package dependency management 81 | - uses [JavaScript Standard Style](http://standardjs.com/) 82 | - uses `sequelize` and `sequelize-cli` as ORM and data migration tool 83 | > can change easily to diffrent ORM and migration tool. 84 | - Filename convention - `camelCase` should never be used. This leaves `snake_case` and `kebab-case`, I prefer `snake_case` for file. 85 | 86 | ## CLI Tools 87 | 88 | - `yarn start` - start the Node-DDD API Boilerplate for production 89 | - `yarn start:dev` - start the Node-DDD API Boilerplate locally/development 90 | - `yarn start:cc` - start `codecrumbs` will give you quick overview the structure of the project 91 | - `yarn test` - run Unit tests 92 | - `yarn db:reset` - run all migrations and seeds. 93 | - `yarn db:refresh` - run all migrations. 94 | - `yarn lint` - lint codebase using JavaScript Standard Style 95 | - `yarn lint:fix` - fix code according to JS Standard Style 96 | - `yarn migrate` - apply db changes using migration script 97 | - `yarn add ` - add a new package to package.json 98 | - `yarn remove ` - remove package from package.json 99 | - `npx sequelize model:create --name newmodel` --attributes "id:integer, title:string - create a new model 100 | 101 | ## Using Sequelize 102 | 103 | Sequelize is used to define mappings between models and database tables. It will automatically add the attributes `created_at` and `updated_at` to the tables created. However for consistency for our naming we change this to `createdAt` and `updatedAt`. This will cause issue when using model so we have to add this on config: 104 | 105 | ```js 106 | module.exports = function (sequelize, DataTypes) { 107 | const User = sequelize.define('users', { 108 | ... 109 | }, { 110 | timestamps: false, // Add this 111 | }) 112 | } 113 | ``` 114 | 115 | Basic commands 116 | 117 | ```sh 118 | $ sequelize db:migrate Run pending migrations. 119 | $ sequelize db:migrate:old_schema Update legacy migration table 120 | $ sequelize db:migrate:undo Revert the last migration run. 121 | $ sequelize db:migrate:undo:all Revert all migrations ran. 122 | $ sequelize db:seed Run seeders. 123 | $ sequelize db:seed:undo Deletes data from the database. 124 | $ sequelize db:seed:undo:all Deletes data from the database. 125 | $ sequelize model:create --name modelname --attributes "text:text, url:string" # create model 126 | $ sequelize seed:create # create seeder 127 | ``` 128 | 129 | > If you did not install your sequelize-cli globally you can run this commands by `npx` 130 | 131 | #### Setting up associations — migration and model files 132 | **IMPORTANT**: as of `6/23/17` the model file created with the `sequelize db:model` command still initializes a model with an empty `classMethods` object with an `associate` property in the options passed to `sequelize.define` method. **You cannot define associations this way anymore as of Sequelize v4.0.0-1.** This tripped me up for a hot second because the generated model file did not reflect this change, and the fact that support for the old way had been removed is seemingly buried in the changelogs. Don’t make the same mistake I did and be super confused for too long about why associations aren’t working. 133 | 134 | ```js 135 | //old way that will be included in your generated model file 136 | module.exports = function(sequelize, DataTypes) { 137 | var nameofmodel = sequelize.define('nameofmodel', { 138 | ...model attributes 139 | }, { 140 | classMethods: { 141 | associate: function(models) { 142 | // associations can be defined here 143 | } 144 | } 145 | }) 146 | return nameofmodel 147 | }; 148 | //The right way to set associations in model files 149 | module.exports = function(sequelize, DataTypes) { 150 | var nameofmodel = sequelize.define('nameofmodel', { 151 | ...model attributes 152 | }); 153 | 154 | nameofmodel.associate = function (models) { 155 | // associations can be defined here 156 | }; 157 | return nameofmodel 158 | } 159 | ``` 160 | ### Sequelize CLI Documentation 161 | 162 | For reference, see: [https://github.com/sequelize/cli](https://github.com/sequelize/cli) 163 | 164 | ## Tech 165 | 166 | - [Express](https://expressjs.com/) - Node Framweork 167 | - [Awilix](https://github.com/jeffijoe/awilix) - dependency resolution support powered by `Proxy` 168 | - [PM2](https://github.com/Unitech/pm2) - production process manager for Node.js applications with a built-in load balancer 169 | - [Nodemon](https://nodemon.io/) - Use for development file reload. 170 | - [Tcomb](https://github.com/gcanti/tcomb) - s a library for Node.js and the browser which allows you to check the types of JavaScript values at runtime with a simple and concise syntax 171 | - [Express-status-monitor](https://github.com/RafalWilinski/express-status-monitor) - Simple, self-hosted module based on Socket.io and Chart.js to report realtime server metrics for Express-based node servers. 172 | - [CORS](https://github.com/expressjs/cors) - a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options. 173 | - [Body-parser](https://github.com/expressjs/body-parser) - Node.js body parsing middleware. 174 | - [Compression](https://github.com/expressjs/compression) - Node.js compression middleware. 175 | - [Http-status](https://github.com/adaltas/node-http-status) - Utility to interact with HTTP status code. 176 | - [Winston](https://github.com/winstonjs/winston) - A multi-transport async logging library for node.js. 177 | - [Morgan](https://github.com/expressjs/morgan) - HTTP request logger middleware for node.js 178 | - [Ramda](http://ramdajs.com/) - A practical functional library for JavaScript programmers. 179 | - [Sequelize](http://docs.sequelizejs.com/) - promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and more. 180 | - [Faker](https://github.com/marak/Faker.js/) - generate massive amounts of fake data in the browser and node.js 181 | - [Bcrypt](https://github.com/kelektiv/node.bcrypt.js) - Lib to help you hash passwords 182 | - [Passport](https://github.com/jaredhanson/passport) - is Express-compatible authentication middleware for Node.js. 183 | - [Passport-jwt](https://github.com/themikenicholson/passport-jwt) - A Passport strategy for authenticating with a JSON Web Token. 184 | - [Json Webtoken](https://github.com/auth0/node-jsonwebtoken) - An implementation of JSON Web Tokens. 185 | - [Moment](https://momentjs.com/) - Parse, validate, manipulate, and display dates and times in JavaScript. 186 | - [Moment-timezone](https://momentjs.com/timezone/) - Parse and display dates in any timezone. 187 | - [Swagger-ui](https://swagger.io/swagger-ui/) - visualize and interact with the API’s resources without having any of the implementation logic in place. 188 | - [Swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc)- enables you to integrate Swagger using JSDoc comments in your code. Just add @swagger on top of your DocBlock and declare the meaning of your code in yaml complying to the OpenAPI specification. 189 | 190 | ### Logging 191 | - [winston](https://github.com/winstonjs/winston) - a multi-transport async logging library for Node.js. It is designed to be a simple and universal logging library with support for multiple transports. A transport is essentially a storage device for your logs. Each instance of a winston logger can have multiple transports configured at different levels. For example, one may want error logs to be stored in a persistent remote location (like a database), but all logs output to the console or a local file. 192 | - [morgan](https://github.com/expressjs/morgan) - HTTP request logger middleware for Node.js. A helper that collects logs from your server, such as your request logs. 193 | 194 | ### Tests 195 | - [mocha](https://mochajs.org/) - JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun 196 | - [chai](http://chaijs.com/) - a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework. 197 | - [supertest](https://github.com/visionmedia/supertest) - HTTP assertions made easy via superagent. 198 | - [cross-env](https://github.com/kentcdodds/cross-env) - makes it so you can have a single command without worrying about setting or using the environment variable properly for the platform 199 | - We mainly use this so **mocha** can use the absolute path of files 200 | 201 | ### Pre-commit 202 | 203 | Adding `pre-commit` to your project can be helpful to encourage consistency and quality of your code repository. 204 | 205 | - [pre-commit](https://github.com/observing/pre-commit) - **pre-commit** is a pre-commit hook installer for `git`. It will ensure that your `npm test` (or other specified scripts) passes before you can commit your changes. This all conveniently configured in your `package.json`. 206 | - [lint-staged](https://github.com/okonet/lint-staged) - Linting makes more sense when running before committing your code. By doing that you can ensure no errors are going into repository and enforce code style. But running a lint process on a whole project is slow and linting results can be irrelevant. Ultimately you only want to lint files that will be committed. 207 | 208 | ## JavaScript Standard Style 209 | 210 | ### The Rules 211 | 212 | - **2 spaces** – for indentation 213 | - **Single quotes for strings** – except to avoid escaping 214 | - **No unused variables** – this one catches *tons* of bugs! 215 | - **No semicolons** – [It's][1] [fine.][2] [Really!][3] 216 | - **Never start a line with `(`, `[`, or `` ` ``** 217 | - This is the **only** gotcha with omitting semicolons – *automatically checked for you!* 218 | - [More details][4] 219 | - **Space after keywords** `if (condition) { ... }` 220 | - **Space after function name** `function name (arg) { ... }` 221 | - Always use `===` instead of `==` – but `obj == null` is allowed to check `null || undefined`. 222 | - Always handle the node.js `err` function parameter 223 | - Always prefix browser globals with `window` – except `document` and `navigator` are okay 224 | - Prevents accidental use of poorly-named browser globals like `open`, `length`, 225 | `event`, and `name`. 226 | - **And [more goodness](https://standardjs.com/)** 227 | 228 | ## Contributing 229 | 230 | This boilerplate is open to suggestions and contributions, documentation contributions are also important! :) 231 | 232 | ## Acknowledgments 233 | This boilerplate is forked and modified from [node-api-boilerplate](https://github.com/talyssonoc/node-api-boilerplate) - [Talysson de Oliveira Cassiano](https://github.com/talyssonoc) :clap: 234 | 235 | ## License 236 | MIT License - fork, modify and use however you want. 237 | 238 | 239 | -------------------------------------------------------------------------------- /cluster.js: -------------------------------------------------------------------------------- 1 | const pm2 = require('pm2') 2 | 3 | const instances = process.env.WEB_CONCURRENCY || 1 4 | const maxMemory = process.env.WEB_MEMORY || 512 5 | 6 | pm2.connect(() => { 7 | pm2.start({ 8 | script: 'index.js', 9 | name: 'node-ddd-api', 10 | instances: instances, 11 | max_memory_restart: `${maxMemory}M`, 12 | env: { 13 | NODE_ENV: process.env.NODE_ENV || 'development', 14 | NODE_PATH: '.' 15 | } 16 | }, (err) => { 17 | if (err) { 18 | console.error('Error while launching applications', err.stack || err) 19 | return pm2.disconnect() 20 | } 21 | 22 | console.log('PM2 and application has been succesfully started') 23 | 24 | pm2.launchBus((_, bus) => { 25 | console.log('[PM2] Log streaming started') 26 | 27 | bus.on('log:out', (packet) => { 28 | console.log('[App:%s] %s', packet.process.name, packet.data) 29 | }) 30 | 31 | bus.on('log:err', (packet) => { 32 | console.error('[App:%s][Err] %s', packet.process.name, packet.data) 33 | }) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const dotEnvPath = path.resolve('.env') 3 | 4 | /** 5 | * since mocha don't see enviroment variables we have to use dotenv 6 | */ 7 | require('dotenv').config({ path: dotEnvPath }) 8 | 9 | module.exports = { 10 | development: { 11 | 'url': process.env.DATABASE_URL, 12 | 'dialect': 'postgres' 13 | }, 14 | test: { 15 | 'url': process.env.DATABASE_URL_TEST, 16 | 'dialect': 'postgres', 17 | logging: false // remove logs 18 | }, 19 | staging: { 20 | 'url': process.env.DATABASE_URL_STAGING, 21 | 'dialect': 'postgres', 22 | 'ssl': true, 23 | 'dialectOptions': { 24 | 'ssl': { 25 | 'require': true 26 | } 27 | } 28 | }, 29 | production: { 30 | 'url': process.env.DATABASE_URL_PRODUCTION, 31 | 'dialect': 'postgres', 32 | 'ssl': true, 33 | 'dialectOptions': { 34 | 'ssl': { 35 | 'require': true 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/environments/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | version: process.env.APP_VERSION, 3 | port: process.env.PORT || 4000, 4 | timezone: process.env.TIMEZONE, 5 | logging: { 6 | maxsize: 100 * 1024, // 100mb 7 | maxFiles: 2, 8 | colorize: false 9 | }, 10 | authSecret: process.env.SECRET, 11 | authSession: { 12 | session: false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /config/environments/production.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | version: process.env.APP_VERSION, 4 | port: process.env.PORT || 4000, 5 | timezone: process.env.TIMEZONE, 6 | logging: { 7 | maxsize: 100 * 1024, // 100mb 8 | maxFiles: 2, 9 | colorize: false 10 | }, 11 | authSecret: process.env.SECRET, 12 | authSession: { 13 | session: false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /config/environments/test.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | version: process.env.APP_VERSION, 4 | port: process.env.PORT || 4000, 5 | timezone: process.env.TIMEZONE, 6 | logging: { 7 | maxsize: 100 * 1024, // 100mb 8 | maxFiles: 2, 9 | colorize: false 10 | }, 11 | authSecret: process.env.SECRET, 12 | authSession: { 13 | session: false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load() 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | function loadDbConfig () { 7 | if (fs.existsSync(path.join(__dirname, './database.js'))) { 8 | return require('./database')[ENV] 9 | } 10 | 11 | throw new Error('Database is configuration is required') 12 | } 13 | 14 | const ENV = process.env.NODE_ENV || 'development' 15 | 16 | const envConfig = require(path.join(__dirname, 'environments', ENV)) 17 | const dbConfig = loadDbConfig() 18 | const config = Object.assign({ 19 | env: ENV, 20 | db: dbConfig 21 | }, envConfig) 22 | 23 | module.exports = config 24 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.6" 2 | services: 3 | web: 4 | build: . 5 | volumes: 6 | - .:/usr/src/app 7 | - /usr/src/app/node_modules 8 | ports: 9 | - "4000:4000" 10 | depends_on: 11 | - postgres 12 | - redis 13 | 14 | postgres: 15 | image: postgres:9.6.6-alpine 16 | restart: always 17 | volumes: 18 | - ./postgres-data:/var/lib/postgresql/data 19 | - ./postgres-initdb.sh:/docker-entrypoint-initdb.d/initdb.sh 20 | ports: 21 | - "5432:5432" 22 | environment: 23 | POSTGRES_USER: postgres 24 | POSTGRES_PASSWORD: password123 25 | 26 | redis: 27 | image: redis:4.0.5-alpine 28 | command: ["redis-server", "--appendonly", "yes"] 29 | hostname: redis 30 | volumes: 31 | - ./redis-data:/data 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshuaalpuerto/node-ddd-boilerplate/a80118a7041105db2bd69d3698429dfed07f2e5e/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Node DDD Boilerplate 2 | > RESTful api with Domain Driven Design 3 | 4 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 5 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 6 | ![Travis CI](https://travis-ci.org/joshuaalpuerto/node-ddd-boilerplate.svg?branch=master) 7 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 8 | ![Dependecies](https://david-dm.org/joshuaalpuerto/node-ddd-boilerplate/status.svg) 9 | ![Dev Dependecies](https://david-dm.org/joshuaalpuerto/node-ddd-boilerplate/dev-status.svg) 10 | [![Coverage Status](https://coveralls.io/repos/github/joshuaalpuerto/node-ddd-boilerplate/badge.svg?branch=master)](https://coveralls.io/github/joshuaalpuerto/node-ddd-boilerplate?branch=master) 11 | 12 | ## Development Environment Setup 13 | 14 | 1. Make sure you have `nvm`, node `v10.7` or `LTS` version of node installed 15 | 2. Install `yarn` - `npm install -g yarn`. 16 | 3. Use a smart `.npmrc`. By default, `npm` doesn’t save installed dependencies to package.json (and you should always track your dependencies!). 17 | 18 | ## Docker support 19 | 20 | **Prerequisites** 21 | 1. [Docker](https://www.docker.com/products/docker-engine) Community Edition v17 or higher 22 | 23 | ```sh 24 | $ docker-compose up -d 25 | ``` 26 | Access `http://localhost:/api/` and you're ready to go! 27 | > http://localhost:4000/api/v1 28 | 29 | ### Docker CLI 30 | - `yarn docker:db:reset` - reset and run all migrations and seeders. 31 | - `yarn docker:db:refresh` - reset and run all migrations. 32 | - `yarn docker:db:refresh-test` - reset and run all migrations for test 33 | - `yarn docker:test` - refreshes test database and run unit and black-box testing. 34 | 35 | *...will add more* 36 | 37 | ## Quick Start 38 | 39 | 1. Clone the repository with `git clone --depth=1 https://github.com/joshuaalpuerto/node-ddd-boilerplate.git` 40 | 2. Install the dependencies with [Yarn](https://yarnpkg.com/en/docs/install/) 41 | 3. Install global dependencies [Application Setup](https://github.com/joshuaalpuerto/node-ddd-boilerplate#application-setup-development) 42 | 4. Create the development and test [Databases](https://github.com/joshuaalpuerto/node-ddd-boilerplate#database-setup-development) 43 | 5. Run database migrations and seed with `yarn db:refresh` 44 | 6. Run the application in development mode with `yarn start` 45 | 7. Access `http://localhost:/api/` and you're ready to go! 46 | > http://localhost:4000/api/v1 47 | 48 | ### Application Setup (Development) 49 | 50 | ```sh 51 | $ npm install -g standard # JavaScript Standard Style 52 | $ npm install -g babel-eslint # required by StandardJs 53 | $ npm install -g snazzy # Format JavaScript Standard Style as beautiful output 54 | $ npm install -g sequelize-cli # CLI for Sequelize 55 | ``` 56 | 57 | ### Database Setup (Development) 58 | 59 | 1. Install [PostgreSql](https://www.postgresql.org/) - v9.6. 60 | 2. Create an empty database named - `node_ddd` and `node_ddd_test` for test enviroment. 61 | 3. Rename the .env and populate it with the correct credentials and settings of your Postgresql databases 62 | 4. Enable SSL in the `postgresql.conf` configuration file of your Postgresql installation. 63 | 64 | Follow the the steps in the next section to enable Posgresql SSL connections. 65 | 66 | ```sh 67 | $ psql 68 | psql (9.6.0) 69 | Type "help" for help. 70 | 71 | $ CREATE DATABASE node_ddd; 72 | $ CREATE DATABASE node_ddd_test; 73 | ``` 74 | 75 | ## Overview 76 | 77 | - uses Node.js > v9 78 | - written using ES6 79 | - uses Yarn for package dependency management 80 | - uses [JavaScript Standard Style](http://standardjs.com/) 81 | - uses `sequelize` and `sequelize-cli` as ORM and data migration tool 82 | > can change easily to diffrent ORM and migration tool. 83 | - Filename convention - `camelCase` should never be used. This leaves `snake_case` and `kebab-case`, I prefer `snake_case` for file. 84 | 85 | ## CLI Tools 86 | 87 | - `yarn start` - start the Node-DDD API Boilerplate for production 88 | - `yarn start:dev` - start the Node-DDD API Boilerplate locally/development 89 | - `yarn start:cc` - start `codecrumbs` will give you quick overview the structure of the project 90 | - `yarn test` - run Unit tests 91 | - `yarn db:reset` - run all migrations and seeds. 92 | - `yarn db:refresh` - run all migrations. 93 | - `yarn lint` - lint codebase using JavaScript Standard Style 94 | - `yarn lint:fix` - fix code according to JS Standard Style 95 | - `yarn migrate` - apply db changes using migration script 96 | - `yarn add ` - add a new package to package.json 97 | - `yarn remove ` - remove package from package.json 98 | - `npx sequelize model:create --name newmodel` --attributes "id:integer, title:string - create a new model 99 | 100 | ## Using Sequelize 101 | 102 | Sequelize is used to define mappings between models and database tables. It will automatically add the attributes `created_at` and `updated_at` to the tables created. However for consistency for our naming we change this to `createdAt` and `updatedAt`. This will cause issue when using model so we have to add this on config: 103 | 104 | ```js 105 | module.exports = function (sequelize, DataTypes) { 106 | const User = sequelize.define('users', { 107 | ... 108 | }, { 109 | timestamps: false, // Add this 110 | }) 111 | } 112 | ``` 113 | 114 | Basic commands 115 | 116 | ```sh 117 | $ sequelize db:migrate Run pending migrations. 118 | $ sequelize db:migrate:old_schema Update legacy migration table 119 | $ sequelize db:migrate:undo Revert the last migration run. 120 | $ sequelize db:migrate:undo:all Revert all migrations ran. 121 | $ sequelize db:seed Run seeders. 122 | $ sequelize db:seed:undo Deletes data from the database. 123 | $ sequelize db:seed:undo:all Deletes data from the database. 124 | $ sequelize model:create --name modelname --attributes "text:text, url:string" # create model 125 | $ sequelize seed:create # create seeder 126 | ``` 127 | 128 | > If you did not install your sequelize-cli globally you can run this commands by `npx` 129 | 130 | #### Setting up associations — migration and model files 131 | **IMPORTANT**: as of `6/23/17` the model file created with the `sequelize db:model` command still initializes a model with an empty `classMethods` object with an `associate` property in the options passed to `sequelize.define` method. **You cannot define associations this way anymore as of Sequelize v4.0.0-1.** This tripped me up for a hot second because the generated model file did not reflect this change, and the fact that support for the old way had been removed is seemingly buried in the changelogs. Don’t make the same mistake I did and be super confused for too long about why associations aren’t working. 132 | 133 | ```js 134 | //old way that will be included in your generated model file 135 | module.exports = function(sequelize, DataTypes) { 136 | var nameofmodel = sequelize.define('nameofmodel', { 137 | ...model attributes 138 | }, { 139 | classMethods: { 140 | associate: function(models) { 141 | // associations can be defined here 142 | } 143 | } 144 | }) 145 | return nameofmodel 146 | }; 147 | //The right way to set associations in model files 148 | module.exports = function(sequelize, DataTypes) { 149 | var nameofmodel = sequelize.define('nameofmodel', { 150 | ...model attributes 151 | }); 152 | 153 | nameofmodel.associate = function (models) { 154 | // associations can be defined here 155 | }; 156 | return nameofmodel 157 | } 158 | ``` 159 | ### Sequelize CLI Documentation 160 | 161 | For reference, see: [https://github.com/sequelize/cli](https://github.com/sequelize/cli) 162 | 163 | ## Tech 164 | 165 | - [Express](https://expressjs.com/) - Node Framweork 166 | - [Awilix](https://github.com/jeffijoe/awilix) - dependency resolution support powered by `Proxy` 167 | - [PM2](https://github.com/Unitech/pm2) - production process manager for Node.js applications with a built-in load balancer 168 | - [Nodemon](https://nodemon.io/) - Use for development file reload. 169 | - [Tcomb](https://github.com/gcanti/tcomb) - s a library for Node.js and the browser which allows you to check the types of JavaScript values at runtime with a simple and concise syntax 170 | - [Express-status-monitor](https://github.com/RafalWilinski/express-status-monitor) - Simple, self-hosted module based on Socket.io and Chart.js to report realtime server metrics for Express-based node servers. 171 | - [CORS](https://github.com/expressjs/cors) - a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options. 172 | - [Body-parser](https://github.com/expressjs/body-parser) - Node.js body parsing middleware. 173 | - [Compression](https://github.com/expressjs/compression) - Node.js compression middleware. 174 | - [Http-status](https://github.com/adaltas/node-http-status) - Utility to interact with HTTP status code. 175 | - [Winston](https://github.com/winstonjs/winston) - A multi-transport async logging library for node.js. 176 | - [Morgan](https://github.com/expressjs/morgan) - HTTP request logger middleware for node.js 177 | - [Ramda](http://ramdajs.com/) - A practical functional library for JavaScript programmers. 178 | - [Sequelize](http://docs.sequelizejs.com/) - promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and more. 179 | - [Faker](https://github.com/marak/Faker.js/) - generate massive amounts of fake data in the browser and node.js 180 | - [Bcrypt](https://github.com/kelektiv/node.bcrypt.js) - Lib to help you hash passwords 181 | - [Passport](https://github.com/jaredhanson/passport) - is Express-compatible authentication middleware for Node.js. 182 | - [Passport-jwt](https://github.com/themikenicholson/passport-jwt) - A Passport strategy for authenticating with a JSON Web Token. 183 | - [Json Webtoken](https://github.com/auth0/node-jsonwebtoken) - An implementation of JSON Web Tokens. 184 | - [Moment](https://momentjs.com/) - Parse, validate, manipulate, and display dates and times in JavaScript. 185 | - [Moment-timezone](https://momentjs.com/timezone/) - Parse and display dates in any timezone. 186 | - [Swagger-ui](https://swagger.io/swagger-ui/) - visualize and interact with the API’s resources without having any of the implementation logic in place. 187 | - [Swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc)- enables you to integrate Swagger using JSDoc comments in your code. Just add @swagger on top of your DocBlock and declare the meaning of your code in yaml complying to the OpenAPI specification. 188 | 189 | ### Logging 190 | - [winston](https://github.com/winstonjs/winston) - a multi-transport async logging library for Node.js. It is designed to be a simple and universal logging library with support for multiple transports. A transport is essentially a storage device for your logs. Each instance of a winston logger can have multiple transports configured at different levels. For example, one may want error logs to be stored in a persistent remote location (like a database), but all logs output to the console or a local file. 191 | - [morgan](https://github.com/expressjs/morgan) - HTTP request logger middleware for Node.js. A helper that collects logs from your server, such as your request logs. 192 | 193 | ### Tests 194 | - [mocha](https://mochajs.org/) - JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun 195 | - [chai](http://chaijs.com/) - a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework. 196 | - [supertest](https://github.com/visionmedia/supertest) - HTTP assertions made easy via superagent. 197 | - [cross-env](https://github.com/kentcdodds/cross-env) - makes it so you can have a single command without worrying about setting or using the environment variable properly for the platform 198 | - We mainly use this so **mocha** can use the absolute path of files 199 | 200 | ### Pre-commit 201 | 202 | Adding `pre-commit` to your project can be helpful to encourage consistency and quality of your code repository. 203 | 204 | - [pre-commit](https://github.com/observing/pre-commit) - **pre-commit** is a pre-commit hook installer for `git`. It will ensure that your `npm test` (or other specified scripts) passes before you can commit your changes. This all conveniently configured in your `package.json`. 205 | - [lint-staged](https://github.com/okonet/lint-staged) - Linting makes more sense when running before committing your code. By doing that you can ensure no errors are going into repository and enforce code style. But running a lint process on a whole project is slow and linting results can be irrelevant. Ultimately you only want to lint files that will be committed. 206 | 207 | ## JavaScript Standard Style 208 | 209 | ### The Rules 210 | 211 | - **2 spaces** – for indentation 212 | - **Single quotes for strings** – except to avoid escaping 213 | - **No unused variables** – this one catches *tons* of bugs! 214 | - **No semicolons** – [It's][1] [fine.][2] [Really!][3] 215 | - **Never start a line with `(`, `[`, or `` ` ``** 216 | - This is the **only** gotcha with omitting semicolons – *automatically checked for you!* 217 | - [More details][4] 218 | - **Space after keywords** `if (condition) { ... }` 219 | - **Space after function name** `function name (arg) { ... }` 220 | - Always use `===` instead of `==` – but `obj == null` is allowed to check `null || undefined`. 221 | - Always handle the node.js `err` function parameter 222 | - Always prefix browser globals with `window` – except `document` and `navigator` are okay 223 | - Prevents accidental use of poorly-named browser globals like `open`, `length`, 224 | `event`, and `name`. 225 | - **And [more goodness](https://standardjs.com/)** 226 | 227 | ## Contributing 228 | 229 | This boilerplate is open to suggestions and contributions, documentation contributions are also important! :) 230 | 231 | ## Acknowledgments 232 | This boilerplate is forked and modified from [node-api-boilerplate](https://github.com/talyssonoc/node-api-boilerplate) - [Talysson de Oliveira Cassiano](https://github.com/talyssonoc) :clap: 233 | 234 | ## License 235 | MIT License - fork, modify and use however you want. 236 | 237 | 238 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - General 2 | - [Introduction](/) 3 | - [CLI Commands](general/cli-commands.md) 4 | - [Authentication](general/authentication.md) 5 | - [Coding Standard](general/coding-standard.md) 6 | - Setup 7 | - [Config](setup/config.md) 8 | - [Database Setup](setup/database.md) 9 | - [Logging](setup/logging.md) 10 | - [Upgrading](setup/upgrading.md) 11 | - Organization and Architecture 12 | - [Paths and Require](organization-architecture/paths-require.md) 13 | - [Folder Structure](organization-architecture/folder-structure.md) 14 | - [Patterns recommendations and operations](organization-architecture/recommendation.md) 15 | - [Dependency injection container](organization-architecture/di-container.md) 16 | - Testing 17 | - [Testing](testing/testing.md) 18 | - [Unit Testing](testing/unit-testing.md) 19 | - [Black box](testing/black-box.md) 20 | -------------------------------------------------------------------------------- /docs/general/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | As your REST APIs must be stateless, so does your authentication layer. For this, **JWT (JSON Web Token) is ideal.** 4 | 5 | JWT consists of three parts: 6 | 7 | - **Header**, containing the type of the token and the hashing algorithm 8 | - **Payload** containing the claims 9 | - **Signature** (JWT does not encrypt the payload, just signs it!) 10 | 11 | Adding JWT-based authentication to your application is very straightforward: 12 | 13 | ```js 14 | router.use(auth.authenticate()) 15 | ``` 16 | 17 | After that, the API endpoints are protected with JWT. To access the protected endpoints, you have to provide the token in the `Authorization` header field. 18 | 19 | ```sh 20 | curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com 21 | ``` 22 | 23 | One thing that you could notice is that the JWT module does not depend on any database layer. This is the case because all JWT tokens can be verified on their own, and they can also contain time to live values. 24 | 25 | **Also, you always have to make sure that all your API endpoints are only accessible through a secure connection using HTTPS.** 26 | -------------------------------------------------------------------------------- /docs/general/cli-commands.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | ## Scripts 4 | 5 | - `yarn start` - Start the Application in Production 6 | - `yarn start:dev` - Start Application in `watchmode` 7 | - `yarn start:cc` - Start `codecrumbs` good for overall overview 8 | - `yarn start:docs` - Run documentation 9 | - `yarn test` - Run [Blackbox](be-testing-blackbox-testing.html) and [Unit](be-testing-unit-testing.html) tests. 10 | - `yarn db:refresh` - run all migrations. 11 | - `yarn db:refresh-test` - run all migrations for testing. 12 | - `yarn seed` - seed development database. 13 | - `yarn seed:test` - seed test database. 14 | - `standard` - lint codebase using JavaScript Standard Style 15 | - `standard --fix` - fix code according to JS Standard Style 16 | 17 | > Check [package.json](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/master/package.json#L10-L37) for more available scripts. 18 | 19 | ## Docker CLI 20 | 21 | - `yarn docker:db:reset` - reset and run all migrations and seeders. 22 | - `yarn docker:db:refresh` - reset and run all migrations. 23 | - `yarn docker:db:refresh-test` - reset and run all migrations for test 24 | - `yarn docker:test` - refreshes test database and run unit and black-box testing. 25 | -------------------------------------------------------------------------------- /docs/general/coding-standard.md: -------------------------------------------------------------------------------- 1 | # Coding Standard 2 | 3 | ## JavaScript Standard Style 4 | 5 | ### The Rules 6 | 7 | - **2 spaces** – for indentation 8 | - **Single quotes for strings** – except to avoid escaping 9 | - **No unused variables** – this one catches *tons* of bugs! 10 | - **No semicolons** – [It's][1] [fine.][2] [Really!][3] 11 | - **Never start a line with `(`, `[`, or `` ` ``** 12 | - This is the **only** gotcha with omitting semicolons – *automatically checked for you!* 13 | - [More details][4] 14 | - **Space after keywords** `if (condition) { ... }` 15 | - **Space after function name** `function name (arg) { ... }` 16 | - Always use `===` instead of `==` – but `obj == null` is allowed to check `null || undefined`. 17 | - Always handle the node.js `err` function parameter 18 | - Always prefix browser globals with `window` – except `document` and `navigator` are okay 19 | - Prevents accidental use of poorly-named browser globals like `open`, `length`, 20 | `event`, and `name`. 21 | - **And [more goodness][5]** – *give `standard` a try today!* 22 | 23 | [1]: http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding 24 | [2]: http://inimino.org/~inimino/blog/javascript_semicolons 25 | [3]: https://www.youtube.com/watch?v=gsfbh17Ax9I 26 | [4]: https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style#semicolons 27 | [5]: https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style#javascript-standard-style 28 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | node-ddd-boilerplate - RESTful api Domain Driven Design 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/organization-architecture/di-container.md: -------------------------------------------------------------------------------- 1 | # Dependency injection container 2 | 3 | Dependency injection is a way to implement the [Inversion of control](https://www.martinfowler.com/articles/injection.html) pattern. The core idea of this pattern is that every dependency of a object that _can_ (and makes sense to) be decoupled from that should be injected to make it more flexible and reusable, making the call-site have control over the dependencies of the called method, thus _inversion of control_. 4 | 5 | In this boilerplate we make extensive use of dependency injection in the `app`, `interfaces` and `infra` layers, for example: 6 | 7 | - Injecting the ORM classes in the repositories at the `infra` layer; 8 | - Injecting the repositories in the operations at the `app` layer; 9 | - Injecting the operations in the controllers at the `interfaces` layer. 10 | 11 | We implement dependency injection using the [Awilix](https://www.npmjs.com/package/awilix) library, that is very flexible and easy to use. The author of the library has three wonderful posts about dependency injection on Node and how to use Awilix, you can read them here: [part 1](https://medium.com/@Jeffijoe/dependency-injection-in-node-js-2016-edition-f2a88efdd427), [part 2](https://medium.com/@Jeffijoe/dependency-injection-in-node-js-2016-edition-part-2-aedc5fd6eed0) and [part 3](https://medium.com/@Jeffijoe/dependency-injection-in-node-js-2016-edition-part-3-c01471c09c6d). For the injections on the controllers we use the [Express Awilix adapter](https://www.npmjs.com/package/awilix-express). 12 | 13 | The base of our dependency injection is a design pattern called _composition root_, and in the boilerplate it sits on the root of our `src` folder, in the [`src/container.js`](https://github.com/joshuaalpuerto/node-ddd-boilerplate/tree/master/src/container.js) file. That's where we'll define what each of our injected dependencies will return, you should edit this file according to the growth of your application, like adding new operations and repositories to be injected. 14 | -------------------------------------------------------------------------------- /docs/organization-architecture/folder-structure.md: -------------------------------------------------------------------------------- 1 | # Folder Structure 2 | 3 | This boilerplate uses a folder structure and logical architecture focused on separation of concerns based in [Domain-driven design](http://dddcommunity.org/) and [Clean architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html). Instead of the classical `controllers`/`models`/`services` folders, we now have [layers](http://wiki.c2.com/?FourLayerArchitecture) inside the `src` folder. Each of the folder layers is scoped by a namespace regarding the concern it is about (like `user`, `errors`, `logging` and so on): 4 | 5 | ## Application layer (`app` folder) 6 | 7 | The application layer is responsible to mediate between your input interfaces and your business domain. In this layer we'll have the use cases of your application and your application services (like a `MailService` that communicates with a `MailchimpService` from the infrastructure layer). 8 | 9 | ## Domain layer (`domain` folder) 10 | 11 | Here you'll define your business domain classes, functions and services that compose your [domain model](https://martinfowler.com/eaaCatalog/domainModel.html). All your business rules should be declared in this layer so the application layer can use it to compose your use cases. 12 | 13 | ## Infrastructure layer (`infra` folder) 14 | 15 | This is the lowest of the layers. In the infra layer you'll have the communication with what is outside your application, like the database (check the repository pattern section on [[Patterns recommendations and operations]]), mail services and direct communication with frameworks. 16 | 17 | ## Input interfaces layer (`interfaces` folder) 18 | 19 | This folder contains all the entry points for your application. From the beginning here's where your Express controllers will be (inside the `interfaces/http` folder). 20 | -------------------------------------------------------------------------------- /docs/organization-architecture/paths-require.md: -------------------------------------------------------------------------------- 1 | # Paths and Require 2 | 3 | You'll notice that in the files from the codebase most of the time we don't use `../` on the `require`s but instead we require files from the application as the root of the project was a Node package. 4 | 5 | It's important to warn you that __it's not the default behavior of Node's require__, but we change that defining a environment variable called `NODE_PATH` with the path of the root of the project. Doing that we're defining that the root of the project may be used as any other package, making our life easier when it comes to requiring files, but __it will not prevent you from using relative paths normally when requiring files__! 6 | 7 | This variable should be set on every entry point of execution of your project for it to work properly, that's why we use it in our npm test scripts on [`package.json`](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/22767a4cdda0f5232391709515a4af41d15796ee/package.json#L15-L16) file, we don't do that for the production mode (the `start` script) because it's done on the [`cluster.js` file](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/22767a4cdda0f5232391709515a4af41d15796ee/cluster.js#L13) directly. 8 | 9 | Setting this variable to make your life easier will never bite you, but be aware that you have to remember to set it in case you add new npm scripts that will interact with files that use non-relative paths on `require`. 10 | -------------------------------------------------------------------------------- /docs/organization-architecture/recommendation.md: -------------------------------------------------------------------------------- 1 | # Patterns recommendations and operations 2 | 3 | As stated in [Folder structure](be-architecture-folder-structure.html), the boilerplate follows an architecture inspired by DDD and Clean Architecture. It implies in some patterns and methodologies having to be followed to ensure separation of concerns and make the codebase more maintainable, I'll list of them here and suggest some links in the end with further info of patterns and practices that work well with this architecture. 4 | 5 | ## Separation of concerns 6 | 7 | Separation of concerns is ensured in this codebase in two ways: 8 | 9 | 1) Separating the source code in [layer](be-architecture-folder-structure.html), each layer with a different responsibility that should be followed; 10 | 2) Each layer is also separated by actual _concerns_. When we talk about concerns in software development it's not about functionality, like `controllers` or `models`, but about _concepts_ or _contexts_, like `users`, `logging`, `persistence` and so on. 11 | 12 | All the patterns below have direct relation with separation of concerns. 13 | 14 | ## Repository pattern 15 | 16 | Inside the use cases you should also not touch the database directly, it's not the responsibility of the application layer to know how to work with data persistence. So we implement [repositories](https://martinfowler.com/eaaCatalog/repository.html) that handle the persistence internally and inject them on the operations instances. 17 | 18 | __Attention for this point__: the repositories __interfaces__ (as in OOP interfaces, __not__ the `interfaces` layer) belongs to the `domain` layer, and their __implementations__ (that effectively talk to the database) belongs to the `infra` layer, but since we don't have interfaces in JavaScript we just implement them on the `infra` layer and inject it with the name of the imaginary interface. An example of that is the `UsersRepository`, we use it in the operations like [`usersRepository`](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/22767a4cdda0f5232391709515a4af41d15796ee/src/app/user/post.js#L8), but what we are really [__injecting__](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/22767a4cdda0f5232391709515a4af41d15796ee/src/container.js#L17) is the [`SequelizeUsersRepository`](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/master/src/infra/repositories/user.js) that communicates internally with the SQL database. The important point here is that: __The operation doesn't know how the repository works internally, it just knows the `UsersRepository` methods and the parameters it expects__. 19 | 20 | The repository implementations should also return something that the `domain` and the `app` layers can have access, so that's why we use mappers for that, that receives stuff from the database and convert it to objects from the `domain` layer. An example of that is the [`Transformer`](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/master/src/infra/repositories/transforms/user.js), that knows how to convert an record from the `users` table of the database to an instance of the [`User`](https://github.com/joshuaalpuerto/node-ddd-boilerplate/blob/master/src/domain/user/user.js) domain class and _vice versa_. 21 | 22 | Separating the persistence from the `app` layer like this make it easier to test the `app` layer with different simulated responses from the database, including errors. 23 | 24 | ## Further info 25 | 26 | You can know more about the subjects that we talked about here in the following links: 27 | 28 | - [Architecture the Lost Years](https://www.youtube.com/watch?v=WpkDN78P884) 29 | - [Domain-driven design](https://domainlanguage.com/ddd/) 30 | - [FourLayerArchitecture](http://wiki.c2.com/?FourLayerArchitecture) 31 | - [Clean architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) 32 | -------------------------------------------------------------------------------- /docs/setup/config.md: -------------------------------------------------------------------------------- 1 | # Configurations 2 | 3 | The configuration for each environment is on the `config/environments/.js` file, here's an example of environment file: 4 | 5 | ```javascript 6 | module.exports = { 7 | web: { 8 | port: 3000 9 | }, 10 | logging: { 11 | appenders: [ 12 | { type: 'console' } 13 | ] 14 | } 15 | }; 16 | ``` 17 | 18 | This file setups the port 3000 for the web server and uses a console appender for the logger. Feel free to add more keys and values to be used across the app, but try to always inject the configuration instead of requiring the `config/index.js` file directly. 19 | -------------------------------------------------------------------------------- /docs/setup/database.md: -------------------------------------------------------------------------------- 1 | # Database Setup 2 | 3 | By default we use [Sequelize](http://docs.sequelizejs.com/) setup with PostgreSQL, you can change it by [installing the adapter for your DBMS](http://docs.sequelizejs.com/en/latest/docs/getting-started/#installation) and then changing the setting for your environment on `config/database.js`. 4 | 5 | You can use different dialects for each environment, it's a common pattern to use SQLite on test environment and PostgreSQL for development and production. 6 | -------------------------------------------------------------------------------- /docs/setup/logging.md: -------------------------------------------------------------------------------- 1 | # Upgrading 2 | 3 | Once you update your **Node** version, you also need to re-install you dependencies(node_modules). This boilerplate is using `bcrypt` for encryptions, and it uses `node-gyp` which needs to be compiled. That said, you might encounter this error: 4 | 5 | ```sh 6 | ~/workspace$ node app.js 7 | module.js:682 8 | return process.dlopen(module, path._makeLong(filename)); 9 | ^ 10 | 11 | Error: The module '/home/workspace/node_modules/bcrypt/lib/binding/bcrypt_lib.node' 12 | was compiled against a different Node.js version using 13 | NODE_MODULE_VERSION 67. This version of Node.js requires 14 | NODE_MODULE_VERSION 57. Please try re-compiling or re-installing 15 | the module (for instance, using npm rebuild or npm install). 16 | at Object.Module._extensions..node (module.js:682:18) 17 | at Module.load (module.js:566:32) 18 | at tryModuleLoad (module.js:506:12) 19 | at Function.Module._load (module.js:498:3) 20 | at Module.require (module.js:597:17) 21 | at require (internal/module.js:11:18) 22 | at Object. (/home/treehouse/workspace/node_modules/bcrypt/bcrypt.js:6:16) 23 | at Module._compile (module.js:653:30) 24 | at Object.Module._extensions..js (module.js:664:10) 25 | at Module.load (module.js:566:32) 26 | ``` 27 | 28 | ### How to fix 29 | 1. Ensure that you don't have any `pm2` process. 30 | - `pm2 list` - to show the process 31 | - `pm2 kill` - kill the process 32 | 2. Rebuild your **bcrypt** 33 | ```sh 34 | npm rebuild bcrypt --update-binary 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/testing/black-box.md: -------------------------------------------------------------------------------- 1 | # Blackbox 2 | 3 | One of the best ways to test your REST APIs is to treat them as black boxes. 4 | 5 | **Black-box testing is a method of testing where the functionality of an application is examined without the knowledge of its internal structures or workings.** So none of the dependencies are mocked or stubbed, but the system is tested as a whole. 6 | 7 | One of the modules that can help you with black-box testing Node.js REST APIs is [supertest](https://www.npmjs.com/package/supertest). 8 | 9 | A simple test case which checks if a user is returned using the test runner mocha can be implemented like this: 10 | 11 | ```js 12 | const request = require('supertest') 13 | 14 | describe('GET /user/:id', function() { 15 | it('returns a user', function() { 16 | // newer mocha versions accepts promises as well 17 | return request(app) 18 | .get('/user') 19 | .set('Accept', 'application/json') 20 | .expect(200, { 21 | id: '1', 22 | name: 'John Math' 23 | }, done) 24 | }) 25 | }) 26 | ``` 27 | 28 | **You may ask: how does the data gets populated into the database which serves the REST API?** 29 | 30 | In general, it is a good approach to write your tests in a way that they make as few assumptions about the state of the system as possible. Still, in some scenarios you can find yourself in a spot when you need to know what is the state of the system exactly, so you can make assertions and achieve higher test coverage. 31 | 32 | So based on your needs, you can populate the database with test data in one of the following ways: 33 | 34 | - run your black-box test scenarios on a known subset of production data, 35 | - populate the database with crafted data before the test cases are run. (see [commands](be-general-commands.html)) 36 | 37 | Of course, black-box testing does not mean that you don't have to do unit testing, you still have to write unit tests for your APIs. 38 | 39 | -------------------------------------------------------------------------------- /docs/testing/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | The default test suite of the boilerplate uses [Mocha](https://www.npmjs.com/package/mocha) as the testing framework and [Chai](https://www.npmjs.com/package/chai) for the assertions and [Sinon](Standalone test spies, stubs and mocks). They are located in the `test` folder in the root of the project and follow the same folder architecture as the `src` folder, except for the [functional tests](testing/black-box.md). 4 | -------------------------------------------------------------------------------- /docs/testing/unit-testing.md: -------------------------------------------------------------------------------- 1 | # Unit Testing 2 | 3 | We write unit tests to see if a given module (unit) works. All the dependencies are stubbed, meaning we are providing fake dependencies for a module. 4 | 5 | **You should write the test for the exposed methods, not for the internal workings of the given module.** 6 | 7 | ## The Anatomy of a Unit Test 8 | Each unit test has the following structure: 9 | 10 | 1. Test setup 11 | 2. Calling the tested method 12 | 3. Asserting 13 | 14 | **Each unit test should test one concern only.** 15 | *(Of course this doesn't mean that you can add one assertion only).* 16 | 17 | ## Modules Used for Node.js Unit Testing 18 | 19 | For unit testing, we are going to use the following modules: 20 | 21 | - test runner: [mocha](https://www.npmjs.com/package/mocha) 22 | - assertion library: [chai](http://www.chaijs.com/), alternatively the assert module (for asserting) 23 | - test spies, stubs and mocks: [sinon](http://sinonjs.org/) (for test setup). 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const app = container.resolve('app') 3 | 4 | app 5 | .start() 6 | .catch((error) => { 7 | app.logger.error(error.stack) 8 | process.exit() 9 | }) 10 | 11 | function exitHandler (exitCode = 0) { 12 | app.close() 13 | app.logger.info('Exiting with code:', exitCode) 14 | process.exit(exitCode) 15 | } 16 | 17 | const unexpectedErrorHandler = (error) => { 18 | app.logger.error(error) 19 | exitHandler(1) 20 | } 21 | 22 | process.on('uncaughtException', unexpectedErrorHandler) 23 | process.on('unhandledRejection', unexpectedErrorHandler) 24 | process.on('SIGTERM', (_, code) => exitHandler(code)) 25 | process.on('SIGINT', (_, code) => exitHandler(code)) 26 | -------------------------------------------------------------------------------- /logs/README.md: -------------------------------------------------------------------------------- 1 | # Logs 2 | > We simply put here the logs of certain enviroment -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-ddd-boilerplate", 3 | "version": "0.0.0-development", 4 | "description": "RESTful api Domain Driven Design", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">= 12", 8 | "npm": ">=6" 9 | }, 10 | "scripts": { 11 | "gc": "git-cz", 12 | "start": "NODE_PATH=. node cluster.js", 13 | "start:dev": "NODE_PATH=. nodemon cluster.js", 14 | "start:cc": "codecrumbs -d src -e src/container.js", 15 | "start:docs": "docsify serve docs", 16 | "test": "standard --verbose | snazzy && npm run db:refresh-test && npm run test:cov", 17 | "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", 18 | "test:cov": "cross-env NODE_PATH=. NODE_ENV=test istanbul cover node_modules/mocha/bin/_mocha -- --exit", 19 | "test:specs": "cross-env NODE_PATH=. NODE_ENV=test mocha --exit", 20 | "db:reset": "cross-env NODE_PATH=. && echo $NODE_PATH && yarn db:refresh && yarn seed", 21 | "db:refresh": "sequelize db:migrate:undo:all && sequelize db:migrate", 22 | "db:refresh-test": "sequelize db:migrate:undo:all --env test && sequelize db:migrate --env test", 23 | "migrate": "sequelize db:migrate", 24 | "migrate:test": "sequelize db:migrate --env test", 25 | "seed": "cross-env NODE_ENV=development NODE_PATH=. sequelize db:seed:all --seeders-path=./src/infra/sequelize/seeders/development", 26 | "seed:test": "cross-env NODE_ENV=development sequelize db:seed:all --env test --seeders-path=./src/infra/sequelize/seeders/development", 27 | "lint": "standard --verbose | snazzy", 28 | "lint:fix": "standard --fix", 29 | "lint:staged": "lint-staged", 30 | "fresh-install": "rm -rf node_modules && npm cache clean --force && npm install", 31 | "travis-deploy-once": "travis-deploy-once", 32 | "semantic-release": "semantic-release", 33 | "docker:script": "docker-compose run --rm web", 34 | "docker:db:reset": "npm run docker:script yarn db:reset", 35 | "docker:db:refresh": "npm run docker:script yarn db:refresh", 36 | "docker:db:refresh-test": "npm run docker:script yarn db:refresh", 37 | "docker:test": "npm run docker:script yarn test", 38 | "docker:test:specs": "npm run docker:script npm run test:specs" 39 | }, 40 | "lint-staged": { 41 | "*.js": "lint" 42 | }, 43 | "pre-commit": "lint:staged", 44 | "author": "Joshua C Alpuerto ", 45 | "license": "ISC", 46 | "dependencies": { 47 | "@godaddy/terminus": "4.3.1", 48 | "awilix": "12.0.5", 49 | "bcrypt": "4.0.1", 50 | "body-parser": "1.18.3", 51 | "compression": "1.7.3", 52 | "cors": "2.8.5", 53 | "cross-env": "5.2.0", 54 | "dotenv": "6.2.0", 55 | "express": "4.16.4", 56 | "express-status-monitor": "1.2.3", 57 | "faker": "4.1.0", 58 | "http-status": "1.3.1", 59 | "jsonwebtoken": "8.5.0", 60 | "moment": "2.24.0", 61 | "moment-timezone": "0.5.23", 62 | "morgan": "1.9.1", 63 | "nodemon": "1.18.10", 64 | "passport": "0.4.0", 65 | "passport-jwt": "4.0.0", 66 | "pg": "8.15.6", 67 | "pm2": "6.0.5", 68 | "ramda": "0.26.1", 69 | "sequelize": "5.21.1", 70 | "swagger-jsdoc": "3.2.9", 71 | "tcomb": "3.2.29", 72 | "winston": "3.2.1" 73 | }, 74 | "devDependencies": { 75 | "babel-eslint": "^10.1.0", 76 | "chai": "4.2.0", 77 | "codecrumbs": "1.1.5", 78 | "commitizen": "3.0.7", 79 | "coveralls": "3.0.3", 80 | "cz-conventional-changelog": "2.1.0", 81 | "docsify-cli": "4.3.0", 82 | "istanbul": "1.1.0-alpha.1", 83 | "lint-staged": "8.1.5", 84 | "mocha": "6.1.4", 85 | "mocha-lcov-reporter": "1.3.0", 86 | "npm-check": "5.9.0", 87 | "pre-commit": "1.2.2", 88 | "semantic-release": "15.13.15", 89 | "sequelize-cli": "^5.5.1", 90 | "sinon": "7.2.7", 91 | "sinon-chai": "3.3.0", 92 | "snazzy": "^8.0.0", 93 | "standard": "^12.0.1", 94 | "supertest": "3.4.2", 95 | "travis-deploy-once": "5.0.11" 96 | }, 97 | "standard": { 98 | "ignore": [ 99 | "/public/docs" 100 | ], 101 | "globals": [ 102 | "request", 103 | "app", 104 | "expect", 105 | "describe", 106 | "it", 107 | "config", 108 | "beforeEach", 109 | "afterEach" 110 | ] 111 | }, 112 | "repository": { 113 | "type": "git", 114 | "url": "https://github.com/joshuaalpuerto/node-ddd-boilerplate.git" 115 | }, 116 | "czConfig": { 117 | "path": "node_modules/cz-conventional-changelog" 118 | }, 119 | "release": { 120 | "publish": [ 121 | "@semantic-release/github" 122 | ] 123 | }, 124 | "nodemonConfig": { 125 | "ignore": [ 126 | "test/*", 127 | "logs/*", 128 | "redis-data/*", 129 | "postgres-data/*", 130 | "coverage/*" 131 | ], 132 | "delay": "1000" 133 | }, 134 | "resolutions": { 135 | "axios": "0.18.1", 136 | "fstream": "1.0.12" 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /postgres-initdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | psql --variable=ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 4 | CREATE DATABASE "node_ddd"; 5 | EOSQL 6 | 7 | psql --variable=ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 8 | CREATE DATABASE "node_ddd_test"; 9 | EOSQL 10 | 11 | psql --variable=ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname=node_ddd <<-EOSQL 12 | CREATE EXTENSION "uuid-ossp"; 13 | CREATE EXTENSION "hstore"; 14 | EOSQL 15 | 16 | psql --variable=ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname=node_ddd_test <<-EOSQL 17 | CREATE EXTENSION "uuid-ossp"; 18 | CREATE EXTENSION "hstore"; 19 | EOSQL 20 | -------------------------------------------------------------------------------- /public/README.md: -------------------------------------------------------------------------------- 1 | # PUBLIC 2 | > We put all our static files needed. -------------------------------------------------------------------------------- /public/docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshuaalpuerto/node-ddd-boilerplate/a80118a7041105db2bd69d3698429dfed07f2e5e/public/docs/favicon-16x16.png -------------------------------------------------------------------------------- /public/docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshuaalpuerto/node-ddd-boilerplate/a80118a7041105db2bd69d3698429dfed07f2e5e/public/docs/favicon-32x32.png -------------------------------------------------------------------------------- /public/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /public/docs/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 58 | -------------------------------------------------------------------------------- /public/docs/swagger-ui-bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAwjMA;;;;;;AA65DA;;;;;;;;;;;;;;;;;;;;;;;;;;AAs9TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAm/pBA;;;;;AAu5QA;;;;;AAynBA;AAi0CA;;;;;;AAq/YA;;;;;;AAojaA;AA8lvBA","sourceRoot":""} -------------------------------------------------------------------------------- /public/docs/swagger-ui-standalone-preset.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA80CA;;;;;;AAqpFA","sourceRoot":""} -------------------------------------------------------------------------------- /public/docs/swagger-ui.css: -------------------------------------------------------------------------------- 1 | .swagger-ui{font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .wrapper{width:100%;max-width:1460px;margin:0 auto;padding:0 20px}.swagger-ui .opblock-tag-section{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .opblock-tag{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{font-size:24px;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock-tag.no-desc span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .opblock-tag svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui .opblock-tag small{font-size:14px;font-weight:400;-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameter__type{font-size:12px;padding:5px 0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .view-line-link{position:relative;top:3px;width:20px;margin:0 5px;cursor:pointer;-webkit-transition:all .5s;transition:all .5s}.swagger-ui .opblock{margin:0 0 15px;border:1px solid #000;border-radius:4px;-webkit-box-shadow:0 0 3px rgba(0,0,0,.19);box-shadow:0 0 3px rgba(0,0,0,.19)}.swagger-ui .opblock .tab-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .opblock .tab-header .tab-item{padding:0 40px;cursor:pointer}.swagger-ui .opblock .tab-header .tab-item:first-of-type{padding:0 40px 0 0}.swagger-ui .opblock .tab-header .tab-item.active h4 span{position:relative}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{position:absolute;bottom:-15px;left:50%;width:120%;height:4px;content:"";-webkit-transform:translateX(-50%);transform:translateX(-50%);background:#888}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{padding:8px 20px;min-height:50px;background:hsla(0,0%,100%,.8);-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1);box-shadow:0 1px 2px rgba(0,0,0,.1)}.swagger-ui .opblock .opblock-section-header,.swagger-ui .opblock .opblock-section-header label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock .opblock-section-header label{font-size:12px;font-weight:700;margin:0;margin-left:auto;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-section-header label span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{font-size:14px;-webkit-box-flex:1;-ms-flex:1;flex:1;margin:0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary-method{font-size:14px;font-weight:700;min-width:80px;padding:6px 15px;text-align:center;border-radius:3px;background:#000;text-shadow:0 1px 0 rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 10px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .opblock .opblock-summary-operation-id .view-line-link,.swagger-ui .opblock .opblock-summary-path .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated .view-line-link{position:relative;top:2px;width:0;margin:0;cursor:pointer;-webkit-transition:all .5s;transition:all .5s}.swagger-ui .opblock .opblock-summary-operation-id:hover .view-line-link,.swagger-ui .opblock .opblock-summary-path:hover .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated:hover .view-line-link{width:18px;margin:0 5px}.swagger-ui .opblock .opblock-summary-path__deprecated{text-decoration:line-through}.swagger-ui .opblock .opblock-summary-operation-id{font-size:14px}.swagger-ui .opblock .opblock-summary-description{font-size:13px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:5px;cursor:pointer}.swagger-ui .opblock.opblock-post{border-color:#49cc90;background:rgba(73,204,144,.1)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:#49cc90}.swagger-ui .opblock.opblock-put{border-color:#fca130;background:rgba(252,161,48,.1)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:#fca130}.swagger-ui .opblock.opblock-delete{border-color:#f93e3e;background:rgba(249,62,62,.1)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:#f93e3e}.swagger-ui .opblock.opblock-get{border-color:#61affe;background:rgba(97,175,254,.1)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:#61affe}.swagger-ui .opblock.opblock-patch{border-color:#50e3c2;background:rgba(80,227,194,.1)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:#50e3c2}.swagger-ui .opblock.opblock-head{border-color:#9012fe;background:rgba(144,18,254,.1)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:#9012fe}.swagger-ui .opblock.opblock-options{border-color:#0d5aa7;background:rgba(13,90,167,.1)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{opacity:.6;border-color:#ebebeb;background:hsla(0,0%,92%,.1)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .filter .operation-filter-input{width:100%;margin:20px 0;padding:10px;border:2px solid #d8dde7}.swagger-ui .tab{display:-webkit-box;display:-ms-flexbox;display:flex;margin:20px 0 10px;padding:0;list-style:none}.swagger-ui .tab li{font-size:12px;min-width:100px;min-width:90px;padding:0;cursor:pointer;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .tab li:first-of-type{position:relative;padding-left:0}.swagger-ui .tab li:first-of-type:after{position:absolute;top:0;right:6px;width:1px;height:100%;content:"";background:rgba(0,0,0,.2)}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-title_normal{font-size:12px;margin:0 0 5px;padding:15px 20px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-title_normal h4{font-size:12px;margin:0 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-external-docs-wrapper h4{padding-left:0}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{width:100%;padding:8px 40px}.swagger-ui .body-param-options{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{font-size:12px;margin:10px 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status .response-undocumented{font-size:11px;font-family:Source Code Pro,monospace;font-weight:600;color:#999}.swagger-ui .response-col_links{padding-left:2em;max-width:40em;font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_links .response-undocumented{font-size:11px;font-family:Source Code Pro,monospace;font-weight:600;color:#999}.swagger-ui .response-col_description__inner span{font-size:12px;font-style:italic;display:block;margin:10px 0;padding:10px;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .response-col_description__inner span p{margin:0}.swagger-ui .response-col_description__inner span a{font-family:Source Code Pro,monospace;font-weight:600;color:#89bf04;text-decoration:underline}.swagger-ui .response-col_description__inner span a:hover{color:#81b10c}.swagger-ui .opblock-body pre{font-size:12px;margin:0;padding:10px;white-space:pre-wrap;word-wrap:break-word;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;border-radius:4px;background:#41444e;overflow-wrap:break-word;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .opblock-body pre span{color:#fff!important}.swagger-ui .opblock-body pre .headerline{display:block}.swagger-ui .scheme-container{margin:0 0 20px;padding:30px 0;background:#fff;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.15);box-shadow:0 1px 2px 0 rgba(0,0,0,.15)}.swagger-ui .scheme-container .schemes{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .scheme-container .schemes>label{font-size:12px;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scheme-container .schemes>label select{min-width:130px;text-transform:uppercase}.swagger-ui .server-container{margin:0 0 20px;padding:30px 0;background:#fff;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.15);box-shadow:0 1px 2px 0 rgba(0,0,0,.15)}.swagger-ui .server-container .computed-url{margin:2em 0}.swagger-ui .server-container .computed-url code{color:grey;display:inline-block;padding:4px;font-size:16px;margin:0 1em;font-style:italic}.swagger-ui .server-container .servers{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .server-container .servers .servers-title{margin-right:1em}.swagger-ui .server-container .servers>label{font-size:12px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .server-container .servers>label select{min-width:130px}.swagger-ui .server-container .servers table tr{width:30em}.swagger-ui .server-container .servers table td{display:inline-block;max-width:15em;vertical-align:middle;padding-top:10px;padding-bottom:10px}.swagger-ui .server-container .servers table td:first-of-type{padding-right:2em}.swagger-ui .server-container .servers table td input{width:100%;height:100%}.swagger-ui .loading-container{padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{font-size:10px;font-weight:700;position:absolute;top:50%;left:50%;content:"loading";-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-transform:uppercase;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .loading-container .loading:before{position:absolute;top:50%;left:50%;display:block;width:60px;height:60px;margin:-30px;content:"";-webkit-animation:rotation 1s infinite linear,opacity .5s;animation:rotation 1s infinite linear,opacity .5s;opacity:1;border:2px solid rgba(85,85,85,.1);border-top-color:rgba(0,0,0,.6);border-radius:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@-webkit-keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.swagger-ui .renderedMarkdown p{font-family:Open Sans,sans-serif;color:#3b4151;margin-top:0;margin-bottom:0}.swagger-ui .response-content-type{padding-top:1em}.swagger-ui .response-content-type.controls-accept-header select{border-color:green}.swagger-ui .response-content-type.controls-accept-header small{color:green;font-size:.7em}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}.swagger-ui section h3{font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui a.nostyle{display:inline}.swagger-ui a.nostyle,.swagger-ui a.nostyle:visited{text-decoration:inherit;color:inherit;cursor:pointer}.swagger-ui .btn{font-size:14px;font-weight:700;padding:5px 23px;-webkit-transition:all .3s;transition:all .3s;border:2px solid #888;border-radius:4px;background:transparent;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1);box-shadow:0 1px 2px rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .btn.btn-sm{font-size:12px;padding:4px 23px}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{border-color:#ff6060;font-family:Titillium Web,sans-serif;color:#ff6060}.swagger-ui .btn.authorize{line-height:1;display:inline;color:#49cc90;border-color:#49cc90}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{-webkit-animation:swagger-ui-pulse 2s infinite;animation:swagger-ui-pulse 2s infinite;color:#fff;border-color:#4990e2}@-webkit-keyframes swagger-ui-pulse{0%{color:#fff;background:#4990e2;-webkit-box-shadow:0 0 0 0 rgba(73,144,226,.8);box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{-webkit-box-shadow:0 0 0 5px rgba(73,144,226,0);box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;-webkit-box-shadow:0 0 0 0 rgba(73,144,226,0);box-shadow:0 0 0 0 rgba(73,144,226,0)}}@keyframes swagger-ui-pulse{0%{color:#fff;background:#4990e2;-webkit-box-shadow:0 0 0 0 rgba(73,144,226,.8);box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{-webkit-box-shadow:0 0 0 5px rgba(73,144,226,0);box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;-webkit-box-shadow:0 0 0 0 rgba(73,144,226,0);box-shadow:0 0 0 0 rgba(73,144,226,0)}}.swagger-ui .btn-group{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px}.swagger-ui .btn-group .btn{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{padding:0 10px;border:none;background:none}.swagger-ui .authorization__btn.locked{opacity:1}.swagger-ui .authorization__btn.unlocked{opacity:.4}.swagger-ui .expand-methods,.swagger-ui .expand-operation{border:none;background:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{width:20px;height:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#444}.swagger-ui .expand-methods svg{-webkit-transition:all .3s;transition:all .3s;fill:#777}.swagger-ui button{cursor:pointer;outline:none}.swagger-ui button.invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}.swagger-ui select{font-size:14px;font-weight:700;padding:5px 40px 5px 10px;border:2px solid #41444e;border-radius:4px;background:#f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;background-size:20px;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.25);box-shadow:0 1px 2px 0 rgba(0,0,0,.25);font-family:Titillium Web,sans-serif;color:#3b4151;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui select[multiple]{margin:5px 0;padding:5px;background:#f7f7f7}.swagger-ui select.invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}.swagger-ui .opblock-body select{min-width:230px}.swagger-ui label{font-size:12px;font-weight:700;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{min-width:100px;margin:5px 0;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;background:#fff}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}.swagger-ui textarea{font-size:12px;width:100%;min-height:280px;padding:10px;border:none;border-radius:4px;outline:none;background:hsla(0,0%,100%,.8);font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{font-size:12px;min-height:100px;margin:0;padding:10px;resize:none;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .checkbox{padding:5px 0 10px;-webkit-transition:opacity .5s;transition:opacity .5s;color:#333}.swagger-ui .checkbox label{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .checkbox p{font-weight:400!important;font-style:italic;margin:0!important;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{position:relative;top:3px;display:inline-block;width:16px;height:16px;margin:0 8px 0 0;padding:5px;cursor:pointer;border-radius:1px;background:#e8e8e8;-webkit-box-shadow:0 0 0 2px #e8e8e8;box-shadow:0 0 0 2px #e8e8e8;-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E") 50% no-repeat}.swagger-ui .dialog-ux{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0}.swagger-ui .dialog-ux .backdrop-ux{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{position:absolute;z-index:9999;top:50%;left:50%;width:100%;min-width:300px;max-width:650px;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:1px solid #ebebeb;border-radius:4px;background:#fff;-webkit-box-shadow:0 10px 30px 0 rgba(0,0,0,.2);box-shadow:0 10px 30px 0 rgba(0,0,0,.2)}.swagger-ui .dialog-ux .modal-ux-content{overflow-y:auto;max-height:540px;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{font-size:12px;margin:0 0 5px;color:#41444e;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-content h4{font-size:18px;font-weight:600;margin:15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-header{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 0;border-bottom:1px solid #ebebeb;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .dialog-ux .modal-ux-header .close-modal{padding:0 10px;border:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui .dialog-ux .modal-ux-header h3{font-size:20px;font-weight:600;margin:0;padding:0 20px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .model{font-size:12px;font-weight:300;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:#aaa!important}.swagger-ui .model-toggle{font-size:10px;position:relative;top:6px;display:inline-block;margin:auto .3em;cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.swagger-ui .model-toggle:after{display:block;width:20px;height:20px;content:"";background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E") 50% no-repeat;background-size:100%}.swagger-ui .model-jump-to-path{position:relative;cursor:pointer}.swagger-ui .model-jump-to-path .view-line-link{position:absolute;top:-.4em;cursor:pointer}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{position:absolute;top:-1.8em;visibility:hidden;padding:.1em .5em;white-space:nowrap;color:#ebebeb;border-radius:4px;background:rgba(0,0,0,.7)}.swagger-ui .model p{margin:0 0 1em}.swagger-ui section.models{margin:30px 0;border:1px solid rgba(59,65,81,.3);border-radius:4px}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{margin:0 0 5px;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui section.models h4{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;margin:0;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;font-family:Titillium Web,sans-serif;color:#777;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui section.models h4 svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui section.models h4 span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{font-size:16px;margin:0 0 10px;font-family:Titillium Web,sans-serif;color:#777}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{margin:0 20px 15px;-webkit-transition:all .5s;transition:all .5s;border-radius:4px;background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{padding:10px;border-radius:4px;background:rgba(0,0,0,.1)}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-box.deprecated{opacity:.5}.swagger-ui .model-title{font-size:16px;font-family:Titillium Web,sans-serif;color:#555}.swagger-ui .model-deprecated-warning{font-size:16px;font-weight:600;margin-right:1em;font-family:Titillium Web,sans-serif;color:#f93e3e}.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-name{display:inline-block;width:100px}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#999}.swagger-ui table{width:100%;padding:0 10px;border-collapse:collapse}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{width:124px;padding:0 0 0 2em}.swagger-ui table.headers td{font-size:12px;font-weight:300;vertical-align:middle;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{max-width:20%;min-width:6em;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{font-size:12px;font-weight:700;padding:12px 0;text-align:left;border-bottom:1px solid rgba(59,65,81,.2);font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description input[type=text]{width:100%;max-width:340px}.swagger-ui .parameters-col_description select{border-width:1px}.swagger-ui .parameter__name{font-size:16px;font-weight:400;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required:after{font-size:10px;position:relative;top:-6px;padding:5px;content:"required";color:rgba(255,0,0,.6)}.swagger-ui .parameter__in{color:#888}.swagger-ui .parameter__deprecated,.swagger-ui .parameter__in{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600}.swagger-ui .parameter__deprecated{color:red}.swagger-ui .table-container{padding:20px}.swagger-ui .topbar{padding:8px 30px;background-color:#89bf04}.swagger-ui .topbar .topbar-wrapper,.swagger-ui .topbar a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .topbar a{font-size:1.5em;font-weight:700;-webkit-box-flex:1;-ms-flex:1;flex:1;max-width:300px;text-decoration:none;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:3;-ms-flex:3;flex:3;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .topbar .download-url-wrapper input[type=text]{width:100%;min-width:350px;margin:0;border:2px solid #547f00;border-radius:4px 0 0 4px;outline:none}.swagger-ui .topbar .download-url-wrapper .select-label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;max-width:600px;margin:0}.swagger-ui .topbar .download-url-wrapper .select-label span{font-size:16px;-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px 0 0;text-align:right}.swagger-ui .topbar .download-url-wrapper .select-label select{-webkit-box-flex:2;-ms-flex:2;flex:2;width:100%;border:2px solid #547f00;outline:none;-webkit-box-shadow:none;box-shadow:none}.swagger-ui .topbar .download-url-wrapper .download-url-button{font-size:16px;font-weight:700;padding:4px 40px;border:none;border-radius:0 4px 4px 0;background:#547f00;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .info{margin:50px 0}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5{font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info code{padding:3px 5px;border-radius:4px;background:rgba(0,0,0,.05);font-family:Source Code Pro,monospace;font-weight:600;color:#9012fe}.swagger-ui .info a{font-size:14px;-webkit-transition:all .4s;transition:all .4s;font-family:Open Sans,sans-serif;color:#4990e2}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{font-size:12px;font-weight:300!important;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .info .title{font-size:36px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info .title small{font-size:10px;position:relative;top:-5px;display:inline-block;margin:0 0 0 5px;padding:2px 4px;vertical-align:super;border-radius:57px;background:#7d8492}.swagger-ui .info .title small pre{margin:0;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .auth-btn-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .auth-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{padding-right:20px}.swagger-ui .auth-container{margin:0 0 10px;padding:10px 20px;border-bottom:1px solid #ebebeb}.swagger-ui .auth-container:last-of-type{margin:0;padding:10px 20px;border:0}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{font-size:12px;padding:10px;border-radius:4px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .scopes h2{font-size:14px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{margin:20px;padding:10px 20px;-webkit-animation:scaleUp .5s;animation:scaleUp .5s;border:2px solid #f93e3e;border-radius:4px;background:rgba(249,62,62,.1)}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{font-size:14px;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .errors-wrapper .errors small{color:#666}.swagger-ui .errors-wrapper hgroup{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .errors-wrapper hgroup h4{font-size:20px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}@-webkit-keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.swagger-ui .Resizer.vertical.disabled{display:none} 2 | /*# sourceMappingURL=swagger-ui.css.map*/ -------------------------------------------------------------------------------- /public/docs/swagger-ui.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""} -------------------------------------------------------------------------------- /public/docs/swagger-ui.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAmxeA","sourceRoot":""} -------------------------------------------------------------------------------- /src/app/README.md: -------------------------------------------------------------------------------- 1 | # APPLICATION 2 | > The application layer defines the actual behavior of our application, thus being responsible for performing interactions among units of the domain layer. 3 | -------------------------------------------------------------------------------- /src/app/company/delete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * function for getter company. 3 | */ 4 | module.exports = ({ companyRepository }) => { 5 | // code for getting all the items 6 | const remove = ({ id }) => { 7 | return Promise 8 | .resolve() 9 | .then(() => 10 | companyRepository.update({ 11 | isDeleted: 1 12 | }, { 13 | where: { id } 14 | }) 15 | ) 16 | .catch((error) => { 17 | throw new Error(error) 18 | }) 19 | } 20 | 21 | return { 22 | remove 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/company/get.js: -------------------------------------------------------------------------------- 1 | /** 2 | * function for getter company. 3 | */ 4 | module.exports = ({ companyRepository }) => { 5 | // code for getting all the items 6 | const all = () => { 7 | return Promise 8 | .resolve() 9 | .then(() => 10 | companyRepository.getAll({ 11 | attributes: [ 12 | 'id', 'name', 'address', 'contact', 'tin', 'sss', 'philhealth', 'isDeleted', 'createdBy', 'updatedBy' 13 | ] 14 | }) 15 | ) 16 | .catch(error => { 17 | throw new Error(error) 18 | }) 19 | } 20 | 21 | return { 22 | all 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/company/index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const get = require('./get') 3 | const post = require('./post') 4 | const put = require('./put') 5 | const remove = require('./delete') 6 | 7 | const { companyRepository } = container.resolve('repository') 8 | 9 | const getUseCase = get({ companyRepository }) 10 | const postUseCase = post({ companyRepository }) 11 | const putUseCase = put({ companyRepository }) 12 | const deleteUseCase = remove({ companyRepository }) 13 | 14 | module.exports = { 15 | getUseCase, 16 | postUseCase, 17 | putUseCase, 18 | deleteUseCase 19 | } 20 | -------------------------------------------------------------------------------- /src/app/company/post.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will hold all the get use-case for company domain 3 | */ 4 | const { Company } = require('src/domain/company') 5 | 6 | /** 7 | * function for getter company. 8 | */ 9 | module.exports = ({ companyRepository }) => { 10 | // code for getting all the items 11 | const create = ({ body }) => { 12 | return Promise 13 | .resolve() 14 | .then(() => { 15 | const company = Company(body) 16 | return companyRepository.create(company) 17 | }) 18 | .catch((error) => { 19 | throw new Error(error) 20 | }) 21 | } 22 | 23 | return { 24 | create 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/company/put.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will hold all the get use-case for company domain 3 | */ 4 | const { Company } = require('src/domain/company') 5 | 6 | /** 7 | * function for getter company. 8 | */ 9 | module.exports = ({ companyRepository }) => { 10 | // code for getting all the items 11 | const update = ({ id, body }) => { 12 | return new Promise(async (resolve, reject) => { 13 | try { 14 | const company = Company(body) 15 | await companyRepository.update(company, { 16 | where: { id } 17 | }) 18 | 19 | resolve(company) 20 | } catch (error) { 21 | reject(error) 22 | } 23 | }) 24 | } 25 | 26 | return { 27 | update 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * We want to start here so we can manage other infrastructure 4 | * database 5 | * memcache 6 | * express server 7 | */ 8 | module.exports = ({ server, database }) => { 9 | return { 10 | start: () => 11 | database.authenticate() 12 | .then(server.setupHealthCheck) 13 | .then(server.start), 14 | close: () => 15 | database.close() 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/token/index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const post = require('./post') 3 | 4 | const { repository: { userRepository }, jwt } = container.resolve(['repository', 'jwt']) 5 | 6 | const postUseCase = post({ 7 | userRepository, 8 | webToken: jwt 9 | }) 10 | 11 | module.exports = { 12 | postUseCase 13 | } 14 | -------------------------------------------------------------------------------- /src/app/token/post.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will hold all the get use-case for user domain 3 | */ 4 | const Token = require('src/domain/token') 5 | 6 | /** 7 | * function for getter user. 8 | */ 9 | module.exports = ({ userRepository, webToken }) => { 10 | // code for getting all the items 11 | const validate = ({ body }) => { 12 | return new Promise(async (resolve, reject) => { 13 | try { 14 | const credentials = Token(body) 15 | const userCredentials = await userRepository.findOne({ 16 | attributes: [ 17 | 'id', 'firstName', 'lastName', 'middleName', 'email', 'password', 'roleId', 'isDeleted', 'createdBy' 18 | ], 19 | where: { 20 | email: credentials.email, 21 | isDeleted: 0 22 | } 23 | }) 24 | 25 | const validatePass = userRepository.validatePassword(userCredentials.password) 26 | 27 | if (!validatePass(credentials.password)) { 28 | throw new Error('Invalid Credentials') 29 | } 30 | const signIn = webToken.signin() 31 | 32 | resolve({ 33 | token: signIn({ 34 | id: userCredentials.id, 35 | firstName: userCredentials.firstName, 36 | lastName: userCredentials.lastName, 37 | middleName: userCredentials.middleName, 38 | email: userCredentials.email 39 | }) 40 | }) 41 | } catch (error) { 42 | reject(error) 43 | } 44 | }) 45 | } 46 | 47 | return { 48 | validate 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/user/delete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * function for getter user. 3 | */ 4 | module.exports = ({ userRepository }) => { 5 | // code for getting all the items 6 | const remove = ({ id }) => { 7 | return Promise 8 | .resolve() 9 | .then(() => 10 | userRepository.update({ 11 | isDeleted: 1 12 | }, { 13 | where: { id } 14 | }) 15 | ) 16 | .catch((error) => { 17 | throw new Error(error) 18 | }) 19 | } 20 | 21 | return { 22 | remove 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/user/get.js: -------------------------------------------------------------------------------- 1 | /** 2 | * function for getter user. 3 | */ 4 | module.exports = ({ userRepository }) => { 5 | // code for getting all the items 6 | const all = () => { 7 | return Promise 8 | .resolve() 9 | .then(() => 10 | userRepository.getAll({ 11 | attributes: [ 12 | 'id', 'firstName', 'lastName', 'middleName', 'email', 'roleId', 'isDeleted', 'createdBy', 'updatedBy' 13 | ] 14 | }) 15 | ) 16 | .catch(error => { 17 | throw new Error(error) 18 | }) 19 | } 20 | 21 | return { 22 | all 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/user/index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const get = require('./get') 3 | const post = require('./post') 4 | const put = require('./put') 5 | const remove = require('./delete') 6 | 7 | const { userRepository } = container.resolve('repository') 8 | 9 | const getUseCase = get({ userRepository }) 10 | const postUseCase = post({ userRepository }) 11 | const putUseCase = put({ userRepository }) 12 | const deleteUseCase = remove({ userRepository }) 13 | 14 | module.exports = { 15 | getUseCase, 16 | postUseCase, 17 | putUseCase, 18 | deleteUseCase 19 | } 20 | -------------------------------------------------------------------------------- /src/app/user/post.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will hold all the get use-case for user domain 3 | */ 4 | const { User } = require('src/domain/user') 5 | /** 6 | * function for getter user. 7 | */ 8 | module.exports = ({ userRepository }) => { 9 | // code for getting all the items 10 | const create = ({ body }) => { 11 | return Promise 12 | .resolve() 13 | .then(() => { 14 | const password = body.password || 'test' 15 | const entity = Object.assign({}, body, { 16 | password 17 | }) 18 | const user = User(entity) 19 | return userRepository.create(user) 20 | }) 21 | .catch((error) => { 22 | throw new Error(error) 23 | }) 24 | } 25 | 26 | return { 27 | create 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/user/put.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will hold all the get use-case for user domain 3 | */ 4 | const { User } = require('src/domain/user') 5 | 6 | /** 7 | * function for getter user. 8 | */ 9 | module.exports = ({ userRepository }) => { 10 | // code for getting all the items 11 | const update = ({ id, body }) => { 12 | return new Promise(async (resolve, reject) => { 13 | try { 14 | const user = User(body) 15 | await userRepository.update(user, { 16 | where: { id } 17 | }) 18 | 19 | resolve(user) 20 | } catch (error) { 21 | reject(error) 22 | } 23 | }) 24 | } 25 | 26 | return { 27 | update 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/container.js: -------------------------------------------------------------------------------- 1 | const { createContainer, asValue, asFunction, InjectionMode } = require('awilix') 2 | // you can do this 3 | const app = require('./app') 4 | const server = require('./interfaces/http/server') 5 | const router = require('./interfaces/http/router') 6 | const auth = require('./interfaces/http/auth') 7 | const config = require('../config') 8 | const logger = require('./infra/logging/logger') 9 | const database = require('./infra/database') 10 | const jwt = require('./infra/jwt') 11 | const response = require('./infra/support/response') 12 | const date = require('./infra/support/date') 13 | const repository = require('./infra/repositories') 14 | const health = require('./infra/health') 15 | 16 | const container = createContainer({ 17 | injectionMode: InjectionMode.PROXY, // default inejction 18 | // It enables additional correctness checks that can help you catch bugs early. 19 | // specialy with singleton values (stale cache issues) 20 | // read more here https://github.com/jeffijoe/awilix?tab=readme-ov-file#strict-mode 21 | strict: true 22 | }) 23 | 24 | // SYSTEM 25 | container 26 | .register({ 27 | app: asFunction(app).singleton(), 28 | server: asFunction(server).singleton(), 29 | router: asFunction(router).singleton(), 30 | logger: asFunction(logger).singleton(), 31 | database: asFunction(database).singleton(), 32 | auth: asFunction(auth).singleton(), 33 | jwt: asFunction(jwt).singleton(), 34 | response: asFunction(response).singleton(), 35 | date: asFunction(date).singleton(), 36 | config: asValue(config), 37 | repository: asFunction(repository).singleton(), 38 | health: asFunction(health).singleton() 39 | }) 40 | 41 | module.exports = { 42 | // could be singler dependency or array 43 | resolve: (dependency) => { 44 | // E.g. 45 | // const { logger, response: { Success, Fail }, auth } = container.resolve(['logger', 'response', 'auth']) 46 | if (Array.isArray(dependency)) { 47 | return dependency.reduce((deps, dep) => ({ 48 | ...deps, 49 | [dep]: container.resolve(dep) 50 | }), {}) 51 | } 52 | 53 | // eg. const jwt = container.resolve('jwt') 54 | return container.resolve(dependency) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/domain/README.md: -------------------------------------------------------------------------------- 1 | # DOMAIN 2 | > In this layer, we may define units which play the role of entities and business rules and have a direct relationship to our domain. -------------------------------------------------------------------------------- /src/domain/company/company.js: -------------------------------------------------------------------------------- 1 | const t = require('tcomb') 2 | const { compose } = require('ramda') 3 | const { cleanData } = require('../helper') 4 | 5 | const Company = t.struct({ 6 | id: t.maybe(t.String), 7 | name: t.String, 8 | address: t.String, 9 | contact: t.String, 10 | tin: t.String, 11 | sss: t.String, 12 | philhealth: t.String, 13 | isDeleted: t.Number, 14 | createdBy: t.maybe(t.String), 15 | updatedBy: t.maybe(t.String), 16 | createdAt: t.maybe(t.Date), 17 | updatedAt: t.maybe(t.Date) 18 | }) 19 | 20 | module.exports = compose( 21 | cleanData, 22 | Company 23 | ) 24 | -------------------------------------------------------------------------------- /src/domain/company/index.js: -------------------------------------------------------------------------------- 1 | 2 | const Company = require('./company') 3 | 4 | module.exports = { 5 | Company 6 | } 7 | -------------------------------------------------------------------------------- /src/domain/helper.js: -------------------------------------------------------------------------------- 1 | 2 | const { complement, compose, isNil, pickBy } = require('ramda') 3 | 4 | const notNull = compose(complement(isNil)) 5 | /** 6 | * we need to remove undefined array means not required data. 7 | */ 8 | const cleanData = (entity) => pickBy(notNull, entity) 9 | 10 | module.exports = { 11 | cleanData 12 | } 13 | -------------------------------------------------------------------------------- /src/domain/token/index.js: -------------------------------------------------------------------------------- 1 | const t = require('tcomb') 2 | 3 | const Token = t.struct({ 4 | email: t.String, 5 | password: t.String 6 | }) 7 | 8 | module.exports = Token 9 | -------------------------------------------------------------------------------- /src/domain/user/index.js: -------------------------------------------------------------------------------- 1 | const User = require('./user') 2 | 3 | module.exports = { 4 | User 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/user/user.js: -------------------------------------------------------------------------------- 1 | const t = require('tcomb') 2 | const { compose } = require('ramda') 3 | const { cleanData } = require('../helper') 4 | 5 | const User = t.struct({ 6 | id: t.maybe(t.String), 7 | firstName: t.String, 8 | lastName: t.String, 9 | middleName: t.String, 10 | email: t.String, 11 | password: t.maybe(t.String), 12 | roleId: t.Number, 13 | verificationCode: t.maybe(t.String), 14 | isVerified: t.maybe(t.Number), 15 | isDeleted: t.Number, 16 | createdBy: t.maybe(t.String), 17 | updatedBy: t.maybe(t.String), 18 | createdAt: t.maybe(t.Date), 19 | updatedAt: t.maybe(t.Date) 20 | }) 21 | 22 | module.exports = compose( 23 | cleanData, 24 | User 25 | ) 26 | -------------------------------------------------------------------------------- /src/infra/README.md: -------------------------------------------------------------------------------- 1 | # INFRASTRUCTURE 2 | > This is the lowest layer of all, and it’s the boundary to whatever is external to our application: the database, email services, queue engines, etc. -------------------------------------------------------------------------------- /src/infra/database/README.md: -------------------------------------------------------------------------------- 1 | # Database 2 | > Main entry point of what backing service we gonna use. -------------------------------------------------------------------------------- /src/infra/database/index.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('src/infra/sequelize') 2 | 3 | module.exports = ({ logger, config }) => { 4 | if (!config.db) { 5 | /* eslint-disable no-console */ 6 | logger.error('Database config file log not found, disabling database.') 7 | /* eslint-enable no-console */ 8 | return false 9 | } 10 | 11 | return sequelize({ config, basePath: __dirname }) 12 | } 13 | -------------------------------------------------------------------------------- /src/infra/database/models/company.js: -------------------------------------------------------------------------------- 1 | module.exports = function (sequelize, DataTypes) { 2 | const Company = sequelize.define('companies', { 3 | id: { 4 | type: DataTypes.UUID, 5 | defaultValue: DataTypes.UUIDV4, 6 | primaryKey: true, 7 | allowNull: false 8 | }, 9 | name: { 10 | type: DataTypes.STRING, 11 | allowNull: false 12 | }, 13 | address: { 14 | type: DataTypes.STRING, 15 | allowNull: false 16 | }, 17 | contact: { 18 | type: DataTypes.STRING, 19 | allowNull: false 20 | }, 21 | tin: { 22 | type: DataTypes.STRING, 23 | allowNull: false 24 | }, 25 | sss: { 26 | type: DataTypes.STRING, 27 | allowNull: false 28 | }, 29 | philhealth: { 30 | type: DataTypes.STRING, 31 | allowNull: false 32 | }, 33 | isDeleted: { 34 | type: DataTypes.INTEGER, 35 | defaultValue: 0 36 | }, 37 | createdBy: { 38 | type: DataTypes.UUID, 39 | allowNull: false 40 | }, 41 | updatedBy: { 42 | type: DataTypes.UUID, 43 | allowNull: true 44 | } 45 | }, { 46 | freezeTableName: true, 47 | timestamps: false, 48 | classMethods: { 49 | associate () { 50 | // associations can be defined here 51 | } 52 | } 53 | }) 54 | 55 | return Company 56 | } 57 | -------------------------------------------------------------------------------- /src/infra/database/models/user.js: -------------------------------------------------------------------------------- 1 | const { encryptPassword } = require('../../encryption') 2 | 3 | module.exports = function (sequelize, DataTypes) { 4 | const User = sequelize.define('users', { 5 | id: { 6 | type: DataTypes.UUID, 7 | defaultValue: DataTypes.UUIDV4, 8 | primaryKey: true, 9 | allowNull: false 10 | }, 11 | firstName: { 12 | type: DataTypes.STRING, 13 | allowNull: false 14 | }, 15 | lastName: { 16 | type: DataTypes.STRING, 17 | allowNull: false 18 | }, 19 | middleName: { 20 | type: DataTypes.STRING, 21 | allowNull: false 22 | }, 23 | email: { 24 | type: DataTypes.STRING, 25 | allowNull: false, 26 | unique: true 27 | }, 28 | password: { 29 | type: DataTypes.STRING, 30 | allowNull: false 31 | }, 32 | roleId: { 33 | type: DataTypes.INTEGER, 34 | allowNull: false 35 | }, 36 | verificationCode: { 37 | type: DataTypes.STRING, 38 | defaultValue: '' 39 | }, 40 | isVerified: { 41 | type: DataTypes.INTEGER, 42 | defaultValue: 0 43 | }, 44 | isDeleted: { 45 | type: DataTypes.INTEGER, 46 | defaultValue: 0 47 | }, 48 | createdBy: { 49 | type: DataTypes.UUID, 50 | allowNull: false 51 | }, 52 | updatedBy: { 53 | type: DataTypes.UUID, 54 | allowNull: true 55 | } 56 | }, { 57 | hooks: { 58 | beforeCreate: user => { 59 | user.password = encryptPassword(user.password) 60 | } 61 | }, 62 | freezeTableName: true, 63 | timestamps: false, 64 | classMethods: { 65 | associate () { 66 | // associations can be defined here 67 | } 68 | } 69 | }) 70 | 71 | return User 72 | } 73 | -------------------------------------------------------------------------------- /src/infra/encryption/index.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcrypt') 2 | 3 | const encryptPassword = (password) => { 4 | const salt = bcrypt.genSaltSync() 5 | return bcrypt.hashSync(password, salt) 6 | } 7 | 8 | const comparePassword = (password, encodedPassword) => { 9 | return bcrypt.compareSync(password, encodedPassword) 10 | } 11 | 12 | module.exports = { 13 | encryptPassword, 14 | comparePassword 15 | } 16 | -------------------------------------------------------------------------------- /src/infra/health/index.js: -------------------------------------------------------------------------------- 1 | const { createTerminus, HealthCheckError } = require('@godaddy/terminus') 2 | 3 | module.exports = ({ database }) => { 4 | // Healthchecks 5 | const readinessProbe = app => async () => { 6 | const isServerListening = app.listening 7 | const isDBConnected = await database.authenticate() 8 | if (!isDBConnected || !isServerListening) { 9 | throw HealthCheckError 10 | } 11 | 12 | return true 13 | } 14 | 15 | return { 16 | start: app => 17 | createTerminus(app, { 18 | '/livez': () => {}, 19 | '/readyz': readinessProbe 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/infra/jwt/index.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const { 3 | compose, 4 | trim, 5 | replace, 6 | partialRight 7 | } = require('ramda') 8 | 9 | module.exports = ({ config }) => ({ 10 | signin: (options) => (payload) => { 11 | const opt = Object.assign({}, options, { expiresIn: '1h' }) 12 | return jwt.sign(payload, config.authSecret, opt) 13 | }, 14 | verify: (options) => (token) => { 15 | const opt = Object.assign({}, options) 16 | return jwt.verify(token, config.authSecret, opt) 17 | }, 18 | decode: (options) => (token) => { 19 | const opt = Object.assign({}, options) 20 | const decodeToken = compose( 21 | partialRight(jwt.decode, [opt]), 22 | trim, 23 | replace(/JWT|jwt/g, '') 24 | ) 25 | 26 | return decodeToken(token) 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /src/infra/logging/logger.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const winston = require('winston') 3 | 4 | if (!fs.existsSync(`logs`)) { 5 | fs.mkdirSync(`logs`) 6 | } 7 | 8 | module.exports = ({ config }) => { 9 | // eslint-disable-next-line new-cap 10 | return new winston.createLogger({ 11 | transports: [ 12 | new winston.transports.Console(), 13 | new winston.transports.File(Object.assign( 14 | config.logging, { 15 | filename: `logs/${config.env}.log` 16 | })) 17 | ] 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/infra/repositories/company/index.js: -------------------------------------------------------------------------------- 1 | const { toEntity } = require('./transform') 2 | 3 | module.exports = ({ model }) => { 4 | const getAll = (...args) => 5 | model.findAll(...args).then((entity) => 6 | entity.map((data) => { 7 | const { dataValues } = data 8 | return toEntity(dataValues) 9 | }) 10 | ) 11 | 12 | const create = (...args) => 13 | model.create(...args).then(({ dataValues }) => toEntity(dataValues)) 14 | 15 | const update = (...args) => 16 | model.update(...args) 17 | 18 | const destroy = (...args) => 19 | model.destroy(...args) 20 | 21 | return { 22 | getAll, 23 | create, 24 | update, 25 | destroy 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/infra/repositories/company/transform.js: -------------------------------------------------------------------------------- 1 | const { Company } = require('src/domain/company') 2 | 3 | const toEntity = Company 4 | 5 | module.exports = { 6 | toEntity 7 | } 8 | -------------------------------------------------------------------------------- /src/infra/repositories/index.js: -------------------------------------------------------------------------------- 1 | const User = require('./user') 2 | const Company = require('./company') 3 | 4 | module.exports = ({ database }) => { 5 | const userModel = database.models.users 6 | const companyModel = database.models.companies 7 | 8 | return { 9 | userRepository: User({ model: userModel }), 10 | companyRepository: Company({ model: companyModel }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/infra/repositories/user/index.js: -------------------------------------------------------------------------------- 1 | const { toEntity } = require('./transform') 2 | const { comparePassword } = require('../../encryption') 3 | 4 | module.exports = ({ model }) => { 5 | const getAll = (...args) => 6 | model.findAll(...args).then((entity) => 7 | entity.map((data) => { 8 | const { dataValues } = data 9 | return toEntity(dataValues) 10 | }) 11 | ) 12 | 13 | const create = (...args) => 14 | model.create(...args).then(({ dataValues }) => toEntity(dataValues)) 15 | 16 | const update = (...args) => 17 | model.update(...args) 18 | .catch((error) => { throw new Error(error) }) 19 | 20 | const findById = (...args) => 21 | model.findByPk(...args) 22 | .then(({ dataValues }) => toEntity(dataValues)) 23 | .catch((error) => { throw new Error(error) }) 24 | 25 | const findOne = (...args) => 26 | model.findOne(...args) 27 | .then(({ dataValues }) => toEntity(dataValues)) 28 | .catch((error) => { throw new Error(error) }) 29 | 30 | const validatePassword = (endcodedPassword) => (password) => 31 | comparePassword(password, endcodedPassword) 32 | 33 | const destroy = (...args) => 34 | model.destroy(...args) 35 | 36 | return { 37 | getAll, 38 | create, 39 | update, 40 | findById, 41 | findOne, 42 | validatePassword, 43 | destroy 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/infra/repositories/user/transform.js: -------------------------------------------------------------------------------- 1 | const { User } = require('src/domain/user') 2 | 3 | const toEntity = User 4 | 5 | module.exports = { 6 | toEntity 7 | } 8 | -------------------------------------------------------------------------------- /src/infra/sequelize/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const Sequelize = require('sequelize') 4 | 5 | module.exports = ({ config, basePath }) => { 6 | const sequelize = new Sequelize( 7 | config.db.url, 8 | // we have to remove the depraction warning 9 | // https://github.com/sequelize/sequelize/issues/8417 10 | { ...config.db } 11 | 12 | ) 13 | 14 | const db = { 15 | authenticate: () => sequelize.authenticate(), 16 | close: () => sequelize.close(), 17 | models: {} 18 | } 19 | 20 | const dir = path.join(basePath, './models') 21 | fs.readdirSync(dir).forEach(file => { 22 | const modelDir = path.join(dir, file) 23 | const model = sequelize.import(modelDir) 24 | db.models[model.name] = model 25 | }) 26 | 27 | Object.keys(db.models).forEach(key => { 28 | if ('associate' in db.models[key]) { 29 | db.models[key].associate(db.models) 30 | } 31 | }) 32 | 33 | return db 34 | } 35 | -------------------------------------------------------------------------------- /src/infra/sequelize/migrations/001-users.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.createTable('users', { 5 | id: { 6 | type: Sequelize.UUID, 7 | defaultValue: Sequelize.UUIDV4, 8 | primaryKey: true, 9 | allowNull: false 10 | }, 11 | firstName: { 12 | type: Sequelize.STRING, 13 | allowNull: false 14 | }, 15 | lastName: { 16 | type: Sequelize.STRING, 17 | allowNull: false 18 | }, 19 | middleName: { 20 | type: Sequelize.STRING, 21 | allowNull: false 22 | }, 23 | email: { 24 | type: Sequelize.STRING, 25 | allowNull: false, 26 | unique: true 27 | }, 28 | password: { 29 | type: Sequelize.STRING, 30 | allowNull: false 31 | }, 32 | roleId: { 33 | type: Sequelize.INTEGER, 34 | allowNull: false 35 | }, 36 | verificationCode: { 37 | type: Sequelize.STRING, 38 | defaultValue: '' 39 | }, 40 | isVerified: { 41 | type: Sequelize.INTEGER, 42 | defaultValue: 0 43 | }, 44 | isDeleted: { 45 | type: Sequelize.INTEGER, 46 | defaultValue: 0 47 | }, 48 | createdBy: { 49 | type: Sequelize.UUID, 50 | allowNull: false 51 | }, 52 | updatedBy: { 53 | type: Sequelize.UUID, 54 | allowNull: true 55 | }, 56 | createdAt: { 57 | allowNull: false, 58 | type: Sequelize.DATE, 59 | defaultValue: Sequelize.fn('NOW') 60 | }, 61 | updatedAt: { 62 | allowNull: false, 63 | type: Sequelize.DATE, 64 | defaultValue: Sequelize.fn('NOW') 65 | } 66 | }) 67 | }, 68 | down: function (queryInterface) { 69 | return queryInterface.dropTable('users') 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/infra/sequelize/migrations/002-company.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.createTable('companies', { 5 | id: { 6 | type: Sequelize.UUID, 7 | defaultValue: Sequelize.UUIDV4, 8 | primaryKey: true, 9 | allowNull: false 10 | }, 11 | name: { 12 | type: Sequelize.STRING, 13 | allowNull: false 14 | }, 15 | address: { 16 | type: Sequelize.STRING, 17 | allowNull: false 18 | }, 19 | contact: { 20 | type: Sequelize.STRING, 21 | allowNull: false 22 | }, 23 | tin: { 24 | type: Sequelize.STRING, 25 | allowNull: false 26 | }, 27 | sss: { 28 | type: Sequelize.STRING, 29 | allowNull: false 30 | }, 31 | philhealth: { 32 | type: Sequelize.STRING, 33 | allowNull: false 34 | }, 35 | isDeleted: { 36 | type: Sequelize.INTEGER, 37 | defaultValue: 0 38 | }, 39 | createdBy: { 40 | type: Sequelize.UUID, 41 | allowNull: false 42 | }, 43 | updatedBy: { 44 | type: Sequelize.UUID, 45 | allowNull: true 46 | }, 47 | createdAt: { 48 | allowNull: false, 49 | type: Sequelize.DATE, 50 | defaultValue: Sequelize.fn('NOW') 51 | }, 52 | updatedAt: { 53 | allowNull: false, 54 | type: Sequelize.DATE, 55 | defaultValue: Sequelize.fn('NOW') 56 | } 57 | }) 58 | }, 59 | down: function (queryInterface) { 60 | return queryInterface.dropTable('companies') 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/infra/sequelize/seeders/development/001-users.js: -------------------------------------------------------------------------------- 1 | const Faker = require('../../../support/fakers') 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.bulkInsert('users', Faker('users'), {}) 6 | }, 7 | 8 | down: function (queryInterface, Sequelize) { 9 | return queryInterface.bulkDelete('users', null, {}) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/sequelize/seeders/development/002-company.js: -------------------------------------------------------------------------------- 1 | const Faker = require('../../../support/fakers') 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.bulkInsert('companies', Faker('companies'), {}) 6 | }, 7 | 8 | down: function (queryInterface, Sequelize) { 9 | return queryInterface.bulkDelete('companies', null, {}) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/sequelize/seeders/production/001-users.js: -------------------------------------------------------------------------------- 1 | const Faker = require('../../../support/fakers') 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.bulkInsert('users', Faker('users'), {}) 6 | }, 7 | 8 | down: function (queryInterface, Sequelize) { 9 | return queryInterface.bulkDelete('users', null, {}) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/sequelize/seeders/production/002-company.js: -------------------------------------------------------------------------------- 1 | const Faker = require('../../../support/fakers') 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.bulkInsert('companies', Faker('companies'), {}) 6 | }, 7 | 8 | down: function (queryInterface, Sequelize) { 9 | return queryInterface.bulkDelete('companies', null, {}) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/support/date.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment') 2 | 3 | module.exports = ({ config }) => { 4 | const currentDate = moment().tz(config.timezone) 5 | 6 | const addHour = (duration) => currentDate.add(duration, 'hours') 7 | 8 | return { 9 | addHour 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/support/fakers/README.md: -------------------------------------------------------------------------------- 1 | # FAKER 2 | > Put all the faker data here. -------------------------------------------------------------------------------- /src/infra/support/fakers/development/companies.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | const { range, map, compose } = require('ramda') 3 | 4 | module.exports = () => { 5 | const numberCompanies = range(0, 5) 6 | const populateCompany = compose( 7 | map(() => ({ 8 | id: faker.random.uuid(), 9 | name: faker.company.companyName(), 10 | contact: faker.phone.phoneNumber(), 11 | address: faker.address.streetAddress(), 12 | tin: faker.finance.iban(), 13 | sss: faker.finance.iban(), 14 | philhealth: faker.finance.iban(), 15 | isDeleted: 0, 16 | // TODO: we need to make sure to match company to the user 17 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b', 18 | createdAt: new Date(), 19 | updatedAt: new Date() 20 | })) 21 | ) 22 | 23 | return populateCompany(numberCompanies) 24 | } 25 | -------------------------------------------------------------------------------- /src/infra/support/fakers/development/users.js: -------------------------------------------------------------------------------- 1 | const { encryptPassword } = require('src/infra/encryption') 2 | 3 | module.exports = () => { 4 | const password = encryptPassword('pass') 5 | 6 | return [{ 7 | id: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b', 8 | firstName: 'Test', 9 | lastName: 'Developer', 10 | middleName: 'Super Dev', 11 | email: 'testdev@gmail.com', 12 | password: password, 13 | roleId: 1, 14 | verificationCode: 'ba1bfda5-1c27-4755-bd23-36c7a4dbfd2b', 15 | isVerified: 1, 16 | isDeleted: 0, 17 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 18 | }] 19 | } 20 | -------------------------------------------------------------------------------- /src/infra/support/fakers/index.js: -------------------------------------------------------------------------------- 1 | 2 | require('dotenv').load() 3 | const path = require('path') 4 | 5 | module.exports = function createFixtureRoutes (fixtureURI) { 6 | const basePath = process.env.NODE_ENV || 'development' 7 | const fixturePath = path.resolve(`src/infra/support/fakers/${basePath}`, fixtureURI) 8 | 9 | const Fixture = require(fixturePath) 10 | return Fixture() 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/support/response.js: -------------------------------------------------------------------------------- 1 | const { assoc } = require('ramda') 2 | 3 | module.exports = ({ config }) => { 4 | const defaultResponse = (success = true) => { 5 | return { 6 | success, 7 | version: config.version, 8 | date: new Date() 9 | } 10 | } 11 | 12 | const Success = (data) => { 13 | return assoc( 14 | 'data', 15 | data, 16 | defaultResponse(true) 17 | ) 18 | } 19 | 20 | const Fail = (data) => { 21 | return assoc( 22 | 'error', 23 | data, 24 | defaultResponse(false) 25 | ) 26 | } 27 | 28 | return { 29 | Success, 30 | Fail 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/interfaces/README.md: -------------------------------------------------------------------------------- 1 | # INTERFACES 2 | > This layer holds all the entry points of our application, such as controllers, the CLI, websockets, graphic user interfaces (in case of desktop applications), and so on. -------------------------------------------------------------------------------- /src/interfaces/http/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport') 2 | const { ExtractJwt, Strategy } = require('passport-jwt') 3 | /** 4 | * middleware to check the if auth vaid 5 | */ 6 | 7 | module.exports = ({ config, repository: { userRepository } }) => { 8 | const params = { 9 | secretOrKey: config.authSecret, 10 | jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('jwt') 11 | } 12 | 13 | const strategy = new Strategy(params, (payload, done) => { 14 | userRepository.findById(payload.id) 15 | .then((user) => { 16 | done(null, user) 17 | }) 18 | .catch((error) => done(error, null)) 19 | }) 20 | 21 | passport.use(strategy) 22 | 23 | passport.serializeUser(function (user, done) { 24 | done(null, user) 25 | }) 26 | 27 | passport.deserializeUser(function (user, done) { 28 | done(null, user) 29 | }) 30 | 31 | return { 32 | initialize: () => { 33 | return passport.initialize() 34 | }, 35 | authenticate: () => { 36 | return passport.authenticate('jwt') 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/interfaces/http/middlewares/error_handler.js: -------------------------------------------------------------------------------- 1 | const Status = require('http-status') 2 | 3 | /* istanbul ignore next */ 4 | module.exports = (err, req, res, next, logger, config) => { // eslint-disable-line no-unused-vars 5 | logger.error(err) 6 | 7 | const response = Object.assign({ 8 | type: 'InternalServerError' 9 | }, config.env === 'development' && { 10 | message: err.message, 11 | stack: err.stack 12 | }) 13 | 14 | res.status(Status.INTERNAL_SERVER_ERROR).json(response) 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/http/middlewares/http_logger.js: -------------------------------------------------------------------------------- 1 | const morgan = require('morgan') 2 | 3 | module.exports = (logger) => { 4 | return morgan('common', { 5 | stream: { 6 | write: (message) => { 7 | logger.info(message.slice(0, -1)) 8 | } 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/company/index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const router = require('./router') 3 | const instance = require('./instance') 4 | 5 | module.exports = () => { 6 | const { logger, response: { Success, Fail }, auth } = container.resolve(['logger', 'response', 'auth']) 7 | const app = instance() 8 | 9 | return { 10 | app, 11 | router: router({ logger, auth, response: { Success, Fail }, ...app }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/company/instance.js: -------------------------------------------------------------------------------- 1 | 2 | const { getUseCase, postUseCase, putUseCase, deleteUseCase } = require('src/app/company') 3 | 4 | module.exports = () => { 5 | return { 6 | getUseCase, 7 | postUseCase, 8 | putUseCase, 9 | deleteUseCase 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/company/router.js: -------------------------------------------------------------------------------- 1 | const Status = require('http-status') 2 | const { Router } = require('express') 3 | 4 | module.exports = ({ 5 | getUseCase, 6 | postUseCase, 7 | putUseCase, 8 | deleteUseCase, 9 | logger, 10 | auth, 11 | response: { Success, Fail } 12 | }) => { 13 | const router = Router() 14 | 15 | /** 16 | * @swagger 17 | * definitions: 18 | * company: 19 | * properties: 20 | * id: 21 | * type: string 22 | * format: uuid 23 | * name: 24 | * type: string 25 | * address: 26 | * type: string 27 | * contact: 28 | * type: string 29 | * tin: 30 | * type: string 31 | * sss: 32 | * type: string 33 | * philhealth: 34 | * type: string 35 | * isDeleted: 36 | * type: number 37 | * createdBy: 38 | * type: string 39 | * format: uuid 40 | */ 41 | 42 | router.use(auth.authenticate()) 43 | 44 | /** 45 | * @swagger 46 | * /companies: 47 | * get: 48 | * tags: 49 | * - Companies 50 | * description: Returns a list of companies 51 | * security: 52 | * - JWT: [] 53 | * responses: 54 | * 200: 55 | * description: An array of companies 56 | * schema: 57 | * type: array 58 | * items: 59 | * $ref: '#/definitions/company' 60 | * 401: 61 | * $ref: '#/responses/Unauthorized' 62 | */ 63 | router 64 | .get('/', (req, res) => { 65 | getUseCase 66 | .all(req, res) 67 | .then(data => { 68 | res.status(Status.OK).json(Success(data)) 69 | }) 70 | .catch((error) => { 71 | logger.error(error) // we still need to log every error for debugging 72 | res.status(Status.BAD_REQUEST).json( 73 | Fail(error.message)) 74 | }) 75 | }) 76 | 77 | /** 78 | * @swagger 79 | * /companies: 80 | * post: 81 | * tags: 82 | * - Companies 83 | * description: Create new company 84 | * security: 85 | * - JWT: [] 86 | * produces: 87 | * - application/json 88 | * parameters: 89 | * - name: body 90 | * description: Company's Entity 91 | * in: body 92 | * required: true 93 | * type: string 94 | * schema: 95 | * $ref: '#/definitions/company' 96 | * responses: 97 | * 200: 98 | * description: Successfully Created 99 | * schema: 100 | * $ref: '#/definitions/company' 101 | * 401: 102 | * $ref: '#/responses/Unauthorized' 103 | * 400: 104 | * $ref: '#/responses/BadRequest' 105 | */ 106 | router 107 | .post('/', (req, res) => { 108 | postUseCase 109 | .create({ body: req.body }) 110 | .then(data => { 111 | res.status(Status.OK).json(Success(data)) 112 | }) 113 | .catch((error) => { 114 | logger.error(error) // we still need to log every error for debugging 115 | res.status(Status.BAD_REQUEST).json( 116 | Fail(error.message)) 117 | }) 118 | }) 119 | 120 | /** 121 | * @swagger 122 | * /companies: 123 | * put: 124 | * tags: 125 | * - Companies 126 | * description: Update Company 127 | * security: 128 | * - JWT: [] 129 | * produces: 130 | * - application/json 131 | * parameters: 132 | * - name: id 133 | * in: path 134 | * required: true 135 | * description: Company's ID to update 136 | * type: string 137 | * - name: body 138 | * description: Company's Entity 139 | * in: body 140 | * required: true 141 | * type: string 142 | * schema: 143 | * $ref: '#/definitions/company' 144 | * responses: 145 | * 200: 146 | * description: Successfully Updated 147 | * schema: 148 | * $ref: '#/definitions/company' 149 | * 401: 150 | * $ref: '#/responses/Unauthorized' 151 | * 400: 152 | * $ref: '#/responses/BadRequest' 153 | */ 154 | router 155 | .put('/:id', (req, res) => { 156 | putUseCase 157 | .update({ id: req.params.id, body: req.body }) 158 | .then(data => { 159 | res.status(Status.OK).json(Success(data)) 160 | }) 161 | .catch((error) => { 162 | logger.error(error) // we still need to log every error for debugging 163 | res.status(Status.BAD_REQUEST).json( 164 | Fail(error.message)) 165 | }) 166 | }) 167 | 168 | /** 169 | * @swagger 170 | * /companies: 171 | * delete: 172 | * tags: 173 | * - Companies 174 | * description: Delete Company 175 | * security: 176 | * - JWT: [] 177 | * produces: 178 | * - application/json 179 | * parameters: 180 | * - name: id 181 | * in: path 182 | * required: true 183 | * description: Company's ID to delete 184 | * type: string 185 | * responses: 186 | * 200: 187 | * description: Successfully Deleted 188 | * schema: 189 | * $ref: '#/definitions/company' 190 | * 401: 191 | * $ref: '#/responses/Unauthorized' 192 | */ 193 | router 194 | .delete('/:id', (req, res) => { 195 | deleteUseCase 196 | .remove({ id: req.params.id }) 197 | .then(data => { 198 | res.status(Status.OK).json(Success(data)) 199 | }) 200 | .catch((error) => { 201 | logger.error(error) // we still need to log every error for debugging 202 | res.status(Status.BAD_REQUEST).json( 203 | Fail(error.message)) 204 | }) 205 | }) 206 | return router 207 | } 208 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/index.js: -------------------------------------------------------------------------------- 1 | const swaggerJSDoc = require('swagger-jsdoc') 2 | const Status = require('http-status') 3 | const { Router } = require('express') 4 | 5 | module.exports = () => { 6 | const router = Router() 7 | 8 | // swagger definition 9 | const swaggerDefinition = { 10 | info: { 11 | title: 'Node DDD API Explorer', 12 | version: '1.0.0', 13 | description: 'Available REST Endpoints of Node DDD RESTful API' 14 | }, 15 | host: `${process.env.API_SWAGGER}:${process.env.PORT}/api/${process.env.APP_VERSION}`, 16 | basePath: '/', 17 | securityDefinitions: { 18 | JWT: { 19 | description: '', 20 | type: 'apiKey', 21 | name: 'Authorization', 22 | in: 'header' 23 | } 24 | } 25 | } 26 | 27 | // options for the swagger docs 28 | const options = { 29 | // import swaggerDefinitions 30 | swaggerDefinition: swaggerDefinition, 31 | // path to the API docs 32 | apis: ['src/interfaces/http/modules/**/*.js'] 33 | } 34 | 35 | // initialize swagger-jsdoc 36 | const swaggerSpec = swaggerJSDoc(options) 37 | /** 38 | * @swagger 39 | * responses: 40 | * Unauthorized: 41 | * description: Unauthorized 42 | * BadRequest: 43 | * description: BadRequest / Invalid Input 44 | */ 45 | 46 | /** 47 | * @swagger 48 | * /: 49 | * get: 50 | * tags: 51 | * - Status 52 | * description: Returns API status 53 | * produces: 54 | * - application/json 55 | * responses: 56 | * 200: 57 | * description: API Status 58 | */ 59 | router.get('/', (req, res) => { 60 | res.status(Status.OK).json({ status: 'API working' }) 61 | }) 62 | 63 | router.get('/swagger.json', (req, res) => { 64 | res.status(Status.OK).json(swaggerSpec) 65 | }) 66 | 67 | return router 68 | } 69 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/token/index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const router = require('./router') 3 | const instance = require('./instance') 4 | 5 | module.exports = () => { 6 | const { logger, response: { Success, Fail }, jwt } = container.resolve(['logger', 'response', 'jwt']) 7 | const app = instance() 8 | 9 | return { 10 | app, 11 | router: router({ logger, jwt, response: { Success, Fail }, ...app }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/token/instance.js: -------------------------------------------------------------------------------- 1 | 2 | const { postUseCase } = require('src/app/token') 3 | 4 | module.exports = () => { 5 | return { 6 | postUseCase 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/token/router.js: -------------------------------------------------------------------------------- 1 | const Status = require('http-status') 2 | const { Router } = require('express') 3 | 4 | module.exports = ({ 5 | postUseCase, 6 | logger, 7 | response: { Success, Fail } 8 | }) => { 9 | const router = Router() 10 | 11 | /** 12 | * @swagger 13 | * definitions: 14 | * auth: 15 | * properties: 16 | * email: 17 | * type: string 18 | * password: 19 | * type: string 20 | */ 21 | 22 | /** 23 | * @swagger 24 | * /token: 25 | * post: 26 | * tags: 27 | * - Authentication 28 | * description: Authenticate 29 | * consumes: 30 | * - application/json 31 | * produces: 32 | * - application/json 33 | * parameters: 34 | * - name: body 35 | * description: User's credentials 36 | * in: body 37 | * required: true 38 | * type: string 39 | * schema: 40 | * $ref: '#/definitions/auth' 41 | * responses: 42 | * 200: 43 | * description: Successfully login 44 | * 400: 45 | * $ref: '#/responses/BadRequest' 46 | */ 47 | router 48 | .post('/', (req, res) => { 49 | postUseCase 50 | .validate({ body: req.body }) 51 | .then(data => { 52 | res.status(Status.OK).json(Success(data)) 53 | }) 54 | .catch((error) => { 55 | logger.error(error) // we still need to log every error for debugging 56 | res.status(Status.BAD_REQUEST).json( 57 | Fail(error.message)) 58 | }) 59 | }) 60 | 61 | return router 62 | } 63 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/user/index.js: -------------------------------------------------------------------------------- 1 | const container = require('src/container') 2 | const router = require('./router') 3 | const instance = require('./instance') 4 | 5 | module.exports = () => { 6 | const { logger, response: { Success, Fail }, auth } = container.resolve(['logger', 'response', 'auth']) 7 | const app = instance() 8 | 9 | return { 10 | app, 11 | router: router({ logger, auth, response: { Success, Fail }, ...app }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/user/instance.js: -------------------------------------------------------------------------------- 1 | 2 | const { getUseCase, postUseCase, putUseCase, deleteUseCase } = require('src/app/user') 3 | 4 | module.exports = () => { 5 | return { 6 | getUseCase, 7 | postUseCase, 8 | putUseCase, 9 | deleteUseCase 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/http/modules/user/router.js: -------------------------------------------------------------------------------- 1 | const Status = require('http-status') 2 | const { Router } = require('express') 3 | 4 | module.exports = ({ 5 | getUseCase, 6 | postUseCase, 7 | putUseCase, 8 | deleteUseCase, 9 | logger, 10 | auth, 11 | response: { Success, Fail } 12 | }) => { 13 | const router = Router() 14 | 15 | /** 16 | * @swagger 17 | * definitions: 18 | * user: 19 | * properties: 20 | * id: 21 | * type: string 22 | * format: uuid 23 | * firstName: 24 | * type: string 25 | * lastName: 26 | * type: string 27 | * middleName: 28 | * type: string 29 | * email: 30 | * type: string 31 | * roleId: 32 | * type: number 33 | * isDeleted: 34 | * type: number 35 | * createdBy: 36 | * type: string 37 | * format: uuid 38 | */ 39 | 40 | router.use(auth.authenticate()) 41 | 42 | /** 43 | * @swagger 44 | * /users: 45 | * get: 46 | * tags: 47 | * - Users 48 | * description: Returns a list of users 49 | * security: 50 | * - JWT: [] 51 | * responses: 52 | * 200: 53 | * description: An array of users 54 | * schema: 55 | * type: array 56 | * items: 57 | * $ref: '#/definitions/user' 58 | * 401: 59 | * $ref: '#/responses/Unauthorized' 60 | */ 61 | router 62 | .get('/', (req, res) => { 63 | getUseCase 64 | .all(req, res) 65 | .then(data => { 66 | res.status(Status.OK).json(Success(data)) 67 | }) 68 | .catch((error) => { 69 | logger.error(error) // we still need to log every error for debugging 70 | res.status(Status.BAD_REQUEST).json( 71 | Fail(error.message)) 72 | }) 73 | }) 74 | /** 75 | * @swagger 76 | * /users: 77 | * post: 78 | * tags: 79 | * - Users 80 | * description: Create new user 81 | * security: 82 | * - JWT: [] 83 | * produces: 84 | * - application/json 85 | * parameters: 86 | * - name: body 87 | * description: User's Entity 88 | * in: body 89 | * required: true 90 | * type: string 91 | * schema: 92 | * $ref: '#/definitions/user' 93 | * responses: 94 | * 200: 95 | * description: Successfully Created 96 | * schema: 97 | * $ref: '#/definitions/user' 98 | * 401: 99 | * $ref: '#/responses/Unauthorized' 100 | * 400: 101 | * $ref: '#/responses/BadRequest' 102 | */ 103 | router 104 | .post('/', (req, res) => { 105 | postUseCase 106 | .create({ body: req.body }) 107 | .then(data => { 108 | res.status(Status.OK).json(Success(data)) 109 | }) 110 | .catch((error) => { 111 | logger.error(error) // we still need to log every error for debugging 112 | res.status(Status.BAD_REQUEST).json( 113 | Fail(error.message)) 114 | }) 115 | }) 116 | /** 117 | * @swagger 118 | * /users: 119 | * put: 120 | * tags: 121 | * - Users 122 | * description: Update User 123 | * security: 124 | * - JWT: [] 125 | * produces: 126 | * - application/json 127 | * parameters: 128 | * - name: id 129 | * in: path 130 | * required: true 131 | * description: User's ID to update 132 | * type: string 133 | * - name: body 134 | * description: User's Entity 135 | * in: body 136 | * required: true 137 | * type: string 138 | * schema: 139 | * $ref: '#/definitions/user' 140 | * responses: 141 | * 200: 142 | * description: Successfully Updated 143 | * schema: 144 | * $ref: '#/definitions/user' 145 | * 401: 146 | * $ref: '#/responses/Unauthorized' 147 | * 400: 148 | * $ref: '#/responses/BadRequest' 149 | */ 150 | router 151 | .put('/:id', (req, res) => { 152 | putUseCase 153 | .update({ id: req.params.id, body: req.body }) 154 | .then(data => { 155 | res.status(Status.OK).json(Success(data)) 156 | }) 157 | .catch((error) => { 158 | logger.error(error) // we still need to log every error for debugging 159 | res.status(Status.BAD_REQUEST).json( 160 | Fail(error.message)) 161 | }) 162 | }) 163 | 164 | /** 165 | * @swagger 166 | * /users: 167 | * delete: 168 | * tags: 169 | * - Users 170 | * description: Delete User 171 | * security: 172 | * - JWT: [] 173 | * produces: 174 | * - application/json 175 | * parameters: 176 | * - name: id 177 | * in: path 178 | * required: true 179 | * description: User's ID to delete 180 | * type: string 181 | * responses: 182 | * 200: 183 | * description: Successfully Deleted 184 | * schema: 185 | * $ref: '#/definitions/user' 186 | * 401: 187 | * $ref: '#/responses/Unauthorized' 188 | */ 189 | router 190 | .delete('/:id', (req, res) => { 191 | deleteUseCase 192 | .remove({ id: req.params.id }) 193 | .then(data => { 194 | res.status(Status.OK).json(Success(data)) 195 | }) 196 | .catch((error) => { 197 | logger.error(error) // we still need to log every error for debugging 198 | res.status(Status.BAD_REQUEST).json( 199 | Fail(error.message)) 200 | }) 201 | }) 202 | return router 203 | } 204 | -------------------------------------------------------------------------------- /src/interfaces/http/router.js: -------------------------------------------------------------------------------- 1 | const statusMonitor = require('express-status-monitor') 2 | const cors = require('cors') 3 | const bodyParser = require('body-parser') 4 | const compression = require('compression') 5 | 6 | const { Router } = require('express') 7 | const { partialRight } = require('ramda') 8 | 9 | const controller = require('./utils/create_controller') 10 | const httpLogger = require('./middlewares/http_logger') 11 | const errorHandler = require('./middlewares/error_handler') 12 | 13 | module.exports = ({ config, logger, database }) => { 14 | const router = Router() 15 | 16 | /* istanbul ignore if */ 17 | if (config.env === 'development') { 18 | router.use(statusMonitor()) 19 | } 20 | 21 | /* istanbul ignore if */ 22 | if (config.env !== 'test') { 23 | router.use(httpLogger(logger)) 24 | } 25 | 26 | const apiRouter = Router() 27 | 28 | apiRouter 29 | .use(cors({ 30 | origin: [ 31 | 'http://localhost:3000' 32 | ], 33 | methods: ['GET', 'POST', 'PUT', 'DELETE'], 34 | allowedHeaders: ['Content-Type', 'Authorization'] 35 | })) 36 | .use(bodyParser.json()) 37 | .use(compression()) 38 | 39 | /* 40 | * Add your API routes here 41 | * 42 | * You can use the `controllers` helper like this: 43 | * apiRouter.use('/users', controller(controllerPath)) 44 | * 45 | * The `controllerPath` is relative to the `interfaces/http` folder 46 | */ 47 | 48 | apiRouter.use('/', controller('index')) 49 | apiRouter.use('/token', controller('token').router) 50 | apiRouter.use('/users', controller('user').router) 51 | apiRouter.use('/companies', controller('company').router) 52 | 53 | router.use(`/api/${config.version}`, apiRouter) 54 | 55 | router.use(partialRight(errorHandler, [logger, config])) 56 | 57 | return router 58 | } 59 | -------------------------------------------------------------------------------- /src/interfaces/http/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | module.exports = ({ config, router, logger, auth, database, health }) => { 4 | const app = express() 5 | 6 | app.disable('x-powered-by') 7 | app.use(auth.initialize()) 8 | app.use(router) 9 | 10 | // we define our static folder 11 | app.use(express.static('public')) 12 | 13 | return { 14 | app, 15 | setupHealthCheck: () => health.start(app), 16 | start: () => { 17 | const http = app.listen(config.port, () => { 18 | const { port } = http.address() 19 | logger.info(`🤘 API - Port ${port}`) 20 | }) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/interfaces/http/utils/create_controller.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = function createControllerRoutes (controllerUri) { 4 | const controllerPath = path.resolve('src/interfaces/http/modules', controllerUri) 5 | const Controller = require(controllerPath) 6 | 7 | return Controller() 8 | } 9 | -------------------------------------------------------------------------------- /test/api/companies/delete_companies.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository, 4 | companyRepository 5 | } = app.resolve('repository') 6 | 7 | describe('Routes: DELETE Companies', () => { 8 | const BASE_URI = `/api/${config.version}` 9 | 10 | const signIn = app.resolve('jwt').signin() 11 | let token 12 | let companyId 13 | 14 | beforeEach((done) => { 15 | // we need to add user before we can request our token 16 | userRepository 17 | .destroy({ where: {} }) 18 | .then(() => 19 | userRepository.create({ 20 | firstName: 'Test', 21 | lastName: 'Dev', 22 | middleName: 'Super Dev', 23 | email: 'testdev1@gmail.com', 24 | password: 'pass', 25 | roleId: 1, 26 | isDeleted: 0, 27 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 28 | }) 29 | ).then((user) => { 30 | token = signIn({ 31 | id: user.id, 32 | firstName: user.firstName, 33 | lastName: user.lastName, 34 | middleName: user.middleName, 35 | email: user.email 36 | }) 37 | done() 38 | }) 39 | }) 40 | 41 | describe('Should DELETE companies', () => { 42 | beforeEach((done) => { 43 | companyRepository 44 | .destroy({ where: {} }) 45 | .then(() => 46 | companyRepository.create({ 47 | 'name': 'My Company Test', 48 | 'address': '1705 German Hollow', 49 | 'contact': '658.412.5787', 50 | 'tin': 'KZ460888270914935SZV', 51 | 'sss': 'TR6529864874412796R3T19934', 52 | 'philhealth': 'IL455238030594064057191', 53 | 'isDeleted': 0, 54 | 'createdBy': '4efda34e-5e05-483a-8e3f-ac31d20dc2a8' 55 | }) 56 | ) 57 | .then(({ id }) => { 58 | companyId = id 59 | done() 60 | }) 61 | }) 62 | 63 | it('should delete company', (done) => { 64 | request.delete(`${BASE_URI}/companies/${companyId}`) 65 | .set('Authorization', `JWT ${token}`) 66 | .expect(200) 67 | .end((err, res) => { 68 | console.log(res.body) 69 | expect(res.body.success).to.eql(true) 70 | done(err) 71 | }) 72 | }) 73 | 74 | it('should return unauthorized if no token', (done) => { 75 | request.delete(`${BASE_URI}/companies/${companyId}`) 76 | .expect(401) 77 | .end((err, res) => { 78 | expect(res.text).to.equals('Unauthorized') 79 | done(err) 80 | }) 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /test/api/companies/get_companies.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository, 4 | companyRepository 5 | } = app.resolve('repository') 6 | 7 | describe('Routes: GET Companies', () => { 8 | const BASE_URI = `/api/${config.version}` 9 | 10 | const signIn = app.resolve('jwt').signin() 11 | let token 12 | 13 | beforeEach((done) => { 14 | // we need to add user before we can request our token 15 | userRepository 16 | .destroy({ where: {} }) 17 | .then(() => 18 | userRepository.create({ 19 | firstName: 'Test', 20 | lastName: 'Dev', 21 | middleName: 'Super Dev', 22 | email: 'testdev1@gmail.com', 23 | password: 'pass', 24 | roleId: 1, 25 | isDeleted: 0, 26 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 27 | }) 28 | ).then((user) => { 29 | token = signIn({ 30 | id: user.id, 31 | firstName: user.firstName, 32 | lastName: user.lastName, 33 | middleName: user.middleName, 34 | email: user.email 35 | }) 36 | done() 37 | }) 38 | }) 39 | 40 | describe('Should return companies', () => { 41 | beforeEach((done) => { 42 | companyRepository 43 | .destroy({ where: {} }) 44 | .then(() => 45 | companyRepository.create({ 46 | 'name': 'My Company Test', 47 | 'address': '1705 German Hollow', 48 | 'contact': '658.412.5787', 49 | 'tin': 'KZ460888270914935SZV', 50 | 'sss': 'TR6529864874412796R3T19934', 51 | 'philhealth': 'IL455238030594064057191', 52 | 'isDeleted': 0, 53 | 'createdBy': '4efda34e-5e05-483a-8e3f-ac31d20dc2a8' 54 | }) 55 | ) 56 | .then(() => done()) 57 | }) 58 | 59 | it('should return all companies', (done) => { 60 | request.get(`${BASE_URI}/companies`) 61 | .set('Authorization', `JWT ${token}`) 62 | .expect(200) 63 | .end((err, res) => { 64 | expect(res.body.data).to.have.length(1) 65 | done(err) 66 | }) 67 | }) 68 | 69 | it('should return unauthorized if no token', (done) => { 70 | request.get(`${BASE_URI}/companies`) 71 | .expect(401) 72 | .end((err, res) => { 73 | expect(res.text).to.equals('Unauthorized') 74 | done(err) 75 | }) 76 | }) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /test/api/companies/post_companies.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository, 4 | companyRepository 5 | } = app.resolve('repository') 6 | 7 | describe('Routes: POST Companies', () => { 8 | const BASE_URI = `/api/${config.version}` 9 | 10 | const signIn = app.resolve('jwt').signin() 11 | let token 12 | 13 | beforeEach((done) => { 14 | // we need to add user before we can request our token 15 | // we need to add user before we can request our token 16 | userRepository 17 | .destroy({ where: {} }) 18 | .then(() => 19 | userRepository.create({ 20 | firstName: 'Test', 21 | lastName: 'Dev', 22 | middleName: 'Super Dev', 23 | email: 'testdev1@gmail.com', 24 | password: 'pass', 25 | roleId: 1, 26 | isDeleted: 0, 27 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 28 | }) 29 | ).then((user) => { 30 | token = signIn({ 31 | id: user.id, 32 | firstName: user.firstName, 33 | lastName: user.lastName, 34 | middleName: user.middleName, 35 | email: user.email 36 | }) 37 | done() 38 | }) 39 | }) 40 | 41 | describe('Should post companies', () => { 42 | beforeEach((done) => { 43 | companyRepository 44 | .destroy({ where: {} }) 45 | .then(() => done()) 46 | }) 47 | 48 | it('should return create company', (done) => { 49 | request.post(`${BASE_URI}/companies`) 50 | .set('Authorization', `JWT ${token}`) 51 | .send({ 52 | 'name': 'My Company Test', 53 | 'address': '1705 German Hollow', 54 | 'contact': '658.412.5787', 55 | 'tin': 'KZ460888270914935SZV', 56 | 'sss': 'TR6529864874412796R3T19934', 57 | 'philhealth': 'IL455238030594064057191', 58 | 'isDeleted': 0, 59 | 'createdBy': '4efda34e-5e05-483a-8e3f-ac31d20dc2a8' 60 | }) 61 | .expect(200) 62 | .end((err, res) => { 63 | expect(res.body.data.name).to.eql('My Company Test') 64 | expect(res.body.data.address).to.eql('1705 German Hollow') 65 | expect(res.body.data.contact).to.eql('658.412.5787') 66 | done(err) 67 | }) 68 | }) 69 | 70 | it('should validate companies object is not complete', (done) => { 71 | request.post(`${BASE_URI}/companies`) 72 | .set('Authorization', `JWT ${token}`) 73 | .send({ 74 | 'name': 'My Company Test', 75 | 'address': '1705 German Hollow', 76 | 'contact': '658.412.5787' 77 | }) 78 | .expect(400) 79 | .end((err, res) => { 80 | expect(res.body).to.include.keys('error') 81 | done(err) 82 | }) 83 | }) 84 | 85 | it('should return unauthorized if no token', (done) => { 86 | request.post(`${BASE_URI}/companies`) 87 | .expect(401) 88 | .end((err, res) => { 89 | expect(res.text).to.equals('Unauthorized') 90 | done(err) 91 | }) 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /test/api/companies/put_companies.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository, 4 | companyRepository 5 | } = app.resolve('repository') 6 | 7 | describe('Routes: PUT Companies', () => { 8 | const BASE_URI = `/api/${config.version}` 9 | 10 | const signIn = app.resolve('jwt').signin() 11 | let token 12 | let companyId 13 | 14 | beforeEach((done) => { 15 | // we need to add user before we can request our token 16 | userRepository 17 | .destroy({ where: {} }) 18 | .then(() => 19 | userRepository.create({ 20 | firstName: 'Test', 21 | lastName: 'Dev', 22 | middleName: 'Super Dev', 23 | email: 'testdev1@gmail.com', 24 | password: 'pass', 25 | roleId: 1, 26 | isDeleted: 0, 27 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 28 | }) 29 | ).then((user) => { 30 | token = signIn({ 31 | id: user.id, 32 | firstName: user.firstName, 33 | lastName: user.lastName, 34 | middleName: user.middleName, 35 | email: user.email 36 | }) 37 | done() 38 | }) 39 | }) 40 | 41 | describe('Should PUT companies', () => { 42 | beforeEach((done) => { 43 | companyRepository 44 | .destroy({ where: {} }) 45 | .then(() => 46 | companyRepository.create({ 47 | 'name': 'My Company Test', 48 | 'address': '1705 German Hollow', 49 | 'contact': '658.412.5787', 50 | 'tin': 'KZ460888270914935SZV', 51 | 'sss': 'TR6529864874412796R3T19934', 52 | 'philhealth': 'IL455238030594064057191', 53 | 'isDeleted': 0, 54 | 'createdBy': '4efda34e-5e05-483a-8e3f-ac31d20dc2a8' 55 | }) 56 | ) 57 | .then(({ id }) => { 58 | companyId = id 59 | done() 60 | }) 61 | }) 62 | 63 | it('should update company', (done) => { 64 | request.put(`${BASE_URI}/companies/${companyId}`) 65 | .set('Authorization', `JWT ${token}`) 66 | .send({ 67 | 'name': 'Test company', 68 | 'address': 'Test Address', 69 | 'contact': '123456789', 70 | 'tin': 'KZ460888270914935SZV', 71 | 'sss': 'TR6529864874412796R3T19934', 72 | 'philhealth': 'IL455238030594064057191', 73 | 'isDeleted': 0, 74 | 'createdBy': '4efda34e-5e05-483a-8e3f-ac31d20dc2a8' 75 | }) 76 | .expect(200) 77 | .end((err, res) => { 78 | expect(res.body.data.name).to.eql('Test company') 79 | expect(res.body.data.address).to.eql('Test Address') 80 | expect(res.body.data.contact).to.eql('123456789') 81 | done(err) 82 | }) 83 | }) 84 | 85 | it('should validate user object is not complete', (done) => { 86 | request.put(`${BASE_URI}/companies/${companyId}`) 87 | .set('Authorization', `JWT ${token}`) 88 | .send({ 89 | 'name': 'Test company', 90 | 'address': 'Test Address', 91 | 'contact': '123456789' 92 | }) 93 | .expect(400) 94 | .end((err, res) => { 95 | expect(res.body).to.include.keys('error') 96 | done(err) 97 | }) 98 | }) 99 | 100 | it('should return unauthorized if no token', (done) => { 101 | request.put(`${BASE_URI}/companies/${companyId}`) 102 | .expect(401) 103 | .end((err, res) => { 104 | expect(res.text).to.equals('Unauthorized') 105 | done(err) 106 | }) 107 | }) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /test/api/index.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | describe('Routes: Index', () => { 3 | const BASE_URI = `/api/${config.version}` 4 | 5 | describe('GET /', () => { 6 | it('returns the API status', done => { 7 | request.get(`${BASE_URI}/`) 8 | .expect(200) 9 | .end((err, res) => { 10 | const expected = { status: 'API working' } 11 | expect(res.body).to.eql(expected) 12 | done(err) 13 | }) 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/api/token.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository 4 | } = app.resolve('repository') 5 | 6 | describe('Routes: Login', () => { 7 | const BASE_URI = `/api/${config.version}` 8 | 9 | beforeEach((done) => { 10 | // we need to add user before we can request our token 11 | userRepository 12 | .destroy({ where: {} }) 13 | .then(() => 14 | userRepository.create({ 15 | firstName: 'Test', 16 | lastName: 'Dev', 17 | middleName: 'Super Dev', 18 | email: 'testdev1@gmail.com', 19 | password: 'pass', 20 | roleId: 1, 21 | isDeleted: 0, 22 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 23 | }) 24 | ).then(() => done()) 25 | }) 26 | 27 | describe('POST Token', () => { 28 | it('should retrieved token', done => { 29 | request.post(`${BASE_URI}/token`) 30 | .send({ 31 | email: 'testdev1@gmail.com', 32 | password: 'pass' 33 | }) 34 | .expect(200) 35 | .end((err, res) => { 36 | expect(res.body).to.include.keys('data') 37 | expect(res.body.data).to.include.keys('token') 38 | done(err) 39 | }) 40 | }) 41 | 42 | it('should throw error if email not existing', done => { 43 | request.post(`${BASE_URI}/token`) 44 | .send({ 45 | email: 'testdev1234@gmail.com', 46 | password: 'pass' 47 | }) 48 | .expect(400) 49 | .end((err, res) => { 50 | expect(res.body).to.include.keys('error') 51 | done(err) 52 | }) 53 | }) 54 | 55 | it('should throw error if password incorrect', done => { 56 | request.post(`${BASE_URI}/token`) 57 | .send({ 58 | email: 'testdev1@gmail.com', 59 | password: 'pass123' 60 | }) 61 | .expect(400) 62 | .end((err, res) => { 63 | expect(res.body).to.include.keys('error') 64 | expect(res.body.error).to.equal('Invalid Credentials') 65 | done(err) 66 | }) 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/api/users/delete_users.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository 4 | } = app.resolve('repository') 5 | 6 | describe('Routes: DELETE Users', () => { 7 | const BASE_URI = `/api/${config.version}` 8 | 9 | const signIn = app.resolve('jwt').signin() 10 | let token 11 | let userId 12 | 13 | beforeEach((done) => { 14 | // we need to add user before we can request our token 15 | userRepository 16 | .destroy({ where: {} }) 17 | .then(() => 18 | userRepository.create({ 19 | firstName: 'Test', 20 | lastName: 'Dev', 21 | middleName: 'Super Dev', 22 | email: 'testdev1@gmail.com', 23 | password: 'pass', 24 | roleId: 1, 25 | isDeleted: 0, 26 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 27 | }) 28 | ).then((user) => { 29 | userId = user.id 30 | token = signIn({ 31 | id: user.id, 32 | firstName: user.firstName, 33 | lastName: user.lastName, 34 | middleName: user.middleName, 35 | email: user.email 36 | }) 37 | done() 38 | }) 39 | }) 40 | 41 | describe('Should DELETE users', () => { 42 | it('should delete user', (done) => { 43 | request.delete(`${BASE_URI}/users/${userId}`) 44 | .set('Authorization', `JWT ${token}`) 45 | .expect(200) 46 | .end((err, res) => { 47 | expect(res.body.success).to.eql(true) 48 | 49 | done(err) 50 | }) 51 | }) 52 | 53 | it('should return unauthorized if no token', (done) => { 54 | request.delete(`${BASE_URI}/users/${userId}`) 55 | .expect(401) 56 | .end((err, res) => { 57 | expect(res.text).to.equals('Unauthorized') 58 | done(err) 59 | }) 60 | }) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/api/users/get_users.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const { 4 | userRepository 5 | } = app.resolve('repository') 6 | 7 | describe('Routes: GET UsersEntity', () => { 8 | const BASE_URI = `/api/${config.version}` 9 | 10 | const signIn = app.resolve('jwt').signin() 11 | let token 12 | 13 | beforeEach((done) => { 14 | // we need to add user before we can request our token 15 | userRepository 16 | .destroy({ where: {} }) 17 | .then(() => 18 | userRepository.create({ 19 | firstName: 'Test', 20 | lastName: 'Dev', 21 | middleName: 'Super Dev', 22 | email: 'testdev1@gmail.com', 23 | password: 'pass', 24 | roleId: 1, 25 | isDeleted: 0, 26 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 27 | }) 28 | ).then(() => 29 | userRepository.create({ 30 | firstName: 'John', 31 | lastName: 'doe', 32 | middleName: 'JohnDoe', 33 | email: 'superjohndoe@gmail.com', 34 | password: 'pass', 35 | roleId: 1, 36 | isDeleted: 0, 37 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 38 | }) 39 | ).then((user) => { 40 | token = signIn({ 41 | id: user.id, 42 | firstName: user.firstName, 43 | lastName: user.lastName, 44 | middleName: user.middleName, 45 | email: user.email 46 | }) 47 | done() 48 | }) 49 | }) 50 | 51 | describe('Should return users', () => { 52 | it('should return all users', (done) => { 53 | request.get(`${BASE_URI}/users`) 54 | .set('Authorization', `JWT ${token}`) 55 | .expect(200) 56 | .end((err, res) => { 57 | expect(res.body.data).to.have.length(2) 58 | done(err) 59 | }) 60 | }) 61 | 62 | it('should return unauthorized if no token', (done) => { 63 | request.get(`${BASE_URI}/users`) 64 | .expect(401) 65 | .end((err, res) => { 66 | expect(res.text).to.equals('Unauthorized') 67 | done(err) 68 | }) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/api/users/post_users.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const { 3 | userRepository 4 | } = app.resolve('repository') 5 | 6 | describe('Routes: POST Users', () => { 7 | const BASE_URI = `/api/${config.version}` 8 | 9 | const signIn = app.resolve('jwt').signin() 10 | let token 11 | 12 | beforeEach((done) => { 13 | // we need to add user before we can request our token 14 | userRepository 15 | .destroy({ where: {} }) 16 | .then(() => 17 | userRepository.create({ 18 | firstName: 'Test', 19 | lastName: 'Dev', 20 | middleName: 'Super Dev', 21 | email: 'testdev1@gmail.com', 22 | password: 'pass', 23 | roleId: 1, 24 | isDeleted: 0, 25 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 26 | }) 27 | ).then((user) => { 28 | token = signIn({ 29 | id: user.id, 30 | firstName: user.firstName, 31 | lastName: user.lastName, 32 | middleName: user.middleName, 33 | email: user.email 34 | }) 35 | done() 36 | }) 37 | }) 38 | 39 | describe('Should post users', () => { 40 | it('should return create user', (done) => { 41 | request.post(`${BASE_URI}/users`) 42 | .set('Authorization', `JWT ${token}`) 43 | .send({ 44 | firstName: 'John', 45 | lastName: 'Doe', 46 | middleName: 'JohnDoe', 47 | email: 'johndoe@mgail.com.com', 48 | roleId: 1, 49 | isDeleted: 0, 50 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 51 | }) 52 | .expect(200) 53 | .end((err, res) => { 54 | expect(res.body.data.firstName).to.eql('John') 55 | expect(res.body.data.lastName).to.eql('Doe') 56 | expect(res.body.data.email).to.eql('johndoe@mgail.com.com') 57 | done(err) 58 | }) 59 | }) 60 | 61 | it('should validate user object is not complete', (done) => { 62 | request.post(`${BASE_URI}/users`) 63 | .set('Authorization', `JWT ${token}`) 64 | .send({ 65 | firstName: 'John', 66 | lastName: 'Doe', 67 | middleName: 'JohnDoe' 68 | }) 69 | .expect(400) 70 | .end((err, res) => { 71 | expect(res.body).to.include.keys('error') 72 | done(err) 73 | }) 74 | }) 75 | 76 | it('should return unauthorized if no token', (done) => { 77 | request.post(`${BASE_URI}/users`) 78 | .expect(401) 79 | .end((err, res) => { 80 | expect(res.text).to.equals('Unauthorized') 81 | done(err) 82 | }) 83 | }) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /test/api/users/put_users.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const { 4 | userRepository 5 | } = app.resolve('repository') 6 | 7 | describe('Routes: PUT Users', () => { 8 | const BASE_URI = `/api/${config.version}` 9 | 10 | const signIn = app.resolve('jwt').signin() 11 | let token 12 | let userId 13 | 14 | beforeEach((done) => { 15 | // we need to add user before we can request our token 16 | userRepository 17 | .destroy({ where: {} }) 18 | .then(() => 19 | userRepository.create({ 20 | firstName: 'Test', 21 | lastName: 'Dev', 22 | middleName: 'Super Dev', 23 | email: 'testdev1@gmail.com', 24 | password: 'pass', 25 | roleId: 1, 26 | isDeleted: 0, 27 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 28 | }) 29 | ).then((user) => { 30 | userId = user.id 31 | token = signIn({ 32 | id: user.id, 33 | firstName: user.firstName, 34 | lastName: user.lastName, 35 | middleName: user.middleName, 36 | email: user.email 37 | }) 38 | done() 39 | }) 40 | }) 41 | 42 | describe('Should PUT users', () => { 43 | it('should update user', (done) => { 44 | request.put(`${BASE_URI}/users/${userId}`) 45 | .set('Authorization', `JWT ${token}`) 46 | .send({ 47 | firstName: 'John', 48 | lastName: 'Doe', 49 | middleName: 'JohnDoe', 50 | email: 'testdev1@gmail.com', 51 | password: 'pass', 52 | roleId: 1, 53 | isDeleted: 0, 54 | createdBy: '48e40a9c-c5e9-4d63-9aba-b77cdf4ca67b' 55 | }) 56 | .expect(200) 57 | .end((err, res) => { 58 | expect(res.body.data.firstName).to.eql('John') 59 | expect(res.body.data.lastName).to.eql('Doe') 60 | expect(res.body.data.middleName).to.eql('JohnDoe') 61 | done(err) 62 | }) 63 | }) 64 | 65 | it('should validate user object is not complete', (done) => { 66 | request.put(`${BASE_URI}/users/${userId}`) 67 | .set('Authorization', `JWT ${token}`) 68 | .send({ 69 | firstName: 'John', 70 | lastName: 'Doe', 71 | middleName: 'JohnDoe' 72 | }) 73 | .expect(400) 74 | .end((err, res) => { 75 | expect(res.body).to.include.keys('error') 76 | done(err) 77 | }) 78 | }) 79 | 80 | it('should return unauthorized if no token', (done) => { 81 | request.put(`${BASE_URI}/users/${userId}`) 82 | .expect(401) 83 | .end((err, res) => { 84 | expect(res.text).to.equals('Unauthorized') 85 | done(err) 86 | }) 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /test/factory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We need to find a way to user the repositories for our test that it we will have minimum impact once we change our ORM or our DATABASE 3 | */ 4 | const { curry } = require('ramda') 5 | 6 | // we will call each repo on thier test cases here we will just compose the items. 7 | 8 | const models = (name) => app.resolve('database').models[name] 9 | 10 | const repository = curry((repo, model) => { 11 | return repo(model) 12 | }) 13 | 14 | module.exports = { 15 | models, 16 | repository 17 | } 18 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | test/setup.js 2 | test/**/*.spec.js 3 | --recursive 4 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest') 2 | const chai = require('chai') 3 | const container = require('src/container') 4 | const server = container.resolve('server') 5 | const config = container.resolve('config') 6 | const logger = container.resolve('logger') 7 | 8 | /** 9 | * turn off logger since we are testing on winston 10 | */ 11 | logger.transports.forEach((t) => (t.silent = true)) 12 | 13 | global.expect = chai.expect 14 | global.app = container 15 | global.request = request(server.app) 16 | global.config = config 17 | -------------------------------------------------------------------------------- /test/unit/app/company/delete.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect, use } = require('chai') 3 | const sinon = require('sinon') 4 | const sinonChai = require('sinon-chai') 5 | 6 | const deleteUsecase = require('src/app/company/delete') 7 | 8 | use(sinonChai) 9 | 10 | describe('App -> Company -> Delete', () => { 11 | let useCase 12 | let method 13 | 14 | describe('Success path', () => { 15 | beforeEach(() => { 16 | const MockRepository = { 17 | update: () => {} 18 | } 19 | 20 | method = sinon.spy(MockRepository, 'update') 21 | useCase = deleteUsecase({ 22 | companyRepository: MockRepository 23 | }) 24 | }) 25 | 26 | it('should have called delete method of companyRepository', async () => { 27 | await useCase.remove({ id: 1 }) 28 | // eslint-disable-next-line 29 | expect(method).to.have.been.called 30 | }) 31 | }) 32 | 33 | describe('Fail path', () => { 34 | beforeEach(() => { 35 | const MockRepository = { 36 | // eslint-disable-next-line prefer-promise-reject-errors 37 | update: () => Promise.reject('Error') 38 | } 39 | 40 | useCase = deleteUsecase({ 41 | companyRepository: MockRepository 42 | }) 43 | }) 44 | 45 | it('should display error on rejection', async () => { 46 | let error 47 | try { 48 | await useCase.remove({ id: 1 }) 49 | } catch (e) { 50 | error = e.message 51 | } 52 | expect(error).to.equal('Error') 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/unit/app/company/get.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect } = require('chai') 3 | const getUsecase = require('src/app/company/get') 4 | 5 | describe('App -> Company -> Get', () => { 6 | let useCase 7 | const mockData = [{ 8 | company: 'Test' 9 | }] 10 | 11 | describe('Success path', () => { 12 | beforeEach(() => { 13 | const MockRepository = { 14 | getAll: () => mockData 15 | } 16 | 17 | useCase = getUsecase({ 18 | companyRepository: MockRepository 19 | }) 20 | }) 21 | 22 | it('should display all the records on success', async () => { 23 | const lists = await useCase.all() 24 | expect(lists).to.equal(mockData) 25 | }) 26 | }) 27 | 28 | describe('Fail path', () => { 29 | beforeEach(() => { 30 | const MockRepository = { 31 | // eslint-disable-next-line prefer-promise-reject-errors 32 | getAll: () => Promise.reject('Error') 33 | } 34 | 35 | useCase = getUsecase({ 36 | companyRepository: MockRepository 37 | }) 38 | }) 39 | 40 | it('should display error on rejection', async () => { 41 | let error 42 | try { 43 | await useCase.all() 44 | } catch (e) { 45 | error = e.message 46 | } 47 | expect(error).to.equal('Error') 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/unit/app/company/post.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect } = require('chai') 3 | const postUsecase = require('src/app/company/post') 4 | 5 | describe('App -> Company -> Post', () => { 6 | const body = { 7 | name: 'Test comp1', 8 | address: 'address1', 9 | contact: '11234456', 10 | tin: '123-3123-123134', 11 | sss: '23-12334444-9', 12 | philhealth: '1233-21321-3312', 13 | isDeleted: 0, 14 | createdBy: '1231' 15 | } 16 | let useCase 17 | 18 | describe('Success path', () => { 19 | beforeEach(() => { 20 | const MockRepository = { 21 | create: (data) => data 22 | } 23 | 24 | useCase = postUsecase({ 25 | companyRepository: MockRepository 26 | }) 27 | }) 28 | 29 | it('should create the record', async () => { 30 | const lists = await useCase.create({ body }) 31 | expect(lists.name).to.equal(body.name) 32 | expect(lists.address).to.equal(body.address) 33 | expect(lists.contact).to.equal(body.contact) 34 | }) 35 | }) 36 | 37 | describe('Fail path', () => { 38 | beforeEach(() => { 39 | const MockRepository = { 40 | // eslint-disable-next-line prefer-promise-reject-errors 41 | create: () => Promise.reject('Error') 42 | } 43 | 44 | useCase = postUsecase({ 45 | companyRepository: MockRepository 46 | }) 47 | }) 48 | 49 | it('should display error on rejection', async () => { 50 | let error 51 | try { 52 | await useCase.create({ body }) 53 | } catch (e) { 54 | error = e.message 55 | } 56 | expect(error).to.equal('Error') 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /test/unit/app/company/put.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect, use } = require('chai') 3 | const sinon = require('sinon') 4 | const sinonChai = require('sinon-chai') 5 | 6 | const updateUsecase = require('src/app/company/put') 7 | 8 | use(sinonChai) 9 | 10 | describe('App -> Company -> Put', () => { 11 | const body = { 12 | name: 'Test comp1', 13 | address: 'address1', 14 | contact: '11234456', 15 | tin: '123-3123-123134', 16 | sss: '23-12334444-9', 17 | philhealth: '1233-21321-3312', 18 | isDeleted: 0, 19 | createdBy: '1231' 20 | } 21 | let useCase 22 | let method 23 | 24 | describe('Success path', () => { 25 | beforeEach(() => { 26 | const MockRepository = { 27 | update: (data) => data 28 | } 29 | 30 | method = sinon.spy(MockRepository, 'update') 31 | useCase = updateUsecase({ 32 | companyRepository: MockRepository 33 | }) 34 | }) 35 | 36 | it('should have called delete method of companyRepository', async () => { 37 | await useCase.update({ id: 1, body }) 38 | // eslint-disable-next-line 39 | expect(method).to.have.been.called 40 | }) 41 | }) 42 | 43 | describe('Fail path', () => { 44 | beforeEach(() => { 45 | const MockRepository = { 46 | // eslint-disable-next-line prefer-promise-reject-errors 47 | update: () => Promise.reject('Error') 48 | } 49 | 50 | useCase = updateUsecase({ 51 | companyRepository: MockRepository 52 | }) 53 | }) 54 | 55 | it('should display error on rejection', async () => { 56 | let error 57 | try { 58 | await useCase.update({ id: 1, body }) 59 | } catch (e) { 60 | error = e 61 | } 62 | expect(error).to.equal('Error') 63 | }) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /test/unit/app/index.spec.js: -------------------------------------------------------------------------------- 1 | const { expect, use } = require('chai') 2 | const sinon = require('sinon') 3 | const sinonChai = require('sinon-chai') 4 | 5 | const app = require('src/app/') 6 | 7 | use(sinonChai) 8 | 9 | describe('App -> index', () => { 10 | let dbAuthenticate 11 | let setupHealthCheck 12 | let serverStart 13 | 14 | const mockDatabase = { 15 | authenticate: () => Promise.resolve(true) 16 | } 17 | const mockServer = { 18 | setupHealthCheck: () => Promise.resolve(true), 19 | start: () => Promise.resolve(true) 20 | } 21 | 22 | const instance = app({ 23 | database: mockDatabase, 24 | server: mockServer 25 | }) 26 | 27 | beforeEach(() => { 28 | dbAuthenticate = sinon.spy(mockDatabase, 'authenticate') 29 | setupHealthCheck = sinon.spy(mockServer, 'setupHealthCheck') 30 | serverStart = sinon.spy(mockServer, 'start') 31 | }) 32 | 33 | afterEach(() => { 34 | sinon.restore() 35 | }) 36 | 37 | describe('Success path', () => { 38 | it('server should start sucessfully', async () => { 39 | await instance.start() 40 | // eslint-disable-next-line 41 | expect(dbAuthenticate).to.have.been.called 42 | // eslint-disable-next-line 43 | expect(setupHealthCheck).to.have.been.called 44 | // eslint-disable-next-line 45 | expect(serverStart).to.have.been.called 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/unit/app/user/delete.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect, use } = require('chai') 3 | const sinon = require('sinon') 4 | const sinonChai = require('sinon-chai') 5 | 6 | const deleteUsecase = require('src/app/user/delete') 7 | 8 | use(sinonChai) 9 | 10 | describe('App -> User -> Delete', () => { 11 | let useCase 12 | let method 13 | 14 | describe('Success path', () => { 15 | beforeEach(() => { 16 | const MockRepository = { 17 | update: () => {} 18 | } 19 | 20 | method = sinon.spy(MockRepository, 'update') 21 | useCase = deleteUsecase({ 22 | userRepository: MockRepository 23 | }) 24 | }) 25 | 26 | it('should have called delete method of userRepository', async () => { 27 | await useCase.remove({ id: 1 }) 28 | // eslint-disable-next-line 29 | expect(method).to.have.been.called 30 | }) 31 | }) 32 | 33 | describe('Fail path', () => { 34 | beforeEach(() => { 35 | const MockRepository = { 36 | // eslint-disable-next-line prefer-promise-reject-errors 37 | update: () => Promise.reject('Error') 38 | } 39 | 40 | useCase = deleteUsecase({ 41 | userRepository: MockRepository 42 | }) 43 | }) 44 | 45 | it('should display error on rejection', async () => { 46 | let error 47 | try { 48 | await useCase.remove({ id: 1 }) 49 | } catch (e) { 50 | error = e.message 51 | } 52 | expect(error).to.equal('Error') 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/unit/app/user/get.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect } = require('chai') 3 | const getUsecase = require('src/app/user/get') 4 | 5 | describe('App -> User -> Get', () => { 6 | let useCase 7 | const mockData = [{ 8 | firstName: 'Test', 9 | lastName: 'Developer' 10 | }] 11 | 12 | describe('Success path', () => { 13 | beforeEach(() => { 14 | const MockRepository = { 15 | getAll: () => mockData 16 | } 17 | 18 | useCase = getUsecase({ 19 | userRepository: MockRepository 20 | }) 21 | }) 22 | 23 | it('should display all the records on success', async () => { 24 | const lists = await useCase.all() 25 | expect(lists).to.equal(mockData) 26 | }) 27 | }) 28 | 29 | describe('Fail path', () => { 30 | beforeEach(() => { 31 | const MockRepository = { 32 | // eslint-disable-next-line prefer-promise-reject-errors 33 | getAll: () => Promise.reject('Error') 34 | } 35 | 36 | useCase = getUsecase({ 37 | userRepository: MockRepository 38 | }) 39 | }) 40 | 41 | it('should display error on rejection', async () => { 42 | let error 43 | try { 44 | await useCase.all() 45 | } catch (e) { 46 | error = e.message 47 | } 48 | expect(error).to.equal('Error') 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/unit/app/user/post.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect } = require('chai') 3 | const postUsecase = require('src/app/user/post') 4 | 5 | describe('App -> User -> Post', () => { 6 | let useCase 7 | 8 | describe('Success path', () => { 9 | beforeEach(() => { 10 | const MockRepository = { 11 | create: (data) => data 12 | } 13 | 14 | useCase = postUsecase({ 15 | userRepository: MockRepository 16 | }) 17 | }) 18 | 19 | it('should create the records and list the data and append the default password', async () => { 20 | const body = { 21 | firstName: 'test', 22 | lastName: 'dev', 23 | middleName: 'test', 24 | email: 'test@gmail.com', 25 | roleId: 1, 26 | isDeleted: 0, 27 | createdBy: '123' 28 | } 29 | const lists = await useCase.create({ body }) 30 | expect(lists.firstName).to.equal(body.firstName) 31 | expect(lists.lastName).to.equal(body.lastName) 32 | expect(lists.middleName).to.equal(body.middleName) 33 | }) 34 | }) 35 | 36 | describe('Fail path', () => { 37 | const body = { 38 | firstName: 'test', 39 | lastName: 'dev', 40 | middleName: 'test', 41 | email: 'test@gmail.com', 42 | roleId: 1, 43 | isDeleted: 0, 44 | createdBy: '123' 45 | } 46 | 47 | beforeEach(() => { 48 | const MockRepository = { 49 | // eslint-disable-next-line prefer-promise-reject-errors 50 | create: () => Promise.reject('Error') 51 | } 52 | 53 | useCase = postUsecase({ 54 | userRepository: MockRepository 55 | }) 56 | }) 57 | 58 | it('should display error on rejection', async () => { 59 | let error 60 | try { 61 | await useCase.create({ body }) 62 | } catch (e) { 63 | error = e.message 64 | } 65 | expect(error).to.equal('Error') 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/unit/app/user/put.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const { expect, use } = require('chai') 3 | const sinon = require('sinon') 4 | const sinonChai = require('sinon-chai') 5 | 6 | const updateUsecase = require('src/app/user/put') 7 | 8 | use(sinonChai) 9 | 10 | describe('App -> User -> Put', () => { 11 | const body = { 12 | firstName: 'test', 13 | lastName: 'dev', 14 | middleName: 'test', 15 | email: 'test@gmail.com', 16 | roleId: 1, 17 | isDeleted: 0, 18 | createdBy: '123' 19 | } 20 | let useCase 21 | let method 22 | 23 | describe('Success path', () => { 24 | beforeEach(() => { 25 | const MockRepository = { 26 | update: (data) => data 27 | } 28 | 29 | method = sinon.spy(MockRepository, 'update') 30 | useCase = updateUsecase({ 31 | userRepository: MockRepository 32 | }) 33 | }) 34 | 35 | it('should have called delete method of userRepository', async () => { 36 | await useCase.update({ id: 1, body }) 37 | // eslint-disable-next-line 38 | expect(method).to.have.been.called 39 | }) 40 | }) 41 | 42 | describe('Fail path', () => { 43 | beforeEach(() => { 44 | const MockRepository = { 45 | // eslint-disable-next-line prefer-promise-reject-errors 46 | update: () => Promise.reject('Error') 47 | } 48 | 49 | useCase = updateUsecase({ 50 | userRepository: MockRepository 51 | }) 52 | }) 53 | 54 | it('should display error on rejection', async () => { 55 | let error 56 | try { 57 | await useCase.update({ id: 1, body }) 58 | } catch (e) { 59 | error = e 60 | } 61 | expect(error).to.equal('Error') 62 | }) 63 | }) 64 | }) 65 | --------------------------------------------------------------------------------