Skip to content

Commit

Permalink
added reviews and security middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
roshangrewal committed Jun 22, 2020
1 parent 43a9ef7 commit e1e63b1
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 5 deletions.
16 changes: 15 additions & 1 deletion controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/async');
const sendEmail = require('../utils/sendEmail');
const User = require('../models/User');
const { reset } = require('nodemon');

// @desc Register user
// @route POST /api/v1/auth/register
Expand Down Expand Up @@ -72,6 +71,21 @@ exports.getMe = asyncHandler(async (req, res, next) => {
});
});

// @desc Log user out / clear cookies
// @route GET /api/v1/auth/logout
// @access Private
exports.logout = asyncHandler(async (req, res, next) => {
res.cookie('token', 'none', {
expires: new Date(Date.now() + 10 * 1000),
httpOnly: true,
});

res.status(200).json({
success: true,
data: {},
});
});

// @desc Update user details
// @route PUT /api/v1/auth/updatedetails
// @access Private
Expand Down
125 changes: 125 additions & 0 deletions controllers/reviews.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/async');
const Review = require('../models/Review');
const Bootcamp = require('../models/Bootcamp');

// @desc Get reviews
// @route GET /api/v1/reviews
// @route GET /api/v1/bootcamps/:bootcampId/reviews
// @access Public
exports.getReviews = asyncHandler(async (req, res, next) => {
if (req.params.bootcampId) {
const reviews = await Review.find({ bootcamp: req.params.bootcampId });

return res.status(200).json({
success: true,
count: reviews.length,
data: reviews,
});
} else {
res.status(200).json(res.advancedResults);
}
});

// @desc Get single review
// @route GET /api/v1/reviews/:id
// @access Public
exports.getReview = asyncHandler(async (req, res, next) => {
const review = await Review.findById(req.params.id).populate({
path: 'bootcamp',
select: 'name description',
});

if (!review) {
return next(
new ErrorResponse(`No review found with the id of ${req.params.id}`, 404)
);
}

res.status(200).json({
success: true,
data: review,
});
});

// @desc Add review
// @route POST /api/v1/bootcamps/:bootcampId/reviews
// @access Private
exports.addReview = asyncHandler(async (req, res, next) => {
req.body.bootcamp = req.params.bootcampId;

req.body.user = req.user.id;

const bootcamp = await Bootcamp.findById(req.params.bootcampId);

if (!bootcamp) {
return next(
new ErrorResponse(
`No bootcamp with the id of ${req.params.bootcampId}`,
404
)
);
}

const review = await Review.create(req.body);

res.status(201).json({
success: true,
data: review,
});
});

// @desc Update review
// @route PUT /api/v1/reviews/:id
// @access Private
exports.updateReview = asyncHandler(async (req, res, next) => {
let review = await Review.findById(req.params.id);

if (!review) {
return next(
new ErrorResponse(`No review with the id of ${req.params.id}`, 404)
);
}

// Make sure review belongs to user or user is admin
if (review.user.toString() !== req.user.id && req.user.role !== 'admin') {
return next(new ErrorResponse(`Not authorized to update review`, 401));
}

review = await Review.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});

review.save();

res.status(200).json({
success: true,
data: review,
});
});

// @desc Delete review
// @route DELETE /api/v1/reviews/:id
// @access Private
exports.deleteReview = asyncHandler(async (req, res, next) => {
const review = await Review.findById(req.params.id);

if (!review) {
return next(
new ErrorResponse(`No review with the id of ${req.params.id}`, 404)
);
}

// Make sure review belongs to user or user is admin
if (review.user.toString() !== req.user.id && req.user.role !== 'admin') {
return next(new ErrorResponse(`Not authorized to update review`, 401));
}

await review.remove();

res.status(200).json({
success: true,
data: {},
});
});
5 changes: 3 additions & 2 deletions middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ exports.protect = asyncHandler(async (req, res, next) => {
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
// Set token from Bearer token in header
token = req.headers.authorization.split(' ')[1];
}

// Set token from cookie
// else if (req.cookies.token) {
// token = req.cookies.token;
// }
Expand All @@ -33,7 +34,7 @@ exports.protect = asyncHandler(async (req, res, next) => {

next();
} catch (err) {
return next(new ErrorResponse('Not authorize to access this route', 401));
return next(new ErrorResponse('Not authorized to access this route', 401));
}
});

Expand Down
2 changes: 1 addition & 1 deletion middleware/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const errorHandler = (err, req, res, next) => {

// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = `Bootcamp not found with id of ${err.value}`;
const message = `Resource not found with id of ${err.value}`;
error = new ErrorResponse(message, 404);
}

Expand Down
75 changes: 75 additions & 0 deletions models/Review.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const mongoose = require('mongoose');

const ReviewSchema = new mongoose.Schema({
title: {
type: String,
trim: true,
required: [true, 'Please add a title for the review'],
maxlength: 100,
},
text: {
type: String,
required: [true, 'Please add a some text'],
},
rating: {
type: Number,
min: 1,
max: 10,
required: [true, 'Please add a rating between 1 to 10'],
},
createdAt: {
type: Date,
default: Date.now,
},
bootcamp: {
type: mongoose.Schema.ObjectId,
ref: 'Bootcamp',
required: true,
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
});

// Prevent user from submitting more than one review per bootcamp
ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true });

// Static method to get avg rating for bootcamp and save
ReviewSchema.statics.getAverageReview = async function (bootcampId) {
// console.log('Calculating avg Review...'.blue);

const obj = await this.aggregate([
{
$match: { bootcamp: bootcampId },
},
{
$group: {
_id: '$bootcamp',
averageRating: { $avg: '$rating' },
},
},
]);

// console.log(obj);
try {
await this.model('Bootcamp').findByIdAndUpdate(bootcampId, {
averageRating: obj[0].averageRating,
});
} catch (err) {
console.error(err);
}
};

// Call getAverageReview after save
ReviewSchema.post('save', function () {
this.constructor.getAverageReview(this.bootcamp);
});

// Call getAverageReview before remove
ReviewSchema.pre('remove', function () {
this.constructor.getAverageReview(this.bootcamp);
});

module.exports = mongoose.model('Review', ReviewSchema);
Loading

0 comments on commit e1e63b1

Please sign in to comment.