Skip to content

Commit

Permalink
Merge 08fd2ad into 35d53b4
Browse files Browse the repository at this point in the history
  • Loading branch information
Veraclins committed Aug 15, 2018
2 parents 35d53b4 + 08fd2ad commit 26285f6
Show file tree
Hide file tree
Showing 21 changed files with 356 additions and 71 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ logs
*.log
.DS_Store
.nyc_output
.vs
.vs/

npm-debug.log*

Expand Down
Binary file removed .vs/slnx.sqlite
Binary file not shown.
11 changes: 1 addition & 10 deletions server/auth/init.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import passport from 'passport';
import models from '../models';

const userModel = models.User;

const init = () => {
passport.serializeUser((user, done) => {
done(null, user.id);
});

passport.deserializeUser((id, done) => {
userModel.findById(id, (err, user) => {
done(err, user);
});
done(null, user);
});
};

Expand Down
4 changes: 1 addition & 3 deletions server/controllers/ArticleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import models from '../models';
import randomString from '../helpers/randomString';
import dashReplace from '../helpers/replaceDash';

const {
Article
} = models;
const { Article } = models;
/**
* This class contains all the methods responsible for creating and querying
* articles on the app
Expand Down
20 changes: 13 additions & 7 deletions server/controllers/AuthController.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,9 @@ export default class AuthController {
};
}
if (user.email === email) {
return {
email: [`User with email: ${email} already exists.`],
};
return { email: [`User with email: ${email} already exists.`] };
}
return {
username: [`User with username: ${username} already exists.`],
};
return { username: [`User with username: ${username} already exists.`] };
}

/**
Expand Down Expand Up @@ -121,6 +117,16 @@ export default class AuthController {
});
}

/**
* This method is responsible for structuring the user information from
* user login/signup through the passport Google
* authentication strategy.
* @param {object} accessToken the user token sent by Google
* @param {object} refreshToken the user refresh token sent by Google
* @param {object} profile the user profile object containing the user information from linkedIn
* @param {*} done the callback function that completes passport auth and attach
* the user object to the request
*/
static googleCallback(accessToken, refreshToken, profile, done) {
const displayName = profile.displayName.split(' ').join('-');
User.findOrCreate({
Expand Down Expand Up @@ -172,7 +178,7 @@ export default class AuthController {
if (created) {
return done(null, user);
}
return done(null, user);
return done(null, false);
});
}

Expand Down
9 changes: 0 additions & 9 deletions server/controllers/PasswordResetController.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,6 @@ export default class PasswordResetController {
// Send User Email Method Using SendGrid
PasswordResetController.resetProcessEmail(req, res, resetConfirmMessage, message);
});
} else {
return res.status(404).send({
status: 'fail',
errors: {
email: [
'The email you provided does not exist, please check again.',
],
},
});
}
})
.catch(err => next(err));
Expand Down
93 changes: 93 additions & 0 deletions server/controllers/RatingController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import models from '../models';

const { Article, Rating } = models;
/**
* This class contains all the methods responsible for rating
* articles on the app
* It is made up static methods which can be called from anywhere in the app.
*/
export default class RatingController {
/**
* Prep the request that is sent and get the information needed to rate
* the article.
* @param {object} req the request object
* @param {object} res the response object
*/
static prepRating(req, res) {
const { id: userId, username } = req.user;
const { slug, rating, author } = req.params;
if (username === author) {
return res.status(403).send({
status: 'error',
errors: {
user: ['You cannot rate your own article']
}
});
}
let star = rating < 1 ? 1 : rating;
star = star > 5 ? 5 : star;
return {
userId, slug, star
};
}

/**
* Prepares the response to be sent to the user.
* @param {object} rating the rating object returned from the database
* @param {object} res the response object
* @param {boolean} updated a boolean that indicates if it is a new rating or an
* updated rating (used to set the appropriate status code)
*/
static ratingResponse(rating, res, updated = false) {
const statusCode = updated ? 200 : 201;
const {
userId, value, slug
} = rating.dataValues;
return res.status(statusCode).send({
status: 'success',
rating: {
userId, value, slug
},
});
}

/**
* Rate an article and return the rating (number of stars).
* @param {object} req the request object
* @param {object} res the response object
*/
static rateArticle(req, res) {
const ratingInfo = RatingController.prepRating(req, res);
const { userId, slug, star } = ratingInfo;
if (!slug) return;
Article.findOne({
where: { slug },
attributes: ['id'],
})
.then((article) => {
if (!article) {
return res.status(404).send({
status: 'error',
errors: {
article: ['No article with the given URL. Please check the URL again']
}
});
}
const { id: articleId } = article.dataValues;
Rating.findOne({ where: { articleId, userId } })
.then((rating) => {
if (!rating) {
return Rating.create({ userId, articleId, value: star })
.then(newRating => RatingController.ratingResponse(newRating, res));
}
return Rating.update({ value: star }, {
returning: true,
where: { articleId, userId },
})
.then(([, [updatedRating]]) => {
RatingController.ratingResponse(updatedRating, res, true);
});
});
});
}
}
3 changes: 2 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ app.use('/api', router);
app.all('*', (req, res) => {
res.status(404).json({
status: 'error',
message: 'Oh-oh! Seems like the page you requested does not exist. Please check the url again.',
message: 'Oh-oh! Seems like the page you requested does not exist. Please check the URL again.',
});
});

// Error handler
// no stack traces leaked to user in production
app.use((err, req, res) => {
res.status(err.status || 500);
Expand Down
12 changes: 10 additions & 2 deletions server/migrations/20180806205808-create-rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ module.exports = {
},
userId: {
type: Sequelize.INTEGER,
allowNull: false
allowNull: false,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Users',
key: 'id',
as: 'userId'
},
},
articleId: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
Expand All @@ -22,7 +30,7 @@ module.exports = {
},
},
value: {
type: Sequelize.STRING,
type: Sequelize.INTEGER,
allowNull: false
},
createdAt: {
Expand Down
15 changes: 13 additions & 2 deletions server/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export default (sequelize, DataTypes) => {
allowNull: false,
unique: true
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
Expand All @@ -17,13 +21,18 @@ export default (sequelize, DataTypes) => {
type: DataTypes.STRING,
allowNull: true
},
});
}, {});
Article.associate = (models) => {
Article.belongsTo(models.User, {
foreignKey: 'userId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
Article.belongsTo(models.Category, {
foreignKey: 'categoryId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
Article.hasMany(models.Comment, {
foreignKey: 'articleId',
as: 'comments',
Expand All @@ -35,8 +44,10 @@ export default (sequelize, DataTypes) => {
as: 'tags'
});
Article.hasMany(models.Rating, {
foreignKey: 'ratingId',
foreignKey: 'articleId',
as: 'ratings',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
});
Article.belongsTo(models.Category, {
foreignKey: 'categoryId',
Expand Down
1 change: 1 addition & 0 deletions server/models/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default (sequelize, DataTypes) => {
});
Category.associate = (models) => {
Category.hasMany(models.Article, {
foreignKey: 'categoryId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
Expand Down
17 changes: 16 additions & 1 deletion server/models/rating.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@

export default (sequelize, DataTypes) => {
const Rating = sequelize.define('Rating', {
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
articleId: {
type: DataTypes.INTEGER,
allowNull: false
},
value: {
type: DataTypes.STRING,
type: DataTypes.INTEGER,
allowNull: false
},
}, {});
Expand All @@ -11,6 +20,12 @@ export default (sequelize, DataTypes) => {
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
Rating.belongsTo(models.User, {
foreignKey: 'userId',
as: 'rater',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
return Rating;
};
4 changes: 4 additions & 0 deletions server/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export default (sequelize, DataTypes) => {
foreignKey: 'userId',
as: 'comments',
});
User.hasMany(models.Rating, {
foreignKey: 'userId',
as: 'ratings',
});
};

return User;
Expand Down
22 changes: 13 additions & 9 deletions server/routes/article.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { Router } from 'express';
import ArticleController from '../controllers/ArticleController';
import userAuthenticate from '../middlewares/isLoggedIn';
import isLoggedIn from '../middlewares/isLoggedIn';
import ArticleValidation from '../middlewares/validations/ArticleValidation';
import CommentValidation from '../middlewares/validations/CommentValidation';
import CommentController from '../controllers/CommentController';
import RatingController from '../controllers/RatingController';

const articleRouter = Router();

articleRouter.post('/', userAuthenticate, ArticleValidation.validateCreateArticle, ArticleController.createArticle);
articleRouter.post('/', isLoggedIn, ArticleValidation.validateCreateArticle, ArticleController.createArticle);

articleRouter.put('/:slug', userAuthenticate, ArticleValidation.validateUpdateArticle, ArticleController.updateArticle);
articleRouter.put('/:slug', isLoggedIn, ArticleValidation.validateUpdateArticle, ArticleController.updateArticle);

articleRouter.delete('/:slug', userAuthenticate, ArticleController.removeArticle);
articleRouter.delete('/:slug', isLoggedIn, ArticleController.removeArticle);

articleRouter.post('/:slug/comments', userAuthenticate, CommentValidation.validateComment, CommentController.createComment);
articleRouter.post('/:slug/comments', isLoggedIn, CommentValidation.validateComment, CommentController.createComment);

articleRouter.get('/:slug/comments', userAuthenticate, CommentController.getComments);
articleRouter.get('/:slug/comments', isLoggedIn, CommentController.getComments);

articleRouter.get('/:slug/comments/:id', userAuthenticate, CommentController.getComment);
articleRouter.get('/:slug/comments/:id', isLoggedIn, CommentController.getComment);

articleRouter.put('/:slug/comments/:id', userAuthenticate, CommentValidation.validateComment, CommentController.updateComment);
articleRouter.put('/:slug/comments/:id', isLoggedIn, CommentValidation.validateComment, CommentController.updateComment);

articleRouter.delete('/:slug/comments/:id', isLoggedIn, CommentController.deleteComment);

articleRouter.post('/:author/:slug/:rating', isLoggedIn, RatingController.rateArticle);

articleRouter.delete('/:slug/comments/:id', userAuthenticate, CommentController.deleteComment);

export default articleRouter;
10 changes: 10 additions & 0 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ authRouter.post('/signup', userValidator.signupValidation, AuthController.signUp
authRouter.post('/login', userValidator.loginValidation, AuthController.login);


/**
* Handles email verification url
*/
authRouter.get('/verify', VerifyController.activateUser);

/**
* Handles email verification url resend
*/
authRouter.post('/verify', userValidator.emailValidation, VerifyController.resendVerificationEmail);

// passport mock route
authRouter.get('/mock', passport.authenticate('mock'));

Expand Down
Loading

0 comments on commit 26285f6

Please sign in to comment.