Skip to content

Commit

Permalink
feature(bookmark): User can bookmark novels
Browse files Browse the repository at this point in the history
- add enpoint to bookmark novels
- add endpoint to fetch bookmarks
- write test for edge cases

[Finishes #167165000]
  • Loading branch information
Rythae committed Aug 26, 2019
1 parent be231e0 commit 20e1f9f
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 13 deletions.
47 changes: 47 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,53 @@ paths:
description: Novel not found
500:
description: internal server error

/novels/:novelId/bookmarks:
post:
tags:
- Novels
summary: Bookmark Novels
requestBody:
description: User should be able to bookmark a novel
content:
application/json:
schema:
$ref: '#/components/schemas/NovelInput'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/NovelInput'
required: true
responses:
201:
description: Bookmark Novels
404:
description: Not found
500:
description: internal server error

/novels/bookmarks:
get:
tags:
- Novels
summary: Fetch Bookmarks
requestBody:
description: Bookmarks fetched successfully
content:
application/json:
schema:
$ref: '#/components/schemas/NovelInput'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/NovelInput'
required: true
responses:
201:
description: Bookmarks fetched successfully
404:
description: Not found
500:
description: internal server error

components:
schemas:
token:
Expand Down
62 changes: 60 additions & 2 deletions src/controllers/novelController.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const { Novel } = models;
const {
novelServices: {
addNovel, findGenre, findNovel, findAllNovels,
highlightNovelText, getNovelHighlights
highlightNovelText, getNovelHighlights,
findNovelById, bookmarkNovel, getAllBookmark
},
notificationServices: { addNotification }
} = services;
Expand Down Expand Up @@ -179,10 +180,67 @@ const highlightNovel = async (request, response) => {
}
};

/**
* createBookmark
* @param {object} req
* @param {object} res
* @returns {object} json
*/

const postBookmark = async (req, res) => {
const { novelId } = req.params;
const {
id: userId
} = req.user;

try {
const novel = await findNovelById(novelId);
if (!novel) {
return responseMessage(res, 404, { error: 'novel not found' });
}

const newBookmark = await bookmarkNovel(userId, novel.id);
res.status(201).json({
bookmark: {
novelId: novel.id,
title: novel.title,
updatedAt: newBookmark.updatedAt
}
});
} catch (error) {
return responseMessage(res, 500, { error: error.message });
}
};

/**
* getAllBookmark
* @param {object} req
* @param {object} res
* @returns {object} json
*/

const fetchBookmarks = async (req, res) => {
const {
id: userId
} = req.user;

try {
const bookmarks = await getAllBookmark(userId);
return successResponse(res, 201, {
message: 'Bookmarks fetched successfully',
bookmarks
});
} catch (error) {
return responseMessage(res, 500, { error: error.message });
}
};

export default {
createNovel,
getNovels,
createGenre,
highlightNovel,
getSingleNovel
getSingleNovel,
postBookmark,
fetchBookmarks
};
32 changes: 32 additions & 0 deletions src/database/migrations/20190812055421-create-bookmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const up = (queryInterface, Sequelize) => queryInterface.createTable('Bookmarks', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
userId: {
allowNull: false,
type: Sequelize.UUID,
references: {
model: 'Users',
key: 'id'
},
},
novelId: {
allowNull: true,
type: Sequelize.UUID,
references: {
model: 'Novels',
key: 'id'
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
export const down = queryInterface => queryInterface.dropTable('Bookmarks');
30 changes: 30 additions & 0 deletions src/database/models/bookmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default (sequelize, DataTypes) => {
const Bookmark = sequelize.define('Bookmark', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
userId: {
allowNull: false,
type: DataTypes.UUID
},
novelId: {
allowNull: false,
type: DataTypes.UUID
}
}, {});
Bookmark.associate = (models) => {
Bookmark.belongsTo(models.User, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});

Bookmark.belongsTo(models.Novel, {
foreignKey: 'novelId',
onDelete: 'CASCADE'
});
};
return Bookmark;
};
5 changes: 5 additions & 0 deletions src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export default (Sequelize, DataTypes) => {
foreignKey: 'userId',
onDelete: 'CASCADE'
});

User.hasMany(models.Bookmark, {
foreignKey: 'userId',
onDelete: 'CASCADE'
});
};
return User;
};
11 changes: 8 additions & 3 deletions src/routes/novel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const {
},
highlightValidator: { createHighlightValidator },
verifyToken,
authorizeUser
authorizeUser,
} = middlewares;
const {
createNovel, getNovels, createGenre,
getSingleNovel, highlightNovel
getSingleNovel, highlightNovel, postBookmark, fetchBookmarks
} = novelController;
const novel = express.Router();
const NOVEL_URL = '/novels';
Expand All @@ -27,7 +27,12 @@ novel.post(`${NOVEL_URL}/:slug/like`, verifyToken, authorizeUser(['author', 'adm
// Route to create a genre
novel.post('/genres', verifyToken, authorizeUser(['author', 'admin', 'superadmin']), genreValidator, createGenre);

// Route to get all novels
// Route to get bookmark

novel.post(`${NOVEL_URL}/:novelId/bookmarks`, verifyToken, postBookmark);
novel.get(`${NOVEL_URL}/bookmarks`, verifyToken, fetchBookmarks);

// Route to get novels
novel.get(`${NOVEL_URL}`, verifyToken, getNovelValidator, getNovels);

// Route to get novel by slug
Expand Down
54 changes: 49 additions & 5 deletions src/services/novelService.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import models from '../database/models';
import helpers from '../helpers';

const {
Genre, Novel, Like, User, Highlight
Genre, Novel, Like, User, Highlight, Bookmark
} = models;
const { generateReadTime } = helpers;

Expand All @@ -21,6 +21,16 @@ const findNovel = async (param) => {
return novel;
};

/**
* Finds a novel from the database by id
* @param {string} param
* @returns {object} a novel object
*/

const findNovelById = param => Novel.findOne({
where: { id: param }
});

/**
* Finds a novelLikes from the database by userid and novelId
* @param {string} userId
Expand All @@ -47,6 +57,38 @@ const removeNovelLike = (userId, novelId) => Like.destroy({
}
});

/**
*
*
* @param {string} userId
* @param {string} novelId
* @returns {object} json
*/
const bookmarkNovel = async (userId, novelId) => {
const createBookmark = await Bookmark.create({
userId,
novelId
});
return {
novelId: createBookmark.novelId,
title: createBookmark.title,
updatedAt: createBookmark.updatedAt
};
};

/**
*
*
* @param {string} userId
* @returns {object} json
*/
const getAllBookmark = async (userId) => {
const bookmarks = await Bookmark.findAll({
where: { userId }
});
return bookmarks;
};

/**
*
*
Expand Down Expand Up @@ -100,14 +142,13 @@ const addNovel = async (novel, author) => {
*
* @param {object} offset
* @param {object} limit
* @param {object} queryFilter
* @returns {object} json
*/
const findAllNovels = async (offset, limit, queryFilter) => {
const findAllNovels = async (offset, limit) => {
const novels = await Novel.findAll({
offset,
limit,
...queryFilter
include: [{ model: User }, { model: Genre }]
});
return novels;
};
Expand Down Expand Up @@ -172,9 +213,12 @@ export default {
findGenre,
findNovel,
addNovel,
findNovelById,
findNovelLike,
removeNovelLike,
findAllNovels,
highlightNovelText,
getNovelHighlights
getNovelHighlights,
bookmarkNovel,
getAllBookmark
};
2 changes: 1 addition & 1 deletion tests/highlight.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const {

let authorToken, readerToken, reader2Token, novelSlug;

const endpointUser = '/api/v1/users/login';
const endpointUser = '/api/v1/auth/login';
const endpointNovel = '/api/v1/novels';

describe('Test for highlights', () => {
Expand Down
Loading

0 comments on commit 20e1f9f

Please sign in to comment.