Skip to content

Commit

Permalink
Move business logic into services
Browse files Browse the repository at this point in the history
  • Loading branch information
hagopj13 committed May 16, 2020
1 parent 304648f commit c6c4206
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 78 deletions.
60 changes: 12 additions & 48 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,34 @@
const httpStatus = require('http-status');
const catchAsync = require('../utils/catchAsync');
const { User, Token } = require('../models');
const { tokenService, emailService } = require('../services');
const ApiError = require('../utils/ApiError');
const { authService, userService, tokenService, emailService } = require('../services');

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

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

const refreshTokens = catchAsync(async (req, res) => {
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 ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
const tokens = await authService.refreshAuth(req.body.refreshToken);
res.send({ ...tokens });
});

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

const resetPassword = catchAsync(async (req, res) => {
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 ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
await authService.resetPassword(req.query.token, req.body.password);
res.status(httpStatus.NO_CONTENT).send();
});

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

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

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

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

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

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

Expand Down
47 changes: 47 additions & 0 deletions src/services/auth.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const httpStatus = require('http-status');
const tokenService = require('./token.service');
const userService = require('./user.service');
const Token = require('../models/token.model');
const ApiError = require('../utils/ApiError');

const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};

const refreshAuth = async (refreshToken) => {
try {
const refreshTokenDoc = await tokenService.verifyToken(refreshToken, 'refresh');
const user = await userService.getUserById(refreshTokenDoc.user);
if (!user) {
throw new Error();
}
await refreshTokenDoc.remove();
return tokenService.generateAuthTokens(user);
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
};

const resetPassword = async (resetPasswordToken, newPassword) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, 'resetPassword');
const user = await userService.getUserById(resetPasswordTokenDoc.user);
if (!user) {
throw new Error();
}
await Token.deleteMany({ user: user.id, type: 'resetPassword' });
await userService.updateUserById(user.id, { password: newPassword });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
};

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

const generateToken = (userId, expires, secret = config.jwt.secret) => {
const payload = {
Expand Down Expand Up @@ -32,13 +35,13 @@ const verifyToken = async (token, type) => {
return tokenDoc;
};

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

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

return {
access: {
Expand All @@ -52,10 +55,14 @@ const generateAuthTokens = async (userId) => {
};
};

const generateResetPasswordToken = async (userId) => {
const generateResetPasswordToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(userId, expires);
await saveToken(resetPasswordToken, userId, expires, 'resetPassword');
const resetPasswordToken = generateToken(user.id, expires);
await saveToken(resetPasswordToken, user.id, expires, 'resetPassword');
return resetPasswordToken;
};

Expand Down
55 changes: 55 additions & 0 deletions src/services/user.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const httpStatus = require('http-status');
const { User } = require('../models');
const ApiError = require('../utils/ApiError');

const createUser = async (userBody) => {
if (await User.isEmailTaken(userBody.email)) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
const user = await User.create(userBody);
return user;
};

const getUsers = async (filter, options) => {
const users = await User.find(filter, null, options);
return users;
};

const getUserById = async (id) => {
return User.findById(id);
};

const getUserByEmail = async (email) => {
return User.findOne({ email });
};

const updateUserById = async (userId, updateBody) => {
const user = await getUserById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
if (updateBody.email && (await User.isEmailTaken(updateBody.email, userId))) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
Object.assign(user, updateBody);
await user.save();
return user;
};

const deleteUserById = async (userId) => {
const user = await getUserById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
await user.remove();
return user;
};

module.exports = {
createUser,
getUsers,
getUserById,
getUserByEmail,
updateUserById,
deleteUserById,
};

0 comments on commit c6c4206

Please sign in to comment.