Skip to content

Commit

Permalink
Merge 3522760 into 3094143
Browse files Browse the repository at this point in the history
  • Loading branch information
Bobsar0 committed Jan 30, 2019
2 parents 3094143 + 3522760 commit 7542189
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 12 deletions.
183 changes: 183 additions & 0 deletions server/controllers/RatingController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import db from '../models';
import response from '../helpers/response';
import Util from '../helpers/Util';

const { Rating, Article, User } = db;

/**
* @class RatingController
*/
class RatingController {
/**
*
* @description Method to create ratings.
* @static
* @param {object} req Express Request object
* @param {object} res Express Response object
* @returns {object} Json response
* @memberof RatingController
*/

/**
* @static
* @description Method to handle fetching of all ratings of an article
* @param {object} req HTTP request object
* @param {object} res HTTP response object
* @returns {object} api route response with the ratings
*/
static async getArticleRatings(req, res) {
try {
const { userId } = req.user;
const user = await User.findByPk(userId);
if (!user) return response(res, 404, 'failure', 'User not found');

const { slug } = req.params;
const article = await Util.resourceExists(Article, { slug });
if (!article) return response(res, 404, 'failure', 'Article not found');

const ratingsQuery = await Rating.findAll({
include: [{
model: User,
as: 'rater',
attributes: ['userName', 'bio']
}],
where: {
articleId: article.dataValues.id
},
});

if (ratingsQuery.length === 0) {
return response(res, 200, 'success', 'This article has not been rated');
}
const ratings = [];
ratingsQuery.map(rating => ratings.push(rating.dataValues));
return response(res, 200, 'success', 'Ratings successfully found', null, {
ratings,
ratingsCount: ratings.length
});
} catch (error) {
return response(res, 500, 'failure', 'Something went wrong on the server', `server error: ${error.message}`);
}
}

/**
* @static
* @description Method to handle fetching of a single user's ratings
* @param {object} req HTTP request object
* @param {object} res HTTP response object
* @returns {object} api route response with the ratings
*/
static async getUserRatings(req, res) {
try {
const { userId } = req.user;
const user = await User.findByPk(userId);
if (!user) return response(res, 404, 'failure', 'User not found');


const ratingsQuery = await Rating.findAll({
where: {
userId
},
});
if (ratingsQuery.length === 0) {
return response(res, 200, 'success', 'You have not rated any article yet');
}
const ratings = [];
ratingsQuery.map(rating => ratings.push(rating.dataValues));
return response(res, 200, 'success', 'Ratings successfully found', null, {
ratings,
ratingsCount: ratings.length
});
} catch (error) {
return response(res, 500, 'failure', 'Something went wrong on the server', `server error: ${error.message}`);
}
}

/**
* @static
* @description Method to Update a single user's ratings
* @param {object} req HTTP request object
* @param {object} res HTTP response object
* @returns {object} api route response with the ratings
*/
static async updateOrCreate(req, res) {
try {
const { userId } = req.user;
const user = await User.findByPk(userId);
if (!user) return response(res, 404, 'failure', 'User not found');

const { slug } = req.params;
const { rating } = req.body;

const article = await Util.resourceExists(Article, { slug });
if (!article) return response(res, 404, 'failure', 'Article not found');

if (!rating) return response(res, 400, 'failure', 'Please provide a rating');
// eslint-disable-next-line no-restricted-globals
if (!Number.isSafeInteger(parseInt(rating, 10)) || isNaN(rating) || rating.toString().trim() === '' || rating < 1 || rating > 5) {
return response(res, 400, 'failure', 'Rating should be between 1 and 5');
}

const rated = await Util.resourceExists(Rating, {
articleId: article.dataValues.id,
userId
});
if (!rated) {
if (article.dataValues.userId === userId) return response(res, 403, 'failure', 'You cannot rate your own article');
const rate = await Rating.create({
rating: Number(rating),
articleId: article.dataValues.id,
userId
});
return response(res, 201, 'success', 'You have successfully rated this article', null, rate.dataValues);
}

const previousRating = rated.dataValues.rating;
if (previousRating === Number(rating)) {
return response(res, 200, 'success', 'You did not update the rating');
}
const newRating = await rated.update({
rating: Number(rating)
});
const { articleId, createdAt, updatedAt } = newRating.dataValues;
return response(
res, 200, 'success', 'You have successfully updated your rating', null,
{
previousRating, currentRating: Number(rating), articleId, userId, createdAt, updatedAt
}
);
} catch (error) {
return response(res, 500, 'failure', 'Something went wrong on the server', `server error: ${error.message}`);
}
}

/**
* @static
* @description Method to delete a user's rating
* @param {object} req HTTP request object
* @param {object} res HTTP response object
* @returns {object} api route response
*/
static async delete(req, res) {
try {
const { userId } = req.user;
const { slug } = req.params;

const article = await Util.resourceExists(Article, { slug });
if (!article) return response(res, 404, 'failure', 'Article not found');

const rated = await Util.resourceExists(Rating, {
articleId: article.dataValues.id,
userId
});
if (!rated) return response(res, 404, 'failure', 'Rating not found');

await rated.destroy();
return response(res, 200, 'success', 'Rating deleted successfully');
} catch (error) {
return response(res, 500, 'failure', 'Something went wrong on the server', `server error: ${error.message}`);
}
}
}

export default RatingController;
14 changes: 14 additions & 0 deletions server/helpers/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ class Util {
const date = `${new Date(dateString).toDateString()} ${new Date(dateString).toLocaleTimeString()}`;
return date;
}

/**
* @static
* @description a function to ascertain if a resource exists
* @param {object} model Model to check for resource existence
* @param {object} data
* @returns {object} returns resource if true or null otherwise
*/
static async resourceExists(model, data) {
const resource = await model.findOne({
where: data
});
return resource;
}
}

export default Util;
4 changes: 2 additions & 2 deletions server/middlewares/AuthMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AuthMiddleware {
* @param {function} next next middleware function
* @returns {object} returns error message if user is not authenticated
*/
static checkIfUserIsAuthenticated(req, res, next) {
static async checkIfUserIsAuthenticated(req, res, next) {
try {
const { authorization } = req.headers;
if (!authorization) {
Expand All @@ -34,7 +34,7 @@ class AuthMiddleware {
return response(res, 401, 'failure', 'Token is invalid, You need to log in again');
}

return response(res, 500, 'failure', 'An error occured on the server', error);
return response(res, 500, 'failure', 'An error occured on the server', error.message);
}
}

Expand Down
Empty file removed server/migrations/.gitkeep
Empty file.
34 changes: 34 additions & 0 deletions server/migrations/20190111130817-create-commentLike.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export default {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
.then(() => {
return queryInterface.createTable('CommentLikes', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.literal('uuid_generate_v4()')
},
userId: {
type: Sequelize.UUID,
allowNull: false
},
commentId: {
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) => queryInterface.dropTable('CommentLikes')
};
38 changes: 38 additions & 0 deletions server/migrations/20190121154422-create-user-follows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('UserFollows', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
followersId: {
type: Sequelize.UUID,
references: {
model: 'Users',
key: 'id'
}
},
usersId: {
type: Sequelize.UUID,
references: {
model: 'Users',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('UserFollows');
}
};
7 changes: 1 addition & 6 deletions server/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export default (sequelize, DataTypes) => {
Comment,
ArticleLikesDislike,
Bookmark,
Rating,
Share,
ReadingStats
} = models;
Expand All @@ -75,7 +74,7 @@ export default (sequelize, DataTypes) => {
Article.belongsToMany(User, {
through: 'Rating',
as: 'ratings',
foreignKey: 'articleId'
foreignKey: 'articleId',
});
Article.belongsToMany(Tag, {
through: 'ArticleTag',
Expand All @@ -94,10 +93,6 @@ export default (sequelize, DataTypes) => {
foreignKey: 'articleId',
as: 'bookmark'
});
Article.hasOne(Rating, {
foreignKey: 'articleId',
as: 'rating'
});
Article.hasMany(Share, {
foreignKey: 'articleId',
as: 'share'
Expand Down
16 changes: 12 additions & 4 deletions server/models/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,25 @@ export default (sequelize, DataTypes) => {
},
articleId: {
type: DataTypes.UUID,
allowNull: false
allowNull: false,
},
userId: {
type: DataTypes.UUID,
allowNull: false
}
},
{}
}, {}
);

Rating.associate = (models) => {
Rating.belongsTo(models.User, {});
Rating.belongsTo(models.User, {
foreignKey: 'userId',
as: 'rater',
onDelete: 'CASCADE'
});
Rating.belongsTo(models.Article, {
foreignKey: 'articleId',
onDelete: 'CASCADE'
});
};
return Rating;
};
28 changes: 28 additions & 0 deletions server/routes/api/rating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Router } from 'express';
import RatingController from '../../controllers/RatingController';
import AuthMiddleware from '../../middlewares/AuthMiddleware';

const ratingRoutes = Router();

ratingRoutes.get(
'/articles/:slug/ratings',
AuthMiddleware.checkIfUserIsAuthenticated,
RatingController.getArticleRatings
);
ratingRoutes.get(
'/user/ratings',
AuthMiddleware.checkIfUserIsAuthenticated,
RatingController.getUserRatings
);
ratingRoutes.put(
'/articles/:slug/rating',
AuthMiddleware.checkIfUserIsAuthenticated,
RatingController.updateOrCreate
);
ratingRoutes.delete(
'/articles/:slug/rating',
AuthMiddleware.checkIfUserIsAuthenticated,
RatingController.delete
);

export default ratingRoutes;
2 changes: 2 additions & 0 deletions server/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import commentRoutes from './api/comment';
import likeRoutes from './api/likes';
import readStats from './api/readStats';
import reportArticleRoutes from './api/reportArticle';
import ratingRoutes from './api/rating';

const routes = Router();

Expand All @@ -16,5 +17,6 @@ routes.use(commentRoutes);
routes.use(likeRoutes);
routes.use(readStats);
routes.use(reportArticleRoutes);
routes.use(ratingRoutes);

export default routes;
Loading

0 comments on commit 7542189

Please sign in to comment.