├── .env.example ├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── application ├── CreateUser.js ├── DeleteUser.js ├── GetUser.js ├── ListUsers.js └── UpdateUser.js ├── architecture.jpg ├── coverage ├── base.css ├── block-navigation.js ├── favicon.png ├── index.html ├── node-clean-architecture │ ├── application │ │ ├── CreateUser.js.html │ │ ├── DeleteUser.js.html │ │ ├── GetUser.js.html │ │ ├── ListUsers.js.html │ │ ├── UpdateUser.js.html │ │ └── index.html │ ├── domain │ │ ├── User.js.html │ │ └── index.html │ ├── index.html │ ├── index.js.html │ ├── infrastructure │ │ ├── config │ │ │ ├── container.js.html │ │ │ └── index.html │ │ ├── repositories │ │ │ ├── UserRepositoryInMemory.js.html │ │ │ └── index.html │ │ └── webserver │ │ │ ├── index.html │ │ │ └── server.js.html │ └── ports │ │ └── http │ │ ├── UsersController.js.html │ │ ├── errors.js.html │ │ ├── index.html │ │ └── routes.js.html ├── prettify.css ├── prettify.js ├── sort-arrow-sprite.png └── sorter.js ├── docker-compose.yml ├── docs ├── index.html └── openapi.json ├── domain └── User.js ├── index.js ├── infrastructure ├── bootstrap.js ├── bootstrap │ ├── container.js │ ├── database.js │ └── logger.js ├── database │ ├── mongo.js │ └── schemas │ │ └── User.js ├── repositories │ ├── UserRepositoryInMemory.js │ └── UserRepositoryMongo.js └── webserver │ ├── errors.js │ └── fastify.js ├── jest.config.js ├── mongo-init.js ├── package.json ├── ports └── http │ ├── UsersController.js │ └── routes.js ├── preview.png ├── tests ├── integration │ ├── users.integration.test.js │ └── users.test.js └── setup.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | DB_DRIVER=mongo 2 | DB_HOST=localhost 3 | DB_PORT=27017 4 | DB_DATABASE=users 5 | DB_USER=myuser 6 | DB_PASS=mypass 7 | 8 | LOG_LEVEL=info 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es2021": true, 6 | "jest": true 7 | }, 8 | "extends": [ 9 | "standard" 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": 12 13 | }, 14 | "rules": { 15 | }, 16 | "overrides": [ 17 | { 18 | "files": "*.test.js", 19 | "rules": { 20 | "no-unused-expressions": "off" 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nyc_output/ 3 | coverage/ 4 | .env -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "conventionalCommits.scopes": [ 3 | "dependency-injection" 4 | ] 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | # Node Clean Architecture 4 | 5 |  6 | 7 | ## Table of Contents 8 | - [Node Clean Architecture](#node-clean-architecture) 9 | - [Table of Contents](#table-of-contents) 10 | - [Libs](#libs) 11 | - [Installation](#installation) 12 | - [Testing](#testing) 13 | - [Clean Architecture](#clean-architecture) 14 | - [Folder structure](#folder-structure) 15 | - [The Dependency Rule](#the-dependency-rule) 16 | - [Typical Request](#typical-request) 17 | - [Troubleshooting](#troubleshooting) 18 | - [Log `connected to MongoDB database!` doesn't appear](#log-connected-to-mongodb-database-doesnt-appear) 19 | - [I'm getting `EADDRINUSE` upon application start](#im-getting-eaddrinuse-upon-application-start) 20 | 21 | 22 | This backend implements a [RESTful](https://restfulapi.net/) CRUD interface for users and complies with Eric Evan's [DDD](https://en.wikipedia.org/wiki/Domain-driven_design) and Uncle Bob's [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) which is briefly explained here in the document. 23 | 24 | It also exposes a `/docs/` endpoint for further reference and `/coverage/` for test coverage. 25 | 26 | ## Libs 27 | * [Restify](http://restify.com/) 28 | * [Mongoose](https://mongoosejs.com/) 29 | * [Lodash FP](https://github.com/lodash/lodash/wiki/FP-Guide) Functional Programming version 30 | * [Awilix](https://github.com/jeffijoe/awilix) as Dependency Injection container 31 | * [dotenv](https://www.npmjs.com/package/dotenv) 32 | 33 | ## Installation 34 | 35 | ``` 36 | docker-compose up -d 37 | cp .env.example .env 38 | npm start 39 | ``` 40 | You should get 41 | ``` 42 | restify listening at http://[::]:8080 43 | connected to MongoDB database! 44 | ``` 45 | Access http://localhost:8080/docs/ and http://localhost:8080/coverage/ 46 | 47 | ## Testing 48 | 49 | ``` 50 | npm test 51 | ``` 52 | It uses an in-memory DB to run tests so you don't need to have mongodb up and running 53 | 54 | ## Clean Architecture 55 | 56 |  57 | 58 | ### Folder structure 59 | ``` 60 | 61 | 62 | └ application → Application services layer 63 | └ use_cases → Application business rules 64 | └ domain → Enterprise core business layer such as domain model objects (Aggregates, Entities, Value Objects) and repository interfaces 65 | └ infrastructure → Frameworks, drivers and tools such as Database, the Web Framework, mailing/logging/glue code etc. 66 | └ config → Application configuration files, modules and services 67 | └ container.js → Module that manage service implementations by environment 68 | └ database → Database ORMs middleware 69 | └ schemas → Mongoose schemas 70 | └ repositories → Implementation of domain repository interfaces 71 | └ webserver → Restify Web server configuration (server, routes, plugins, etc.) 72 | └ server.js → Restify server definition 73 | └ ports/http → Adapters and formatters for use cases and entities to external agency such as Database or the Web 74 | └ UserController.js → Restify route handlers 75 | └ routes.js → Restify route definitions 76 | └ errors.js → Standard errors for the whole application 77 | └ index.js → Main application entry point 78 | ``` 79 | 80 | ### The Dependency Rule 81 | 82 | >The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity. 83 | 84 | Extracted from https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html#the-dependency-rule 85 | 86 | ### Typical Request 87 | 88 |  89 | 90 | ## Troubleshooting 91 | 92 | ### Log `connected to MongoDB database!` doesn't appear 93 | The backend uses its own database (`users`) to run its business logic, so you need to ensure this database is created with proper user credentials. The script `mongo-init.js` is run when `docker-compose up` runs for the first time. 94 | 95 | Check in `docker-compose logs mongo` to see if something unusual is happening 96 | 97 | ### I'm getting `EADDRINUSE` upon application start 98 | You need port `8080` to be free in order to boot up the application. Check if it's already in use and shut the application down before you `npm start` again 99 | 100 |
112 | ![]() 114 | JavaScript 115 | 116 | 117 | |
118 |
119 |
120 | ![]() 122 | CSS 3 123 | 124 | 125 | |
126 |
127 |
133 | ![]() 135 | Fastify 136 | 137 | v3.9.2 138 | |
139 |
140 |
146 | ![]() 148 | Mongoose 149 | 150 | v5.10.3 151 | |
152 |
153 |
159 | ![]() 161 | Dotenv 162 | 163 | 164 | |
165 |
166 |
167 | ![]() 169 | ESLint 170 | 171 | 172 | |
173 |
174 |
175 | ![]() 177 | Git 178 | 179 | 180 | |
181 |
182 |
183 | ![]() 185 | Jest 186 | 187 | v28.1.0 188 | |
189 |
190 |
191 | ![]() 193 | Yarn 194 | 195 | 196 | |
197 |
198 |
199 | ![]() 201 | nodemon 202 | 203 | v2.0.4 204 | |
205 |
206 |
207 | ![]() 209 | npm 210 | 211 | 212 | |
213 |
214 |
220 | ![]() 222 | Shell 223 | 224 | 225 | |
226 |
227 |
228 | ![]() 230 | semantic-release 231 | 232 | 233 | |
234 |
235 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
application | 77 |
78 |
79 | |
80 | 100% | 81 |16/16 | 82 |100% | 83 |0/0 | 84 |100% | 85 |10/10 | 86 |100% | 87 |9/9 | 88 |
domain | 92 |
93 |
94 | |
95 | 100% | 96 |8/8 | 97 |0% | 98 |0/1 | 99 |100% | 100 |2/2 | 101 |100% | 102 |7/7 | 103 |
infrastructure/config | 107 |
108 |
109 | |
110 | 84.85% | 111 |28/33 | 112 |50% | 113 |4/8 | 114 |80% | 115 |4/5 | 116 |86.67% | 117 |26/30 | 118 |
infrastructure/repositories | 122 |
123 |
124 | |
125 | 100% | 126 |25/25 | 127 |100% | 128 |4/4 | 129 |100% | 130 |6/6 | 131 |100% | 132 |23/23 | 133 |
infrastructure/webserver | 137 |
138 |
139 | |
140 | 100% | 141 |24/24 | 142 |100% | 143 |0/0 | 144 |100% | 145 |4/4 | 146 |100% | 147 |24/24 | 148 |
ports/http | 152 |
153 |
154 | |
155 | 100% | 156 |23/23 | 157 |100% | 158 |0/0 | 159 |100% | 160 |7/7 | 161 |100% | 162 |21/21 | 163 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 | 1x 66 | 2x 67 | 2x 68 | 2x 69 | 70 | | module.exports = ({ UserRepository, User }) => 71 | (name, cpf, birthdate, subscription, dependents) => { 72 | const user = new User(null, name, cpf, birthdate, subscription, dependents) 73 | return UserRepository.persist(user) 74 | } 75 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 | 2x 62 | | module.exports = ({ UserRepository }) => id => UserRepository.remove(id) 63 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 | 2x 62 | | module.exports = ({ UserRepository }) => id => UserRepository.get(id) 63 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 | 2x 62 | | module.exports = ({ UserRepository }) => () => UserRepository.find() 63 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 | 1x 63 | 2x 64 | | module.exports = ({ UserRepository }) => 65 | (id, data) => UserRepository.merge(id, data) 66 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
CreateUser.js | 77 |
78 |
79 | |
80 | 100% | 81 |4/4 | 82 |100% | 83 |0/0 | 84 |100% | 85 |2/2 | 86 |100% | 87 |4/4 | 88 |
DeleteUser.js | 92 |
93 |
94 | |
95 | 100% | 96 |3/3 | 97 |100% | 98 |0/0 | 99 |100% | 100 |2/2 | 101 |100% | 102 |1/1 | 103 |
GetUser.js | 107 |
108 |
109 | |
110 | 100% | 111 |3/3 | 112 |100% | 113 |0/0 | 114 |100% | 115 |2/2 | 116 |100% | 117 |1/1 | 118 |
ListUsers.js | 122 |
123 |
124 | |
125 | 100% | 126 |3/3 | 127 |100% | 128 |0/0 | 129 |100% | 130 |2/2 | 131 |100% | 132 |1/1 | 133 |
UpdateUser.js | 137 |
138 |
139 | |
140 | 100% | 141 |3/3 | 142 |100% | 143 |0/0 | 144 |100% | 145 |2/2 | 146 |100% | 147 |2/2 | 148 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 | 2x 71 | 72 | 2x 73 | 2x 74 | 2x 75 | 2x 76 | 2x 77 | 2x 78 | 79 | 80 | | module.exports = () => class {
81 | constructor (id = null, name, cpf, birthdate, subscription, dependents) {
82 | this.id = id
83 | this.name = name
84 | this.cpf = cpf
85 | this.birthdate = birthdate
86 | this.subscription = subscription
87 | this.dependents = dependents
88 | }
89 | }
90 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
User.js | 77 |
78 |
79 | |
80 | 100% | 81 |8/8 | 82 |0% | 83 |0/1 | 84 |100% | 85 |2/2 | 86 |100% | 87 |7/7 | 88 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
index.js | 77 |
78 |
79 | |
80 | 100% | 81 |4/4 | 82 |100% | 83 |0/0 | 84 |100% | 85 |0/0 | 86 |100% | 87 |4/4 | 88 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 | 1x 67 | 68 | 1x 69 | 1x 70 | 71 | 1x 72 | | require('dotenv').config() 73 | 74 | const container = require('./infrastructure/config/container')() 75 | const server = require('./infrastructure/webserver/server') 76 | 77 | module.exports = server(container.cradle) 78 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 | 1x 104 | 105 | 1x 106 | 107 | 1x 108 | 1x 109 | 110 | 1x 111 | 112 | 113 | 114 | 1x 115 | 1x 116 | 117 | 1x 118 | 119 | 120 | 121 | 122 | 1x 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 1x 136 | 1x 137 | 138 | 139 | 140 | 141 | 142 | 1x 143 | 144 | 1x 145 | 146 | | const { createContainer, asFunction } = require('awilix') 147 | 148 | const container = createContainer() 149 | 150 | const resolveDB = ({ DB_DRIVER, NODE_ENV }) => { 151 | Eif (NODE_ENV === 'test') DB_DRIVER = 'in-memory' 152 | 153 | Eif (DB_DRIVER === 'in-memory') inMemoryDB() 154 | else if (DB_DRIVER === 'mongo') mongoDB() 155 | } 156 | 157 | const inMemoryDB = () => { 158 | const UserRepositoryInMemory = require('../repositories/UserRepositoryInMemory') 159 | 160 | container.register({ 161 | UserRepository: asFunction(UserRepositoryInMemory).singleton() 162 | }) 163 | } 164 | 165 | const mongoDB = () => { 166 | const UserRepositoryMongo = require('../repositories/UserRepositoryMongo') 167 | 168 | // Load Database and Schemas 169 | container.loadModules([ 170 | 'infrastructure/database/**/*.js' 171 | ]) 172 | 173 | container.register({ 174 | UserRepository: asFunction(UserRepositoryMongo) 175 | }) 176 | } 177 | 178 | module.exports = () => { 179 | container.loadModules([ 180 | 'ports/**/*.js', 181 | 'application/**/*.js', 182 | 'domain/**/*.js' 183 | ]) 184 | 185 | resolveDB(process.env) 186 | 187 | return container 188 | } 189 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
container.js | 77 |
78 |
79 | |
80 | 75% | 81 |15/20 | 82 |33.33% | 83 |2/6 | 84 |75% | 85 |3/4 | 86 |76.47% | 87 |13/17 | 88 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 104 | 45 105 | 46 106 | 47 | 1x 107 | 1x 108 | 1x 109 | 1x 110 | 111 | 1x 112 | 113 | 1x 114 | 1x 115 | 116 | 117 | 118 | 2x 119 | 120 | 121 | 122 | 2x 123 | 2x 124 | 125 | 1x 126 | 1x 127 | 1x 128 | 129 | 130 | 131 | 6x 132 | 6x 133 | 3x 134 | 135 | 136 | 137 | 2x 138 | 1x 139 | 140 | 1x 141 | 1x 142 | 143 | 144 | 145 | 4x 146 | 2x 147 | 148 | 2x 149 | 150 | 151 | 152 | | const filter = require('lodash/fp/filter') 153 | const first = require('lodash/fp/first') 154 | const remove = require('lodash/fp/remove') 155 | const merge = require('lodash/fp/merge') 156 | 157 | const { NotFoundError, ForbiddenError } = require('restify-errors') 158 | 159 | module.exports = () => { 160 | return { 161 | lastId: 0, 162 | db: [], 163 | find () { 164 | return this.db 165 | }, 166 | 167 | persist (user) { 168 | const duplicated = filter({ cpf: user.cpf }, this.db) 169 | if (duplicated.length > 0) throw new ForbiddenError('This CPF already exists') 170 | 171 | user.id = `${++this.lastId}` 172 | this.db.push(user) 173 | return user 174 | }, 175 | 176 | get (id) { 177 | const user = first(filter({ id }, this.db)) 178 | if (!user) throw new NotFoundError('User not found') 179 | return user 180 | }, 181 | 182 | merge (id, data) { 183 | let user = this.remove(id) 184 | user = merge(user, data) 185 | 186 | this.db.push(user) 187 | return user 188 | }, 189 | 190 | remove (id) { 191 | const user = this.get(id) 192 | this.db = remove({ id }, this.db) 193 | 194 | return user 195 | } 196 | } 197 | } 198 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
UserRepositoryInMemory.js | 77 |
78 |
79 | |
80 | 100% | 81 |25/25 | 82 |100% | 83 |4/4 | 84 |100% | 85 |6/6 | 86 |100% | 87 |23/23 | 88 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
server.js | 77 |
78 |
79 | |
80 | 100% | 81 |10/10 | 82 |100% | 83 |0/0 | 84 |100% | 85 |3/3 | 86 |100% | 87 |10/10 | 88 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 | 1x 78 | 1x 79 | 80 | 1x 81 | 1x 82 | 1x 83 | 84 | 1x 85 | 8x 86 | 87 | 88 | 1x 89 | 1x 90 | 91 | 92 | 1x 93 | 94 | | const each = require('lodash/fp/each') 95 | const restify = require('restify') 96 | 97 | module.exports = ({ routes }) => { 98 | const server = restify.createServer() 99 | server.use(restify.plugins.bodyParser()) 100 | 101 | each(route => { 102 | server[route.method](route.path, route.handler) 103 | })(routes) 104 | 105 | server.listen(8080, () => { 106 | console.log(`${server.name} listening at ${server.url}`) 107 | }) 108 | 109 | return server 110 | } 111 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 104 | 45 105 | 46 106 | 47 107 | 48 108 | 49 109 | 50 110 | 51 111 | 52 112 | 53 113 | 54 114 | 55 115 | 56 116 | 57 117 | 58 118 | 59 119 | 60 120 | 61 121 | 62 122 | 63 123 | 64 124 | 65 125 | 66 126 | 67 127 | 68 128 | 69 129 | 70 130 | 71 131 | 72 132 | 73 133 | 74 134 | 75 135 | 76 136 | 77 | 1x 137 | 138 | 1x 139 | 140 | 2x 141 | 142 | 2x 143 | 2x 144 | 2x 145 | 146 | 147 | 148 | 149 | 150 | 151 | 2x 152 | 2x 153 | 154 | 2x 155 | 2x 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 1x 164 | 165 | 1x 166 | 167 | 168 | 169 | 170 | 2x 171 | 172 | 2x 173 | 2x 174 | 1x 175 | 176 | 1x 177 | 178 | 179 | 180 | 181 | 2x 182 | 183 | 2x 184 | 2x 185 | 1x 186 | 187 | 1x 188 | 189 | 190 | 191 | 192 | 2x 193 | 194 | 2x 195 | 2x 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 2x 204 | 1x 205 | 206 | 1x 207 | 208 | 209 | 210 | 211 | 1x 212 | | const pick = require('lodash/fp/pick')
213 |
214 | const UsersController = (container) => ({
215 | listUsers: async (req, res, next) => {
216 | const { ListUsers } = container
217 |
218 | try {
219 | const users = await ListUsers()
220 | res.send(users)
221 | } catch (err) {
222 | next(err)
223 | }
224 | },
225 |
226 | createUser: async (req, res, next) => {
227 | const { CreateUser } = container
228 | const { name, cpf, birthdate, subscription, dependents } = req.body
229 |
230 | try {
231 | const user = await CreateUser(
232 | name,
233 | cpf,
234 | birthdate,
235 | subscription,
236 | dependents
237 | )
238 |
239 | res.send(201, user)
240 | } catch (err) {
241 | next(err)
242 | }
243 | },
244 |
245 | findUser: async (req, res, next) => {
246 | const { GetUser } = container
247 |
248 | try {
249 | const user = await GetUser(req.params.id)
250 | res.send(user)
251 | } catch (err) {
252 | next(err)
253 | }
254 | },
255 |
256 | deleteUser: async (req, res, next) => {
257 | const { DeleteUser } = container
258 |
259 | try {
260 | await DeleteUser(req.params.id)
261 | res.send(204, {})
262 | } catch (err) {
263 | next(err)
264 | }
265 | },
266 |
267 | updateUser: async (req, res, next) => {
268 | const { UpdateUser } = container
269 |
270 | try {
271 | const permitted = [
272 | 'name',
273 | 'cpf',
274 | 'birthdate',
275 | 'subscription',
276 | 'dependents'
277 | ]
278 |
279 | const user = await UpdateUser(req.params.id, pick(permitted, req.body))
280 | res.send(user)
281 | } catch (err) {
282 | next(err)
283 | }
284 | }
285 | })
286 |
287 | module.exports = UsersController
288 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 | 1x 67 | 68 | 69 | 70 | 71 | 1x 72 | | const errors = require('restify-errors') 73 | 74 | class NotFound extends errors.NotFoundError {} 75 | class AlreadyExists extends errors.ForbiddenError {} 76 | 77 | module.exports = () => ({ NotFound, AlreadyExists }) 78 | |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |File | 64 |65 | | Statements | 66 |67 | | Branches | 68 |69 | | Functions | 70 |71 | | Lines | 72 |73 | |
---|---|---|---|---|---|---|---|---|---|
UsersController.js | 77 |
78 |
79 | |
80 | 96.77% | 81 |30/31 | 82 |100% | 83 |0/0 | 84 |100% | 85 |6/6 | 86 |96.67% | 87 |29/30 | 88 |
errors.js | 92 |
93 |
94 | |
95 | 66.67% | 96 |2/3 | 97 |100% | 98 |0/0 | 99 |0% | 100 |0/1 | 101 |100% | 102 |2/2 | 103 |
routes.js | 107 |
108 |
109 | |
110 | 100% | 111 |3/3 | 112 |100% | 113 |0/0 | 114 |100% | 115 |1/1 | 116 |100% | 117 |2/2 | 118 |
55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |
57 |1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | 9 69 | 10 70 | 11 71 | 12 72 | 13 73 | 14 74 | 15 75 | 16 76 | 17 77 | 18 78 | 19 79 | 20 80 | 21 81 | 22 82 | 23 83 | 24 84 | 25 85 | 26 86 | 27 87 | 28 88 | 29 89 | 30 90 | 31 91 | 32 92 | 33 93 | 34 94 | 35 95 | 36 96 | 37 97 | 38 98 | 39 99 | 40 100 | 41 101 | 42 102 | 43 103 | 44 104 | 45 105 | 46 106 | 47 107 | 48 108 | 49 109 | 50 110 | 51 | 1x 111 | 112 | 1x 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | | const restify = require('restify') 161 | 162 | module.exports = ({ UsersController }) => [ 163 | { 164 | method: 'get', 165 | path: '/users', 166 | handler: UsersController.listUsers 167 | }, 168 | { 169 | method: 'post', 170 | path: '/users', 171 | handler: UsersController.createUser 172 | }, 173 | { 174 | method: 'get', 175 | path: '/users/:id', 176 | handler: UsersController.findUser 177 | }, 178 | { 179 | method: 'del', 180 | path: '/users/:id', 181 | handler: UsersController.deleteUser 182 | }, 183 | { 184 | method: 'patch', 185 | path: '/users/:id', 186 | handler: UsersController.updateUser 187 | }, 188 | { 189 | method: 'put', 190 | path: '/users/:id', 191 | handler: UsersController.updateUser 192 | }, 193 | { 194 | method: 'get', 195 | path: '/docs/*', 196 | handler: restify.plugins.serveStatic({ 197 | directory: `${__dirname}../../../`, 198 | default: 'index.html' 199 | }) 200 | }, 201 | { 202 | method: 'get', 203 | path: '/coverage/*', 204 | handler: restify.plugins.serveStatic({ 205 | directory: `${__dirname}../../../`, 206 | default: 'index.html' 207 | }) 208 | } 209 | ] 210 | |