-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bookmark-article): users should bookmark articles
- add bookmarking functionality - add tests for the endpoint - document feature using swagger [Delivers #166790012]
- Loading branch information
Showing
9 changed files
with
367 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import models from '../models'; | ||
|
||
const { bookmark, articles, users } = models; | ||
/** | ||
* @description Controller for Bookmarking | ||
*/ | ||
class Bookmark { | ||
/** | ||
* @param {object} req | ||
* @param {object} res | ||
* @param {string} string | ||
* @returns {object} response | ||
*/ | ||
static async bookmark(req, res) { | ||
try { | ||
const { slug } = req.params; | ||
const articleId = await articles.findOne({ | ||
where: { slug } | ||
}); | ||
if (!articleId) { | ||
return res.status(404).json({ | ||
error: 'Article is not found.' | ||
}); | ||
} | ||
const loggedinUser = await users.findOne({ where: { username: req.decoded.username } }); | ||
const userId = loggedinUser.id; | ||
const bookmarked = await bookmark.findOne({ | ||
where: { | ||
userId, | ||
articleId: articleId.dataValues.id | ||
}, | ||
}); | ||
const { title } = articleId; | ||
if (!bookmarked) { | ||
await bookmark.create({ | ||
userId, | ||
articleId: articleId.dataValues.id | ||
}); | ||
return res.status(201).json({ | ||
message: 'Successfully bookmarked the article', | ||
article: { | ||
title | ||
} | ||
}); | ||
} | ||
await bookmark.destroy({ where: { userId, articleId: articleId.dataValues.id } }); | ||
return res.status(204).json({ | ||
message: 'Successfully unbookmarked the article', | ||
}); | ||
} catch (error) { | ||
return res.status(500).json({ error: 'Failed to bookmark article, please try again' }); | ||
} | ||
} | ||
|
||
/** | ||
* @param {object} req | ||
* @param {object} res | ||
* @param {string} string | ||
* @returns {object} response | ||
*/ | ||
static async unBookmark(req, res) { | ||
try { | ||
const { slug } = req.params; | ||
const articleId = await articles.findOne({ | ||
where: { slug } | ||
}); | ||
if (!articleId) { | ||
return res.status(404).json({ | ||
error: 'Article is not found.' | ||
}); | ||
} | ||
const loggedinUser = await users.findOne({ where: { username: req.decoded.username } }); | ||
const userId = loggedinUser.id; | ||
const bookmarked = await bookmark.findOne({ | ||
where: { | ||
userId, | ||
articleId: articleId.dataValues.id, | ||
}, | ||
}); | ||
if (bookmarked) { | ||
await bookmark.destroy({ | ||
where: { | ||
userId, | ||
articleId: articleId.dataValues.id | ||
} | ||
}); | ||
} | ||
return res.status(204).json({ | ||
message: 'Successfully unbookmarked the article' | ||
}); | ||
} catch (error) { | ||
return res.status(500).json({ error: 'Failed to unbookmark article, please try again' }); | ||
} | ||
} | ||
|
||
/** | ||
* @param {object} req | ||
* @param {object} res | ||
* @returns {object} response | ||
*/ | ||
static async getBookmarks(req, res) { | ||
try { | ||
const loggedinUser = await users.findOne({ where: { username: req.decoded.username } }); | ||
const userId = loggedinUser.id; | ||
const Bookmarks = await bookmark.findAll({ | ||
attributes: [], | ||
where: { | ||
userId, | ||
}, | ||
|
||
include: [{ model: articles, as: 'article', attributes: ['title', 'description', 'body', 'tagList', 'image', 'createdAt', 'updatedAt'] }], | ||
}); | ||
return res.status(200).json({ | ||
message: 'Article bookmarks retrieved!', | ||
Bookmarks, | ||
}); | ||
} catch (error) { | ||
return res.status(500).json({ error: 'Failed to retrieve bookmarks, please try again' }); | ||
} | ||
} | ||
} | ||
|
||
export default Bookmark; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
export const up = (queryInterface, Sequelize) => queryInterface.createTable('bookmarks', { | ||
id: { | ||
type: Sequelize.UUID, | ||
defaultValue: Sequelize.UUIDV4, | ||
allowNull: false, | ||
primaryKey: true | ||
}, | ||
userId: { | ||
type: Sequelize.UUID, | ||
allowNull: false, | ||
refences: { | ||
model: 'users', | ||
key: 'id' | ||
} | ||
}, | ||
articleId: { | ||
type: Sequelize.UUID, | ||
allowNull: false, | ||
references: { | ||
model: 'articles', | ||
key: 'id' | ||
} | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}); | ||
|
||
export const down = queryInterface => queryInterface.dropTable('bookmarks'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
export default (sequelize, DataTypes) => { | ||
const Bookmark = sequelize.define('bookmark', { | ||
id: { | ||
type: DataTypes.UUID, | ||
defaultValue: DataTypes.UUIDV4, | ||
allowNull: false, | ||
primaryKey: true | ||
}, | ||
userId: { | ||
type: DataTypes.UUID, | ||
defaultValue: DataTypes.UUIDV4, | ||
}, | ||
articleId: { | ||
type: DataTypes.UUID, | ||
defaultValue: DataTypes.UUIDV4, | ||
} | ||
}, {}); | ||
Bookmark.associate = (models) => { | ||
Bookmark.belongsTo(models.users, { | ||
foreignKey: 'userId', targetKey: 'id', as: 'user' | ||
}); | ||
Bookmark.belongsTo(models.articles, { | ||
foreignKey: 'articleId', targetKey: 'id', as: 'article' | ||
}); | ||
}; | ||
return Bookmark; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import chai from 'chai'; | ||
import chaiHttp from 'chai-http'; | ||
import app from '../index'; | ||
import user from './index.test'; | ||
|
||
chai.use(chaiHttp); | ||
const { expect } = chai; | ||
|
||
let tokenGen1; | ||
const slugArticle = 'the-basics-of-java'; | ||
|
||
describe('Article bookmark', () => { | ||
it('signin a user for bookmark', (done) => { | ||
chai.request(app) | ||
.post('/api/users/login') | ||
.send(user.adminLogin) | ||
.end((req, res) => { | ||
const { status, body } = res; | ||
expect(status).to.equal(200); | ||
expect(res.body).to.be.an('object'); | ||
expect(body).to.have.property('message'); | ||
expect(body).to.have.property('user'); | ||
expect(body.user).to.have.property('email'); | ||
expect(body.user).to.have.property('username'); | ||
expect(body.user).to.have.property('token'); | ||
expect(body.message).to.equals('logged in'); | ||
tokenGen1 = body.user.token; | ||
done(); | ||
}); | ||
}); | ||
|
||
it('Should not bookmark the article', (done) => { | ||
chai.request(app) | ||
.post(`/api/articles/${slugArticle}/bookmark`) | ||
.send({}) | ||
.end((err, res) => { | ||
expect(res.status).to.equal(401); | ||
done(); | ||
}); | ||
}); | ||
it('Should successfully bookmark an article', (done) => { | ||
chai.request(app) | ||
.post(`/api/articles/${slugArticle}/bookmark`) | ||
.set('Content-Type', 'application/json') | ||
.set('Authorization', tokenGen1) | ||
.end((err, res) => { | ||
expect(res.status).to.equal(201); | ||
expect(res.body.message).to.equal('Successfully bookmarked the article'); | ||
expect(res.body).to.have.property('article'); | ||
expect(res.body.article).to.have.property('title'); | ||
done(); | ||
}); | ||
}); | ||
it('Should allow the user to view all the article bookmarked', (done) => { | ||
chai.request(app) | ||
.get('/api/bookmark') | ||
.set('Authorization', tokenGen1) | ||
.end((err, res) => { | ||
expect(res.status).to.equal(200); | ||
expect(res.body).to.have.property('Bookmarks').be.an('array'); | ||
done(); | ||
}); | ||
}); | ||
|
||
// Test of unfound bookmark | ||
|
||
it('Should not find the article to bookmark', (done) => { | ||
chai.request(app) | ||
.post('/api/articles/non-existing-article/bookmark') | ||
.set('Content-Type', 'application/json') | ||
.set('Authorization', tokenGen1) | ||
.end((err, res) => { | ||
expect(res.status).to.equal(404); | ||
expect(res.body.error).to.equal('Article is not found.'); | ||
done(); | ||
}); | ||
}); | ||
|
||
// Unbookmark the bookmarked article | ||
|
||
it('Should successfully unbookmark the article', (done) => { | ||
chai.request(app) | ||
.delete(`/api/articles/${slugArticle}/bookmark`) | ||
.set('Content-Type', 'application/json') | ||
.set('Authorization', tokenGen1) | ||
.end((err, res) => { | ||
expect(res.status).to.equal(204); | ||
expect(res.body).to.be.an('object'); | ||
done(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.