Skip to content

Commit

Permalink
feat(Highlight): User can highlight an article text and make a comment
Browse files Browse the repository at this point in the history
Implement algorithm to highlight article text
Write unit texts
write controller to allow user to comment on the article
get all the comment on a highlight

[Delivers #167190463]
  • Loading branch information
cvjude committed Aug 9, 2019
1 parent ea90a88 commit ed2cd5f
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 15 deletions.
44 changes: 44 additions & 0 deletions server/controllers/articleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class ArticleController {
static async getAllArticles(req, res) {
const { searchQuery } = req.query;
const queryFilters = req.body;

let articles;
const { page, limit } = req.query;
if (!page && !limit) {
Expand Down Expand Up @@ -257,6 +258,49 @@ class ArticleController {
dislikes: totalDislikes
});
}

/**
* @static
* @description Allows a user to highlight a Article text and add an optional comment
* @param {*} req Request object
* @param {*} res Response object
* @returns {Object} Object containing the user comment, author, and timestaps
* @memberof CommentController
*/
static async highlightText(req, res) {
const { body: { highlight: { comment, id, slug } }, user } = req;
const post = await models.Article.findOne({
where: {
slug,
}
});
if (!post) return errorStat(res, 404, 'Post not found');
const newComment = {
highlightUser: user.id,
body: comment,
articleId: post.id,
highlightId: id,
authorId: user.id,
};
const userComment = await models.Comment.create(newComment);
await post.addComment(userComment);
const commentResponse = await models.Comment.findOne({
where: { id: userComment.id },
include: [
{
as: 'author',
model: models.User,
attributes: ['firstname', 'lastname', 'image', 'username']
}
],
attributes: {
exclude: [
'authorId'
]
}
});
return successStat(res, 201, 'comment', commentResponse);
}
}

export default ArticleController;
38 changes: 38 additions & 0 deletions server/controllers/commentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,42 @@ export default class CommentController {
if (userPosts.length <= 0) return successStat(res, 200, 'message', 'You have not made any post so far...');
return successStat(res, 200, 'data', userPosts);
}

/**
* @static
* @description allows a user add a comment to an already highlighted text
* @param {*} req Request object
* @param {*} res Response object
* @returns {Object} Object containing the user comment, author, and timestaps
* @memberof CommentController
*/
static async commentAhighligh(req, res) {
const { body: { highlight: { comment, id } }, user } = req;
const highlightedComment = await models.Comment.findOne({ where: { highlightId: id } });
if (!highlightedComment) return errorStat(res, 404, 'Post not found');
const { highlightUser, articleId, highlightId } = highlightedComment;
const newComment = {
highlightUser,
body: comment,
articleId,
highlightId,
authorId: user.id,
};
const userComment = await models.Comment.create(newComment);
return successStat(res, 201, 'comment', userComment);
}

/**
* @static
* @description returns all comments for a highlight
* @param {*} req Request object
* @param {*} res Response object
* @returns {Object} Object containing the user comment, author, and timestaps
* @memberof CommentController
*/
static async getHighlightComment(req, res) {
const { user, params: { id } } = req;
const comments = await user.getComments({ where: { highlightId: id } });
return successStat(res, 200, 'comment', comments);
}
}
4 changes: 3 additions & 1 deletion server/db/models/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module.exports = (sequelize, DataTypes) => {
const Comment = sequelize.define('Comment', {
body: DataTypes.TEXT,
authorId: DataTypes.INTEGER,
articleId: DataTypes.INTEGER
articleId: DataTypes.INTEGER,
highlightId: DataTypes.INTEGER,
highlightUser: DataTypes.INTEGER,
}, {});
Comment.associate = (models) => {
Comment.belongsTo(models.Article, { as: 'article' });
Expand Down
83 changes: 81 additions & 2 deletions server/docs/ah-commando-doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,50 @@ paths:
- bearerAuth: []
summary: Only delete articles created by me
description: Only Owner of an Article can delete that article
parameters:
- in: path
name: slug
schema:
type: string
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
article:
type: object
properties:
id:
type: number
comment:
type: string
responses:
'200':
description: successfully delete an article
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/errorResponse"
'500':
description: Server error
content:
application/json:
schema:
$ref: "#/components/schemas/errorResponse"

/articles/{slug}/highlight:
post:
tags:
- Articles
security:
- bearerAuth: []
summary: Allows a user higlight a text and add a comment to that text
description: Only logged in users can do this
parameters:
- in: path
name: slug
Expand Down Expand Up @@ -966,6 +1010,41 @@ paths:
schema:
$ref: "#/components/schemas/errorResponse"

/comment/{id}/highlight:
get:
tags:
- Comments
security:
- bearerAuth: []
summary: comment on an article
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Id of the post to comment on
description: An endpoint to make a comment on Articles
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/successResponse"
'401':
description: Authorization error
content:
application/json:
schema:
$ref: "#/components/schemas/errorResponse"
'500':
description: Server error
content:
application/json:
schema:
$ref: "#/components/schemas/errorResponse"

/comment/{postId}/:
post:
tags:
Expand Down Expand Up @@ -1509,10 +1588,10 @@ components:
favoritesCount:
type: string
author:
type: object
type: object
readTime:
type: number

image:
type: object
properties:
Expand Down
8 changes: 6 additions & 2 deletions server/middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const {
validateCommentMessage,
validateLikes,
validateId,
validateReportArticle
validateReportArticle,
validateHighlightData,
validateGetHighlight
} = InputValidator;

export default {
Expand All @@ -33,5 +35,7 @@ export default {
validateKeyword,
validateLikes,
validateId,
validateReportArticle
validateReportArticle,
validateHighlightData,
validateGetHighlight
};
32 changes: 28 additions & 4 deletions server/middlewares/inputValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
bookmarkParamSchema,
likesSchema,
idSchema,
reportArticleSchema
reportArticleSchema,
highlightDataSchema,
getHighlightSchema
} from './schema';

import validate from '../helpers/validate';
Expand Down Expand Up @@ -112,7 +114,6 @@ class InputValidator {
}

/**
* @method validateBookmark
* @description Validates user details on signup
* @param {object} req - The Request Object
Expand All @@ -139,8 +140,6 @@ class InputValidator {
}

/**
* @method validateId
* @description Validates user details on signup
* @method validateId
* @description Validate id
* @param {object} req - The Request Object
Expand All @@ -165,6 +164,31 @@ class InputValidator {
const reportId = { ...req.params };
return validate(reportId, reportArticleSchema, req, res, next);
}

/**
* @method validateHighlightdata
* @description Validate the highligh data as given from the frontend
* @param {object} req - The Request Object
* @param {object} res - The Response Object
* @param {function} next - The next function to point to the next middleware
* @returns {function} validate() - An execucted validate function
*/
static validateHighlightData(req, res, next) {
const highlightData = { ...req.body.highlight, ...req.params };
return validate(highlightData, highlightDataSchema, req, res, next);
}

/**
* @method validateHighlightdata
* @description Validate the highligh id
* @param {object} req - The Request Object
* @param {object} res - The Response Object
* @param {function} next - The next function to point to the next middleware
* @returns {function} validate() - An execucted validate function
*/
static validateGetHighlight(req, res, next) {
return validate(req.params, getHighlightSchema, req, res, next);
}
}

export default InputValidator;
12 changes: 11 additions & 1 deletion server/middlewares/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const articleSchema = {

tagList: Joi.string()
.required()
.regex(/^[a-zA-Z0-9\ \-]+$/)
.regex(/^[a-zA-Z0-9 -]+$/)
.error((errors) => {
errors.forEach((err) => {
if (err.type === 'string.regex.base') {
Expand Down Expand Up @@ -213,3 +213,13 @@ export const idSchema = {
export const reportArticleSchema = {
reportId: Joi.number()
};

export const highlightDataSchema = {
id: Joi.number().integer().required().min(1),
comment: Joi.string().trim().required(),
slug: Joi.string()
};

export const getHighlightSchema = {
id: Joi.number().integer().required().min(1)
};
11 changes: 8 additions & 3 deletions server/routes/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import middlewares from '../middlewares';
const router = express.Router();

const {
validateArticle, multerUploads, verifyToken, validateFilter, validateKeyword, validateLikes
validateArticle,
multerUploads,
verifyToken,
validateFilter,
validateKeyword,
validateHighlightData
} = middlewares;

const {
Expand All @@ -14,7 +19,7 @@ const {
getOneArticle,
editArticle,
deleteArticle,
likeOrDislikeArticle,
highlightText
} = ArticleController;
router.post('/', verifyToken, validateArticle, createArticle);

Expand All @@ -23,9 +28,9 @@ router.get('/', validateKeyword, getAllArticles);
router.get('/:slug', getOneArticle);
router.put('/:slug/edit', verifyToken, multerUploads, editArticle);
router.delete('/:slug', verifyToken, deleteArticle);
router.post('/:articleId/likes', verifyToken, validateLikes, likeOrDislikeArticle);

// filters article search result based on selected filters
router.post('/search/filter', validateFilter, getAllArticles);
router.post('/:slug/highlight', verifyToken, validateHighlightData, highlightText);

export default router;
10 changes: 8 additions & 2 deletions server/routes/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import CommentController from '../controllers/commentController';

const {
verifyToken,
validateCommentMessage
validateCommentMessage,
validateHighlightData,
validateGetHighlight
} = middlewares;

const {
addAComment,
getCommentsOnASingleArticle,
editOwnComment,
deleteOwnComment,
getAllCommentsForAllPosts
getAllCommentsForAllPosts,
commentAhighligh,
getHighlightComment
} = CommentController;

const commentRouter = express();
Expand All @@ -22,5 +26,7 @@ commentRouter.get('/:postId', verifyToken, getCommentsOnASingleArticle);
commentRouter.put('/:commentId', verifyToken, validateCommentMessage, editOwnComment);
commentRouter.delete('/:commentId', verifyToken, deleteOwnComment);
commentRouter.get('/', verifyToken, getAllCommentsForAllPosts);
commentRouter.post('/:id/highlight', verifyToken, validateHighlightData, commentAhighligh);
commentRouter.get('/:id/highlight', verifyToken, validateGetHighlight, getHighlightComment);

export default commentRouter;
Loading

0 comments on commit ed2cd5f

Please sign in to comment.