Skip to content

Commit

Permalink
Merge 2fb4c06 into 703a187
Browse files Browse the repository at this point in the history
  • Loading branch information
ssanusi committed Apr 28, 2019
2 parents 703a187 + 2fb4c06 commit 5a534b4
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 77 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"db-migrate:test": "NODE_ENV=test ./node_modules/.bin/babel-node ./node_modules/.bin/sequelize db:migrate",
"db-migrate:undo": "./node_modules/.bin/babel-node ./node_modules/.bin/sequelize db:migrate:undo:all",
"db-migrate:undo:test": "NODE_ENV=test ./node_modules/.bin/babel-node ./node_modules/.bin/sequelize db:migrate:undo:all",
"db-refresh": "npm run db-migrate:undo && npm run db-migrate && npm run db-seed",
"db-refresh": "npm run db-drop && npm run db-create && npm run db-migrate:undo && npm run db-migrate && npm run db-seed",
"db-refresh:test": "NODE_ENV=test npm run db-migrate:undo:test && npm run db-migrate:test && npm run db-seed:test",
"db-seed": "./node_modules/.bin/babel-node ./node_modules/.bin/sequelize db:seed:all",
"db-seed:test": "NODE_ENV=test ./node_modules/.bin/babel-node ./node_modules/.bin/sequelize db:seed:all",
Expand Down
4 changes: 0 additions & 4 deletions src/config/emailConfig.js

This file was deleted.

39 changes: 0 additions & 39 deletions src/controllers/like.js

This file was deleted.

36 changes: 36 additions & 0 deletions src/controllers/likeArticle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { LikeArticle } from '../models';
import { responseFormat, errorResponseFormat } from '../utils';
import { likeDislike } from '../utils/comment';

const likeArticle = async (req, res) => {
const { id: userId } = req.user;
const { id: articleId } = req.params;
const condition = { userId, articleId };

try {
const likeCommentResult = await likeDislike(LikeArticle, condition);
const { like, likeCount } = likeCommentResult;
if (like) {
return res.status(200).json(
responseFormat({
status: 'success',
data: { message: 'you successfully liked the article', Like: likeCount },
}),
);
}
return res.status(200).json(
responseFormat({
status: 'success',
data: { message: 'you successfully un-liked the article', Like: likeCount },
}),
);
} catch (error) {
if (error.parent.constraint === 'like_articles_articleId_fkey') {
return res
.status(400)
.json(errorResponseFormat({ message: 'ArticleId is not on Article Table' }));
}
}
};

export default likeArticle;
33 changes: 33 additions & 0 deletions src/controllers/likeComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { likeDislike } from '../utils/comment';
import { LikeComment } from '../models';
import { responseFormat, errorResponseFormat } from '../utils';

export const likeComment = async (req, res) => {
const { id: userId } = req.user;
const { id: commentId } = req.params;
const condition = { userId, commentId };
try {
const likeCommentResult = await likeDislike(LikeComment, condition);
const { like, likeCount } = likeCommentResult;
if (like) {
return res.status(200).json(
responseFormat({
status: 'success',
data: { message: 'you successfully liked the comment', Like: likeCount },
}),
);
}
return res.status(200).json(
responseFormat({
status: 'success',
data: { message: 'you successfully un-liked the comment', Like: likeCount },
}),
);
} catch (error) {
if (error.parent.constraint === 'like_comments_commentId_fkey') {
return res
.status(400)
.json(errorResponseFormat({ message: 'commentId is not on comment Table' }));
}
}
};
15 changes: 1 addition & 14 deletions src/middlewares/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { findById } from '../utils/query';
import { Article } from '../models';
import { responseHandler, parseErrorResponse, checkIDParamType } from '../utils';
import { getAllArticles, getAnArticleByID } from '../controllers/article';
import { validateComment } from '../utils/comment';


/**
*@name articleValidation
Expand Down Expand Up @@ -129,19 +129,6 @@ export const articleRatingValidation = async (req, res, next) => {
next();
};

export const commentValidation = async (req, res, next) => {
const validate = await validateComment(req.body);
if (validate.fails()) {
const validationErrors = validate.errors.all();
const errorMessages = parseErrorResponse(validationErrors);
return res.status(400).json({
status: 'fail',
data: errorMessages
});
}
next();
};

/**
*@name getArticleHandler
*@description Middleware for handling requests for an article
Expand Down
30 changes: 30 additions & 0 deletions src/middlewares/comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Comment } from '../models';
import { validateComment } from '../utils/comment';
import { parseErrorResponse, responseHandler } from '../utils';


export const commentValidation = async (req, res, next) => {
const validate = await validateComment(req.body);
if (validate.fails()) {
const validationErrors = validate.errors.all();
const errorMessages = parseErrorResponse(validationErrors);
return res.status(400).json({
status: 'fail',
data: errorMessages,
});
}
next();
};

export const verifyComment = async (req, res, next) => {
const { id } = req.params;
const comment = await Comment.findByPk(id);
if (!comment) {
return responseHandler(res, 404, { status: 'fail', message: 'Comment not found!' });
}
const {
dataValues: { userId: authorId },
} = comment;
req.authorId = authorId;
next();
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default {
up: (queryInterface, Sequelize) => queryInterface.createTable('likes', {
up: (queryInterface, Sequelize) => queryInterface.createTable('like_articles', {
id: {
allowNull: false,
primaryKey: true,
Expand Down Expand Up @@ -32,5 +32,5 @@ export default {
type: Sequelize.DATE,
},
}),
down: queryInterface => queryInterface.dropTable('likes'),
down: queryInterface => queryInterface.dropTable('like_articles'),
};
7 changes: 7 additions & 0 deletions src/migrations/20190331200856-create-like-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LikeComment } from '../models';

export default {
up: queryInterface => queryInterface.createTable(LikeComment.tableName,
LikeComment.rawAttributes),
down: queryInterface => queryInterface.dropTable('like_comments'),
};
2 changes: 1 addition & 1 deletion src/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default (sequelize, DataTypes) => {
through: 'article_user',
onDelete: 'CASCADE',
});
Article.hasMany(models.Like, {
Article.hasMany(models.LikeArticle, {
foreignKey: 'userId',
as: 'userLikes',
onDelete: 'CASCADE',
Expand Down
14 changes: 7 additions & 7 deletions src/models/Like.js → src/models/like_article.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* @returns {Model} Returns Like model
*/
export default (sequelize, DataTypes) => {
const Like = sequelize.define(
'Like',
const LikeArticle = sequelize.define(
'LikeArticle',
{
id: {
type: DataTypes.UUID,
Expand All @@ -24,20 +24,20 @@ export default (sequelize, DataTypes) => {
},
},
{
tableName: 'likes',
tableName: 'like_articles',
},
);
Like.associate = (models) => {
Like.belongsTo(models.Article, {
LikeArticle.associate = (models) => {
LikeArticle.belongsTo(models.Article, {
foreignKey: 'articleId',
as: 'article',
onDelete: 'CASCADE',
});
Like.belongsTo(models.User, {
LikeArticle.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user',
onDelete: 'CASCADE',
});
};
return Like;
return LikeArticle;
};
43 changes: 43 additions & 0 deletions src/models/like_comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @name init
* @param {sequelize} sequelize
* @param {DataTypes} DataTypes
* @returns {Model} Returns LikeArticle model
*/
export default (sequelize, DataTypes) => {
const LikeComment = sequelize.define(
'LikeComment',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
},
userId: {
type: DataTypes.UUID,
allowNull: false,
},
commentId: {
type: DataTypes.UUID,
allowNull: false,
},
},
{
tableName: 'like_comments',
},
);
LikeComment.associate = (models) => {
LikeComment.belongsTo(models.Comment, {
foreignKey: 'commentId',
as: 'comment',
onDelete: 'CASCADE',
});
LikeComment.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user',
onDelete: 'CASCADE',
});
};
return LikeComment;
};
9 changes: 7 additions & 2 deletions src/routers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
viewUser,
} from '../controllers/authentication/user';
import checkBody from '../middlewares/signUpValidator';
import likeArticle from '../controllers/like';
import likeArticle from '../controllers/likeArticle';
import {
articleValidation,
verifyArticle,
Expand All @@ -34,7 +34,6 @@ import {
articleReportValidation,
articleTagValidation,
articleRatingValidation,
commentValidation,
checkQueryParams,
} from '../middlewares/articles';
import { checkParam } from '../middlewares/checkParam';
Expand All @@ -43,6 +42,8 @@ import { editUser } from '../controllers/editUser';
import { followAndUnfollowUser, getFollowing, getFollowers } from '../controllers/follower';
import getAuthors from '../controllers/authors';
import highlightArticle from '../controllers/highlight';
import { commentValidation, verifyComment } from '../middlewares/comments';
import { likeComment } from '../controllers/likeComment';
import { forgotPassword, resetPassword } from '../controllers/authentication/passwordReset';
import resetFieldValidation from '../middlewares/auth/resetPassword';

Expand All @@ -59,6 +60,10 @@ router
.patch(checkParam, Auth.authenticateUser, verifyArticle, likeArticle);
router.route('/articles/:id/highlight').post(Auth.authenticateUser, checkParam, highlightArticle);

router
.route('/comments/:id/like')
.patch(checkParam, Auth.authenticateUser, verifyComment, likeComment);

/**
* Resource handling articles
* @name router:/articles/:id?
Expand Down
17 changes: 17 additions & 0 deletions src/seeders/20190402090906-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ export default {
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '0560a5cc-99d2-4bed-84cd-f1c7e8d98d47',
fullName: 'test test',
email: 'test@andela.com',
username: 'testtest',
password: '$2a$10$F83SBdFDL.dFEeCKm3YC6O/NXKeYO/QxxsZY2FN/W2DoG38hhdhbi',
bio:
"Hold on now, aren't there already specs for this kind of thing? - Well... no. While there are a few handy specifications for dealing with JSON data, most notably Douglas Crockford's JSONRequest proposal, there's nothing to address the problems of general application-level messaging. More on this later.",
imageUrl: 'http://waterease.herokuapp.com/images/board/comfort.jpg',
notification: true,
role: 'author',
verified: true,
passwordResetToken: null,
resetTokenExpires: null,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '146fa633-98dd-467f-9e4d-48eac3ee14e4',
fullName: 'Simple Mart',
Expand Down
15 changes: 15 additions & 0 deletions src/utils/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,18 @@ export const validateComment = (payload) => {
};
return new Validator(payload, rules, errorMessages);
};

export const likeDislike = async (Model, condition) => {
const [, isNewRecord] = await Model.findOrCreate({ where: condition });
const { userId, ...countCondition } = condition;
if (!isNewRecord) {
const deleteRecord = await Model.destroy({ where: condition });
if (deleteRecord) {
const likeCount = await Model.count({ where: countCondition });
return { like: false, likeCount };
}
} else {
const likeCount = await Model.count({ where: countCondition });
return { like: true, likeCount };
}
};
4 changes: 1 addition & 3 deletions src/utils/mailContent/passwordReset.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import baseUrl from '../../config/emailConfig';

export const subject = 'Password Reset Instruction';

export const content = resetToken => `
<div>
<p>Hi, </p>
<p>You requested for password reset for your account</p>
<p>Please click on the following link, or paste the link in your browser to complete this process within four hours of receiving this email.</p>
<p>${baseUrl}/api/v1/password-reset/${resetToken}</p>
<p>${process.env.BASE_URL}/password-reset/${resetToken}</p>
<p>If you did not request for this, please ignore this email.</p>
</div>`;
Loading

0 comments on commit 5a534b4

Please sign in to comment.