Skip to content

Commit

Permalink
feat(comment): like specific comment
Browse files Browse the repository at this point in the history
- Add endpoint for liking a comment
- Add option of get results
- Add tests
- Add documentation
[Delivers #165413123]
  • Loading branch information
Niyitangasam committed May 21, 2019
1 parent e6842c7 commit ba80399
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 3 deletions.
35 changes: 35 additions & 0 deletions controllers/comment.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,41 @@ class Comments {
const result = await commentHelper.updateOneReply(req);
return res.status(201).send(result);
}

/**
* @param {object} req - Request object
* @param {object} res - Response object
* @returns {object} response
* @static
*/
static async reactOnComment(req, res) {
const result = await commentHelper.createFeedback(req);
const feedbackCreated = result.toJSON();
return res.status(201).send({ feedback: feedbackCreated });
}

/**
* @param {object} req - Request object
* @param {object} res - Response object
* @returns {object} response
* @static
*/
static async getLikes(req, res) {
const { numberOfLikes } = req.body;
const result = await commentHelper.getLikes(req);
return res.status(200).send({ likes: result, count: numberOfLikes });
}

/**
* @function deleteAFeedback
* @param {Object} req
* @param {Object} res
* @returns {Object} Object with one comment on an article and its author
*/
static async deleteFeedback(req, res) {
await commentHelper.deleteFeedback(req);
return res.send({ message: { body: ['Deleted successfully'] } });
}
}

export default Comments;
137 changes: 135 additions & 2 deletions helpers/commentHelper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import joi from 'joi';
import model from '../models';

const { User, Article, Comment } = model;
const {
User, Article, Comment, CommentFeedback
} = model;

/**
* @exports CommentHelper
Expand Down Expand Up @@ -109,7 +111,8 @@ class CommentHelper {
const { slug } = req.params;
const fetchedComments = await Comment.findAll({
where: { titleSlug: slug },
include: [{ model: User, as: 'author', attributes: ['username', 'bio', 'image'] }],
include: [{ model: User, as: 'author', attributes: ['username', 'bio', 'image'] },
{ model: CommentFeedback, as: 'like', attributes: ['feedback', 'userId'] }],
attributes: ['id', 'createdAt', 'updatedAt', 'body']
});
if (!fetchedComments[0]) {
Expand Down Expand Up @@ -306,6 +309,136 @@ class CommentHelper {
const updatedReply = await Comment.findOne({ where: { id: replyId } });
return { response: updatedReply };
}

/**
* Add feedback on article (Either like or neutral)
* @param {object} req - an object
* @return {Object} Return object of feedback created
* @static
*/
static async createFeedback(req) {
const { id } = req.user;
const commentId = req.params.id;
const { option } = req.params;
const feedbackCreated = await CommentFeedback.create({
userId: id,
commentId,
feedback: option
});
return feedbackCreated;
}

/**
* Check if it is valid option
* @param {object} req - an object
* @param {object} res - an object
* @param {object} next - an object
* @return {boolean} Returns if true if it is valid else return false
* @static
*/
static isValidOption(req, res, next) {
if (req.params.option !== 'like') {
return res.status(422).send({ status: 422, Error: 'Only option must only be \'like\'' });
}
next();
}

/**
* Check if comment exist and the user who created it
* @param {object} req - an object
* @param {object} res - an object
* @param {object} next - an object
* @return {boolean} Returns if true if it is valid else return a message explaining an error
* @static
*/
static async isItValidComment(req, res, next) {
const { option } = req.params;
const { id } = req.params;
const userId = req.user.id;
const comment = await Comment.findOne({ where: { id } });
if (!comment) {
return res.status(404).send({ errors: { body: ['comment not found'] } });
}
const commentFeedback = await CommentFeedback
.findOne({ where: { commentId: comment.id, userId } });
if (!commentFeedback) {
next();
}
if (option === 'like' && commentFeedback.feedback === 'like') {
await CommentFeedback.update({ feedback: 'neutral' }, { where: { commentId: id, userId } });
return res.status(200).send({ message: 'Your like become neutral' });
}
await CommentFeedback.update({ feedback: 'like' }, { where: { commentId: id, userId } });
return res.status(200).send({ message: 'your liked a comment' });
}

/**
* Calculate number of likes per comment
* @function likesNumber
* @param {object} req
* @param {object} res
* @param {object} next
* @returns { number } number of comments
*/
static async likesNumber(req, res, next) {
const { id } = req.params;
const result = await CommentFeedback.findAndCountAll({
where: { commentId: id, feedback: 'like' },
});
req.body.numberOfLikes = result.count;
next();
return true;
}

/**
* Get likes
* @param {object} req - an object
* @return {Object} Returns an object
* @static
*/
static async getLikes(req) {
const { id } = req.params;
const likesFetched = await CommentFeedback.findAll({
where: { commentId: id, feedback: 'like' },
include: [{ model: User, as: 'liked', attributes: ['username', 'bio', 'image'] },
{ model: Comment, as: 'like', attributes: ['titleSlug', 'body'] },
],
});
return likesFetched;
}

/**
* Check if comment feedback exist
* @param {object} req - an object
* @param {object} res - an object
* @param {object} next - an object
* @return {boolean} Returns if true if fedback exists
* @static
*/
static async isFeedbackExist(req, res, next) {
const { id } = req.params;
const commentFeedback = await CommentFeedback.findOne({ where: { id } });
if (!commentFeedback) {
return res.status(404).send({ errors: { body: ['No feedback found'] } });
}
if (commentFeedback.userId !== req.user.id) {
return res.status(404).send({ errors: { body: ['No authorized, Only Owner can delete it'] } });
}
next();
}

/**
* Delete comment feedback
* @param {object} req - an object
* @param {object} res - an object
*@return {boolean} Return true if deleted
*/
static async deleteFeedback(req) {
const { id } = req.params;
const deleteFeedback = await
CommentFeedback.destroy({ where: { id }, returning: true });
return deleteFeedback;
}
}

export default CommentHelper;
42 changes: 42 additions & 0 deletions migrations/20190517181756-create-comment-feedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const commentFeedbacksMigrations = {
up: (queryInterface, Sequelize) => queryInterface.createTable('CommentFeedbacks', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Users',
key: 'id'
}
},
commentId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Comments',
key: 'id'
}
},
feedback: {
type: Sequelize.ENUM,
values: ['like', 'neutral']
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: queryInterface => queryInterface.dropTable('CommentFeedbacks')
};
export default commentFeedbacksMigrations;
1 change: 1 addition & 0 deletions models/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const comments = (sequelize, DataTypes) => {
onUpdate: 'CASCADE'
});
Comment.hasMany(models.Comment, { foreignKey: 'replyid' });
Comment.hasMany(models.CommentFeedback, { as: 'like', foreignKey: 'commentId' });
};
return Comment;
};
Expand Down
26 changes: 26 additions & 0 deletions models/commentfeedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const commentFeedbacks = (sequelize, DataTypes) => {
const CommentFeedback = sequelize.define('CommentFeedback', {
userId: DataTypes.INTEGER,
commentId: DataTypes.INTEGER,
feedback: DataTypes.ENUM({
values: ['like', 'neutral']
})
}, {});
CommentFeedback.associate = (models) => {
CommentFeedback.belongsTo(models.User, {
foreignKey: 'userId',
as: 'liked',
onDelete: 'CASCADE',
onupdate: 'CASCADE'
});
CommentFeedback.belongsTo(models.Comment, {
foreignKey: 'commentId',
as: 'like',
targetKey: 'id',
onDelete: 'CASCADE',
onupdate: 'CASCADE'
});
};
return CommentFeedback;
};
export default commentFeedbacks;
1 change: 1 addition & 0 deletions models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const users = (sequelize, DataTypes) => {
User.hasMany(models.Follows, { foreignKey: 'follower' });
User.hasMany(models.Notification, { as: 'user', foreignKey: 'userId' });
User.hasMany(models.Share, { foreignKey: 'userId' });
User.hasMany(models.CommentFeedback, { as: 'liked', foreignKey: 'userId' });
};
return User;
};
Expand Down
1 change: 1 addition & 0 deletions routes/api/comment/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ router.get('/:id/replies', Auth, comment.getCommentReplies);
router.delete('/:id/reply/:replyId', Auth, commentHelper.isReplytExist, comment.deleteOneReply);
router.post('/:id/reply/:replyId', Auth, commentHelper.isReplytExist, comment.updateOneReply);


export default router;
61 changes: 61 additions & 0 deletions routes/api/comment/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,64 @@
* '400':
* The reply have failed to be created
*/

/**
* @swagger
* /comment/{id}/likes:
* get:
* tags:
* - like
* name: like
* summary: get likes on a comment
* produces:
* - application/json
* consumes:
* - application/json
* parameters:
* - name: id
* in: path
* schema:
* type:integer
* required:
* - id
* responses:
* '200':
* description: likes have been fetched successfully
* '400':
* likes have been failed to be fetched
*/
/**
* @swagger
* /comment/{id}/feedback/{like}:
* post:
* tags:
* - like
* name: like
* summary: like a comment
* produces:
* - application/json
* consumes:
* - application/json
* parameters:
* - name: like
* in: path
* schema:
* type: string
* required:
* - like
* - name: id
* in: path
* schema:
* type: integer
* required:
* - id
* - name: authorization
* in: header
* schema:
* type: string
* required:
* - authorization
* responses:
* '200':
* description: Successfully liked a comment
*/
12 changes: 12 additions & 0 deletions routes/api/comment/like.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import express from 'express';
import Auth from '../../../middlewares/auth';
import comment from '../../../controllers/comment.controller';
import commentHelper from '../../../helpers/commentHelper';

const router = express.Router();
router.delete('/:id', Auth, commentHelper.isFeedbackExist, comment.deleteFeedback);
router.post('/:id/feedback/:option', Auth, commentHelper.isValidOption, commentHelper.isItValidComment, comment.reactOnComment);
router.get('/:id/likes', commentHelper.likesNumber, comment.getLikes);


export default router;
3 changes: 2 additions & 1 deletion routes/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import users from './profile/profile';
import notifications from './auth/notification';
import article from './article/articles';
import comment from './comment/comment';
import likes from './comment/like';

const router = express.Router();

Expand All @@ -17,7 +18,7 @@ router.use('/login', auth);
router.use('/articles', article);
router.use('/article', comment);
router.use('/comment', comment);

router.use('/comments', likes);
router.use((err, req, res, next) => {
if (err.name === 'ValidationError') {
return res.status(422).json({
Expand Down
Loading

0 comments on commit ba80399

Please sign in to comment.