Skip to content

Commit

Permalink
Merge branch 'develop' into ft/167190446-follow-co-authours
Browse files Browse the repository at this point in the history
  • Loading branch information
chuxmykel committed Aug 2, 2019
2 parents e55e0dd + 00b127c commit 40e03e7
Show file tree
Hide file tree
Showing 20 changed files with 1,371 additions and 129 deletions.
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
"pg-hstore": "^2.3.3",
"sequelize": "^5.10.2",
"sequelize-cli": "^5.5.0",
"sequelize-slugify": "^0.7.0",
"sinon": "^7.3.2",
"swagger-ui-express": "^4.0.7",
"uuid": "^3.3.2",
"yamljs": "^0.3.0"
},
"devDependencies": {
Expand Down
161 changes: 161 additions & 0 deletions server/controllers/articleController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import uuid from 'uuid';
import sequelize from 'sequelize';
import models from '../db/models';
import utils from '../helpers/Utilities';

const { Op } = sequelize;
/**
* @Module ArticleController
* @description Controlls all activities related to Articles
*/
class ArticleController {
/**
* @description Creates an Article
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} Article data
* @memberof ArticleController
*/
static async createArticle(req, res) {
const {
title,
description,
tagList,
articleBody,
favorited,
favoriteCounts,
image
} = req.body.article;

const article = await models.Article.create({
title,
description,
tagList,
articleBody,
favorited,
uuid: uuid.v1().split('-')[0],
favoriteCounts,
authorId: req.user.id,
image
});
const userData = await models.Article.findOne({
where: { authorId: req.user.id },
include: [
{
model: models.User,
attributes: {
exclude: [
'id',
'createdAt',
'updatedAt',
'firstname',
'lastname',
'password'
]
}
}
]
});
const userDetails = JSON.parse(JSON.stringify(userData));
article.tagList = [...article.dataValues.tagList.split(' ')];
article.author = {
username: userDetails.User.username,
email: userDetails.User.email,
bio: userDetails.User.bio,
image: userDetails.User.image
};
return utils.successStat(res, 201, 'articles', article);
}

/**
* @description returns all Articles
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} An array of all Articles
* @memberof ArticleController
*/
static async getAllArticles(req, res) {
const articles = await models.Article.findAll();
return utils.successStat(res, 200, 'articles', articles);
}

/**
* @description get a sinlge article
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} returns a single article
* @memberof ArticleController
*/
static async getOneArticle(req, res) {
const article = await models.Article.findOne({
where: { slug: req.params.slug }
});
if (!article) {
return utils.errorStat(res, 404, 'Article not found');
}
return utils.successStat(res, 200, 'article', article);
}

/**
* @description Edit an Article and returns the edited article
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {Object} Edited article
* @memberof ArticleController
*/
static async editArticle(req, res) {
const {
title,
description,
articleBody,
tagList,
image
} = req.body.article;
const editedArticle = await models.Article.update(
{
title,
description,
articleBody,
tagList,
image
},
{
returning: true,
where: { [Op.and]: [{ slug: req.params.slug }] }
}
);

if (editedArticle[1].length < 1) {
return utils.errorStat(res, 404, 'Article not found');
}

const article = editedArticle[1][editedArticle[1].length - 1].dataValues;
return utils.successStat(res, 200, 'article', article);
}

/**
* @description Deletes an Article
* @param {Object} req - request object
* @param {Object} res - response object
* @returns {String} returns a message indicating that the article was deleted
* @memberof ArticleController
*/
static async deleteArticle(req, res) {
const deletedArticle = await models.Article.destroy({
returning: true,
where: { [Op.and]: [{ slug: req.params.slug }] }
});

if (!deletedArticle) {
return utils.errorStat(res, 404, 'Article not found');
}
return utils.successStat(
res,
200,
'message',
'Article deleted successfully'
);
}
}

export default ArticleController;
9 changes: 7 additions & 2 deletions server/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class UserController {

const mail = new Mail({
to: user.email,
subject: 'Welcome email',
subject: 'Password Reset',
messageHeader: `Hi, ${user.firstname}!`,
messageBody: 'We are exicted to get you started. First, you have to verify your account. Just click on the link below',
iButton: true
Expand Down Expand Up @@ -114,7 +114,7 @@ class UserController {
if (!user) return errorStat(res, 404, `No user found with email address: ${email}`);
const { id, username } = user;
const token = generateToken({ id, username, email });
await PasswordResetTokens.create({ token, userId: id });
await PasswordResetTokens.create({ token, userId: id, email });
const link = `http://${process.env.APP_URL}/api/v1/users/resetPassword/${id}/${token}`;
const mail = new Mail({
to: email,
Expand Down Expand Up @@ -147,11 +147,16 @@ class UserController {
if (!user) return errorStat(res, 404, 'No user found');
const isTokenAvailable = await PasswordResetTokens.findOne({ where: { userId: id, token, } });
const payload = await verifyToken(token, (err, decoded) => decoded);
/* istanbul ignore next */
if (!payload
|| payload.id !== Number(id)
/* istanbul ignore next */
|| !isTokenAvailable) return errorStat(res, 401, 'Invalid Reset Token');
/* istanbul ignore next */
await models.User.update({ password }, { where: { id: user.id } });
/* istanbul ignore next */
PasswordResetTokens.destroy({ where: { userId: id } });
/* istanbul ignore next */
return successStat(res, 200, 'message', 'Success, Password Reset Successfully');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ module.exports = {
type: Sequelize.INTEGER,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
Expand Down
54 changes: 54 additions & 0 deletions server/db/migrations/20190730213651-create-article.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Articles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
articleBody: {
type: Sequelize.STRING
},
description: {
type: Sequelize.STRING
},
tagList: {
type: Sequelize.STRING
},
slug: {
type: Sequelize.STRING
},
favorited: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
favoriteCounts: {
type: Sequelize.INTEGER,
defaultValue: 0
},
uuid: {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING
},
authorId: {
type: Sequelize.INTEGER
},
author: {
type: Sequelize.JSON
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: queryInterface => queryInterface.dropTable('Articles')
};
35 changes: 35 additions & 0 deletions server/db/models/article.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable func-names */
import SequelizeSlugify from 'sequelize-slugify';

module.exports = (sequelize, DataTypes) => {
const Article = sequelize.define(
'Article',
{
title: DataTypes.STRING,
articleBody: DataTypes.STRING,
description: DataTypes.STRING,
tagList: DataTypes.STRING,
uuid: DataTypes.STRING,
slug: {
type: DataTypes.STRING,
unique: true
},
author: DataTypes.JSON,
favorited: DataTypes.BOOLEAN,
favoriteCounts: DataTypes.INTEGER,
image: DataTypes.STRING,
authorId: DataTypes.INTEGER
},
{}
);

SequelizeSlugify.slugifyModel(Article, {
source: ['title'],
suffixSource: ['uuid'],
slugOptions: { lower: true }
});
Article.associate = function (models) {
Article.belongsTo(models.User, { foreignKey: 'id', onDelete: 'CASCADE' });
};
return Article;
};
3 changes: 2 additions & 1 deletion server/db/models/passwordresettokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
module.exports = (sequelize, DataTypes) => {
const PasswordResetTokens = sequelize.define('PasswordResetTokens', {
token: DataTypes.STRING,
userId: DataTypes.INTEGER
userId: DataTypes.INTEGER,
email: DataTypes.STRING
}, {});
PasswordResetTokens.associate = () => {
// associations can be defined here
Expand Down
1 change: 1 addition & 0 deletions server/db/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = (sequelize, DataTypes) => {
hooks: true,
timestamps: false,
});
User.hasMany(models.Article, { foreignKey: 'authorId', onDelete: 'CASCADE' });
};
return User;
};
Loading

0 comments on commit 40e03e7

Please sign in to comment.