Skip to content

Commit

Permalink
Merge branch 'develop' into ft-rating-pagination-167313406
Browse files Browse the repository at this point in the history
  • Loading branch information
Mnickii committed Aug 30, 2019
2 parents b7c1fce + 701e548 commit d2a5058
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 4 deletions.
8 changes: 7 additions & 1 deletion src/controllers/articles.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import NotificationServices from '../services/notification.service';
import cloudinaryHelper from '../helpers/cloudinaryHelper';
import OpenUrlHelper from '../helpers/share.article.helper';
import Util from '../helpers/util';

import statsService from '../services/db.service';

const { notifyViaEmailAndPush } = NotificationServices;
const util = new Util();
Expand Down Expand Up @@ -123,6 +123,12 @@ class Articles {
const article = _.pick(findArticle, ['slug', 'title', 'description', 'body', 'taglist', 'favorited', 'favoritedcount', 'flagged', 'images', 'views']);
const readTime = Helper.calculateReadTime(article.body);
article.readtime = readTime;
if (req.auth) {
const { description } = article;
const readerId = req.auth.id;
const item = 'article';
await statsService.createStat({ description, item, readerId }, 'Stats');
}
return res.status(200).json({
status: 200,
message: 'Article successfully retrieved',
Expand Down
4 changes: 4 additions & 0 deletions src/controllers/comments.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import models from '../models';
import Helper from '../helpers/helper';
import NotificationServices from '../services/notification.service';
import Util from '../helpers/util';
import StatsService from '../services/db.service';


const util = new Util();
Expand Down Expand Up @@ -109,6 +110,9 @@ class Comments {
await util.setError(200, 'No comments found');
return util.send(res);
}
const readerId = req.auth.id;
const item = 'comment';
await StatsService.createStat({ readerId, item, slug: 'all comments' }, 'Stats');
await util.setSuccess(200, 'All comments successfully retrieved', comments);
return util.send(res);
}
Expand Down
15 changes: 15 additions & 0 deletions src/controllers/stats.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable require-jsdoc */
import StatsService from '../services/db.service';
import { success } from '../helpers/responses';

const { getStat } = StatsService;

class statsController {
static async getStats(req, res) {
const readerId = req.auth.id;
const stats = await getStat({ readerId }, 'Stats');
return success('your reading stats', stats).send(res);
}
}

export default statsController;
18 changes: 18 additions & 0 deletions src/helpers/responses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Util from './util';

const util = new Util();

export const notFound = (msg) => {
util.setError(404, `${msg} not found`);
return util;
};

export const error = (msg, code = 400) => {
util.setError(code, msg);
return util;
};

export const success = (msg, data = null) => {
util.setSuccess(200, msg, data);
return util;
};
37 changes: 37 additions & 0 deletions src/middlewares/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable require-jsdoc */

import jwt from 'jsonwebtoken';

import StatsService from '../services/db.service';
import { notFound, error } from '../helpers/responses';

const { getStat } = StatsService;

class statsWare {
static async checkStats(req, res, next) {
if (!req.auth) {
return error('you are not logged in').send(res);
}
const readerId = req.auth.id;
const stats = await getStat({ readerId }, 'Stats');
if (!stats.length) {
return notFound('reading stats').send(res);
}
next();
}

static async saveStat(req, res, next) {
try {
let token = req.headers['x-access-token'] || req.headers.authorization;
token = token.slice(7, token.length);
jwt.verify(token, process.env.SECRET_KEY, (err, decode) => {
req.auth = decode;
next();
});
} catch (err) {
next();
}
}
}

export default statsWare;
33 changes: 33 additions & 0 deletions src/migrations/20190828152317-create-stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Stats', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
slug: {
type: Sequelize.STRING
},
item: {
type: Sequelize.STRING
},
readerId: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Stats');
}
};
12 changes: 12 additions & 0 deletions src/models/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const Stats = sequelize.define('Stats', {
slug: DataTypes.STRING,
item: DataTypes.STRING,
readerId: DataTypes.INTEGER
}, {});
Stats.associate = function (models) {
// associations can be defined here
}
return Stats;
};
7 changes: 5 additions & 2 deletions src/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ module.exports = (sequelize, DataTypes) => {
}, {});

user.associate = ({
Follow, Article, Highlight

Follow, Article, Highlight, Stats
}) => {
user.hasMany(Follow, {
foreignKey: 'followerId',
Expand All @@ -34,6 +33,10 @@ module.exports = (sequelize, DataTypes) => {
targetKey: 'id',
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
});
user.hasMany(Stats, {
as: 'reader',
foreignKey: 'readerId'
})
user.belongsToMany(Article, {
through: 'BookMarks',
Expand Down
3 changes: 2 additions & 1 deletion src/routes/api/article/article.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import tagController from '../../../controllers/tag';
import TagWare from '../../../middlewares/tag.middleware';
import highlight from '../../../controllers/highlight.controller';
import share from '../../../middlewares/shareHighlight.middleware';
import StatsWare from '../../../middlewares/stats';

const router = express.Router();
const {
Expand All @@ -24,7 +25,7 @@ const {
router.post('/:articleId/favorite', [auth, confirmEmailAuth, validateId], FavoritesController.createOrRemoveFavorite);
router.post('/', [auth, confirmEmailAuth], imageUpload.array('images', 10), validate(schema.articleSchema), articleController.createArticles);
router.get('/', checkQuery, articleController.getAllArticles);
router.get('/:slug', articleController.getOneArticle);
router.get('/:slug', StatsWare.saveStat, articleController.getOneArticle);
router.delete('/:slug', [auth, confirmEmailAuth], articleController.deleteArticle);
router.patch('/:slug', [auth, confirmEmailAuth], imageUpload.array('images', 10), articleController.UpdateArticle);
router.post('/:slug/share/:channel', [auth, confirmEmailAuth], articleController.shareArticle);
Expand Down
8 changes: 8 additions & 0 deletions src/routes/api/user/user.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import followController from '../../../controllers/follow.controller';
import resetPasswordValidation from '../../../middlewares/validators/resetpassword.validation';
import BookMarkController from '../../../controllers/bookmarks.controller';
import BookMarkWare from '../../../middlewares/bookmarks';
import statsController from '../../../controllers/stats.controller';
import statsWare from '../../../middlewares/stats';

const {
checkBookmark, checkUserBookMarks, checkDuplicate, createCopy,
Expand All @@ -23,6 +25,9 @@ const {
unCollect
} = BookMarkController;

const { getStats } = statsController;
const { saveStat, checkStats } = statsWare;

// bookmarks routes
router.post('/bookmarks/copy', createCopy, copyBookmark);
router.patch('/bookmarks/update', createCopy, editBookMark);
Expand All @@ -40,6 +45,9 @@ router.delete('/bookmarks/:name', [validateToken, confirmEmaiAuth], checkBookmar
router.delete('/bookmarks', [validateToken, confirmEmaiAuth], checkUserBookMarks, deleteUserBookMarks);


// stats route
router.get('/stats', saveStat, checkStats, getStats);

router.get('/verify', verifyEmail);
router.get('/allusers', [validateToken, admin, confirmEmaiAuth], UserController.getAllUsers);
router.post('/signup', validateUser, UserController.signup);
Expand Down
18 changes: 18 additions & 0 deletions src/services/db.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable require-jsdoc */
import models from '../models';

const { Stats, Article } = models;
const Models = { Stats, Article };
const conditon = where => ({ where });

class StatsService {
static async createStat(where, model) {
return Models[model].create(where);
}

static async getStat(where, model) {
return Models[model].findAll(conditon(where));
}
}

export default StatsService;
67 changes: 67 additions & 0 deletions test/auth-stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { chai, expect, server } from './test-setup';

let token;
describe('Test user stats', () => {
before((done) => {
chai.request(server)
.post('/api/v1/users/login')
.send({ email: 'admin@gmail.com', password: 'ASqw12345' })
.end((error, res) => {
token = `Bearer ${res.body.token}`;
done();
});
});
describe('test getting stats', () => {
it('should return correct response if none', (done) => {
chai.request(server)
.get('/api/v1/users/stats')
.set('Authorization', token)
.end((err, res) => {
expect(res.status).to.be.equal(404);
expect(res.body.message).to.include('not found');
done();
});
});
it('should return correct response if user not logged in', (done) => {
chai.request(server)
.get('/api/v1/users/stats')
.end((err, res) => {
expect(res.status).to.be.equal(400);
expect(res.body.message).to.include('not logged in');
done();
});
});
});
describe('test stats gathering', () => {
it('should not populate stats if user unauthenticated', (done) => {
chai.request(server)
.get('/api/v1/articles/fakeslug2')
.end(() => {
chai.request(server)
.get('/api/v1/users/stats')
.set('Authorization', token)
.end((err, res) => {
expect(res.status).to.be.equal(404);
expect(res.body.message).to.include('not found');
done();
});
});
});
it('should log every read', (done) => {
chai.request(server)
.get('/api/v1/articles/fakeslug2')
.set('Authorization', token)
.end(() => {
chai.request(server)
.get('/api/v1/users/stats')
.set('Authorization', token)
.end((err, res) => {
expect(res.status).to.be.equal(200);
expect(res.body).to.have.deep.property('message', 'your reading stats');
expect(res.body.data).to.be.a('Array');
done();
});
});
});
});
});

0 comments on commit d2a5058

Please sign in to comment.