Skip to content

Commit

Permalink
Improve controllers and services
Browse files Browse the repository at this point in the history
  • Loading branch information
hagopj13 committed May 12, 2020
1 parent f5b91d0 commit ffb47ae
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 173 deletions.
56 changes: 45 additions & 11 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,70 @@
const httpStatus = require('http-status');
const catchAsync = require('../utils/catchAsync');
const { authService, userService, emailService } = require('../services');
const { User, Token } = require('../models');
const { tokenService, emailService } = require('../services');
const AppError = require('../utils/AppError');

const register = catchAsync(async (req, res) => {
const user = await userService.createUser(req.body);
const tokens = await authService.generateAuthTokens(user.id);
if (await User.isEmailTaken(req.body.email)) {
throw new AppError(httpStatus.BAD_REQUEST, 'Email already taken');
}
const user = await User.create(req.body);
const tokens = await tokenService.generateAuthTokens(user.id);
const response = { user: user.transform(), tokens };
res.status(httpStatus.CREATED).send(response);
});

const login = catchAsync(async (req, res) => {
const user = await authService.loginUser(req.body.email, req.body.password);
const tokens = await authService.generateAuthTokens(user.id);
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.isPasswordMatch(password))) {
throw new AppError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
const tokens = await tokenService.generateAuthTokens(user.id);
const response = { user: user.transform(), tokens };
res.send(response);
});

const refreshTokens = catchAsync(async (req, res) => {
const tokens = await authService.refreshAuthTokens(req.body.refreshToken);
const response = { ...tokens };
res.send(response);
try {
const refreshTokenDoc = await tokenService.verifyToken(req.body.refreshToken, 'refresh');
const user = await User.findById(refreshTokenDoc.user);
if (!user) {
throw new Error();
}
await refreshTokenDoc.remove();
const tokens = await tokenService.generateAuthTokens(user.id);
const response = { ...tokens };
res.send(response);
} catch (error) {
throw new AppError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
});

const forgotPassword = catchAsync(async (req, res) => {
const resetPasswordToken = await authService.generateResetPasswordToken(req.body.email);
const user = await User.findOne({ email: req.body.email });
if (!user) {
throw new AppError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const resetPasswordToken = await tokenService.generateResetPasswordToken(user.id);
await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken);
res.status(httpStatus.NO_CONTENT).send();
});

const resetPassword = catchAsync(async (req, res) => {
await authService.resetPassword(req.query.token, req.body.password);
res.status(httpStatus.NO_CONTENT).send();
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(req.query.token, 'resetPassword');
const user = await User.findById(resetPasswordTokenDoc.user);
if (!user) {
throw new Error();
}
user.password = req.body.password;
await user.save();
await Token.deleteMany({ user: user.id, type: 'resetPassword' });
res.status(httpStatus.NO_CONTENT).send();
} catch (error) {
throw new AppError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
});

module.exports = {
Expand Down
35 changes: 29 additions & 6 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
const httpStatus = require('http-status');
const { pick } = require('lodash');
const { User } = require('../models');
const AppError = require('../utils/AppError');
const catchAsync = require('../utils/catchAsync');
const { userService } = require('../services');
const { getQueryOptions } = require('../utils/query.utils');

const createUser = catchAsync(async (req, res) => {
const user = await userService.createUser(req.body);
if (await User.isEmailTaken(req.body.email)) {
throw new AppError(httpStatus.BAD_REQUEST, 'Email already taken');
}
const user = await User.create(req.body);
res.status(httpStatus.CREATED).send(user.transform());
});

const getUsers = catchAsync(async (req, res) => {
const users = await userService.getUsers(req.query);
const filter = pick(req.query, ['name', 'role']);
const options = getQueryOptions(req.query);
const users = await User.find(filter, null, options);
const response = users.map(user => user.transform());
res.send(response);
});

const getUser = catchAsync(async (req, res) => {
const user = await userService.getUserById(req.params.userId);
const user = await User.findById(req.params.userId);
if (!user) {
throw new AppError(httpStatus.NOT_FOUND, 'User not found');
}
res.send(user.transform());
});

const updateUser = catchAsync(async (req, res) => {
const user = await userService.updateUser(req.params.userId, req.body);
const user = await User.findById(req.params.userId);
if (!user) {
throw new AppError(httpStatus.NOT_FOUND, 'User not found');
}
if (req.body.email && (await User.isEmailTaken(req.body.email, user.id))) {
throw new AppError(httpStatus.BAD_REQUEST, 'Email already taken');
}
Object.assign(user, req.body);
await user.save();
res.send(user.transform());
});

const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUser(req.params.userId);
const user = await User.findById(req.params.userId);
if (!user) {
throw new AppError(httpStatus.NOT_FOUND, 'User not found');
}
await user.remove();
res.status(httpStatus.NO_CONTENT).send();
});

Expand Down
10 changes: 10 additions & 0 deletions src/models/user.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ const userSchema = mongoose.Schema(
}
);

userSchema.statics.isEmailTaken = async function(email, excludeUserId) {
const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
return !!user;
};

userSchema.methods.isPasswordMatch = async function(password) {
const user = this;
return bcrypt.compare(password, user.password);
};

userSchema.methods.toJSON = function() {
const user = this;
return omit(user.toObject(), ['password']);
Expand Down
85 changes: 0 additions & 85 deletions src/services/auth.service.js

This file was deleted.

2 changes: 0 additions & 2 deletions src/services/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
module.exports.authService = require('./auth.service');
module.exports.emailService = require('./email.service');
module.exports.tokenService = require('./token.service');
module.exports.userService = require('./user.service');
33 changes: 30 additions & 3 deletions src/services/token.service.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const jwt = require('jsonwebtoken');
const moment = require('moment');
const httpStatus = require('http-status');
const config = require('../config/config');
const { Token } = require('../models');
const AppError = require('../utils/AppError');

const generateToken = (userId, expires, secret = config.jwt.secret) => {
const payload = {
Expand All @@ -29,13 +27,42 @@ const verifyToken = async (token, type) => {
const payload = jwt.verify(token, config.jwt.secret);
const tokenDoc = await Token.findOne({ token, type, user: payload.sub, blacklisted: false });
if (!tokenDoc) {
throw new AppError(httpStatus.NOT_FOUND, 'Token not found');
throw new Error('Token not found');
}
return tokenDoc;
};

const generateAuthTokens = async userId => {
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(userId, accessTokenExpires);

const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = generateToken(userId, refreshTokenExpires);
await saveToken(refreshToken, userId, refreshTokenExpires, 'refresh');

return {
access: {
token: accessToken,
expires: accessTokenExpires.toDate(),
},
refresh: {
token: refreshToken,
expires: refreshTokenExpires.toDate(),
},
};
};

const generateResetPasswordToken = async userId => {
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(userId, expires);
await saveToken(resetPasswordToken, userId, expires, 'resetPassword');
return resetPasswordToken;
};

module.exports = {
generateToken,
saveToken,
verifyToken,
generateAuthTokens,
generateResetPasswordToken,
};
66 changes: 0 additions & 66 deletions src/services/user.service.js

This file was deleted.

File renamed without changes.

0 comments on commit ffb47ae

Please sign in to comment.