Skip to content

Commit

Permalink
Merge pull request #31 from andela/ft-user-create-article-164489786
Browse files Browse the repository at this point in the history
Ft user create article 164489786
  • Loading branch information
Cyril Ologho committed Apr 18, 2019
2 parents ccb9e0d + b6cef39 commit 56d23ec
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 20 deletions.
35 changes: 35 additions & 0 deletions controllers/article.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import models from '../models';
import Slug from '../helpers/slug';

const { article: ArticleModel } = models;
/**
* @description CRUD for article Class
*/
class Article {
/**
*@author: Innocent Nkunzi
* @param {Object} req
* @param {Object} res
* @returns {Object} Article
*/
static async createArticle(req, res) {
if (!req.body.title) {
return res.status(400).json({ error: 'title can not be null' });
}
const slugInstance = new Slug(req.body.title);
const descriptData = req.body.description || `${req.body.body.substring(0, 100)}...`;
try {
const {
title, body, taglist
} = req.body;
const slug = slugInstance.returnSlug(title);
const authorid = 100;
const newArticle = {
title, body, description: descriptData, slug, authorid, taglist
};
const article = await ArticleModel.createArticle(newArticle);
return res.status(201).json({ article });
} catch (error) { return res.status(400).json({ message: error.errors[0].message }); }
}
}
export default Article;
47 changes: 47 additions & 0 deletions helpers/slug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import slugify from 'slugify';
import hasha from 'hasha';
/**
* This class to maake a slug
*/
class Slug {
/**
* @author: Innocent Nkunzi
* @param {param} strings
* @returns {*} returns a slug
*/
constructor(strings) {
this.strings = strings;
this.randomNumber = Math.floor(Math.random() * 10000);
this.randomString = Math.random().toString(36).substr(2, 1);
this.date = new Date();
this.slugMaker();
}

/**
* @author: Innocent Nkunzi
* @returns {*} it returns a slug without an ID
*/
slugMaker() {
if (this.strings.length <= 40) {
return slugify(this.strings);
}
const slug = this.strings.substring(0, 40);
return slugify(slug);
}

/**
* @author: Innocent Nkunzi
* @returns {*} it returns a full slug with a hashed ID
*/
returnSlug() {
const slug = this.slugMaker();
const thisDate = Date.now().toString();

let nowHash = hasha(thisDate);
nowHash = nowHash.substring(0, 7);
const newSlug = `${slug}-${nowHash}`;
return newSlug;
}
}

export default Slug;
10 changes: 2 additions & 8 deletions migrations/20190403114724-create-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@ module.exports = {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
slug: { type: Sequelize.STRING, allowNull: false, unique: true },
title: { type: Sequelize.STRING, required: true },
description: { type: Sequelize.TEXT, allowNull: false },
description: { type: Sequelize.TEXT, allowNull: true },
body: { type: Sequelize.TEXT, required: true },
taglist: { type: Sequelize.ARRAY(Sequelize.STRING), defaultValue: [] },
authorid: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'users', key: 'id', onDelete: 'CASCADE', onUpdate: 'CASCADE'
}
},
authorid: { type: Sequelize.INTEGER, allowNull: false, references: { model: 'users', key: 'id', onDelete: 'CASCADE' } },
createdAt: { allowNull: false, type: Sequelize.DATE },
updatedAt: { allowNull: false, type: Sequelize.DATE },
}),
Expand Down
23 changes: 18 additions & 5 deletions models/article.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
module.exports = (sequelize, DataTypes) => {
import sequelizeTrasform from 'sequelize-transforms';

const ArticleModel = (sequelize, DataTypes) => {
const Article = sequelize.define('article', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
slug: { type: DataTypes.STRING, allowNull: false, unique: true },
title: { type: DataTypes.STRING, allowNull: false },
body: { type: DataTypes.TEXT, allowNull: false },
taglist: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true },
description: { type: DataTypes.TEXT, allowNull: true },
title: {
type: DataTypes.STRING,
allowNull: false,
trim: true,
validate: { len: { args: 5 }, notEmpty: true }
},
body: {
type: DataTypes.TEXT, trim: true, allowNull: false, validate: { len: { args: 255, msg: 'Body needs to be above 255 characters' }, notEmpty: true }
},
taglist: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true, defaultValue: [] },
description: { type: DataTypes.TEXT, trim: true },
authorid: { type: DataTypes.INTEGER, allowNull: false }
}, {});
sequelizeTrasform(Article);
Article.createArticle = article => Article.create(article);

Article.associate = (models) => {
Article.belongsTo(models.user, {
foreignKey: 'authorid', onDelete: 'CASCADE'
});
};
return Article;
};
export default ArticleModel;
1 change: 1 addition & 0 deletions models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import helper from '../helpers/helper';

const UserModel = (Sequelize, DataTypes) => {
const User = Sequelize.define('user', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
firstname: { type: DataTypes.STRING, allowNull: true },
lastname: { type: DataTypes.STRING, allowNull: true },
username: { type: DataTypes.STRING, allowNull: false },
Expand Down
3 changes: 2 additions & 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": {
"test": "NODE_ENV=test npm run migrate && nyc --reporter=html --reporter=text mocha --require @babel/polyfill --require @babel/register ./test/*.js --exit",
"test": "NODE_ENV=test nyc --reporter=html --reporter=text mocha --require @babel/polyfill --require @babel/register ./test/*.js --exit",
"start": "babel-node ./index.js",
"dev": "nodemon --exec babel-node ./index.js ",
"migrate": "node_modules/.bin/sequelize db:migrate:undo && node_modules/.bin/sequelize db:migrate",
Expand Down Expand Up @@ -47,6 +47,7 @@
"request": "^2.87.0",
"sequelize": "^4.42.0",
"sequelize-cli": "^5.4.0",
"sequelize-transforms": "^2.0.0",
"slug": "^1.0.0",
"slugify": "^1.3.4",
"swagger-ui-express": "^4.0.2",
Expand Down
3 changes: 2 additions & 1 deletion routes/api/articles.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import express from 'express';
import articleController from '../../controllers/article';

const router = express.Router();

router.post('/');
router.post('/', articleController.createArticle);
router.get('/');
router.delete('/:slug');
router.put('/:slug');
Expand Down
117 changes: 117 additions & 0 deletions test/article.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import chai from 'chai';
import faker from 'faker';
import chaiHttp from 'chai-http';
import db from '../models';
import fakeData from './mockData/articleMockData';
import index from '../index';

const articleModel = db.article;
const userModel = db.user;

chai.should();
chai.use(chaiHttp);

/**
* @author: Innocent Nkunzi
* @description: tests related to article
*/

before('Cleaning the database first', async () => {
await articleModel.destroy({ truncate: true, cascade: true });
await userModel.destroy({ where: { email: userModel.email }, truncate: true, cascade: true });
});
const user = {
id: 100,
username: 'nkunziinnocent',
email: 'nkunzi@gmail.com',
password: '@Nkunzi1234',
};
describe('Create a user to be used in in creating article', () => {
it('should create a user', (done) => {
chai.request(index).post('/api/auth/signup').send(user).then((res) => {
res.should.have.status(200);
res.body.user.should.be.a('object');
res.body.user.should.have.property('username');
done();
})
.catch(err => err);
}).timeout(15000);
});
describe('Create an article', () => {
it('should create an article', (done) => {
chai.request(index).post('/api/articles').send(fakeData).then((res) => {
res.should.have.status(201);
res.body.should.have.property('article');
res.body.article.should.be.a('object');
res.body.article.should.have.property('id');
res.body.article.should.have.property('slug');
res.body.article.should.have.property('title');
res.body.article.should.have.property('description');
res.body.article.should.have.property('createdAt');
res.body.article.should.have.property('updatedAt');
done();
})
.catch(err => err);
}).timeout(15000);
});
describe('It checks title errors', () => {
it('Should not create an article if the title is empty', (done) => {
const newArticle = {
title: '',
description: faker.lorem.paragraph(),
body: faker.lorem.paragraphs(),
};
chai.request(index).post('/api/articles').send(newArticle).then((res) => {
res.should.have.status(400);
res.body.should.be.a('object');
res.body.should.have.property('error').eql('title can not be null');
done();
})
.catch(err => err);
});
});
describe('Test the body', () => {
it('should not create and article if the body is empty', (done) => {
const newArticle = {
title: faker.random.words(),
description: faker.lorem.paragraph(),
body: ''
};
chai.request(index).post('/api/articles').send(newArticle).then((res) => {
res.should.have.status(400);
res.body.should.be.a('object');
// res.body.should.have.property('error').eql('The body can\'t be empty');
done();
})
.catch(err => err);
});
it('should return an error if the body is not predefined', (done) => {
const longTitleArticle = {
title: faker.lorem.sentences(),
description: faker.lorem.paragraph(),
};
chai.request(index).post('/api/articles').send(longTitleArticle).then((res) => {
res.should.have.status(400);
res.body.should.be.a('object');
res.body.should.have.property('message').eql('article.body cannot be null');
done();
})
.catch(err => err);
});
});
describe('Test the title', () => {
it('should substring a long title to only 40 characters', (done) => {
const longTitleArticle = {
title: faker.lorem.sentence(),
body: faker.lorem.paragraphs(),
description: faker.lorem.paragraph(),
};
chai.request(index).post('/api/articles').send(longTitleArticle).then((res) => {
res.should.have.status(201);
res.body.should.be.a('object');
res.body.should.have.property('article');
done();
})
.catch(err => err);
});
});
6 changes: 3 additions & 3 deletions test/mockData/articleMockData.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import faker from 'faker';

module.exports = {
title: faker.random.words(),
description: faker.lorem.sentences(),
body: faker.lorem.sentences(),
authorid: faker.random.number(),
description: faker.lorem.paragraphs(),
body: faker.lorem.paragraphs(),
authorid: 100
};
4 changes: 2 additions & 2 deletions test/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('Test User', () => {
done();
})
.catch(error => logError(error));
});
}).timeout(15000);

it('Should not create user if both email and username are taken', (done) => {
chai.request(app).post('/api/auth/signup').send(user).then((res) => {
Expand All @@ -47,7 +47,7 @@ describe('Test User', () => {
done();
})
.catch(error => logError(error));
});
}).timeout(15000);

it('Should not create user if username is taken', (done) => {
user.email = 'email@tes1.com';
Expand Down

0 comments on commit 56d23ec

Please sign in to comment.