diff --git a/public/404.html b/public/404.html
new file mode 100644
index 0000000..efacd41
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+ 404
+
+
+
+
+
+
+
+ Oh no!!
+
+ You’re either misspelling the URL
+ or requesting a page that's no longer here.
+
+
+
+
+
+
diff --git a/server.js b/server.js
index 9825592..4ebaefc 100644
--- a/server.js
+++ b/server.js
@@ -1,5 +1,27 @@
+const mongoose = require('mongoose');
const express = require('express');
+const dotenv = require('dotenv');
+const path = require('path');
+
+dotenv.config({ path: './config.env' });
+
const app = express();
+
+const DB = process.env.DATABASE.replace('', process.env.DATABASE_PASSWORD);
+
+mongoose
+ .connect(DB, {
+ useUnifiedTopology: true,
+ useNewUrlParser: true,
+ })
+ .then(() => console.log('DB connection successful!'));
+
+const api = require('./src/api');
+
+const port = process.env.PORT || 3000;
+app.listen(port, () => {
+ console.log(`App running on port ${port}...`);
+=======
const api = require('./src/api');
const path = require('path');
const cloudinary = require('cloudinary');
@@ -12,23 +34,18 @@ cloudinary.config({
api_secret: process.env.CLOUDINARY_SECRET_KEY,
});
-mongoose.connect(process.env.MONGODB_CONNECT_STRING);
app.use(cors());
-app.listen('3000', () => {
- console.log('Running');
-});
+// ROUTING
app.use(express.json());
app.use('/api/v1', api);
+// ERROR HANDLER
app.use((req, res) => {
- res.status(404).json({
- statusCode: 404,
- message: 'API not found',
- });
+ res.status(404).sendFile(path.join(__dirname, '/public/404.html'));
});
app.use((error, req, res, next) => {
diff --git a/src/api/auth/auth.controller.js b/src/api/auth/auth.controller.js
index d1c21ff..c5f7754 100644
--- a/src/api/auth/auth.controller.js
+++ b/src/api/auth/auth.controller.js
@@ -1,82 +1,50 @@
-const { promisify } = require('util');
-const jwt = require('jsonwebtoken');
-const User = require('../models/userModel');
-const catchAsync = require('../utils/catchAsync');
-const AppError = require('../utils/appError');
-
-const signToken = (id) => {
- return jwt.sign({ id }, process.env.JWT_SECRET, {
- expiresIn: process.env.JWT_EXPIRES_IN,
- });
+const authService = require('./auth.service');
+
+module.exports = {
+ signup: async (req, res, next) => {
+ try {
+ res.send(await authService.signup(req.body));
+ } catch (error) {
+ next(error);
+ }
+ },
+ login: async (req, res, next) => {
+ try {
+ res.send(await authService.login(req.body));
+ } catch (error) {
+ next(error);
+ }
+ },
+ protect: async (req, res, next) => {
+ try {
+ await authService.protect(req);
+ next();
+ } catch (error) {
+ next(error);
+ }
+ },
+ forgotPassword: async (req, res, next) => {
+ try {
+ let DTO = await authService.forgotPassword(req);
+ res.status(200).json(DTO);
+ } catch (error) {
+ next(error);
+ }
+ },
+ resetPassword: async (req, res, next) => {
+ try {
+ let DTO = await authService.resetPassword(req.body, req.params.token);
+ res.status(200).json(DTO);
+ } catch (error) {
+ next(error);
+ }
+ },
+ updatePassword: async (req, res, next) => {
+ try {
+ let DTO = await authService.updatePassword(req.user.id, req.body);
+ res.status(200).json(DTO);
+ } catch (error) {
+ next(error);
+ }
+ },
};
-
-exports.signup = catchAsync(async (req, res, next) => {
- const newUser = await User.create({
- name: req.body.name,
- email: req.body.email,
- password: req.body.password,
- passwordConfirm: req.body.passwordConfirm,
- });
-
- const token = signToken(newUser.id);
-
- res.status(201).json({
- status: 'success',
- token,
- data: {
- user: newUser,
- },
- });
-});
-
-exports.login = async (req, res, next) => {
- const { email, password } = req.body;
-
- // 1) Check if email and password exist
- if (!email || !password) {
- next(new AppError('Please provide both an email and password!', 400));
- }
- // 2) Check if user exists and password are 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
- const token = signToken(user._id);
- res.status(200).json({
- status: 'success',
- token,
- });
-};
-
-exports.protect = catchAsync(async (req, res, next) => {
- let token;
- // 1) Getting token and check of it's there
- if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
- token = req.headers.authorization.split(' ')[1];
- }
-
- 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 token 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;
- next();
-});
diff --git a/src/api/auth/auth.service.js b/src/api/auth/auth.service.js
index e69de29..3a42793 100644
--- a/src/api/auth/auth.service.js
+++ b/src/api/auth/auth.service.js
@@ -0,0 +1,189 @@
+const crypto = require('crypto');
+const { promisify } = require('util');
+const jwt = require('jsonwebtoken');
+const User = require('./../../models/userModel');
+const AppError = require('./../../common/appError');
+const sendEmail = require('./../../common/email');
+
+const signToken = (id) => {
+ return jwt.sign({ id }, process.env.JWT_SECRET, {
+ expiresIn: process.env.JWT_EXPIRES_IN,
+ });
+};
+
+// const createSendToken = (user, 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;
+
+// return {
+// msg: 'Success',
+// };
+// };
+
+exports.signup = async (body) => {
+ try {
+ const user = await User.findOne({ email: body.email });
+ if (user) {
+ throw new AppError(409, 'Email already exists! Please try another.');
+ }
+ await User.create({
+ name: body.name,
+ email: body.email,
+ password: body.password,
+ passwordConfirm: body.passwordConfirm,
+ });
+ // createSendToken(newUser, req, res);
+ return {
+ statusCode: 200,
+ message: 'Your account has been created',
+ };
+ } catch (error) {
+ errorStatusCode = error.statusCode ? error.statusCode : 500;
+ throw new AppError(errorStatusCode, error.message);
+ }
+};
+
+exports.login = async (body) => {
+ try {
+ const { email, password } = body;
+ // 1) Check if email and password exist
+ if (!email || !password) {
+ throw 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))) {
+ throw new AppError(401, 'Incorrect email or password');
+ }
+
+ // 3) If everything ok, send token to client
+ // createSendToken(user, req, res);
+
+ const token = signToken(user._id);
+ user.password = undefined;
+ return {
+ statusCode: 200,
+ message: 'Your account has logged in',
+ token,
+ data: user,
+ };
+ } catch (error) {
+ errorStatusCode = error.statusCode ? error.statusCode : 500;
+ throw new AppError(errorStatusCode, error.message);
+ }
+};
+
+exports.forgotPassword = async (req) => {
+ // 1) Get user based on POST email
+ const user = await User.findOne({ email: req.body.email });
+ if (!user) {
+ throw new AppError(404, 'There is no user with email address.');
+ }
+
+ // 2) Generate the random reset token
+ const resetToken = user.createPasswordResetToken();
+ await user.save({ validateBeforeSave: false });
+
+ // 3) Send it to user's email
+ const resetURL = `${req.protocol}://${req.get('host')}/api/v1/users/resetPassword/${resetToken}`;
+
+ const message = `Forgot your password? Submit a PATCH request with your new password and passwordConfirm to: ${resetURL}.\nIf you didn't forget your password, please ignore this email!`;
+
+ try {
+ await sendEmail({
+ email: user.email,
+ subject: 'Your password reset token (valid for 10 min)',
+ message,
+ });
+
+ return {
+ statusCode: 200,
+ message: 'Token sent to email!',
+ };
+ } catch (error) {
+ user.passwordResetToken = undefined;
+ user.passwordResetExpires = undefined;
+ await user.save({ validateBeforeSave: false });
+
+ throw new AppError(500, 'There was an error sending the email. Try again later!');
+ }
+};
+
+exports.resetPassword = async (body, token) => {
+ try {
+ // 1) Get user based on the token
+ const hashedToken = crypto.createHash('sha256').update(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) {
+ throw new AppError(400, 'Token is invalid or has expired');
+ }
+ user.password = body.password;
+ user.passwordConfirm = 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, req, res);
+ token = signToken(user._id);
+ user.password = undefined;
+ return {
+ statusCode: 200,
+ message: 'Your password has been reset',
+ token,
+ data: user,
+ };
+ } catch {
+ errorStatusCode = error.statusCode ? error.statusCode : 500;
+ throw new AppError(errorStatusCode, error.message);
+ }
+};
+
+exports.updatePassword = async (userID, body) => {
+ try {
+ // 1) Get user from collection
+ const user = await User.findById(userID).select('+password');
+
+ // 2) Check if POSTed current password is correct
+ if (!(await user.correctPassword(body.passwordCurrent, user.password))) {
+ throw new AppError(401, 'Your current password is wrong.');
+ }
+
+ // 3) If so, update password
+ user.password = body.password;
+ user.passwordConfirm = body.passwordConfirm;
+ await user.save();
+
+ // 4) Log user in, send JWT
+ const token = signToken(user._id);
+ user.password = undefined;
+
+ return {
+ statusCode: 200,
+ message: 'Your password has been reset',
+ token,
+ data: user,
+ };
+
+ // createSendToken(user, req, res);
+ } catch (error) {
+ errorStatusCode = error.statusCode ? error.statusCode : 500;
+ throw new AppError(errorStatusCode, error.message);
+ }
+};
diff --git a/src/api/auth/index.js b/src/api/auth/index.js
index e69de29..4d1c729 100644
--- a/src/api/auth/index.js
+++ b/src/api/auth/index.js
@@ -0,0 +1,15 @@
+// route endpoint /auth
+const authController = require('./auth.controller');
+const router = require('express').Router();
+const { protectRoute } = require('../../common/protectRoute');
+// const { userPermission } = require('../../common/userPermission');
+
+router.post('/signup', authController.signup);
+router.post('/login', authController.login);
+
+router.route('/forgotPassword').post(authController.forgotPassword);
+router.patch('/resetPassword/:token', authController.resetPassword);
+
+router.route('/updateMyPassword').patch(protectRoute, authController.updatePassword);
+
+module.exports = router;
diff --git a/src/common/appError.js b/src/common/appError.js
new file mode 100644
index 0000000..21c5678
--- /dev/null
+++ b/src/common/appError.js
@@ -0,0 +1,14 @@
+class AppError extends Error {
+ constructor(statusCode, message) {
+ super();
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, AppError);
+ }
+
+ this.message = message;
+ this.statusCode = statusCode;
+ }
+}
+
+module.exports = AppError;
diff --git a/src/common/email.js b/src/common/email.js
new file mode 100644
index 0000000..4434ec9
--- /dev/null
+++ b/src/common/email.js
@@ -0,0 +1,28 @@
+const nodemailer = require('nodemailer');
+
+const sendEmail = async (options) => {
+ // 1) Create a transporter
+ const transporter = nodemailer.createTransport({
+ host: process.env.EMAIL_HOST,
+ port: process.env.EMAIL_PORT,
+ auth: {
+ user: process.env.EMAIL_USERNAME,
+ pass: process.env.EMAIL_PASSWORD,
+ },
+ // Activate in email "less secure app" option
+ });
+
+ // 2) Define the email options
+ const mailOptions = {
+ from: 'LongChau ',
+ to: options.email,
+ subject: options.subject,
+ text: options.message,
+ // html
+ };
+
+ // 3) Actually send the email
+ await transporter.sendMail(mailOptions);
+};
+
+module.exports = sendEmail;
diff --git a/src/common/protectRoute.js b/src/common/protectRoute.js
new file mode 100644
index 0000000..9dddf83
--- /dev/null
+++ b/src/common/protectRoute.js
@@ -0,0 +1,36 @@
+const jwt = require('jsonwebtoken');
+const AppError = require('./appError');
+const { promisify } = require('util');
+const User = require('./../models/userModel');
+
+exports.protectRoute = 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) {
+ throw new AppError(401, 'You are not logged in! Please log in to get access.');
+ }
+
+ // 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) {
+ throw new AppError(401, 'The user belonging to this token does no longer exist.');
+ }
+
+ // 4) Check if user changed password after the token was issued
+ if (currentUser.changedPasswordAfter(decoded.iat)) {
+ throw new AppError(401, 'User recently changed password! Please log in again.');
+ }
+
+ // GRANT ACCESS TO PROTECTED ROUTE
+ req.user = currentUser;
+ next();
+};
diff --git a/src/common/userPermission.js b/src/common/userPermission.js
new file mode 100644
index 0000000..2865c63
--- /dev/null
+++ b/src/common/userPermission.js
@@ -0,0 +1,10 @@
+const AppError = require('./appError');
+
+exports.userPermission = (...roles) => {
+ return (req, res, next) => {
+ if (!roles.includes(req.user.role)) {
+ throw new AppError(403, 'You do not have permission to perform this action');
+ }
+ next();
+ };
+};
diff --git a/src/models/order.js b/src/models/order.js
new file mode 100644
index 0000000..04d3c8d
--- /dev/null
+++ b/src/models/order.js
@@ -0,0 +1,25 @@
+const mongoose = require('mongoose');
+
+const Schema = mongoose.Schema;
+
+const orderSchema = new Schema({
+ products: [
+ {
+ product: { type: Object, required: true },
+ quantity: { type: Number, required: true },
+ },
+ ],
+ user: {
+ name: {
+ type: String,
+ required: true,
+ },
+ userId: {
+ type: mongoose.Schema.ObjectId,
+ ref: 'User',
+ required: true,
+ },
+ },
+});
+
+module.exports = mongoose.model('Order', orderSchema);
diff --git a/src/models/productModel.js b/src/models/productModel.js
index 4b9f054..f8ee4e7 100644
--- a/src/models/productModel.js
+++ b/src/models/productModel.js
@@ -31,6 +31,10 @@ const productSchema = mongoose.Schema({
type: Date,
default: Date.now,
},
+ countInStocks: {
+ type: Number,
+ required: true,
+ },
userID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
@@ -38,12 +42,4 @@ const productSchema = mongoose.Schema({
},
});
-productSchema.virtual('id').get(function () {
- return this._id.toHexString();
-});
-
-productSchema.set('toJSON', {
- virtuals: true,
-});
-
exports.Product = mongoose.model('Product', productSchema);
diff --git a/src/models/userModel.js b/src/models/userModel.js
index cd2165d..00106ca 100644
--- a/src/models/userModel.js
+++ b/src/models/userModel.js
@@ -110,16 +110,14 @@ userSchema.pre('save', function (next) {
next();
});
-userSchema.pre(/^find/, function (next) {
- // this points to the current query
- this.find({ active: { $ne: false } });
- next();
-});
-
userSchema.methods.correctPassword = async function (candidatePassword, userPassword) {
return await bcrypt.compare(candidatePassword, userPassword);
};
+userSchema.methods.duplicateEmail = async function (candidateEmail, userEmail) {
+ return candidateEmail === userEmail;
+};
+
userSchema.methods.changedPasswordAfter = function (JWTTimestamp) {
if (this.passwordChangedAt) {
const changedTimestamp = parseInt(this.passwordChangedAt.getTime() / 1000, 10);