Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"es6": true,
"mocha": true
},
"parserOptions": {
"ecmaVersion": 2017
},
"extends": "eslint:recommended",
"rules": {
"comma-spacing": ["error", { "before": false, "after": true }],
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
node_modules
config/database.js
coverage/
.nyc_output
10 changes: 0 additions & 10 deletions .istanbul.yml

This file was deleted.

13 changes: 13 additions & 0 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"cwd": "./src",
"exclude": [
"container.js",
"app/Application.js",
"interfaces/http/Server.js"
],
"reporter": [
"html",
"lcov",
"text-summary"
]
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"start": "node cluster.js",
"dev": "cross-env NODE_PATH=. NODE_ENV=development nodemon",
"test": "cross-env NODE_PATH=. NODE_ENV=test mocha",
"coverage": "cross-env NODE_PATH=. NODE_ENV=test istanbul cover node_modules/mocha/bin/_mocha",
"coverage": "cross-env NODE_PATH=. NODE_ENV=test nyc mocha",
"lint": "eslint {src,test,config}/**/*.js",
"sequelize": "cross-env NODE_PATH=. sequelize",
"console": "cross-env NODE_PATH=. node src/interfaces/console/index.js",
Expand All @@ -31,6 +31,7 @@
"cross-env": "^3.2.3",
"del": "^2.2.2",
"dotenv": "^4.0.0",
"eslint": "^4.7.2",
"express": "^4.15.2",
"express-status-monitor": "^0.1.9",
"http-status": "^1.0.1",
Expand All @@ -48,12 +49,11 @@
"chai-change": "^2.1.2",
"chance": "^1.0.6",
"dirty-chai": "^2.0.1",
"eslint": "^3.17.1",
"factory-girl": "^4.0.0",
"istanbul": "^0.4.5",
"listr": "^0.11.0",
"mocha": "^3.2.0",
"nodemon": "^1.11.0",
"nyc": "^11.2.1",
"replace-in-file": "^2.5.0",
"supertest": "^3.0.0",
"supertest-as-promised": "^4.0.2"
Expand Down
5 changes: 2 additions & 3 deletions scripts/cleanup.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ const tasks = new Listr([
return replace({
files: containerPath,
from: [
/\s*const.*app\/user'\);/,
/\s*const \{(\n.*)+.*app\/user'\);/,
/\s*const.*UsersRepository'\);/,
/\, User: UserModel/,
/\s*usersRepository.*\}\]/,
/\,\s*UserModel/,
/createUser.*\n/,
/\s*getAllUsers.*GetAllUsers/,
/\s+createUser(.|\n)+.*DeleteUser\n/,
],
to: ''
});
Expand Down
27 changes: 27 additions & 0 deletions src/app/user/DeleteUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const Operation = require('src/app/Operation');

class DeleteUser extends Operation {
constructor({ usersRepository }) {
super();
this.usersRepository = usersRepository;
}

async execute(userId) {
const { SUCCESS, ERROR, NOT_FOUND } = this.outputs;

try {
await this.usersRepository.remove(userId);
this.emit(SUCCESS);
} catch(error) {
if(error.message === 'NotFoundError') {
return this.emit(NOT_FOUND, error);
}

this.emit(ERROR, error);
}
}
}

DeleteUser.setOutputs(['SUCCESS', 'ERROR', 'NOT_FOUND']);

module.exports = DeleteUser;
26 changes: 26 additions & 0 deletions src/app/user/GetUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const Operation = require('src/app/Operation');

class GetUser extends Operation {
constructor({ usersRepository }) {
super();
this.usersRepository = usersRepository;
}

async execute(userId) {
const { SUCCESS, NOT_FOUND } = this.outputs;

try {
const user = await this.usersRepository.getById(userId);
this.emit(SUCCESS, user);
} catch(error) {
this.emit(NOT_FOUND, {
type: error.message,
details: error.details
});
}
}
}

GetUser.setOutputs(['SUCCESS', 'ERROR', 'NOT_FOUND'])

module.exports = GetUser;
32 changes: 32 additions & 0 deletions src/app/user/UpdateUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const Operation = require('src/app/Operation');

class UpdateUser extends Operation {
constructor({ usersRepository }) {
super();
this.usersRepository = usersRepository;
}

async execute(userId, userData) {
const {
SUCCESS, NOT_FOUND, VALIDATION_ERROR, ERROR
} = this.outputs;

try {
const user = await this.usersRepository.update(userId, userData);
this.emit(SUCCESS, user);
} catch(error) {
switch(error.message) {
case 'ValidationError':
return this.emit(VALIDATION_ERROR, error);
case 'NotFoundError':
return this.emit(NOT_FOUND, error);
default:
this.emit(ERROR, error);
}
}
}
}

UpdateUser.setOutputs(['SUCCESS', 'NOT_FOUND', 'VALIDATION_ERROR', 'ERROR']);

module.exports = UpdateUser;
5 changes: 4 additions & 1 deletion src/app/user/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
GetAllUsers: require('./GetAllUsers'),
CreateUser: require('./CreateUser')
CreateUser: require('./CreateUser'),
GetUser: require('./GetUser'),
UpdateUser: require('./UpdateUser'),
DeleteUser: require('./DeleteUser')
};
13 changes: 11 additions & 2 deletions src/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ const { scopePerRequest } = require('awilix-express');

const config = require('../config');
const Application = require('./app/Application');
const { CreateUser, GetAllUsers } = require('./app/user');
const {
CreateUser,
GetAllUsers,
GetUser,
UpdateUser,
DeleteUser
} = require('./app/user');

const Server = require('./interfaces/http/Server');
const router = require('./interfaces/http/router');
Expand Down Expand Up @@ -53,7 +59,10 @@ container.registerValue({
// Operations
container.registerClass({
createUser: CreateUser,
getAllUsers: GetAllUsers
getAllUsers: GetAllUsers,
getUser: GetUser,
updateUser: UpdateUser,
deleteUser: DeleteUser
});

module.exports = container;
58 changes: 58 additions & 0 deletions src/infra/user/SequelizeUsersRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class SequelizeUsersRepository {
return users.map(UserMapper.toEntity);
}

async getById(id) {
const user = await this._getById(id);

return UserMapper.toEntity(user);
}

async add(user) {
const { valid, errors } = user.validate();

Expand All @@ -25,9 +31,61 @@ class SequelizeUsersRepository {
return UserMapper.toEntity(newUser);
}

async remove(id) {
const user = await this._getById(id);

await user.destroy();
return;
}

async update(id, newData) {
const user = await this._getById(id);

const transaction = await this.UserModel.sequelize.transaction();

try {
const updatedUser = await user.update(newData, { transaction });
const userEntity = UserMapper.toEntity(updatedUser);

const { valid, errors } = userEntity.validate();

if(!valid) {
const error = new Error('ValidationError');
error.details = errors;

throw error;
}

await transaction.commit();

return userEntity;
} catch(error) {
await transaction.rollback();

throw error;
}
}

async count() {
return await this.UserModel.count();
}

// Private

async _getById(id) {
try {
return await this.UserModel.findById(id, { rejectOnEmpty: true });
} catch(error) {
if(error.name === 'SequelizeEmptyResultError') {
const notFoundError = new Error('NotFoundError');
notFoundError.details = `User with id ${id} can't be found.`;

throw notFoundError;
}

throw error;
}
}
}

module.exports = SequelizeUsersRepository;
67 changes: 67 additions & 0 deletions src/interfaces/http/user/UsersController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ const UsersController = {
const router = Router();

router.get('/', inject('getAllUsers'), this.index);
router.get('/:id', inject('getUser'), this.show);
router.post('/', inject('createUser'), this.create);
router.put('/:id', inject('updateUser'), this.update);
router.delete('/:id', inject('deleteUser'), this.delete);

return router;
},
Expand All @@ -25,6 +28,26 @@ const UsersController = {
getAllUsers.execute();
},

show(req, res, next) {
const { getUser } = req;

const { SUCCESS, ERROR, NOT_FOUND } = getUser.outputs;

getUser
.on(SUCCESS, (user) => {
res.status(Status.OK).json(user);
})
.on(NOT_FOUND, (error) => {
res.status(Status.NOT_FOUND).json({
type: 'NotFoundError',
details: error.details
});
})
.on(ERROR, next);

getUser.execute(Number(req.params.id));
},

create(req, res, next) {
const { createUser } = req;
const { SUCCESS, ERROR, VALIDATION_ERROR } = createUser.outputs;
Expand All @@ -42,6 +65,50 @@ const UsersController = {
.on(ERROR, next);

createUser.execute(req.body);
},

update(req, res, next) {
const { updateUser } = req;
const { SUCCESS, ERROR, VALIDATION_ERROR, NOT_FOUND } = updateUser.outputs;

updateUser
.on(SUCCESS, (user) => {
res.status(Status.ACCEPTED).json(user);
})
.on(VALIDATION_ERROR, (error) => {
res.status(Status.BAD_REQUEST).json({
type: 'ValidationError',
details: error.details
});
})
.on(NOT_FOUND, (error) => {
res.status(Status.NOT_FOUND).json({
type: 'NotFoundError',
details: error.details
});
})
.on(ERROR, next);

updateUser.execute(Number(req.params.id), req.body);
},

delete(req, res, next) {
const { deleteUser } = req;
const { SUCCESS, ERROR, NOT_FOUND } = deleteUser.outputs;

deleteUser
.on(SUCCESS, () => {
res.status(Status.ACCEPTED).end();
})
.on(NOT_FOUND, (error) => {
res.status(Status.NOT_FOUND).json({
type: 'NotFoundError',
details: error.details
});
})
.on(ERROR, next);

deleteUser.execute(Number(req.params.id));
}
};

Expand Down
2 changes: 1 addition & 1 deletion test/api/users/createUser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { expect } = require('chai');

describe('API :: POST /api/users', () => {
context('when sent data is ok', () => {
it('creates and returns 200 and the new user', async () => {
it('creates and returns 201 and the new user', async () => {
const { body } = await request()
.post('/api/users')
.send({
Expand Down
Loading