Skip to content

Commit

Permalink
Merge pull request #84 from andela/feature/164798034-user-can-see-the…
Browse files Browse the repository at this point in the history
…ir-reading-stats

#164798034 User can see their reading stats
  • Loading branch information
Rotimi Babalola committed Apr 11, 2019
2 parents 451c843 + 3007533 commit 274f826
Show file tree
Hide file tree
Showing 14 changed files with 329 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"joi": "^14.3.1",
"jsonwebtoken": "^8.5.1",
"lint-staged": "^8.1.5",
"moment": "^2.24.0",
"nodemailer": "^6.1.0",
"passport": "^0.4.0",
"passport-facebook": "^3.0.0",
Expand Down
3 changes: 3 additions & 0 deletions server/controllers/article.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import tagsHelpers from '../helpers/tags-helpers';
import serverError from '../helpers/server-error';
import serchDatabase from '../helpers/search-database';
import readingTime from '../helpers/reading-time';
import statistic from '../helpers/statistics-storer';
import notifications from '../helpers/notifications';

const { findArticle } = serchDatabase;
Expand Down Expand Up @@ -56,6 +57,8 @@ const getOneArticle = async (req, res) => {
});
}

await statistic.saveUserStatistic(req.user.userObj.id, article.id);

return res.status(200).json({
article,
});
Expand Down
2 changes: 2 additions & 0 deletions server/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ratingController from './rating.controller';
import ResetPasswordController from './reset-password.controllers';
import commentController from './comment.controller';
import getFollowersController from './get-followers.controllers';
import getDailyStatistics from './statistics.controller';
import adminController from './admin.controllers';
import bookmarkController from './bookmark.controller';

Expand All @@ -20,6 +21,7 @@ export default {
ResetPasswordController,
commentController,
getFollowersController,
getDailyStatistics,
adminController,
bookmarkController,
};
119 changes: 119 additions & 0 deletions server/controllers/statistics.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import moment from 'moment';
import models from '../models';
import serverError from '../helpers/server-error';

const { Statistic, Sequelize } = models;

const { Op } = Sequelize;

/**
* @description Get daily statistics of readers
* @param {*} req
* @param {*} res
* @returns {object} and objevt containing the message and the number of books read by the user
*/
const getDailyStatistic = async (req, res) => {
try {
const statisticBox = await Statistic.findAndCountAll({
where: {
user_id: req.params.userid,
createdAt: {
[Op.gte]: moment()
.subtract(12, 'hours')
.toDate(),
},
},
attributes: ['article_id', 'createdAt'],
});
if (statisticBox.count === 0) {
return res.status(200).json({
message: 'You did not read any article today.',
});
}

return res.status(200).json({
message: 'Your Reading Statistic Today',
statistic: `${statisticBox.count} article read today`,
});
} catch (err) {
return serverError;
}
};

/**
* @description Get weekly statistics of readers
* @param {*} req
* @param {*} res
* @returns {object} and objevt containing the message and the number of books read by the user
*/
const getWeeklyStatistic = async (req, res) => {
try {
const statisticBox = await Statistic.findAndCountAll({
where: {
user_id: req.params.userid,
createdAt: {
[Op.gte]: moment()
.subtract(6, 'days')
.toDate(),
},
},
attributes: ['article_id', 'createdAt'],
});

if (statisticBox.count === 0) {
return res.status(200).json({
message: 'You did not read any article this week.',
});
}

return res.status(200).json({
message: 'Your Reading Statistic This week',
statistic: `${statisticBox.count} article read this week`,
});
} catch (err) {
return serverError;
}
};

/**
* @description Get monthly statistics of readers
* @param {*} req
* @param {*} res
* @returns {object} and objevt containing the message and the number of books read by the user
*/
const getMonthlyStatistic = async (req, res) => {
try {
const statisticBox = await Statistic.findAndCountAll({
where: {
user_id: req.params.userid,
createdAt: {
[Op.gte]: moment()
.subtract(30, 'days')
.toDate(),
},
},
attributes: ['article_id', 'createdAt'],
});

if (statisticBox.count === 0) {
return res.status(200).json({
message: 'You did not read any article this month.',
});
}

return res.status(200).json({
message: 'Your Reading Statistic This week',
statistic: `${statisticBox.count} article read this month`,
});
} catch (err) {
return serverError;
}
};

const controller = {
getDailyStatistic,
getWeeklyStatistic,
getMonthlyStatistic,
};

export default controller;
26 changes: 26 additions & 0 deletions server/helpers/statistics-storer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import models from '../models';
import serverError from './server-error';

const { Statistic } = models;

/**
*
* @param {*} userId
* @param {*} articleId
* @returns {*} userId, articleId in .
*/
const saveUserStatistic = async (userId, articleId) => {
try {
const storedStatistic = await Statistic.create({
user_id: userId,
article_id: articleId,
});
return storedStatistic;
} catch (err) {
return serverError();
}
};

const statistic = { saveUserStatistic };

export default statistic;
4 changes: 4 additions & 0 deletions server/middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import tokenValidator from './verify-token.middlewares';
import ratingMiddleware from './rating.middlewares';
import commentMiddleware from './comment.middlewares';
import adminMiddleware from './admin.middlewares';
import uuidMiddleware from './uuid.middleware';
import userIdMiddleware from './user-id-is-correct.middleware';

export default {
authValidator,
Expand All @@ -16,4 +18,6 @@ export default {
ratingMiddleware,
commentMiddleware,
adminMiddleware,
uuidMiddleware,
userIdMiddleware,
};
12 changes: 12 additions & 0 deletions server/middlewares/user-id-is-correct.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const checkIfUserIdIsCorrect = (req, res, next) => {
if (req.params.userid !== req.user.userObj.id) {
return res.status(401).json({
errors: {
body: ["Sorry, you cannot access another user's reading statistic"],
},
});
}
return next();
};

export default { checkIfUserIdIsCorrect };
14 changes: 14 additions & 0 deletions server/middlewares/uuid.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import validations from '../helpers/validations';

const checkUUID = (req, res, next) => {
if (!validations.verifyUUID(req.params.userid)) {
return res.status(400).json({
errors: {
body: ['userId is not valid'],
},
});
}
return next();
};

export default { checkUUID };
7 changes: 7 additions & 0 deletions server/migrations/20190409154219-create-statistic.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Statistics', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
unique: true,
},
user_id: {
type: Sequelize.UUID,
},
Expand Down
7 changes: 7 additions & 0 deletions server/models/statistic.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
module.exports = (sequelize, DataTypes) => {
const Statistic = sequelize.define('Statistic', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
unique: true,
},
user_id: {
type: DataTypes.UUID,
},
Expand Down
3 changes: 3 additions & 0 deletions server/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import commentRoute from './comment.routes';
import likeComment from './like-comment.routes';
import followRoutes from './follow.routes';
import getFollowersRoute from './get-followers.routes';
import statisticRoute from './statistic.routes';
import adminRoute from './admin.routes';
import bookmarkRoute from './bookmark.routes';

Expand Down Expand Up @@ -66,6 +67,8 @@ router.use(profileRoute);
router.use(likeRoute);
router.use(profileRoute);
router.use(getFollowersRoute);
router.use(getFollowersRoute);
router.use(statisticRoute);
router.use(adminRoute);
router.use(bookmarkRoute);

Expand Down
31 changes: 31 additions & 0 deletions server/routes/statistic.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import express from 'express';
import controllers from '../controllers/statistics.controller';
import middlewares from '../middlewares';

const { tokenValidator, uuidMiddleware, userIdMiddleware } = middlewares;

const statisticRoute = express.Router();

statisticRoute.get(
'/:userid/statistic/daily',
tokenValidator.verifyToken,
userIdMiddleware.checkIfUserIdIsCorrect,
uuidMiddleware.checkUUID,
controllers.getDailyStatistic
);
statisticRoute.get(
'/:userid/statistic/week',
tokenValidator.verifyToken,
uuidMiddleware.checkUUID,
userIdMiddleware.checkIfUserIdIsCorrect,
controllers.getWeeklyStatistic
);
statisticRoute.get(
'/:userid/statistic/month',
tokenValidator.verifyToken,
uuidMiddleware.checkUUID,
userIdMiddleware.checkIfUserIdIsCorrect,
controllers.getMonthlyStatistic
);

export default statisticRoute;
15 changes: 15 additions & 0 deletions tests/statistic-store.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import chai, { expect } from 'chai';
import chaiHttp from 'chai-http';
import statistic from '../server/helpers/statistics-storer';

chai.use(chaiHttp);

describe('Statistic storer function', () => {
it('should accept a userId and aticleId and store it to the statistic table', async () => {
const statisticStore = await statistic.saveUserStatistic(
'2242e4ff-366d-46cc-9384-40eadb3b2644',
'7139d3af-b8b4-44f6-a49f-9305791700f4'
);
expect(statisticStore).to.be.an('object');
});
});
Loading

0 comments on commit 274f826

Please sign in to comment.