Skip to content

Commit

Permalink
feat(user): should be able to bookmark articles
Browse files Browse the repository at this point in the history
- users can bookmark articles for reading later

- users can get a list of articles they’re following



[(Delivers ) #159987417]


  • Loading branch information
joeeasy committed Oct 18, 2018
1 parent c9d414e commit 9e1226e
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 41 deletions.
66 changes: 63 additions & 3 deletions server/controllers/articleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Op } from 'sequelize';
import slug from 'slug';
import uuid from 'uuid-random';
import ratingHelpers from '../helpers/ratingHelpers';
import helpers from '../helpers/helpers';
import models from '../models';
import { dataUri } from '../config/multer/multerConfig';
import imageUpload from '../helpers/imageUpload';
Expand All @@ -16,14 +17,16 @@ const {
Ratings,
ArticleLikes,
Tags,
Categories
Categories,
Bookmarks
} = models;
const { analyseRatings } = ratingHelpers;

const {
getArticlesAndLikesCountForTheWeek, getPopularArticles
} = GetAuthorsOfTheWeekHelpers;

const { parsedId } = helpers;
/**
* @desc This a controller object literal that handles
* containing functions that handles action relating to Articles
Expand Down Expand Up @@ -264,7 +267,64 @@ const articlesController = {
'There was an error processing your request'
);
}
}
};
},
/**
* @method createBookmark
* @description allows users to book mark an article
* @param {Object} req the response object
* @param {Object} res the response object
* @returns {res} the booked marked articles
*/
createBookmark: (req, res) => {
const { title } = req.body;
const articleId = parsedId(req.params.articleId);
if (!Number.isInteger(articleId)) {
return res.status(400).jsend.fail({
message: 'Article is not valid'
});
}
Bookmarks.findOrCreate({
where: {
title,
userId: req.currentUser.id,
articleId,
}
})
.spread((bookmark, created) => (!(created)
? res.status(400).jsend.fail({ message: 'You have already bookmarked this article' })
: res.status(201).jsend.success({
bookmark,
message: 'Your book mark has been created successfully'
})))
.catch(err => res.status(500).jsend.fail({
message: 'Something went wrong, please try again',
error: err.message
}));
},

/**
* @method fetcBookmark
* @description allows users to fetch all their bookmarks
* @param {Object} req the response object
* @param {Object} res the response object
* @returns {res} an array of bookmarks
*/
fetchBookmark: (req, res) => {
const { id } = req.currentUser;
Bookmarks.findAll({
where: {
userId: id
}
})
.then(bookmarks => ((bookmarks.length === 0)
? res.status(404).jsend.fail({
message: 'You have not bookmarked any article'
})
: res.status(200).jsend.success({
message: 'Here are your bookmarks',
bookmarks
})));
},
}

export default articlesController;
1 change: 0 additions & 1 deletion server/helpers/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ const helpers = {
});
});
},

/**
* @description Converts a string separated by spaces to an array of integers found in the string
* @param {srtring} str A string containing integers
Expand Down
3 changes: 1 addition & 2 deletions server/migrations/20180830135814-create-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ module.exports = {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING,
defaultValue: 'https://res.cloudinary.com/dbsxxymfz/image/upload/v1536757459/dummy-profile.png'
type: Sequelize.STRING
},
premium: {
type: Sequelize.BOOLEAN
Expand Down
40 changes: 40 additions & 0 deletions server/migrations/20180913075318-create-bookmarks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Bookmarks', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
userId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'Users',
key: 'id',
as: 'userId'
}
},
articleId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'Articles',
key: 'id',
as: 'articleId'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}, { freezeTableName: true }),
down: queryInterface => queryInterface.dropTable('Bookmarks')
};
24 changes: 24 additions & 0 deletions server/models/bookmarks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const bookmarks = (sequelize, DataTypes) => {
const Bookmarks = sequelize.define('Bookmarks', {
title: {
type: DataTypes.STRING,
allowNull: false
}
});

Bookmarks.associate = (models) => {
Bookmarks.belongsTo(models.Users, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});

Bookmarks.belongsTo(models.Articles, {
foreignKey: 'articleId',
as: 'articles'
});
};

return Bookmarks;
};

export default bookmarks;
10 changes: 9 additions & 1 deletion server/routes/articleRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const {
like,
getArticles,
getFeaturedArticles,
getPopularArticlesForTheWeek
getPopularArticlesForTheWeek,
createBookmark,
fetchBookmark
} = articleController;
const { addComment, updateComment, updateReply } = commentController;
const {
Expand Down Expand Up @@ -123,4 +125,10 @@ articleRoutes.get('/search', searchController);
articleRoutes.get('/featured', getFeaturedArticles);
articleRoutes.get('/popular', getPopularArticlesForTheWeek);

// CREATE BOOKMARK ROUTE
articleRoutes.post('/bookmarks/add/:articleId', auth, createBookmark);

// GET ALL THE BOOKMARKS OF A USER
articleRoutes.get('/bookmarks/user/all', auth, fetchBookmark);

export default articleRoutes;
72 changes: 38 additions & 34 deletions swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,38 @@
}
}
}
}
},
"/api/v1/articles/bookmarks": {
"parameters": [{
"name": "token",
"in": "headers",
"required": true,
"description": "Authorization header",
"type": "string"
}],
"post": {
"summary": "Allows users to bookmark an article",
"description": "",
"schema": {
"$ref": "#/definitions/Bookmarks"
},
"responses": {
"200": {
"description": "Add articles to bookmark"
}
}
},
"get": {
"summary": "Allows users view the list of all articles that have been bookmarked",
"description": "Allows and authenticated user to view the list of all articles he has bookmarked",
"responses": {
"200": {
"description": "Returns bookmarked all articles"
}
}
}

}
},
"definitions": {
"signin": {
Expand Down Expand Up @@ -1809,42 +1840,15 @@
"Update": {
"type": "object",
"properties": {
"firstname": {
"type": "string",
"example": "joe"
},
"lastname": {
"type": "string",
"example": "easy"
},
"username": {
"type": "string",
"example": "metis"
},
"email": {
"type": "string",
"example": "metis@gmail.com"
},
"password": {
"type": "string",
"example": "password"
},
"bio": {
"title": {
"type": "string",
"example": "Here is a brief information about me"
"example": "The day I'll never forget"
},
"interest": {
"type": "array",
"example": ["gaming", "entreprenuership"]
"id": {
"type": "number",
"example": 1
}
},
"json": {
"name": "User"
}
},
"userId": {
"type": "number",
"example": 1
}
},
"ArticleRequestBody": {
Expand Down Expand Up @@ -1931,4 +1935,4 @@
}
}
}
}
}
92 changes: 92 additions & 0 deletions test/articles.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import nock from 'nock';
import fs from 'fs';
import app from '../server/app';
import response from './responses/cloudinaryApiResponse';
import generateToken from '../server/helpers/generateToken';

chai.use(chaiHttp);
const { expect, should } = chai;
Expand Down Expand Up @@ -386,3 +387,94 @@ describe('GET FEATURED ARTICLES TESTS', () => {
});
});
});

const verifiedToken = generateToken(7200, { id: 1, isVerified: true });
const verifiedToken2 = generateToken(7200, { id: 41, isVerified: true });
describe('BOOKMARK AN ARTICLE', () => {
it('Should create a bookmark', (done) => {
chai
.request(app)
.post('/api/v1/articles/bookmarks/add/1')
.set('Authorization', verifiedToken)
.send({
title: 'How I Learnt React in Andela',
})
.end((err, res) => {
expect(res).to.have.status(201);
expect(res.body).to.be.an('object');
expect(res.body.data.message).to.equal('Your book mark has been created successfully');
done();
});
});
it('Should not bookmark an article when it has already been bookmarked', (done) => {
chai
.request(app)
.post('/api/v1/articles/bookmarks/add/1')
.set('Authorization', verifiedToken)
.send({
title: 'How I Learnt React in Andela',
})
.end((err, res) => {
expect(res).to.have.status(400);
expect(res.body).to.be.an('object');
expect(res.body.data.message).to.equal('You have already bookmarked this article');
done();
});
});
it('Should not bookmark an article when fields are empty or not a number', (done) => {
chai
.request(app)
.post('/api/v1/articles/bookmarks/add/bnnjk')
.set('Authorization', verifiedToken)
.send({
title: 'Hello world',
})
.end((err, res) => {
expect(res).to.have.status(400);
expect(res.body).to.be.an('object');
expect(res.body.data.message).to.equal('Article is not valid');
done();
});
});
it('Should not bookmark an article when article does not exist', (done) => {
chai
.request(app)
.post('/api/v1/articles/bookmarks/add/43')
.set('Authorization', verifiedToken)
.send({
title: 'Hello world'
})
.end((err, res) => {
expect(res).to.have.status(500);
expect(res.body).to.be.an('object');
expect(res.body.data.message).to.equal('Something went wrong, please try again');
done();
});
});
});
describe('FETCH ALL BOOKMARK', () => {
it('Should fetch all the users bookmark', (done) => {
chai
.request(app)
.get('/api/v1/articles/bookmarks/user/all')
.set('Authorization', verifiedToken)
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object');
expect(res.body.data.message).to.equal('Here are your bookmarks');
done();
});
});
it('Should fetch all the users bookmark', (done) => {
chai
.request(app)
.get('/api/v1/articles/bookmarks/user/all')
.set('Authorization', verifiedToken2)
.end((err, res) => {
expect(res).to.have.status(404);
expect(res.body).to.be.an('object');
expect(res.body.data.message).to.equal('You have not bookmarked any article');
done();
});
});
});

0 comments on commit 9e1226e

Please sign in to comment.