Skip to content

Commit

Permalink
Merge 762a39c into fe59377
Browse files Browse the repository at this point in the history
  • Loading branch information
Inclet committed May 13, 2019
2 parents fe59377 + 762a39c commit 545e4c1
Show file tree
Hide file tree
Showing 14 changed files with 486 additions and 62 deletions.
44 changes: 44 additions & 0 deletions controllers/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,5 +305,49 @@ class Article {
error: 'Article can only be rated once.'
});
}

/**
*@author: Clet Mwunguzi
* @param {Object} req
* @param {Object} res
* @returns {Object} Rate
*/
static async fetchArticleRating(req, res) {
const { slug } = req.params;

if (Number(slug)) {
return res.status(400).send({
status: 400,
error: 'slug of an article can not be a number.'
});
}

const results = await ArticleModel.verifyArticle(slug);
if (!results) {
return res.status(404).send({
status: 404,
error: 'Article can not be found.'
});
}
const { title, slug: articleSlug } = results.dataValues;
const allArticles = await ratingModel.allRatings(UserModel, ArticleModel, slug);
const { count, rows } = allArticles;

if (rows.length === 0) {
return res.status(404).send({
status: 404,
error: 'No rating found for this article'
});
}
return res.status(200).send({
status: 200,
article: {
title,
slug: articleSlug
},
who_rated: rows,
UsersCount: count
});
}
}
export default Article;
10 changes: 5 additions & 5 deletions middlewares/tokenValidation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import promiseResolver from '../helpers/promiseResolver';
import client from '../config/redisConfig';

dotenv.config();

Expand All @@ -15,12 +16,11 @@ const validateToken = async (req, res, next) => {
}
if (token) {
try {
const decoded = await jwt.verify(token, process.env.SECRETKEY);
const resolver = await promiseResolver(token);
if (resolver === 'blacklisted') {
return res.send({ status: 401, error: 'Token is no longer valid' });
const decoded = await jwt.verify(token, process.env.secretKey);
if (client.connected) {
const resolver = await promiseResolver(token);
if (resolver === 'blacklisted') { return res.send({ status: 401, error: 'Token is no longer valid' }); }
}

const { id: userid } = decoded;
req.user = userid;
next();
Expand Down
7 changes: 6 additions & 1 deletion migrations/20190403114724-create-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('articles', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
slug: { type: Sequelize.STRING, allowNull: false, unique: true },
slug:
{
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
title: { type: Sequelize.STRING, required: true },
description: { type: Sequelize.TEXT, allowNull: true },
body: { type: Sequelize.TEXT, required: true },
Expand Down
16 changes: 13 additions & 3 deletions migrations/20190424133422-create-rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,21 @@ module.exports = {
},
userId: {
type: Sequelize.INTEGER,
allowNull: false
allowNull: false,
references: {
model: 'users',
key: 'id'
},
onDelete: 'CASCADE',
},
articleSlug: {
type: Sequelize.STRING,
allowNull: false
allowNull: false,
references: {
model: 'articles',
key: 'slug'
},
onDelete: 'CASCADE',
},
rating: {
type: Sequelize.INTEGER,
Expand All @@ -28,5 +38,5 @@ module.exports = {
type: Sequelize.DATE
}
}),
down: (queryInterface, Sequelize) => queryInterface.dropTable('ratings')
down: queryInterface => queryInterface.dropTable('ratings')
};
9 changes: 7 additions & 2 deletions models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import sequelizeTrasform from 'sequelize-transforms';
const ArticleModel = (sequelize, DataTypes) => {
const Article = sequelize.define('article', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
slug: { type: DataTypes.STRING, allowNull: false, unique: true },
slug: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
onUpdate: 'CASCADE'
},
title: {
type: DataTypes.STRING,
allowNull: false,
Expand All @@ -25,7 +30,6 @@ const ArticleModel = (sequelize, DataTypes) => {
Article.findArticleSlug = (authorid, slug) => Article.findOne({ where: { authorid, slug } });
Article.deleteArticle = slug => Article.destroy({ where: { id: slug } });
Article.getAll = (limit, offset) => Article.findAll({ limit, offset });
Article.verifyArticle = id => Article.findOne({ where: { id } });
Article.verifyArticle = slug => Article.findOne({ where: { slug } });

Article.updateFoundArticle = (id, data) => {
Expand All @@ -44,6 +48,7 @@ const ArticleModel = (sequelize, DataTypes) => {
Article.belongsTo(models.user, {
foreignKey: 'authorid', onDelete: 'CASCADE'
});
Article.hasMany(models.rating, { foreignKey: 'articleSlug', onDelete: 'CASCADE' });
};
Article.associate = (models) => {
Article.hasMany(models.bookmark, {
Expand Down
34 changes: 30 additions & 4 deletions models/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ module.exports = (sequelize, DataTypes) => {
},
articleSlug: {
type: DataTypes.STRING,
allowNull: false
allowNull: false,
references: {
model: 'article',
key: 'slug'
},
onDelete: 'CASCADE'
},
rating: {
type: DataTypes.INTEGER,
Expand All @@ -20,10 +25,31 @@ module.exports = (sequelize, DataTypes) => {
where: { userId: id, articleSlug: article },
defaults: { rating: rate }
});
rating.rateUpdate = (rateId, rate) => rating.update({ rating: rate },
{ returning: true, where: { id: rateId } });

rating.avgFind = id => rating.findAll({ where: { articleId: id }, attributes: ['articleId', [sequelize.fn('AVG', sequelize.col('rating')), 'avgRating']], group: 'rating.articleId' });
rating.rateUpdate = (rateId, rate) => rating.update({
rating: rate
},
{
returning: true,
where: { id: rateId }
});

rating.avgFind = id => rating.findAll({
where: { articleId: id },
attributes: ['articleId', [sequelize.fn('AVG', sequelize.col('rating')), 'avgRating']],
group: 'rating.articleId'
});

rating.allRatings = (userModel, articleModel, slug) => rating.findAndCountAll({
where: { articleSlug: slug },
attributes: ['rating'],
include: [{
model: userModel,
attributes: ['id', 'firstname', 'lastname', 'username']
}
],
});

rating.associate = (models) => {
rating.belongsTo(models.user, { foreignKey: 'userId', onDelete: 'CASCADE' });
rating.belongsTo(models.article, { foreignKey: 'articleSlug', onDelete: 'CASCADE' });
Expand Down
1 change: 0 additions & 1 deletion models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ const UserModel = (Sequelize, DataTypes) => {
foreignKey: 'userid', onDelete: 'CASCADE'
});
};

return User;
};
export default UserModel;
1 change: 1 addition & 0 deletions routes/api/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ router.put('/:slug', AuthToken, errorHandler(articleController.updateArticle));
router.post('/:slug/bookmark', AuthToken, errorHandler(articleController.bookmarkArticle));
router.get('/', errorHandler(articleController.articlePagination));
router.post('/:slug/rate/:rate', AuthToken, articleController.rateArticle);
router.get('/:slug/rates', AuthToken, articleController.fetchArticleRating);

export default router;
101 changes: 85 additions & 16 deletions swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ paths:
type: 'string'
example: 'Welcome to Author Haven'
'404':
description: Route not found
description: Route not found
# responses may fall through to errors
/api/users:
# returns a jwt token after registration
Expand Down Expand Up @@ -182,21 +182,21 @@ paths:
'200':
description: success
schema:
type: object
properties:
user:
type: object
properties:
username:
type: string
email:
type: string
bio:
type: string
token:
type: string
image:
type: string
type: object
properties:
user:
type: object
properties:
username:
type: string
email:
type: string
bio:
type: string
token:
type: string
image:
type: string
'404':
description: Route not found
/api/auth/google:
Expand Down Expand Up @@ -484,3 +484,72 @@ paths:
type: 'String'
example: 'image1.png'

/api/articles/{articleSlug}/rates:
get:
description: Returns an object with title, slug of an article, and names of users who rated it.
produces:
- application/json
parameters:
- in: path
name: articleSlug
description: it's slug of an existing article
required: true
type: string
- in: header
name: authorization
type: string
required: true

responses:
'400':
description: slug of an article can not be a number
schema:
type: object
properties:
status:
type: 'integer'
error:
type: 'string'
'404':
description: Article can not be found
schema:
type: object
properties:
status:
type: 'integer'
error:
type: 'string'
'200':
description: fetch rating of an article
schema:
type: object
properties:
status:
type: 'integer'
article:
type: object
properties:
title:
type: 'string'
slug:
type: 'string'
who_rated:
type: 'array'
items:
type: object
properties:
rating:
type: 'integer'
user:
type: object
properties:
id:
type: 'integer'
firstname:
type: 'string'
lastname:
type: 'string'
username:
type: 'string'
UsersCount:
type: 'integer'
16 changes: 6 additions & 10 deletions test/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,13 @@ chai.should();
chai.use(chaiHttp);

const logError = debug('app:*');
dotenv.config();
process.env.NODE_ENV = 'test';
/**
* @author: Innocent Nkunzi
* @description: tests related to article
*/

dotenv.config();
process.env.NODE_ENV = 'test';

describe('Cleaning the database', () => {
before('Cleaning the database first', async () => {
await articleModel.destroy({ truncate: true, cascade: true });
await userModel.destroy({ where: { email: userModel.email }, truncate: true, cascade: true });
});
});
// A user to be used to create article
const user = {
username: 'nkunziinnocent',
email: 'nkunzi@gmail.com',
Expand All @@ -43,6 +35,10 @@ const newUser = {
};
let userToken, testToken;
describe('Create a user to be used in in creating article', () => {
before('Cleaning the database first', async () => {
await articleModel.destroy({ truncate: true, cascade: true });
await userModel.destroy({ where: { email: userModel.email }, truncate: true, cascade: true });
});
it('should create a user', (done) => {
chai
.request(index)
Expand Down
Loading

0 comments on commit 545e4c1

Please sign in to comment.