Skip to content

Commit

Permalink
feature(views and popular articles): get article views and popular ar…
Browse files Browse the repository at this point in the history
…ticles [Finishes #169124758]
  • Loading branch information
habinezadalvan committed Oct 14, 2019
1 parent bde4b1b commit ae116bd
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: node_js

node_js:
- 'stable'
- '10'

cache:
directories:
Expand Down
6 changes: 0 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"dotenv": "^6.2.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-ip": "^1.0.3",
"express-session": "^1.15.6",
"http": "0.0.0",
"joi": "^14.3.1",
Expand Down Expand Up @@ -64,11 +63,6 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/node": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/register": "^7.5.5",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"coveralls": "^3.0.5",
Expand Down
2 changes: 0 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import winston from 'winston';
import express from 'express';
import expressip from 'express-ip';
import bodyParser from 'body-parser';
import logging from './helpers/logging';
import routes from './routes/index';
import './config/cloudinary.config';
import { mock } from './middlewares/validators/socialLogin-mock';

const app = express();
app.use(expressip().getIpInfoMiddleware);
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));

Expand Down
50 changes: 28 additions & 22 deletions src/controllers/articles.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const { notifyViaEmailAndPush } = NotificationServices;
const util = new Util();

const db = models.Article;
const viewDb = models.View;

/**
*
Expand Down Expand Up @@ -87,6 +86,7 @@ class Articles {
if (!articles) {
return res.status(200).json({ status: 200, message: 'There is no article.' });
}

const allArticles = _.map(
articles,
_.partialRight(_.pick, [
Expand All @@ -103,7 +103,16 @@ class Articles {
])
);

allArticles.map((article) => {
const popularArticles = allArticles.slice(0);
popularArticles.sort((a, b) => b.views - a.views);
const mostPopular = popularArticles.slice(0, 9);

if (req.query.popular) {
util.setSuccess(200, 'The most popular articles on authors haven', mostPopular);
return util.send(res);
}

allArticles.map(async (article) => {
const readTime = Helper.calculateReadTime(article.body);
article.readtime = readTime;
return true;
Expand All @@ -130,6 +139,7 @@ class Articles {
const findArticle = await db.findOne({
where: { slug: req.params.slug }
});

if (!findArticle) {
return res.status(200).json({
status: 200,
Expand All @@ -149,34 +159,31 @@ class Articles {
'images',
'views'
]);
const readTime = Helper.calculateReadTime(article.body);
article.readtime = readTime;
// find ip address
const ipAddress = req.header('x-forwarded-for') || req.connection.remoteAddress;

// check if the article was viewed
const findViewsOfArticle = await viewDb.findOne({ where: { articleId: findArticle.id } });

let viewObject = {
articleId: findArticle.id,
userId: req.auth ? req.auth.id : null,
IP_address: ipAddress,
userType: req.auth ? 'loggedInUser' : 'guest',
slug: findArticle.slug,
title: findArticle.title,
description: findArticle.description,
body: findArticle.body,
flagged: findArticle.flagged,
favorited: findArticle.favorited,
favoritedcount: findArticle.favoritedcount,
images: findArticle.images,
views: 1
};
// update or create article views
if (findViewsOfArticle) {
viewObject = { ...viewObject, views: findViewsOfArticle.views + 1 };
await viewDb.update(viewObject, {
if (findArticle) {
viewObject = { ...viewObject, views: findArticle.views + 1 };
await db.update(viewObject, {
where: {
articleId: findArticle.id
id: findArticle.id
},
returing: true
});
} else {
await viewDb.create(viewObject);
await db.create(viewObject);
}

const readTime = Helper.calculateReadTime(article.body);
article.readtime = readTime;
if (req.auth) {
const { description } = article;
const readerId = req.auth.id;
Expand All @@ -186,8 +193,7 @@ class Articles {
return res.status(200).json({
status: 200,
message: 'Article successfully retrieved',
data: article,
views: findViewsOfArticle.views
data: article
});
}

Expand Down
44 changes: 44 additions & 0 deletions src/helpers/viewArticle.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import models from '../models';
import Util from './util';

const util = new Util();
const db = models.Article;
const viewDb = models.View;

const viewArticleHelper = async (req, res, slug) => {
const findArticle = await db.findOne({ where: { slug } });
if (!findArticle) {
util.setError(404, 'That article does not exist');
util.send(res);
}
// find ip address
const ipAddress = req.header('x-forwarded-for') || req.connection.remoteAddress;

// check if the article was viewed
const findViewsOfArticle = await viewDb.findOne({ where: { articleId: findArticle.id } });

let viewObject = {
articleId: findArticle.id,
userId: req.auth ? req.auth.id : null,
IP_address: ipAddress,
userType: req.auth ? 'loggedInUser' : 'guest',
views: 1
};
// update or create article views
if (findViewsOfArticle) {
viewObject = { ...viewObject, views: findViewsOfArticle.views + 1 };
await viewDb.update(viewObject, {
where: {
articleId: findArticle.id
},
returing: true
});
} else {
await viewDb.create(viewObject);
}
return {
views: findViewsOfArticle.views
};
};

export default viewArticleHelper;
2 changes: 1 addition & 1 deletion src/middlewares/query.check.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const searchArticleQuerybuilder = (searchQueries) => {

export const checkQuery = (req, res, next) => {
let {
limit, page, ...searchQueries
limit, page, popular, ...searchQueries
} = req.query;
const validQueries = ['author', 'keyword', 'tag', 'title'];
let isValidRequest = true;
Expand Down
1 change: 1 addition & 0 deletions src/migrations/20190813125249-create-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module.exports = {
},
views: {
type: Sequelize.INTEGER,
defaultValue: 0,
allowNull: true
},
createdAt: {
Expand Down
24 changes: 12 additions & 12 deletions src/routes/api/article/article.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ router.patch('/:articleId/:name', [auth, confirmEmailAuth], checkArticle, checkT
router.delete('/:articleId/:name', [auth, confirmEmailAuth], checkArticle, checkTagName, deleteArticleTag);


// Highlight
router.post('/:slug/highlight', [auth], highlight.bodyHighlightedText);
router.delete('/highlight/:id', auth, highlight.deleteHighlightComment);
router.get('/:articleId/highlight', auth, highlight.getHighlights);
router.get('/:id/highlight/share/:channel', [auth, share], highlight.shareHightlight);

// tags

router.post('/:articleId/tags', [auth, confirmEmailAuth], checkArticle, tagLimit, tagLength, createArticleTag);
router.get('/:articleId/tags', checkArticle, getArticleTags);
router.patch('/:articleId/:name', [auth, confirmEmailAuth], checkArticle, checkTagName, editArticleTag);
router.delete('/:articleId/:name', [auth, confirmEmailAuth], checkArticle, checkTagName, deleteArticleTag);
// // Highlight
// router.post('/:slug/highlight', [auth], highlight.bodyHighlightedText);
// router.delete('/highlight/:id', auth, highlight.deleteHighlightComment);
// router.get('/:articleId/highlight', auth, highlight.getHighlights);
// router.get('/:id/highlight/share/:channel', [auth, share], highlight.shareHightlight);

// // tags

// router.post('/:articleId/tags', [auth, confirmEmailAuth], checkArticle, tagLimit, tagLength, createArticleTag);
// router.get('/:articleId/tags', checkArticle, getArticleTags);
// router.patch('/:articleId/:name', [auth, confirmEmailAuth], checkArticle, checkTagName, editArticleTag);
// router.delete('/:articleId/:name', [auth, confirmEmailAuth], checkArticle, checkTagName, deleteArticleTag);

export default router;
4 changes: 3 additions & 1 deletion src/services/article.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ class articleService {
* @static
* @param {*} offset
* @param {*} limit
* @param {*} popular
* @param {*} searchQueries
* @returns {object} data
* @memberof articleService
*/
static async getAllArticles(offset, limit, searchQueries) {
static async getAllArticles(offset, limit, popular, searchQueries) {
try {
return await db.findAll({
where: searchQueries,
order: [
['createdAt', 'DESC']
],
popular,
offset,
limit,
});
Expand Down

0 comments on commit ae116bd

Please sign in to comment.