mirror of
https://github.com/kevin-DL/complete-node-bootcamp.git
synced 2026-01-16 13:14:35 +00:00
Initial commit 🚀
This commit is contained in:
241
4-natours/after-section-14/controllers/authController.js
Normal file
241
4-natours/after-section-14/controllers/authController.js
Normal file
@@ -0,0 +1,241 @@
|
||||
const crypto = require('crypto');
|
||||
const { promisify } = require('util');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('./../models/userModel');
|
||||
const catchAsync = require('./../utils/catchAsync');
|
||||
const AppError = require('./../utils/appError');
|
||||
const Email = require('./../utils/email');
|
||||
|
||||
const signToken = id => {
|
||||
return jwt.sign({ id }, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRES_IN
|
||||
});
|
||||
};
|
||||
|
||||
const createSendToken = (user, statusCode, req, res) => {
|
||||
const token = signToken(user._id);
|
||||
|
||||
res.cookie('jwt', token, {
|
||||
expires: new Date(
|
||||
Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
|
||||
),
|
||||
httpOnly: true,
|
||||
secure: req.secure || req.headers('x-forwarded-proto') === 'https'
|
||||
});
|
||||
|
||||
// Remove password from output
|
||||
user.password = undefined;
|
||||
|
||||
res.status(statusCode).json({
|
||||
status: 'success',
|
||||
token,
|
||||
data: {
|
||||
user
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.signup = catchAsync(async (req, res, next) => {
|
||||
const newUser = await User.create(req.body);
|
||||
|
||||
const url = `${req.protocol}://${req.get('host')}/me`;
|
||||
// console.log(url);
|
||||
await new Email(newUser, url).sendWelcome();
|
||||
|
||||
createSendToken(newUser, 201, req, res);
|
||||
});
|
||||
|
||||
exports.login = catchAsync(async (req, res, next) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// 1) Check if email and password exist
|
||||
if (!email || !password) {
|
||||
return next(new AppError('Please provide email and password!', 400));
|
||||
}
|
||||
// 2) Check if user exists && password is correct
|
||||
const user = await User.findOne({ email }).select('+password');
|
||||
|
||||
if (!user || !(await user.correctPassword(password, user.password))) {
|
||||
return next(new AppError('Incorrect email or password', 401));
|
||||
}
|
||||
|
||||
// 3) If everything ok, send token to client
|
||||
createSendToken(user, 200, req, res);
|
||||
});
|
||||
|
||||
exports.logout = (req, res) => {
|
||||
res.cookie('jwt', 'loggedout', {
|
||||
expires: new Date(Date.now() + 10 * 1000),
|
||||
httpOnly: true
|
||||
});
|
||||
res.status(200).json({ status: 'success' });
|
||||
};
|
||||
|
||||
exports.protect = catchAsync(async (req, res, next) => {
|
||||
// 1) Getting token and check of it's there
|
||||
let token;
|
||||
if (
|
||||
req.headers.authorization &&
|
||||
req.headers.authorization.startsWith('Bearer')
|
||||
) {
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
} else if (req.cookies.jwt) {
|
||||
token = req.cookies.jwt;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return next(
|
||||
new AppError('You are not logged in! Please log in to get access.', 401)
|
||||
);
|
||||
}
|
||||
|
||||
// 2) Verification token
|
||||
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
|
||||
|
||||
// 3) Check if user still exists
|
||||
const currentUser = await User.findById(decoded.id);
|
||||
if (!currentUser) {
|
||||
return next(
|
||||
new AppError(
|
||||
'The user belonging to this token does no longer exist.',
|
||||
401
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 4) Check if user changed password after the token was issued
|
||||
if (currentUser.changedPasswordAfter(decoded.iat)) {
|
||||
return next(
|
||||
new AppError('User recently changed password! Please log in again.', 401)
|
||||
);
|
||||
}
|
||||
|
||||
// GRANT ACCESS TO PROTECTED ROUTE
|
||||
req.user = currentUser;
|
||||
res.locals.user = currentUser;
|
||||
next();
|
||||
});
|
||||
|
||||
// Only for rendered pages, no errors!
|
||||
exports.isLoggedIn = async (req, res, next) => {
|
||||
if (req.cookies.jwt) {
|
||||
try {
|
||||
// 1) verify token
|
||||
const decoded = await promisify(jwt.verify)(
|
||||
req.cookies.jwt,
|
||||
process.env.JWT_SECRET
|
||||
);
|
||||
|
||||
// 2) Check if user still exists
|
||||
const currentUser = await User.findById(decoded.id);
|
||||
if (!currentUser) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// 3) Check if user changed password after the token was issued
|
||||
if (currentUser.changedPasswordAfter(decoded.iat)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// THERE IS A LOGGED IN USER
|
||||
res.locals.user = currentUser;
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
exports.restrictTo = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
// roles ['admin', 'lead-guide']. role='user'
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return next(
|
||||
new AppError('You do not have permission to perform this action', 403)
|
||||
);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
exports.forgotPassword = catchAsync(async (req, res, next) => {
|
||||
// 1) Get user based on POSTed email
|
||||
const user = await User.findOne({ email: req.body.email });
|
||||
if (!user) {
|
||||
return next(new AppError('There is no user with email address.', 404));
|
||||
}
|
||||
|
||||
// 2) Generate the random reset token
|
||||
const resetToken = user.createPasswordResetToken();
|
||||
await user.save({ validateBeforeSave: false });
|
||||
|
||||
// 3) Send it to user's email
|
||||
try {
|
||||
const resetURL = `${req.protocol}://${req.get(
|
||||
'host'
|
||||
)}/api/v1/users/resetPassword/${resetToken}`;
|
||||
await new Email(user, resetURL).sendPasswordReset();
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
message: 'Token sent to email!'
|
||||
});
|
||||
} catch (err) {
|
||||
user.passwordResetToken = undefined;
|
||||
user.passwordResetExpires = undefined;
|
||||
await user.save({ validateBeforeSave: false });
|
||||
|
||||
return next(
|
||||
new AppError('There was an error sending the email. Try again later!'),
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
exports.resetPassword = catchAsync(async (req, res, next) => {
|
||||
// 1) Get user based on the token
|
||||
const hashedToken = crypto
|
||||
.createHash('sha256')
|
||||
.update(req.params.token)
|
||||
.digest('hex');
|
||||
|
||||
const user = await User.findOne({
|
||||
passwordResetToken: hashedToken,
|
||||
passwordResetExpires: { $gt: Date.now() }
|
||||
});
|
||||
|
||||
// 2) If token has not expired, and there is user, set the new password
|
||||
if (!user) {
|
||||
return next(new AppError('Token is invalid or has expired', 400));
|
||||
}
|
||||
user.password = req.body.password;
|
||||
user.passwordConfirm = req.body.passwordConfirm;
|
||||
user.passwordResetToken = undefined;
|
||||
user.passwordResetExpires = undefined;
|
||||
await user.save();
|
||||
|
||||
// 3) Update changedPasswordAt property for the user
|
||||
// 4) Log the user in, send JWT
|
||||
createSendToken(user, 200, req, res);
|
||||
});
|
||||
|
||||
exports.updatePassword = catchAsync(async (req, res, next) => {
|
||||
// 1) Get user from collection
|
||||
const user = await User.findById(req.user.id).select('+password');
|
||||
|
||||
// 2) Check if POSTed current password is correct
|
||||
if (!(await user.correctPassword(req.body.passwordCurrent, user.password))) {
|
||||
return next(new AppError('Your current password is wrong.', 401));
|
||||
}
|
||||
|
||||
// 3) If so, update password
|
||||
user.password = req.body.password;
|
||||
user.passwordConfirm = req.body.passwordConfirm;
|
||||
await user.save();
|
||||
// User.findByIdAndUpdate will NOT work as intended!
|
||||
|
||||
// 4) Log user in, send JWT
|
||||
createSendToken(user, 200, req, res);
|
||||
});
|
||||
75
4-natours/after-section-14/controllers/bookingController.js
Normal file
75
4-natours/after-section-14/controllers/bookingController.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||||
const Tour = require('../models/tourModel');
|
||||
const User = require('../models/userModel');
|
||||
const Booking = require('../models/bookingModel');
|
||||
const catchAsync = require('../utils/catchAsync');
|
||||
const factory = require('./handlerFactory');
|
||||
|
||||
exports.getCheckoutSession = catchAsync(async (req, res, next) => {
|
||||
// 1) Get the currently booked tour
|
||||
const tour = await Tour.findById(req.params.tourId);
|
||||
// console.log(tour);
|
||||
|
||||
// 2) Create checkout session
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
// success_url: `${req.protocol}://${req.get('host')}/my-tours/?tour=${
|
||||
// req.params.tourId
|
||||
// }&user=${req.user.id}&price=${tour.price}`,
|
||||
success_url: `${req.protocol}://${req.get('host')}/my-tours?alert=booking`,
|
||||
cancel_url: `${req.protocol}://${req.get('host')}/tour/${tour.slug}`,
|
||||
customer_email: req.user.email,
|
||||
client_reference_id: req.params.tourId,
|
||||
line_items: [
|
||||
{
|
||||
name: `${tour.name} Tour`,
|
||||
description: tour.summary,
|
||||
images: [
|
||||
`${req.protocol}://${req.get('host')}/img/tours/${tour.imageCover}`
|
||||
],
|
||||
amount: tour.price * 100,
|
||||
currency: 'usd',
|
||||
quantity: 1
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 3) Create session as response
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
session
|
||||
});
|
||||
});
|
||||
|
||||
const createBookingCheckout = async session => {
|
||||
const tour = session.client_reference_id;
|
||||
const user = (await User.findOne({ email: session.customer_email })).id;
|
||||
const price = session.display_items[0].amount / 100;
|
||||
await Booking.create({ tour, user, price });
|
||||
};
|
||||
|
||||
exports.webhookCheckout = (req, res, next) => {
|
||||
const signature = req.headers['stripe-signature'];
|
||||
|
||||
let event;
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
req.body,
|
||||
signature,
|
||||
process.env.STRIPE_WEBHOOK_SECRET
|
||||
);
|
||||
} catch (err) {
|
||||
return res.status(400).send(`Webhook error: ${err.message}`);
|
||||
}
|
||||
|
||||
if (event.type === 'checkout.session.completed')
|
||||
createBookingCheckout(event.data.object);
|
||||
|
||||
res.status(200).json({ received: true });
|
||||
};
|
||||
|
||||
exports.createBooking = factory.createOne(Booking);
|
||||
exports.getBooking = factory.getOne(Booking);
|
||||
exports.getAllBookings = factory.getAll(Booking);
|
||||
exports.updateBooking = factory.updateOne(Booking);
|
||||
exports.deleteBooking = factory.deleteOne(Booking);
|
||||
106
4-natours/after-section-14/controllers/errorController.js
Normal file
106
4-natours/after-section-14/controllers/errorController.js
Normal file
@@ -0,0 +1,106 @@
|
||||
const AppError = require('./../utils/appError');
|
||||
|
||||
const handleCastErrorDB = err => {
|
||||
const message = `Invalid ${err.path}: ${err.value}.`;
|
||||
return new AppError(message, 400);
|
||||
};
|
||||
|
||||
const handleDuplicateFieldsDB = err => {
|
||||
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
|
||||
|
||||
const message = `Duplicate field value: ${value}. Please use another value!`;
|
||||
return new AppError(message, 400);
|
||||
};
|
||||
|
||||
const handleValidationErrorDB = err => {
|
||||
const errors = Object.values(err.errors).map(el => el.message);
|
||||
|
||||
const message = `Invalid input data. ${errors.join('. ')}`;
|
||||
return new AppError(message, 400);
|
||||
};
|
||||
|
||||
const handleJWTError = () =>
|
||||
new AppError('Invalid token. Please log in again!', 401);
|
||||
|
||||
const handleJWTExpiredError = () =>
|
||||
new AppError('Your token has expired! Please log in again.', 401);
|
||||
|
||||
const sendErrorDev = (err, req, res) => {
|
||||
// A) API
|
||||
if (req.originalUrl.startsWith('/api')) {
|
||||
return res.status(err.statusCode).json({
|
||||
status: err.status,
|
||||
error: err,
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
}
|
||||
|
||||
// B) RENDERED WEBSITE
|
||||
console.error('ERROR 💥', err);
|
||||
return res.status(err.statusCode).render('error', {
|
||||
title: 'Something went wrong!',
|
||||
msg: err.message
|
||||
});
|
||||
};
|
||||
|
||||
const sendErrorProd = (err, req, res) => {
|
||||
// A) API
|
||||
if (req.originalUrl.startsWith('/api')) {
|
||||
// A) Operational, trusted error: send message to client
|
||||
if (err.isOperational) {
|
||||
return res.status(err.statusCode).json({
|
||||
status: err.status,
|
||||
message: err.message
|
||||
});
|
||||
}
|
||||
// B) Programming or other unknown error: don't leak error details
|
||||
// 1) Log error
|
||||
console.error('ERROR 💥', err);
|
||||
// 2) Send generic message
|
||||
return res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Something went very wrong!'
|
||||
});
|
||||
}
|
||||
|
||||
// B) RENDERED WEBSITE
|
||||
// A) Operational, trusted error: send message to client
|
||||
if (err.isOperational) {
|
||||
return res.status(err.statusCode).render('error', {
|
||||
title: 'Something went wrong!',
|
||||
msg: err.message
|
||||
});
|
||||
}
|
||||
// B) Programming or other unknown error: don't leak error details
|
||||
// 1) Log error
|
||||
console.error('ERROR 💥', err);
|
||||
// 2) Send generic message
|
||||
return res.status(err.statusCode).render('error', {
|
||||
title: 'Something went wrong!',
|
||||
msg: 'Please try again later.'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = (err, req, res, next) => {
|
||||
// console.log(err.stack);
|
||||
|
||||
err.statusCode = err.statusCode || 500;
|
||||
err.status = err.status || 'error';
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
sendErrorDev(err, req, res);
|
||||
} else if (process.env.NODE_ENV === 'production') {
|
||||
let error = { ...err };
|
||||
error.message = err.message;
|
||||
|
||||
if (error.name === 'CastError') error = handleCastErrorDB(error);
|
||||
if (error.code === 11000) error = handleDuplicateFieldsDB(error);
|
||||
if (error.name === 'ValidationError')
|
||||
error = handleValidationErrorDB(error);
|
||||
if (error.name === 'JsonWebTokenError') error = handleJWTError();
|
||||
if (error.name === 'TokenExpiredError') error = handleJWTExpiredError();
|
||||
|
||||
sendErrorProd(error, req, res);
|
||||
}
|
||||
};
|
||||
90
4-natours/after-section-14/controllers/handlerFactory.js
Normal file
90
4-natours/after-section-14/controllers/handlerFactory.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const catchAsync = require('./../utils/catchAsync');
|
||||
const AppError = require('./../utils/appError');
|
||||
const APIFeatures = require('./../utils/apiFeatures');
|
||||
|
||||
exports.deleteOne = Model =>
|
||||
catchAsync(async (req, res, next) => {
|
||||
const doc = await Model.findByIdAndDelete(req.params.id);
|
||||
|
||||
if (!doc) {
|
||||
return next(new AppError('No document found with that ID', 404));
|
||||
}
|
||||
|
||||
res.status(204).json({
|
||||
status: 'success',
|
||||
data: null
|
||||
});
|
||||
});
|
||||
|
||||
exports.updateOne = Model =>
|
||||
catchAsync(async (req, res, next) => {
|
||||
const doc = await Model.findByIdAndUpdate(req.params.id, req.body, {
|
||||
new: true,
|
||||
runValidators: true
|
||||
});
|
||||
|
||||
if (!doc) {
|
||||
return next(new AppError('No document found with that ID', 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
data: doc
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.createOne = Model =>
|
||||
catchAsync(async (req, res, next) => {
|
||||
const doc = await Model.create(req.body);
|
||||
|
||||
res.status(201).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
data: doc
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.getOne = (Model, popOptions) =>
|
||||
catchAsync(async (req, res, next) => {
|
||||
let query = Model.findById(req.params.id);
|
||||
if (popOptions) query = query.populate(popOptions);
|
||||
const doc = await query;
|
||||
|
||||
if (!doc) {
|
||||
return next(new AppError('No document found with that ID', 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
data: doc
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.getAll = Model =>
|
||||
catchAsync(async (req, res, next) => {
|
||||
// To allow for nested GET reviews on tour (hack)
|
||||
let filter = {};
|
||||
if (req.params.tourId) filter = { tour: req.params.tourId };
|
||||
|
||||
const features = new APIFeatures(Model.find(filter), req.query)
|
||||
.filter()
|
||||
.sort()
|
||||
.limitFields()
|
||||
.paginate();
|
||||
// const doc = await features.query.explain();
|
||||
const doc = await features.query;
|
||||
|
||||
// SEND RESPONSE
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
results: doc.length,
|
||||
data: {
|
||||
data: doc
|
||||
}
|
||||
});
|
||||
});
|
||||
16
4-natours/after-section-14/controllers/reviewController.js
Normal file
16
4-natours/after-section-14/controllers/reviewController.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const Review = require('./../models/reviewModel');
|
||||
const factory = require('./handlerFactory');
|
||||
// const catchAsync = require('./../utils/catchAsync');
|
||||
|
||||
exports.setTourUserIds = (req, res, next) => {
|
||||
// Allow nested routes
|
||||
if (!req.body.tour) req.body.tour = req.params.tourId;
|
||||
if (!req.body.user) req.body.user = req.user.id;
|
||||
next();
|
||||
};
|
||||
|
||||
exports.getAllReviews = factory.getAll(Review);
|
||||
exports.getReview = factory.getOne(Review);
|
||||
exports.createReview = factory.createOne(Review);
|
||||
exports.updateReview = factory.updateOne(Review);
|
||||
exports.deleteReview = factory.deleteOne(Review);
|
||||
223
4-natours/after-section-14/controllers/tourController.js
Normal file
223
4-natours/after-section-14/controllers/tourController.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const Tour = require('./../models/tourModel');
|
||||
const catchAsync = require('./../utils/catchAsync');
|
||||
const factory = require('./handlerFactory');
|
||||
const AppError = require('./../utils/appError');
|
||||
|
||||
const multerStorage = multer.memoryStorage();
|
||||
|
||||
const multerFilter = (req, file, cb) => {
|
||||
if (file.mimetype.startsWith('image')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new AppError('Not an image! Please upload only images.', 400), false);
|
||||
}
|
||||
};
|
||||
|
||||
const upload = multer({
|
||||
storage: multerStorage,
|
||||
fileFilter: multerFilter
|
||||
});
|
||||
|
||||
exports.uploadTourImages = upload.fields([
|
||||
{ name: 'imageCover', maxCount: 1 },
|
||||
{ name: 'images', maxCount: 3 }
|
||||
]);
|
||||
|
||||
// upload.single('image') req.file
|
||||
// upload.array('images', 5) req.files
|
||||
|
||||
exports.resizeTourImages = catchAsync(async (req, res, next) => {
|
||||
if (!req.files.imageCover || !req.files.images) return next();
|
||||
|
||||
// 1) Cover image
|
||||
req.body.imageCover = `tour-${req.params.id}-${Date.now()}-cover.jpeg`;
|
||||
await sharp(req.files.imageCover[0].buffer)
|
||||
.resize(2000, 1333)
|
||||
.toFormat('jpeg')
|
||||
.jpeg({ quality: 90 })
|
||||
.toFile(`public/img/tours/${req.body.imageCover}`);
|
||||
|
||||
// 2) Images
|
||||
req.body.images = [];
|
||||
|
||||
await Promise.all(
|
||||
req.files.images.map(async (file, i) => {
|
||||
const filename = `tour-${req.params.id}-${Date.now()}-${i + 1}.jpeg`;
|
||||
|
||||
await sharp(file.buffer)
|
||||
.resize(2000, 1333)
|
||||
.toFormat('jpeg')
|
||||
.jpeg({ quality: 90 })
|
||||
.toFile(`public/img/tours/${filename}`);
|
||||
|
||||
req.body.images.push(filename);
|
||||
})
|
||||
);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
exports.aliasTopTours = (req, res, next) => {
|
||||
req.query.limit = '5';
|
||||
req.query.sort = '-ratingsAverage,price';
|
||||
req.query.fields = 'name,price,ratingsAverage,summary,difficulty';
|
||||
next();
|
||||
};
|
||||
|
||||
exports.getAllTours = factory.getAll(Tour);
|
||||
exports.getTour = factory.getOne(Tour, { path: 'reviews' });
|
||||
exports.createTour = factory.createOne(Tour);
|
||||
exports.updateTour = factory.updateOne(Tour);
|
||||
exports.deleteTour = factory.deleteOne(Tour);
|
||||
|
||||
exports.getTourStats = catchAsync(async (req, res, next) => {
|
||||
const stats = await Tour.aggregate([
|
||||
{
|
||||
$match: { ratingsAverage: { $gte: 4.5 } }
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: { $toUpper: '$difficulty' },
|
||||
numTours: { $sum: 1 },
|
||||
numRatings: { $sum: '$ratingsQuantity' },
|
||||
avgRating: { $avg: '$ratingsAverage' },
|
||||
avgPrice: { $avg: '$price' },
|
||||
minPrice: { $min: '$price' },
|
||||
maxPrice: { $max: '$price' }
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { avgPrice: 1 }
|
||||
}
|
||||
// {
|
||||
// $match: { _id: { $ne: 'EASY' } }
|
||||
// }
|
||||
]);
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
stats
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.getMonthlyPlan = catchAsync(async (req, res, next) => {
|
||||
const year = req.params.year * 1; // 2021
|
||||
|
||||
const plan = await Tour.aggregate([
|
||||
{
|
||||
$unwind: '$startDates'
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
startDates: {
|
||||
$gte: new Date(`${year}-01-01`),
|
||||
$lte: new Date(`${year}-12-31`)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: { $month: '$startDates' },
|
||||
numTourStarts: { $sum: 1 },
|
||||
tours: { $push: '$name' }
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: { month: '$_id' }
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { numTourStarts: -1 }
|
||||
},
|
||||
{
|
||||
$limit: 12
|
||||
}
|
||||
]);
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
plan
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// /tours-within/:distance/center/:latlng/unit/:unit
|
||||
// /tours-within/233/center/34.111745,-118.113491/unit/mi
|
||||
exports.getToursWithin = catchAsync(async (req, res, next) => {
|
||||
const { distance, latlng, unit } = req.params;
|
||||
const [lat, lng] = latlng.split(',');
|
||||
|
||||
const radius = unit === 'mi' ? distance / 3963.2 : distance / 6378.1;
|
||||
|
||||
if (!lat || !lng) {
|
||||
next(
|
||||
new AppError(
|
||||
'Please provide latitutr and longitude in the format lat,lng.',
|
||||
400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const tours = await Tour.find({
|
||||
startLocation: { $geoWithin: { $centerSphere: [[lng, lat], radius] } }
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
results: tours.length,
|
||||
data: {
|
||||
data: tours
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.getDistances = catchAsync(async (req, res, next) => {
|
||||
const { latlng, unit } = req.params;
|
||||
const [lat, lng] = latlng.split(',');
|
||||
|
||||
const multiplier = unit === 'mi' ? 0.000621371 : 0.001;
|
||||
|
||||
if (!lat || !lng) {
|
||||
next(
|
||||
new AppError(
|
||||
'Please provide latitutr and longitude in the format lat,lng.',
|
||||
400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const distances = await Tour.aggregate([
|
||||
{
|
||||
$geoNear: {
|
||||
near: {
|
||||
type: 'Point',
|
||||
coordinates: [lng * 1, lat * 1]
|
||||
},
|
||||
distanceField: 'distance',
|
||||
distanceMultiplier: multiplier
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
distance: 1,
|
||||
name: 1
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
data: distances
|
||||
}
|
||||
});
|
||||
});
|
||||
111
4-natours/after-section-14/controllers/userController.js
Normal file
111
4-natours/after-section-14/controllers/userController.js
Normal file
@@ -0,0 +1,111 @@
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const User = require('./../models/userModel');
|
||||
const catchAsync = require('./../utils/catchAsync');
|
||||
const AppError = require('./../utils/appError');
|
||||
const factory = require('./handlerFactory');
|
||||
|
||||
// const multerStorage = multer.diskStorage({
|
||||
// destination: (req, file, cb) => {
|
||||
// cb(null, 'public/img/users');
|
||||
// },
|
||||
// filename: (req, file, cb) => {
|
||||
// const ext = file.mimetype.split('/')[1];
|
||||
// cb(null, `user-${req.user.id}-${Date.now()}.${ext}`);
|
||||
// }
|
||||
// });
|
||||
const multerStorage = multer.memoryStorage();
|
||||
|
||||
const multerFilter = (req, file, cb) => {
|
||||
if (file.mimetype.startsWith('image')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new AppError('Not an image! Please upload only images.', 400), false);
|
||||
}
|
||||
};
|
||||
|
||||
const upload = multer({
|
||||
storage: multerStorage,
|
||||
fileFilter: multerFilter
|
||||
});
|
||||
|
||||
exports.uploadUserPhoto = upload.single('photo');
|
||||
|
||||
exports.resizeUserPhoto = catchAsync(async (req, res, next) => {
|
||||
if (!req.file) return next();
|
||||
|
||||
req.file.filename = `user-${req.user.id}-${Date.now()}.jpeg`;
|
||||
|
||||
await sharp(req.file.buffer)
|
||||
.resize(500, 500)
|
||||
.toFormat('jpeg')
|
||||
.jpeg({ quality: 90 })
|
||||
.toFile(`public/img/users/${req.file.filename}`);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
const filterObj = (obj, ...allowedFields) => {
|
||||
const newObj = {};
|
||||
Object.keys(obj).forEach(el => {
|
||||
if (allowedFields.includes(el)) newObj[el] = obj[el];
|
||||
});
|
||||
return newObj;
|
||||
};
|
||||
|
||||
exports.getMe = (req, res, next) => {
|
||||
req.params.id = req.user.id;
|
||||
next();
|
||||
};
|
||||
|
||||
exports.updateMe = catchAsync(async (req, res, next) => {
|
||||
// 1) Create error if user POSTs password data
|
||||
if (req.body.password || req.body.passwordConfirm) {
|
||||
return next(
|
||||
new AppError(
|
||||
'This route is not for password updates. Please use /updateMyPassword.',
|
||||
400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 2) Filtered out unwanted fields names that are not allowed to be updated
|
||||
const filteredBody = filterObj(req.body, 'name', 'email');
|
||||
if (req.file) filteredBody.photo = req.file.filename;
|
||||
|
||||
// 3) Update user document
|
||||
const updatedUser = await User.findByIdAndUpdate(req.user.id, filteredBody, {
|
||||
new: true,
|
||||
runValidators: true
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
data: {
|
||||
user: updatedUser
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.deleteMe = catchAsync(async (req, res, next) => {
|
||||
await User.findByIdAndUpdate(req.user.id, { active: false });
|
||||
|
||||
res.status(204).json({
|
||||
status: 'success',
|
||||
data: null
|
||||
});
|
||||
});
|
||||
|
||||
exports.createUser = (req, res) => {
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'This route is not defined! Please use /signup instead'
|
||||
});
|
||||
};
|
||||
|
||||
exports.getUser = factory.getOne(User);
|
||||
exports.getAllUsers = factory.getAll(User);
|
||||
|
||||
// Do NOT update passwords with this!
|
||||
exports.updateUser = factory.updateOne(User);
|
||||
exports.deleteUser = factory.deleteOne(User);
|
||||
89
4-natours/after-section-14/controllers/viewsController.js
Normal file
89
4-natours/after-section-14/controllers/viewsController.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const Tour = require('../models/tourModel');
|
||||
const User = require('../models/userModel');
|
||||
const Booking = require('../models/bookingModel');
|
||||
const catchAsync = require('../utils/catchAsync');
|
||||
const AppError = require('../utils/appError');
|
||||
|
||||
exports.alerts = (req, res, next) => {
|
||||
const { alert } = req.query;
|
||||
if (alert === 'booking')
|
||||
res.locals.alert =
|
||||
"Your booking was successful! Please check your email for a confirmation. If your booking doesn't show up here immediatly, please come back later.";
|
||||
next();
|
||||
};
|
||||
|
||||
exports.getOverview = catchAsync(async (req, res, next) => {
|
||||
// 1) Get tour data from collection
|
||||
const tours = await Tour.find();
|
||||
|
||||
// 2) Build template
|
||||
// 3) Render that template using tour data from 1)
|
||||
res.status(200).render('overview', {
|
||||
title: 'All Tours',
|
||||
tours
|
||||
});
|
||||
});
|
||||
|
||||
exports.getTour = catchAsync(async (req, res, next) => {
|
||||
// 1) Get the data, for the requested tour (including reviews and guides)
|
||||
const tour = await Tour.findOne({ slug: req.params.slug }).populate({
|
||||
path: 'reviews',
|
||||
fields: 'review rating user'
|
||||
});
|
||||
|
||||
if (!tour) {
|
||||
return next(new AppError('There is no tour with that name.', 404));
|
||||
}
|
||||
|
||||
// 2) Build template
|
||||
// 3) Render template using data from 1)
|
||||
res.status(200).render('tour', {
|
||||
title: `${tour.name} Tour`,
|
||||
tour
|
||||
});
|
||||
});
|
||||
|
||||
exports.getLoginForm = (req, res) => {
|
||||
res.status(200).render('login', {
|
||||
title: 'Log into your account'
|
||||
});
|
||||
};
|
||||
|
||||
exports.getAccount = (req, res) => {
|
||||
res.status(200).render('account', {
|
||||
title: 'Your account'
|
||||
});
|
||||
};
|
||||
|
||||
exports.getMyTours = catchAsync(async (req, res, next) => {
|
||||
// 1) Find all bookings
|
||||
const bookings = await Booking.find({ user: req.user.id });
|
||||
|
||||
// 2) Find tours with the returned IDs
|
||||
const tourIDs = bookings.map(el => el.tour);
|
||||
const tours = await Tour.find({ _id: { $in: tourIDs } });
|
||||
|
||||
res.status(200).render('overview', {
|
||||
title: 'My Tours',
|
||||
tours
|
||||
});
|
||||
});
|
||||
|
||||
exports.updateUserData = catchAsync(async (req, res, next) => {
|
||||
const updatedUser = await User.findByIdAndUpdate(
|
||||
req.user.id,
|
||||
{
|
||||
name: req.body.name,
|
||||
email: req.body.email
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
runValidators: true
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).render('account', {
|
||||
title: 'Your account',
|
||||
user: updatedUser
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user