Skip to content

Commit

Permalink
feat: add registration validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Mireille Niwemuhuza authored and Mireille Niwemuhuza committed Jun 11, 2019
1 parent d20a2c8 commit 78deff8
Show file tree
Hide file tree
Showing 17 changed files with 556 additions and 28 deletions.
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@ DATABASE_URL=
LOCAL_DB_USER=
LOCAL_DB_PASSWORD=
LOCAL_DB_NAME=
TEST_DB_USER=
TEST_DB_PASSWORD=
TEST_DB_NAME=
TEST_DATABASE_URL=
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
language: node_js
node_js:
- "stable"
before_script:
- yarn add sequelize-cli
- sequelize db:migrate
- sequelize db:seed:all
script:
- yarn test
before_install:
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
"build": "babel src --out-dir dist",
"serve": "node dist/index.js",
"start": "node dist/index.js",
"test": "nyc --reporter=html --reporter=text mocha ./test/*.js --exit",
"test": "nyc --reporter=text ./node_modules/.bin/mocha --recursive ./test/* --exit --require @babel/register --require @babel/polyfill ",
"coverage": "nyc report --reporter=text-lcov | coveralls"
},
"author": "Andela Simulations Programme",
"license": "MIT",
"dependencies": {
"@hapi/joi": "^15.0.3",
"body-parser": "^1.18.3",
"cors": "^2.8.4",
"dotenv": "^8.0.0",
Expand All @@ -23,6 +24,7 @@
"express-jwt": "^5.3.1",
"express-session": "^1.15.6",
"jsonwebtoken": "^8.5.1",
"joi": "^14.3.1",
"method-override": "^2.3.10",
"methods": "^1.1.2",
"morgan": "^1.9.1",
Expand Down
5 changes: 4 additions & 1 deletion src/api/routes/authRouter.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Router } from 'express';
import authController from '../controllers/auth';
import signupValidation from '../../middleware/validateBody';
import emailValidation from '../../middleware/userExists';
import usernameValidation from '../../middleware/usernameExists';

const authRouter = Router();
const { signup } = authController;

authRouter.post('/signup', signup);
authRouter.post('/signup', signupValidation, usernameValidation.usernameExist, emailValidation.userExist, signup);

export default authRouter;
6 changes: 1 addition & 5 deletions src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ config.staging = {
};

config.test = {
username: process.env.TEST_DB_USER,
password: process.env.TEST_DB_PASSWORD,
database: process.env.TEST_DB_NAME,
host: '127.0.0.1',
dialect: 'postgres'
use_env_variable: 'TEST_DATABASE_URL',
};

config.production = {
Expand Down
File renamed without changes.
49 changes: 49 additions & 0 deletions src/helpers/signupSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Joi from '@hapi/joi';

export default {
signup: (userData) => {
const schema = {
firstName: Joi.string()
.trim()
.required()
.regex(/^[A-Za-z_-]+$/)
.min(3)
.label('First name is required, it must have at least 3 letters and must contain only letters'),
lastName: Joi.string()
.trim()
.required()
.regex(/^[A-Za-z_.-]+$/)
.min(3)
.label('Last name is required, it must have at least 3 letters and must contain only letters'),
username: Joi.string()
.trim()
.lowercase()
.required()
.regex(/^[a-zA-Z0-9_.-]+$/)
.min(3)
.label('Username is required, it must have at least 3 letters and must contain only letters, numbers, underscores(_), hyphens (-) and points (.)'),
email: Joi.string()
.trim()
.lowercase()
.email()
.required()
.label('Email is required and should look like this : example@email.com!'),
password: Joi.string()
.trim()
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/)
.required()
.label('Password is required and must be at least 8 letters containing'
+ ' at least a number a Lowercase letter and an Uppercase letter'),
confirmPassword: Joi.any()
.required()
.valid(Joi.ref('password'))
.label('Password and Confirm Password do not match'),
bio: Joi.string(),
image: Joi.string(),
dateOfBirth: Joi.string(),
gender: Joi.string()
};

return Joi.validate(userData, schema, { abortEarly: false });
},
};
9 changes: 6 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from 'express';
import dotenv from 'dotenv';
import swaggerUi from 'swagger-ui-express';
import api from './api/routes';
import api from './api/routes/index';
import globalMiddleware from './middleware/globalMiddleware';
import swaggerDoc from '../swagger.json';
import db from './sequelize/models/index';
Expand All @@ -12,14 +12,17 @@ dotenv.config();
const port = process.env.PORT || 3000;
const app = express();

globalMiddleware(app);

app.use('/', swaggerUi.serve, swaggerUi.setup(swaggerDoc));
globalMiddleware(app);
app.use('/api', api);
app.get('/', swaggerUi.serve, swaggerUi.setup(swaggerDoc));


sequelize.sync().then(() => {
app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`Database succesfully connected\nServer listening on port: ${port} in ${process.env.NODE_ENV} mode`);
});
});

export default app;
23 changes: 23 additions & 0 deletions src/middleware/userExists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import models from '../sequelize/models';

const checkEmail = {
async userExist(req, res, next) {
const { email } = req.body;
await models.User.findAll({
where: {
email
},
}).then((data) => {
if (data.length > 0) {
return res.status(400).json({
status: 400,
message: 'This email is already in use',
});
}
});

next();
},
};

export default checkEmail;
23 changes: 23 additions & 0 deletions src/middleware/usernameExists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import models from '../sequelize/models';

const usernameAvailability = {
async usernameExist(req, res, next) {
const { username } = req.body;
await models.User.findAll({
where: {
username
},
}).then((data) => {
if (data.length > 0) {
return res.status(400).json({
status: 400,
message: 'This username is not available, Please choose another one!',
});
}
});

next();
},
};

export default usernameAvailability;
24 changes: 24 additions & 0 deletions src/middleware/validateBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import validate from '../helpers/signupSchema';


const validateBody = (req, res, next) => {
const data = req.body;
const err = validate.signup(data);

if (err.error === null) {
req.body = data;
next();
} else {
const allErrors = [];

err.error.details.forEach((errors) => {
allErrors.push(errors.context.label);
});

return res.status(400).send({
message: allErrors,
});
}
};

export default validateBody;
48 changes: 48 additions & 0 deletions src/sequelize/migrations/20190611080237-create-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@


module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
},
username: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
},
bio: {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING
},
dateOfBirth: {
type: Sequelize.DATE
},
gender: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: queryInterface => queryInterface.dropTable('Users')
};
3 changes: 3 additions & 0 deletions src/sequelize/models/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import fs from 'fs';
import path from 'path';
import Sequelize from 'sequelize';
import dotenv from 'dotenv';

dotenv.config();

const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
Expand Down
16 changes: 16 additions & 0 deletions src/sequelize/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@


module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING,
bio: DataTypes.STRING,
image: DataTypes.STRING,
dateOfBirth: DataTypes.DATE,
gender: DataTypes.STRING
}, {});
return User;
};
23 changes: 23 additions & 0 deletions src/sequelize/seeders/20190611101225-test-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@


module.exports = {
up: queryInterface =>
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
*/

queryInterface.bulkInsert('Users', [{
firstName: 'Mireille',
lastName: 'Niwemuhuza',
username: 'mifeille',
email: 'nimilleer@gmail.com',
password: 'Mireille1!',
bio: '',
image: '',
dateOfBirth: '12/12/2000',
gender: '',
createdAt: new Date(),
updatedAt: new Date(),
}], {}),
};
67 changes: 67 additions & 0 deletions test/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import chaiHttp from 'chai-http';
import chai from 'chai';
import dotenv from 'dotenv';
import server from '../src/index';


dotenv.config();

const { expect } = chai;
chai.use(chaiHttp);


describe('User Registration', () => {
it('should not let a user signup without valid credentials ', (done) => {
chai.request(server)
.post('/api/auth/signup')
.send({
firstName: 'Emy',
lastName: 'Rukundo',
username: 'emy2',
email: 'rukundogmail.com',
password: 'Rukundo1!',
confirmPassword: 'Rukundo1!'
})
.end((err, res) => {
expect(res.status).to.equal(400);
expect(res.body).to.be.an('object');
done();
});
});

it('should not let a user signup with an already existing email ', (done) => {
chai.request(server)
.post('/api/auth/signup')
.send({
firstName: 'Emy',
lastName: 'Rukundo',
username: 'mifeillee',
email: 'nimilleer@gmail.com',
password: 'Rukundo1!',
confirmPassword: 'Rukundo1!'
})
.end((err, res) => {
expect(res.status).to.equal(400);
expect(res.body).to.be.an('object');
done();
});
});

it('should not let a user signup with an already existing username ', (done) => {
chai.request(server)
.post('/api/auth/signup')
.send({
firstName: 'Emy',
lastName: 'Rukundo',
username: 'mifeille',
email: 'nimiller@gmail.com',
password: 'Rukundo1!',
confirmPassword: 'Rukundo1!'
})
.end((err, res) => {
expect(res.status).to.equal(400);
expect(res.body).to.be.an('object');
done();
});
});
});
Loading

0 comments on commit 78deff8

Please sign in to comment.