Skip to content

Commit

Permalink
[Feature #166240828] Enable the users to fetch articles ratings
Browse files Browse the repository at this point in the history
  • Loading branch information
nkpremices committed Jul 3, 2019
1 parent 86a977d commit ab2904b
Show file tree
Hide file tree
Showing 24 changed files with 763 additions and 237 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ src/api/migrations

# Seeders
src/api/seeders
# Models

# Models
src/api/models
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"precommit": "lint-staged",
"migrate": "node_modules/.bin/sequelize db:migrate",
"seed": "node_modules/.bin/sequelize db:seed:all",
"seed:undo": "node_modules/.bin/sequelize db:seed:undo:all",
"migrate:undo": "node_modules/.bin/sequelize db:migrate:undo:all"
},
"lint-staged": {
Expand Down Expand Up @@ -94,7 +95,6 @@
"src/middlewares/passport.js",
"src/index.js",
"src/middlewares/errorHandler.js",
"src/api/models",
"tests/*"
]
},
Expand Down
99 changes: 99 additions & 0 deletions src/api/controllers/articleRatingsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import _ from 'lodash';
import models, { sequelize } from '../models';
import status from '../../helpers/constants/status.codes';
import sendError from '../../helpers/error.sender';
import calculateRatingsPercentage from '../../helpers/calculate.ratings.percentages';
import errorMessages from '../../helpers/constants/error.messages';
import generatePaginationDetails from '../../helpers/generate.pagination.details';

const { Rating, User } = models;

/**
* containing all controllers of the ratings process
*
* @export
* @class ratings
*/
export default class articleRatings {
/**
* A controller to calculate the ratings percentages,
*
* @param {Object} req - the request object
* @param {Object} res - the result object
* @returns {Object} res
*/
static async getArticleRatingStatistics(req, res) {
// Initialising variables
const result = {};
const { slug } = req.params;

// Fetching the articles count per rating
const ratingsCount = await Rating.findAll({
where: {
articleSlug: slug
},
group: ['rating'],
attributes: ['rating', [sequelize.fn('COUNT', 'TagName'), 'count']]
});

// Sending the result
if (_.isEmpty(ratingsCount)) {
// Error if there are no ratings for the article
sendError(status.NOT_FOUND, res, 'ratings', errorMessages.ratingsNotFound);
result.status = status.NOT_FOUND;
} else {
// Calculating the percentages
const ratingsWithPercentages = calculateRatingsPercentage(ratingsCount);

// sending the result back
result.status = status.OK;
result.data = ratingsWithPercentages;
res.status(status.OK).json(result);
}
}

/**
* A controller to get users for a given rating
* with pagination support
*
* @param {Object} req - the request object
* @param {Object} res - the result object
* @returns {Object} res
*/
static async getRatingUsers(req, res) {
// Initialising variables
const result = {};
const { offset, limit } = req.query;
const { slug, rating } = req.params;

// Fetching data from the database
const ratingUsers = await User.findAndCountAll({
attributes: ['id', 'username', 'email', 'firstName', 'lastName'],
include: [
{
model: Rating,
where: {
articleSlug: slug,
rating
},
attributes: []
}
],
offset,
limit
});

if (_.isEmpty(ratingUsers.rows)) {
sendError(status.NOT_FOUND, res, 'Data', 'No users fond for the provided entries');
} else {
// sending the result back
result.status = status.OK;
result.paginationDetails = generatePaginationDetails(ratingUsers.count,
ratingUsers.rows,
offset,
limit);
result.data = ratingUsers.rows;
res.status(status.OK).json(result);
}
}
}
7 changes: 4 additions & 3 deletions src/api/controllers/verifyEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export default class {
*/
static async verifyEmail(req, res) {
const { token } = req.params;
const result = decodejwt(token);
if (result !== undefined) {
let result;
try {
result = decodejwt(token);
const action = await models.User.update({ verified: true },
{
where: {
Expand All @@ -32,7 +33,7 @@ export default class {
} else {
error(status.NOT_FOUND, res, 'user', errorMessages.noUser);
}
} else {
} catch (err) {
error(status.BAD_REQUEST, res, 'link', errorMessages.emailLinkInvalid);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/api/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ const models = {
User: sequelize.import('./user'),
Article: sequelize.import('./article'),
Category: sequelize.import('./category'),
Rating: sequelize.import('./rating'),
Rating: sequelize.import('./rating')
};

Object.keys(models).forEach((key) => {
if ('associate' in models[key]) {
models[key].associate(models);
Expand Down
6 changes: 3 additions & 3 deletions src/api/models/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export default (sequelize, DataTypes) => {
});

Rating.removeAttribute('id');
Rating.associate = ({ User, Article }) => {
Rating.belongsTo(User, { foreignKey: 'userId' });
Rating.belongsTo(Article, { foreignKey: 'articleSlug' });
Rating.associate = (models) => {
Rating.belongsTo(models.User, { foreignKey: 'userId' });
Rating.belongsTo(models.Article, { foreignKey: 'articleSlug' });
};
return Rating;
};
3 changes: 3 additions & 0 deletions src/api/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export default (sequelize, DataTypes) => {
onDelete: 'CASCADE',
hooks: true
});
User.hasMany(models.Rating, {
foreignKey: 'userId'
});
};

User.findByEmail = (email) => {
Expand Down
15 changes: 14 additions & 1 deletion src/api/routes/articleRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import checkArticleOwner from '../../middlewares/checkArticleOwnership';
import checkExistingRates from '../../middlewares/checkExistingRating';
import uploadImage from '../../middlewares/upload';
import errorHandler from '../../middlewares/errorHandler';
import articleRatingsController from '../controllers/articleRatingsController';
import authorization from '../../middlewares/authorization';
import validateRatingsRoute from '../../middlewares/validations/ratings.routes';

const articleRouter = new Router();

articleRouter.post('/:slug/rating',
checkValidToken,
bodyVerifier,
checkArticle.getArticle,
validateInputs(true, 'rateArticle', ['rating']),
validateInputs('rateArticle', ['rating']),
checkExistingRates.ExistingRating,
ratingsController.rateArticle);

Expand All @@ -32,4 +35,14 @@ articleRouter.post('/',
checkValidToken,
uploadImage.single('coverImage'),
errorHandler(articleController.create));

articleRouter.get('/:slug/ratings/statistics',
authorization,
articleRatingsController.getArticleRatingStatistics);

articleRouter.get('/:slug/:rating/users',
authorization,
validateRatingsRoute,
articleRatingsController.getRatingUsers);

export default articleRouter;
4 changes: 2 additions & 2 deletions src/api/routes/password.reset.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ const router = new Router();

router.post('/',
verifyBody,
validateInputs(true, 'resetPassword', ['email']),
validateInputs('resetPassword', ['email']),
validateResetEmail,
resetController.sendRecoveryEmail);

router.post('/update/:email',
verifyBody,
validateInputs(true, 'updatePassword', ['password']),
validateInputs('updatePassword', ['password']),
validateUpdatePassword,
resetController.updatePassword);

Expand Down
18 changes: 15 additions & 3 deletions src/api/routes/profileRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ import profileController from '../controllers/profileController';
import upload from '../../middlewares/upload';
import validateInputs from '../../middlewares/validations/body.inputs';


const profileRouter = new Router();
const fields = ['country', 'lastName', 'firstName', 'address', 'gender', 'phoneNumber', 'bio', 'profileImage', 'facebook', 'twitter'];
const fields = [
'country',
'lastName',
'firstName',
'address',
'gender',
'phoneNumber',
'bio',
'profileImage',
'facebook',
'twitter'
];

profileRouter.patch('/', upload.single('profileImage'), validateInputs(true, 'profile', fields),
profileRouter.patch('/',
upload.single('profileImage'),
validateInputs('profile', fields),
profileController.update);
profileRouter.get('/:username', profileController.read);

Expand Down
10 changes: 7 additions & 3 deletions src/api/seeders/20190624184857-Article.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ export default {
title: 'How to create sequalize seeds',
slug: 'How-to-create-sequalize-seeds',
description: 'How to set dummy data automatically',
body: 'Suppose we want to insert some data.',
body:
"Suppose we want to insert some data into a few tables by default. If we follow up on previous example we can consider creating a demo user for User table.To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database table with sample data or test data.Let's create a seed file which will add a demo user to our User table.",
coverImage: 'default.jpeg',
tagList: ['postgres', 'express', 'sequelize'],
category: 1,
author: 1,
createdAt: new Date(),
updatedAt: new Date()
},
{
title: 'What is a Version 1 UUID',
title: 'What is a Version 1 UUID?',
slug: 'What-is-a-Version-1-UUID',
description: 'Velit non sit culpa pariatur proident',
body: 'A Version 1 UUID is a universall',
body:
'A Version 1 UUID is a universally unique identifier that is generated using a timestamp and the MAC address of the computer on which it was generated.',
coverImage: 'default.jpeg',
tagList: ['UUID', 'express', 'sequelize'],
category: 1,
author: 1,
createdAt: new Date(),
Expand Down
Loading

0 comments on commit ab2904b

Please sign in to comment.