Skip to content

Commit

Permalink
Merge 6bb1018 into 8c2e6ed
Browse files Browse the repository at this point in the history
  • Loading branch information
henperi authored Dec 20, 2018
2 parents 8c2e6ed + 6bb1018 commit f916f75
Show file tree
Hide file tree
Showing 27 changed files with 714 additions and 105 deletions.
59 changes: 38 additions & 21 deletions controllers/ArticlesController.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {
calcReadingTime
} from '../helpers/articleHelper';
import ReadingStatsModelQuery from '../lib/ReadingStatsModelQuery';
import eventEmitter from '../helpers/eventEmitter';

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

/**
* @description ArticlesController class
Expand All @@ -29,7 +30,7 @@ class ArticlesController {
tags, body, title, description, image
} = req.body;
try {
const articleTitle = await ArticleQueryModel.getArticleByTitle();
const articleTitle = await ArticleQueryModel.getArticleByTitle(req.body.title);
const articleSlug = checkTitle(req.body.title, articleTitle);
const readingTime = calcReadingTime(body);
const newArticle = await Article.create({
Expand All @@ -39,7 +40,7 @@ class ArticlesController {
body,
image,
readingTime,
slug: articleSlug,
slug: articleSlug
});

if (tags) {
Expand Down Expand Up @@ -69,14 +70,16 @@ class ArticlesController {
} = req.query;
try {
const articles = await Article.findAndCountAll({
include: {
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
include: [
{
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
}
}
},
],
order: [[orderBy, order]]
});
if (articles.length === 0) {
Expand Down Expand Up @@ -108,14 +111,20 @@ class ArticlesController {
try {
const fetchArticle = await Article.findOne({
where: { ...whereFilter },
include: {
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
include: [
{
model: Tag,
as: 'tags',
attributes: ['tagName'],
through: {
attributes: []
}
},
{
model: HighlightedText,
as: 'highlightedPortions'
}
},
]
});
if (req.app.locals.user) {
const { userId } = req.app.locals.user;
Expand All @@ -127,7 +136,7 @@ class ArticlesController {
}
return StatusResponse.success(res, {
message: 'success',
article: fetchArticle,
article: fetchArticle
});
} catch (error) {
return StatusResponse.internalServerError(res, {
Expand Down Expand Up @@ -164,8 +173,8 @@ class ArticlesController {

const updatedArticle = await articles.update(req.body, {
where: { ...whereFilter },
fields: ['title', 'body', 'readingTime', 'description', 'image', 'isPublished'],
returning: true,
fields: ['slug', 'title', 'body', 'readingTime', 'description', 'image', 'isPublished'],
returning: true
});

if (tags) {
Expand All @@ -174,8 +183,16 @@ class ArticlesController {
updatedArticle['1']['0'].dataValues.tags = tags;
}

// Use an event emitter to call updateHighlights
eventEmitter.emit(
'UPDATEHIGHLIGHT',
article.highlightedPortions,
updatedArticle[1][0].body,
userId
);

return StatusResponse.success(res, {
message: 'Article updated successfully',
message: 'Article updated successfully, some highlights were adjusted or removed',
article: updatedArticle
});
} catch (error) {
Expand Down
14 changes: 10 additions & 4 deletions controllers/AuthController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import StatusResponse from '../helpers/StatusResponse';
import UserModelQuery from '../lib/UserModelQuery';
import getToken from '../helpers/getToken';
import mailer from '../helpers/mailer';
import helper from '../helpers/helper';
import generateEmailToken from '../helpers/generateEmailToken';

/**
* Signup validation class
Expand All @@ -23,7 +23,7 @@ class AuthController {
const genSalt = bcrypt.genSaltSync(8);
const hashPassword = bcrypt.hashSync(password, genSalt);

const emailToken = helper.generateEmailToken(email);
const emailToken = generateEmailToken(email);

const link = `http://${req.headers.host}/api/v1/users/verify-email/${emailToken}`;

Expand Down Expand Up @@ -101,7 +101,10 @@ class AuthController {
};
return StatusResponse.notfound(res, payload);
}
const { id, profile: { username } } = user;
const {
id,
profile: { username }
} = user;
const token = getToken(id, username);
user.dataValues.password = undefined;
const payload = {
Expand All @@ -124,7 +127,10 @@ class AuthController {
* @return {object} login response to user
*/
static socialAuth(req, res) {
const { id, profile: { username } } = req.user;
const {
id,
profile: { username }
} = req.user;
const token = getToken({ id, username });
const payload = {
message: 'user logged in succesfully',
Expand Down
154 changes: 154 additions & 0 deletions controllers/HighlightsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import models from '../models';
import StatusResponse from '../helpers/StatusResponse';
import highlightsLogic from '../lib/highlightsLogic';

const { HighlightedText, users: User } = models;
const { updateHighlights } = highlightsLogic;
/**
* Highlights Controller
* @description This controller creates and stores highlighted texts with or without comments when
* a portion of an article is selected.
* @description It also fetches all the highlighted texts belonging to an article
* @description It also fetches all the comments belonging to a highlighted section of an article
* when the highlighted section is clicked
*/
class HighlightsController {
/**
* @description Method to create a highlights alone
* @param {object} req
* @param {object} res
* @returns {object} The reponse object
*/
static async create(req, res) {
const { userId } = req.app.locals.user;
const { articleId } = req.params;
const { highlightedText, comment } = req.body;
const { body } = req.app.locals.article;

const startIndex = req.body.startIndex || body.indexOf(highlightedText);
const stopIndex = req.body.stopIndex || startIndex + highlightedText.length;
const highlightId = `${startIndex}-${stopIndex}`;

try {
const createdHighlight = await HighlightedText.create({
highlightId,
startIndex,
stopIndex,
comment,
articleId,
userId,
text: highlightedText
});

const payload = {
success: true,
message: 'You successfully highlighted a section of this article',
createdHighlight
};
return StatusResponse.created(res, payload);
} catch (error) {
return StatusResponse.internalServerError(res, {
message: `Something went wrong, please try again.... ${error}`
});
}
}

/**
* @description Method to fetch all highlights belonging to an article
* @param {object} req
* @param {object} res
* @returns {object} The reponse object
*/
static async fetchByArticleId(req, res) {
const { articleId } = req.params;

try {
const highlightedPortions = await HighlightedText.findAll({
where: {
articleId,
stillExist: true
},
group: ['highlightId', 'id']
});

const payload = {
success: true,
message: 'All highlights belonging to this article has been fetched successfully',
highlightedPortions
};
return StatusResponse.success(res, payload);
} catch (error) {
return StatusResponse.internalServerError(res, {
message: `Something went wrong, please try again.... ${error}`
});
}
}

/**
* @description Method to fetch all comments belonging to a highlight
* @param {object} req
* @param {object} res
* @returns {object} The reponse object
*/
static async fetchAHighlightsComments(req, res) {
const { articleId, highlightId } = req.params;

try {
const comments = await HighlightedText.findAll({
where: {
articleId,
highlightId,
comment: {
$ne: null
}
},
include: {
model: User,
attributes: ['id', 'email']
},
attributes: ['comment']
});

const payload = {
success: true,
message: 'All comments belonging to this highlight has been fetched successfully',
highlightId,
comments
};
return StatusResponse.success(res, payload);
} catch (error) {
return StatusResponse.internalServerError(res, {
message: `Something went wrong, please try again.... ${error}`
});
}
}

/**
* @description Method to update an articles highlighted indexes if the articles body is updated
* @param {object} req
* @param {object} res
* @returns {object} The reponse object
*/
static async update(req, res) {
const { userId } = req.app.locals.user;
const { highlightedPortions } = req.body;
const { body } = req.app.locals.article;

try {
const updatedPortions = await updateHighlights(highlightedPortions, body, userId);

const payload = {
success: true,
message: 'Highlight indexes have been updated',
updatedPortions
};
return StatusResponse.success(res, payload);
} catch (error) {
return StatusResponse.internalServerError(res, {
message: `Something went wrong, please try again.... ${error}`
});
}
}
}

export default HighlightsController;
15 changes: 15 additions & 0 deletions events/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import eventEmitter from '../helpers/eventEmitter';

import highlightsLogic from '../lib/highlightsLogic';

const { updateHighlights } = highlightsLogic;

const events = {
register() {
eventEmitter.on('UPDATEHIGHLIGHT', (highlightedPortions, body, userId) => {
updateHighlights(highlightedPortions, body, userId);
});
}
};

export default events;
15 changes: 6 additions & 9 deletions helpers/articleHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import slugify from 'slugify';
import models from '../models';

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

const checkTitle = (title, articleTitle) => {
let articleSlug;
Expand All @@ -27,7 +25,7 @@ const checkUser = (article, userId) => article.userId === userId;
* @returns {Object} object - the sequelize object of article tags
*/
const createNewTags = async (tags) => {
let tagList = tags.map(async thisTag => Tag.findOrCreate({
let tagList = await tags.map(async thisTag => Tag.findOrCreate({
where: {
tagName: thisTag
}
Expand All @@ -39,12 +37,11 @@ const createNewTags = async (tags) => {
return tagIds;
};


const calcReadingTime = (bodyText) => {
const matches = bodyText.match(/\S+/g);
const numberOfWords = matches ? matches.length : 0;
const averageWPM = 225;
const readingTime = Math.ceil(numberOfWords / averageWPM);
const averageWordsPerMinute = 225;
const readingTime = Math.ceil(numberOfWords / averageWordsPerMinute);

return readingTime > 1 ? `${readingTime} mins read` : `${readingTime} min read`;
};
Expand Down
10 changes: 10 additions & 0 deletions helpers/eventEmitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import EventEmitter from 'events';
/**
* myEmitter
* class MyEmitter
*/
class MyEmitter extends EventEmitter {}

const eventEmitter = new MyEmitter();

export default eventEmitter;
Loading

0 comments on commit f916f75

Please sign in to comment.