Skip to content
This repository has been archived by the owner on Jul 20, 2020. It is now read-only.

Commit

Permalink
feat(userRoles): User Roles Settings (#24)
Browse files Browse the repository at this point in the history
add roles field in the user database
added internationalized messages
seeded a default admin
add the update roles controller and routes
add the feature's tests and documentation
[Finishes #170947557]
  • Loading branch information
Baraka-Mugisha committed Feb 18, 2020
1 parent b50ff29 commit 37278ec
Show file tree
Hide file tree
Showing 21 changed files with 337 additions and 58 deletions.
28 changes: 2 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"db:migrate": "npx sequelize db:migrate --env test",
"db:reset": "npx sequelize db:migrate:undo:all --env test && npm run db:migrate --env test",
"create": "npx sequelize db:migrate",
"reset:dev": "npm run drop && npm run create && npm run seed:dev && nodemon --exec babel-node src/index.js",
"seed": "npx sequelize db:seed:all --env test",
"seed:dev": "npx sequelize db:seed:all",
"drop": "npx sequelize db:migrate:undo:all",
Expand Down
27 changes: 15 additions & 12 deletions src/controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import dotenv from 'dotenv';
import localStorage from 'localStorage';
import db from '../models';
import sendMsg from '../utils/user-created-email';
import provideToken from '../utils/provideToken';
import { provideToken } from '../utils/tokenHandler';
import Response from '../utils/ResponseHandler';

dotenv.config();

/**
*
* @description AuthController Controller
Expand All @@ -17,7 +18,7 @@ export default class AuthController {
/**
* @description Sign up method
* @static
* @param {Object} req
* @param {Object} req`
* @param {Object} res
* @returns {Object} User
* @memberof authController
Expand All @@ -35,7 +36,7 @@ export default class AuthController {
});

if (existingUser) {
return Response.errorResponse(res, 409, 'Email already exists');
return Response.errorResponse(res, 409, res.__('Email already exists'));
}
const hashedPassword = bcrypt.hashSync(password, Number(process.env.passwordHashSalt));
const user = await db.User.create({
Expand All @@ -44,13 +45,14 @@ export default class AuthController {
lastName,
email,
password: hashedPassword,
role: 'requester'
});
const token = provideToken(user.id, user.isVerified, email);
const token = provideToken(user.id, user.isVerified, email, user.role);
localStorage.setItem('token', token);
sendMsg(email, token, firstName);
return Response.signupResponse(res, 201, 'User successfully registered', token);
return Response.signupResponse(res, 201, res.__('User is successfully registered'), token);
} catch (error) {
return Response.errorResponse(res, 500, `${error.message}`);
return Response.errorResponse(res, 500, res.__(`${error.message}`));
}
}

Expand All @@ -70,19 +72,20 @@ export default class AuthController {
where: {
email,
},
attributes: ['id', 'email', 'password', 'isVerified'],
attributes: ['id', 'email', 'password', 'isVerified', 'role'],
});
if (!user) {
return Response.errorResponse(res, 401, 'Incorrect email or password');
return Response.errorResponse(res, 401, res.__('Incorrect email or password'));
}
if (bcrypt.compareSync(password, user.password)) {
const token = provideToken(user.dataValues.id, user.dataValues.isVerified);
const { id, isVerified, role } = user.dataValues;
const token = provideToken(id, isVerified, email, role);
localStorage.setItem('token', token);
return Response.login(res, 200, 'User is successfully logged in', token);
return Response.login(res, 200, res.__('User is successfully logged in'), token);
}
return Response.errorResponse(res, 401, 'Incorrect email or password');
return Response.errorResponse(res, 401, res.__('Incorrect email or password'));
} catch (error) {
return Response.errorResponse(res, 500, error.message);
return Response.errorResponse(res, 500, res.__(error.message));
}
}

Expand Down
43 changes: 43 additions & 0 deletions src/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import db from '../models';
import Response from '../utils/ResponseHandler';

/**
* @description User Controller
* @class UserController
*/
export default class UserController {
/**
* @description login method
* @static
* @param {Object} req
* @param {Object} res
* @returns {Object} User
* @memberof authController
*/
static async setRoles(req, res) {
try {
const { email, role } = req.body;
const admin = req.payload;
const avaiableAdmins = await db.User.findAll({
where: { role: 'super administrator' }
});
if (admin.role !== 'super administrator' || (avaiableAdmins.length === 2 && role === 'super administrator')) {
return Response.errorResponse(res, 401, res.__('you are not authorised for this operation'));
}
const existingUser = await db.User.findOne({
where: { email }
});
if (!existingUser) {
return Response.errorResponse(res, 404, res.__('The user doesn\'t exist'));
}
if (existingUser.role === role) {
return Response.errorResponse(res, 409, res.__('The user is already a %s', role));
}

await db.User.update({ role }, { where: { email }, attributes: ['email', 'role'] });
return Response.success(res, 200, res.__('User roles updated successfully'));
} catch (error) {
return Response.errorResponse(res, 500, res.__(error.message));
}
}
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dotenv from 'dotenv';
import welcome from './routes/welcome';
import swagger from './swagger/index';
import authRouter from './routes/authRoutes';
import userRouter from './routes/userRoutes';

dotenv.config();
i18n.configure({
Expand All @@ -24,6 +25,7 @@ const port = process.env.PORT || 3000;
app.use('/api', welcome);
app.use('/api-doc', swagger);
app.use('/api/v1/auth', authRouter);
app.use('/api/v1/user', userRouter);

app.use(express.json());

Expand Down
3 changes: 3 additions & 0 deletions src/migrations/20200209182554-create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module.exports = {
password: {
type: Sequelize.STRING
},
role: {
type: Sequelize.STRING
},
isVerified: {
allowNull: false,
type: Sequelize.BOOLEAN,
Expand Down
1 change: 1 addition & 0 deletions src/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = (sequelize, DataTypes) => {
lastName: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING,
role: DataTypes.STRING,
isVerified: DataTypes.BOOLEAN
}, {});
User.associate = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/authRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from 'express';
import AuthController from '../controllers/authController';
import validationResult from '../validation/validationResult';
import signupInputRules from '../validation/validationRules';
import { signupInputRules } from '../validation/validationRules';
import verificationController from '../controllers/verificationController';
import validateParams from '../validation/validateParams';

Expand Down
11 changes: 11 additions & 0 deletions src/routes/userRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import express from 'express';
import userController from '../controllers/userController';
import validationResult from '../validation/validationResult';
import { changeRoles } from '../validation/validationRules';
import { decode } from '../utils/tokenHandler';

const userRouter = express.Router();

userRouter.patch('/setRoles', decode, changeRoles, validationResult, userController.setRoles);

export default userRouter;
7 changes: 5 additions & 2 deletions src/seeders/20200212121323-User.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
const bcrypt = require('bcrypt')
const uuid = require('uuid/v4');

module.exports = {
up: (queryInterface) => queryInterface.bulkInsert(
'Users',
[
{
id: "712cc013-275d-4855-b2ac-77c054ad3d28",
id: uuid(),
firstName: 'Bienjee',
lastName: 'Bieio',
email: 'jean@andela.com',
password: bcrypt.hashSync('Bien@BAR789', Number(process.env.passwordHashSalt)),
isVerified: false,
role: 'super administrator',
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: "jbubihyuebucbeceb",
id: uuid(),
firstName: 'devrepubli',
lastName: 'devrpo',
email: 'jdev@andela.com',
password: bcrypt.hashSync('Bien@BAR789', Number(process.env.passwordHashSalt)),
isVerified: false,
role: 'requester',
createdAt: new Date(),
updatedAt: new Date(),
},
Expand Down
20 changes: 19 additions & 1 deletion src/services/localesServices/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,23 @@
"login": "login",
"logout": "logout",
"User is successfully logged out": "User is successfully logged out",
"Login first or create an account if you do not have one": "Login first or create an account if you do not have one"
"There is no such user": "There is no such user",
"you are not authorised for this operation": "you are not authorised for this operation",
"User roles updated successfully": "User roles updated successfully",
"The user doesn't exist": "The user doesn't exist",
"The email is required": "The email is required",
"Invalid value": "Invalid value",
"manager": "manager",
"travel team member": "travel team member",
"requester": "requester",
"travel administrator": "travel administrator",
"super administrator": "super administrator",
"the acceptable roles are manager, travel team member, requester, travel administrator, super administrator": "the acceptable roles are manager, travel team member, requester, travel administrator, super administrator",
"The user is already a %s": "The user is already a %s",
"User is successfully logged in": "User is successfully logged in",
"Incorrect email or password": "Incorrect email or password",
"User is successfully registered": "User is successfully registered",
"Email already exists": "Email already exists",
"Lastname must be atleast 4 characters": "Lastname must be atleast 4 characters",
"Firstname must be atleast 4 characters": "Firstname must be atleast 4 characters"
}
21 changes: 20 additions & 1 deletion src/services/localesServices/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,24 @@
"signup": "enregistrer",
"login": "connecter",
"logout": "déconnecter",
"User is successfully logged out": "l'utilisateur s'est déconnecté avec succès"
"User is successfully logged out": "L'utilisateur est correctement déconnecté",
"There is no such user": "Il n'y a pas un tel utilisateur",
"you are not authorised for this operation": "vous n'êtes pas autorisé pour cette opération",
"User roles updated successfully": "Les rôles utilisateur ont été mis à jour avec succès",
"The user doesn't exist": "L'utilisateur n'existe pas",
"The email is required": "L'email est obligatoire",
"Invalid value": "valeur invalide",
"manager": "directeur",
"travel team member": "membre de l'équipe de voyage",
"requester": "demandeur",
"travel administrator": "administrateur de voyages",
"super administrator": "super administrateur",
"the acceptable roles are manager, travel team member, requester, travel administrator, super administrator": "les rôles acceptables sont gestionnaire, membre de l'équipe de voyage, demandeur, administrateur de voyage, super administrateur",
"The user is already a %s": "L'utilisateur est déjà un %s",
"User is successfully logged in": "L'utilisateur est connecté avec succès",
"Incorrect email or password": "email ou mot de passe incorrect",
"User is successfully registered": "L'utilisateur est enregistré avec succès",
"Email already exists": "l'email existe déjà",
"Lastname must be atleast 4 characters": "Le nom de famille doit contenir au moins 4 caractères",
"Firstname must be atleast 4 characters": "Le prénom doit contenir au moins 4 caractères"
}
50 changes: 50 additions & 0 deletions src/swagger/user.swagger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @swagger
* definitions:
* setRoles:
* type: object
* properties:
* email:
* type: string
* format: email
* role:
* type: string
* required:
* - email
* - role
*/

/**
* @swagger
* /api/v1/user/setRoles:
* patch:
* tags:
* - User Roles
* name: updateRoles
* summary: updates the roles of a user
* produces:
* - application/json
* consumes:
* - application/json
* parameters:
* - name: body
* in: body
* schema:
* $ref: '#/definitions/setRoles'
* type: object
* properties:
* email:
* type: string
* role:
* type: string
* required:
* - email
* - role
* responses:
* '200':
* description: User role successfully updated.
* '401':
* description: Unauthorized.
* '409':
* description: User roles already set.
* */
2 changes: 1 addition & 1 deletion src/tests/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('Signup Tests', () => {
.end((err, res) => {
expect(res.body).to.be.an('object');
expect(res.status).to.equal(201);
expect(res.body.message).to.equal('User successfully registered');
expect(res.body.message).to.equal('User is successfully registered');
done();
});
});
Expand Down
Loading

0 comments on commit 37278ec

Please sign in to comment.