-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from andela/ft_159206054_user_create_articles
#159206054 User can create articles
- Loading branch information
Showing
24 changed files
with
1,363 additions
and
57 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 |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import cloudinary from 'cloudinary'; | ||
|
||
// configure cloudinary to be able to upload image | ||
cloudinary.config({ | ||
cloud_name: process.env.CLOUD_NAME, | ||
api_key: process.env.API_KEY, | ||
api_secret: process.env.API_SECRET, | ||
}); | ||
|
||
export default cloudinary; |
This file was deleted.
Oops, something went wrong.
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,163 @@ | ||
import cloudinary from '../config/cloudinary'; | ||
import Utilities from '../helpers/utilities'; | ||
import { Article, User } from '../models'; | ||
import createArticleHelper from '../helpers/createArticleHelper'; | ||
|
||
/** | ||
* Article class for users | ||
* @param {method} createArticle - Create article | ||
* @param {method} getArticle - Get a single article | ||
* @param {method} editArticle update a single article | ||
*/ | ||
class ArticleController { | ||
/** | ||
* Create an article for a user | ||
* @param {object} req - The request object | ||
* @param {object} res - The response object sent to user | ||
* @return {object} A object containing created articles. | ||
*/ | ||
static createArticle(req, res) { | ||
const { | ||
title, description, body, tagList, imageData, | ||
} = req.body.article; | ||
|
||
const { userId } = req; | ||
|
||
const articleObject = { | ||
title, description, body, tagList, imageData, userId | ||
}; | ||
/** | ||
* check if image was provided in the request | ||
* upload the image to cloudinary, save the article | ||
* with the cloudinary URL in database but if an error | ||
* was encountered from cloudinary go ahead and create the article | ||
*/ | ||
if (imageData) { | ||
return cloudinary.v2.uploader.upload(imageData, { tags: 'basic_sample' }) | ||
.then(image => createArticleHelper(res, articleObject, image.url)) | ||
.catch(() => createArticleHelper(res, articleObject)); | ||
} | ||
|
||
// if there no image was provided go ahead to create the article | ||
return createArticleHelper(res, articleObject); | ||
} | ||
|
||
/** | ||
* get an article using slug as query parameter | ||
* @param {object} req - request object | ||
* @param {object} res - response object | ||
* @param {function} next - next function | ||
* @returns {object} - the found article from database or error if not found | ||
*/ | ||
static getArticle(req, res, next) { | ||
const { slug } = req.params; | ||
|
||
return Article | ||
.findOne({ | ||
where: { slug, }, | ||
include: [{ | ||
model: User, | ||
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] } | ||
}], | ||
attributes: { exclude: ['userId'] } | ||
}) | ||
.then((article) => { | ||
// if the article does not exist | ||
if (!article) { | ||
return res.status(404).json({ | ||
errors: { | ||
body: [ | ||
'Ooops! the article cannot be found.' | ||
] | ||
} | ||
}); | ||
} | ||
|
||
return res.status(200).json({ article }); | ||
}) | ||
.catch(next); | ||
} | ||
|
||
/** | ||
* get all articles created | ||
* @param {object} req - request object | ||
* @param {object} res - response object | ||
* @param {function} next - next function | ||
* @returns {object} - the found article from database or empty if not found | ||
*/ | ||
static listAllArticles(req, res, next) { | ||
return Article | ||
.findAll({ | ||
include: [{ | ||
model: User, | ||
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] } | ||
}], | ||
attributes: { exclude: ['userId'] } | ||
}) | ||
.then((articles) => { | ||
// check if there was no article created | ||
if (articles.length === 0) { | ||
return res.status(200).json({ | ||
message: 'No articles created', | ||
articles, | ||
}); | ||
} | ||
|
||
return res.status(200).json({ articles, articlesCount: articles.length }); | ||
}) | ||
.catch(next); | ||
} | ||
|
||
/** | ||
* @function editArticle | ||
* @summary: API controller to handle requests to edit an article | ||
* @param {object} req: request object | ||
* @param {object} res: response object | ||
* @param {function} next - next function | ||
* @returns {object} api response: article object for | ||
* successful requests, or error object for | ||
* requests that fail | ||
*/ | ||
static editArticle(req, res, next) { | ||
const { title, description, body } = req.body.article; | ||
const { count } = req; | ||
const { slug } = req.params; | ||
return Article.update({ | ||
title, | ||
description, | ||
body, | ||
updatedCount: Utilities.increaseCount(count) | ||
}, { | ||
where: { | ||
slug, | ||
}, | ||
returning: true, | ||
plain: true | ||
}) | ||
.then(result => res.status(200).json({ | ||
success: true, | ||
article: result[1] | ||
})) | ||
.catch(next); | ||
} | ||
|
||
/** | ||
* @function deleteArticle | ||
* @summary: API controller to handle requests to delete an article | ||
* @param {object} req: request object | ||
* @param {object} res: response object | ||
* @param {function} next - next function | ||
* @returns {object} api response: article object for | ||
* successful requests, or error object for requests that fail | ||
*/ | ||
static deleteArticle(req, res, next) { | ||
const { slug } = req.params; | ||
Article.destroy({ | ||
where: { slug } | ||
}) | ||
.then(() => res.status(200).json({ message: 'Article successfully deleted' })) | ||
.catch(next); | ||
} | ||
} | ||
|
||
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
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,38 @@ | ||
import generateUniqueSlug from './generateUniqueSlug'; | ||
import { Article, User } from '../models'; | ||
|
||
|
||
/** | ||
* @description an helper function to help create article in database | ||
* @param {object} res - response object | ||
* @param {object} articleObject - contains extracted article fields | ||
* @param {string} imageUrl - image url from cloudinary | ||
* @returns object - the created article from the database | ||
*/ | ||
|
||
const createArticleHelper = (res, articleObject, imageUrl = null) => { | ||
const { | ||
title, description, body, tagList, userId | ||
} = articleObject; | ||
|
||
return Article | ||
.create({ | ||
slug: generateUniqueSlug(title), | ||
title, | ||
description, | ||
body, | ||
userId, | ||
tagList, | ||
imageUrl, | ||
}) | ||
.then(article => Article.findById(article.id, { | ||
include: [{ | ||
model: User, | ||
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] } | ||
}], | ||
attributes: { exclude: ['userId'] } | ||
})) | ||
.then(article => res.status(201).json({ article })); | ||
}; | ||
|
||
export default createArticleHelper; |
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 slugify from 'slugify'; | ||
import cuid from 'cuid'; | ||
|
||
/** create a string as slug to make articles unique | ||
* @param {string} title - title of the article from request body | ||
* @returns {string} slug - timestamped string with randomly generated slug to avoid collision | ||
*/ | ||
const generateUniqueSlug = (title) => { | ||
const sluggedTitle = slugify(title); | ||
const slug = `${sluggedTitle}-${cuid()}`; | ||
return slug.toLowerCase(); | ||
}; | ||
|
||
export default generateUniqueSlug; |
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
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,45 @@ | ||
import db from '../models/index'; | ||
|
||
export const articleExists = (req, res, next) => { | ||
const { slug } = req.params; | ||
db.Article.find({ | ||
where: { slug } | ||
}) | ||
.then((foundArticle) => { | ||
if (!foundArticle) { | ||
return res.status(404).json({ | ||
errors: { | ||
body: ['this article does not exist'] | ||
} | ||
}); | ||
} | ||
if (req.userId !== foundArticle.userId) { | ||
return res.status(404).json({ | ||
errors: { | ||
body: ['Not enough permission to perform this operartion'] | ||
} | ||
}); | ||
} | ||
req.count = foundArticle.updatedCount; | ||
next(); | ||
}) | ||
.catch((err) => { | ||
res.status(500).json({ | ||
error: err | ||
}); | ||
}); | ||
}; | ||
|
||
export const checkCount = (req, res, next) => { | ||
const { count } = req; | ||
if (count > 2) { | ||
return res.status(403).json({ | ||
errors: { | ||
body: [ | ||
'this article has exceeded its edit limit' | ||
] | ||
} | ||
}); | ||
} | ||
next(); | ||
}; |
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.