Skip to content

Commit

Permalink
166816210-feature(article, ratings, user, utils): fetch article ratings
Browse files Browse the repository at this point in the history
- update articles  model
- add get article ratings controller
- update rate article controller
- add validations
- add  unit integration tests
- add documentation
[Delivers #166816210]
  • Loading branch information
vincentayorinde committed Jul 12, 2019
1 parent 16e03c9 commit eda6d2b
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 7 deletions.
53 changes: 47 additions & 6 deletions controllers/articles/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { uploadImage, deleteImage } from '../../utils';
import {
uploadImage, deleteImage, findRatedArticle, storeRating, avgRating
} from '../../utils';
import db from '../../db/models';
import Notification from '../../utils/notifications';

Expand Down Expand Up @@ -27,11 +29,13 @@ export default {
message: 'Article not found',
});
}

const rate = await avgRating(article.id);
return res.status(200).json({
article
article,
avgRatings: rate
});
} catch (e) {
console.log('the e>>>>>>>>>>>', e);
/* istanbul ignore next */
return res.status(500).json({
message: 'Something went wrong',
Expand Down Expand Up @@ -161,7 +165,7 @@ export default {
rateArticle: async (req, res) => {
const { params: { slug }, body: { rate }, user } = req;
try {
const foundArticle = await db.Article.findOne({
const foundArticle = await findRatedArticle({
where: { slug }
});
if (!foundArticle) {
Expand All @@ -176,11 +180,13 @@ export default {
}
});
if (checkRating) {
checkRating.update({
const updatedRating = await checkRating.update({
stars: rate
});
await storeRating(foundArticle.id);
return res.status(200).json({
message: 'Rating updated successfully'
message: 'Rating updated successfully',
updatedRating
});
}
const rating = await user.createRate({
Expand All @@ -192,6 +198,7 @@ export default {
rating,
});
} catch (e) {
/* istanbul ignore next */
return res.status(500).json({
message: 'Something went wrong',
error: e.message
Expand Down Expand Up @@ -302,4 +309,38 @@ export default {
});
}
},
getArticleRatings: async (req, res) => {
try {
const foundArticle = await findRatedArticle({
where: { slug: req.params.slug }
});
if (!foundArticle) {
return res.status(404).json({
message: 'Article does not exist'
});
}
const fetchRating = await db.Ratings.findAll({
where: {
articleId: foundArticle.id,
},
attributes: { exclude: ['userId'] },
include: [{
model: db.User,
as: 'user',
attributes: ['id', 'username', 'image', 'firstName', 'lastName']
}],
});
return res.status(200).json({
message: 'All ratings for Article',
totalRates: fetchRating.length,
rates: fetchRating
});
} catch (e) {
/* istanbul ignore next */
return res.status(500).json({
message: 'Something went wrong',
error: e.message
});
}
},
};
6 changes: 5 additions & 1 deletion db/models/Article.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = (sequelize, DataTypes) => {
description: DataTypes.TEXT,
slug: DataTypes.STRING,
body: DataTypes.TEXT,
image: DataTypes.STRING,
image: DataTypes.STRING
},
{}
);
Expand All @@ -34,6 +34,10 @@ module.exports = (sequelize, DataTypes) => {
Article.hasMany(models.ArticleTag, {
foreignKey: 'articleId',
as: 'articleTag',
});
Article.hasMany(models.Ratings, {
foreignKey: 'articleId',
as: 'rating',
cascade: true
});
};
Expand Down
6 changes: 6 additions & 0 deletions routes/v1/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ router.delete(

router.post('/vote/:slug', Middleware.authenticate, Middleware.isblackListedToken, Article.voteArticle);

router.get(
'/rate/:slug',
Validation.articleSlug,
Article.getArticleRatings,
);

export default router;
21 changes: 21 additions & 0 deletions swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,27 @@ paths:
description: Article rated successfully
'400':
description: "Invalid rating supplied"
get:
tags:
- Articles
summary: get all specific article ratings
operationId: getArticleRatings
consumes:
- application/json
- application/x-www-form-urlencoded
produces:
- application/json
parameters:
- name: "slug"
in: "path"
description: An article slug to be rated by a user
required: true
type: string
responses:
'200':
description: All ratings for Article
'404':
description: "Article does not exist"
/articles/{slug}:
get:
tags:
Expand Down
33 changes: 33 additions & 0 deletions tests/routes/articles.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ describe('ARTICLES TEST', () => {
it('Anyone should be able to view an article', async () => {
const newUser = await createUser(register);
const newArticle = await createArticle({ ...article, authorId: newUser.id });
await createRate({
userId: newUser.id,
articleId: newArticle.id,
stars: '4'
});
const res = await chai
.request(app)
.get(`/api/v1/articles/${newArticle.slug}`);
Expand Down Expand Up @@ -509,4 +514,32 @@ describe('ARTICLES TEST', () => {
expect(res.body.error).to.equal('This article does not exist');
});
});
describe('Get Article Ratings', () => {
let articleData;
beforeEach(async () => {
const user = await createUser(register);
articleData = await createArticle({ ...article, authorId: user.id });
});
it('should get a specific article ratings', async () => {
const res = await chai
.request(app)
.get(`/api/v1/articles/rate/${articleData.slug}`)
.send();
expect(res.statusCode).to.equal(200);
expect(res.body).to.be.an('object');
expect(res.body).to.include.all.keys('message', 'totalRates', 'rates');
expect(res.body.message).to.equal('All ratings for Article');
expect(res.body.totalRates).to.be.a('number');
expect(res.body.rates).to.be.an('array');
});
it('should not get a specific article ratings if article does not exists', async () => {
const res = await chai
.request(app)
.get('/api/v1/articles/rate/wrong-slug')
.send();
expect(res.statusCode).to.equal(404);
expect(res.body).to.be.an('object');
expect(res.body.message).to.equal('Article does not exist');
});
});
});
24 changes: 24 additions & 0 deletions utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,27 @@ export const createUser = async (user) => {
export const randomString = () => crypto.randomBytes(11).toString('hex');

export const hashPassword = password => bcrypt.hash(password, 10);

export const findRatedArticle = async ratingParams => db.Article.findOne(ratingParams);

export const avgRating = async (articleId) => {
let avg = await (db.Ratings.findOne({
where: { articleId },
attributes: [[db.sequelize.fn('AVG',
db.sequelize.col('stars')), 'avgRating']],
group: ['articleId'],
order: [[db.sequelize.fn('AVG', db.sequelize.col('stars')), 'DESC']]
}));
avg = parseFloat(avg.dataValues.avgRating).toFixed(2);
return avg;
};

export const storeRating = async (foundArticleId) => {
const articleAvg = await avgRating(foundArticleId);
const getArticle = await db.Article.findOne({
where: { id: foundArticleId },
});
getArticle.update({
rating: parseFloat(articleAvg).toFixed(2)
});
};

0 comments on commit eda6d2b

Please sign in to comment.