diff --git a/.codeclimate.yml b/.codeclimate.yml index 388efd9..841dcea 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -9,3 +9,9 @@ exclude_patterns: - "src/sequelize/seeders/" - "**/node_modules/" - "test/**/*.js" +- "src/middleware/search.js" +- "src/helpers/user/*.js" +- "src/helpers/article/*.js" +- "src/helpers/comment/*.js" +- "src/helpers/validationSchemas.js" +- "src/middleware/shareArticle.js" \ No newline at end of file diff --git a/package.json b/package.json index db71d1c..904a322 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,9 @@ "src/sequelize/**/*.js", "test/**/*.js", "src/helpers/notifications/*.js", - "src/helpers/SocketIO.js" + "src/helpers/SocketIO.js", + "src/helpers/stats/*.js", + "src/helpers/article/*.js" ] } } diff --git a/src/api/controllers/articleBlocks.js b/src/api/controllers/articleBlocks.js new file mode 100644 index 0000000..6524ba5 --- /dev/null +++ b/src/api/controllers/articleBlocks.js @@ -0,0 +1,121 @@ +import models from '../../sequelize/models'; + +import AuthorNotifier from '../../helpers/NotifyAuthorOnArticleBlock'; + +const { + notifyAuthorblock, notifyAuthorUnblock +} = AuthorNotifier; +const { + Article, BlockedArticles, ReportedArticles, User +} = models; + +// eslint-disable-next-line no-array-constructor +const days = new Array( + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday' +); + +/** + * @class + */ +class ArticleBlocks { + /** + * @param {object} req + * @param {object} res + * @return {object} returns a message with operation status + */ + static async reportArticle(req, res) { + const { username } = req.user; + const { comment } = req.body; + const { slug } = req.params; + ReportedArticles.create({ + slug, + comment, + username, + createdAt: new Date(), + updatedAt: new Date() + }).then((out) => { + res.status(201).send({ + status: 201, + data: out + }); + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} returns a message with operation status + */ + static async blockArticle(req, res) { + const { slug } = req.params; + const { user } = req; + const { description } = req.body; + const article = await Article.findOne({ where: { slug } }); + const reporterUsername = await ReportedArticles.findOne({ + where: { slug } + }); + const { dataValues: { email, lastName } } = await User.findOne({ + where: { id: article.authorId } + }); + const username = !reporterUsername + ? null + : reporterUsername.dataValues.username; + const repoterId = username === null ? null : await User.findOne({ where: { username } }); + const id = repoterId === null ? null : repoterId.dataValues.id; + const object = { + reporterId: id, + articleId: article.id, + authorId: article.authorId, + moderatorId: user.id, + blockedDay: days[new Date().getDay() - 1] || 'Sunday', + description + }; + BlockedArticles.create(object).then(async (responce) => { + await Article.update( + { blocked: true }, + { where: { id: responce.articleId } } + ); + await notifyAuthorblock({ email, lastName }); + res.status(201).send({ + status: 201, + data: { + message: 'Article blocked successfully', + responce + } + }); + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} returns a message with operation status + */ + static async unBlockArticle(req, res) { + const { slug } = req.params; + const { id, authorId } = await Article.findOne({ where: { slug } }); + + const { dataValues: { email, lastName } } = await User.findOne({ + where: { id: authorId } + }); + + BlockedArticles.destroy({ where: { articleId: id } }).then(async () => { + await Article.update({ blocked: false }, { where: { slug } }); + await notifyAuthorUnblock({ email, lastName, slug }); + res.status(200).send({ + status: 200, + data: { + message: 'Article unblocked successfully' + } + }); + }); + } +} + +export default ArticleBlocks; diff --git a/src/api/controllers/articleDislikes.js b/src/api/controllers/articleDislikes.js new file mode 100644 index 0000000..137ae7e --- /dev/null +++ b/src/api/controllers/articleDislikes.js @@ -0,0 +1,67 @@ +import db from '../../sequelize/models'; +import updateDislike from '../../helpers/article/updateDislike'; +import hasLiked from '../../helpers/article/hasLiked'; +import dislikesCount from '../../helpers/article/countDislikes'; + +const { LikeDislike } = db; +/** + * @class + */ +class ArticleDislikes { +/** + * @param {object} req + * @param {object} res + * @return {object} returns a message with operation status + */ + static async dislikeArticle(req, res) { + const { id: currentUser } = req.user; + const { slug } = req.params; + + try { + const foundArticle = req.article; + + // If user has liked before, remove like, add dislike. + const { id } = hasLiked(foundArticle.id, currentUser); + if (id) { + updateDislike(id); + return res.status(200).json({ + message: `User ${currentUser} has disliked article ${slug}` + }); + } + + // the user hasn't disliked before, create new dislike + await LikeDislike.create({ + userId: currentUser, + articleId: foundArticle.id, + dislikes: 1, + likes: 0 + }); + + return res + .status(200) + .json({ message: `User ${currentUser} has disliked article ${slug}` }); + } catch (error) { + return res.status(500).json({ error: `${error}` }); + } + } + + /** +* @param {object} req +* @param {object} res +* @return {object} returns a message with operation status +*/ + static async getDislikes(req, res) { + const { slug } = req.params; + const foundArticle = req.article; + + return res.json({ + status: 200, + data: { + slug, + dislikes: dislikesCount(foundArticle.id) + } + }); + } +} + +export default ArticleDislikes; diff --git a/src/api/controllers/articleLikes.js b/src/api/controllers/articleLikes.js new file mode 100644 index 0000000..ff6a49f --- /dev/null +++ b/src/api/controllers/articleLikes.js @@ -0,0 +1,67 @@ +import models from '../../sequelize/models'; +import updateLike from '../../helpers/article/updateLike'; +import hasDisliked from '../../helpers/article/hasDisliked'; +import likesCount from '../../helpers/article/countLikes'; + +const { LikeDislike } = models; +/** + * @class + */ +class ArticleLikes { + /** + * @param {object} req + * @param {object} res + * @return {object} returns a message with operation status + */ + static async likeArticle(req, res) { + const { id: currentUser } = req.user; + const { slug } = req.params; + + try { + const foundArticle = req.article; + + // If user has disliked before, remove dislike, add like. + const { id } = hasDisliked(foundArticle.id, currentUser); + if (id) { + await updateLike(id); + return res + .status(200) + .json({ message: `User ${currentUser} has liked article ${slug}` }); + } + + // the user hasn't liked or disliked before, create new like + await LikeDislike.create({ + userId: currentUser, + articleId: foundArticle.id, + dislikes: 0, + likes: 1 + }); + + return res + .status(200) + .json({ message: `User ${currentUser} has liked article ${slug}` }); + } catch (error) { + return res.status(500).json({ error: `${error}` }); + } + } + + /** + * @param {object} req + * @param {object} res + * @return {object} returns a message with operation status + */ + static async getLikes(req, res) { + const { slug } = req.params; + const foundArticle = req.article; + + return res.status(200).json({ + status: 200, + data: { + articleSlug: slug, + numberOfLikes: likesCount(foundArticle.id) + } + }); + } +} + +export default ArticleLikes; diff --git a/src/api/controllers/articlesController.js b/src/api/controllers/articlesController.js index 1ec3bfb..47f7e25 100644 --- a/src/api/controllers/articlesController.js +++ b/src/api/controllers/articlesController.js @@ -4,35 +4,16 @@ import models from '../../sequelize/models'; import readTime from '../../helpers/ReadTime.helper'; import eventEmitter from '../../helpers/notifications/EventEmitter'; import findUser from '../../helpers/FindUser'; -import AuthorNotifier from '../../helpers/NotifyAuthorOnArticleBlock'; const { - notifyAuthorblock, notifyAuthorUnblock -} = AuthorNotifier; - -const { - User, Article, - LikeDislike, - ReportedArticles, - BlockedArticles, Share } = models; -// eslint-disable-next-line no-array-constructor -const days = new Array( - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - 'Sunday' -); /** * @Author - Audace Uhiriwe */ -class articlesController { +class ArticlesController { /** * creating a new article * @param {object} req - Request. @@ -40,46 +21,11 @@ class articlesController { * @returns {object} - returns created article */ static async createArticle(req, res) { - const { id } = req.user; - - // @check if that user is verified - const user = await User.findOne({ where: { id } }); - if (user.dataValues.verified === false) { - return res - .status(403) - .send({ error: 'Please Verify your account, first!' }); - } - const dataValues = await articles.createNewArticle(req); - const { - slug, - title, - description, - body, - tagList, - author, - updatedAt, - createdAt, - readtime, - views - } = dataValues; - const userInfo = await findUser(author.username); - eventEmitter.emit('publishArticle', userInfo.id, slug); - const result = { - // eslint-disable-next-line max-len - slug, - title, - description, - body, - tagList, - updatedAt, - createdAt, - author, - readtime, - views - }; + const userInfo = await findUser(dataValues.author.username); + eventEmitter.emit('publishArticle', userInfo.id, dataValues.slug); res.status(201).send({ - article: result + article: dataValues }); } @@ -92,7 +38,7 @@ class articlesController { const allArticle = await articles.getAllArticle(); if (!allArticle[0]) { - return res.status(404).send({ error: 'Whoops! No Articles found!' }); + return res.status(200).send({ message: 'No articles yet!', data: allArticle }); } res.status(200).send({ articles: allArticle @@ -188,317 +134,11 @@ class articlesController { res.status(200).send({ message: 'Article deleted successfully!' }); } - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async reportArticle(req, res) { - const { username } = req.user; - const { comment } = req.body; - const { slug } = req.params; - ReportedArticles.create({ - slug, - comment, - username, - createdAt: new Date(), - updatedAt: new Date() - }).then((out) => { - res.status(201).send({ - status: 201, - data: out - }); - }); - } - - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async likeArticle(req, res) { - const { id: currentUser } = req.user; - const { slug } = req.params; - - try { - // Find the article - const query = await Article.findAll({ where: { slug } }); - - if (!query[0]) { - return res - .status(404) - .json({ message: `Article with slug: ${slug} not found` }); - } - - const { dataValues: foundArticle } = query[0]; - - // Check the current user has not liked or disliked this article before - const hasLiked = await LikeDislike.findAll({ - where: { - articleId: foundArticle.id, - userId: currentUser, - likes: 1 - } - }); - const hasDisliked = await LikeDislike.findAll({ - where: { - articleId: foundArticle.id, - userId: currentUser, - dislikes: 1 - } - }); - - // If the user has already liked send a response - if (hasLiked[0]) { - return res.status(403).json({ - message: `User ${currentUser} has already liked article: ${slug}` - }); - } - - // If user has disliked before, remove dislike, add like. - if (hasDisliked[0]) { - await LikeDislike.update( - { dislikes: 0, likes: 1 }, - { where: { id: hasDisliked[0].id } } - ); - return res - .status(200) - .json({ message: `User ${currentUser} has liked article ${slug}` }); - } - - // the user hasn't liked or disliked before, create new like - await LikeDislike.create({ - userId: currentUser, - articleId: foundArticle.id, - dislikes: 0, - likes: 1 - }); - - return res - .status(200) - .json({ message: `User ${currentUser} has liked article ${slug}` }); - } catch (error) { - return res.status(500).json({ error: `${error}` }); - } - } - - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async dislikeArticle(req, res) { - const { id: currentUser } = req.user; - const { slug } = req.params; - - try { - // Find the article - const query = await Article.findAll({ where: { slug } }); - - if (!query[0]) { - return res - .status(404) - .json({ message: `Article with slug: ${slug} not found` }); - } - - const { dataValues: foundArticle } = query[0]; - - // Check the current user has not liked or disliked this article before - const hasLiked = await LikeDislike.findAll({ - where: { - articleId: foundArticle.id, - userId: currentUser, - likes: 1 - } - }); - const hasDisliked = await LikeDislike.findAll({ - where: { - articleId: foundArticle.id, - userId: currentUser, - dislikes: 1 - } - }); - - // If the user has already disliked send a response - if (hasDisliked[0]) { - return res.status(403).json({ - message: `User ${currentUser} has already disliked article: ${slug}` - }); - } - - // If user has liked before, remove like, add dislike. - if (hasLiked[0]) { - await LikeDislike.update( - { dislikes: 1, likes: 0 }, - { where: { id: hasLiked[0].id } } - ); - return res.status(200).json({ - message: `User ${currentUser} has disliked article ${slug}` - }); - } - - // the user hasn't disliked before, create new dislike - await LikeDislike.create({ - userId: currentUser, - articleId: foundArticle.id, - dislikes: 1, - likes: 0 - }); - - return res - .status(200) - .json({ message: `User ${currentUser} has disliked article ${slug}` }); - } catch (error) { - return res.status(500).json({ error: `${error}` }); - } - } - - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async getLikes(req, res) { - const { slug } = req.params; - - // Find the article - const query = await Article.findAll({ where: { slug } }); - - if (!query[0]) { - return res - .status(404) - .json({ message: `Article with slug: ${slug} not found` }); - } - - const { dataValues: foundArticle } = query[0]; - - // Get likes - const likeCount = await LikeDislike.count({ - where: { articleId: foundArticle.id, likes: 1 } - }); - - return res.status(200).json({ - status: 200, - data: { - articleSlug: slug, - numberOfLikes: likeCount - } - }); - } - - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async getDislikes(req, res) { - const { slug } = req.params; - - // Find the article - const query = await Article.findAll({ where: { slug } }); - - if (!query[0]) { - return res - .status(404) - .json({ message: `Article with slug: ${slug} not found` }); - } - - const { dataValues: foundArticle } = query[0]; - - // Get likes - const likeCount = await LikeDislike.count({ - where: { - articleId: foundArticle.id, - dislikes: 1 - } - }); - - return res.status(200).json({ - status: 200, - data: { - articleSlug: slug, - numberOfDislikes: likeCount - } - }); - } - - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async blockArticle(req, res) { - const { slug } = req.params; - const { user } = req; - const { description } = req.body; - const article = await Article.findOne({ where: { slug } }); - const reporterUsername = await ReportedArticles.findOne({ - where: { slug } - }); - const { dataValues: { email, lastName } } = await User.findOne({ - where: { id: article.authorId } - }); - const username = !reporterUsername - ? null - : reporterUsername.dataValues.username; - const repoterId = username === null ? null : await User.findOne({ where: { username } }); - const id = repoterId === null ? null : repoterId.dataValues.id; - const object = { - reporterId: id, - articleId: article.id, - authorId: article.authorId, - moderatorId: user.id, - blockedDay: days[new Date().getDay() - 1], - description - }; - BlockedArticles.create(object).then(async (responce) => { - await Article.update( - { blocked: true }, - { where: { id: responce.articleId } } - ); - await notifyAuthorblock({ email, lastName }); - res.status(201).send({ - status: 201, - data: { - message: 'Article blocked successfully', - responce - } - }); - }); - } - - /** - * @param {object} req - * @param {object} res - * @return {object} returns a message with operation status - */ - static async unBlockArticle(req, res) { - const { slug } = req.params; - const { id, authorId } = await Article.findOne({ where: { slug } }); - - const { dataValues: { email, lastName } } = await User.findOne({ - where: { id: authorId } - }); - - BlockedArticles.destroy({ where: { articleId: id } }).then(async () => { - await Article.update({ blocked: false }, { where: { slug } }); - await notifyAuthorUnblock({ email, lastName, slug }); - res.status(200).send({ - status: 200, - data: { - message: 'Article unblocked successfully' - } - }); - }); - } - /** * @param {object} req * @param {object} res * @returns {object} Object representing the response returned */ - - // eslint-disable-next-line require-jsdoc static async share(req, res) { const { slug, provider } = req.share; const { id } = req.user; @@ -514,4 +154,4 @@ class articlesController { } } -export default articlesController; +export default ArticlesController; diff --git a/src/api/controllers/auth.js b/src/api/controllers/auth.js index 2d00f5a..55539a7 100644 --- a/src/api/controllers/auth.js +++ b/src/api/controllers/auth.js @@ -7,9 +7,10 @@ import db from '../../sequelize/models/index'; import verifyTemplate from '../../helpers/emailVerifyTemplate'; import template from '../../helpers/emailTemplate'; import workers from '../../workers'; +import optIn from '../../helpers/subscriptions/OptIn'; const { generateToken, decodeToken } = tokenHelper; -const { User, Blacklist, Opt } = db; +const { User, Blacklist } = db; const { queueEmailWorker } = workers; dotenv.config(); @@ -28,14 +29,12 @@ class AuthController { */ static async register(req, res) { let { body } = req; - - if (Object.keys(req.body).length === 0) { + if (!Object.keys(req.body).length) { return res.status(400).json({ status: 400, error: 'No data sent' }); } - body = await omit(body, ['roles']); const userObject = { @@ -52,15 +51,8 @@ class AuthController { password: null, }); - await Opt.create({ - userId: newUser.id, - type: 'email' - }); - - await Opt.create({ - userId: newUser.id, - type: 'inapp' - }); + await optIn({ userId: newUser.id, type: 'email' }); + await optIn({ userId: newUser.id, type: 'inapp' }); const htmlToSend = verifyTemplate.sendVerification(`${newUser.firstName} ${newUser.lastName}`, newUser.email, token); @@ -72,7 +64,7 @@ class AuthController { message: 'You will reveive an account verification email shortly', email: `${newUser.email}`, token - }, + } }); } } diff --git a/src/api/controllers/comments.js b/src/api/controllers/comments.js index 2277595..4b80861 100644 --- a/src/api/controllers/comments.js +++ b/src/api/controllers/comments.js @@ -5,7 +5,7 @@ import eventEmitter from '../../helpers/notifications/EventEmitter'; /** * @class */ -export default class comments { +export default class Comments { /** * @description - Users should create comment * @param {Object} req - Request Object @@ -124,47 +124,31 @@ export default class comments { * @returns {Object} - Response object */ static async deleteComment(req, res) { - const { id, firstName, roles } = req.user; + const { roles } = req.user; const { commentId } = req.params; - const findComment = await models.Comment.findAll({ - where: { - id: commentId - } - }); - const nestedComments = await models.Comment.findAll({ - where: { - commentId - } - }); - const { userId } = findComment[0].dataValues; - if (userId === id || roles.includes('moderator' || 'admin')) { - if (nestedComments[0]) { - await models.Comment.update( - { - comment: - 'This comment has been deleted!' - }, - { where: { id: commentId } } - ).then(() => { - return res.status(200).json({ - message: roles.includes('moderator' || 'admin') ? 'Comment deleted by moderator' : 'Comment deleted!' - }); + const { nestedComments } = req; + if (nestedComments[0]) { + await models.Comment.update( + { + comment: 'This comment has been deleted!' + }, + { where: { id: commentId } } + ).then(() => { + return res.status(200).json({ + message: roles.includes('moderator' || 'admin') ? 'Comment deleted by moderator' : 'Comment deleted!' }); - } else { - await models.Comment.destroy({ - where: { - id: commentId - } - }).then(() => { - return res.status(200).json({ - message: 'Comment deleted!' - }); + }); + } else { + await models.Comment.destroy({ + where: { + id: commentId + } + }).then(() => { + return res.status(200).json({ + message: 'Comment deleted!' }); - } + }); } - return res.status(403).json({ - message: `Dear ${firstName}, You do not have the right to delete this comment!` - }); } /** diff --git a/src/api/controllers/optOut.js b/src/api/controllers/optOut.js new file mode 100644 index 0000000..517ab34 --- /dev/null +++ b/src/api/controllers/optOut.js @@ -0,0 +1,43 @@ +import optOut from '../../helpers/subscriptions/OPtOut'; + +/** + * @class + */ +class OptOut { + /** + * @description User should be able to opt-out in-app notifications + * @param {Object} req Request Object + * @param {Object} res Response Object + * @returns {Object} Response object + */ + static async optOutApp(req, res) { + const { id } = req.user; + optOut({ + userId: id, + type: 'inapp' + }); + res.json({ + message: 'You are now unsubscribed from receiving inapp notifications' + }); + } + + /** + * @description User should be able to opt-out email notifications + * @param {Object} req Request object + * @param {Object} res Response object + * @returns {Object} Response object + */ + static async optOutEmail(req, res) { + const { id } = req.user; + optOut({ + userId: id, + type: 'email' + }); + + return res.json({ + message: 'You are now unsubscribed from receiving email notifications!' + }); + } +} + +export default OptOut; diff --git a/src/api/controllers/profiles.js b/src/api/controllers/profiles.js index e201eac..77782ae 100644 --- a/src/api/controllers/profiles.js +++ b/src/api/controllers/profiles.js @@ -37,12 +37,8 @@ export default class ProfilesController { static async updateProfile(req, res) { let { body } = req; const { user, file, params } = req; - - if (!file && Object.keys(body) < 1) return res.status(400).send({ message: 'Cannot update empty object' }); - - if (file && Object.keys(body) < 1) { - uploadImageWorker(file, params.id, null); - return res.status(200).json({ status: 200, message: 'Your image will be updated shortly' }); + if (!file && Object.keys(body) < 1) { + return res.status(400).send({ message: 'Cannot update empty object' }); } if (!user.roles.includes('admin')) { @@ -51,24 +47,14 @@ export default class ProfilesController { const updatedProfile = { ...body }; - try { - const updatedUser = await User.update( - updatedProfile, - { where: { id: params.id } }, - ); - - if (!updatedUser[0]) { - return res - .status(404) - .json({ message: `Could not find user with id: ${user.id}` }); - } + const updatedUser = await User.update( + updatedProfile, + { where: { id: params.id } }, + ); - uploadImageWorker(file, params.id, null); + uploadImageWorker(file, params.id, null); - return res.status(200).json({ user: { updatedUser } }); - } catch (error) { - return res.status(500).json({ error: `${error}` }); - } + return res.status(200).json({ user: { updatedUser } }); } /** @@ -131,7 +117,7 @@ export default class ProfilesController { })) .catch(error => (error.name === 'SequelizeUniqueConstraintError' ? res.status(400).send({ - error: ` ${username} is already your follower ` + error: ` You already follow ${username} ` }) : res.status(500).json({ error: 'something went wrong' }))); } @@ -155,11 +141,11 @@ export default class ProfilesController { }) : null; - if (unfollowedUser && unfollowedUser.errors) { - return res - .status(500) - .json({ errors: 'Internal server error' }); - } + // if (unfollowedUser && unfollowedUser.errors) { + // return res + // .status(500) + // .json({ errors: 'Internal server error' }); + // } return unfollowedUser ? res.status(200).json({ diff --git a/src/api/controllers/ratingController.js b/src/api/controllers/ratingController.js index 3febbf2..1cbc5cd 100644 --- a/src/api/controllers/ratingController.js +++ b/src/api/controllers/ratingController.js @@ -117,20 +117,20 @@ class Ratings { data: { report: { '1st': Number(report.oneStars), - '2st': Number(report.twoStars), - '3st': Number(report.threeStars), - '4st': Number(report.fourStars), - '5st': Number(report.fiveStars), + '2nd': Number(report.twoStars), + '3rd': Number(report.threeStars), + '4th': Number(report.fourStars), + '5th': Number(report.fiveStars), 'Number of User ': Number(report.totalCounts), 'Total Ratings': Number(report.totalRatings), Average: Number(report.average) }, percentage: { '1st': `${percentage.oneStars} %`, - '2st': `${percentage.twoStars} %`, - '3st': `${percentage.threeStars} %`, - '4st': `${percentage.fourStars} %`, - '5st': `${percentage.fiveStars} %` + '2nd': `${percentage.twoStars} %`, + '3rd': `${percentage.threeStars} %`, + '4th': `${percentage.fourStars} %`, + '5th': `${percentage.fiveStars} %` } } }); diff --git a/src/api/controllers/socialLogin.js b/src/api/controllers/socialLogin.js index f66b370..6bcab1d 100644 --- a/src/api/controllers/socialLogin.js +++ b/src/api/controllers/socialLogin.js @@ -1,5 +1,6 @@ import models from '../../sequelize/models'; import tokenGeneration from '../../helpers/Token.helper'; +import loginResponse from '../../helpers/auth/socialLoginResponse'; const userInfo = { async googleLogin(req, res) { @@ -14,18 +15,8 @@ const userInfo = { socialId: req.user.id, }); if (newUser) { - const { - dataValues: { - id, firstName, lastName, email, provider - } - } = newUser; const token = await tokenGeneration.generateToken(newUser.dataValues); - return res.status(200).json({ - message: `Welcome to Authors Haven ${displayName} `, - data: { - token, id, firstName, lastName, email, provider - }, - }); + loginResponse(res, newUser, displayName, token); } }, async facebookLogin(req, res) { @@ -41,18 +32,8 @@ const userInfo = { socialId: req.user.id, }); if (newUser) { - const { - dataValues: { - id, firstName, lastName, email, provider - } - } = newUser; const token = await tokenGeneration.generateToken(newUser.dataValues); - return res.status(200).json({ - message: `Welcome to Authors Haven ${displayName} `, - data: { - token, id, firstName, lastName, email, provider - }, - }); + loginResponse(res, newUser, displayName, token); } }, async twitterLogin(req, res) { @@ -70,18 +51,8 @@ const userInfo = { socialId: req.user.id, }); if (newUser) { - const { - dataValues: { - id, firstName, lastName, email, provider - } - } = newUser; const token = await tokenGeneration.generateToken(newUser.dataValues); - return res.status(200).json({ - message: `Welcome to Authors Haven ${displayName} `, - data: { - token, id, firstName, lastName, email, provider - }, - }); + loginResponse(res, newUser, displayName, token); } }, }; diff --git a/src/api/controllers/stats.js b/src/api/controllers/stats.js index a3d830f..2500795 100644 --- a/src/api/controllers/stats.js +++ b/src/api/controllers/stats.js @@ -1,15 +1,16 @@ import models from '../../sequelize/models'; +import shareCount from '../../helpers/stats/shareCount'; +import commentCount from '../../helpers/stats/commentCount'; const { Article, - Comment, Share } = models; /** * @Author - Mireille Niwemuhuza */ -class statsController { +class StatsController { /** * @description - Users should be able to view how many times an article has been viewed * @param {object} req - Request object @@ -40,18 +41,12 @@ class statsController { static async commentNumber(req, res) { const { slug } = req.params; - // Count comments - const countComment = await Comment.count({ - where: { - slug - } - }); - + const count = await commentCount(slug); return res.status(200).json({ status: 200, data: { slug, - countComment + comments: count } }); } @@ -65,15 +60,9 @@ class statsController { static async facebookShares(req, res) { const { slug } = req.params; - // Count facebook shares - const shares = await Share.count({ - where: { - slug, - provider: 'facebook' - } - }); + const shares = await shareCount(slug, 'facebook'); - return res.status(200).json({ + res.status(200).json({ status: 200, data: { slug, @@ -92,15 +81,8 @@ class statsController { const { slug } = req.params; // Count shares on twitter - const shares = await Share.count({ - where: { - slug, - provider: 'twitter' - } - }); - + const shares = await shareCount(slug, 'twitter'); return res.status(200).json({ - status: 200, data: { slug, shares @@ -118,18 +100,13 @@ class statsController { const { slug } = req.params; // Count shares on email - const shares = await Share.count({ - where: { - slug, - provider: 'email' - } - }); + const count = await shareCount(slug, 'email'); - return res.status(200).json({ + return res.json({ status: 200, data: { slug, - shares + shares: count } }); } @@ -159,4 +136,4 @@ class statsController { }); } } -export default statsController; +export default StatsController; diff --git a/src/api/routes/articlesRouter.js b/src/api/routes/articlesRouter.js index fc2e9e7..f1cf714 100644 --- a/src/api/routes/articlesRouter.js +++ b/src/api/routes/articlesRouter.js @@ -1,5 +1,8 @@ import { Router } from 'express'; import articlesController from '../controllers/articlesController'; +import articleLikes from '../controllers/articleLikes'; +import articleDislikes from '../controllers/articleDislikes'; +import articleBlocks from '../controllers/articleBlocks'; import Auth from '../../middleware/auth'; import check from '../../middleware/checkOwner'; import validateBody from '../../middleware/validateBody'; @@ -20,6 +23,10 @@ import highlight from '../controllers/highlightController'; import highlightExist from '../../middleware/highlightExist'; import textExist from '../../middleware/textExist'; +import isVerified from '../../middleware/isVerified'; +import checkArticleLikes from '../../middleware/checkArticleLikes'; +import hasNested from '../../middleware/hasNestedComments'; +import deleteAllowed from '../../middleware/commentDeleteAllowed'; const articlesRouter = Router(); const { @@ -31,16 +38,17 @@ const { getOneArticle, updateArticle, deleteArticle, - likeArticle, - dislikeArticle, - getLikes, - getDislikes, - reportArticle, - blockArticle, - unBlockArticle, share } = articlesController; + +const { reportArticle, blockArticle, unBlockArticle } = articleBlocks; + +const { + likeArticle, getLikes +} = articleLikes; +const { dislikeArticle, getDislikes } = articleDislikes; const { verifyToken, checkIsModerator } = Auth; +const { checkLikes, checkDislikes } = checkArticleLikes; const { createRatings, UpdateRatings } = RatingController; const { bookmark } = bookmarkController; const { createHighlights } = highlight; @@ -55,28 +63,28 @@ const { checkComment, checkParameter, articleExists } = comment; const { liked, disliked } = checkLikesandDislikes; articlesRouter - .post('/', verifyToken, validateBody('createArticle'), createArticle) + .post('/', verifyToken, validateBody('createArticle'), isVerified, createArticle) .get('/', paginate, searchForArticle, getAllArticle); articlesRouter .get('/:slug', slugExist, isThisArticleBlocked, getOneArticle) - .put('/:slug', verifyToken, check.articleOwner, validateBody('updateArticle'), updateArticle) - .delete('/:slug', verifyToken, check.articleOwner, deleteArticle); + .put('/:slug', verifyToken, isVerified, check.articleOwner, validateBody('updateArticle'), updateArticle) + .delete('/:slug', verifyToken, isVerified, check.articleOwner, deleteArticle); articlesRouter - .get('/:slug/like', getLikes) - .post('/:slug/like', verifyToken, likeArticle); + .get('/:slug/like', slugExist, getLikes) + .post('/:slug/like', verifyToken, slugExist, checkLikes, likeArticle); articlesRouter - .get('/:slug/dislike', getDislikes) - .post('/:slug/dislike', verifyToken, dislikeArticle); + .get('/:slug/dislike', slugExist, getDislikes) + .post('/:slug/dislike', verifyToken, slugExist, checkDislikes, dislikeArticle); // Comments routes articlesRouter.post('/:slug/comments', verifyToken, validateBody('checkComment'), articleExists, checkComment, createComment); articlesRouter.post('/:slug/comments/:commentId', verifyToken, validateBody('checkComment'), articleExists, checkComment, commentAcomment); articlesRouter.patch('/comments/:commentId', verifyToken, validateBody('checkComment'), checkParameter, editComment); -articlesRouter.delete('/comments/:commentId', verifyToken, checkParameter, deleteComment); -articlesRouter.get('/:slug/comments', getComment); +articlesRouter.delete('/comments/:commentId', verifyToken, checkParameter, hasNested, deleteAllowed, deleteComment); +articlesRouter.get('/:slug/comments', slugExist, getComment); articlesRouter.post('/:slug/rating', verifyToken, validateBody('validateRating'), slugExist, createRatings); articlesRouter.put('/:slug/rating', verifyToken, validateBody('validateRating'), slugExist, UpdateRatings); @@ -100,7 +108,7 @@ articlesRouter.get('/:slug/share/email', verifyToken, slugExist, shareArticle, s articlesRouter.post('/:slug/bookmark', verifyToken, slugExist, bookmark); -articlesRouter.post('/:slug/report', verifyToken, validateBody('checkComment'), slugExist, reportArticle); +articlesRouter.post('/:slug/report', verifyToken, isVerified, validateBody('checkComment'), slugExist, reportArticle); // get comment edit history articlesRouter.get('/comments/:commentId/history', verifyToken, checkParameter, commentHistory); diff --git a/src/api/routes/userRouter.js b/src/api/routes/userRouter.js index edff992..6716256 100644 --- a/src/api/routes/userRouter.js +++ b/src/api/routes/userRouter.js @@ -4,6 +4,9 @@ import Auth from '../../middleware/auth'; import validateBody from '../../middleware/validateBody'; import upload from '../../handlers/multer'; import OptController from '../controllers/optController'; +import profileExists from '../../middleware/profile/profileExists'; +import uploadImage from '../../middleware/profile/uploadImage'; + const userRouter = Router(); const { updateProfile, deleteProfile } = ProfilesController; @@ -18,9 +21,7 @@ userRouter.delete('/optinemail', verifyToken, optOutEmail); userRouter.delete('/optinapp', verifyToken, optOutApp); userRouter .route('/:id') - .put(verifyToken, checkOwnership, validateBody('updateUser'), upload.single('image'), updateProfile) + .put(verifyToken, profileExists, checkOwnership, validateBody('updateUser'), upload.single('image'), uploadImage, updateProfile) .delete(verifyToken, checkIsAdmin, deleteProfile); -userRouter.put('/', verifyToken, validateBody('updateUser'), upload.single('image'), updateProfile); - export default userRouter; diff --git a/src/helpers/SendMail.helper.js b/src/helpers/SendMail.helper.js deleted file mode 100644 index aac84bf..0000000 --- a/src/helpers/SendMail.helper.js +++ /dev/null @@ -1,38 +0,0 @@ - -import mailer from 'nodemailer'; -import mailTemplate from './MailTemplate.helper'; - -const transporter = mailer.createTransport({ - service: 'gmail', - auth: { - user: process.env.AUTHOSHAVEN_USER, - pass: process.env.AUTHOSHAVEN_PASS - } -}); -/** - * @author Elie Mugenzi - * @class MailHelper - * @description A helper class for sending emails - */ -class MailHelper { - /** - * Send mail - * @param {Object} param0 - Object which contains email information - * @returns {Object} Results after sending mail - */ - static async sendMail({ - to, names, subject, message, token - }) { - const msg = { - from: `Authors Haven<${process.env.AUTHOSHAVEN_USER}>`, - to, - subject, - text: message, - html: mailTemplate({ to, token, names }) - }; - const result = await transporter.sendMail(msg); - return result; - } -} - -export default MailHelper; diff --git a/src/helpers/article/countDislikes.js b/src/helpers/article/countDislikes.js new file mode 100644 index 0000000..df85d6b --- /dev/null +++ b/src/helpers/article/countDislikes.js @@ -0,0 +1,15 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const dislikesCount = async (articleId) => { + const dislikes = await LikeDislike.count({ + where: { + articleId, + dislikes: 1 + } + }); + return dislikes; +}; + +export default dislikesCount; diff --git a/src/helpers/article/countLikes.js b/src/helpers/article/countLikes.js new file mode 100644 index 0000000..5e4aaac --- /dev/null +++ b/src/helpers/article/countLikes.js @@ -0,0 +1,16 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const likesCount = async (articleId) => { + const likes = await LikeDislike.count({ + where: { + articleId, + likes: 1 + } + }); + + return likes; +}; + +export default likesCount; diff --git a/src/helpers/article/hasDisliked.js b/src/helpers/article/hasDisliked.js new file mode 100644 index 0000000..5936c28 --- /dev/null +++ b/src/helpers/article/hasDisliked.js @@ -0,0 +1,16 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const hasDisliked = async (articleId, userId) => { + const { id } = await LikeDislike.findOne({ + where: { + articleId, + userId + } + }); + + return { id } || null; +}; + +export default hasDisliked; diff --git a/src/helpers/article/hasLiked.js b/src/helpers/article/hasLiked.js new file mode 100644 index 0000000..2bc0431 --- /dev/null +++ b/src/helpers/article/hasLiked.js @@ -0,0 +1,17 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const hasLiked = async (articleId, userId) => { + const { id } = await LikeDislike.findOne({ + where: { + articleId, + userId, + likes: 1 + } + }); + + return { id } || null; +}; + +export default hasLiked; diff --git a/src/helpers/article/isBlocked.js b/src/helpers/article/isBlocked.js new file mode 100644 index 0000000..c0a6892 --- /dev/null +++ b/src/helpers/article/isBlocked.js @@ -0,0 +1,15 @@ +import db from '../../sequelize/models'; + +const { BlockedArticles } = db; + +const isArticleBlocked = async (articleId) => { + const blocked = await BlockedArticles.findOne({ + where: { + articleId + } + }); + + return !!blocked; +}; + +export default isArticleBlocked; diff --git a/src/helpers/article/updateDislike.js b/src/helpers/article/updateDislike.js new file mode 100644 index 0000000..2b402cf --- /dev/null +++ b/src/helpers/article/updateDislike.js @@ -0,0 +1,16 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const updateDislike = async (id) => { + await LikeDislike.update({ + likes: 0, + dislikes: 1 + }, { + where: { + id + } + }); +}; + +export default updateDislike; diff --git a/src/helpers/article/updateLike.js b/src/helpers/article/updateLike.js new file mode 100644 index 0000000..d4d2b26 --- /dev/null +++ b/src/helpers/article/updateLike.js @@ -0,0 +1,17 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const updateLike = async (id) => { + const res = await LikeDislike.update({ + likes: 1, + dislikes: 0 + }, { + where: { + id + } + }); + return !!res; +}; + +export default updateLike; diff --git a/src/helpers/auth/socialLoginResponse.js b/src/helpers/auth/socialLoginResponse.js new file mode 100644 index 0000000..9d16982 --- /dev/null +++ b/src/helpers/auth/socialLoginResponse.js @@ -0,0 +1,18 @@ + + +const socialResponse = (res, dataValues, displayName, token) => { + const { + id, firstName, lastName, email, provider + } = dataValues; + const responseData = { + id, firstName, lastName, email, provider + }; + + res.json({ + message: `Welcome to AuthorsHaven, ${displayName}`, + token, + data: responseData + }); +}; + +export default socialResponse; diff --git a/src/helpers/comment/hasDisliked.js b/src/helpers/comment/hasDisliked.js new file mode 100644 index 0000000..8aafa52 --- /dev/null +++ b/src/helpers/comment/hasDisliked.js @@ -0,0 +1,16 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const hasDislikedComment = async (userId, commentId) => { + const disliked = await LikeDislike.findOne({ + where: { + commentId, + userId, + dislikes: 1 + } + }); + return !!disliked; +}; + +export default hasDislikedComment; diff --git a/src/helpers/comment/hasLiked.js b/src/helpers/comment/hasLiked.js new file mode 100644 index 0000000..2d1424e --- /dev/null +++ b/src/helpers/comment/hasLiked.js @@ -0,0 +1,16 @@ +import db from '../../sequelize/models'; + +const { LikeDislike } = db; + +const hasLikedComment = async (userId, commentId) => { + const liked = await LikeDislike.findOne({ + where: { + commentId, + userId, + likes: 1 + } + }); + return !!liked; +}; + +export default hasLikedComment; diff --git a/src/helpers/mailer/Mail.config.js b/src/helpers/mailer/Mail.config.js new file mode 100644 index 0000000..e16c201 --- /dev/null +++ b/src/helpers/mailer/Mail.config.js @@ -0,0 +1,14 @@ +import mailer from 'nodemailer'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const transporter = mailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.AUTHOSHAVEN_USER, + pass: process.env.AUTHOSHAVEN_PASS + } +}); + +export default transporter; diff --git a/src/helpers/mailer/SendAnyEmail.js b/src/helpers/mailer/SendAnyEmail.js index 89564c6..39cb414 100644 --- a/src/helpers/mailer/SendAnyEmail.js +++ b/src/helpers/mailer/SendAnyEmail.js @@ -1,25 +1,15 @@ -import dotenv from 'dotenv'; -import nodemailer from 'nodemailer'; - -dotenv.config(); +import mailer from './Mail.config'; const sendEmail = async (mail, htmlToSend, subject) => { - const transport = nodemailer.createTransport({ - service: 'gmail', - auth: { - user: process.env.AUTHOSHAVEN_USER, - pass: process.env.AUTHOSHAVEN_PASS - } - }); const mailOptions = { - from: 'Authors Haven', + from: `Authors Haven<${process.env.AUTHOSHAVEN_USER}>`, to: `${mail.email}`, subject, text: '', html: htmlToSend }; - transport.sendMail(mailOptions, async () => true); + mailer.sendMail(mailOptions, async () => true); }; export default sendEmail; diff --git a/src/helpers/responses/201.js b/src/helpers/responses/201.js new file mode 100644 index 0000000..ca5d61b --- /dev/null +++ b/src/helpers/responses/201.js @@ -0,0 +1,8 @@ +const created = (res, message = 'Created!', data = {}) => { + res.status(201).json({ + message, + data + }); +}; + +export default created; diff --git a/src/helpers/stats/commentCount.js b/src/helpers/stats/commentCount.js new file mode 100644 index 0000000..d1a21e3 --- /dev/null +++ b/src/helpers/stats/commentCount.js @@ -0,0 +1,14 @@ +import db from '../../sequelize/models'; + +const { Comment } = db; + +const commentCount = async (slug) => { + const count = await Comment.count({ + where: { + slug + } + }); + return count; +}; + +export default commentCount; diff --git a/src/helpers/stats/shareCount.js b/src/helpers/stats/shareCount.js new file mode 100644 index 0000000..dec45df --- /dev/null +++ b/src/helpers/stats/shareCount.js @@ -0,0 +1,15 @@ +import db from '../../sequelize/models'; + +const { Share } = db; + +const shareCount = async (slug, provider) => { + const count = await Share.count({ + where: { + slug, + provider + } + }); + return count; +}; + +export default shareCount; diff --git a/src/helpers/subscriptions/OPtOut.js b/src/helpers/subscriptions/OPtOut.js new file mode 100644 index 0000000..05c2790 --- /dev/null +++ b/src/helpers/subscriptions/OPtOut.js @@ -0,0 +1,29 @@ +import db from '../../sequelize/models'; + +const { Opt } = db; + +const optOut = async ({ userId, type }) => { + switch (type) { + case 'email': + await Opt.destroy({ + where: { + userId, + type + } + }); + break; + case 'inapp': + await Opt.destroy({ + where: { + userId, + type + } + }); + break; + default: + break; + } + return true; +}; + +export default optOut; diff --git a/src/helpers/subscriptions/OptIn.js b/src/helpers/subscriptions/OptIn.js new file mode 100644 index 0000000..57fa42d --- /dev/null +++ b/src/helpers/subscriptions/OptIn.js @@ -0,0 +1,21 @@ +import db from '../../sequelize/models'; + +const { Opt } = db; + +const optIn = async ({ userId, type }) => { + switch (type) { + case 'email': + await Opt.create({ + userId, + type + }); + break; + case 'inapp': + break; + default: + break; + } + return true; +}; + +export default optIn; diff --git a/src/helpers/subscriptions/isOptedIn.js b/src/helpers/subscriptions/isOptedIn.js new file mode 100644 index 0000000..b01b19f --- /dev/null +++ b/src/helpers/subscriptions/isOptedIn.js @@ -0,0 +1,15 @@ +import db from '../../sequelize/models'; + +const { Opt } = db; + +const isOptedIn = async (userId, type) => { + const inn = await Opt.findOne({ + where: { + userId, + type + } + }); + return !!inn; +}; + +export default isOptedIn; diff --git a/src/helpers/user/findUserByEmail.js b/src/helpers/user/findUserByEmail.js new file mode 100644 index 0000000..80cd20b --- /dev/null +++ b/src/helpers/user/findUserByEmail.js @@ -0,0 +1,14 @@ +import db from '../../sequelize/models'; + +const { User } = db; + +const findByEmail = async (email) => { + const user = await User.findOne({ + where: { + email + } + }); + return !!user; +}; + +export default findByEmail; diff --git a/src/helpers/user/findUserByUsername.js b/src/helpers/user/findUserByUsername.js new file mode 100644 index 0000000..431dbef --- /dev/null +++ b/src/helpers/user/findUserByUsername.js @@ -0,0 +1,15 @@ +import db from '../../sequelize/models'; + +const { User } = db; + +const findByUsername = async (username) => { + const user = await User.findOne({ + where: { + username + } + }); + + return !!user; +}; + +export default findByUsername; diff --git a/src/middleware/articleNotBlocked.js b/src/middleware/articleNotBlocked.js index 3110749..fd34027 100644 --- a/src/middleware/articleNotBlocked.js +++ b/src/middleware/articleNotBlocked.js @@ -1,18 +1,11 @@ -import db from '../sequelize/models/index'; - -const { BlockedArticles } = db; +import isBlocked from '../helpers/article/isBlocked'; const notblocked = async (req, res, next) => { const { article } = req; - const blockedArticle = await BlockedArticles.findOne({ - where: { articleId: article.id } - }); - if (!blockedArticle) { + if (!isBlocked(article.id)) { return res.status(400).send({ status: 400, - error: { - message: 'The article you are trying to unblock is not blocked.' - } + error: 'The article you are trying to unblock is not blocked.' }); } next(); diff --git a/src/middleware/checkArticleLikes.js b/src/middleware/checkArticleLikes.js new file mode 100644 index 0000000..25f62cd --- /dev/null +++ b/src/middleware/checkArticleLikes.js @@ -0,0 +1,63 @@ +import db from '../sequelize/models'; + +const { LikeDislike } = db; +/** + * @class + */ +class CheckArticleLikes { + /** + * + * @param {Object} req - Request Object + * @param {Object} res - Response object + * @param {Function} next - Next function + * @returns {Object} - Returns Response object + */ + static async checkLikes(req, res, next) { + const foundArticle = req.article; + const { id } = req.user; + const { slug } = req.params; + + const hasLiked = await LikeDislike.findOne({ + where: { + articleId: foundArticle.id, + userId: id, + likes: 1 + } + }); + if (hasLiked) { + return res.status(400).json({ + error: `You already liked this article: ${slug}` + }); + } + next(); + } + + /** + * + * @param {Object} req - Request object + * @param {Object} res - Response object + * @param {Function} next - Next function + * @returns {Object} - Response Object + */ + static async checkDislikes(req, res, next) { + const { id } = req.user; + const { slug } = req.params; + const foundArticle = req.article; + + const hasDisliked = await LikeDislike.findOne({ + where: { + articleId: foundArticle.id, + userId: id, + dislikes: 1 + } + }); + if (hasDisliked) { + return res.status(400).json({ + error: `You already disliked this article: ${slug}` + }); + } + next(); + } +} + +export default CheckArticleLikes; diff --git a/src/middleware/checkLikesDislikes.js b/src/middleware/checkLikesDislikes.js index a40b494..62be4bf 100644 --- a/src/middleware/checkLikesDislikes.js +++ b/src/middleware/checkLikesDislikes.js @@ -1,4 +1,6 @@ -import models from '../sequelize/models'; + +import hasLiked from '../helpers/comment/hasLiked'; +import hasDisliked from '../helpers/comment/hasDisliked'; /** * @class checkLikesDislikes @@ -15,20 +17,10 @@ export default class checkLikesDislikes { static async liked(req, res, next) { const { commentId } = req.params; const { id, firstName } = req.user; - const hasLiked = await models.LikeDislike.findAll({ - where: { - commentId, - userId: id, - likes: 1 - } - }); - // If the user has already liked that comment - if (hasLiked[0]) { - return res.status(400).json({ - message: `Dear ${firstName}, You have already liked this comment!` - }); - } - next(); + const isLiked = await hasLiked(id, commentId); + return isLiked ? res.status(400).json({ + message: `Dear ${firstName}, you have already liked this comment!` + }) : next(); } /** @@ -41,17 +33,10 @@ export default class checkLikesDislikes { static async disliked(req, res, next) { const { commentId } = req.params; const { id, firstName } = req.user; - const hasDisliked = await models.LikeDislike.findAll({ - where: { - commentId, - userId: id, - dislikes: 1 - } - }); - // If the user has already disliked that comment - if (hasDisliked[0]) { + const isDisliked = await hasDisliked(id, commentId); + if (isDisliked) { return res.status(400).json({ - message: `Dear ${firstName}, You have already disliked this comment!` + message: `Dear ${firstName}, you have already disliked this comment!` }); } next(); diff --git a/src/middleware/checkUsernameExist.js b/src/middleware/checkUsernameExist.js deleted file mode 100644 index e78b477..0000000 --- a/src/middleware/checkUsernameExist.js +++ /dev/null @@ -1,21 +0,0 @@ -import models from '../sequelize/models'; - -const usernameAvailability = { - async usernameExist(req, res, next) { - const { username } = req.params; - const user = await models.User.findAll({ - where: { - username - } - }); - if (!user.length) { - return res.status(404).json({ - error: 'username does not exist' - }); - } - - next(); - } -}; - -export default usernameAvailability; diff --git a/src/middleware/commentDeleteAllowed.js b/src/middleware/commentDeleteAllowed.js new file mode 100644 index 0000000..9c1e135 --- /dev/null +++ b/src/middleware/commentDeleteAllowed.js @@ -0,0 +1,14 @@ + + +const deleteAllowed = async (req, res, next) => { + const { id, roles } = req.user; + const { userToDelete } = req; + if (userToDelete === id || roles.includes('moderator' || 'admin')) { + next(); + } else { + res.status(403).json({ + error: 'You are not allowed to delete this comment' + }); + } +}; +export default deleteAllowed; diff --git a/src/middleware/hasNestedComments.js b/src/middleware/hasNestedComments.js new file mode 100644 index 0000000..c244a21 --- /dev/null +++ b/src/middleware/hasNestedComments.js @@ -0,0 +1,24 @@ +import db from '../sequelize/models'; + +const { Comment } = db; +const hasNested = async (req, res, next) => { + const { commentId } = req.params; + const foundComment = await Comment.findByPk(commentId); + if (!foundComment) { + return res.status(404).json({ + error: 'No Comment found!' + }); + } + const nestedComments = await Comment.findAll({ + where: { + commentId + } + }); + + const { userId } = foundComment; + req.nestedComments = nestedComments; + req.userToDelete = userId; + next(); +}; + +export default hasNested; diff --git a/src/middleware/isVerified.js b/src/middleware/isVerified.js new file mode 100644 index 0000000..972a94a --- /dev/null +++ b/src/middleware/isVerified.js @@ -0,0 +1,22 @@ + +import db from '../sequelize/models'; + +const { User } = db; + +const isVerified = async (req, res, next) => { + const { id } = req.user; + const currentUser = await User.findOne({ + where: { + id + } + }); + if (!currentUser.verified) { + return res.status(403).json({ + error: 'Please Verify your account, first!' + }); + } + + next(); +}; + +export default isVerified; diff --git a/src/middleware/profile/isReadyToUpdate.js b/src/middleware/profile/isReadyToUpdate.js new file mode 100644 index 0000000..9bc7a59 --- /dev/null +++ b/src/middleware/profile/isReadyToUpdate.js @@ -0,0 +1,11 @@ + +const readyToUpdate = (req, res, next) => { + const { file, body } = req; + if (!file && Object.keys(body) < 1) { + return res.status(400).send({ message: 'Cannot update empty object' }); + } + + next(); +}; + +export default readyToUpdate; diff --git a/src/middleware/profile/profileExists.js b/src/middleware/profile/profileExists.js new file mode 100644 index 0000000..87f9818 --- /dev/null +++ b/src/middleware/profile/profileExists.js @@ -0,0 +1,17 @@ +import db from '../../sequelize/models'; + +const { User } = db; + +const userExists = async (req, res, next) => { + const { id } = req.params; + const user = await User.findByPk(id); + if (!user) { + return res.status(404).json({ + error: 'User you want to update is not available' + }); + } + + next(); +}; + +export default userExists; diff --git a/src/middleware/profile/uploadImage.js b/src/middleware/profile/uploadImage.js new file mode 100644 index 0000000..c3e7164 --- /dev/null +++ b/src/middleware/profile/uploadImage.js @@ -0,0 +1,14 @@ +import workers from '../../workers'; + +const { uploadImageWorker } = workers; +const uploadImage = async (req, res, next) => { + const { file, body, params } = req; + if (file && Object.keys(body) < 1) { + uploadImageWorker(file, params.id, null); + return res.status(200).json({ status: 200, message: 'Your image will be updated shortly' }); + } + + next(); +}; + +export default uploadImage; diff --git a/src/middleware/subscriptions/optIn.js b/src/middleware/subscriptions/optIn.js new file mode 100644 index 0000000..592e958 --- /dev/null +++ b/src/middleware/subscriptions/optIn.js @@ -0,0 +1,38 @@ +import isOptedIn from '../../helpers/subscriptions/isOptedIn'; + +/** + * @class + */ +class OptedIn { + /** + * @param {Object} req - Request object + * @param {Object} res - Response object + * @param {Function} next - Next function + * @returns {Object}- Response + */ + static async optedInEmail(req, res, next) { + const { id } = req.user; + if (isOptedIn(id, 'email')) { + return res.status(400).json({ + error: 'You are already opted-in' + }); + } + + next(); + } + + /** + * @param {Object} req - Request object + * @param {Object} res - Response object + * @param {Function} next - Next function + * @returns {Object} - Response + */ + static async optedInApp(req, res, next) { + const { id } = req.user; + return isOptedIn(id, 'inapp') ? res.status(400).json({ + error: 'You are already opted in' + }) : next(); + } +} + +export default OptedIn; diff --git a/src/middleware/subscriptions/optOut.js b/src/middleware/subscriptions/optOut.js new file mode 100644 index 0000000..7bfef9e --- /dev/null +++ b/src/middleware/subscriptions/optOut.js @@ -0,0 +1,40 @@ +import isOptedIn from '../../helpers/subscriptions/isOptedIn'; + +/** + * @class + */ +class OptedOut { + /** + * @param {Object} req - Request object + * @param {Object} res - Response object + * @param {Function} next - Next function + * @returns {Object}- Response + */ + static async optedOutEmail(req, res, next) { + const { id } = req.user; + const isTrue = await isOptedIn(id, 'email'); + if (!isTrue) { + return res.status(400).json({ + error: 'You are already opted-out' + }); + } + + next(); + } + + /** + * @param {Object} req - Request object + * @param {Object} res - Response object + * @param {Function} next - Next function + * @returns {Object} - Response + */ + static async optedOutApp(req, res, next) { + const { id } = req.user; + const isTrue = await isOptedIn(id, 'inapp'); + return !isTrue ? res.status(400).json({ + error: 'You are already opted out' + }) : next(); + } +} + +export default OptedOut; diff --git a/src/middleware/validUser.js b/src/middleware/validUser.js index 5be7a99..8bac39b 100644 --- a/src/middleware/validUser.js +++ b/src/middleware/validUser.js @@ -1,47 +1,32 @@ import models from '../sequelize/models'; +import findByUsername from '../helpers/user/findUserByUsername'; +import findByEmail from '../helpers/user/findUserByEmail'; const validUser = { async emailExists(req, res, next) { const { email } = req.body; - await models.User.findAll({ - where: { - email - }, - }).then((data) => { - if (data.length > 0) { - return res.status(400).json({ - status: 400, - message: 'This email is already in use', - }); - } - next(); - }); + const user = await findByEmail(email); + return user ? res.status(400).json({ + error: 'This email is already in use' + }) : next(); }, async usernameExists(req, res, next) { const { username } = req.body; - await models.User.findAll({ - where: { - username - }, - }).then((data) => { - if (data.length > 0) { - return res.status(400).json({ - status: 400, - message: 'This username is not available, Please choose another one!', - }); - } - next(); - }); + const user = await findByUsername(username); + if (user) { + return res.status(400).json({ + error: 'This username is not available, try another one' + }); + } + next(); }, async userNameExist(req, res, next) { const { username } = req.params; - // console.log(username); const user = await models.User.findAll({ where: { username } }); - // console.log(user); if (!user.length) { return res.status(404).json({ error: 'username does not exist' diff --git a/test/mock/articles.js b/test/mock/articles.js index f49e80d..f5b10ef 100644 --- a/test/mock/articles.js +++ b/test/mock/articles.js @@ -20,6 +20,8 @@ export default { slug: 'this-is-andela-jfsf', description: 'Hello @people, This is Andela', body: 'bk ksjkjfvvsohgsonhsfogkfghfgkfnokgfkhsgknfgkfsghfsigfgifgfkfgsfgfgkfgfgkfgukhfgkhgkg', - tagList: 'this,is,andela' + tagList: 'this,is,andela', + readtime: 'Less than a minute', + authorId: 1 } };