Skip to content

Commit

Permalink
feat(users): Fetch Article Ratings
Browse files Browse the repository at this point in the history
- Adds migragtions to change enum datatype of article table to integers
- Implements fetch ratings for an article
- Writes tests
- Add pagination support to fetch all article ratings

[Delivers #166840924]
  • Loading branch information
tolumide-ng committed Jul 22, 2019
1 parent ec065fc commit 306adac
Show file tree
Hide file tree
Showing 23 changed files with 401 additions and 63 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"exclude": [
"**/*.spec.js",
"**/test/utils",
"**/helpers/passport/"
"**/helpers/passport/",
"**/database/"
]
},
"husky": {
Expand Down
13 changes: 7 additions & 6 deletions src/controllers/article.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ class ArticleController {
static async getBookMarks(req, res) {
const { id: userId } = req.currentUser;

const allBookMarks = await BaseRepository.findAndInclude(
db.Bookmark,
{ userId },
db.Article,
'article'
);
const allBookMarks = await BaseRepository.findAndInclude({
model: db.Bookmark,
options: { userId },
alias: 'article',
associatedModel: db.Article,
attributes: ['id', 'title', 'authorId', 'description']
});

if (allBookMarks.length < 1) {
return responseGenerator.sendError(
Expand Down
36 changes: 36 additions & 0 deletions src/controllers/rating.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import db from '../database/models';
import BaseRepository from '../repository/base.repository';
import responseGenerator from '../helpers/responseGenerator';
import { checkRater } from '../middleware/users.middleware';
import Pagination from '../helpers/pagination';

const { Rating } = db;

Expand Down Expand Up @@ -60,6 +61,41 @@ class RatingsController {
return responseGenerator.sendError(res, 500, error.message);
}
}

/**
* Get all bookmarked articles
* @async
* @param {object} req - Request object
* @param {object} res - Response object
* @return {json} Returns containing all ratings of an article
* @static
*/
static async getRatings(req, res) {
const { articleId } = req.params;

const averageRating = await BaseRepository.averageRating(
articleId,
db.Rating,
'articleId',
'ratings'
);
const [response] = averageRating;
if (!response) {
return responseGenerator.sendError(
res,
404,
'This article has not been rated yet'
);
}

const summary = {
articleId: response.articleId,
averageRating: Number(response.avgRating).toFixed(2),
totalNumberOfRatings: Number(response.totalCount).toFixed(2)
};

return responseGenerator.sendSuccess(res, 200, null, { ...summary });
}
}

export default RatingsController;
33 changes: 18 additions & 15 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,15 @@ class UserController {
*/
static async getFollowers(req, res) {
const { id: followeeId } = req.currentUser;
// const followers = await BaseRepository.findAll(db.Follower, { followeeId });
const followers = await BaseRepository.findAndInclude(
db.Follower,
{ followeeId },
db.User,
'followed'
);
if (followers.length > 1) {

const followers = await BaseRepository.findAndInclude({
model: db.Follower,
options: { followeeId },
alias: 'followed',
associatedModel: db.User,
attributes: ['id', 'username', 'email']
});
if (followers.length >= 1) {
return responseGenerator.sendSuccess(res, 200, followers);
}
return responseGenerator.sendError(
Expand All @@ -381,13 +382,15 @@ class UserController {
*/
static async getFollowings(req, res) {
const { id: followerId } = req.currentUser;
const followings = await BaseRepository.findAndInclude(
db.Follower,
{ followerId },
db.User,
'followed'
);
if (followings.length > 1) {

const followings = await BaseRepository.findAndInclude({
model: db.Follower,
options: { followerId },
alias: 'followed',
associatedModel: db.User,
attributes: ['id', 'username', 'email']
});
if (followings.length >= 1) {
return responseGenerator.sendSuccess(res, 200, followings);
}
return responseGenerator.sendError(
Expand Down
3 changes: 1 addition & 2 deletions src/database/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ module.exports = {
development: {
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
database: process.env.DB_DBNAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
logging: false,
ssl: true,
define: {
timestamps: false
Expand Down
26 changes: 26 additions & 0 deletions src/database/migrations/20190717153022-changeRatingsDataType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return Promise.all([
(queryInterface.removeColumn('Ratings', 'ratings'),
queryInterface.sequelize.query('DROP TYPE "enum_Ratings_ratings";'),
queryInterface.addColumn('Ratings', 'ratings', {
type: Sequelize.INTEGER,
validate: {
isInt: true,
isIn: [[1, 2, 3, 4, 5]],
min: 0
}
}))
]);
},
down: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.removeColumn('Ratings', 'ratings'),
queryInterface.addColumn('Ratings', 'ratings', {
type: Sequelize.ENUM,
values: [1, 2, 3, 4, 5],
allowNull: false
})
]);
}
};
7 changes: 7 additions & 0 deletions src/database/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,12 @@ export default (sequelize, DataTypes) => {
foreignKey: 'articleId'
});
};
Article.associate = models => {
Article.belongsToMany(models.Rating, {
through: 'Ratings',
foreignKey: 'articleId',
as: 'ratings'
});
};
return Article;
};
2 changes: 1 addition & 1 deletion src/database/models/follower.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ module.exports = (sequelize, DataTypes) => {
Follower.associate = models => {
Follower.belongsTo(models.User, {
foreignKey: 'followeeId',
as: 'followee'
as: 'followed'
});
};
return Follower;
Expand Down
4 changes: 2 additions & 2 deletions src/database/models/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export default (sequelize, DataTypes) => {
);
Rating.associate = models => {
Rating.belongsTo(models.User, {
as: 'rater',
as: 'user',
foreignKey: 'userId'
});
Rating.belongsTo(models.Article, {
as: 'rating',
as: 'article',
foreignKey: 'articleId'
});
};
Expand Down
14 changes: 11 additions & 3 deletions src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,18 @@ module.exports = (sequelize, DataTypes) => {
User.associate = models => {
User.belongsToMany(models.User, {
through: 'Followers',
as: 'follower',
as: 'users',
foreignKey: 'followerId'
});
};
User.associate = models => {
User.belongsToMany(models.User, {
through: 'Followers',
as: 'followee',
as: 'users',
foreignKey: 'followeeId'
});

};
User.associate = models => {
User.hasMany(models.Rating, {
foreignKey: 'userId',
as: 'rating'
Expand All @@ -70,5 +71,12 @@ module.exports = (sequelize, DataTypes) => {
as: 'articleId'
});
};
User.associate = models => {
User.belongsToMany(models.Article, {
through: 'Ratings',
foreignKey: 'userId',
as: 'rater'
});
};
return User;
};
26 changes: 22 additions & 4 deletions src/middleware/article.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,27 @@ import db from '../database/models';
import Baserepository from '../repository/base.repository';
import responseGenerator from '../helpers/responseGenerator';

export const articleExist = async (req, res, next) => {
const { articleId } =
req.body.articleId !== undefined ? req.body : req.params;
export const articleExistParams = async (req, res, next) => {
const { articleId } = req.params;

const findArticle = await Baserepository.findOneByField(db.Article, {
id: articleId
});

if (!findArticle) {
return responseGenerator.sendError(
res,
404,
'The requested article was not found'
);
}
req.article = findArticle;
next();
};

export const articleExistBody = async (req, res, next) => {
const { articleId } = req.body;

const findArticle = await Baserepository.findOneByField(db.Article, {
id: articleId
});
Expand All @@ -20,4 +38,4 @@ export const articleExist = async (req, res, next) => {
next();
};

export default articleExist;
export default { articleExistBody, articleExistParams };
4 changes: 2 additions & 2 deletions src/middleware/auth.middleware.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import jwt from 'jsonwebtoken';
import responseGenerator from '../helpers/responseGenerator';

const decodeToken = (req, res, next, token) => {
jwt.verify(token, process.env.JWT_SECRET, (error, decode) => {
const decodeToken = async (req, res, next, token) => {
await jwt.verify(token, process.env.JWT_SECRET, (error, decode) => {
if (!error) {
req.currentUser = decode;
return next();
Expand Down
1 change: 1 addition & 0 deletions src/middleware/pagination.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const paginationValidations = (req, res, next) => {
page,
limit
};

next();
};

Expand Down
17 changes: 16 additions & 1 deletion src/middleware/validation.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,26 @@ const articleId = Joi.number()
.integer()
.required();

const number = Joi.number()
.integer()
.required();

const bookmarkArticle = {
body: {
articleId
}
};

const fetchRatings = {
params: {
articleId
},
query: {
page: number,
limit: number
}
};

const followOrUnfollow = {
body: {
followeeId
Expand Down Expand Up @@ -91,5 +105,6 @@ export default {
'/user/:id': updateProfileSchema,
'/bookmark': bookmarkArticle,
'/unbookmark': bookmarkArticle,
'/:articleId/ratings': ratingSchema
'/:articleId/ratings': ratingSchema,
'/ratings/:articleId': fetchRatings
};
Loading

0 comments on commit 306adac

Please sign in to comment.