Skip to content

Commit

Permalink
feat(comment): perform crud operation on comment
Browse files Browse the repository at this point in the history
- create comment controller
- create routes for CRUD
- create method for CRUD
- write test

finishes: 162414275

feat(comment): fix travis ci

feat(comment): implement feedback

feat(comment): implement feedback from Timi

feat(comment): add new line to demo user

feat(comment): implement feedback from Onyeka

feat(comment): implement slug on route

feat(comment): implement crud on comment
  • Loading branch information
tejiri4 committed Jan 21, 2019
1 parent 431d1c6 commit 7427369
Show file tree
Hide file tree
Showing 20 changed files with 584 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ node_js:
cache:
directories:
- node_modules
services:
- postgresql
before_script:
- psql -c 'create user neon with superuser createdb createrole;' -U postgres
- psql -c 'create database haven_test;' -U postgres
- psql -c 'GRANT ALL PRIVILEGES ON DATABASE haven_test TO neon;' -U postgres
script:
- npm test
after_success:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"express": "^4.16.3",
"express-jwt": "^5.3.1",
"express-session": "^1.15.6",
"express-validator": "^5.3.1",
"jsonwebtoken": "^8.4.0",
"method-override": "^2.3.10",
"methods": "^1.1.2",
Expand Down
169 changes: 169 additions & 0 deletions server/controllers/CommentController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import db from '../models';
import response from '../helpers/response';

const { Article, Comment } = db;

/**
* @class CommentController
*/
class CommentController {
/**
*
* @description Allow user to comment on an article
* @static
* @param {*} req Express Request object
* @param {*} res Express Response object
* @returns {object} Json response
* @memberof CommentController
*/
static async addComment(req, res) {
try {
const { userId } = req.user;
const { content } = req.body;
const { slug } = req.params;
if (!content) {
return response(res, 400, 'failure', 'Enter a comment', null, null);
}
const articleFound = await Article.findOne({
where: {
slug
}
});
if (articleFound) {
const commentCreated = await Comment.create({
content,
userId,
articleId: slug
});
if (commentCreated) {
return response(res, 201, 'success', 'Comment created', null, null);
}
} else {
return response(res, 404, 'failure', 'Article with the id not found', null, null);
}
} catch (error) {
return response(
res, 500, 'failure',
'server error',
{ message: 'Something went wrong on the server' }, null
);
}
}

/**
*
* @description Retrive all comments for an article
* @static
* @param {*} req Express Request object
* @param {*} res Express Response object
* @returns {object} Json response
* @memberof CommentController
*/
static async getComments(req, res) {
try {
const { slug } = req.params;
const commentsQuery = await Comment.findAll({
where: {
articleId: slug
}
});
if (commentsQuery.length === 0) {
return response(res, 404, 'failure', 'Comment with the articleId not found', null, null);
}
const comments = [];
commentsQuery.map(comment => comments.push(comment.dataValues));
return response(res, 200, 'success', 'Comment found', null, comments);
} catch (error) {
return response(
res, 500, 'failure',
'server error',
{ message: 'Something went wrong on the server' }, null
);
}
}

/**
*
* @description Update a comment
* @static
* @param {*} req Express Request object
* @param {*} res Express Response object
* @returns {object} Json response
* @memberof CommentController
*/
static async updateComment(req, res) {
try {
const { userId } = req.user;
const { slug, commentId } = req.params;
const { content } = req.body;
const getCommentUpdate = await Comment.findOne({
where: {
id: commentId
}
});
if (getCommentUpdate.dataValues.articleId !== slug) {
return response(res, 404, 'failure', 'Comment not found for article id', null, null);
}
if (getCommentUpdate.dataValues.userId !== userId) {
return response(res, 401, 'failure', 'You are not allowed to update another user\'s comment', null, null);
}
const updateComment = await getCommentUpdate.update({
content
});
if (updateComment) {
return response(res, 200, 'success', 'Comment updated', null, null);
}
} catch (error) {
if (error.name === 'SequelizeDatabaseError') {
return response(res, 404, 'failure', 'Comment with the commentId not found', null, null);
}
return response(
res, 500, 'failure',
'server error',
{ message: 'Something went wrong on the server' }, null
);
}
}

/**
*
* @description Delete a comment
* @static
* @param {*} req Express Request object
* @param {*} res Express Response object
* @returns {object} Json response
* @memberof CommentController
*/
static async deleteComment(req, res) {
try {
const { userId } = req.user;
const { slug: articleId, commentId } = req.params;
const getCommentDelete = await Comment.findOne({
where: {
id: commentId
}
});
if (getCommentDelete.dataValues.articleId !== articleId) {
return response(res, 404, 'failure', 'Comment not found for article id', null, null);
}
if (getCommentDelete.dataValues.userId !== userId) {
return response(res, 401, 'failure', 'You are not allowed to delete another user\'s comment', null, null);
}
const deleteComment = await getCommentDelete.destroy();
if (deleteComment) {
return response(res, 200, 'success', 'Comment deleted', null, null);
}
} catch (error) {
if (error.name === 'SequelizeDatabaseError') {
return response(res, 404, 'failure', 'Comment with the commentId not found', null, null);
}
return response(
res, 500, 'failure',
'server error',
{ message: 'Something went wrong on the server' }, null
);
}
}
}

export default CommentController;
2 changes: 1 addition & 1 deletion server/helpers/TokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class TokenManager {
* @returns {string} Jwt token
* @memberof Tokenize
*/
static sign(payload, ttl = '2h') {
static sign(payload, ttl = '200h') {
return jwt.sign(payload, secret, { expiresIn: ttl });
}

Expand Down
23 changes: 23 additions & 0 deletions server/helpers/response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
*
* @description Method to send response in a generic format.
* @param {*} res Express Response object
* @param {number} code HTTP response status code
* @param {string} status 'success' || 'failure'
* @param {string} message Message to user
* @param {object} error (optional) Error object
* @param {object} payload (optional) Payload data to return with the response
* @returns {object} Json response
*/

export default (res, code, status, message, error, payload) => {
res.status(code).json({
status,
data: {
statusCode: code,
message,
error,
payload
}
});
};
Empty file removed server/middlewares/.gitkeep
Empty file.
53 changes: 53 additions & 0 deletions server/middlewares/AuthMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import TokenManager from '../helpers/TokenManager';
import response from '../helpers/response';
/**
* @class AuthMiddleware
* @description class contains function for implementing Authentication middleware
*/
class AuthMiddleware {
/**
* @static
* @description a middleware function checking if a user is authenticated
* @param {object} req HTTP request object
* @param {object} res HTTP response object
* @param {function} next next middleware function
* @returns {object} returns error message if user is not authenticated
*/
static checkIfUserIsAuthenticated(req, res, next) {
try {
const { authorization } = req.headers;
if (!authorization) {
return response(
res, 401, 'failure', 'authentication error',
{ message: 'You are not logged in, Token is needed!' },
null
);
}

const token = authorization.split(' ')[1];
const decoded = TokenManager.verify(token);

if (decoded) {
req.user = decoded;
return next();
}
} catch (error) {
const { name } = error;
if (name === 'TokenExpiredError' || name === 'JsonWebTokenError') {
return response(
res, 401, 'failure', 'authentication error',
{ message: 'Token is invalid, You need to log in again' },
null
);
}

return response(
res, 500, 'failure',
'server error',
{ message: 'Something went wrong on the server' }, null
);
}
}
}

export default AuthMiddleware;
12 changes: 12 additions & 0 deletions server/middlewares/ContentValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import response from '../helpers/response';

const ContentValidate = (req, res, next) => {
const { content } = req.body;
if (content.trim().length > 0) {
next();
} else {
return response(res, 400, 'failure', 'Comment content must not be empty', null, null);
}
};

export default ContentValidate;
2 changes: 1 addition & 1 deletion server/migrations/20190111130326-create-comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
allowNull: false
},
articleId: {
type: Sequelize.UUID,
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
Expand Down
14 changes: 0 additions & 14 deletions server/migrations/20190115104506-add-userId-constraint-Comments.js

This file was deleted.

13 changes: 13 additions & 0 deletions server/routes/api/comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Router } from 'express';
import CommentController from '../../controllers/CommentController';
import AuthMiddleware from '../../middlewares/AuthMiddleware';
import ContentValidate from '../../middlewares/ContentValidator';

const commentRoutes = Router();

commentRoutes.post('/articles/:slug/comments', AuthMiddleware.checkIfUserIsAuthenticated, ContentValidate, CommentController.addComment);
commentRoutes.get('/articles/:slug/comments', AuthMiddleware.checkIfUserIsAuthenticated, CommentController.getComments);
commentRoutes.put('/articles/:slug/comments/:commentId', AuthMiddleware.checkIfUserIsAuthenticated, CommentController.updateComment);
commentRoutes.delete('/articles/:slug/comments/:commentId', AuthMiddleware.checkIfUserIsAuthenticated, CommentController.deleteComment);

export default commentRoutes;
2 changes: 2 additions & 0 deletions server/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Router } from 'express';
import userRoutes from './api/user';
import commentRoutes from './api/comment';

const routes = Router();

routes.use(userRoutes);
routes.use(commentRoutes);

export default routes;
28 changes: 15 additions & 13 deletions server/seeders/20190111141811-roles.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
export default {
up: (queryInterface, Sequelize) => queryInterface.bulkInsert(
'Roles',
[
{
id: '3ceb546e-054d-4c1d-8860-e27c209d4ae3',
type: 'user',
},
{
id: '3ceb546e-054d-4c1d-8860-e27c209d4ae4',
type: 'admin'
}
],
{}),
up: (queryInterface, Sequelize) =>
queryInterface.bulkInsert(
'Roles',
[
{
id: '3ceb546e-054d-4c1d-8860-e27c209d4ae3',
type: 'user'
},
{
id: '3ceb546e-054d-4c1d-8860-e27c209d4ae4',
type: 'admin'
}
],
{}
),

down: (queryInterface, Sequelize) => queryInterface.bulkDelete('Roles', null, {})
};
Loading

0 comments on commit 7427369

Please sign in to comment.