1471 lines
42 KiB
JavaScript
1471 lines
42 KiB
JavaScript
const User = require("../models/User");
|
|
const { body, validationResult } = require("express-validator");
|
|
const { rateLimit } = require("express-rate-limit");
|
|
const bcrypt = require("bcryptjs");
|
|
const jwt = require("jsonwebtoken");
|
|
const crypto = require("crypto");
|
|
const sharp = require("sharp");
|
|
const { secretKey } = require("./../authentication");
|
|
const { _sr } = require("../plugins/serverResponses");
|
|
const {
|
|
res404,
|
|
res500,
|
|
hasWhiteSpaces,
|
|
isLatinCharactersWithSymbol,
|
|
generateRandomDigits,
|
|
} = require("../plugins/controllersHelperFunctions");
|
|
const fs = require("fs");
|
|
const nodemailer = require("nodemailer");
|
|
const Category = require("../models/Category");
|
|
const Logger = require("../models/Logger");
|
|
const cacheService = require("../plugins/cache");
|
|
const { sms } = require("../plugins/sms");
|
|
|
|
////// variables
|
|
const profileWidth = 500;
|
|
const profileHeight = 500;
|
|
const profileQuality = 100;
|
|
const hostURL = "localhost:6688";
|
|
const limit = 20;
|
|
const select = "-password -token";
|
|
|
|
const limiter = rateLimit({
|
|
windowMs: 10 * 60 * 1000, // 10 minutes
|
|
limit: 10,
|
|
standardHeaders: "draft-7",
|
|
legacyHeaders: false,
|
|
});
|
|
|
|
/** @todo add private to query */
|
|
|
|
/** auth --------------------------------------------- */
|
|
/////////////////////////////////////////////////////////////////////// login
|
|
module.exports.login = [
|
|
limiter,
|
|
[
|
|
body("username")
|
|
.if((value, { req }) => {
|
|
return req.body.password && req.body.password.length >= 4;
|
|
})
|
|
.custom((value, { req }) => {
|
|
var test = value;
|
|
return User.findOne({ username: value }).then((user) => {
|
|
if (!user) return Promise.reject(_sr["fa"].not_found.password);
|
|
else return true;
|
|
});
|
|
}),
|
|
|
|
body("password")
|
|
.notEmpty()
|
|
.withMessage(_sr["fa"].required.password)
|
|
.bail()
|
|
.isLength({ min: 4 })
|
|
.withMessage(_sr["fa"].min_char.min4),
|
|
|
|
body("remember_me")
|
|
.exists()
|
|
.withMessage(_sr["fa"].required.remember_me)
|
|
.bail()
|
|
.isBoolean()
|
|
.withMessage(_sr["fa"].format.boolean),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
var { username, password, remember_me } = req.body;
|
|
|
|
/** get user */
|
|
var user = await User.findOne({
|
|
$or: [{ username: username }, { email: username }],
|
|
});
|
|
const logger = await Logger.findOne({ user: user._id });
|
|
if (!user.registration_done) {
|
|
return res.status(403).json({
|
|
message: `اکانت شما غیرفعال می باشد لطفا با ادمین تماس بگیرید`,
|
|
});
|
|
}
|
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
|
|
/**check user */
|
|
if (user) {
|
|
/** check password */
|
|
|
|
if (isMatch) {
|
|
if (logger) {
|
|
await Logger.findOneAndUpdate(
|
|
{ user: user._id },
|
|
{ $set: { LastLogin: new Date() } },
|
|
{ new: true }
|
|
);
|
|
} else {
|
|
const newLogger = new Logger({
|
|
user: user,
|
|
LastLogin: new Date(),
|
|
});
|
|
await newLogger.save();
|
|
}
|
|
/** if have token and it true show result else generate new token */
|
|
if (user.token) {
|
|
jwt.verify(
|
|
user.token.replace(/^Bearer\s/, ""),
|
|
secretKey,
|
|
async (err, decoeded) => {
|
|
if (err || !decoeded) {
|
|
/** generate new token */
|
|
var token = await jwt.sign({ _id: user._id }, secretKey, {
|
|
expiresIn: remember_me ? "7d" : "1h",
|
|
});
|
|
user.token = token;
|
|
await user.save();
|
|
return res.json({ token: user.token });
|
|
} else {
|
|
return res.json({ token: user.token });
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
/** generate new token */
|
|
var token = await jwt.sign({ _id: user._id }, secretKey, {
|
|
expiresIn: remember_me ? "7d" : "1h",
|
|
});
|
|
user.token = token;
|
|
await user.save();
|
|
return res.json({ token: user.token });
|
|
}
|
|
} else {
|
|
return res.status(422).json({
|
|
validation: { username: { msg: _sr["fa"].not_found.password } },
|
|
});
|
|
}
|
|
} else {
|
|
return res.status(422).json({
|
|
validation: { username: { msg: _sr["fa"].not_found.password } },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
return res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
/** otp --------------------------------------------- */
|
|
/////////////////////////////////////////////////////////////////////// otp
|
|
module.exports.otp = [
|
|
limiter,
|
|
[
|
|
body("phone")
|
|
.notEmpty()
|
|
.withMessage(_sr["fa"].required.phone_number)
|
|
.bail()
|
|
.isMobilePhone("fa-IR")
|
|
.withMessage(_sr["fa"].required.phone_number),
|
|
// .isLength({ min: 4 })
|
|
// .withMessage(_sr["fa"].min_char.min4),
|
|
],
|
|
async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
const { phone } = req.body;
|
|
const user = await User.findOne({ mobileNumber: phone });
|
|
if (!user)
|
|
return res.status(406).json({
|
|
message: "شماره موبایل نامعتبر می باشد",
|
|
});
|
|
const existOtp = cacheService.get(`OTP:LOGIN:${phone}`);
|
|
if (existOtp)
|
|
return res.status(400).json({
|
|
message: "کد به شماره موبایل شما ارسال شده است",
|
|
ttl: (cacheService.getTtl(`OTP:LOGIN:${phone}`) - Date.now()) / 1000,
|
|
});
|
|
const otp = crypto.randomInt(10000, 99999);
|
|
const message = `کد ورود شما ${otp} میباشد. استانداری لغو11`;
|
|
/** save otp in cache */
|
|
cacheService.set(`OTP:LOGIN:${phone}`, otp, 120);
|
|
/** send sms to user */
|
|
const smsResult = await sms(phone, message);
|
|
|
|
if (!smsResult.IsSuccessful) {
|
|
console.log(smsResult);
|
|
return res500(res, `مشکلی در ارسال پیامک پیش آمده است`);
|
|
}
|
|
|
|
return res.status(200).json({
|
|
message: "کد به شماره موبایل شما ارسال شد",
|
|
});
|
|
} catch (error) {
|
|
return res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// verify otp
|
|
module.exports.verifyOtp = [
|
|
limiter,
|
|
[
|
|
body("phone")
|
|
.notEmpty()
|
|
.withMessage(_sr["fa"].required.phone_number)
|
|
.bail()
|
|
.isMobilePhone("fa-IR")
|
|
.withMessage(_sr["fa"].required.phone_number),
|
|
|
|
body("code")
|
|
.notEmpty()
|
|
.withMessage(_sr["fa"].required.otp_code)
|
|
.bail()
|
|
.isLength({ min: 5, max: 5 })
|
|
.withMessage(_sr["fa"].format.otp_length),
|
|
],
|
|
async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
const { phone, code } = req.body;
|
|
|
|
// Check if the OTP exists in the cache
|
|
const cachedOtp = cacheService.get(`OTP:LOGIN:${phone}`);
|
|
if (!cachedOtp) {
|
|
return res.status(400).json({
|
|
message: "کد وارد شده منقضی شده است یا نامعتبر میباشد",
|
|
});
|
|
}
|
|
|
|
// Verify if the provided OTP matches the cached OTP
|
|
if (cachedOtp.toString() !== code.trim()) {
|
|
return res.status(422).json({
|
|
validation: { code: { msg: "کد وارد شده نامعتبر میباشد" } },
|
|
});
|
|
}
|
|
|
|
// Find the user by phone number
|
|
const user = await User.findOne({ mobileNumber: phone });
|
|
if (!user) {
|
|
return res.status(406).json({
|
|
message: "شماره موبایل نامعتبر می باشد",
|
|
});
|
|
}
|
|
|
|
// Check if the user's registration is complete
|
|
if (!user.registration_done) {
|
|
return res.status(403).json({
|
|
message: `اکانت شما غیرفعال می باشد لطفا با ادمین تماس بگیرید`,
|
|
});
|
|
}
|
|
|
|
// Create or update login logger
|
|
const logger = await Logger.findOne({ user: user._id });
|
|
if (logger) {
|
|
await Logger.findOneAndUpdate(
|
|
{ user: user._id },
|
|
{ $set: { LastLogin: new Date() } },
|
|
{ new: true }
|
|
);
|
|
} else {
|
|
const newLogger = new Logger({
|
|
user: user,
|
|
LastLogin: new Date(),
|
|
});
|
|
await newLogger.save();
|
|
}
|
|
|
|
// Check if the user already has a valid token
|
|
if (user.token) {
|
|
jwt.verify(
|
|
user.token.replace(/^Bearer\s/, ""),
|
|
secretKey,
|
|
async (err, decoded) => {
|
|
if (err || !decoded) {
|
|
// Generate a new token if the existing one is invalid
|
|
const token = await jwt.sign({ _id: user._id }, secretKey, {
|
|
expiresIn: "2h",
|
|
});
|
|
user.token = token;
|
|
await user.save();
|
|
// Remove the OTP from the cache
|
|
cacheService.del(`OTP:LOGIN:${phone}`);
|
|
return res.json({ token: user.token });
|
|
} else {
|
|
// Remove the OTP from the cache
|
|
cacheService.del(`OTP:LOGIN:${phone}`);
|
|
return res.json({ token: user.token });
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
// Generate a new token if none exists
|
|
const token = await jwt.sign({ _id: user._id }, secretKey, {
|
|
expiresIn: "1h",
|
|
});
|
|
user.token = token;
|
|
await user.save();
|
|
// Remove the OTP from the cache
|
|
cacheService.del(`OTP:LOGIN:${phone}`);
|
|
return res.json({ token: user.token });
|
|
}
|
|
} catch (error) {
|
|
return res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// logout
|
|
module.exports.logout = [
|
|
async (req, res) => {
|
|
const token = req.headers.authorization?.replace(/^Bearer\s/, "");
|
|
if (token) {
|
|
User.findOneAndUpdate(
|
|
{ token: token },
|
|
{ token: null },
|
|
(err, oldData) => {
|
|
if (err || !oldData)
|
|
res.status(401).json({ message: "Your not logged in" });
|
|
else return res.json({ message: "You are logged out." });
|
|
}
|
|
);
|
|
} else {
|
|
return res.status(401).json({ message: "Your not logged in" });
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// get user
|
|
module.exports.getuser = [
|
|
async (req, res) => {
|
|
const token = req.headers.authorization?.replace(/^Bearer\s/, "");
|
|
// check if token is not destroyed
|
|
var user = await User.findOne({ token: token }).select("-password");
|
|
|
|
if (!user) {
|
|
return res.status(401).json({ message: "unauthorized" });
|
|
}
|
|
|
|
if (!user.registration_done) {
|
|
return res.status(401).json({
|
|
message: `اکانت شما غیرفعال می باشد لطفا با ادمین تماس بگیرید`,
|
|
});
|
|
}
|
|
|
|
//verifies secret and checks if the token is expired
|
|
try {
|
|
var checkToken = await jwt.verify(
|
|
user.token.replace(/^Bearer\s/, ""),
|
|
secretKey
|
|
);
|
|
|
|
if (!checkToken) {
|
|
return res.status(401).json({ message: "unauthorized" });
|
|
}
|
|
|
|
return res.json({ user: user });
|
|
} catch (error) {
|
|
return res.status(401).json({ message: "unauthorized" });
|
|
}
|
|
},
|
|
];
|
|
|
|
/** end auth --------------------------------------------- */
|
|
|
|
/////////////////////////////////////////////////////////////////////// admin
|
|
module.exports.registerAdmin = [
|
|
[
|
|
body("firstName").notEmpty().withMessage(_sr["fa"].required.first_name),
|
|
|
|
body("lastName").notEmpty().withMessage(_sr["fa"].required.last_name),
|
|
|
|
body("mobileNumber")
|
|
.if((value, { req }) => {
|
|
return req.body.mobileNumber;
|
|
})
|
|
.custom((value, { req }) => {
|
|
if (!_.isEmpty(value) && !lodash.isNumeric(value)) {
|
|
return Promise.reject(_sr["fa"].format.number);
|
|
}
|
|
return User.findOne({ mobileNumber: value }).then((user) => {
|
|
if (user) return Promise.reject(_sr["fa"].duplicated.phoneNumber);
|
|
else return true;
|
|
});
|
|
}),
|
|
|
|
// body('email')
|
|
// .optional().isEmail().withMessage(_sr['fa'].format.email)
|
|
// .if((value, {req}) => {
|
|
// return req.body.email
|
|
// })
|
|
// .custom((value, {req}) => {
|
|
// return User.findOne({email: value})
|
|
// .then(user => {
|
|
// if (user) return Promise.reject(_sr['fa'].duplicated.email)
|
|
// else return true
|
|
// })
|
|
// }),
|
|
|
|
body("username")
|
|
.isLength({ min: 4 })
|
|
.withMessage(_sr["fa"].min_char.min4)
|
|
.bail()
|
|
.custom((value, { req }) => {
|
|
if (hasWhiteSpaces(value))
|
|
return Promise.reject(_sr["fa"].response.whiteSpace);
|
|
else return true;
|
|
})
|
|
.bail()
|
|
.custom((value, { req }) => {
|
|
if (!isLatinCharactersWithSymbol(value))
|
|
return Promise.reject(_sr["fa"].response.latinChar);
|
|
else return true;
|
|
})
|
|
.bail()
|
|
.custom((value, { req }) => {
|
|
return User.findOne({ username: value }).then((user) => {
|
|
if (user) return Promise.reject(_sr["fa"].duplicated.username);
|
|
else return true;
|
|
});
|
|
}),
|
|
|
|
body("password")
|
|
.isLength({ min: 4 })
|
|
.withMessage(_sr["fa"].min_char.min4)
|
|
.custom((value, { req }) => {
|
|
if (value === req.body.password_confirmation) return Promise.resolve();
|
|
else return Promise.reject(_sr["fa"].response.passwords_not_match);
|
|
}),
|
|
|
|
body("permissions")
|
|
.if((value, { req }) => {
|
|
return req.body.specialPermissions;
|
|
})
|
|
.custom((value, { req }) => {
|
|
var permission = _.isString(value) ? JSON.parse(value) : value;
|
|
if (!_.isArray(permission)) {
|
|
return Promise.reject(`این فیلد باید از نوع آرایه باشد`);
|
|
}
|
|
|
|
if (!permission.length) {
|
|
return Promise.reject(`شما باید یک دسترسی انتخاب کنید`);
|
|
}
|
|
|
|
if (
|
|
permission.length &&
|
|
!permission.includes("superAdmin") &&
|
|
!permission.includes("news") &&
|
|
!permission.includes("categories")
|
|
) {
|
|
return Promise.reject(
|
|
`شما باید همراه با دسترسی مخصوص، دسترسی مدیریت صفحات یا اخبار را ارسال کنید`
|
|
);
|
|
} else {
|
|
return true;
|
|
}
|
|
}),
|
|
|
|
body("specialPermissions")
|
|
.optional()
|
|
.custom((value, { req }) => {
|
|
var specialPermissions =
|
|
_.isString(value) && !_.isEmpty(value) ? JSON.parse(value) : value;
|
|
if (!_.isArray(specialPermissions)) {
|
|
return Promise.reject(`این فیلد باید از نوع آرایه باشد`);
|
|
}
|
|
|
|
return true;
|
|
}),
|
|
|
|
body("portals").custom((value, { req }) => {
|
|
var portals = _.isString(value) ? JSON.parse(value) : value;
|
|
if (!_.isArray(portals)) {
|
|
return Promise.reject(`این فیلد باید از نوع آرایه باشد`);
|
|
}
|
|
|
|
if (!portals.length) {
|
|
return Promise.reject(`شما باید محدوده دسترسی را مشخص کنید`);
|
|
}
|
|
|
|
let isValid = portals.every((item) => {
|
|
return portal.includes(item);
|
|
});
|
|
|
|
if (isValid) {
|
|
return true;
|
|
} else {
|
|
return Promise.reject(`${_sr["fa"].optional.onlyValid} => ${portal}`);
|
|
}
|
|
}),
|
|
|
|
body("registration_done").isBoolean().withMessage(_sr["fa"].format.boolean),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
var {
|
|
username,
|
|
password,
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
mobileNumber,
|
|
permissions,
|
|
specialPermissions,
|
|
registration_done,
|
|
portals,
|
|
} = req.body;
|
|
|
|
/** this code fix problem with array in form data */
|
|
permissions = _.isString(permissions)
|
|
? JSON.parse(permissions)
|
|
: permissions;
|
|
specialPermissions = _.isString(specialPermissions)
|
|
? JSON.parse(specialPermissions)
|
|
: specialPermissions;
|
|
portals = _.isString(portals) ? JSON.parse(portals) : portals;
|
|
|
|
var data = {
|
|
username,
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
mobileNumber,
|
|
permissions,
|
|
registration_done,
|
|
private: false,
|
|
_creator: req.user?._id || null,
|
|
_updatedBy: req.user?._id || null,
|
|
specialPermissions: permissions.includes("superAdmin")
|
|
? []
|
|
: specialPermissions,
|
|
portals: [...new Set(portals)],
|
|
};
|
|
|
|
/**
|
|
* if we have pic upload to server
|
|
*/
|
|
if (req.files && req.files.profilePic) {
|
|
const image = req.files.profilePic;
|
|
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) {
|
|
return res.status(422).json({
|
|
validation: { profilePic: { msg: _sr["fa"].format.image } },
|
|
});
|
|
}
|
|
const imageName =
|
|
"user_" +
|
|
Date.now() +
|
|
"_" +
|
|
username +
|
|
"." +
|
|
image.mimetype.split("/")[1];
|
|
data.profilePic = imageName;
|
|
try {
|
|
const target = sharp(image.data);
|
|
await target
|
|
.resize(profileWidth, profileHeight, {
|
|
fit: "cover",
|
|
})
|
|
.jpeg({
|
|
quality: profileQuality,
|
|
})
|
|
.toFile(`./static/uploads/images/users/${imageName}`);
|
|
} catch (e) {
|
|
return res500(res, e?.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* generate salt
|
|
*/
|
|
var salt = bcrypt.genSaltSync(10);
|
|
|
|
/**
|
|
* hash password with salt
|
|
*/
|
|
var hashPassword = bcrypt.hashSync(password, salt);
|
|
|
|
if (!hashPassword) {
|
|
return res500(res, `error in hash`);
|
|
}
|
|
|
|
data.password = hashPassword;
|
|
|
|
/**
|
|
* create new admin
|
|
*/
|
|
const newAdmin = await User.create(data);
|
|
|
|
/**
|
|
* if we have error show him
|
|
*/
|
|
if (!newAdmin) {
|
|
return res500(`error in register admin`);
|
|
}
|
|
|
|
/**
|
|
* show result
|
|
*/
|
|
return res.status(201).json({
|
|
message: _sr["fa"].response.success_save,
|
|
});
|
|
} catch (error) {
|
|
return res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// get user
|
|
module.exports.getUserByAdmin = [
|
|
async (req, res) => {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
/** get one user */
|
|
const user = await User.findById(userId)
|
|
.populate("specialPermissions")
|
|
.select(select);
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
/** show result */
|
|
return res.json(user);
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// get all user
|
|
module.exports.getAllUsers = [
|
|
async (req, res) => {
|
|
try {
|
|
/** get all user */
|
|
const users = await User.paginate(
|
|
{ private: false },
|
|
{ page: req.query.page, limit, select }
|
|
);
|
|
|
|
/** if not exist show message to client */
|
|
if (!users) {
|
|
return res500(
|
|
res,
|
|
`مشکلی در گرفتن اطلاعات پیش آمده لطفا دوباره تلاش کنید`
|
|
);
|
|
}
|
|
|
|
/** show result */
|
|
return res.json(users);
|
|
} catch (error) {
|
|
return res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
module.exports.changeUserStatus = [
|
|
async (req, res) => {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
let user = await User.findOne({ private: false, _id: userId });
|
|
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
user.registration_done = !user.registration_done;
|
|
|
|
user.save();
|
|
|
|
/** show result */
|
|
return res.json(_sr["fa"].response.success_save);
|
|
} catch (error) {
|
|
return res500(res, `مشکلی در اجرای درخواست شما بوجود امده است`);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// delete one user
|
|
module.exports.deleteOneUser = [
|
|
async (req, res) => {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
/** get one user */
|
|
const user = await User.findOne({ _id: userId });
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
if (user.private) {
|
|
return res
|
|
.status(403)
|
|
.json({ message: `شما نمیتوانید این کاربر را حذف کنید` });
|
|
}
|
|
|
|
/**
|
|
* if exist old pic delete it from file
|
|
*/
|
|
if (user.profilePic) {
|
|
var oldPic = `./static${user.profilePic}`;
|
|
|
|
if (fs.existsSync(oldPic)) {
|
|
fs.unlink(oldPic, (err) => {
|
|
if (err) return res500(res, err?.message);
|
|
});
|
|
}
|
|
}
|
|
|
|
/** delete user */
|
|
await user.deleteOne();
|
|
|
|
/** show result */
|
|
return res.json(user);
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// update user
|
|
module.exports.updateUser = [
|
|
[
|
|
body("firstName").notEmpty().withMessage(_sr["fa"].required.first_name),
|
|
|
|
body("lastName").notEmpty().withMessage(_sr["fa"].required.last_name),
|
|
|
|
body("mobileNumber")
|
|
.if((value, { req }) => {
|
|
return req.body.mobileNumber;
|
|
})
|
|
.custom((value, { req }) => {
|
|
if (!_.isEmpty(value) && !lodash.isNumeric(value)) {
|
|
return Promise.reject(_sr["fa"].format.number);
|
|
}
|
|
return true;
|
|
}),
|
|
|
|
// body('email')
|
|
// .optional().isEmail().withMessage(_sr['fa'].format.email),
|
|
|
|
body("permissions")
|
|
.if((value, { req }) => {
|
|
return req.body.specialPermissions;
|
|
})
|
|
.custom((value, { req }) => {
|
|
let permission = _.isString(value) ? JSON.parse(value) : value;
|
|
let specialPermissions = _.isString(req.body.specialPermissions)
|
|
? JSON.parse(req.body.specialPermissions)
|
|
: req.body.specialPermissions;
|
|
if (!_.isArray(permission)) {
|
|
return Promise.reject(`این فیلد باید از نوع آرایه باشد`);
|
|
}
|
|
|
|
if (!permission.length) {
|
|
return Promise.reject(`شما باید یک دسترسی انتخاب کنید`);
|
|
}
|
|
|
|
if (
|
|
specialPermissions.length !== 0 &&
|
|
!permission.includes("superAdmin") &&
|
|
!permission.includes("news") &&
|
|
!permission.includes("categories")
|
|
) {
|
|
return Promise.reject(
|
|
`شما باید همراه با دسترسی مخصوص، دسترسی مدیریت صفحات یا اخبار را ارسال کنید`
|
|
);
|
|
} else {
|
|
return true;
|
|
}
|
|
}),
|
|
|
|
body("specialPermissions")
|
|
.optional()
|
|
.custom((value, { req }) => {
|
|
var specialPermissions =
|
|
_.isString(value) && !_.isEmpty(value) ? JSON.parse(value) : value;
|
|
if (!_.isArray(specialPermissions)) {
|
|
return Promise.reject(`این فیلد باید از نوع آرایه باشد`);
|
|
}
|
|
|
|
return true;
|
|
}),
|
|
|
|
body("portals").custom((value, { req }) => {
|
|
var portals = _.isString(value) ? JSON.parse(value) : value;
|
|
if (!_.isArray(portals)) {
|
|
return Promise.reject(`این فیلد باید از نوع آرایه باشد`);
|
|
}
|
|
|
|
if (!portals.length) {
|
|
return Promise.reject(`شما باید محدوده دسترسی را مشخص کنید`);
|
|
}
|
|
|
|
let isValid = portals.every((item) => {
|
|
return portal.includes(item);
|
|
});
|
|
|
|
if (isValid) {
|
|
return true;
|
|
} else {
|
|
return Promise.reject(`${_sr["fa"].optional.onlyValid} => ${portal}`);
|
|
}
|
|
}),
|
|
|
|
body("registration_done").isBoolean().withMessage(_sr["fa"].format.boolean),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
var {
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
mobileNumber,
|
|
permissions,
|
|
specialPermissions,
|
|
registration_done,
|
|
portals,
|
|
} = req.body;
|
|
|
|
/** this code fix problem with array in form data */
|
|
permissions = _.isString(permissions)
|
|
? JSON.parse(permissions)
|
|
: permissions;
|
|
specialPermissions = _.isString(specialPermissions)
|
|
? JSON.parse(specialPermissions)
|
|
: specialPermissions;
|
|
portals = _.isString(portals) ? JSON.parse(portals) : portals;
|
|
|
|
/** get one user */
|
|
var user = await User.findById(userId);
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
if (!user.private && !req.user.private) {
|
|
return res
|
|
.status(403)
|
|
.json({ message: `شما نمیتوانید این کاربر را آپدیت کنید` });
|
|
}
|
|
|
|
/** check new email is dupllicate or not */
|
|
if (user.email !== email) {
|
|
var checkDupplicate = await User.find({ email: email });
|
|
|
|
if (checkDupplicate.length) {
|
|
return res.status(422).json({
|
|
validation: { email: { msg: `${_sr["fa"].duplicated.email}` } },
|
|
});
|
|
}
|
|
}
|
|
/** check new mobileNumber is dupllicate or not */
|
|
if (user.mobileNumber !== mobileNumber) {
|
|
var checkDupplicate = await User.find({ mobileNumber: mobileNumber });
|
|
|
|
if (checkDupplicate.length) {
|
|
return res.status(422).json({
|
|
validation: {
|
|
mobileNumber: { msg: `${_sr["fa"].duplicated.phoneNumber}` },
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
user.firstName = firstName;
|
|
user.lastName = lastName;
|
|
user.password = user.password;
|
|
user.scope = user.scope;
|
|
user.token = user.token;
|
|
user.mobileNumber = mobileNumber;
|
|
user.email = email;
|
|
user.registration_done = registration_done;
|
|
user._creator = req.user._id;
|
|
user.username = user.username;
|
|
user.permissions = permissions;
|
|
user.specialPermissions = permissions?.includes("superAdmin")
|
|
? []
|
|
: specialPermissions;
|
|
user.private = user.private;
|
|
user.portals = [...new Set(portals)];
|
|
|
|
if (req.files && req.files.profilePic) {
|
|
const image = req.files.profilePic;
|
|
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) {
|
|
return res.status(422).json({
|
|
validation: { profilePic: { msg: _sr["fa"].format.image } },
|
|
});
|
|
}
|
|
const imageName =
|
|
"user_" +
|
|
Date.now() +
|
|
"_" +
|
|
user.username +
|
|
"." +
|
|
image.mimetype.split("/")[1];
|
|
|
|
try {
|
|
const target = sharp(image.data);
|
|
await target
|
|
.resize(profileWidth, profileHeight, {
|
|
fit: "cover",
|
|
})
|
|
.jpeg({
|
|
quality: profileQuality,
|
|
})
|
|
.toFile(`./static/uploads/images/users/${imageName}`);
|
|
|
|
/**
|
|
* if exist old pic delete it from file
|
|
*/
|
|
if (user.profilePic) {
|
|
var oldPic = `./static${user.profilePic}`;
|
|
|
|
if (fs.existsSync(oldPic)) {
|
|
fs.unlink(oldPic, (err) => {
|
|
if (err) return res500(res, err?.message);
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
return res500(res, e?.message);
|
|
}
|
|
|
|
user.profilePic = imageName;
|
|
}
|
|
|
|
await user.save();
|
|
|
|
/** show result */
|
|
return res.json(user);
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// change password
|
|
module.exports.changePassword = [
|
|
[
|
|
body("password")
|
|
.isLength({ min: 4 })
|
|
.withMessage(_sr["fa"].min_char.min4)
|
|
.custom((value, { req }) => {
|
|
if (value === req.body.password_confirmation) return Promise.resolve();
|
|
else return Promise.reject(_sr["fa"].response.passwords_not_match);
|
|
}),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
const password = req.body.password;
|
|
|
|
/** get one user */
|
|
var user = await User.findById(userId);
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
if (!user.private && !req.user.private) {
|
|
return res
|
|
.status(403)
|
|
.json({ message: `شما نمی توانید رمز عبور این کاربر را تغییر دهید` });
|
|
}
|
|
|
|
/**
|
|
* generate salt
|
|
*/
|
|
var salt = bcrypt.genSaltSync(10);
|
|
|
|
/**
|
|
* hash password with salt
|
|
*/
|
|
var hashPassword = bcrypt.hashSync(password, salt);
|
|
|
|
if (!hashPassword) {
|
|
return res500(res, `error in hash`);
|
|
}
|
|
|
|
user.password = hashPassword;
|
|
user.token = null;
|
|
|
|
await user.save();
|
|
|
|
/** show result */
|
|
return res.json({ message: _sr["fa"].response.success_save });
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// get profile(get self)
|
|
module.exports.getProfile = [
|
|
async (req, res) => {
|
|
try {
|
|
const userId = req.user._id;
|
|
|
|
/** get one user */
|
|
const user = await User.findById(userId).select(select);
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
/** show result */
|
|
return res.json(user);
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// update profile(get self)
|
|
module.exports.updateProfile = [
|
|
[
|
|
body("firstName").notEmpty().withMessage(_sr["fa"].required.first_name),
|
|
|
|
body("lastName").notEmpty().withMessage(_sr["fa"].required.last_name),
|
|
|
|
body("mobileNumber").isNumeric().withMessage(_sr["fa"].format.number),
|
|
|
|
// body('email')
|
|
// .isEmail().withMessage(_sr['fa'].format.email),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
const userId = req.user._id;
|
|
|
|
const { firstName, lastName, email, mobileNumber } = req.body;
|
|
|
|
/** get one user */
|
|
var user = await User.findById(userId);
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
/** check new email is dupllicate or not */
|
|
if (user.email != email) {
|
|
var checkDupplicate = await User.find({ email: email });
|
|
|
|
if (checkDupplicate.length) {
|
|
return res.status(422).json({
|
|
validation: { email: { msg: `${_sr["fa"].duplicated.email}` } },
|
|
});
|
|
}
|
|
}
|
|
/** check new mobileNumber is dupllicate or not */
|
|
if (user.mobileNumber != mobileNumber) {
|
|
var checkDupplicate = await User.find({ mobileNumber: mobileNumber });
|
|
|
|
if (checkDupplicate.length) {
|
|
return res.status(422).json({
|
|
validation: {
|
|
mobileNumber: { msg: `${_sr["fa"].duplicated.phoneNumber}` },
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
user.firstName = firstName;
|
|
user.lastName = lastName;
|
|
user.password = user.password;
|
|
user.scope = user.scope;
|
|
user.token = user.token;
|
|
user.mobileNumber = mobileNumber;
|
|
user.email = email;
|
|
user.registration_done = user.registration_done;
|
|
user._creator = req.user._id;
|
|
user.username = user.username;
|
|
user.permissions = user.permissions;
|
|
user.specialPermissions = user.specialPermissions;
|
|
user.private = user.private;
|
|
|
|
/** if exist a picture */
|
|
if (req.files && req.files.profilePic) {
|
|
const image = req.files.profilePic;
|
|
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) {
|
|
return res.status(422).json({
|
|
validation: { profilePic: { msg: _sr["fa"].format.image } },
|
|
});
|
|
}
|
|
const imageName =
|
|
"user_" +
|
|
Date.now() +
|
|
"_" +
|
|
user.username +
|
|
"." +
|
|
image.mimetype.split("/")[1];
|
|
|
|
try {
|
|
const target = sharp(image.data);
|
|
await target
|
|
.resize(profileWidth, profileHeight, {
|
|
fit: "cover",
|
|
})
|
|
.jpeg({
|
|
quality: profileQuality,
|
|
})
|
|
.toFile(`./static/uploads/images/users/${imageName}`);
|
|
|
|
/**
|
|
* if exist old pic delete it from file
|
|
*/
|
|
if (user.profilePic) {
|
|
var oldPic = `./static${user.profilePic}`;
|
|
|
|
if (fs.existsSync(oldPic)) {
|
|
fs.unlink(oldPic, (err) => {
|
|
if (err) return res500(res, err?.message);
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
return res500(res, e?.message);
|
|
}
|
|
|
|
user.profilePic = imageName;
|
|
}
|
|
|
|
await user.save();
|
|
|
|
/** show result */
|
|
return res.json(user);
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// change password (profile)
|
|
module.exports.changePasswordProfile = [
|
|
[
|
|
body("password")
|
|
.isLength({ min: 4 })
|
|
.withMessage(_sr["fa"].min_char.min4)
|
|
.custom((value, { req }) => {
|
|
if (value === req.body.password_confirmation) return Promise.resolve();
|
|
else return Promise.reject(_sr["fa"].response.passwords_not_match);
|
|
}),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
const userId = req.user._id;
|
|
|
|
const password = req.body.password;
|
|
|
|
/** get one user */
|
|
var user = await User.findById(userId);
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کاربر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
/**
|
|
* generate salt
|
|
*/
|
|
var salt = bcrypt.genSaltSync(10);
|
|
|
|
/**
|
|
* hash password with salt
|
|
*/
|
|
var hashPassword = bcrypt.hashSync(password, salt);
|
|
|
|
if (!hashPassword) {
|
|
return res500(res, `مشکلی در تغییر پسورد پیش آمده است`);
|
|
}
|
|
|
|
user.password = hashPassword;
|
|
user.token = null;
|
|
|
|
await user.save();
|
|
|
|
/** show result */
|
|
return res.json({ message: _sr["fa"].response.success_save });
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// forget password generate token
|
|
module.exports.forgetPassGenerateToken = [
|
|
[
|
|
body("email")
|
|
.optional()
|
|
.isEmail()
|
|
.withMessage(_sr["fa"].format.email)
|
|
.if((value, { req }) => {
|
|
return req.body.email;
|
|
})
|
|
.custom((value, { req }) => {
|
|
return User.findOne({ email: value }).then((user) => {
|
|
if (!user) return Promise.reject(_sr["fa"].not_found.email);
|
|
else return true;
|
|
});
|
|
}),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
const email = req.body.email;
|
|
|
|
/** get one user */
|
|
var user = await User.findOne({ email: email });
|
|
|
|
/** if not exist show message to client */
|
|
if (!user) {
|
|
return res404(res, `کابر مورد نظر پیدا نشد`);
|
|
}
|
|
|
|
const token = crypto.randomBytes(20).toString("hex");
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
let transporter = nodemailer.createTransport({
|
|
host: "www.rubynaturalco.com",
|
|
port: 587,
|
|
secure: false, // true for 465, false for other ports
|
|
auth: {
|
|
user: "recovery@rubynaturalco.com",
|
|
pass: "jd-m2?3ao?b(",
|
|
},
|
|
tls: {
|
|
rejectUnauthorized: false,
|
|
},
|
|
});
|
|
|
|
const emailHTMLTemplate = `
|
|
<div style="direction: rtl;background-color: #065495;overflow: hidden;">
|
|
<div style="width: 50%;border-radius: 10px;margin: 150px auto;background: #fff;text-align: center;padding: 20px;box-shadow: 0 15px 30px #000;">
|
|
<H1 style="color: #000;">برای بازیابی کلمه عبور خود روی لینک زیر کلیک کنید.</H1>
|
|
<div style="text-align: center;margin-top: 20px;">
|
|
<a href="http://${hostURL}/auth/resetpass-newpass/${token}"
|
|
target="_blank"
|
|
style="display: inline-block;padding: 15px;background: #065495;color: #fff;border-radius: 5px;text-decoration: none;">بازیابی کلمه عبور</a>
|
|
</div>
|
|
</div>
|
|
<span style="opacity: 0">${Date.now()}</span>
|
|
</div>
|
|
`;
|
|
|
|
// send mail with defined transport object
|
|
let info = await transporter.sendMail({
|
|
from: '"Ostandari Portal" <recovery@rubynaturalco.com>', // sender address
|
|
to: user.email, // list of receivers
|
|
subject: "لینک بازیابی کلمه عبور",
|
|
text: "کلمه عبور خود را بازیابی کنید",
|
|
html: emailHTMLTemplate,
|
|
});
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
user.token = token;
|
|
|
|
await user.save();
|
|
|
|
/** show result */
|
|
return res.json(token);
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
/////////////////////////////////////////////////////////////////////// forget password get token and change pass
|
|
module.exports.forgetPassGetToken = [
|
|
[
|
|
body("password")
|
|
.isLength({ min: 4 })
|
|
.withMessage(_sr["fa"].min_char.min4)
|
|
.custom((value, { req }) => {
|
|
if (value === req.body.password_confirmation) return Promise.resolve();
|
|
else return Promise.reject(_sr["fa"].response.passwords_not_match);
|
|
}),
|
|
],
|
|
async (req, res) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty())
|
|
return res.status(422).json({ validation: errors.mapped() });
|
|
|
|
try {
|
|
var token = req.params.token;
|
|
|
|
var password = req.body.password;
|
|
|
|
var user = await User.findOne({ token: token });
|
|
|
|
if (!user) {
|
|
return res500(
|
|
res,
|
|
_sr["fa"].response.expired_reset_link
|
|
); /** @todo change response */
|
|
}
|
|
|
|
/**
|
|
* generate salt
|
|
*/
|
|
var salt = bcrypt.genSaltSync(10);
|
|
|
|
/**
|
|
* hash password with salt
|
|
*/
|
|
var hashPassword = bcrypt.hashSync(password, salt);
|
|
|
|
if (!hashPassword) {
|
|
return res500(
|
|
res,
|
|
`مشکلی در تغییر رمز عبور پیش آمده است`
|
|
); /** @todo change response */
|
|
}
|
|
|
|
user.password = hashPassword;
|
|
user.token = null;
|
|
|
|
await user.save();
|
|
|
|
/** show result */
|
|
return res.json({ message: _sr["fa"].response.success_save });
|
|
} catch (error) {
|
|
res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|
|
|
|
////////////////////////////////////////////////////////////////////// search
|
|
module.exports.search = [
|
|
async (req, res) => {
|
|
try {
|
|
var query = { private: false };
|
|
query["$and"] = [];
|
|
|
|
var {
|
|
name,
|
|
email,
|
|
phone,
|
|
permissions,
|
|
specialPermissions,
|
|
portals,
|
|
status,
|
|
username,
|
|
} = req.body;
|
|
|
|
/** search by name and family */
|
|
if (name && !_.isEmpty(name)) {
|
|
query["$and"].push({
|
|
$or: [
|
|
{
|
|
firstName: {
|
|
$regex: ".*" + name + ".*",
|
|
},
|
|
lastName: {
|
|
$regex: ".*" + name + ".*",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
/** search by email */
|
|
if (email && !_.isEmpty(email)) {
|
|
query["$and"].push({
|
|
email: email,
|
|
});
|
|
}
|
|
/** search by phone */
|
|
if (phone && !_.isEmpty(phone)) {
|
|
query["$and"].push({
|
|
mobileNumber: phone,
|
|
});
|
|
}
|
|
/** search by permissions */
|
|
if (permissions && !_.isEmpty(permissions)) {
|
|
query["$and"].push({
|
|
permissions: {
|
|
$in: permissions,
|
|
},
|
|
});
|
|
}
|
|
/** search by specialPermissions */
|
|
if (specialPermissions && !_.isEmpty(specialPermissions)) {
|
|
query["$and"].push({
|
|
specialPermissions: {
|
|
$in: specialPermissions,
|
|
},
|
|
});
|
|
}
|
|
/** search by portals */
|
|
if (portals && !_.isEmpty(portals)) {
|
|
query["$and"].push({
|
|
portals: {
|
|
$in: portals,
|
|
},
|
|
});
|
|
}
|
|
/** search by status */
|
|
if (!_.isUndefined(status) && _.isBoolean(status)) {
|
|
query["$and"].push({
|
|
registration_done: status,
|
|
});
|
|
}
|
|
/** search by username */
|
|
if (username && !_.isEmpty(username)) {
|
|
query["$and"].push({
|
|
username: username,
|
|
});
|
|
}
|
|
|
|
_.isEmpty(query["$and"]) && delete query["$and"];
|
|
// _.isEmpty(input['$or']) && delete input['$or'];
|
|
|
|
/** get users */
|
|
var users = await User.paginate(query, {
|
|
page: req.query.page,
|
|
limit,
|
|
select,
|
|
});
|
|
|
|
/** show result */
|
|
return res.json(users);
|
|
} catch (error) {
|
|
return res500(res, error?.message);
|
|
}
|
|
},
|
|
];
|