Skip to content

Commit

Permalink
feat(like-dislike): like or dislike an article (#26)
Browse files Browse the repository at this point in the history
- create an endpoint to like or dislike a specific article

[Finishes #166789881]
  • Loading branch information
Emile-Nsengimana authored and emukungu committed Jul 22, 2019
1 parent 88c9729 commit 71dbc80
Show file tree
Hide file tree
Showing 11 changed files with 1,373 additions and 1,050 deletions.
10 changes: 5 additions & 5 deletions src/controllers/articleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { Users, Articles, Tag } = model;
/**
* artticle controller
*/
class articleManager {
class ArticleManager {
/**
*
* @param {object} req
Expand Down Expand Up @@ -71,7 +71,7 @@ class articleManager {
*
* @param {object} req
* @param {object} res
* @returns{object} acknowledgement message for delete action result
* @returns {object} acknowledgement message for delete action result
*/
static async removeArticle(req, res) {
try {
Expand All @@ -94,7 +94,7 @@ class articleManager {
*
* @param {object} req
* @param {object} res
* @returns{object} a single article
* @returns {object} a single article
*/
static async readArticle(req, res) {
try {
Expand Down Expand Up @@ -123,7 +123,7 @@ class articleManager {
*
* @param {object} req
* @param {object} res
* @returns{object} updated article
* @returns {object} updated article
*/
static async updateArticle(req, res) {
try {
Expand Down Expand Up @@ -179,4 +179,4 @@ class articleManager {
}
}
}
export default articleManager;
export default ArticleManager;
152 changes: 152 additions & 0 deletions src/controllers/likeDislikeArticleController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import model from '../db/models/index';

const { likeDislikes } = model;

/**
* like/dislike controller
*/
class LikeDislike {
/**
*
* @param {string} slug
* @returns {integer} number of likes
*/
static async countLikesDislikes(slug) {
const likes = await likeDislikes.findAll({ where: { slug, like: true } });
const disLikes = await likeDislikes.findAll({ where: { slug, dislike: true } });
return ([likes.length, disLikes.length]);
}

/**
*
* @param {string} slug
* @param {integer} id
* @param {object} liked
* @returns {object} like/dislike info
*/
static async revertLikeArticleAction(slug, id, liked) {
await likeDislikes.update({ like: liked }, { where: { slug, userId: id } });
const likesDislikes = await LikeDislike.countLikesDislikes(slug);
const articleLikeDislikeInfo = {
liked,
disLiked: false,
likes: likesDislikes[0],
disLikes: likesDislikes[1]
};
return articleLikeDislikeInfo;
}

/**
*
* @param {integer} id
* @param {string} slug
* @param {string} likedOrDisliked
* @returns {object} like/dislike info
*/
static async createNewLikeOrDislike(id, slug, likedOrDisliked) {
if (likedOrDisliked === 'like') {
const newArticleLike = { userId: id, slug, like: true };
const likeArticle = await likeDislikes.create(newArticleLike);
const { like, dislike } = likeArticle.dataValues;
const likesDislikes = await LikeDislike.countLikesDislikes(slug);
const isLiked = {
liked: like,
disliked: dislike,
likes: likesDislikes[0],
dislikes: likesDislikes[1]
};
return isLiked;
}
const newArticleDislike = { userId: id, slug, dislike: true };
const dislikeArticle = await likeDislikes.create(newArticleDislike);
const { like, dislike } = dislikeArticle.dataValues;
const likesDislikes = await LikeDislike.countLikesDislikes(slug);
const isDisliked = {
liked: like,
disliked: dislike,
likes: likesDislikes[0],
dislikes: likesDislikes[1]
};
return isDisliked;
}

/**
*
* @param {object} req
* @param {object} res
* @returns {object} likes
*/
static async likeArticle(req, res) {
const { slug } = req.params;
const { id } = req.user;
const userReactedOnArticle = await likeDislikes.findOne({ where: { slug, userId: id } });
if (userReactedOnArticle) {
const { like: liked, dislike: disliked } = userReactedOnArticle.dataValues;
if (disliked) {
await LikeDislike.revertDisLikeArticleAction(slug, id, false);
const likeArticle = await LikeDislike.revertLikeArticleAction(slug, id, true);
return res.status(200).json(likeArticle);
}
if (liked) {
const unLikeArticle = await LikeDislike.revertLikeArticleAction(slug, id, false);
return res.status(200).json(unLikeArticle);
}
const likeArticle = await LikeDislike.revertLikeArticleAction(slug, id, true);
return res.status(200).json(likeArticle);
}
const newArticleLike = await LikeDislike.createNewLikeOrDislike(id, slug, 'like');
return res.status(200).json(newArticleLike);
}


/**
*
* @param {string} slug
* @param {integer} id
* @param {object} disLiked
* @returns {object} like/dislike info
*/
static async revertDisLikeArticleAction(slug, id, disLiked) {
await likeDislikes.update({ dislike: disLiked }, { where: { slug, userId: id } });
const likesDislikes = await LikeDislike.countLikesDislikes(slug);
const articleLikeDislikeInfo = {
liked: false,
disLiked,
likes: likesDislikes[0],
disLikes: likesDislikes[1]
};
return articleLikeDislikeInfo;
}

/**
*
* @param {object} req
* @param {object} res
* @returns {object} likes
*/
static async dislikeArticle(req, res) {
const userReactedOnArticle = await likeDislikes
.findOne({ where: { slug: req.params.slug, userId: req.user.id } });
if (userReactedOnArticle) {
const { like: liked, dislike: disliked } = userReactedOnArticle.dataValues;
if (liked) {
await LikeDislike.revertLikeArticleAction(req.params.slug, req.user.id, false);
const disLikeArticle = await LikeDislike
.revertDisLikeArticleAction(req.params.slug, req.user.id, true);
return res.status(200).json(disLikeArticle);
}
if (disliked) {
const unDisLikeArticle = await LikeDislike
.revertDisLikeArticleAction(req.params.slug, req.user.id, false);
return res.status(200).json(unDisLikeArticle);
}
const disLikeArticle = await LikeDislike
.revertDisLikeArticleAction(req.params.slug, req.user.id, true);
return res.status(200).json(disLikeArticle);
}
const newArticleDislike = await LikeDislike.createNewLikeOrDislike(req.user.id, req.params.slug, 'dislike');
return res.status(200).json(newArticleDislike);
}
}

export default LikeDislike;
38 changes: 38 additions & 0 deletions src/db/migrations/20190716155850-create-like-dislikes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('likeDislikes', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER
},
slug: {
type: Sequelize.STRING
},
like: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
dislike: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('likeDislikes');
}
};
13 changes: 13 additions & 0 deletions src/db/models/likedislikes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const likeDislikes = sequelize.define('likeDislikes', {
userId: DataTypes.INTEGER,
slug: DataTypes.STRING,
like: DataTypes.BOOLEAN,
dislike: DataTypes.BOOLEAN
}, {});
likeDislikes.associate = function(models) {
// associations can be defined here
};
return likeDislikes;
};
12 changes: 9 additions & 3 deletions src/db/seeders/20190710155910-Articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ module.exports = {
slug: 'TIA',
createdAt: moment.utc().format(),
updatedAt: moment.utc().format()
},
{
},{
title: 'Delete this',
body: 'This is Andela',
description: 'From the heart and deep on the soul of young African software engineers',
authorId: 1,
slug: 'dropTIA',
createdAt: moment.utc().format(),
updatedAt: moment.utc().format()
},{
title: 'like dislike',
body: 'This is Andela',
description: 'From the heart and deep on the soul of young African software engineers',
authorId: 1,
slug: 'like-africa',
createdAt: moment.utc().format(),
updatedAt: moment.utc().format()
}], {});
},

down: (queryInterface, Sequelize) => {}
};
26 changes: 26 additions & 0 deletions src/db/seeders/20190716184533-likeDislikes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import moment from 'moment';

'use strict';

module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('likeDislikes', [{
userId: 1,
slug: 'TIA',
like: true,
dislike: false,
createdAt: moment.utc().format(),
updatedAt: moment.utc().format()
},
{
userId: 1,
slug: 'TIA2',
like: false,
dislike: false,
createdAt: moment.utc().format(),
updatedAt: moment.utc().format()
}], {});
},

down: (queryInterface, Sequelize) => {}
};
16 changes: 16 additions & 0 deletions src/middlewares/articleValidation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import Joi from '@hapi/joi';
import model from '../db/models/index';

const { Articles } = model;

/**
* rating article validaton
Expand Down Expand Up @@ -32,5 +35,18 @@ class ArticleRatingValidation {
req.rate = checkRating.value;
next();
}

/**
* @param {object} req
* @param {object} res
* @param {object} next
* @returns {Object} descriptive error message
*/
static async checkSlug(req, res, next) {
const { slug } = req.params;
const findArticle = await Articles.findOne({ where: { slug } });
if (!findArticle) { return res.status(404).json({ error: 'article not found' }); }
next();
}
}
export default ArticleRatingValidation;
8 changes: 5 additions & 3 deletions src/routes/api/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import auth from '../../middlewares/auth';
import checkUser from '../../middlewares/checkUser';
import { multerUploads } from '../../middlewares/multer';
import { cloudinaryConfig } from '../../db/config/cloudinaryConfig';

import likeDislikeController from '../../controllers/likeDislikeArticleController';
import articleRatingControllers from '../../controllers/articleRatingControllers';
import ArticleRatingValidation from '../../middlewares/articleValidation';
import articleValidation from '../../middlewares/articleValidation';

const router = express.Router();
router.use('*', cloudinaryConfig);
Expand All @@ -16,7 +16,9 @@ router.delete('/article/:slug', auth.checkAuthentication, checkUser.isArticleOwn
router.get('/articles/:slug', articleController.readArticle);
router.put('/articles/:slug', auth.checkAuthentication, checkUser.isArticleOwner, multerUploads, articleController.updateArticle);
router.get('/articles', articleController.listAllArticles);
router.post('/articles/:slug/ratings', auth.checkAuthentication, ArticleRatingValidation.rating, articleRatingControllers.rateArticle);
router.post('/articles/:slug/ratings', auth.checkAuthentication, articleValidation.rating, articleRatingControllers.rateArticle);
router.get('/ratings/:articleId', articleRatingControllers.ratingAverage);
router.post('/articles/like/:slug', auth.checkAuthentication, articleValidation.checkSlug, likeDislikeController.likeArticle);
router.post('/articles/dislike/:slug', auth.checkAuthentication, articleValidation.checkSlug, likeDislikeController.dislikeArticle);

export default router;
Loading

0 comments on commit 71dbc80

Please sign in to comment.