Skip to content

Commit

Permalink
(ft-article-reaction): add like or dislike article
Browse files Browse the repository at this point in the history
users should be able to like or dislike an article

[Finishes #164139689]
  • Loading branch information
Dubby20 committed Mar 21, 2019
2 parents 10d086b + 81c1f48 commit c5b2871
Show file tree
Hide file tree
Showing 45 changed files with 2,244 additions and 192 deletions.
1 change: 1 addition & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"plugins": ["transform-es2015-destructuring", "transform-object-rest-spread"],
"presets": [ "es2015" ]
}
70 changes: 35 additions & 35 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
{
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"root": true,
"extends": "airbnb-base",
"env": {
"node": true,
"es6": true,
"mocha": true
},
"rules": {
"one-var": 0,
"one-var-declaration-per-line": 0,
"new-cap": 0,
"consistent-return": 0,
"no-param-reassign": 0,
"comma-dangle": 0,
"curly": ["error", "multi-line"],
"import/no-unresolved": [2, { "commonjs": true }],
"no-shadow": ["error", { "allow": ["req", "res", "err"] }],
"valid-jsdoc": ["error", {
"requireReturn": true,
"requireReturnType": true,
"requireParamDescription": false,
"requireReturnDescription": true
}],
"require-jsdoc": ["error", {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true
}
}]
}
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"root": true,
"extends": "airbnb-base",
"env": {
"node": true,
"es6": true,
"mocha": true
},
"rules": {
"one-var": 0,
"one-var-declaration-per-line": 0,
"new-cap": 0,
"consistent-return": 0,
"no-param-reassign": 0,
"comma-dangle": 0,
"curly": ["error", "multi-line"],
"import/no-unresolved": [2, { "commonjs": true }],
"no-shadow": ["error", { "allow": ["req", "res", "err"] }],
"valid-jsdoc": ["error", {
"requireReturn": true,
"requireReturnType": true,
"requireParamDescription": false,
"requireReturnDescription": true
}],
"require-jsdoc": ["error", {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true
}
}]
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Authors Haven - A Social platform for the creative at heart.

[![Build Status](https://travis-ci.com/andela/vidar-ah-backend.svg?branch=develop)](https://travis-ci.com/andela/vidar-ah-backend) [![Coverage Status](https://coveralls.io/repos/github/andela/vidar-ah-backend/badge.svg?branch=ft-create-user-article-%23164139686)](https://coveralls.io/github/andela/vidar-ah-backend?branch=ft-create-user-article-%23164139686) [![](https://img.shields.io/badge/Protected_by-Hound-a873d1.svg)](https://houndci.com)
[![Build Status](https://travis-ci.com/andela/vidar-ah-backend.svg?branch=develop)](https://travis-ci.com/andela/vidar-ah-backend) [![Coverage Status](https://coveralls.io/repos/github/andela/vidar-ah-backend/badge.svg?branch=develop)](https://coveralls.io/github/andela/vidar-ah-backend?branch=develop) [![](https://img.shields.io/badge/Protected_by-Hound-a873d1.svg)](https://houndci.com)
=======

## Vision
Expand Down
223 changes: 196 additions & 27 deletions controllers/articles.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Article, Reaction } from '../models';

import {
Article,
Reaction,
User,
Ratings
} from '../models';
import Paginate from '../helpers/paginate';
/**
* @class ArticleController
* @override
* @export
*/
export default class ArticleController {
/**
/**
* @description - Create a new article
* @static
* @param {Object} req - the request object
Expand All @@ -17,21 +22,64 @@ export default class ArticleController {
static async createArticle(req, res) {
const images = req.images || [];
const {
title, description, body, slug
title, description, body, slug, categoryId
} = req.body;
const taglist = req.body.taglist ? req.body.taglist.split(',') : [];
const { id } = req.user;
try {
const result = await Article.create({
title, description, body, slug, images, taglist, userId: id
title,
description,
body,
slug,
images,
taglist,
userId: id,
categoryId: categoryId || 1
});
return res.status(201).json({
success: true,
message: 'New article created successfully',
article: result,
});
} catch (error) {
return res.status(500).json({ success: false, error: [error.message] });
return res.status(500).json({ success: false, errors: [error.message] });
}
}

/**
* @description - Rate an article
* @static
* @param {Object} req - the request object
* @param {Object} res - the response object
* @memberof ArticleController
* @returns {Object} class instance
*/
static async rateArticle(req, res) {
const { id } = req.user;
const rating = Number(req.body.rating);
const { articleId } = req.params;
try {
const previousRating = await Ratings.findOne({ where: { userId: id, articleId } });
if (previousRating) {
const updatedRating = await previousRating.update({ rating });
return res.status(201).json({
success: true,
message: `Article rating has been updated as ${rating}`,
rating: updatedRating
});
}
return res.status(200).json({
success: true,
message: `Article has been rated as ${rating}`,
articleRating: (await Ratings.create({
userId: id,
articleId,
rating
}))
});
} catch (error) {
return res.status(400).json({ success: false, errors: ['Error rating this article'] });
}
}

Expand Down Expand Up @@ -113,22 +161,7 @@ export default class ArticleController {
static async likeArticle(req, res) {
const { id: userId } = req.user;
const { slug: articleSlug } = req.params;

try {
// const likes = await Reaction.findAndCountAll({
// where: {
// like: true
// },
// include: [{
// model: User,
// as: 'user',
// attributes: ['username', 'bio', 'name']
// }, {
// model: Article,
// as: 'article',
// attributes: ['articleSlug']
// }]
// });
const likeArticle = await Reaction.findOne({
where: {
articleSlug,
Expand All @@ -139,16 +172,23 @@ export default class ArticleController {
await Reaction.create({
articleSlug,
userId,
like: true,
likes: true,
});
const allLikes = await Reaction.findAndCountAll({
where: {
articleSlug,
userId,
likes: true
},
});
return res.status(201).json({
success: true,
message: 'Article liked successfully',
// like: likes.count
likes: allLikes.count
});
} if (
(likeArticle)
&& (likeArticle.like === true)) {
&& (likeArticle.likes === true)) {
await Reaction.destroy({
where: {
articleSlug,
Expand Down Expand Up @@ -190,15 +230,23 @@ export default class ArticleController {
await Reaction.create({
articleSlug,
userId,
like: false
likes: false
});
const allDisLikes = await Reaction.findAndCountAll({
where: {
articleSlug,
userId,
likes: false
},
});
return res.status(201).json({
success: true,
message: 'Article disliked successfully'
message: 'Article disliked successfully',
dislikes: allDisLikes.count
});
} if (
(likeArticle)
&& (likeArticle.like === false)) {
&& (likeArticle.likes === false)) {
await Reaction.destroy({
where: {
articleSlug,
Expand All @@ -217,4 +265,125 @@ export default class ArticleController {
});
}
}

/**
* @description - Search for articles
* @static
* @param {Object} req - the request object
* @param {Object} res - the response object
* @memberof ArticleController
* @returns {Object} class instance
*/
static async searchForArticles(req, res) {
try {
let { offset, limit } = req.query;
offset = Number(offset) || 0;
limit = Number(limit) || 10;
const searchTerms = ArticleController.generateSearchQuery(req.query);
const results = await Article.findAndCountAll({
where: {
...searchTerms,
},
include: [{
model: User,
attributes: ['username', 'email', 'name', 'bio'],
as: 'author',
}],
offset,
limit,
});
const { count } = results;
const meta = Paginate({ count, limit, offset });
return res.status(200).json({
results,
...meta,
success: true
});
} catch (error) {
return res.status(500).json({
success: false,
errors: ['Oops, something went wrong.']
});
}
}

/**
* @description - Generate queries for search and filter
* @static
* @param {Object} searchTerms - the terms that the user wants to search for
* @memberof ArticleController
* @returns {Object} class instance
*/
static generateSearchQuery(searchTerms) {
const {
author, term, endDate, startDate, tags, categoryId
} = searchTerms;

const filterFields = {
'$author.username$': {
$like: `%${author}%`
},
createdAt: {
$between: [startDate, endDate]
},
title: {
$like: `%${term}%`,
},
description: {
$like: `%${term}%`,
},
taglist: {
$contains: tags ? [...tags.split(',')] : []
},
categoryId: Number(categoryId),
};

if (!author) {
delete filterFields['$author.username$'];
}
if (!startDate || !endDate) {
delete filterFields.createdAt;
}
if (!categoryId) {
delete filterFields.categoryId;
}
return filterFields;
}

/**
* @description - Get article by slug
* @static
* @param {Object} req - the request object
* @param {Object} res - the response object
* @memberof ArticleController
* @returns {Object} class instance
*/
static async getArticleBySlug(req, res) {
try {
const {
params: {
slug
}
} = req;
const article = await Article.findOne({
where: {
slug
},
include: [{
as: 'author',
model: User,
attributes: ['username', 'email', 'name', 'bio'],
}]
});
return res.status(200).json({
success: true,
article: article.dataValues
});
} catch (error) {
return res.status(404).json({
success: false,
errors: ['Article not found.'],
});
}
}
}
Loading

0 comments on commit c5b2871

Please sign in to comment.