Skip to content

Commit

Permalink
feat(comment): implement delete comment
Browse files Browse the repository at this point in the history
- ensure only authenticated users can delete comments
- ensure that users can only delete their own comments

[Finishes #164403025]
  • Loading branch information
Chrismarcel committed Mar 6, 2019
1 parent 0524f71 commit 1d08aa7
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 29 deletions.
36 changes: 32 additions & 4 deletions server/controllers/articleComment.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ class Comment {
const articleId = article.id;
const userComment = await ArticleComment.create({ articleId, comment, userId });
delete userComment.dataValues.id;
return response(res).created({ userComment });
return response(res).created({
message: 'Comment created successfully',
userComment
});
} catch (error) {
return response(res).serverError({ errors: { server: ['database error'] } });
}
Expand All @@ -55,12 +58,37 @@ class Comment {
where: {
id
},
returning: true,
raw: true
returning: true
});
const userComment = articleUpdate[1][0];
delete userComment.id;
return response(res).success({ userComment });
return response(res).success({
message: 'Comment updated successfully',
userComment
});
} catch (error) {
return response(res).serverError({ errors: { server: ['database error'] } });
}
}

/**
* @method deleteComment
* @description - Deletes comment
* @param {object} req - The request object
* @param {object} res - The response object
* @returns {object} - The updated article object
*/
static async deleteComment(req, res) {
const { commentRow } = req;

try {
const { id } = commentRow;
await ArticleComment.destroy({
where: {
id
}
});
return response(res).success({ message: 'Comment has been deleted successfully.' });
} catch (error) {
return response(res).serverError({ errors: { server: ['database error'] } });
}
Expand Down
6 changes: 4 additions & 2 deletions server/database/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ dotenv.config();
module.exports = {
development: {
use_env_variable: 'DATABASE_URL',
dialect: 'postgres'
dialect: 'postgres',
logging: false
},
test: {
use_env_variable: 'DATABASE_URL',
dialect: 'postgres'
dialect: 'postgres',
logging: false
},
production: {
use_env_variable: 'DATABASE_URL',
Expand Down
54 changes: 41 additions & 13 deletions server/middlewares/ValidateComment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { check, validationResult } from 'express-validator/check';
import { response } from '../utils';
import models from '../database/models';

const { ArticleComment } = models;
const { ArticleComment, Article } = models;

/**
* @class ValidateComment
Expand All @@ -13,17 +13,20 @@ class ValidateComment {
/**
* @method validateMethods
* @description Validates registration details provided by user
* @param {object} commentId - Boolean to check if comment id is specified
* @param {string} requestType - The request type being sent
* @param {boolean} commentId - Boolean to check if comment id is specified
* @returns {array} - Array of validation methods
*/
static validateMethods(commentId = false) {
const propsToValidate = [
check('comment')
static validateMethods(requestType, commentId = false) {
const propsToValidate = [];

if (requestType === 'update' || requestType === 'create') {
propsToValidate.push([check('comment')
.exists()
.withMessage('Comment field must be specified.')
.isLength({ min: 1 })
.withMessage('Comment must not be empty.')
];
.withMessage('Comment must not be empty.')]);
}

if (commentId) {
propsToValidate.push([
Expand All @@ -47,26 +50,51 @@ class ValidateComment {
* @returns {object} - JSON response object
*/
static async validateComment(req, res, next) {
const { commentId } = req.params;
const { commentId, slug } = req.params;
const errorFormatter = ({ msg }) => [msg];
const errorMessages = validationResult(req).formatWith(errorFormatter);

if (!errorMessages.isEmpty()) {
return response(res).badRequest({ errors: errorMessages.mapped() });
}

if (commentId) {
const commentRow = await ValidateComment.doesCommentExist(commentId, res);
if (!commentRow || slug !== commentRow['Article.slug']) {
return response(res).notFound({ errors: { comment: ['Comment not found.'] } });
}

if (commentRow.userId !== req.user.id) {
return response(res).forbidden({
errors: {
comment: ['You are not permitted to make changes to this article']
}
});
}
req.commentRow = commentRow;
}
next();
}

/**
* @method doesCommentExist
* @param {integer} commentId - The comment ID
* @param {object} res - The response object
* @returns {Promise} Article Comment Promise
*/
static async doesCommentExist(commentId, res) {
try {
const commentRow = await ArticleComment.findOne({
include: [{
model: Article,
attributes: ['slug']
}],
where: {
id: commentId
},
raw: true
});
if (commentId && !commentRow) {
return response(res).notFound({ errors: { comment: ['Comment not found.'] } });
}
req.commentRow = commentRow;
return next();
return commentRow;
} catch (error) {
return response(res).serverError({ errors: { server: ['database error'] } });
}
Expand Down
8 changes: 7 additions & 1 deletion server/middlewares/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import AuthenticateUser from './AuthenticateUser';
import ValidateUser from './ValidateUser';
import ValidateComment from './ValidateComment';
import AuthenticateArticle from './AuthenticateArticle';

export { AuthenticateUser, ValidateUser, ValidateComment };
export {
AuthenticateUser,
ValidateUser,
ValidateComment,
AuthenticateArticle
};
16 changes: 12 additions & 4 deletions server/routes/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import express from 'express';
import { ArticleController, Comment } from '../controllers';
import createArticleValidation from '../validations/create-article';
import ArticleCommentLikeController from '../controllers/article-comment-like';
import { AuthenticateUser, ValidateComment } from '../middlewares';
import { AuthenticateUser, ValidateComment, AuthenticateArticle } from '../middlewares';
import rateArticleValidation from '../validations/rate-article';
import AuthenticateArticle from '../middlewares/AuthenticateArticle';

const router = express.Router();

Expand All @@ -16,7 +15,8 @@ router.post('/articles',
controller.create.bind(controller));
router.post('/articles/:slug/comment',
AuthenticateUser.verifyUser,
ValidateComment.validateMethods(),
AuthenticateArticle.verifyArticle,
ValidateComment.validateMethods('create', false),
ValidateComment.validateComment,
Comment.postComment);

Expand All @@ -42,8 +42,16 @@ router.get('/articles/rating/:slug',

router.patch('/articles/:slug/comment/:commentId',
AuthenticateUser.verifyUser,
ValidateComment.validateMethods(true),
AuthenticateArticle.verifyArticle,
ValidateComment.validateMethods('update', true),
ValidateComment.validateComment,
Comment.updateComment);

router.delete('/articles/:slug/comment/:commentId',
AuthenticateUser.verifyUser,
AuthenticateArticle.verifyArticle,
ValidateComment.validateMethods('delete', true),
ValidateComment.validateComment,
Comment.deleteComment);

export default router;
40 changes: 35 additions & 5 deletions server/test/comment.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { expect } = chai;

let userToken;

describe('POST comment /api/articles/:id/comment', () => {
describe('POST comment /api/articles/:slug/comment', () => {
before((done) => {
chai
.request(app)
Expand All @@ -34,9 +34,8 @@ describe('POST comment /api/articles/:id/comment', () => {
comment: 'This is a random comment'
})
.end((err, res) => {
const { articleId, comment } = res.body.userComment;
const { comment } = res.body.userComment;
expect(res.status).to.be.equal(201);
expect(articleId).to.be.equal(1);
expect(comment).to.be.equal('This is a random comment');
done(err);
});
Expand Down Expand Up @@ -98,9 +97,8 @@ describe('PATCH update comment', () => {
comment: 'This is an updated random comment'
})
.end((err, res) => {
const { articleId, comment } = res.body.userComment;
const { comment } = res.body.userComment;
expect(res.status).to.be.equal(200);
expect(articleId).to.be.equal(1);
expect(comment).to.be.equal('This is an updated random comment');
done(err);
});
Expand Down Expand Up @@ -149,3 +147,35 @@ describe('PATCH update comment', () => {
});
});
});

describe('DELETE user comment', () => {
before((done) => {
chai
.request(app)
.post('/api/users')
.send({
firstname: 'Chris',
lastname: 'James',
email: 'chreez3@gmail.com',
username: 'Chreez3',
password: '12345678'
})
.end((err, res) => {
const { token } = res.body.user;
userToken = token;
done(err);
});
});

it('should delete a comment', (done) => {
chai.request(app)
.delete('/api/articles/this-is-an-article-1/comment/1')
.set('authorization', `Bearer ${userToken}`)
.end((err, res) => {
const { message } = res.body;
expect(res.status).to.be.equal(200);
expect(message).to.be.equal('Comment has been deleted successfully.');
done(err);
});
});
});

0 comments on commit 1d08aa7

Please sign in to comment.