-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
574 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ before_script: | |
|
||
script: | ||
- npm test | ||
|
||
after_success: | ||
- npm run coveralls | ||
- npm run coverage | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
|
||
import db from '../models'; | ||
import articleValidation from '../validation/articles'; | ||
|
||
const { Article, User } = db; | ||
|
||
/** | ||
* Article controller function | ||
*/ | ||
class ArticleController { | ||
/** | ||
* @static | ||
* @description returns slug from article title | ||
* @param {string} title | ||
* @returns {string} slug | ||
*/ | ||
static slugify(title) { | ||
return title.replace(/\s+/g, '-') + Math.floor((Math.random() * 1000000) + 1).toString(); | ||
} | ||
|
||
/** | ||
* @static | ||
* @param {reuest} req | ||
* @param {response} res | ||
* @return {json} res | ||
* @description creates article. | ||
*/ | ||
static create(req, res) { | ||
// validation file :(to be moved to validations file) | ||
const { errors, isValid } = articleValidation.validateArticle(req.body); | ||
if (!isValid) { | ||
return res.status(400).json({ errors }); | ||
} | ||
|
||
// get parameters from request | ||
const { | ||
title, description, body, | ||
} = req.body; | ||
|
||
|
||
// set author id to current user id | ||
const authorId = req.userId; | ||
|
||
// generate slug | ||
const slug = ArticleController.slugify(title); | ||
|
||
User.findById(authorId, { | ||
attributes: ['username', 'email', 'bio', 'image'] | ||
}).then((user) => { | ||
// create article | ||
Article.create({ | ||
title, description, body, authorId, slug | ||
}) | ||
.then(article => res.status(201).json({ | ||
article: { | ||
slug: article.slug, | ||
title: article.title, | ||
description: article.description, | ||
body: article.body, | ||
author: user, | ||
createdAt: article.createdAt, | ||
updatedAt: article.updatedAt, | ||
} | ||
})); | ||
}); | ||
} | ||
|
||
/** | ||
* @static | ||
* @param {reuest} req | ||
* @param {response} res | ||
* @return {json} res | ||
* @description returns all article. | ||
*/ | ||
static getAll(req, res) { | ||
return Article.findAll({ | ||
include: [{ | ||
model: User, | ||
as: 'author', | ||
attributes: ['username', 'email', 'bio', 'image'] | ||
}], | ||
attributes: ['slug', 'title', 'description', 'body', 'createdAt', 'updatedAt'] | ||
}) | ||
.then((article) => { | ||
res.status(200).json({ | ||
articles: article | ||
}); | ||
}) | ||
.catch(error => res.status(500).json(error)); | ||
} | ||
|
||
/** | ||
* @static | ||
* @param {reuest} req | ||
* @param {response} res | ||
* @return {json} res | ||
* @description returns specific article that has the slug passes as req param (article_slug). | ||
*/ | ||
static getSpecific(req, res) { | ||
return Article.findOne({ | ||
where: { slug: req.params.article_slug }, | ||
include: [{ | ||
model: User, | ||
as: 'author', | ||
attributes: ['username', 'email', 'bio', 'image'] | ||
}], | ||
attributes: ['slug', 'title', 'description', 'body', 'createdAt', 'updatedAt'] | ||
}) | ||
.then(article => res.status(200).json({ | ||
article | ||
})) | ||
.catch(error => res.status(400).json(error)); | ||
} | ||
|
||
/** | ||
* @static | ||
* @param {reuest} req | ||
* @param {response} res | ||
* @return {json} res | ||
* @description returns specific article with given slug. | ||
*/ | ||
static update(req, res) { | ||
return Article.findOne({ | ||
where: { slug: req.params.article_slug }, | ||
include: [{ | ||
model: User, | ||
as: 'author', | ||
attributes: ['username', 'email', 'bio', 'image'] | ||
}], | ||
}) | ||
.then((article) => { | ||
// return 404 if article not found | ||
if (!article) { | ||
return res.status(404).json({ | ||
errors: { message: 'article not found' } | ||
}); | ||
} | ||
|
||
// check if article belongs to current user | ||
if (parseInt(article.authorId, 10) !== parseInt(req.userId, 10)) { | ||
return res.status(403).json({ | ||
errors: { message: 'forbidden from editing another user\'s article' } | ||
}); | ||
} | ||
|
||
return article.update({ | ||
title: req.body.title || article.title, | ||
slug: ArticleController.slugify(req.body.title) || article.slug, | ||
description: req.body.description || article.description, | ||
body: req.body.body || article.body | ||
}) | ||
.then((self) => { | ||
const updated = self; | ||
delete updated.id; | ||
delete updated.authorId; | ||
res.status(200).json({ article: updated }); | ||
}) | ||
.catch(error => res.status(400).json(error)); | ||
}) | ||
.catch(error => res.status(400).json(error)); | ||
} | ||
|
||
/** | ||
* @static | ||
* @param {reuest} req | ||
* @param {response} res | ||
* @return {json} res | ||
* @description deletes an article object with given slug in param. | ||
*/ | ||
static delete(req, res) { | ||
return Article.findOne({ where: { slug: req.params.article_slug } }) | ||
.then((article) => { | ||
if (!article) { | ||
return res.status(404).json({ | ||
message: 'article not found', | ||
}); | ||
} | ||
|
||
// check if article belongs to current user | ||
if (parseInt(article.authorId, 10) !== parseInt(req.userId, 10)) { | ||
return res.status(403).json({ | ||
errors: { message: 'forbidden from deleting another user\'s article' } | ||
}); | ||
} | ||
|
||
return article.destroy() | ||
.then(() => res.status(200).json({ | ||
message: 'article successfully deleted', | ||
})) | ||
.catch(error => res.status(400).json(error)); | ||
}) | ||
.catch(error => res.status(400).json(error)); | ||
} | ||
} | ||
|
||
export default ArticleController; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
module.exports = { | ||
up: (queryInterface, Sequelize) => queryInterface.createTable('Articles', { | ||
id: { | ||
allowNull: false, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
type: Sequelize.INTEGER | ||
}, | ||
title: { | ||
allowNull: false, | ||
type: Sequelize.STRING | ||
}, | ||
slug: { | ||
type: Sequelize.STRING, | ||
unique: true, | ||
allowNull: false | ||
}, | ||
description: { | ||
type: Sequelize.STRING | ||
}, | ||
body: { | ||
allowNull: false, | ||
type: Sequelize.TEXT | ||
}, | ||
authorId: { | ||
allowNull: false, | ||
type: Sequelize.INTEGER, | ||
onDelete: 'CASCADE', | ||
references: { | ||
model: 'Users', | ||
key: 'id', | ||
as: 'authorId', | ||
}, | ||
}, | ||
likeDislikeId: { | ||
type: Sequelize.INTEGER, | ||
references: { | ||
model: 'Users', | ||
key: 'id', | ||
as: 'authorId', | ||
} | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}), | ||
down: queryInterface => queryInterface.dropTable('Articles') | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module.exports = (sequelize, DataTypes) => { | ||
const Article = sequelize.define('Article', { | ||
title: { | ||
type: DataTypes.STRING, | ||
allowNull: false, | ||
}, | ||
slug: { | ||
type: DataTypes.STRING, | ||
unique: true, | ||
allowNull: false, | ||
}, | ||
description: DataTypes.STRING, | ||
body: { | ||
type: DataTypes.TEXT, | ||
allowNull: false | ||
}, | ||
authorId: { | ||
type: DataTypes.INTEGER, | ||
allowNull: false, | ||
}, | ||
likeDislikeId: DataTypes.INTEGER | ||
}, {}); | ||
|
||
Article.associate = (models) => { | ||
// 1:m relationship | ||
Article.belongsTo(models.User, { | ||
as: 'author', foreignKey: 'authorId' | ||
}); | ||
}; | ||
return Article; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import ArticleController from '../../controllers/article'; | ||
import auth from '../../middleware/auth'; | ||
|
||
// get authenticateUser method | ||
const { authenticateUser, authorizeAuthor } = auth; | ||
const router = require('express').Router(); | ||
|
||
router.post('/', authenticateUser, authorizeAuthor, ArticleController.create); | ||
router.get('/', authenticateUser, ArticleController.getAll); | ||
router.get('/:article_slug', authenticateUser, ArticleController.getSpecific); | ||
router.put('/:article_slug', authenticateUser, ArticleController.update); | ||
router.delete('/:article_slug', authenticateUser, ArticleController.delete); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.