Skip to content

Commit

Permalink
Merge d18f5ae into 3e61d6e
Browse files Browse the repository at this point in the history
  • Loading branch information
henperi committed Dec 14, 2018
2 parents 3e61d6e + d18f5ae commit 3932077
Show file tree
Hide file tree
Showing 23 changed files with 503 additions and 78 deletions.
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",
"editor.tabSize": 2,
"files.eol": "\n",
"javascript.format.enable": false,
"prettier.eslintIntegration": true,
"editor.formatOnSave": true
}
90 changes: 76 additions & 14 deletions controllers/ArticlesController.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import model from '../models';
import models from '../models';
import StatusResponse from '../helpers/StatusResponse';
import {
checkIdentifier,
pageInfo,
checkTitle,
checkUser
checkUser,
calcReadingTime,
createNewTags
} from '../helpers/articleHelper';

const { articles } = model;
const { articles: Article, tags: Tag } = models;

/**
* @description ArticlesController class
*/
Expand All @@ -20,29 +23,59 @@ class ArticlesController {
*/
static async create(req, res) {
const { userId } = res.locals.user;
const {
tags, body, title, description, image
} = req.body;

try {
const articleTitle = await articles.findOne({
const articleTitle = await Article.findOne({
where: {
title: req.body.title
}
});
const articleSlug = checkTitle(req.body.title, articleTitle);
const newArticle = await articles.create({
const readingTime = calcReadingTime(body);

const newArticle = await Article.create({
userId,
title: req.body.title,
description: req.body.description,
title,
description,
body,
image,
readingTime,
slug: articleSlug,
body: req.body.body,
image: req.body.image
});
if (!newArticle) {
const payload = { message: 'Could not create article, try again' };
return StatusResponse.notfound(res, payload);
}

return StatusResponse.created(res, {
message: 'Article successfully created',
article: newArticle
if (tags) {
const createTags = await createNewTags(tags);
await newArticle.addTags(createTags);
}

const createdArticle = await Article.findOne({
where: { id: newArticle.id },
include: {
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
}
}
});

if (!createdArticle) {
const payload = { message: 'Article created' };
return StatusResponse.notfound(res, payload);
}
const payload = { article: createdArticle, message: 'Article successfully created' };
return StatusResponse.created(res, payload);
} catch (error) {
return StatusResponse.internalServerError(res, {
message: `something went wrong, please try again.... ${error}`
message: `Something went wrong, please try again.... ${error}`
});
}
}
Expand All @@ -54,13 +87,23 @@ class ArticlesController {
* @returns {object} Returned object
*/
static async list(req, res) {
const { articles } = models;

const {
size, page = 1, order = 'ASC', orderBy = 'createdAt'
} = req.query;

try {
const { limit, offset } = pageInfo(page, size);
const fetchArticles = await articles.findAndCountAll({
include: {
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
}
},
limit,
offset,
order: [[orderBy, order]]
Expand Down Expand Up @@ -88,11 +131,21 @@ class ArticlesController {
* @returns {object} Returned object
*/
static async get(req, res) {
const { articles } = models;

const paramsSlug = checkIdentifier(req.params.identifier);

try {
const fetchArticle = await articles.findOne({
where: { ...paramsSlug }
where: { ...paramsSlug },
include: {
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
}
},
});
return StatusResponse.success(res, {
message: 'success',
Expand All @@ -112,6 +165,8 @@ class ArticlesController {
* @returns {object} Returned object
*/
static async update(req, res) {
const { articles } = models;

const { userId } = res.locals.user;
const paramsSlug = checkIdentifier(req.params.identifier);
try {
Expand All @@ -133,6 +188,11 @@ class ArticlesController {
returning: true,
plain: true
});
const { tags } = req.body;
if (tags) {
const createTags = await createNewTags(tags);
await updatedArticle.setTags(createTags);
}

return StatusResponse.success(res, {
message: 'Article updated successfully',
Expand All @@ -152,6 +212,8 @@ class ArticlesController {
* @returns {object} Returned object
*/
static async archive(req, res) {
const { articles } = models;

const { userId } = res.locals.user;
const paramsSlug = checkIdentifier(req.params.identifier);
try {
Expand Down
62 changes: 49 additions & 13 deletions helpers/articleHelper.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import slug from './generateSlug';
import models from '../models';

const checkIdentifier = paramsSlug => (
Number.isInteger(parseInt(paramsSlug, 10))
? { id: paramsSlug }
: { slug: paramsSlug }
);
const { tags: Tag } = models;

const checkIdentifier = paramsSlug => (Number.isInteger(parseInt(paramsSlug, 10))
? {
id: paramsSlug
}
: {
slug: paramsSlug
});

const pageInfo = (page, size) => {
const currentPage = Math.abs(Number(page)) || 1;
Expand All @@ -15,26 +20,57 @@ const pageInfo = (page, size) => {
offset = (currentPage - 1) * limit;

if (Number.isNaN(offset)) offset = 10;
return { limit, offset };
return {
limit,
offset
};
};

const checkTitle = (title, articleTitle) => {
let articleSlug;
if (!articleTitle) {
articleSlug = slug(title);
} else {
articleSlug = `${slug(title)}-${(
Math.floor(Math.random() * (25 ** 6))).toString(36)}`;
articleSlug = `${slug(title)}-${Math.floor(Math.random() * (25 ** 6)).toString(36)}`;
}
return articleSlug;
};


const checkUser = (article, userId) => article.userId === userId;

/**
* @description This method is used to calculate an articles reading time
* @param {String} bodyText - sentences, phrases, paragraphs etc
* @returns {String} readingTime
*/
const calcReadingTime = (bodyText) => {
const matches = bodyText.match(/\S+/g);
const numberOfWords = matches ? matches.length : 0;
const averageWPM = 225;
const readingTime = Math.ceil(numberOfWords / averageWPM);

return readingTime > 1 ? `${readingTime} mins read` : `${readingTime} min read`;
};

/**
* @description This method is used to create new tags abd return the created tag ids
* @param {Array} tags - An array of tags <= 5
* @param {Object} article - the recently created sequelize article
* @returns {Object} object - the sequelize object of article tags
*/
const createNewTags = async (tags) => {
let tagList = tags.map(async thisTag => Tag.findOrCreate({
where: {
tagName: thisTag
}
}));

tagList = await Promise.all(tagList);
const tagIds = tagList.map(pickedTag => pickedTag[0].id);

return tagIds;
};

export {
checkIdentifier,
pageInfo,
checkTitle,
checkUser
checkIdentifier, pageInfo, checkTitle, checkUser, calcReadingTime, createNewTags
};
2 changes: 1 addition & 1 deletion helpers/mailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const mailer = {
html: emailBody
};

sgMail
return sgMail
.send(msg)
.then(() => true)
.catch(() => false);
Expand Down
96 changes: 96 additions & 0 deletions helpers/statusResponse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @description - This class is all about server response
* @returns {class} Response
*/
class StatusResponse {
/**
* @description - success response
* @param {object} res
* @param {object} data
* @returns {object} Success
*/
static success(res, data) {
return res.status(200).json(data);
}

/**
* @description - Not found response
* @param {object} res
* @param {object} data
* @returns {object} Not found
*/
static notfound(res, data) {
return res.status(404).json(data);
}

/**
* @description - Internal server error response
* @param {object} res
* @param {object} data
* @returns {object} Error
*/
static internalServerError(res, data) {
return res.status(500).json(data);
}

/**
* @description - bad request
* @param {object} res
* @param {object} data
* @returns {object} Error
*/
static badRequest(res, data) {
return res.status(400).json(data);
}

/**
* @description - created response
* @param {object} res
* @param {object} data
* @returns {object} Created
*/
static created(res, data) {
return res.status(201).json(data);
}

/**
* @description - Unauthorized credentials
* @param {object} res
* @param {object} data
* @returns {object} Unauthorized
*/
static unauthorized(res, data) {
return res.status(401).json(data);
}

/**
* @param {object} res
* @param {object} data
* @returns {object} json data
*/
static conflict(res, data) {
return res.status(409).json(data);
}

/**
* @description - forbidden credentials
* @param {object} res
* @param {object} data
* @returns {object} forbidden
*/
static forbidden(res, data) {
return res.status(403).json(data);
}

/**
* @description - no content
* @param {object} res
* @param {object} data
* @returns {object} forbidden
*/
static noContent(res, data) {
return res.status(204).json(data);
}
}

export default StatusResponse;
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import validator from 'express-validator';
import passport from 'passport';

import {
auth, profiles, user, password, twitterRouter, article, comment, ratings
auth, profiles, user, password, twitterRouter, articles, comment, ratings
} from './routes';

import logger from './config/logger';
Expand All @@ -28,7 +28,7 @@ app.use('/api/v1/profiles', profiles);
app.use('/api/v1/password', password);
app.use('/api/v1/users', user);
app.use('/api/v1/articles', comment);
app.use('/api/v1/articles', article);
app.use('/api/v1/articles', articles);
app.use('/api/v1/ratings', ratings);
passportAuth();

Expand Down
Loading

0 comments on commit 3932077

Please sign in to comment.