-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(bookmarks): Implement User should be able to bookmark article…
…s for reading later feature - create bookmark model and relationships with user and articles model - write service methods to create bookmarks, get user bookmarks and remove user bookmarks - write controller methods to create bookmarks, get user bookmarks and remove user bookmarks - write middleware method to validate incoming requests for creating, getting and removing user bookmarks - update router file to accomodate new routes - write unit tests - delivers[#166816120]
- Loading branch information
Showing
15 changed files
with
580 additions
and
36 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,82 @@ | ||
import { | ||
createBookmark, | ||
removeBookmark, | ||
getBookmarks | ||
} from '../services/bookmark.service'; | ||
import Helper from '../services/helper'; | ||
|
||
export default { | ||
/** | ||
* @method createBookmark | ||
* Handles the logic for adding an article to a user's bookmark | ||
* Route: POST api/v1/articles/:slug/bookmarks | ||
* @param {object} request | ||
* @param {object} response | ||
* @param {function} next | ||
* @returns {object|function} API Response object or next method | ||
*/ | ||
|
||
async createBookmark(request, response, next) { | ||
try { | ||
const bookmarked = await createBookmark( | ||
request.articleId, | ||
request.user.id | ||
); | ||
if (bookmarked) { | ||
return Helper.successResponse(response, 201, { | ||
message: `Article added to bookmarks` | ||
}); | ||
} | ||
return Helper.failResponse(response, 409, { | ||
message: `Article is already in your bookmarks` | ||
}); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}, | ||
|
||
/** | ||
* @method getUserBookmarks | ||
* Route: GET api/v1/articles/bookmarks | ||
* Handles the logic for fetching all a user's bookmark | ||
* @param {object} request | ||
* @param {object} response | ||
* @param {function} next | ||
* @returns {object|function} API Response object or next method | ||
*/ | ||
|
||
async getUserBookmarks(request, response, next) { | ||
try { | ||
const bookmarks = await getBookmarks(request.user.id); | ||
return Helper.successResponse(response, 200, bookmarks); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}, | ||
|
||
/** | ||
* @method removeUserBookmark | ||
* Route: DELETE api/v1/articles/:slug/bookmarks | ||
* Handles the logic for removing an article from a user's bookmark | ||
* @param {object} request | ||
* @param {object} response | ||
* @param {function} next | ||
* @returns {object|function} API Response object or next method | ||
*/ | ||
|
||
async removeUserBookmark(request, response, next) { | ||
try { | ||
const removed = await removeBookmark(request.articleId, request.user.id); | ||
if (removed) { | ||
return Helper.successResponse(response, 200, { | ||
message: `Article has been removed from your bookmarks` | ||
}); | ||
} | ||
return Helper.failResponse(response, 404, { | ||
message: `Article is not present in your bookmarks` | ||
}); | ||
} catch (error) { | ||
next(error); | ||
} | ||
} | ||
}; |
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 @@ | ||
module.exports = { | ||
up: (queryInterface, Sequelize) => { | ||
return queryInterface.createTable('Bookmarks', { | ||
id: { | ||
allowNull: false, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
type: Sequelize.INTEGER | ||
}, | ||
userId: { | ||
type: Sequelize.INTEGER | ||
}, | ||
articleId: { | ||
type: Sequelize.INTEGER | ||
}, | ||
isDeleted: { | ||
type: Sequelize.BOOLEAN, | ||
allowNull: false, | ||
defaultValue: false | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}); | ||
}, | ||
down: queryInterface => { | ||
return 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
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,19 @@ | ||
export default (sequelize, DataTypes) => { | ||
const Bookmark = sequelize.define( | ||
'Bookmark', | ||
{ | ||
userId: DataTypes.INTEGER, | ||
articleId: DataTypes.INTEGER, | ||
isDeleted: { | ||
type: DataTypes.BOOLEAN, | ||
allowNull: false, | ||
defaultValue: false | ||
} | ||
}, | ||
{} | ||
); | ||
Bookmark.associate = () => { | ||
// associations can be defined here | ||
}; | ||
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,31 @@ | ||
import { getArticleInstance } from '../services/bookmark.service'; | ||
import Utilities from '../helpers/utilities.helper'; | ||
import Helper from '../services/helper'; | ||
|
||
export default { | ||
/** | ||
* @method bookmarkCheck | ||
* Validates incoming bookmark requests | ||
* @param {object} request | ||
* @param {object} response | ||
* @param {function} next | ||
* @returns {object|function} error object response or next middleware function | ||
*/ | ||
|
||
async bookmarkCheck(request, response, next) { | ||
const validSlug = await Utilities.isValidSlug(request.params.slug); | ||
if (!validSlug) { | ||
return Helper.failResponse(response, 400, { | ||
message: 'Article slug is not valid' | ||
}); | ||
} | ||
const article = await getArticleInstance(request.params.slug); | ||
if (!article) { | ||
return Helper.failResponse(response, 404, { | ||
message: `Article does not exist` | ||
}); | ||
} | ||
request.articleId = article.id; | ||
next(); | ||
} | ||
}; |
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,114 @@ | ||
import model from '../db/models'; | ||
|
||
const { Article, Bookmark, User } = model; | ||
/** | ||
* @method createBookmark | ||
* Interacts with the database to add an article to a user's bookmarks | ||
* @param {number} articleId ID of the article to be bookmarked | ||
* @param {number} userId ID of the user making the bookmark request | ||
* @returns {object|boolean} Bookmark instance object or boolean if bookmarks exists | ||
*/ | ||
|
||
export const createBookmark = async (articleId, userId) => { | ||
const bookmarkExists = await Bookmark.findOne({ | ||
where: { articleId, userId, isDeleted: false } | ||
}); | ||
if (bookmarkExists) return false; | ||
|
||
const bookmarked = await Bookmark.create({ | ||
articleId, | ||
userId | ||
}); | ||
return bookmarked; | ||
}; | ||
|
||
/** | ||
* @method getBookmarks | ||
* Interacts with the database to fetch all bookmarks for a user | ||
* @param {number} userId ID of the user making the bookmark request | ||
* @returns {object} Bookmarks response object | ||
*/ | ||
|
||
export const getBookmarks = async userId => { | ||
const userBookmarks = await User.findOne({ | ||
where: { id: userId }, | ||
attributes: { | ||
exclude: [ | ||
'id', | ||
'email', | ||
'password', | ||
'bio', | ||
'image', | ||
'twitterHandle', | ||
'facebookHandle', | ||
'confirmEmail', | ||
'confirmEmailCode', | ||
'isNotified', | ||
'isPublished', | ||
'passwordToken', | ||
'socialAuth', | ||
'roleType', | ||
'createdAt', | ||
'updatedAt' | ||
] | ||
}, | ||
include: [ | ||
{ | ||
model: Article, | ||
as: 'bookmarks', | ||
attributes: [ | ||
'title', | ||
'body', | ||
'image', | ||
'likesCount', | ||
'viewsCount', | ||
'description' | ||
], | ||
through: { | ||
model: Bookmark, | ||
as: 'bookmarks', | ||
where: { isDeleted: false }, | ||
attributes: { | ||
exclude: ['userId', 'articleId', 'isDeleted'] | ||
} | ||
} | ||
} | ||
] | ||
}); | ||
|
||
if (userBookmarks.bookmarks.length < 1) { | ||
return { message: `You currently don't have any bookmarks` }; | ||
} | ||
return userBookmarks; | ||
}; | ||
|
||
/** | ||
* @method removeBookmark | ||
* Interacts with the database to remove an article from a user's bookmarks | ||
* @param {number} articleId | ||
* @param {number} userId | ||
* @returns {boolean} true if article was removed from bookmarks, false if otherwise | ||
*/ | ||
|
||
export const removeBookmark = async (articleId, userId) => { | ||
const bookmark = await Bookmark.findOne({ | ||
where: { articleId, userId, isDeleted: false } | ||
}); | ||
if (!bookmark) return false; | ||
await bookmark.update({ isDeleted: true }); | ||
return true; | ||
}; | ||
|
||
/** | ||
* @method getArticleInstance | ||
* Queries the database to get an article instance | ||
* @param {string} slug | ||
* @returns {object} article instance object | ||
*/ | ||
|
||
export const getArticleInstance = async slug => { | ||
const articleInstance = await Article.findOne({ | ||
where: { slug } | ||
}); | ||
return articleInstance; | ||
}; |
Oops, something went wrong.