Skip to content

Commit

Permalink
167371017-feature(category, article): add category
Browse files Browse the repository at this point in the history
  - add category migration and model
  - add category route
  - add category validation
  - alter article table add categoryId column
  - add tests
  [Delivers #167371017]
  • Loading branch information
Obi chinedu Frank committed Jul 22, 2019
1 parent ab6ccbe commit 0f01a09
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 4 deletions.
19 changes: 17 additions & 2 deletions controllers/articles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,31 @@ export default {
try {
const {
user, body: {
description, body, title, tags, readTime
description, body, title, tags, readTime, categoryId
}
} = req;

const categoryExist = await db.Category.findOne({
where: {
id: categoryId
}
});

if (!categoryExist) {
return res.status(400).send({
message: 'category does not Exist'
});
}

const tagDetails = tags ? tags.split(',') : null;
const tagList = [];
let article = await user.createArticle(
{
description,
body,
title,
readTime
readTime,
categoryId
}
);

Expand Down Expand Up @@ -171,6 +185,7 @@ export default {
title: data.title || foundArticle.title,
image: data.image || foundArticle.image,
readTime: data.readTime || foundArticle.readTime,
categoryId: data.categoryId || foundArticle.categoryId,
});

return res.status(200).json({
Expand Down
28 changes: 28 additions & 0 deletions controllers/categories/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import db from '../../db/models';

export default {
addCategory: async (req, res) => {
const { name } = req.body;

try {
await db.Category.create({
name,
});

return res.status(200).send({
message: 'category added successfully'
});
} catch (e) {
return res.status(400).send({
error: e.message,
});
}
},

getCategories: async (req, res) => {
const categories = await db.Category.findAll();
return res.status(200).send({
categories,
});
}
};
23 changes: 23 additions & 0 deletions db/migrations/20190721215353-create-categories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Categories', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
unique: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: queryInterface => queryInterface.dropTable('Categories')
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.addColumn('Articles', 'categoryId', {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: null,
}),
down: queryInterface => queryInterface.removeColumn('Articles', 'categoryId')
};
5 changes: 5 additions & 0 deletions db/models/Article.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = (sequelize, DataTypes) => {
rating: DataTypes.FLOAT,
readTime: DataTypes.STRING,
flagged: DataTypes.BOOLEAN,
categoryId: DataTypes.INTEGER,
},
{}
);
Expand Down Expand Up @@ -63,6 +64,10 @@ module.exports = (sequelize, DataTypes) => {
as: 'reports',
cascade: true
});
Article.belongsTo(models.Category, {
foreignKey: 'categoryId',
as: 'category'
});
};
return Article;
};
12 changes: 12 additions & 0 deletions db/models/Category.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('Category', {
name: DataTypes.STRING
}, {});
Category.associate = (models) => {
Category.hasMany(models.Article, {
foreignKey: 'categoryId',
as: 'articles',
});
};
return Category;
};
24 changes: 24 additions & 0 deletions routes/v1/categories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import express from 'express';
import Category from '../../controllers/categories';
import Middleware from '../../middlewares';
import Validation from '../../validators/category';

const router = express.Router();

router.post(
'/',
Middleware.authenticate,
Middleware.isblackListedToken,
Middleware.isAdmin,
Validation.category,
Category.addCategory
);

router.get(
'/',
Middleware.authenticate,
Middleware.isblackListedToken,
Category.getCategories
);

export default router;
2 changes: 2 additions & 0 deletions routes/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import members from './members';
import role from './role';
import search from './search';
import report from './reports';
import category from './categories';

const router = express.Router();

Expand All @@ -21,5 +22,6 @@ router.use('/members', members);
router.use('/role', role);
router.use('/search', search);
router.use('/report', report);
router.use('/category', category);

export default router;
1 change: 1 addition & 0 deletions swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ definitions:
- description
- body
- images
- categoryId
properties:
title:
type: string
Expand Down
2 changes: 2 additions & 0 deletions tests/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ export const editComment = async editedComment => db.CommentHistory.create(edite

export const createCommentHistory = async editedComment => db.CommentHistory.create(editedComment);
export const createReport = async report => db.Report.create(report);

export const createCategory = async category => db.Category.create(category);
32 changes: 30 additions & 2 deletions tests/routes/articles.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import sinon from 'sinon';
import chaiHttp from 'chai-http';
import { app, db } from '../../server';
import {
createUser, createArticle, createRate, createArticleVote, createComment, createCommentHistory
createUser, createArticle, createRate, createArticleVote, createComment, createCommentHistory,
createCategory
} from '../helpers';
import * as utils from '../../utils';
import { transporter } from '../../utils/mailer';
Expand All @@ -17,16 +18,19 @@ let register;
let mockUploadImage;
let mockDeleteImage;
let ratingUser;
let category;

describe('ARTICLES TEST', () => {
before(async () => {
mockTransporter = sinon.stub(transporter, 'sendMail').resolves({});
});
beforeEach(async () => {
category = await createCategory({ name: 'comms' });
article = {
title: 'React course by hamza',
description: 'very good book',
body: 'learning react is good for your career...'
body: 'learning react is good for your career...',
categoryId: category.id
};
register = {
firstName: 'vincent',
Expand All @@ -40,6 +44,7 @@ describe('ARTICLES TEST', () => {
await db.ArticleTag.destroy({ truncate: true, cascade: true });
await db.Tag.destroy({ truncate: true, cascade: true });
await db.Comment.destroy({ truncate: true, cascade: true });
await db.Category.destroy({ truncate: true, cascade: true });
});

after(async () => {
Expand All @@ -50,6 +55,7 @@ describe('ARTICLES TEST', () => {
await db.ArticleTag.destroy({ truncate: true, cascade: true });
await db.Tag.destroy({ truncate: true, cascade: true });
await db.Comment.destroy({ truncate: true, cascade: true });
await db.Category.destroy({ truncate: true, cascade: true });
});

describe('Create articles', () => {
Expand All @@ -62,6 +68,7 @@ describe('ARTICLES TEST', () => {
const user = await createUser(register);
const userResponse = user.response();
const { token } = userResponse;
category = await createCategory({ name: 'tech' });
const res = await chai
.request(app)
.post('/api/v1/articles')
Expand All @@ -70,6 +77,7 @@ describe('ARTICLES TEST', () => {
.field('description', 'very good book')
.field('body', 'learning react is good for your career...')
.field('tags', 'Javascript')
.field('categoryId', category.id)
.attach('image', `${__dirname}/test.jpg`)
.set('x-access-token', token);
expect(res.statusCode).to.equal(201);
Expand All @@ -85,6 +93,24 @@ describe('ARTICLES TEST', () => {
expect(res.body.article.tagList).to.have.length(1);
expect(res.body.article.tagList[0]).to.equal('javascript');
});
it('should not create an article if category Id does not exit', async () => {
const user = await createUser(register);
const userResponse = user.response();
const { token } = userResponse;
const res = await chai
.request(app)
.post('/api/v1/articles')
.field('Content-Type', 'multipart/form-data')
.field('title', 'React course by hamza')
.field('description', 'very good book')
.field('body', 'learning react is good for your career...')
.field('tags', 'Javascript')
.field('categoryId', 345678)
.attach('image', `${__dirname}/test.jpg`)
.set('x-access-token', token);
expect(res.statusCode).to.equal(400);
expect(res.body.message).to.equal('category does not Exist');
});
it('should not create an article if info is not complete', async () => {
const user = await createUser(register);
const userResponse = user.response();
Expand Down Expand Up @@ -130,13 +156,15 @@ describe('ARTICLES TEST', () => {
const user = await createUser(register);
const userResponse = user.response();
const { token } = userResponse;
category = await createCategory({ name: 'tech' });
const res = await chai
.request(app)
.post('/api/v1/articles')
.field('Content-Type', 'multipart/form-data')
.field('title', 'React course by hamza')
.field('description', 'very good book')
.field('body', 'learning react is good for your career...')
.field('categoryId', category.id)
.attach('image', `${__dirname}/test.jpg`)
.set('x-access-token', token);
expect(res.statusCode).to.equal(201);
Expand Down
Loading

0 comments on commit 0f01a09

Please sign in to comment.