Skip to content

Commit

Permalink
feat(validation): implement signup validation
Browse files Browse the repository at this point in the history
[Starts #167891575]
  • Loading branch information
meetKazuki committed Aug 22, 2019
1 parent 1b6e517 commit febcfd3
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 79 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Barefoot Nomad - Making company travel and accomodation easy and convinient.
# Barefoot Nomad - Making company travel and accommodation easy and convenient.

[![Build Status](https://travis-ci.org/andela/banshee-bn-backend.svg?branch=develop)](https://travis-ci.org/andela/banshee-bn-backend)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "barefoot-normad",
"name": "barefoot-nomad",
"version": "1.0.0",
"description": "A travel and accomodation application",
"description": "A travel and accommodation application",
"main": "app.js",
"nyc": {
"all": true,
Expand Down Expand Up @@ -43,7 +43,8 @@
"express": "^4.16.3",
"express-jwt": "^5.3.1",
"express-session": "^1.15.6",
"jsonwebtoken": "^8.5.1",
"express-validator": "^6.1.1",
"jsonwebtoken": "^8.3.0",
"method-override": "^2.3.10",
"methods": "^1.1.2",
"passport": "^0.4.0",
Expand Down
8 changes: 3 additions & 5 deletions src/database/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,30 @@ dotenv.config();

const { DB_DEV_NAME, DB_USER_NAME, DB_PASSWORD } = process.env;

const dialect = 'postgres';

module.exports = {
development: {
username: DB_USER_NAME,
password: DB_PASSWORD,
database: DB_DEV_NAME,
host: '127.0.0.1',
port: process.env.DB_PORT,
dialect
dialect: 'postgres',
},
test: {
username: DB_USER_NAME,
password: DB_PASSWORD,
database: process.env.DB_TEST_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect
dialect: 'postgres',
},
production: {
username: process.env.PRODUCTION_USERNAME,
password: process.env.PRODUCTION_PASSWORD,
database: process.env.PRODUCTION_DATABASE,
host: process.env.PRODUCTION_HOST,
port: process.env.PRODUCTION_PORT,
dialect,
dialect: 'postgres',
ssl: true
}
};
1 change: 0 additions & 1 deletion src/database/migrations/20190821100957-create-company.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

module.exports = {
up: (queryInterface, Sequelize) => queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";').then(() => queryInterface.createTable('Companies', {
id: {
Expand Down
128 changes: 63 additions & 65 deletions src/database/migrations/20190821105503-create-user.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,66 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";').then(() => {
queryInterface.createTable('Users', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.literal('uuid_generate_v4()'),
up: (queryInterface, Sequelize) => queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";').then(() => queryInterface.createTable('Users', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.literal('uuid_generate_v4()'),
},
companyId: {
allowNull: false,
type: Sequelize.DataTypes.UUID,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Companies',
key: 'id',
},
companyId: {
allowNull: false,
type: Sequelize.DataTypes.UUID,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Companies',
key: 'id',
},
},
firstName: {
allowNull: false,
type: Sequelize.STRING
},
lastName: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
unique: true,
type: Sequelize.STRING
},
password: {
allowNull: false,
type: Sequelize.STRING
},
gender: {
allowNull: false,
type: Sequelize.ENUM('male', 'female')
},
dob: {
allowNull: false,
type: Sequelize.DATEONLY,
},
role: {
allowNull: false,
type: Sequelize.ENUM('owner', 'admin', 'staff'),
defaultValue: 'staff'
},
status: {
allowNull: false,
type: Sequelize.ENUM('active', 'inactive'),
defaultValue: 'active'
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
}
});
}),
down: queryInterface => queryInterface.dropTable('Users')
},
firstName: {
allowNull: false,
type: Sequelize.STRING
},
lastName: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
unique: true,
type: Sequelize.STRING
},
password: {
allowNull: false,
type: Sequelize.STRING
},
gender: {
allowNull: false,
type: Sequelize.ENUM('male', 'female')
},
dob: {
allowNull: false,
type: Sequelize.DATEONLY,
},
role: {
allowNull: false,
type: Sequelize.ENUM('owner', 'admin', 'staff'),
defaultValue: 'staff'
},
status: {
allowNull: false,
type: Sequelize.ENUM('active', 'inactive'),
defaultValue: 'active'
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('now')
}
})),
down: (queryInterface) => queryInterface.dropTable('Users')
};
2 changes: 1 addition & 1 deletion src/database/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if (config.use_env_variable) {
}

fs.readdirSync(__dirname)
.filter(file => (file
.filter((file) => (file
.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'))
.forEach((file) => {
const model = sequelize.import(path.join(__dirname, file));
Expand Down
2 changes: 1 addition & 1 deletion src/database/seeders/20190821112700-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = {
companyId: 'a6e35eb9-8c59-4c7d-b8d4-ae724aa7fb61',
firstName: 'Desmond',
lastName: 'Edem',
email: 'kukiito@gmail.com',
email: 'kukiito219@gmail.com',
password: '12345678',
gender: 'male',
dob: '2012-09-12',
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/Hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Hash {
/**
* @description - hash password method
* @param {string} password
* @return {string} hased password
* @return {string} hashed password
*/
static hashPassword(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8));
Expand All @@ -15,7 +15,7 @@ class Hash {
* @description - this method compares password
* @param {string} hashPassword
* @param {string} password
* @return {string} hased password
* @return {string} hashed password
*/
static comparePassword(hashPassword, password) {
return bcrypt.compareSync(password, hashPassword);
Expand Down
22 changes: 22 additions & 0 deletions src/middlewares/SignupValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable require-jsdoc */
import { validationResult } from 'express-validator';
import Response from '../utils/response';

export default class SignupValidator {
static validateSignup(req, res, next) {
const errors = validationResult(req);
const validationErrors = [];

if (errors) {
errors.map(error => validationErrors.push(error.msg));
const response = new Response(
'Bad request',
400,
'Invalid credentials',
validationErrors
);
return res.status(response.code).json(response);
}
next();
}
}
31 changes: 31 additions & 0 deletions src/middlewares/UserMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-disable require-jsdoc */
import models from '../database/models';
import Response from '../utils/response';

const { User } = models;

export default class UserMiddleware {
static async verifyExistingUser(req, res, next) {
try {
const { email } = req.body;

const user = await User.findOne({ where: { email } });
if (user) {
const response = new Response(
'Conflict',
409,
'User with email already exist'
);
return res.status(response.code).json(response);
}
next();
} catch (error) {
const response = new Response(
'Internal server error',
500,
`${error}`,
);
return res.status(response.code).json(response);
}
}
}
17 changes: 17 additions & 0 deletions src/utils/capitalize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @function capitalize
* @description
* @param {string} word
* @return {function} call
*/
const capitalize = (word) => {
const formatWord = word
.toLowerCase()
.split(' ')
.map((f) => f.charAt(0).toUpperCase() + f.substring(1))
.join(' ');

return formatWord;
};

export default capitalize;
11 changes: 11 additions & 0 deletions src/utils/response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable require-jsdoc */
class Response {
constructor(status, code, messages, data) {
this.status = status;
this.code = code;
this.messages = messages;
this.data = data;
}
}

export default Response;
5 changes: 5 additions & 0 deletions src/validation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import signup from './signup';

export default {
signup,
};
38 changes: 38 additions & 0 deletions src/validation/signup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { check } from 'express-validator/check';
import capitalize from '../utils/capitalize';

export default {
signupSchema: [
check('firstName')
.trim()
.exists().withMessage('First name is required')
.isLength({ min: 2, max: 15 })
.withMessage('First name should be between 2 to 15 characters')
.isAlpha()
.withMessage('First name should only be alphabets')
.customSanitizer(value => capitalize(value)),

check('lastName')
.trim()
.exists().withMessage('Last name is required')
.isLength({ min: 2, max: 15 })
.withMessage('Last name should be between 2 to 15 characters')
.isAlpha()
.withMessage('Last name should only be alphabets')
.customSanitizer(value => capitalize(value)),

check('email')
.trim()
.exists().withMessage('Email address is required')
.isEmail()
.withMessage('Email address is invalid'),

check('password')
.trim()
.exists().withMessage('Password is required')
.isLength({ min: 8, })
.withMessage('Password must be alphanumeric and not less than 8 characters')
.isAlpha()
.withMessage('Password must be alphanumeric and not less than 8 characters')
]
};
29 changes: 29 additions & 0 deletions test/routes/auth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import chai, { should } from 'chai';
import chaiHttp from 'chai-http';
import app from '../../src';

const baseURI = '/api/v1/auth';

describe('Auth Routes', () => {
describe('Signup Route', () => {
specify('error if user is already registered', async () => {
const response = await chai
.request(app)
.post(`${baseURI}/signup`)
.send(user);
response.should.have.status(409);
response.body.status.should.eql('error');
response.body.should.have.property('error');
});

specify('error if FirstName is undefined');
specify('error if FirstName contains an invalid character');
specify('error if LastName is undefined');
specify('error if LastName contains an invalid character');
specify('error if email is not provided');
specify('error if email is invalid');
specify('error if password is not provided');
specify('error if password is invalid');
specify('error if password is less than minimum length');
});
});

0 comments on commit febcfd3

Please sign in to comment.