Skip to content
This repository has been archived by the owner on Jul 20, 2020. It is now read-only.

Commit

Permalink
feat(deleteComment): user can delete his comment
Browse files Browse the repository at this point in the history
added tests for the feature
added a controller for deleting a comment
[Maintains #170947563]
  • Loading branch information
Baraka-Mugisha committed Mar 16, 2020
1 parent 79c2e6e commit 1ca1e39
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 22 deletions.
27 changes: 26 additions & 1 deletion src/controllers/commentController.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid from 'uuid/v4';
import db from '../models';
import Response from '../utils/ResponseHandler';
import notifService from '../services/notificationService';
Expand Down Expand Up @@ -25,8 +26,9 @@ export default class CommentController {
const { email } = req.managerDetails;
const newComment = await db.Comments.create({
requestId,
commmentOwner: user.id,
commentOwner: user.id,
comment,
id: uuid()
});
if (user.role === 'manager') {
const notifyUser = await notifService.createNotif(req.request.userId, req.request.email, 'your manager posted a comment', '#');
Expand All @@ -52,4 +54,27 @@ export default class CommentController {
return Response.errorResponse(res, 500, res.__('server error'));
}
}

/**
* @param {object} req
* @param {object} res
* @param {object} next
* @return {object} delete comment
*/
static async deleteComment(req, res) {
try {
const { commentId } = req.params;
await db.Comments.update({
deleted: true
}, {
where: {
id: commentId
}
});

return Response.success(res, 200, res.__('Comment is successfully deleted'));
} catch (err) {
return Response.errorResponse(res, 500, res.__('server error'));
}
}
}
25 changes: 24 additions & 1 deletion src/middlewares/protectRoute.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import localStorage from 'localStorage';
import db from '../models';
import Response from '../utils/ResponseHandler';
import { verifyToken } from '../utils/tokenHandler';
Expand Down Expand Up @@ -178,6 +177,30 @@ export default class protectRoutes {
next();
}

/**
* @description check if comment exists
* @param {object} req
* @param {object} res
* @param {object} next
* @returns {object} user
*/
static async checkComment(req, res, next) {
const { user } = req;
const { commentId } = req.params;

const commentExist = await db.Comments.findOne({
where: { id: commentId, deleted: false },
});
if (!commentExist) {
return Response.errorResponse(res, 404, res.__('Comment not found'));
}
if (user.id !== commentExist.commentOwner) {
return Response.success(res, 401, res.__('You are not authorised to delete this comment'));
}
req.comment = commentExist;
next();
}

/**
* @param {object} req
* @param {object} res
Expand Down
10 changes: 7 additions & 3 deletions src/migrations/20200228161331-create-comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ module.exports = {
return queryInterface.createTable('Comments', {
id: {
allowNull: false,
autoIncrement: true,
type: Sequelize.INTEGER,
type: Sequelize.STRING,
primaryKey: true
},
commmentOwner: {
commentOwner: {
type: Sequelize.STRING,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
Expand All @@ -32,6 +31,11 @@ module.exports = {
comment: {
type: Sequelize.TEXT
},
deleted: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
Expand Down
7 changes: 4 additions & 3 deletions src/models/comments.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module.exports = (sequelize, DataTypes) => {
const Comments = sequelize.define('Comments', {
requestId: DataTypes.STRING,
commmentOwner: DataTypes.STRING,
comment: DataTypes.TEXT
commentOwner: DataTypes.STRING,
comment: DataTypes.TEXT,
deleted: DataTypes.BOOLEAN
}, {});
Comments.associate = (models) => {
Comments.belongsTo(models.Request, {
Expand All @@ -11,7 +12,7 @@ module.exports = (sequelize, DataTypes) => {
onUpdate: 'CASCADE',
});
Comments.belongsTo(models.User, {
foreignKey: 'commmentOwner',
foreignKey: 'commentOwner',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
Expand Down
2 changes: 1 addition & 1 deletion src/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports = (sequelize, DataTypes) => {
onUpdate: 'CASCADE',
});
User.hasMany(models.Comments, {
foreignKey: 'commmentOwner',
foreignKey: 'commentOwner',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
Expand Down
9 changes: 7 additions & 2 deletions src/routes/authRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import AuthController from '../controllers/authController';
import validationResult from '../validation/validationResult';
import verificationController from '../controllers/verificationController';
import validateParams from '../validation/validateParams';
import { signupInputRules, resetPasswordRules, forgotPasswordRules } from '../validation/validationRules';
import {
signupInputRules,
resetPasswordRules,
forgotPasswordRules,
loginRules
} from '../validation/validationRules';
import protectRoute from '../middlewares/protectRoute';

const authRouter = express.Router();

authRouter.post('/register', signupInputRules, validationResult, AuthController.registerUser);
authRouter.get('/verification', validateParams.validateToken, verificationController.verifyAccount);
authRouter.post('/login', AuthController.login);
authRouter.post('/login', loginRules, validationResult, AuthController.login);
authRouter.get('/logout', AuthController.logout);

authRouter.put('/password/forgot', forgotPasswordRules, validationResult, AuthController.forgotPassword);
Expand Down
3 changes: 2 additions & 1 deletion src/routes/commentsRoutes.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from 'express';
import commentsController from '../controllers/commentController';
import validationResult from '../validation/validationResult';
import { commentRules } from '../validation/validationRules';
import { commentRules, commentIdRules } from '../validation/validationRules';
import protectRoute from '../middlewares/protectRoute';

const router = express.Router();

router.post('/:requestId/post', protectRoute.verifyUser, protectRoute.checkRequestDetails, commentRules, validationResult, commentsController.postComment);
router.delete('/:commentId', protectRoute.verifyUser, commentIdRules, validationResult, protectRoute.checkComment, commentsController.deleteComment);

export default router;
13 changes: 11 additions & 2 deletions src/services/localesServices/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@
"To view this As a your manager I can add any comment on your request. I can also cancel or approve your request but let me comment first click below": "To view this As a your manager I can add any comment on your request. I can also cancel or approve your request but let me comment first click below",
"To view this %s click below": "To view this %s click below",
"Your Manager commented: %s": "Your Manager commented: %s",
"Your requsester commented: %s": "Your requsester commented: %s",
"Your requester commented: %s": "Your requester commented: %s"
"Your requester commented: %s": "Your requester commented: %s",
"Comment not found": "Comment not found",
"You are not authorised to delete this comment": "You are not authorised to delete this comment",
"Comment is successfully deleted": "Comment is successfully deleted",
"a commentId is required with maximum 200 characters": "a commentId is required with maximum 200 characters",
"The password is required": "The password is required",
"The email is required": "The email is required",
"Invalid value": "Invalid value",
"password can not be empty": "password can not be empty",
"email can not be empty": "email can not be empty",
"a comment is required with maximum 200 characters": "a comment is required with maximum 200 characters"
}
6 changes: 5 additions & 1 deletion src/services/localesServices/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,9 @@
"your manager posted a comment: %s": "votre manager a posté un commentaire: %s",
"Your Manager commented: %s": "Votre demandeur a commenté: %s",
"View comment": "Afficher le commentaire",
"your manager posted a comment": "votre manager a posté un commentaire"
"your manager posted a comment": "votre manager a posté un commentaire",
"Comment not found": "Commentaire non trouvé",
"You are not authorised to delete this comment": "Vous n'êtes pas autorisé à supprimer ce commentaire",
"Comment is successfully deleted": "Le commentaire a bien été supprimé",
"a comment is required with maximum 200 characters": "un commentaire est requis avec un maximum de 200 caractères"
}
37 changes: 37 additions & 0 deletions src/swagger/comments.swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* @swagger
* /api/v1/comments/{requestId}/post:
* post:
* security:
* - bearerAuth: []
* tags:
* - Comments
* name: post a comment on a request
Expand All @@ -20,6 +22,9 @@
* in: body
* schema:
* type: object
* properties:
* comment:
* type: string
* properties:
* comment:
* type: string
Expand All @@ -31,3 +36,35 @@
* '401':
* description: This request belongs to another user and manager.
* */

/**
* @swagger
* /api/v1/comments/{commentId}:
* delete:
* security:
* - bearerAuth: []
* tags:
* - Comments
* name: delete a comment on a request
* summary: Requester and manager should be able to delete a comment they made
* produces:
* - application/json
* consumes:
* - application/json
* parameters:
* - name: token
* in: header
* description: jwt token of the user
* - name: commentId
* in: path
* properties:
* commentId:
* type: string
* required:
* - commentId
* responses:
* '200':
* description: Comment is successfully deleted.
* '404':
* description: Comment not found
* */
33 changes: 29 additions & 4 deletions src/tests/comments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
expect
} = chai;
dotenv.config();
let requesterToken, managerToken, wrongManagerToken;
let requesterToken, managerToken, wrongManagerToken, commentId;
const requestId = '51e74db7-5510-4f50-9f15-e23710331ld5';
const wrongRequestId = '51e74db7-5510-4f50-9f15-e23710331ld555';
chai.use(chaiHttp);
Expand Down Expand Up @@ -108,15 +108,15 @@ describe('COMMENTS TESTS', () => {
expect(res.body.message).to.equal('Comment is successfully posted');
expect(res.body).to.have.property('data');
expect(res.body.data.requestId).to.equal('51e74db7-5510-4f50-9f15-e23710331ld5');
expect(res.body.data.commmentOwner).to.equal('0119b84a-99a4-41c0-8a0e-6e0b6c385165');
expect(res.body.data.commentOwner).to.equal('0119b84a-99a4-41c0-8a0e-6e0b6c385165');
expect(res.body.data.comment).to.equal('As a your manager I can add any comment on your request. I can also cancel or approve your request but let me comment first');
done();
});
clientSocket.on('notification', (msg) => {
expect(JSON.parse(msg)).to.be.an('object');
expect(JSON.parse(msg).receiverId).to.equal('79660e6f-4b7d-4g21-81re-74f54e9e1c8a');
expect(JSON.parse(msg).status).to.equal('unread');
expect(JSON.parse(msg).content).to.equal('your manager posted a comment');
done();
});
});
it('should not allow a manager to comment on a request which does not belong to him', (done) => {
Expand All @@ -143,12 +143,13 @@ describe('COMMENTS TESTS', () => {
comment: 'thanks for your comment manager. This is my request too. And please Approve my request after this comment',
})
.end((err, res) => {
commentId = res.body.data.id;
expect(res.status).to.equal(200);
expect(res.body.status).to.equal(200);
expect(res.body.message).to.equal('Comment is successfully posted');
expect(res.body).to.have.property('data');
expect(res.body.data.requestId).to.have.equal('51e74db7-5510-4f50-9f15-e23710331ld5');
expect(res.body.data.commmentOwner).to.have.equal('79660e6f-4b7d-4g21-81re-74f54e9e1c8a');
expect(res.body.data.commentOwner).to.have.equal('79660e6f-4b7d-4g21-81re-74f54e9e1c8a');
expect(res.body.data.comment).to.have.equal('thanks for your comment manager. This is my request too. And please Approve my request after this comment');
done();
});
Expand All @@ -168,4 +169,28 @@ describe('COMMENTS TESTS', () => {
done();
});
});
it('should not allow a requester to delete a comment that does not exist', (done) => {
chai
.request(app)
.delete('/api/v1/comments/blahblah')
.set('token', requesterToken)
.end((err, res) => {
expect(res.status).to.equal(404);
expect(res.body.status).to.equal(404);
expect(res.body.error).to.equal('Comment not found');
done();
});
});
it('should allow a user to delete a comment', (done) => {
chai
.request(app)
.delete(`/api/v1/comments/${commentId}`)
.set('token', requesterToken)
.end((err, res) => {
expect(res.status).to.equal(200);
expect(res.body.status).to.equal(200);
expect(res.body.message).to.equal('Comment is successfully deleted');
done();
});
});
});
2 changes: 1 addition & 1 deletion src/tests/editTrip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ describe('EDIT TRIP TESTS', () => {
.send(openRequest)
.end((err, res) => {
expect(res.status).to.equal(200);
done();
});
clientSocket.on('notification', (msg) => {
expect(JSON.parse(msg)).to.be.an('object');
expect(JSON.parse(msg).receiverId).to.equal('0119b84a-99a4-41c0-8a0e-6e0b6c385165');
expect(JSON.parse(msg).status).to.equal('unread');
done();
});
});
it('should return trip does not exist or has been approved or rejected', (done) => {
Expand Down
5 changes: 3 additions & 2 deletions src/tests/unitTests/comments.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import chai from 'chai';
import uuid from 'uuid/v4';
import db from '../../models';

const {
Expand All @@ -17,11 +18,11 @@ describe('COMMENT UNIT TESTS', () => {
it('should create a comment', async () => {
const newComment = await db.Comments.create({
requestId,
commmentOwner: '0119b84a-99a4-41c0-8a0e-6e0b6c385165',
id: uuid(),
commentOwner: '0119b84a-99a4-41c0-8a0e-6e0b6c385165',
comment: 'As a your manager I can add any comment on your request. I can also cancel or approve your request but let me comment first'
});
expect(newComment).to.be.an('object');
expect(newComment.id).to.equal(3);
expect(newComment.comment).to.equal('As a your manager I can add any comment on your request. I can also cancel or approve your request but let me comment first');
});
});
16 changes: 16 additions & 0 deletions src/validation/validationRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export const signupInputRules = [
check('password').exists().matches(/(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*\W).{8,30}/).withMessage('At least 8 characters include symbols, uppercase, lowercase and number'),
];

export const loginRules = [
check('email').trim().exists()
.withMessage('The email is required')
.isLength({ min: 1, max: 200 })
.withMessage('email can not be empty'),
check('password').exists()
.withMessage('The password is required')
.isLength({ min: 1, max: 200 })
.withMessage('password can not be empty')
];

export const changeRoles = [
check('email').trim().exists().withMessage('The email is required')
.isEmail()
Expand Down Expand Up @@ -139,3 +150,8 @@ export const commentRules = [
check('comment').exists().trim().isLength({ min: 1, max: 200 })
.withMessage('a comment is required with maximum 200 characters')
];

export const commentIdRules = [
check('commentId').exists().trim().isLength({ min: 1, max: 200 })
.withMessage('a commentId is required with maximum 200 characters')
];

0 comments on commit 1ca1e39

Please sign in to comment.