Skip to content

Commit

Permalink
feature(comment): get all comments
Browse files Browse the repository at this point in the history
Create get comment controller
Create function to authorize users
Add test
Add swagger documentation

[Finishes #167942058]
  • Loading branch information
allebd committed Aug 22, 2019
1 parent 1f5c883 commit 19fb474
Show file tree
Hide file tree
Showing 13 changed files with 387 additions and 11 deletions.
83 changes: 83 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,89 @@ paths:
example: novel not found
500:
description: server error
/novels/{slug}/comments:
get:
summary: Gets all comments
description: Gets all comments
parameters:
- name: slug
in: path
required: true
schema:
type: string
responses:
200:
description: successful request
content:
application/json:
schema:
type: object
properties:
comments:
type: object
properties:
novel:
type: string
example: The Good Book
commentAuthor:
type: string
example: bigShaq004
commentBody:
type: string
example: I really love this book
updatedAt:
type: string
example: '2019-08-05T14:17:58.518Z'
400:
description: bad request
content:
application/json:
schema:
type: object
properties:
errors:
type: array
items:
type: object
properties:
field:
type: string
example: commentBody
message:
type: string
example: commentBody cannot be empty
401:
description: unauthorized access
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: invalid token
403:
description: forbidden access
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: you have to be subscribed to comment on a novel
404:
description: entity not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: novel not found
500:
description: server error
'/profiles/{userId}':
get:
tags:
Expand Down
25 changes: 23 additions & 2 deletions src/controllers/commentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,31 @@ import services from '../services';
const { responseMessage } = helpers;
const {
novelServices: { findNovel },
commentServices: { findComment, createComment },
commentServices: { findComment, findComments, createComment },
notificationServices: { addNotification }
} = services;

/**
*
* @param {object} request
* @param {object} response
* @returns {json} - json
*/
const getComment = async (request, response) => {
try {
const { slug } = request.params;
const novel = await findNovel(slug);
if (!novel) {
return responseMessage(response, 404, { error: 'novel not found' });
}
const novelId = novel.id;
const comments = await findComments(novelId);
responseMessage(response, 200, { comments });
} catch (error) {
responseMessage(response, 500, { error: error.message });
}
};

/**
*
* @param {object} request
Expand Down Expand Up @@ -94,5 +115,5 @@ const replyComment = async (request, response) => {
};

export default {
postComment, replyComment
getComment, postComment, replyComment
};
22 changes: 22 additions & 0 deletions src/database/migrations/20190811124707-create-comment-like.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const up = (queryInterface, Sequelize) => queryInterface.createTable('CommentLikes', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID
},
userId: {
type: Sequelize.UUID
},
commentId: {
type: Sequelize.UUID
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
export const down = queryInterface => queryInterface.dropTable('CommentLikes');
7 changes: 7 additions & 0 deletions src/database/models/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default (sequelize, DataTypes) => {
Comment.associate = (models) => {
Comment.belongsTo(models.User, {
foreignKey: 'userId',
as: 'commentAuthor',
onDelete: 'CASCADE'
});

Expand All @@ -41,6 +42,12 @@ export default (sequelize, DataTypes) => {

Comment.hasMany(models.Comment, {
foreignKey: 'parentId',
as: 'replies',
onDelete: 'CASCADE'
});

Comment.hasMany(models.CommentLike, {
foreignKey: 'commentId',
onDelete: 'CASCADE'
});
};
Expand Down
29 changes: 29 additions & 0 deletions src/database/models/commentlike.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default (sequelize, DataTypes) => {
const CommentLike = sequelize.define('CommentLike', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
userId: {
allowNull: false,
type: DataTypes.UUID
},
commentId: {
allowNull: false,
type: DataTypes.UUID
},
}, {});
CommentLike.associate = (models) => {
CommentLike.belongsTo(models.Comment, {
foreignKey: 'commentId',
onDelete: 'CASCADE'
});
CommentLike.belongsTo(models.User, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});
};
return CommentLike;
};
5 changes: 5 additions & 0 deletions src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ export default (Sequelize, DataTypes) => {
foreignKey: 'userId',
onDelete: 'CASCADE'
});

User.hasOne(models.CommentLike, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});
};
return User;
};
14 changes: 14 additions & 0 deletions src/database/seeders/20190724090406-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,19 @@ export const up = queryInterface => queryInterface.bulkInsert('Users', [{
isSubscribed: true,
createdAt: new Date(),
updatedAt: new Date()
}, {
id: 'be84f364-36fd-466d-a892-54dee6cc09d7',
firstName: 'marvin',
lastName: 'adams',
email: 'marvinadams@gmail.com',
password: bcrypt.hashSync('marvinadams', 10),
avatarUrl: '',
bio: 'I am a writer, and i have authored 25 best selling books',
phoneNo: '2347032123909',
isVerified: true,
isSubscribed: true,
roleId: 'f2dec928-1ff9-421a-b77e-8998c8e2e720',
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('Users', null, {});
8 changes: 8 additions & 0 deletions src/database/seeders/20190806253031-comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,13 @@ export const up = queryInterface => queryInterface.bulkInsert('Comments', [{
parentId: '6a7b986e-1102-4e9a-83b0-cad7df993e1c',
createdAt: new Date(),
updatedAt: new Date()
}, {
id: '8a97e8da-78a6-4352-b32b-7e033bffb664',
commentBody: 'Yeah I think the same too',
userId: 'be84f364-36fd-466d-a892-54dee6cc09d7',
novelId: null,
parentId: '6a7b986e-1102-4e9a-83b0-cad7df993e1c',
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('Comments', null, {});
20 changes: 20 additions & 0 deletions src/database/seeders/20190819071736-commentlike.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const up = queryInterface => queryInterface.bulkInsert('CommentLikes', [{
id: '42f20078-9e37-4bb2-8b53-f4eba5b6456e',
userId: 'be84f364-36fd-466d-a892-54dee6cc09d7',
commentId: '6a7b986e-1102-4e9a-83b0-cad7df993e1c',
createdAt: new Date(),
updatedAt: new Date()
}, {
id: '34ff4237-d579-478e-b10f-01862eb2cfe3',
userId: 'be84f364-36fd-466d-a892-54dee6cc09d7',
commentId: 'b84f246f-ba18-4f83-876d-145be90b494d',
createdAt: new Date(),
updatedAt: new Date()
}, {
id: '279fad7c-abc0-48f8-a637-0e3c97d87c9a',
userId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
commentId: 'b84f246f-ba18-4f83-876d-145be90b494d',
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('CommentLikes', null, {});
8 changes: 6 additions & 2 deletions src/middlewares/commentValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ const {
const { validatorError } = errorHandler;

const commentValidator = {
postComment: [
getCommentValidator: [
isNotEmptySlug(),
validatorError
],
postCommentValidator: [
isValidComment(),
isNotEmptySlug(),
validatorError
],
replyComment: [
replyCommentValidator: [
isNotEmptySlug(),
isValidUUID('parentId'),
isValidComment(),
Expand Down
14 changes: 10 additions & 4 deletions src/routes/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import middlewares from '../middlewares';
const comment = express.Router();
const COMMENT_URL = '/novels/:slug/comments';

const { verifyToken, commentValidator, authorizeUser } = middlewares;
const { postComment, replyComment } = commentController;
const {
verifyToken, authorizeUser,
commentValidator: { getCommentValidator, postCommentValidator, replyCommentValidator }
} = middlewares;
const { getComment, postComment, replyComment } = commentController;

// Route to get a comment and like count
comment.get(`${COMMENT_URL}`, verifyToken, authorizeUser(['author', 'admin', 'superadmin']), getCommentValidator, getComment);

// Route to create a comment
comment.post(`${COMMENT_URL}`, verifyToken, commentValidator.postComment, authorizeUser(['author', 'admin', 'superadmin']), postComment);
comment.post(`${COMMENT_URL}`, verifyToken, authorizeUser(['author', 'admin', 'superadmin']), postCommentValidator, postComment);

// Route to reply a comment
comment.post(`${COMMENT_URL}/:parentId`, verifyToken, commentValidator.replyComment, authorizeUser(['author', 'admin', 'superadmin']), replyComment);
comment.post(`${COMMENT_URL}/:parentId`, verifyToken, authorizeUser(['author', 'admin', 'superadmin']), replyCommentValidator, replyComment);

export default comment;
40 changes: 38 additions & 2 deletions src/services/commentService.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Sequelize from 'sequelize';
import models from '../database/models';

const { Comment } = models;
const { Comment, CommentLike, User } = models;

/**
* Finds a comment from the database by id
Expand All @@ -15,6 +16,41 @@ const findComment = async (param) => {
return comment;
};

/**
* Finds all comment from the database by novel id
* @param {string} novelId
* @returns {object} a comment object
*/

const findComments = async (novelId) => {
const comment = await Comment.findAll({
where: { novelId },
attributes: {
include: [[Sequelize.fn('COUNT', Sequelize.col('CommentLikes.commentId')), 'likesCount']],
exclude: ['parentId', 'novelId', 'userId']
},
include: [
{ model: User, as: 'commentAuthor', attributes: ['firstName', 'lastName'] },
{ model: CommentLike, attributes: [] },
{
model: Comment,
as: 'replies',
attributes: {
include: [[Sequelize.fn('COUNT', Sequelize.col('CommentLikes.commentId')), 'likesCount']],
exclude: ['parentId', 'novelId', 'userId']
},
include: [
{ model: User, as: 'commentAuthor', attributes: ['firstName', 'lastName'] },
{ model: CommentLike, attributes: [] }],
separate: true,
group: ['Comment.id', 'commentAuthor.id']
}
],
group: ['Comment.id', 'commentAuthor.id']
});
return comment;
};

/**
* Creates a comment in the database
* @param {string} param
Expand All @@ -29,5 +65,5 @@ const createComment = async (commentBody, userId, parentId, novelId) => {
};

export default {
findComment, createComment
findComment, findComments, createComment
};
Loading

0 comments on commit 19fb474

Please sign in to comment.