Skip to content

Commit

Permalink
Merge 95fada4 into c8236b2
Browse files Browse the repository at this point in the history
  • Loading branch information
madeofhuman committed Aug 14, 2018
2 parents c8236b2 + 95fada4 commit 2c4098a
Show file tree
Hide file tree
Showing 16 changed files with 642 additions and 20 deletions.
22 changes: 11 additions & 11 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"presets": ["env"],
"sourceMaps": true,
"retainLines": true,
"env": {
"test": {
"plugins": [
"istanbul"
]
}
}
}
"presets": ["env"],
"sourceMaps": true,
"retainLines": true,
"env": {
"test": {
"plugins": [
"istanbul"
]
}
}
}
Binary file added .vs/slnx.sqlite
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"start": "babel-node server/index.js --presets babel-preset-env",
"dev": "nodemon server/index.js --exec babel-node --presets babel-preset-env",
"migrate:test": "sequelize db:migrate:undo:all --env=test && sequelize db:migrate --env=test && sequelize db:seed:all --env=test",
"migrate:dev": "sequelize db:migrate:undo && sequelize db:migrate",
"migrate:dev": "sequelize db:migrate",
"unmigrate:dev": "sequelize db:migrate:undo:all",
"seed:all": "sequelize db:seed:all",
"unseed:all": "sequelize db:seed:undo:all"
},
Expand Down
1 change: 1 addition & 0 deletions server/config/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
database: process.env.DB_NAME || 'elven_ah_dev',
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || '5432',
// operatorsAliases: false,
},
test: {
dialect: 'postgres',
Expand Down
239 changes: 239 additions & 0 deletions server/controllers/CommentController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import models from '../models';

const { Comment, User, Article } = models;

/**
* This class contains all the methods responsible for creating and querying
* comments on the app
* It is made up static methods which can be called from anywhere in the app.
*/
export default class CommentController {
/**
* Create a comment and return the data.
* @param {object} req the request object
* @param {object} res the response object
* @returns {object} the comment that was created.
*/
static async createComment(req, res) {
const { id, username } = req.user;
const { body } = req.body;
const parentId = req.query.id === undefined ? null : req.query.id;
const { slug } = req.params;
const article = await CommentController.getArticleFromSlug(slug);
if (article !== null) {
return Comment.create({
articleId: article.id,
userId: id,
parentId,
body,
}).then(newComment => CommentController.commentResponse(res, newComment, slug, username));
}
res.status(400).json({
status: 'fail',
message: 'Unable to create comment because article does not exist.',
});
}

/**
* Get all comments with one level reply nesting.
* @param {object} req the request object
* @param {object} res the response object
* @returns {object} an object containing an array of all comments.
*/
static getComments(req, res) {
Comment.findAll({
include: [
{ model: User, as: 'commenter' },
]
}).then((comments) => {
const destructuredComments = comments.map(comment => Object.assign(
{},
{
id: comment.id,
parentId: comment.parentId,
createdAt: new Date(comment.createdAt).toLocaleString('en-GB', { hour12: true }),
updatedAt: new Date(comment.updatedAt).toLocaleString('en-GB', { hour12: true }),
body: comment.body,
author: {
username: comment.commenter.username,
bio: comment.commenter.bio,
image: comment.commenter.image,
},
}
));
const result = CommentController.nestComments(destructuredComments);
res.status(200).json({
status: 'success',
comments: result,
});
}).catch(() => {
res.status(400).json({
status: 'fail',
message: 'Unable to get comments.',
});
});
}

/**
* Get a comment by its id
* @param {object} req the request object
* @param {object} res the response object
* @returns {object} an object containing an array of all comments.
*/
static getComment(req, res, next) {
Comment.findById((req.params.id), {
include: [
{ model: User, as: 'commenter' },
]
}).then((comment) => {
if (!comment) {
res.status(404).json({
status: 'fail',
message: 'Unable to get the comment with supplied id.',
});
}
res.status(200).json({
status: 'success',
comment: {
id: comment.id,
parentId: comment.parentId,
createdAt: new Date(comment.createdAt).toLocaleString('en-GB', { hour12: true }),
updatedAt: new Date(comment.updatedAt).toLocaleString('en-GB', { hour12: true }),
body: comment.body,
author: {
username: comment.commenter.username,
bio: comment.commenter.bio,
image: comment.commenter.image,
},
}
});
}).catch(err => next(err));
}

/**
* Update a comment
* @param {object} req the request object
* @param {object} res the response object
* @returns {object} the updated comment.
*/
static updateComment(req, res, next) {
Comment.findById(parseInt(req.params.id, 10))
.then((comment) => {
if (!comment) {
return res.status(404).json({
status: 'fail',
message: 'No comment found, please check the id supplied.',
});
}
Comment.update({
body: req.body.body,
}, {
returning: true,
where: { id: comment.id },
})
.then(([, [updatedComment]]) => {
res.status(200).json({
status: 'success',
comment: {
id: updatedComment.id,
parentId: updatedComment.parentId,
createdAt: new Date(updatedComment.createdAt).toLocaleString('en-GB', { hour12: true }),
updatedAt: new Date(updatedComment.updatedAt).toLocaleString('en-GB', { hour12: true }),
body: updatedComment.body,
author: updatedComment.author,
}
});
}).catch(() => res.status(400).json({
status: 'fail',
message: 'Unable to update comment',
}));
}).catch(err => next(err));
}

/**
* Delete a comment
* @param {object} req the request object
* @param {object} res the response object
* @returns {null}
*/
static deleteComment(req, res) {
Comment.findById(parseInt(req.params.id, 10))
.then((comment) => {
if (!comment) {
return res.status(404).json({
status: 'fail',
message: 'No comment with the supplied id found.',
});
}
Comment.destroy({ where: { id: comment.id } })
.then(() => res.status(200).json({
status: 'success',
message: 'Comment deleted.'
}));
}).catch(() => res.status(400).json({
status: 'fail',
message: 'Invalid comment id supplied.',
}));
}

/**
* One-level nest an array of comments and replies
* @param {object} req the request object
* @param {object} res the response object
* @returns {array} an array of nested comments.
*/
static nestComments(comments) {
const mainComments = comments.filter(comment => comment.parentId === null);
const replies = comments.filter(comment => comment.parentId !== null);
const result = [];
mainComments.forEach((comment) => {
comment.replies = replies.filter(reply => reply.parentId === comment.id);
result.push(comment);
});
return result;
}

/**
* Return the article that has the supplied slug
* @param {object} req the request object
* @param {object} res the response object
* @returns {promise} the article object found
*/
static getArticleFromSlug(slug) {
return new Promise((resolve, reject) => {
Article.findOne({
where: { slug },
attributes: [
'id', 'title', 'userId', 'slug', 'body',
'imageUrl', 'categoryId', 'createdAt', 'updatedAt'
],
})
.then(article => resolve(article))
.catch(err => reject(err));
});
}

/**
* Formats and sends the response to the user when a comment is created.
* @param {object} res the response object
* @param {object} newComment the created comment
* @param {string} slug the slug of the article to which the comment belongs
* @param {string} username the author of the comment
* @returns {object} the comment that was created.
*/
static commentResponse(res, newComment, slug, username) {
return res.status(201).json({
status: 'success',
message: 'Comment has been created',
comment: {
id: newComment.id,
parentId: newComment.parentId,
createdAt: new Date(newComment.createdAt).toLocaleString('en-GB', { hour12: true }),
updatedAt: new Date(newComment.updatedAt).toLocaleString('en-GB', { hour12: true }),
body: newComment.body,
article: slug,
author: username,
},
});
}
}
32 changes: 32 additions & 0 deletions server/middlewares/validations/CommentValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Validator from 'validatorjs';

/**
* @export
* @class Validation
*/
export default class CommentValidation {
/**
* Validate data for comment creation
*
* @param {object} req - HTTP Request
* @param {object} res - HTTP Response
* @param {function} next
* @returns {object} Class instance
* @memberof Validation
*/
static validateComment(req, res, next) {
const commentProperties = {
body: 'required|string',
};

const validator = new Validator(req.body, commentProperties);
validator.passes(() => next());
validator.fails(() => {
const errors = validator.errors.all();
return res.status(400).json({
status: 'error',
errors,
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module.exports = {
defaultValue: false,
},
bio: {
type: Sequelize.STRING,
type: Sequelize.TEXT,
allowNull: true,
},
image: {
Expand Down
2 changes: 1 addition & 1 deletion server/migrations/20180806205551-create-article.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ module.exports = {
allowNull: false
},
imageUrl: {
type: Sequelize.BLOB,
type: Sequelize.STRING,
allowNull: true
},
createdAt: {
Expand Down
10 changes: 9 additions & 1 deletion server/migrations/20180806205745-create-comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ module.exports = {
},
userId: {
type: Sequelize.INTEGER,
allowNull: false
allowNull: false,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Users',
key: 'id',
as: 'userId'
},

},
parentId: {
type: Sequelize.INTEGER,
Expand Down
8 changes: 5 additions & 3 deletions server/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default (sequelize, DataTypes) => {
allowNull: false
},
imageUrl: {
type: DataTypes.BLOB,
type: DataTypes.STRING,
allowNull: true
},
});
Expand All @@ -25,8 +25,10 @@ export default (sequelize, DataTypes) => {
onUpdate: 'CASCADE'
});
Article.hasMany(models.Comment, {
foreignKey: 'commentId',
as: 'comments'
foreignKey: 'articleId',
as: 'comments',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
Article.hasMany(models.Tag, {
foreignKey: 'tagId',
Expand Down
1 change: 0 additions & 1 deletion server/models/category.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

export default (sequelize, DataTypes) => {
const Category = sequelize.define('Category', {
title: {
Expand Down
Loading

0 comments on commit 2c4098a

Please sign in to comment.