Skip to content

Commit

Permalink
ft(add_image): enable image upload for article
Browse files Browse the repository at this point in the history
- add middlewares to generate slug for articles
- create functions to validate article details
- update migrations file for articles
- create middleware to add and upload images
[Delivers #164139686]
  • Loading branch information
kleva-j committed Mar 12, 2019
1 parent 10ad7ec commit e5e143e
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 19 deletions.
14 changes: 10 additions & 4 deletions controllers/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ export default class ArticleController {
* @returns {Object} class instance
*/
static async createArticle(req, res) {
const { title, description, body } = req.body;
const images = req.images || [];
const {
title, description, body, slug
} = req.body;
const { id } = req.user;
const taglist = req.body.taglist || [];
try {
const user = await User.findOne(id, { where: { id } });
if (user) {
const result = await Article.create({ title, description, body });
const userExist = await User.findOne({ where: { id } });
if (userExist) {
const result = await Article.create({
title, description, body, slug, images, taglist
});
return res.status(201).json({
success: true,
message: 'New article created successfully',
Expand Down
2 changes: 1 addition & 1 deletion helpers/trimArticle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ export default (req, res, next) => {
if (title) req.title = title.trim();
if (description) req.description = description.trim();
if (body) req.body = body.trim();
return next;
return next();
};
28 changes: 28 additions & 0 deletions middleware/addImage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import multer from 'multer';
import cloudinary from 'cloudinary';
import cloudinaryStorage from 'multer-storage-cloudinary';

cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET
});

const storage = cloudinaryStorage({
cloudinary,
folder: 'demo',
allowedFormats: ['jpg', 'jpeg', 'png', 'gif'],
});

const imageParser = multer({ storage }).array('articleImages', 5);

export default async (req, res, next) => {
await imageParser(req, res, (error) => {
if (error) return res.json({ success: false, error });
if (req.files) {
req.images = req.files.map(image => image.url);
return next();
}
return next();
});
};
9 changes: 5 additions & 4 deletions middleware/generateSlug.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { User } from '../models';
import { Article } from '../models';
import generateSlug from '../helpers/slug';

export default async (req, res, next) => {
const { title } = req.body;
const slug = generateSlug(req.body.title);
try {
const user = await User.findOne({ where: { slug } });
req.slug = user ? (generateSlug(title)) : slug;
const articleExist = await Article.findOne({ where: { slug } });
req.body.slug = articleExist ? (generateSlug(title)) : slug;
req.body.slug = slug;
} catch (error) {
return res.status(500).json({ success: false, error: [error.message] });
}
return next;
return next();
};
6 changes: 3 additions & 3 deletions middleware/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ export const validateArticle = [
.exists()
.withMessage('Article should have a title.')
.isLength({ min: 6 })
.withMessage('Article must be at least 6 characters long.'),
.withMessage('Title should be at least 6 characters long.'),

check('description')
.exists()
.withMessage('Article should have a description.')
.isLength({ min: 6 })
.withMessage('Description must be at least 6 characters long.'),
.withMessage('Description should be at least 6 characters long.'),

check('body')
.exists()
.withMessage('Article should have a body.')
.isLength({ min: 6 })
.withMessage('Article should have a body with at least 6 characters long.')
.withMessage('Article should have a body with at least 6 characters.'),
];
4 changes: 4 additions & 0 deletions migrations/20190305085324-create-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ module.exports = {
msg: 'Article should have a body'
}
},
images: {
type: Sequelize.ARRAY(Sequelize.STRING),
defaultValue: [],
},
taglist: {
type: Sequelize.ARRAY(Sequelize.STRING),
defaultValue: []
Expand Down
4 changes: 4 additions & 0 deletions models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ module.exports = (sequelize, DataTypes) => {
msg: 'Article should have a body'
}
},
images: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: []
},
taglist: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: []
Expand Down
132 changes: 132 additions & 0 deletions package-lock.json

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

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"babel-preset-es2015": "^6.24.1",
"bcrypt": "^3.0.4",
"body-parser": "^1.18.3",
"cloudinary": "^1.13.2",
"cors": "^2.8.4",
"dotenv": "^6.2.0",
"ejs": "^2.6.1",
Expand All @@ -39,6 +40,8 @@
"mongoose": "^5.4.16",
"mongoose-unique-validator": "^2.0.1",
"morgan": "^1.9.0",
"multer": "^1.4.1",
"multer-storage-cloudinary": "^2.2.1",
"nodemailer": "^5.1.1",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
Expand Down
9 changes: 8 additions & 1 deletion routes/v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import express from 'express';
import UserController from '../controllers/user';
import ProfileController from '../controllers/profile';
import Auth from '../middleware/auth';
import addImages from '../middleware/addImage';
import generateSlug from '../middleware/generateSlug';
import isUserVerified from '../middleware/verifyUser';
import ArticleController from '../controllers/articles';
import {
Expand All @@ -27,6 +29,11 @@ apiRoutes.route('/verify/:verificationId')
.get(UserController.verifyAccount);

apiRoutes.route('/articles')
.post(Auth.verifyUser, isUserVerified, validateArticle, returnValidationErrors, createArticle);
.post(Auth.verifyUser, isUserVerified,
addImages,
validateArticle,
returnValidationErrors,
generateSlug,
createArticle);

export default apiRoutes;
13 changes: 7 additions & 6 deletions test/article.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ describe('ARTICLES', () => {
expect(success).to.be.equal(false);
expect(errors).to.be.an('Array');
expect(errors[0]).to.be.equal('Article should have a title.');
expect(errors[1]).to.be.equal('Article should have a description.');
expect(errors[2]).to.be.equal('Description must be at least 6 characters long.');
expect(errors[1]).to.be.equal('Title should be at least 6 characters long.');
expect(errors[2]).to.be.equal('Article should have a description.');
done(err);
});
});
Expand All @@ -110,10 +110,11 @@ describe('ARTICLES', () => {
expect(success).to.be.equal(false);
expect(errors).to.be.an('Array');
expect(errors[0]).to.be.equal('Article should have a title.');
expect(errors[1]).to.be.equal('Article should have a description.');
expect(errors[2]).to.be.equal('Description must be at least 6 characters long.');
expect(errors[3]).to.be.equal('Article should have a body.');
expect(errors[4]).to.be.equal('Article should have a body with at least 6 characters long.');
expect(errors[1]).to.be.equal('Title should be at least 6 characters long.');
expect(errors[2]).to.be.equal('Article should have a description.');
expect(errors[3]).to.be.equal('Description should be at least 6 characters long.');
expect(errors[4]).to.be.equal('Article should have a body.');
expect(errors[5]).to.be.equal('Article should have a body with at least 6 characters.');
done(err);
});
});
Expand Down

0 comments on commit e5e143e

Please sign in to comment.