Skip to content

Commit

Permalink
Merge pull request #65 from andela/ft-text-highlight-166240846
Browse files Browse the repository at this point in the history
#166240846 highlight text
  • Loading branch information
MemunaHaruna authored and Frank Mutabazi committed Jul 8, 2019
2 parents 3e1e3de + ccbe55b commit 1892fb3
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 13 deletions.
44 changes: 33 additions & 11 deletions src/api/controllers/articleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,10 @@ import errorSender from '../../helpers/error.sender';
import generatePagination from '../../helpers/generate.pagination.details';

const {
Article, Category, User, Report
Article, Category, User, Report, Highlight
} = models;

const { CREATED } = statusCodes;

/**
*
*
* this is the article controller
*
* @export
* @class ArticleController
*/
const { CREATED, BAD_REQUEST } = statusCodes;

/**
* containing all article's model controllers
Expand Down Expand Up @@ -248,4 +239,35 @@ export default class ArticleController {
'Sorry, but that reason does not exist, Thanks');
}
}

/**
* allow a user to highlight a text in an article
*
* @author Alain Burindi
* @static
* @param {object} req the request
* @param {object} res the response to be sent
* @memberof ArticleController
* @returns {Object} res
*/
static async highlight(req, res) {
const { slug } = req.params;
const {
startIndex, lastIndex, text, comment
} = req.body;
const correctLength = lastIndex - startIndex + 1;

if (correctLength === text.length) {
const userId = req.user.user.id;
const highlighted = await Highlight.create({
startIndex, lastIndex, text, comment, articleSlug: slug, userId
});
res.status(CREATED).json({
status: CREATED,
highlighted
});
} else {
errorSender(BAD_REQUEST, res, 'text', errorMessage.textMatch);
}
}
}
43 changes: 43 additions & 0 deletions src/api/migrations/20190703103749-create-highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export default {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Highlights', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
startIndex: {
type: Sequelize.INTEGER
},
lastIndex: {
type: Sequelize.INTEGER
},
text: {
type: Sequelize.STRING
},
comment: {
type: Sequelize.STRING
},
articleSlug: {
type: Sequelize.STRING,
references: { model: 'Articles', key: 'slug' }
},
userId: {
type: Sequelize.INTEGER,
references: { model: 'Users', key: 'id'}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Highlights');
}
};
4 changes: 4 additions & 0 deletions src/api/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export default (sequelize, DataTypes) => {
onDelete: 'CASCADE',
hooks: true
});
Article.hasMany(models.Highlight, {
foreignKey: 'articleSlug',
onDelete: 'CASCADE',
});
};

return Article;
Expand Down
21 changes: 21 additions & 0 deletions src/api/models/highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default (sequelize, DataTypes) => {
const Highlight = sequelize.define('Highlight', {
startIndex: DataTypes.INTEGER,
lastIndex: DataTypes.INTEGER,
text: DataTypes.STRING,
comment: DataTypes.STRING,
articleSlug: DataTypes.STRING,
userId: DataTypes.INTEGER
}, {});
Highlight.associate = (models) => {
Highlight.belongsTo(models.User, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});
Highlight.belongsTo(models.Article, {
foreignKey: 'articleSlug',
onDelete: 'CASCADE'
});
};
return Highlight;
};
4 changes: 3 additions & 1 deletion src/api/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ const models = {
Category: sequelize.import('./category'),
Rating: sequelize.import('./rating'),
Report: sequelize.import('./report'),
Reason: sequelize.import('./reasons')
Reason: sequelize.import('./reasons'),
Highlight: sequelize.import('./highlight')
};

Object.keys(models).forEach((key) => {
if ('associate' in models[key]) {
models[key].associate(models);
}
});

export { sequelize };

export default models;
4 changes: 4 additions & 0 deletions src/api/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export default (sequelize, DataTypes) => {
User.hasMany(models.Rating, {
foreignKey: 'userId'
});
User.hasMany(models.Highlight, {
foreignKey: 'userId',
onDelete: 'CASCADE',
});
};

User.findByEmail = (email) => {
Expand Down
3 changes: 3 additions & 0 deletions src/api/routes/articleRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ articleRouter.post('/:slug/rating',
validateInputs('rateArticle', ['rating']),
checkExistingRates.ExistingRating,
ratingsController.rateArticle);
const highlightFields = ['firstIndex', 'lastIndex', 'comment', 'text'];

articleRouter.get('/', articleController.getArticles);
articleRouter.get('/:slug', articleController.getArticle);
Expand Down Expand Up @@ -50,6 +51,8 @@ articleRouter.delete('/:slug',
checkValidToken,
checkArticleOwner.checkOwner,
articleController.delete);
articleRouter.post('/:slug/highlight', checkValidToken, checkArticle.getArticle, validateInputs('highlight', highlightFields),
articleController.highlight);

articleRouter.post('/:slug/report',
checkValidToken,
Expand Down
1 change: 1 addition & 0 deletions src/helpers/constants/error.messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export default {
noArticles: 'Article not found!',
noMoreArticle: 'There no more articles at the moment.',
reason: 'a reason can not be a string, Please provide a number starting from 1, Thanks',
textMatch: 'The text does not match the indexes'
};
16 changes: 15 additions & 1 deletion src/helpers/constants/validation.schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,19 @@ export default {
body: Joi.string().allow(''),
category: Joi.string().uppercase().allow(''),
tagList: Joi.array().items(Joi.string().alphanum()).allow('')
})
}),
highlight: Joi.object().keys({
startIndex: Joi.number()
.min(1)
.required(),
lastIndex: Joi.number()
.min(1)
.required(),
text: Joi.string()
.trim()
.required(),
comment: Joi.string()
.trim()
.required(),
}),
};
55 changes: 55 additions & 0 deletions tests/highlight.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import chai from 'chai';
import chaiHttp from 'chai-http';

import server from '../src/index';
import statusCodes from '../src/helpers/constants/status.codes';
import signup from '../src/helpers/tests/signup';
import errorMessage from '../src/helpers/constants/error.messages';


chai.use(chaiHttp);
chai.should();

const { CREATED, BAD_REQUEST } = statusCodes;
const highlitedData = {
startIndex: 3,
lastIndex: 8,
text: 'text g',
comment: 'is correct'
};

const token = signup();
describe('Highlight', () => {
it('should highlight a text', (done) => {
chai
.request(server)
.post('/api/articles/How-to-create-sequalize-seeds/highlight')
.set('Authorization', token)
.send({
...highlitedData
})
.end((err, res) => {
res.should.have.status(CREATED);
res.body.highlighted.should.include(highlitedData);
done();
});
});

describe('Validation', () => {
it('the text length should match the indexes', (done) => {
highlitedData.startIndex = 2;
chai
.request(server)
.post('/api/articles/How-to-create-sequalize-seeds/highlight')
.set('Authorization', token)
.send({
...highlitedData
})
.end((err, res) => {
res.should.have.status(BAD_REQUEST);
res.body.errors.should.have.property('text', errorMessage.textMatch);
done();
});
});
});
});

0 comments on commit 1892fb3

Please sign in to comment.