Skip to content

Commit

Permalink
feature(novels): added create novel endpoint
Browse files Browse the repository at this point in the history
added verifyToken middleware
[Starts #167164986]
  • Loading branch information
Nerocodes committed Jul 31, 2019
1 parent 1248009 commit b8cc1ad
Show file tree
Hide file tree
Showing 23 changed files with 521 additions and 14 deletions.
57 changes: 53 additions & 4 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ externalDocs:
url: https://github.com/andela/dahlia-ah-backend

servers:
- url: /api/
- url: /api/v1
description: Local host
- url: https://ah-dahlia-staging.herokuapp.com/api/
description: Heroku deployment
- url: https://ah-dahlia-staging.herokuapp.com/api/v1
description: Heroku Staging deployment
- url: https://ah-dahlia.herokuapp.com/api/v1
description: Heroku Production deployment

tags:
- name: Users
Expand Down Expand Up @@ -68,6 +70,30 @@ paths:
example: the email address you supplied is invalid
409:
description: resource already exists
500:
description: internal server error
/novels:
post:
tags:
- Novels
summary: Creates a new novel
requestBody:
description: fields containing novel data
content:
application/json:
schema:
$ref: '#/components/schemas/NovelInput'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/NovelInput'
required: true
responses:
201:
description: novel created successfully
401:
description: Authentication failed
409:
description: resource already exists
500:
description: internal server error
/auth/forgotpassword:
Expand Down Expand Up @@ -101,6 +127,8 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'
security:
- bearerToken: []
components:
schemas:
User:
Expand Down Expand Up @@ -158,7 +186,28 @@ components:
password:
type: string
example: Flyplanex22
NovelInput:
required:
- title
- description
- body
- genre
type: object
properties:
title:
type: string
example: Think Big
description:
type: string
example: Learn how to think big
body:
type: string
example: There are five steps to thinking big...
genre:
type: string
example: Action
AuthResponse:
properties:
token:
type: string
username:
Expand All @@ -183,7 +232,7 @@ components:
type: string
example: internal server error
securitySchemes:
ApiKeyAuth:
bearerToken:
type: apiKey
name: Authorization
in: header
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "A Social platform for the creative at heart",
"main": "index.js",
"scripts": {
"heroku-postbuild": "npm run db:migrate",
"heroku-postbuild": "npm run db:ready",
"start": "babel-node ./src/index.js",
"mocha-test": "nyc mocha --require @babel/register tests/*.js --timeout 20000 --exit",
"test": "cross-env NODE_ENV=test npm-run-all db:migrate db:seed mocha-test db:undo",
Expand Down
26 changes: 26 additions & 0 deletions src/controllers/novelController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import createNewNovel from '../services/novel';

/**
* createNovel
*
* @param {object} req
* @param {object} res
* @returns {object} json
*/
const createNovel = async (req, res) => {
const newNovel = req.body;
const createdNovel = await createNewNovel(newNovel, req.userId);
if (createdNovel.error) {
return res.status(409).json({
...createdNovel
});
}
return res.status(201).json({
message: 'Novel created successfully',
novel: {
...createdNovel
}
});
};

export default createNovel;
23 changes: 23 additions & 0 deletions src/database/migrations/20190729073412-create-genre.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const up = (queryInterface, Sequelize) => queryInterface.createTable('Genres', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});

const down = queryInterface => queryInterface.dropTable('Genres');

export default { up, down };
59 changes: 59 additions & 0 deletions src/database/migrations/20190729135752-create-novel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const up = (queryInterface, Sequelize) => queryInterface.createTable('Novels', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
authorId: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
allowNull: false,
references: {
model: 'Users',
key: 'id',
as: 'authorId',
},
},
genreId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Genres',
key: 'id',
as: 'genreId',
},
},
slug: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
title: {
allowNull: false,
type: Sequelize.STRING
},
description: {
allowNull: false,
type: Sequelize.STRING
},
body: {
allowNull: false,
type: Sequelize.TEXT
},
favoritesCount: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});

const down = queryInterface => queryInterface.dropTable('Novels');

export default { up, down };
29 changes: 29 additions & 0 deletions src/database/models/genre.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default (sequelize, DataTypes) => {
const Genre = sequelize.define('Genre', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
name: {
type: DataTypes.STRING
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
}
}, {});
Genre.associate = (models) => {
// associations can be defined here
Genre.hasMany(models.Novel, {
foreignKey: 'genreId',
as: 'genre',
});
};
return Genre;
};
58 changes: 58 additions & 0 deletions src/database/models/novel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export default (sequelize, DataTypes) => {
const Novel = sequelize.define('Novel', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
authorId: {
type: DataTypes.INTEGER,
onDelete: 'CASCADE',
allowNull: false
},
genreId: {
type: DataTypes.INTEGER,
allowNull: false
},
slug: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
title: {
allowNull: false,
type: DataTypes.STRING
},
description: {
allowNull: false,
type: DataTypes.STRING
},
body: {
allowNull: false,
type: DataTypes.TEXT
},
favoritesCount: {
type: DataTypes.INTEGER
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
}
}, {});
Novel.associate = (models) => {
// associations can be defined here
Novel.belongsTo(models.User, {
foreignKey: 'authorId',
onDelete: 'CASCADE',
});
Novel.belongsTo(models.Genre, {
foreignKey: 'genreId'
});
};
return Novel;
};
6 changes: 5 additions & 1 deletion src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ export default (sequelize, DataTypes) => {
type: DataTypes.DATE
}
}, {});
User.associate = () => {
User.associate = (models) => {
// associations can be defined here
User.hasMany(models.Novel, {
foreignKey: 'authorId',
as: 'author',
});
};
return User;
};
8 changes: 4 additions & 4 deletions src/database/seeders/20190724090406-user.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import bcrypt from 'bcrypt';

export const up = queryInterface => queryInterface.bulkInsert('Users', [{
firstName: 'Eden',
lastName: 'Hazard',
email: 'eden@gmail.com',
username: 'eden101',
password: bcrypt.hashSync('edenHazard', 10),
password: '$2y$12$WYGmXXP9ZU/HuBw3c7Jy6eECt7L3f6St67HzgP.OpfHPeMavsoDZa',
bio: 'I am the best author in the world',
avatarUrl: null,
phoneNo: null,
Expand All @@ -16,4 +14,6 @@ export const up = queryInterface => queryInterface.bulkInsert('Users', [{
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('Users', null, {});
export const down = queryInterface => queryInterface.bulkDelete('Users', {
email: 'eden@gmail.com',
});
28 changes: 28 additions & 0 deletions src/database/seeders/20190729145216-genre.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const up = queryInterface => queryInterface.bulkInsert('Genres', [{
name: 'action',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'thriller',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'romance',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'fiction',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'motivational',
createdAt: new Date(),
updatedAt: new Date()
}
], {});

export const down = queryInterface => queryInterface.bulkDelete('Genres', null, {});
16 changes: 15 additions & 1 deletion src/helpers/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ const isValidPassword = () => check('password').isLength({ min: 8 })
.isAlphanumeric()
.withMessage('password should contain only numbers and alphabets');

const isNotEmpty = field => check(field)
.trim()
.not()
.isEmpty()
.withMessage(`${field} is a required field`);

const isValidGenre = field => check(field)
.trim()
.not()
.isEmpty()
.withMessage(`${field} is a required field`)
.isIn(['action', 'thriller', 'romance', 'fiction', 'motivational'])
.withMessage('Must be a valid genre: action, thriller, romance, fiction, motivational');

export default {
isValidEmail, isValidName, isValidPassword, isValidUserName
isValidEmail, isValidName, isValidPassword, isValidUserName, isNotEmpty, isValidGenre
};
Loading

0 comments on commit b8cc1ad

Please sign in to comment.