Skip to content

Commit

Permalink
feat(track-comment-history): track comment edit history
Browse files Browse the repository at this point in the history
- track comment edit history

[Finishes #166790019]
  • Loading branch information
kodek-sleuth committed Aug 14, 2019
1 parent 4f10da9 commit f216234
Show file tree
Hide file tree
Showing 17 changed files with 228 additions and 51 deletions.
12 changes: 12 additions & 0 deletions controllers/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ class Article {
model: users,
as: 'author',
attributes: ['username', 'email', 'id']
},
{
as: 'comments',
model: comments,
attributes: ['id', 'body', 'createdAt', 'updatedAt'],
include: [
{
as: 'commentor',
model: users,
attributes: ['username', 'image']
}
]
}
]
});
Expand Down
76 changes: 73 additions & 3 deletions controllers/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from './notifications';

const {
comments, sequelize, articles, users,
comments, sequelize, articles, users, commentEdits
} = db;

class Comment {
Expand All @@ -15,6 +15,11 @@ class Comment {
const { username } = req.decoded;
const transaction = await sequelize.transaction();
try {
if (body.length <= 0) {
return res.status(400).json({
error: 'comment body cannot be null'
});
}
const article = await articles.findOne({
where: {
slug,
Expand Down Expand Up @@ -83,7 +88,8 @@ class Comment {
as: 'commentor',
model: users,
attributes: ['username', 'image']
}]
}
]
}]
});

Expand All @@ -99,7 +105,7 @@ class Comment {
});
} catch (error) {
return res.status(500).json({
error: 'failed to fetch comments'
error: 'failed to fetch comments and history'
});
}
}
Expand Down Expand Up @@ -140,5 +146,69 @@ class Comment {
});
}
}

static async updateComment(req, res) {
try {
const { commentId } = req.params;
const { email } = req.decoded;
const { body } = req.body;
const user = await users.findOne({
where: { email }
});
const comment = await comments.findOne({
where: { id: commentId }
});
if (comment.authorId !== user.id) {
return res.status(403).json({
error: 'user cannot update this comment'
});
}
await comments.update(
{
body
},
{
where: { id: commentId }
}
);
await commentEdits.create({
commentId: comment.id,
body: comment.body
});
return res.status(200).json({
message: 'successfully updated and tracked comment',
comment: {
body,
id: comment.id
}
});
} catch (error) {
return res.status(500).json({
error: 'failed to update and track comment'
});
}
}

static async getSingleComment(req, res) {
try {
const { commentId } = req.params;
const edits = await commentEdits.findAll({
where: { commentId }
});
const comment = await comments.findOne({
where: { id: commentId }
});

return res.status(200).json({
comment,
edits
});
} catch (error) {
return res.status(500).json({
error: 'failed to get comment'
});
}
}
}

export default Comment;
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ app.use(passport.session());
app.use(router);

app.all('*', (_req, res) => {
res.status(400).json({
res.status(404).json({
error: 'address not found',
});
});
Expand Down
24 changes: 24 additions & 0 deletions middlewares/checkArticleOwner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import models from '../models/index';

const { articles, users } = models;

/* @middleware which find if article is exist and
check if user is allowed to update or delete article
*/
export const checkArticleOwner = async (req, res, next) => {
try {
const article = await articles.findOne({ where: { slug: req.params.slug } });
const author = await users.findOne({ where: { email: req.decoded.email } });
if (!article) {
return res.status(404).json({ error: 'sorry the requested article could not be found.' });
}
// @check if authors are the same
if (article.authorId !== author.id) {
return res.status(403).json({ error: 'permission denied, you are not allowed to perform this action.' });
}
req.findArticle = article;
next();
} catch (error) {
return res.status(500).json({ error: `something wrong please try again. ${error}` });
}
};
File renamed without changes.
27 changes: 27 additions & 0 deletions migrations/20190719091756-createTableCommentEdits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const up = (queryInterface, Sequelize) => queryInterface.createTable('commentEdits', {
commentId: {
type: Sequelize.UUID,
references: {
model: 'comments',
key: 'id'
},
onDelete: 'CASCADE',
allowNull: false
},
body: {
type: Sequelize.TEXT,
allowNull: false
},
createdAt: {
defaultValue: Sequelize.fn('now'),
type: Sequelize.DATE,
default: true
},
updatedAt: {
defaultValue: Sequelize.fn('now'),
type: Sequelize.DATE,
default: true
}
});

export const down = queryInterface => queryInterface.dropTable('commentEdits');
3 changes: 3 additions & 0 deletions models/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export default (Sequelize, DataTypes) => {
comments.associate = (models) => {
comments.belongsTo(models.articles, { as: 'comments', foreignKey: 'articleId', onDelete: 'CASCADE', });
comments.belongsTo(models.users, { as: 'commentor', foreignKey: 'authorId', onDelete: 'CASCADE', });
comments.hasMany(models.commentEdits, {
as: 'edits', foreignKey: 'commentId', onDelete: 'CASCADE', sourceKey: 'id'
});
};
return comments;
};
30 changes: 30 additions & 0 deletions models/commentEdits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default (sequelize, DataTypes) => {
const commentEdits = sequelize.define('commentEdits', {
commentId: {
type: DataTypes.UUID,
references: {
model: 'comments',
key: 'id'
},
onDelete: 'CASCADE',
allowNull: false
},
body: {
type: DataTypes.TEXT,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
default: true
},
updatedAt: {
type: DataTypes.DATE,
default: true
}
}, {});
commentEdits.removeAttribute('id');
commentEdits.associate = (models) => {
commentEdits.belongsTo(models.comments, { foreignKey: 'commentId', as: 'edits' });
};
return commentEdits;
};
2 changes: 2 additions & 0 deletions models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ export default (sequelize, DataTypes) => {
allowNull: false,
primaryKey: true
},

firstName: { type: DataTypes.STRING },
lastName: { type: DataTypes.STRING },

username: {
type: DataTypes.STRING,
allowNull: false,
Expand Down
2 changes: 2 additions & 0 deletions routes/api/comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const router = express.Router();
*/
router.post('/articles/:slug/comments', checkToken, commentController.createComment);
router.get('/articles/:articleSlug/comments', commentController.getArticleComments);
router.get('/comments/:commentId', commentController.getSingleComment);
router.patch('/comments/:commentId', checkToken, commentController.updateComment);
router.delete('/comments/:commentId', checkToken, commentController.deleteComment);

export default router;
2 changes: 1 addition & 1 deletion seeders/20190717084942-articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const up = (queryInterface, Sequelize) => queryInterface.bulkInsert(
'articles',
[
{
id: 'c90dee64-663d-4d8b-b34d-12acba22cd57',
id: 'c90dee64-663d-4d8b-b34d-12acba22cd92',
title: 'The basics of java',
slug: 'the-basics-of-java',
body: 'JavaScript is a language which has many frameworks and libraries',
Expand Down
4 changes: 2 additions & 2 deletions seeders/20190726112201-comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ export const up = (queryInterface, Sequelize) => queryInterface.bulkInsert(
[
{
id: 'c90dee64-663d-4d8b-b34d-12acba22cd98',
articleId: 'c90dee64-663d-4d8b-b34d-12acba22cd57',
articleId: 'c90dee64-663d-4d8b-b34d-12acba22cd92',
body: 'JavaScript is a language which has',
authorId: 'c90dee64-663d-4d8b-b34d-12acba22cd44'
},
{
id: 'c90dee64-663d-4d8b-b34d-12acba22cd99',
articleId: 'c90dee64-663d-4d8b-b34d-12acba22cd57',
articleId: 'c90dee64-663d-4d8b-b34d-12acba22cd92',
body: 'JavaScript is a language which has',
authorId: 'c90dee64-663d-4d8b-b34d-12acba22cd44'
}
Expand Down
40 changes: 31 additions & 9 deletions tests/comment.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ describe('Create, Get and Delete Comment', () => {
done();
});
});
it('should not allow user to provide null body while commenting', (done) => {
chai
.request(app)
.post(`/api/articles/${slugArticle}/comments`)
.set('Authorization', `Bearer ${authToken}`)
.send(user.emptyComment)
.end((err, res) => {
expect(res.body.error).to.be.equal('comment body cannot be null');
done();
});
});
it('Should not allow user to comment on article with invalid token', (done) => {
chai
.request(app)
Expand Down Expand Up @@ -128,28 +139,39 @@ describe('Create, Get and Delete Comment', () => {
done();
});
});
it('should not let a user to get comments with an invalid slug', (done) => {
it('should let a user get a singleComment', (done) => {
chai
.request(app)
.get('/api/articles/the-basics-of-jav/comments')
.get('/api/comments/c90dee64-663d-4d8b-b34d-12acba22cd99')
.end((err, res) => {
expect(typeof res.statusCode).to.be.equal('number');
expect(res.statusCode).to.be.equal(404);
expect(res.statusCode).to.be.equal(200);
expect(res.body).to.have.property('comment');
expect(res.body).to.have.property('edits');
done();
});
});
it('should not let a user get a singleComment with invalid id', (done) => {
chai
.request(app)
.get('/api/comments/c90dee64-663d-4d8b')
.end((err, res) => {
expect(typeof res.statusCode).to.be.equal('number');
expect(res.statusCode).to.be.equal(500);
expect(res.body).to.have.property('error');
expect(res.body.error).to.equals('failed to find article and comments');
expect(res.body.error).to.equals('failed to get comment');
done();
});
});
it('should not let a user delete a comment that he/she did not create', (done) => {
it('should not let a user to get comments with an invalid slug', (done) => {
chai
.request(app)
.delete('/api/comments/c90dee64-663d-4d8b-b34d-12acba22cd98')
.set('Authorization', `Bearer ${authToken}`)
.get('/api/articles/the-basics-of-jav/comments')
.end((err, res) => {
expect(typeof res.statusCode).to.be.equal('number');
expect(res.statusCode).to.be.equal(403);
expect(res.statusCode).to.be.equal(404);
expect(res.body).to.have.property('error');
expect(res.body.error).to.equals('you are not allowed to delete this comment');
expect(res.body.error).to.equals('failed to find article and comments');
done();
});
});
Expand Down
7 changes: 6 additions & 1 deletion tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const ratingFalse = {
rating: 'a'
};

const emptyComment = {
body: ''
};

const ratingFalseNumber = {
rating: 7
};
Expand Down Expand Up @@ -234,5 +238,6 @@ export default {
secondLogin,
givenArticle,
givenComment1,
givenComment2
givenComment2,
emptyComment
};
Loading

0 comments on commit f216234

Please sign in to comment.