Skip to content

Commit

Permalink
feat(Users should see their reading stats)- Create User read stats mo…
Browse files Browse the repository at this point in the history
…del- Create functionality for user to see amount of articles read- Write test for featurefinishes(162414288)
  • Loading branch information
Makwe-O committed Jan 29, 2019
1 parent 5c32adf commit 819c2fa
Show file tree
Hide file tree
Showing 16 changed files with 423 additions and 101 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"methods": "^1.1.2",
"mocha-lcov-reporter": "^1.3.0",
"morgan": "^1.9.1",
"node-boolify": "^1.0.5",
"passport": "^0.4.0",
"passport-facebook": "^2.1.1",
"passport-google-oauth20": "^1.0.0",
Expand Down
19 changes: 14 additions & 5 deletions server/controllers/ArticleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import response from '../helpers/response';
import SearchController from './SearchController';
import pagination from '../helpers/pagination';
import TimeToRead from '../helpers/TimeToRead';
import ReadingStatsContoller from './ReadingStatsController';

const { Article, Tag, User } = db;
const { createReadingStats } = ReadingStatsContoller;

/**
* @class ArticleController
Expand Down Expand Up @@ -92,9 +94,8 @@ class ArticleController {
model: User,
as: 'author',
attributes: ['userName', 'bio', 'img']
},

],
}
]
});
const totalArticles = articlesCount.count;

Expand All @@ -117,7 +118,6 @@ class ArticleController {
offset
});


if (articles.count > 0) {
const articleList = articles.rows.map((article) => {
article = article.toJSON();
Expand All @@ -126,7 +126,12 @@ class ArticleController {
return article;
});

const paginatedData = pagination(articleList.length, limit, currentPage, totalArticles);
const paginatedData = pagination(
articleList.length,
limit,
currentPage,
totalArticles
);
const data = {
articles: articleList,
paginatedData
Expand Down Expand Up @@ -185,6 +190,10 @@ class ArticleController {
article.createdAt = Util.formatDate(article.createdAt);
article.updatedAt = Util.formatDate(article.updatedAt);

if (req.user) {
await createReadingStats(req, res);
}

return response(
res,
200,
Expand Down
99 changes: 99 additions & 0 deletions server/controllers/ReadingStatsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import db from '../models';
import response from '../helpers/response';

const { Article, ReadingStats } = db;

/**
*
* @class ReadingStatsController
*/
class ReadingStatsController {
/**
*
* @description Method to get user reading stats
* @static
* @param {*} req
* @param {*} res
* @returns {object} Json response
* @memberof ReadingStatsController
*/
static async getReadingStats(req, res) {
try {
const { userId } = req.user;
const userStats = await ReadingStats.findAndCountAll({
where: {
userId
},
include: [
{
model: Article,
attributes: ['slug', 'title', 'content', 'banner']
}
]
});

if (userStats.count === 0) {
return response(
res,
404,
'failure',
"You currently don't have any reading stats"
);
}
const payload = {
readStats: userStats.rows,
readStatsCount: userStats.count
};
return response(res, 200, 'success', 'ReadingStats', null, payload);
} catch (error) {
return response(
res,
500,
'failure',
'Something went wrong on the server',
null,
null
);
}
}

/**
*
* @description Method to create user reading stats
* @static
* @param {*} req
* @param {*} res
* @returns {object} Json response
* @memberof ReadingStatsController
*/
static async createReadingStats(req, res) {
try {
const { userId } = req.user;
const articleSlug = req.params;
const theArticleSlug = JSON.stringify(articleSlug).split('"')[3];
const theArticle = await Article.findOne({
where: { slug: theArticleSlug }
});
const articleId = theArticle.dataValues.id;

ReadingStats.findOrCreate({
where: { userId, articleId },
attributes: ['id', 'articleId', 'userId']
}).spread((stats, created) => {
if (created) {
return stats;
}
});
} catch (error) {
return response(
res,
500,
'failure',
'Something went wrong on the server',
null,
null
);
}
}
}
export default ReadingStatsController;
33 changes: 32 additions & 1 deletion server/middlewares/AuthManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,45 @@ class AuthManager {
const decoded = await TokenManager.verify(token);

if (!token || !decoded || !decoded.userId) {
return response(res, 403, 'failure', 'User not Authorised!', null, null);
return response(
res,
403,
'failure',
'User not Authorised!',
null,
null
);
}
req.user = decoded;
return next();
} catch (error) {
return response(res, 403, 'failure', 'User not Authorised!', null, null);
}
}

/**
* @static
* @description checks login status of a request
* @param {*} req - request object
* @param {*} res response object
* @param {*} next - next callback
* @returns {*} calls the next middleware
* @memberof VerifyUser
*/
static async checkAuthStatus(req, res, next) {
try {
const { authorization } = req.headers;
const token = authorization.split(' ')[1];
const decoded = TokenManager.verify(token);
if (decoded) {
req.user = decoded;
return next();
}
} catch (error) {
req.isLoggedIn = false;
next();
}
}
}

export default AuthManager;
15 changes: 8 additions & 7 deletions server/middlewares/validations/ArticleValidation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable no-useless-escape */
/* eslint-disable no-unused-expressions */
/* eslint-disable prefer-const */

import { Boolify } from 'node-boolify';
import db from '../../models';
import Util from '../../helpers/Util';
import response from '../../helpers/response';
Expand Down Expand Up @@ -163,9 +165,9 @@ class ArticleValidation {
req.body.banner = req.body.banner
? Util.removeExtraWhitespace(req.body.banner) : article.banner;
req.body.isPublished = req.body.isPublished !== undefined
? Boolean(req.body.isPublished) : article.isPublished;
? Boolify(req.body.isPublished) : article.isPublished;
req.body.isReported = req.body.isReported !== undefined
? Boolean(req.body.isReported) : article.isReported;
? Boolify(req.body.isReported) : article.isReported;
return next();
}
return response(
Expand Down Expand Up @@ -198,7 +200,7 @@ class ArticleValidation {
return next();
}
if (!Number.isSafeInteger(parseInt(limit, 10))) {
return response(res, 400, 'failure', 'There was an issue with your query');
response(res, 400, 'failure', 'There was an issue with your query');
}
return next();
} catch (error) {
Expand All @@ -207,7 +209,7 @@ class ArticleValidation {
'server error',
{
message: 'Something went wrong on the server'
}
}, null
);
}
}
Expand All @@ -230,7 +232,7 @@ class ArticleValidation {
return next();
}
if (!Number.isSafeInteger(parseInt(page, 10))) {
return response(res, 400, 'failure', 'There was an issue with your query');
response(res, 400, 'failure', 'There was an issue with your query');
}
return next();
} catch (error) {
Expand All @@ -239,10 +241,9 @@ class ArticleValidation {
'server error',
{
message: 'Something went wrong on the server'
}
}, null
);
}
}
}

export default ArticleValidation;
37 changes: 37 additions & 0 deletions server/migrations/20190127133424-create-reading-stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export default {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize
.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
.then(() => {
return queryInterface.createTable('ReadingStats', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.literal('uuid_generate_v4()')
},
userId: {
type: Sequelize.UUID,
allowNull: false
},
articleId: {
type: Sequelize.UUID,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
});
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('ReadingStats');
}
};
12 changes: 11 additions & 1 deletion server/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ export default (sequelize, DataTypes) => {
);
Article.associate = (models) => {
const {
User, Tag, Comment, ArticleLikesDislike, Bookmark, Rating, Share
User,
Tag,
Comment,
ArticleLikesDislike,
Bookmark,
Rating,
Share,
ReadingStats
} = models;
Article.belongsTo(User, {
foreignKey: 'userId',
Expand Down Expand Up @@ -95,6 +102,9 @@ export default (sequelize, DataTypes) => {
foreignKey: 'articleId',
as: 'share'
});
Article.hasMany(ReadingStats, {
foreignKey: 'articleId'
});
};
return Article;
};
26 changes: 26 additions & 0 deletions server/models/readingstats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default (sequelize, DataTypes) => {
const ReadingStats = sequelize.define(
'ReadingStats',
{
articleId: {
type: DataTypes.UUID,
allowNull: false
},
userId: {
type: DataTypes.UUID,
allowNull: false
}
},
{}
);
ReadingStats.associate = (models) => {
const { User, Article } = models;
ReadingStats.belongsTo(User, {
foreignKey: 'userId'
});
ReadingStats.belongsTo(Article, {
foreignKey: 'articleId'
});
};
return ReadingStats;
};
Loading

0 comments on commit 819c2fa

Please sign in to comment.