Skip to content

Commit

Permalink
Merge branch 'develop' into ft/167190463-highligt-article-text-comment
Browse files Browse the repository at this point in the history
  • Loading branch information
cvjude committed Aug 8, 2019
2 parents 56276f9 + 0b55ec3 commit a712898
Show file tree
Hide file tree
Showing 64 changed files with 2,894 additions and 225 deletions.
5 changes: 4 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ SERVER_MAIL = noreply@authorsHaven.com
CLOUD_NAME = Your cloudinary cloud name
CLOUD_API_KEY = Your cloudinary api key
CLOUD_API_SECRET = Your cloudinary api secret

PUSHER_APP_ID=YOUR_APP_ID
PUSHER_APP_KEY=YOUR_APP_KEY
PUSHER_APP_SECRET=YOUR_APP_SECRET
PUSHER_APP_CLUSTER=YOUR_APP_CLUSTER
1 change: 1 addition & 0 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const path = require('path');
require('@babel/register');

module.exports = {
'config': path.resolve('server/db/config', 'config.js'),
'models-path': path.resolve('server/db/', 'models'),
Expand Down
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"start": "node build/index.js",
"dev": "export NODE_ENV=production && DEBUG=dev && nodemon --exec babel-node server/index.js",
"dev": "export DEBUG=dev && nodemon --exec babel-node server/index.js",
"debug": "export DEBUG=dev && nodemon --exec babel-node server/index.js --inspect",
"clean": "rm -rf build && mkdir build && npm run copy-docs",
"build": "npm run clean && npm run migrate && babel -d ./build ./server",
Expand All @@ -16,6 +16,7 @@
"seed:undo": "node_modules/.bin/sequelize db:seed:undo:all",
"generate:model": "node_modules/.bin/sequelize model:generate",
"generate:migration": "node_modules/.bin/sequelize migration:generate",
"generate:seed": "node_modules/.bin/sequelize seed:generate",
"migrate": "node_modules/.bin/sequelize db:migrate",
"migrate:undo": "node_modules/.bin/sequelize db:migrate:undo:all",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
Expand Down Expand Up @@ -44,6 +45,7 @@
"express": "^4.16.3",
"faker": "^4.1.0",
"jsonwebtoken": "^8.3.0",
"lodash": "^4.17.15",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"node-cron": "^2.0.3",
Expand All @@ -53,6 +55,7 @@
"passport-google-oauth2": "^0.2.0",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"pusher": "^2.2.2",
"sequelize": "^5.10.2",
"sequelize-cli": "^5.5.0",
"sequelize-slugify": "^0.7.0",
Expand All @@ -69,6 +72,7 @@
"@babel/register": "^7.5.5",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"chai-integer": "^0.1.0",
"coveralls": "^3.0.5",
"eslint": "^6.1.0",
"eslint-config-airbnb-base": "^13.2.0",
Expand All @@ -93,4 +97,4 @@
"git add"
]
}
}
}
139 changes: 122 additions & 17 deletions server/controllers/articleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import sequelize from 'sequelize';
import models from '../db/models';
import helpers from '../helpers';
import Paginate from '../helpers/paginate';
import Notification from '../helpers/notifications';

const { Op } = sequelize;
const { paginateArticles } = Paginate;
const {
errorStat, successStat,
querySearch, filterSearch, errorStat, successStat
} = helpers;

const { Op } = sequelize;
const { paginateArticles } = Paginate;
const parseBool = (string) => {
if (string === 'true') return true;
return false;
};
/**
* @Module ArticleController
* @description Controlls all activities related to Articles
Expand All @@ -30,16 +35,36 @@ class ArticleController {
articleBody,
image
} = req.body.article;
const readTime = Math.floor(articleBody.split(' ').length / 200);
const article = await models.Article.create({
title,
description,
tagList,
articleBody,
uuid: uuid.v1().split('-')[0],
authorId: req.user.id,
image
image,
readTime
});
article.tagList = [...article.dataValues.tagList.split(' ')];
const author = await article.getAuthor({
attributes: ['username'],
include: [{
model: models.User,
through: {
attributes: []
},
as: 'followers',
attributes: ['id', 'username', 'email', 'newPostEmailSub'],
}],
});

const payload = {
resourceType: 'article',
resourceId: article.slug,
message: `${author.username} just posted a new article`,
};
Notification.notify(author.followers, payload);
return successStat(res, 201, 'articles', article);
}

Expand All @@ -51,11 +76,20 @@ class ArticleController {
* @memberof ArticleController
*/
static async getAllArticles(req, res) {
const { searchQuery } = req.query;
const queryFilters = req.body;
let articles;
const { page, limit } = req.query;
if (!page && !limit) {
const articles = await models.Article.findAll({
include: [{ model: models.User, as: 'author', attributes: ['firstname', 'lastname', 'username', 'image', 'email'] }]
});
if (!searchQuery) {
articles = await models.Article.findAll({
include: [{ model: models.User, as: 'author', attributes: ['firstname', 'lastname', 'username', 'image', 'email'] }]
});
} else if (searchQuery && Object.keys(queryFilters)[0] !== 'undefined') {
articles = await filterSearch(searchQuery, queryFilters);
} else {
articles = await querySearch(searchQuery);
}
return successStat(res, 200, 'articles', articles);
}
paginateArticles(req, res);
Expand All @@ -80,12 +114,37 @@ class ArticleController {
attributes: ['firstname', 'lastname', 'username', 'image', 'email']
}]
});

if (!article) {
return errorStat(res, 404, 'Article not found');
}
let comments = await article.getComment();
comments = Object.values(comments).map(comment => comment.dataValues);
return successStat(res, 200, 'article', { article, comments, noOfComments: comments.length });
const likes = await article.countLikes({
where: { likes: true }
});

const dislikes = await article.countLikes({
where: { likes: false }
});

const comments = await models.Comment.findAll({
where: { articleId: article.id },
attributes: {
include: [
[sequelize.fn('SUM', sequelize.literal('CASE likes WHEN false THEN 1 ELSE 0 END')), 'dislikes'],
[sequelize.fn('SUM', sequelize.literal('CASE likes WHEN true THEN 1 ELSE 0 END')), 'likes']
],
},
include: [
{
model: models.Likes,
attributes: [],
}
],
group: ['Comment.id']
});
return successStat(res, 200, 'article', {
article, likes, dislikes, comments, noOfComments: comments.length
});
}

/**
Expand Down Expand Up @@ -154,13 +213,13 @@ class ArticleController {
}

/**
* @static
* @description Allows a user to highlight a Article text and add an optional comment
* @param {*} req Request object
* @param {*} res Response object
* @returns {Object} Object containing the user comment, author, and timestaps
* @memberof CommentController
*/
* @static
* @description Allows a user to highlight a Article text and add an optional comment
* @param {*} req Request object
* @param {*} res Response object
* @returns {Object} Object containing the user comment, author, and timestaps
* @memberof CommentController
*/
static async highlightText(req, res) {
const { body: { highlight: { comment, id, slug } }, user } = req;
const post = await models.Article.findOne({
Expand Down Expand Up @@ -195,6 +254,52 @@ class ArticleController {
});
return successStat(res, 201, 'comment', commentResponse);
}

/**
* @static
* @description Like or Dislike an Article
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {String} returns a message indicating that the article was liked or disliked
*/
static async likeOrDislikeArticle(req, res) {
const { user } = req;
const { liked, resourceId, type } = req.body.liked;
const likes = parseBool(liked);
const modelType = type.charAt(0).toUpperCase() + type.slice(1);

const tableType = await models[modelType].findByPk(resourceId);
if (!tableType) return errorStat(res, 404, `${tableType} not found`);

const likedArticle = await models.Likes.findOne({
where: { resourceId, userId: user.id }
});

if (likedArticle && likedArticle.likes === likes) {
await models.Likes.destroy({ where: { resourceId, userId: user.id } });
} else if (likedArticle && likedArticle.likes !== likes) {
await models.Likes.update({ likes }, { where: { resourceId, userId: user.id } });
} else {
await user.createLike({
likes,
resourceId,
type
});
}

const totalLikes = await tableType.countLikes({
where: { likes: true }
});

const totalDislikes = await tableType.countLikes({
where: { likes: false }
});

return successStat(res, 200, `${type} Likes`, {
likes: totalLikes,
dislikes: totalDislikes
});
}
}

export default ArticleController;
84 changes: 84 additions & 0 deletions server/controllers/bookmarkController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import models from '../db/models';
import utils from '../helpers/Utilities';

/**
* @Module BookmarkController
* @description Controlls all activities related to bookmarks
*/
class BookmarkController {
/**
* @description Bookmarks an article for reading later
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} Success Message
*/
static async bookmarkArticle(req, res) {
const userId = req.user.id;
const { articleId } = req.params;
const article = await models.Article.findByPk(articleId);
if (!article) return utils.errorStat(res, 404, 'Article not found');
const bookmarked = await models.Bookmark.findOne({ where: { articleId } });
if (bookmarked) return utils.errorStat(res, 409, 'Aticle already bookmarked');
const addBookmark = await models.Bookmark.findOrCreate({
where: { articleId, userId },
defaults: { articleId, userId }
});
return utils.successStat(res, 201, 'Bookmarked_article', addBookmark);
}

/**
* @description Remove Article from Bookmark
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} Success Message
*/
static async unbookmarkArticle(req, res) {
const userId = req.user.id;
const { articleId } = req.params;
const article = await models.Article.findByPk(articleId);
if (!article) return utils.errorStat(res, 404, 'Article not found');
const bookmarked = await models.Bookmark.findOne({ where: { articleId } });
if (!bookmarked) return utils.errorStat(res, 404, 'Article not found among bookmarks');
await models.Bookmark.destroy({
where: { articleId, userId },
defaults: { articleId, userId }
});
return utils.successStat(res, 200, 'message', 'Article removed from bookmarks');
}

/**
* @description Get all bookmarked Articles
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} Bookmarked Articles
*/
static async getBookmarkedArticles(req, res) {
const userId = req.user.id;
const bookmarks = await models.Bookmark.findAll({
where: { userId }
});
if (bookmarks.length < 1) return utils.errorStat(res, 404, 'You have no bookmarked Article');
return utils.successStat(res, 200, 'Bookmarks', bookmarks);
}

/**
* @description Get all bookmarked Articles
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} Bookmarked Articles
*/
static async unbookmarkAllArticles(req, res) {
const userId = req.user.id;
const bookmarks = await models.Bookmark.findAll({
where: { userId }
});
if (bookmarks.length < 1) return utils.errorStat(res, 404, 'You have no bookmarked articles to delete');
await models.Bookmark.destroy({
where: { userId },
defaults: { userId }
});
return utils.successStat(res, 200, 'message', 'All bookmaked articles removed');
}
}

export default BookmarkController;
Loading

0 comments on commit a712898

Please sign in to comment.