Skip to content

Commit

Permalink
feature(readTime): view read time
Browse files Browse the repository at this point in the history
Write function to generate read time
Write using tests to view novel
Create a route to view a novel
Create Validations to view a novel
Create a controller to view a novel
Add swagger documentation

[Finishes #167164998]
  • Loading branch information
allebd committed Aug 23, 2019
1 parent 850e6f5 commit 7926cc1
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 18 deletions.
35 changes: 35 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,41 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/notFoundResponse"
'/novel/{slug}':
get:
tags:
- Users
summary: View a novel details.
security:
- ApiKeyAuth: []
parameters:
- name: slug
in: path
required: true
schema:
type: string
responses:
200:
description: novel details
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/NovelInput'
404:
description: novel not found
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
message:
type: string
example: 'novel not found'
500:
description: server error
content:
Expand Down
28 changes: 26 additions & 2 deletions src/controllers/novelController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const {
} = helpers;
const { Novel } = models;
const {
novelServices: { addNovel, findGenre, findAllNovels },
novelServices: {
addNovel, findGenre, findNovel, findAllNovels
},
notificationServices: { addNotification }
} = services;

Expand Down Expand Up @@ -113,4 +115,26 @@ const createGenre = async (request, response) => {
}
};

export default { createNovel, getNovels, createGenre };
/**
* @description returns novel with slug
* @param {object} request express request object
* @param {object} response express response object
* @param {object} next express next argument
* @returns {json} json
*/
const getNovel = async (request, response) => {
try {
const { slug } = request.params;
const novel = await findNovel(slug);
if (!novel) {
return responseMessage(response, 404, { error: 'novel not found' });
}
return responseMessage(response, 200, { novel });
} catch (error) {
responseMessage(response, 500, { error: error.message });
}
};

export default {
createNovel, getNovel, getNovels, createGenre
};
4 changes: 4 additions & 0 deletions src/database/migrations/20190729135752-create-novel.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const up = (queryInterface, Sequelize) => queryInterface.createTable('Novels', {
allowNull: false,
type: Sequelize.TEXT
},
readTime: {
allowNull: false,
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
Expand Down
4 changes: 4 additions & 0 deletions src/database/models/novel.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export default (Sequelize, DataTypes) => {
type: DataTypes.BOOLEAN,
defaultValue: false
},
readTime: {
allowNull: false,
type: DataTypes.INTEGER
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
Expand Down
22 changes: 22 additions & 0 deletions src/database/seeders/20190806144830-novel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'abcd',
genreId: 'ffe299b9-889b-4ad3-86cf-138cd57d5aab',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -17,6 +18,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'abcd',
genreId: 'ffe299b9-889b-4ad3-86cf-138cd57d5aab',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -28,6 +30,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Religion and Christianity proposes that all the answers can be found in the Bible, while Christianity is all bout searching for anwsers.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -39,6 +42,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -50,6 +54,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -61,6 +66,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -72,6 +78,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -83,6 +90,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -94,6 +102,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: '09add0fe-d063-48ce-8e18-0dc590d04dcf',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -105,6 +114,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -116,6 +126,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -127,6 +138,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -138,6 +150,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -149,6 +162,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -160,6 +174,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -171,6 +186,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -182,6 +198,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: '09add0fe-d063-48ce-8e18-0dc590d04dcf',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -193,6 +210,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -204,6 +222,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -215,6 +234,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -226,6 +246,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8',
genreId: '09add0fe-d063-48ce-8e18-0dc590d04dcf',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -237,6 +258,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.',
authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4',
readTime: 1,
createdAt: new Date(),
updatedAt: new Date()
}], {});
Expand Down
12 changes: 12 additions & 0 deletions src/helpers/generateReadTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @description returns read time of string in minutes
* @param {string} string a string
* @returns {number} number in minutes
*/
const generateReadTime = (string) => {
const wordCount = string.split(' ').length;
const readTimeMinutes = Math.round(wordCount / 200);
return readTimeMinutes;
};

export default generateReadTime;
4 changes: 3 additions & 1 deletion src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import verifyUser from './verifyUser';
import notificationConfig from './notificationConfig';
import novelHelpers from './novelHelpers';
import reportHelper from './reportHelper';
import generateReadTime from './generateReadTime';

const { forgotPasswordMessage, emailNotificationMessage } = emailMessages;

Expand All @@ -23,5 +24,6 @@ export default {
emailNotificationMessage,
notificationConfig,
novelHelpers,
reportHelper
reportHelper,
generateReadTime
};
6 changes: 5 additions & 1 deletion src/middlewares/novelValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import validator from '../helpers/validator';
import errorHandler from './errorHandler';

const {
isNotEmpty, isValidGenre, isValidInt, isValidName
isNotEmpty, isValidGenre, isValidInt, isValidName, isNotEmptySlug
} = validator;

const { validatorError } = errorHandler;
Expand All @@ -27,6 +27,10 @@ const novelValidator = {
genreValidator: [
isValidName('name'),
validatorError
],
getNovelBySlugValidator: [
isNotEmptySlug(),
validatorError
]
};

Expand Down
16 changes: 10 additions & 6 deletions src/routes/novel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import middlewares from '../middlewares';
import updateLikes from '../controllers/likesController';
import novelController from '../controllers/novelController';

const novel = express.Router();
const NOVEL_URL = '/novels';

const {
novelValidator: {
createNovelValidator, getNovelValidator, genreValidator
createNovelValidator, getNovelBySlugValidator, getNovelValidator, genreValidator
},
verifyToken,
authorizeUser
} = middlewares;
const { createNovel, getNovels, createGenre } = novelController;
const {
createNovel, getNovel, getNovels, createGenre
} = novelController;
const novel = express.Router();
const NOVEL_URL = '/novels';

// Route to create a novel
novel.post(`${NOVEL_URL}`, verifyToken, authorizeUser(['author', 'admin', 'superadmin']), createNovelValidator, createNovel);
Expand All @@ -24,7 +25,10 @@ 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 novels
// Route to get all novels
novel.get(`${NOVEL_URL}`, verifyToken, getNovelValidator, getNovels);

// Route to get novel by slug
novel.get(`${NOVEL_URL}/:slug`, verifyToken, authorizeUser(['reader', 'author', 'admin', 'superadmin']), getNovelBySlugValidator, getNovel);

export default novel;
Loading

0 comments on commit 7926cc1

Please sign in to comment.