Skip to content

Commit

Permalink
feat(article): Add feature to categorise articles
Browse files Browse the repository at this point in the history
- list all categories of articles

- add category field to the article model

- search article by category

[Delivers #159791931]
  • Loading branch information
klevamane committed Sep 11, 2018
1 parent 34da81d commit 53758d7
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 22 deletions.
12 changes: 11 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
{
"presets": ["env"]
"presets": [
["env", {
"targets": {
"node": "current"
}
}]
],
"plugins": [
"transform-runtime",
"transform-async-to-generator"
]
}
4 changes: 0 additions & 4 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ SECRETE_KEY=
VERIFYTOKEN_EXPIRY=
URL_HOST=
NO_REPLY_MAIL=
<<<<<<< HEAD
CLOUD_NAME=
=======
LOUD_NAME=
>>>>>>> ft(create-article): create user article
API_KEY=
API_SECRET=

Expand Down
2 changes: 1 addition & 1 deletion config/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const passportConfig = (app) => {
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
callbackURL: '/api/users/login/facebook/redirect',
callbackURL: process.envFACEBOOK_REDIRECT_URL,
profileFields: ['id', 'displayName', 'photos', 'email'],
}, AuthController.strategyCallback));
};
Expand Down
6 changes: 3 additions & 3 deletions controllers/ArticleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class ArticleController {
*/
static createArticle(req, res) {
const {
title, description, body, tagList, imageData,
title, description, body, tagList, categorylist, imageData,
} = req.body.article;

const { userId } = req;

const articleObject = {
title, description, body, tagList, imageData, userId
title, description, body, tagList, categorylist, imageData, userId
};
/**
* check if image was provided in the request
Expand Down Expand Up @@ -86,7 +86,7 @@ class ArticleController {
* @returns {object} - the found article from database or empty if not found
*/
static listAllArticles(req, res, next) {
if (req.query.author || req.query.tag || req.query.title) return next();
if (req.query.author || req.query.tag || req.query.title || req.query.category) return next();
return Article
.findAll({
include: [{
Expand Down
3 changes: 2 additions & 1 deletion helpers/createArticleHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Article, User } from '../models';

const createArticleHelper = (res, articleObject, imageUrl = null) => {
const {
title, description, body, tagList, userId
title, description, body, tagList, userId, categorylist
} = articleObject;

return Article
Expand All @@ -23,6 +23,7 @@ const createArticleHelper = (res, articleObject, imageUrl = null) => {
body,
userId,
tagList,
categorylist,
imageUrl,
})
.then(article => Article.findById(article.id, {
Expand Down
22 changes: 17 additions & 5 deletions helpers/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ exports.resendVerificationEmail = async (req, res) => {
exports.searchByTagAuthorOrTitle = (req, res) => {
const searchParameters = {};
// Get every parameter and key passed in the query string
const keysAndValues = Object.entries(req.query);
// Assign page number as the first page if the page number is not passed
let pageNumber = typeof req.query.pageNumber === 'undefined' ? 1 : req.query.pageNumber;
let pageSize = typeof req.query.pageSize === 'undefined' ? 10 : req.query.pageSize;
const queryObjectKeysAndValues = Object.entries(req.query);
let { pageNumber = 1, pageSize = 10 } = req.query;
pageNumber = parseInt(pageNumber, 10);
pageSize = parseInt(pageSize, 10);
if ((pageNumber < 1 || !Number.isInteger(pageNumber)) || pageSize < 10) {
Expand All @@ -69,15 +67,21 @@ exports.searchByTagAuthorOrTitle = (req, res) => {
const offset = (pageNumber - 1) * limit;
const queryStringValues = Object.values(req.query);
let textToSearch = queryStringValues[0];
textToSearch = textToSearch.toLowerCase();
textToSearch = trim(escape(textToSearch));

// selects the search value from the key
const searchCriteria = keysAndValues[0][0];
const searchCriteria = queryObjectKeysAndValues[0][0];
if (searchCriteria === 'tag') {
searchParameters.where = {
[Op.or]: [{ tagList: { [Op.contains]: [textToSearch] } }]
};
}
if (searchCriteria === 'category') {
searchParameters.where = {
[Op.or]: [{ categorylist: { [Op.contains]: [textToSearch] } }]
};
}
if (searchCriteria === 'title') {
searchParameters.where = {
title: { [Op.iLike]: `%${textToSearch}%` }
Expand All @@ -98,3 +102,11 @@ exports.searchByTagAuthorOrTitle = (req, res) => {
}];
return searchParameters;
};

exports.listOfCategories = (req, res) => {
const categorieslist = ['people', 'politics', 'science', 'sports', 'culture',
'entertainment', 'education', 'movies', 'agriculture', 'aquaculture', 'libral',
'fiction', 'cartoon', 'programming', 'it', 'technology', 'business', 'marketing',
'african', 'religion', 'festival', 'season', 'european', 'asian', 'american', 'race', 'cars'];
return res.status(200).json({ message: 'List of categories', categorieslist });
};
14 changes: 14 additions & 0 deletions migrations/20180817064757-add_category_field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
up: (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
queryInterface.addColumn('Articles', 'categorylist', Sequelize.ARRAY(Sequelize.STRING));
},

down: queryInterface => queryInterface.removeColumn('Articles', 'categorylist')
};
1 change: 1 addition & 0 deletions models/Article.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = (sequelize, DataTypes) => {
},
updatedCount: DataTypes.INTEGER,
tagList: DataTypes.ARRAY(DataTypes.STRING),
categorylist: DataTypes.ARRAY(DataTypes.STRING),
favorited: DataTypes.BOOLEAN,
favoritesCount: DataTypes.INTEGER,
imageUrl: DataTypes.STRING
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"babel-cli": "^6.26.0",
"babel-istanbul": "^0.12.2",
"babel-preset-env": "^1.7.0",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"eslint": "^5.2.0",
"eslint-config-airbnb": "^17.0.0",
"eslint-plugin-import": "^2.13.0",
Expand Down
3 changes: 3 additions & 0 deletions routes/api/articleRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import validateArticle from '../../middlewares/validateArticle';
import verifyToken from '../../middlewares/verifyToken';
import { checkCount, articleExists } from '../../middlewares/checkUser';
import searchForArticles from '../../middlewares/searchArticles';
import { listOfCategories } from '../../helpers/exports';

const router = Router();

Expand All @@ -17,6 +18,8 @@ router.delete('/articles/:slug', verifyToken, articleExists, ArticleControllers.

router.get('/articles/:slug', ArticleControllers.getArticle);

router.get('/articles/list/categories', listOfCategories);

router.get('/articles', ArticleControllers.listAllArticles, searchForArticles);

export default router;
22 changes: 22 additions & 0 deletions tests/controllers/articleController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,15 @@ describe('Articles Search by Criteria', () => {
done();
});
});
it('Should search for articles by category', (done) => {
chai.request(app)
.get('/api/articles?category=people')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object').to.have.property('message').to.equal('These are the articles found');
done();
});
});
it('Should search for articles by author', (done) => {
chai.request(app)
.get('/api/articles?author=Lumexat')
Expand All @@ -341,3 +350,16 @@ describe('Articles Search by Criteria', () => {
});
});
});

describe('Category Controller endpoints', () => {
it('Should get the list of all categories', (done) => {
chai.request(app)
.get('/api/articles/list/categories')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object').to.have.property('message').to.equal('List of categories');
expect(res.body).to.have.property('categorieslist').to.be.an('array').with.length.greaterThan(5);
done();
});
});
});
20 changes: 13 additions & 7 deletions tests/controllers/seed/seed.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const validUser = {
user: {
email: 'olumide@yahoo.com',
username: 'Lumexat',
username: 'lumexat',
password: 'spirit2018',
}
};
Expand All @@ -11,7 +11,8 @@ const validArticleData = {
title: 'How to train your dragon',
description: 'Ever wonder how?',
body: 'You have to believe',
tagList: ['reactjs', 'angularjs', 'dragons']
tagList: ['reactjs', 'angularjs', 'dragons'],
categorylist: ['people', 'sports', 'culture']
}
};

Expand All @@ -20,7 +21,8 @@ const validArticleData2 = {
title: 'How to train your dragon',
description: 'Ever wonder how?',
body: 'You have to believe',
tagList: ['fiction']
tagList: ['fiction'],
categorylist: ['people', 'sports', 'culture']
}
};

Expand All @@ -29,31 +31,35 @@ const editedArticle = {
title: 'How to train your dragon right',
description: 'Ever wonder how?',
body: 'You have to believe it to achieve it',
tagList: ['reactjs', 'angularjs', 'dragons']
tagList: ['reactjs', 'angularjs', 'dragons'],
categorylist: ['people', 'sports', 'culture']
}
};

const dataWithNoTitle = {
article: {
description: 'Ever wonder how?',
body: 'You have to believe',
tagList: ['reactjs', 'angularjs', 'dragons']
tagList: ['reactjs', 'angularjs', 'dragons'],
categorylist: ['people', 'sports', 'culture']
}
};

const dataWithNoDescription = {
article: {
title: 'How to train your dragon',
body: 'You have to believe',
tagList: ['reactjs', 'angularjs', 'dragons']
tagList: ['reactjs', 'angularjs', 'dragons'],
categorylist: ['people', 'sports', 'culture']
}
};

const dataWithNoBody = {
article: {
title: 'How to train your dragon',
description: 'Ever wonder how?',
tagList: ['reactjs', 'angularjs', 'dragons']
tagList: ['reactjs', 'angularjs', 'dragons'],
categorylist: ['people', 'sports', 'culture']
}
};

Expand Down

0 comments on commit 53758d7

Please sign in to comment.