Skip to content

Commit

Permalink
Merge pull request #20 from andela/feature/162727469/create-articles
Browse files Browse the repository at this point in the history
#162727469 Create articles
  • Loading branch information
Bamidele Daniel committed Jan 22, 2019
2 parents 25383ff + da90ffd commit bb2ef45
Show file tree
Hide file tree
Showing 11 changed files with 678 additions and 69 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
SECRET=SECRET
EMAILHOST=smtp.elasticemail.com
EMAILPORT=2525
EMAILUSER=email@andela.com
Expand All @@ -6,3 +7,4 @@ DB_NAME=DB_DATABASE_NAME
DB_NAME_TEST=TEST_DB
DB_USER=DB_USERNAME
DB_PASSWORD=DB_PASSWORD
DEFAULT_ARTICLE_IMAGE='https://images.pexels.com/photos/1047540/pexels-photo-1047540.jpeg?dl&fit=crop&crop=entropy&w=1280&h=853'
132 changes: 66 additions & 66 deletions package-lock.json

Large diffs are not rendered by default.

276 changes: 276 additions & 0 deletions src/controllers/ArtsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import models from '../db/models';
import { Response, Slugify } from '../helpers/index';

const { Art, Media, Category } = models;

let response;

/** Arts Controller Class */
class ArtsController {
/**
* @desc POST /api/v1/articles
* @param {object} req
* @param {object} res
* @memberof ArtsController
* This will receive a media object containing a list of media files
* @returns {Object} ARTicle details
*/
static async create(req, res) {
try {
const defaultStatus = 0;
const validationErrors = [];

const { id: artistId } = req.verifyUser;

const {
title, description, categoryId, media,
} = req.body;

const mediaFilesArray = JSON.parse(media);

req.check('title', 'Title is required').notEmpty();
req.check('description', 'Description should be longer').notEmpty()
.isLength({ min: 15 });

const errors = req.validationErrors();

if (errors) {
errors.map(err => validationErrors.push(err.msg));
response = new Response(
'Not Ok',
400,
'Validation Errors Occurred',
{ validationErrors }
);
return res.status(response.code).json(response);
}

const slugifiedTitle = Slugify.slugify(title);

const checkCategory = await Category.findOne({ where: { id: 1 } });
if (!checkCategory) {
await Category.create({ categoryName: 'Architecture' });
}

const createArticle = await Art
.create({
artistId,
slug: slugifiedTitle,
title,
description,
categoryId,
featuredImg: mediaFilesArray[0].url
|| process.env.DEFAULT_ARTICLE_IMAGE,
status: defaultStatus
});

const {
id: artId,
title: artTitle,
description: artDescription,
featuredImg: artFeaturedImg,
categoryId: artCategoryId
} = createArticle.dataValues;

if (mediaFilesArray.length > 0) {
let cnt = 0;
await mediaFilesArray.some((mediaFile) => {
cnt += 1;
Media.create({
artId,
contentUrl: mediaFile.url,
mediaType: mediaFile.extension
});
return cnt === 7;
});
}

response = new Response(
'Ok',
201,
'Article created successfully',
{
artId,
artTitle,
slugifiedTitle,
artDescription,
artFeaturedImg,
artCategoryId
}
);

return res.status(response.code).json(response);
} catch (err) {
response = new Response(
'Not Ok',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}

/**
* @desc PUT /api/v1/articles
* @param {object} req
* @param {object} res
* @memberof ArtsController
* @returns {Object} ARTicle details
*/
static async update(req, res) {
try {
const validationErrors = [];

const { id: artistId } = req.verifyUser;
const { slug } = req.params;

const artToUpdate = await Art.findOne({
where: { slug }
});

if (!artToUpdate) {
response = new Response(
'Not Found',
404,
'Sorry. Article Not Found'
);
return res.status(response.code).json(response);
}

if (artistId !== artToUpdate.artistId) {
response = new Response(
'Not Ok',
403,
'Unauthorized to Edit Article',
{}
);
return res.status(response.code).json(response);
}

const {
title, description, categoryId, media,
} = req.body;

const mediaFilesArray = JSON.parse(media);

req.check('title', 'Title is required').notEmpty();
req.check('description', 'Description should be longer').notEmpty()
.isLength({ min: 15 });

const errors = req.validationErrors();
if (errors) {
errors.map(err => validationErrors.push(err.msg));
response = new Response(
'Not Ok',
400,
'Validation Errors Occurred',
{ validationErrors }
);
return res.status(response.code).json(response);
}

const slugifiedTitle = Slugify.slugify(title);

const updatedArticle = {
id: artToUpdate.id,
title: title || artToUpdate.title,
slug: slugifiedTitle,
description: description || artToUpdate.description,
categoryId: categoryId || artToUpdate.categoryId,
featuredImg: mediaFilesArray[0].url || artToUpdate.featuredImg,
createdAt: artToUpdate.createdAt
};

const mediaToDelete = await Media.destroy({
where: { artId: artToUpdate.id }
});

if (mediaToDelete) {
mediaFilesArray.splice(6);
await mediaFilesArray.forEach((mediaFile) => {
Media.create({
artId: artToUpdate.id,
contentUrl: mediaFile.url,
mediaType: mediaFile.extension
});
});
}

const updateArticleSuccess = await artToUpdate.update(updatedArticle);

response = new Response(
'Ok',
200,
'Article updated successfully',
updateArticleSuccess
);
return res.status(response.code).json(response);
} catch (err) {
response = new Response(
'Not ok',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}

/**
* @desc DELETE /api/v1/articles
* @param {object} req
* @param {object} res
* @memberof ArtsController
* @returns {Object} ARTicle details
*/
static async delete(req, res) {
try {
const { slug } = req.params;

const { id: artistId } = req.verifyUser;

const artToDelete = await Art.findOne({
where: { slug }
});

if (!artToDelete) {
response = new Response(
'Not Found',
404,
'Sorry. Article Not Found'
);
return res.status(response.code).json(response);
}

if (artistId !== artToDelete.artistId) {
response = new Response(
'Not Ok',
403,
'Unauthorized to Delete Article',
{}
);
return res.status(response.code).json(response);
}

const artDeleted = await Art.destroy({
where: { slug }
});

response = new Response(
'Ok',
200,
'Article deleted successfully',
{ artToDelete: artDeleted }
);
return res.status(response.code).json(response);
} catch (err) {
response = new Response(
'Not ok',
500,
`${err}`,
);
return res.status(response.code).json(response);
}
}
}

export default ArtsController;
2 changes: 2 additions & 0 deletions src/db/migrations/20190114160429-create-media.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module.exports = {
},
artId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Arts',
key: 'id',
Expand Down
15 changes: 15 additions & 0 deletions src/helpers/Slugify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import slug from 'slug';

/** Slugify Helper Class */
export default class Slugify {
/**
* Represents a slugify function.
* @param {string} string - The string to be slugified .
* @returns {string} slug
*/
static slugify(string) {
const uuid = Math.random().toString(36).substring(7);
string += `-${uuid}`;
return slug(string, { lower: true });
}
}
1 change: 0 additions & 1 deletion src/helpers/TokenAuthenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class TokenAuthenticate {
static async tokenVerify(req, res, next) {
const token = req.headers.authorization
|| req.headers['x-access-token'] || req.query.token || req.body.token;

if (!token) {
return res.status(401).send({
status: 'error',
Expand Down
8 changes: 7 additions & 1 deletion src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import EmailNotificationAPI from './EmailNotificationAPI';
import TokenAuthenticate from './TokenAuthenticate';
import Response from './response';
import Slugify from './Slugify';

export default {
export {
EmailNotificationAPI,
Slugify,
TokenAuthenticate,
Response
};
17 changes: 17 additions & 0 deletions src/routes/artsRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import express from 'express';
import ArtController from '../controllers/ArtsController';
import { TokenAuthenticate } from '../helpers/index';

const artsRoute = express.Router();

artsRoute.post('/', TokenAuthenticate.tokenVerify, ArtController.create);

artsRoute.put('/:slug', TokenAuthenticate.tokenVerify, ArtController.update);

artsRoute.delete(
'/:slug',
TokenAuthenticate.tokenVerify,
ArtController.delete
);

export default artsRoute;
2 changes: 2 additions & 0 deletions src/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express';
import swaggerJSDoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import artsRoute from './artsRoute';
import authRouter from './authRouter';
import commentRouter from './commentRouter';
import socialRouter from './socialRouter';
Expand All @@ -11,6 +12,7 @@ const swaggerSpec = swaggerJSDoc(require('../utils/swaggerConfig')

router.use('/auth', authRouter);
router.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
router.use('/arts', artsRoute);
router.use('/arts/comments/', commentRouter);
router.use('/auth', socialRouter);

Expand Down
Loading

0 comments on commit bb2ef45

Please sign in to comment.