Skip to content

Commit

Permalink
Merge bbe2b63 into 5d4bbaf
Browse files Browse the repository at this point in the history
  • Loading branch information
klevamane committed Aug 27, 2018
2 parents 5d4bbaf + bbe2b63 commit aa5c080
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 34 deletions.
3 changes: 2 additions & 1 deletion config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ module.exports = {
database: process.env.TEST_DB_DATABASE,
host: process.env.TEST_DB_HOST,
dialect: 'postgres',
operatorsAliases: false
operatorsAliases: false,

},
production: {
username: process.env.PROD_DB_USER,
Expand Down
2 changes: 1 addition & 1 deletion controllers/ArticleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ class ArticleController {
static listAllArticles(req, res, next) {
const { page, limit } = req;
let offset = null;
if (req.query.author || req.query.tag || req.query.title) return next();

if (page || limit) {
// calculate offset
offset = limit * (page - 1);
}

return Article
.findAll({
include: [{
Expand Down
63 changes: 63 additions & 0 deletions helpers/exports.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Sequelize from 'sequelize';
import { trim, escape } from 'validator';
import { User } from '../models';
import sendVerificationEmail from './sendmail';

const { Op } = Sequelize;

exports.resendVerificationEmail = async (req, res) => {
const { email } = req.body.user;
try {
Expand Down Expand Up @@ -35,3 +39,62 @@ exports.resendVerificationEmail = async (req, res) => {

}
};

/**
* @function searchByTagAuthorOrTitle
* @summary Articles can be searched by Tag, Author or TItle
* @param {object} req - Request object
* @param {object} res - Response object
* @returns {object} returns the list of articles matching the search criteria
*/
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;
pageNumber = parseInt(pageNumber, 10);
pageSize = parseInt(pageSize, 10);
if ((pageNumber < 1 || !Number.isInteger(pageNumber)) || pageSize < 10) {
return res.status(400).json({
errors: {
body: [
'Please ensure your query is an integer and pageSize is greater than 9'
]
}
});
}
const limit = pageSize;
const offset = (pageNumber - 1) * limit;
const queryStringValues = Object.values(req.query);
let textToSearch = queryStringValues[0];
textToSearch = trim(escape(textToSearch));

// selects the search value from the key
const searchCriteria = keysAndValues[0][0];
if (searchCriteria === 'tag') {
searchParameters.where = {
[Op.or]: [{ tagList: { [Op.contains]: [textToSearch] } }]
};
}
if (searchCriteria === 'title') {
searchParameters.where = {
title: { [Op.iLike]: `%${textToSearch}%` }
};
}
/**
* set the relationship to search from the right table
* since the attribute to be searched for is on the right table
* */
if (searchCriteria === 'author') {
searchParameters.where = { username: Sequelize.where(Sequelize.col('User.username'), { [Op.eq]: `${textToSearch}` }) };
}
searchParameters.limit = limit;
searchParameters.offset = offset;
searchParameters.include = [{
model: User,
attributes: ['username'],
}];
return searchParameters;
};
18 changes: 12 additions & 6 deletions helpers/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,18 @@ export default class Utilities {

/**
* @function increaseCount
* @summary: A funtion to increase count
* each time an article is updated
* @param {Integer} count: input param
* @returns {Integer} number of count: for updating articles
* @summary: API controller to handle requests
* to delete an article
* @param {Integer} num: input param
* @returns {object} api response: article object for
* successful requests, or error object for
* requests that fail
*/
static increaseCount(count) {
if (Number.isInteger(count)) return count + 1;
static increaseCount(num) {
if (Number.isInteger(num)) {
let updateCount = num;
updateCount += 1;
return updateCount;
}
}
}
26 changes: 26 additions & 0 deletions middlewares/searchArticles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import models from '../models';
import { searchByTagAuthorOrTitle } from '../helpers/exports';

const { Article } = models;
/**
* @function searchForArticle
* @summary Return a user's profile after updating it
* @param {object} req - Request object
* @param {object} res - Response object
* @param {objecg} next - passes error to the error handler
* @returns {object} An object containing all the data related to the user if update successful
*/
const searchForArticles = async (req, res, next) => {
try {
// query the database
const searchParameters = searchByTagAuthorOrTitle(req, res);
const articles = await Article.findAll(searchParameters);
if (articles.length > 0) {
return res.status(200).send({ articles, message: 'These are the articles found' });
}
return res.status(200).json({ message: 'No article found for your search' });
} catch (err) {
next(err);
}
};
export default searchForArticles;
44 changes: 23 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@
"methods": "^1.1.2",
"mocha": "^5.2.0",
"morgan": "^1.9.0",
"node-time-uuid": "^0.1.5",
"nodemailer": "^4.6.7",
"nodemailer-stub-transport": "^1.1.0",
"node-time-uuid": "^0.1.5",
"nyc": "^12.0.2",
"passport": "^0.4.0",
"passport-facebook": "^2.1.1",
Expand All @@ -57,9 +57,10 @@
"request": "^2.87.0",
"sequelize": "^4.38.0",
"slug": "^0.9.1",
"slugify": "^1.3.0",
"underscore": "^1.9.1",
"winston": "^3.0.0",
"slugify": "^1.3.0"
"validator": "^10.5.0",
"winston": "^3.0.0"
},
"devDependencies": {
"@types/dotenv": "^4.0.3",
Expand Down
4 changes: 2 additions & 2 deletions routes/api/articleRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import validateArticle from '../../middlewares/validateArticle';
import verifyToken from '../../middlewares/verifyToken';
import ParamsValidator from '../../middlewares/ParamsValidator';
import { checkCount, articleExists } from '../../middlewares/checkUser';

import searchForArticles from '../../middlewares/searchArticles';

const router = Router();

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

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

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

export default router;
70 changes: 70 additions & 0 deletions tests/controllers/articleController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ chai.use(chaiHttp);
const {
validUser,
validArticleData,
validArticleData2,
dataWithNoTitle,
dataWithNoDescription,
dataWithNoBody,
Expand Down Expand Up @@ -339,4 +340,73 @@ describe('Articles API endpoints', () => {
done();
});
});

it('Should create article with required fields for authenticated user', (done) => {
chai.request(app)
.post('/api/articles')
.set('authorization', `Bearer ${validToken}`)
.send(validArticleData2)
.end((err, res) => {
createdArticle = res.body.article;
expect(res).to.have.status(201);
expect(res.body).to.be.an('object').to.have.property('article');
expect(createdArticle).to.be.an('object').to.have.property('slug');
expect(createdArticle.slug).to.be.a('string');
expect(createdArticle.title).to.equal(validArticleData.article.title);
expect(createdArticle.body).to.equal(validArticleData.article.body);
expect(createdArticle.description).to.equal(validArticleData.article.description);
expect(createdArticle.User).to.be.an('object').to.have.property('username').to.equal(validUser.user.username);
expect(createdArticle.User).to.have.property('bio');
expect(createdArticle.User).to.have.property('image');
done();
});
});
// Articles by search
it('Should not find articles by the wrong title', (done) => {
chai.request(app)
.get('/api/articles/?title=fiction')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object').to.have.property('message').to.equal('No article found for your search');
done();
});
});
});
describe('Articles Search by Criteria', () => {
it('Should search for articles by tag', (done) => {
chai.request(app)
.get('/api/articles?tag=fiction')
.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 title', (done) => {
chai.request(app)
.get('/api/articles?title=train')
.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')
.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 not return any search result for articles not found', (done) => {
chai.request(app)
.get('/api/articles?author=Lumexx')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object').to.have.property('message').to.equal('No article found for your search');
done();
});
});
});
10 changes: 10 additions & 0 deletions tests/controllers/seed/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ const validArticleData = {
}
};

const validArticleData2 = {
article: {
title: 'How to train your dragon',
description: 'Ever wonder how?',
body: 'You have to believe',
tagList: ['fiction']
}
};

const editedArticle = {
article: {
title: 'How to train your dragon right',
Expand Down Expand Up @@ -51,6 +60,7 @@ const dataWithNoBody = {
export default {
validUser,
validArticleData,
validArticleData2,
dataWithNoTitle,
dataWithNoDescription,
dataWithNoBody,
Expand Down

0 comments on commit aa5c080

Please sign in to comment.