const crypto = require('crypto')
const nodemailer = require('nodemailer')
const bcrypt = require('bcryptjs')
const { body, validationResult } = require('express-validator')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const { secretKey } = require('../authentication')
const permissions = require('../plugins/permissions')
const { _sr } = require('../plugins/serverResponses')
const sms = require('../plugins/SMS_Module')
const { notifyAdmin } = require('../WebSocket/controllers/admin_EventsController')
const { saveUserToArpa, updateUserOnArpa } = require('../ArpaWebservice.js')
const {
res404,
res500,
checkValidations,
removeWhiteSpaces,
checkMobileNumber,
normalizeMobileNumber,
nameOptimizer,
checkNationalCode,
isPersian,
isPersianWithDigits,
generateRandomDigits
} = require('../plugins/controllersHelperFunctions')
const _faSr = _sr.fa
const transporter = nodemailer.createTransport({
host: 'smtp.c1.liara.email',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: 'boring_bouman_81kd95',
pass: 'ec9e96d3-6108-4ca5-97a9-39fff09d3557'
},
tls: {
rejectUnauthorized: false
}
})
/// ///////////////////////////////////////////////////////////////// verify account functions
function sendConfirmationEmail(userId) {
return new Promise(async (resolve, reject) => {
try {
const user = await User.findById(userId)
if (!user) throw new Error('invalid id')
const emailKey = generateRandomDigits(6)
user.emailConfirmationKey = emailKey
await user.save()
/// //// email configs
// const hostURL = 'www.asan-service.com'
const emailHTMLTemplate = `
این ایمیل در وبسایت
آسان سرویس
ثبت شده است.
کد تایید آدرس ایمیل شما:
`
// send mail with defined transport object
await transporter.sendMail({
from: '"AsanService" ', // sender address
to: user.email, // list of receivers
subject: 'تایید ایمیل',
text: emailHTMLTemplate,
html: emailHTMLTemplate
})
resolve()
} catch (err) {
reject(err)
}
})
}
function sendConfirmationSMS(userId) {
return new Promise(async (resolve, reject) => {
try {
const user = await User.findById(userId)
if (!user) throw new Error('invalid id')
const mobileKey = generateRandomDigits(6)
user.mobileConfirmationKey = mobileKey
await user.save()
await sms.auth.mobileVerificationCode(user.mobile_number, mobileKey, new Date())
resolve()
} catch (err) {
reject(err)
}
})
}
/// //////////////////////////////////////////////////////////////////// admin accounts
module.exports.register_admin = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.last_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2),
body('username')
.notEmpty()
.withMessage(_faSr.required.username)
.bail()
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
return User.findOne({ username: value }).then(admin => {
if (admin && admin.username === value) return Promise.reject(_faSr.duplicated.username)
else return true
})
}),
body('password')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
if (value === req.body.password_confirmation) return true
else return Promise.reject(_faSr.response.passwords_not_match)
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const { first_name, last_name, username, permissions, personalCode } = req.body
const data = { first_name, last_name, username, permissions, personalCode }
data._creator = req.user_id
data.confirmed = true
data.scope = ['admin']
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(req.body.password, salt)
data.password = hash
const admin = new User(data)
await admin.save()
return res.json({ message: _faSr.response.success_save })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.update_admin = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.last_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2),
body('username')
.notEmpty()
.withMessage(_faSr.required.username)
.bail()
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
return User.findOne({ username: value }).then(admin => {
if (admin && admin.username === value && admin._id.toString() !== req.params.id)
return Promise.reject(_faSr.duplicated.username)
else return true
})
})
],
checkValidations(validationResult),
(req, res) => {
const { username, first_name, last_name, permissions, personalCode } = req.body
const data = { username, first_name, last_name, permissions, personalCode }
data._creator = req.user_id
User.findByIdAndUpdate(req.params.id, data, (err, oldData) => {
if (err || !oldData) return res.status(404).json({ message: _faSr.not_found.user_id })
return res.json({ message: _faSr.response.success_activation })
})
}
]
module.exports.get_admins = [
(req, res) => {
const regex = /admin/
User.find({ scope: regex, private: false }, (err, admins) => {
if (err) return res500(res, err)
return res.json(admins)
})
}
]
module.exports.get_one_admin = [
(req, res) => {
User.findById(req.params.id)
.select('first_name last_name username permissions _creator personalCode')
.populate('_creator', 'first_name last_name')
.exec((err, admin) => {
if (err || !admin) return res.status(404).json({ message: _faSr.not_found.user_id })
else return res.json(admin)
})
}
]
module.exports.delete_admin = [
(req, res) => {
User.findByIdAndDelete(req.params.id, (err, user) => {
if (err || !user) return res.status(404).json({ message: _faSr.not_found.user_id })
else return res.json({ message: _faSr.response.success_save })
})
}
]
module.exports.permissions = [
(req, res) => {
return res.status(200).json(permissions)
}
]
/// //////////////////////////////////////////////////////////////////// user accounts
module.exports.register_user = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('national_code')
.notEmpty()
.withMessage(_faSr.required.national_code)
.bail()
.custom((value, { req }) => {
if (checkNationalCode(value)) return true
else return Promise.reject(_faSr.format.national_code)
})
.bail()
.custom((value, { req }) => {
const nationalCode = value.trim()
return User.findOne({ national_code: nationalCode }).then(user => {
if (user && user.national_code === nationalCode)
return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
else return true
})
}),
body('province_id').notEmpty().withMessage(_faSr.required.province),
body('city_id').notEmpty().withMessage(_faSr.required.city),
body('address')
.notEmpty()
.withMessage(_faSr.required.address)
.bail()
.custom((value, { req }) => {
if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('postal_code')
.notEmpty()
.withMessage(_faSr.required.postal_code)
.bail()
.isNumeric()
.withMessage(_faSr.format.number),
body('email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email)
.bail()
.custom((value, { req }) => {
const email = value.trim().toLowerCase()
return User.findOne({ email }).then(user => {
if (user && user.email === email) return Promise.reject(_faSr.duplicated.email)
else return true
})
}),
body('tel_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.isNumeric()
.withMessage(_faSr.format.phone_number),
body('mobile_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
.bail()
.custom((value, { req }) => {
const mobileNumber = value.trim()
return User.findOne({ mobile_number: mobileNumber }).then(user => {
if (user && user.mobile_number === mobileNumber)
return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
else return true
})
}),
body('password')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
if (value === req.body.password_confirmation) return true
else return Promise.reject(_faSr.response.passwords_not_match)
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const {
first_name,
last_name,
national_code,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
mobile_number,
email,
store_name,
password
} = req.body
const data = {
last_name,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
store_name
}
data.first_name = nameOptimizer(first_name)
data.national_code = national_code.trim()
data.mobile_number = normalizeMobileNumber(mobile_number)
data.email = email.trim().toLowerCase()
// hash user password and done
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(password, salt)
data.password = hash
const user = new User(data)
await user.save()
await sendConfirmationEmail(user._id)
await sendConfirmationSMS(user._id)
return res.json({ message: _faSr.response.success_save })
} catch (err) {
const registerFailurMsg = 'مشکلی در ثبت نام پیش آمده، لطفا مجددا تلاش کنید'
res500(res, registerFailurMsg)
}
}
]
module.exports.update_user = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('national_code')
.notEmpty()
.withMessage(_faSr.required.national_code)
.bail()
.custom((value, { req }) => {
if (checkNationalCode(value)) return true
else return Promise.reject(_faSr.format.national_code)
})
.custom((value, { req }) => {
const nationalCode = value.trim()
return User.findOne({ national_code: nationalCode }).then(user => {
if (user && user.national_code === nationalCode && user._id.toString() !== req.body._id)
return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
else return true
})
}),
body('province_id').notEmpty().withMessage(_faSr.required.province),
body('city_id').notEmpty().withMessage(_faSr.required.city),
body('address')
.notEmpty()
.withMessage(_faSr.required.address)
.bail()
.custom((value, { req }) => {
if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('postal_code')
.notEmpty()
.withMessage(_faSr.required.postal_code)
.bail()
.isNumeric()
.withMessage(_faSr.format.number),
body('tel_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.isNumeric()
.withMessage(_faSr.format.phone_number),
body('mobile_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
.bail()
.custom((value, { req }) => {
const mobileNumber = value.trim()
return User.findOne({ mobile_number: mobileNumber }).then(user => {
if (user && user.mobile_number === mobileNumber && user._id.toString() !== req.body._id)
return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
else return true
})
}),
body('email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email)
.bail()
.custom((value, { req }) => {
const email = value.trim().toLowerCase()
return User.findOne({ email }).then(user => {
if (user && user.email === email && user._id.toString() !== req.body._id)
return Promise.reject(_faSr.duplicated.email)
else return true
})
})
],
checkValidations(validationResult),
async (req, res) => {
const {
first_name,
last_name,
national_code,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
mobile_number,
email,
store_name
} = req.body
const MongoData = {
last_name,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
store_name
}
MongoData.first_name = nameOptimizer(first_name)
MongoData.national_code = national_code.trim()
MongoData.mobile_number = normalizeMobileNumber(mobile_number)
MongoData.email = email.trim().toLowerCase()
try {
const { user_id } = req
const user = await User.findById(user_id)
if (!user) return res404(res, _faSr.not_found.user_id)
const newEmail = user.email !== MongoData.email
const newMobile = user.mobile_number !== MongoData.mobile_number
if (newEmail) user.email_confirmed = false
if (newMobile) user.mobile_confirmed = false
await updateUserOnArpa(MongoData, user.verity_businessID, 'verity')
await updateUserOnArpa(MongoData, user.panatech_businessID, 'panatech')
user.first_name = MongoData.first_name
user.last_name = MongoData.last_name
user.national_code = MongoData.national_code
user.province_id = MongoData.province_id
user.province_name = MongoData.province_name
user.city_id = MongoData.city_id
user.city_name = MongoData.city_name
user.address = MongoData.address
user.postal_code = MongoData.postal_code
user.tel_number = MongoData.tel_number
user.store_name = MongoData.store_name
user.mobile_number = MongoData.mobile_number
user.email = MongoData.email
await user.save()
if (newEmail) await sendConfirmationEmail(user_id)
if (newMobile) await sendConfirmationSMS(user_id)
return res.json({ message: _faSr.response.success_save, data: user })
} catch (e) {
console.log('Arpa Error during update user --', e)
return res500(res, _faSr.response.problem)
}
}
]
module.exports.update_user_password = [
[
body('password')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
if (value === req.body.password_confirmation) return true
else return Promise.reject(_faSr.response.passwords_not_match)
})
],
checkValidations(validationResult),
async (req, res) => {
// hash user password and done
try {
const { _id, password } = req.body
const { user_id } = req
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(password, salt)
const user = await User.findById(_id)
if (!user) throw new Error(_faSr.not_found.user_id)
user.password = hash
if (user.scope.includes('admin')) user._creator = user_id
await user.save()
return res.json({ message: _faSr.response.success_save })
} catch (err) {
if (err) return res500(res, err)
}
}
]
/// // reset user password
module.exports.generate_reset_user_password_link = [
[
body('type')
.notEmpty()
.withMessage('choose type')
.bail()
.custom((value, { req }) => {
if (value === 'email' || value === 'mobile') return Promise.resolve()
else return Promise.reject(new Error('invalid type'))
}),
body('email')
.if((value, { req }) => req.body.type === 'email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email),
body('mobile')
.if((value, { req }) => req.body.type === 'mobile')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const { type, email, mobile } = req.body
const query = {}
if (type === 'email') query.email = email.toLowerCase().trim()
if (type === 'mobile') query.mobile_number = normalizeMobileNumber(mobile)
console.log('queryyyy', query)
const user = await User.findOne(query)
if (!user) throw new Error('err')
const resetPassKey = crypto.randomBytes(5).toString('hex')
user.resetPassToken = resetPassKey
const hostURL = 'www.asan-service.com'
if (type === 'email') {
const emailHTMLTemplate = `
برای بازیابی کلمه عبور حساب کاربری خود روی لینک زیر کلیک کنید.
بازیابی کلمه عبور
چنانچه قادر به کلیلک روی لینک بالا نیستید آدرس زیر را در مرورگر خود باز کنید.
https://${hostURL}/auth/resetpass-newpass/${resetPassKey}
`
// send mail with defined transport object
await transporter.sendMail({
from: '"AsanService" ', // sender address
to: email, // list of receivers
subject: 'لینک بازیابی کلمه عبور',
text: emailHTMLTemplate,
html: emailHTMLTemplate
})
} else {
// const resetLink = `asan-service.com/auth/resetpass-newpass/${resetPassKey}`
const resetLink = `${resetPassKey}`
console.log(resetLink)
await sms.auth.passwordReset(user.mobile_number, resetLink, new Date())
}
await user.save()
let responseMsg = 'لینک بازیابی برای شما ارسال شد'
if (type === 'email')
responseMsg =
'لینک بازیابی برای شما ارسال شد، چنانچه قادر به مشاهده ایمیل در صندوق ورودی خود نیستید پوشه spam را چک کنید.'
return res.json({ message: responseMsg })
} catch (err) {
let errMsg = _faSr.not_found.user_id
if (err.code === 'EENVELOPE' || err.code === 400) errMsg = 'مشکلی در ارسال ایمیل پیش آمد لطفا دوباره تلاش کنید'
return res404(res, errMsg)
}
}
]
module.exports.reset_password_using_token = [
[
body('password')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
if (value === req.body.password_confirmation) return true
else return Promise.reject(_faSr.response.passwords_not_match)
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const { token, password } = req.body
if (!token) throw new Error('err')
const user = await User.findOne({ resetPassToken: token })
if (!user) throw new Error('err')
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(password, salt)
user.password = hash
user.token = null
user.resetPassToken = undefined
await user.save()
return res.json({ message: _faSr.response.success_save })
} catch (err) {
return res404(res, _faSr.response.expired_reset_link)
}
}
]
/// // confirm email
module.exports.changeEmail = [
[
body('email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email)
.bail()
.custom((value, { req }) => {
const email = value.toLowerCase()
return User.findOne({ email }).then(user => {
if (user && user.email === email) return Promise.reject(_faSr.duplicated.email)
else return true
})
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const { user_id } = req
const { email } = req.body
const user = await User.findById(user_id)
if (!user) throw new Error('invalid id')
user.email = email
user.email_confirmed = false
await user.save()
await sendConfirmationEmail(user_id)
return res.json({ message: 'کد تایید برای شما ارسال شد' })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.resendEmailConfirmation = [
async (req, res) => {
try {
const { user_id } = req
await sendConfirmationEmail(user_id)
return res.json({ message: 'کد تایید برای شما ارسال شد' })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.confirmEmail = [
async (req, res) => {
try {
const { user_id } = req
const { verificationKey } = req.body
const user = await User.findById(user_id)
if (!user) throw new Error('err')
if (verificationKey?.trim() !== user.emailConfirmationKey) throw new Error('404')
if (!user.confirmed) {
user.confirmed = true
const verityID = await saveUserToArpa(user, 'verity')
const panatechID = await saveUserToArpa(user, 'panatech')
user.verity_businessID = verityID.businessID
user.verity_businessCode = verityID.businessCode
user.panatech_businessID = panatechID.businessID
user.panatech_businessCode = panatechID.businessCode
}
// save new info to arpa
await updateUserOnArpa(user, user.verity_businessID, 'verity')
await updateUserOnArpa(user, user.panatech_businessID, 'panatech')
// update user info in mongoDB
user.email_confirmed = true
user.emailConfirmationKey = undefined
await user.save()
return res.json({ message: 'ایمل شما تایید شد.' })
} catch (e) {
if (e === '404') return res404(res, 'کد تایید صحیح نمیباشد.')
else return res500(res, 'مشکلی در تایید ایمیل پیش آمده، لطفا دوباره تلاش کنید')
}
}
]
/// // confirm mobile
module.exports.changeMobile = [
[
body('mobile')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
.bail()
.custom((value, { req }) => {
return User.findOne({ mobile_number: value }).then(user => {
if (user && user.mobile_number === value)
return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
else return true
})
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const { user_id } = req
const { mobile } = req.body
const user = await User.findById(user_id)
if (!user) throw new Error('invalid id')
user.mobile_number = mobile
user.mobile_confirmed = false
await user.save()
await sendConfirmationSMS(user_id)
return res.json({ message: 'کد تایید برای شما پیامک شد' })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.resendMobileConfirmation = [
async (req, res) => {
try {
const { user_id } = req
await sendConfirmationSMS(user_id)
return res.json({ message: 'کد تایید برای شما پیامک شد' })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.confirmMobile = [
async (req, res) => {
try {
const { user_id } = req
const { verificationKey } = req.body
const user = await User.findById(user_id)
if (!user) throw new Error('err')
if (verificationKey?.trim() !== user.mobileConfirmationKey) throw new Error('404')
if (!user.confirmed) {
user.confirmed = true
const verityID = await saveUserToArpa(user, 'verity')
const panatechID = await saveUserToArpa(user, 'panatech')
user.verity_businessID = verityID.businessID
user.verity_businessCode = verityID.businessCode
user.panatech_businessID = panatechID.businessID
user.panatech_businessCode = panatechID.businessCode
}
// save new info to arpa
await updateUserOnArpa(user, user.verity_businessID, 'verity')
await updateUserOnArpa(user, user.panatech_businessID, 'panatech')
// update user info in mongoDB
user.mobile_confirmed = true
user.mobileConfirmationKey = undefined
await user.save()
return res.json({ message: 'شماره شما تایید شد.' })
} catch (e) {
if (e === '404') return res404(res, 'کد تایید صحیح نمیباشد.')
else return res500(res, 'مشکلی در تایید شماره پیش آمده، لطفا دوباره تلاش کنید')
}
}
]
/// //////////////////////////////////////////////////////////////////// user managemnet by admin
module.exports.add_user = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('national_code')
.notEmpty()
.withMessage(_faSr.required.national_code)
.bail()
.custom((value, { req }) => {
if (checkNationalCode(value)) return true
else return Promise.reject(_faSr.format.national_code)
})
.bail()
.custom((value, { req }) => {
const nationalCode = value.trim()
return User.findOne({ national_code: nationalCode }).then(user => {
if (user && user.national_code === nationalCode)
return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
else return true
})
}),
body('province_id').notEmpty().withMessage(_faSr.required.province),
body('city_id').notEmpty().withMessage(_faSr.required.city),
body('address')
.notEmpty()
.withMessage(_faSr.required.address)
.bail()
.custom((value, { req }) => {
if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('postal_code')
.notEmpty()
.withMessage(_faSr.required.postal_code)
.bail()
.isNumeric()
.withMessage(_faSr.format.number),
body('email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email)
.bail()
.custom((value, { req }) => {
const email = value.trim().toLowerCase()
return User.findOne({ email }).then(user => {
if (user && user.email === email) return Promise.reject(_faSr.duplicated.email)
else return true
})
}),
body('tel_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.isNumeric()
.withMessage(_faSr.format.phone_number),
body('mobile_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
.bail()
.custom((value, { req }) => {
const mobileNumber = value.trim()
return User.findOne({ mobile_number: mobileNumber }).then(user => {
if (user && user.mobile_number === mobileNumber)
return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
else return true
})
}),
body('password')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.custom((value, { req }) => {
if (value === req.body.password_confirmation) return true
else return Promise.reject(_faSr.response.passwords_not_match)
})
],
checkValidations(validationResult),
async (req, res) => {
try {
const {
first_name,
last_name,
national_code,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
mobile_number,
email,
store_name,
password
} = req.body
const data = {
last_name,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
store_name
}
data.first_name = nameOptimizer(first_name)
data.national_code = national_code.trim()
data.mobile_number = normalizeMobileNumber(mobile_number)
data.email = email.trim().toLowerCase()
data.email_confirmed = true
data.mobile_confirmed = true
data.confirmed = true
// hash user password and done
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(password, salt)
data.password = hash
const user = new User(data)
await user.save()
const verityID = await saveUserToArpa(user, 'verity')
const panatechID = await saveUserToArpa(user, 'panatech')
user.verity_businessID = verityID.businessID
user.verity_businessCode = verityID.businessCode
user.panatech_businessID = panatechID.businessID
user.panatech_businessCode = panatechID.businessCode
// save new info to arpa
await updateUserOnArpa(user, user.verity_businessID, 'verity')
await updateUserOnArpa(user, user.panatech_businessID, 'panatech')
await user.save()
return res.status(200).json({ message: _faSr.response.success_save, user })
} catch (err) {
const registerFailurMsg = 'مشکلی در ثبت نام پیش آمده، لطفا مجددا تلاش کنید'
res500(res, registerFailurMsg)
}
}
]
module.exports.get_all_users_for_admin = [
(req, res) => {
User.find(
{
scope: ['user'],
confirmed: true,
verity_businessID: { $ne: null },
verity_businessCode: { $ne: null }
},
(err, data) => {
if (err) return res500(res, err)
return res.status(200).json(data)
}
)
}
]
module.exports.get_one_user_for_admin = [
(req, res) => {
User.findById(req.params.id, (err, user) => {
if (err || !user) return res404(res, err)
if (!user.seenByAdmin) {
user.seenByAdmin = true
user.save(err => {
if (err) console.log('get one user for admin - save seenByAdmin error :', err)
notifyAdmin('updateNewCustomers')
})
}
return res.json(user)
})
}
]
module.exports.change_verity_info_by_admin = [
[
body('verity_businessCode').notEmpty().withMessage('کد مشتری نباید خالی باشد'),
body('verity_businessID').notEmpty().withMessage('آیدی مشتری نباید خالی باشد')
],
checkValidations(validationResult),
async (req, res) => {
try {
const user = await User.findById(req.params?.id)
if (!user) return res404(res, 'invalid user id')
if (!user.old_verity_businessID && !user.old_verity_businessCode) {
user.old_verity_businessID = user.verity_businessID
user.old_verity_businessCode = user.verity_businessCode
await user.save()
}
user.verity_businessID = req.body.verity_businessID
user.verity_businessCode = req.body.verity_businessCode
await user.save()
return res.json({ message: _faSr.response.success_save })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.change_panatech_info_by_admin = [
[
body('panatech_businessCode').notEmpty().withMessage('کد مشتری نباید خالی باشد'),
body('panatech_businessID').notEmpty().withMessage('آیدی مشتری نباید خالی باشد')
],
checkValidations(validationResult),
async (req, res) => {
try {
const user = await User.findById(req.params?.id)
if (!user) return res404(res, 'invalid user id')
if (!user.old_panatech_businessID && !user.old_panatech_businessCode) {
user.old_panatech_businessID = user.panatech_businessID
user.old_panatech_businessCode = user.panatech_businessCode
await user.save()
}
user.panatech_businessID = req.body.panatech_businessID
user.panatech_businessCode = req.body.panatech_businessCode
await user.save()
return res.json({ message: _faSr.response.success_save })
} catch (err) {
return res500(res, err)
}
}
]
module.exports.update_user_by_admin = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('national_code')
.notEmpty()
.withMessage(_faSr.required.national_code)
.bail()
.custom((value, { req }) => {
if (checkNationalCode(value)) return true
else return Promise.reject(_faSr.format.national_code)
})
.custom((value, { req }) => {
const nationalCode = value.trim()
return User.findOne({ national_code: nationalCode }).then(user => {
if (user && user.national_code === nationalCode && user._id.toString() !== req.body._id)
return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
else return true
})
}),
body('province_id').notEmpty().withMessage(_faSr.required.province),
body('city_id').notEmpty().withMessage(_faSr.required.city),
body('address')
.notEmpty()
.withMessage(_faSr.required.address)
.bail()
.custom((value, { req }) => {
if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('postal_code')
.notEmpty()
.withMessage(_faSr.required.postal_code)
.bail()
.isNumeric()
.withMessage(_faSr.format.number),
body('tel_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.isNumeric()
.withMessage(_faSr.format.phone_number),
body('mobile_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
.bail()
.custom((value, { req }) => {
const mobileNumber = value.trim()
return User.findOne({ mobile_number: mobileNumber }).then(user => {
if (user && user.mobile_number === mobileNumber && user._id.toString() !== req.body._id)
return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
else return true
})
}),
body('email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email)
.bail()
.custom((value, { req }) => {
const email = value.trim().toLowerCase()
return User.findOne({ email }).then(user => {
if (user && user.email === email && user._id.toString() !== req.body._id)
return Promise.reject(_faSr.duplicated.email)
else return true
})
})
],
checkValidations(validationResult),
async (req, res) => {
const {
first_name,
last_name,
national_code,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
mobile_number,
email,
store_name
} = req.body
const MongoData = {
last_name,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
store_name
}
MongoData.first_name = nameOptimizer(first_name)
MongoData.national_code = national_code.trim()
MongoData.mobile_number = normalizeMobileNumber(mobile_number)
MongoData.email = email.trim().toLowerCase()
try {
const user_id = req.params.id
const user = await User.findById(user_id)
if (!user) {
return res404(res, _faSr.not_found.user_id)
}
// check if need to ferify email or mobile
const newEmail = user.email !== MongoData.email
const newMobile = user.mobile_number !== MongoData.mobile_number
if (newEmail) user.email_confirmed = false
if (newMobile) user.mobile_confirmed = false
// save new info to arpa
await updateUserOnArpa(MongoData, user.verity_businessID, 'verity')
await updateUserOnArpa(MongoData, user.panatech_businessID, 'panatech')
// save new info to user
user.first_name = MongoData.first_name
user.last_name = MongoData.last_name
user.national_code = MongoData.national_code
user.province_id = MongoData.province_id
user.province_name = MongoData.province_name
user.city_id = MongoData.city_id
user.city_name = MongoData.city_name
user.address = MongoData.address
user.postal_code = MongoData.postal_code
user.tel_number = MongoData.tel_number
user.store_name = MongoData.store_name
user.mobile_number = MongoData.mobile_number
user.email = MongoData.email
await user.save()
// send confirmation key after user saved
if (newEmail) await sendConfirmationEmail(user_id)
if (newMobile) await sendConfirmationSMS(user_id)
return res.json({ message: _faSr.response.success_save })
} catch (e) {
console.log('Arpa Error during update user --', e)
return res500(res, _faSr.response.problem)
}
}
]
module.exports.updateUserByAdmin = [
[
body('first_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('last_name')
.notEmpty()
.withMessage(_faSr.required.first_name)
.bail()
.isLength({ min: 2 })
.withMessage(_faSr.min_char.min2)
.bail()
.custom((value, { req }) => {
if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('national_code')
.notEmpty()
.withMessage(_faSr.required.national_code)
.bail()
.custom((value, { req }) => {
if (checkNationalCode(value)) return true
else return Promise.reject(_faSr.format.national_code)
})
.custom((value, { req }) => {
const nationalCode = value.trim()
return User.findOne({ national_code: nationalCode }).then(user => {
if (user && user.national_code === nationalCode && user._id.toString() !== req.body._id)
return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
else return true
})
}),
body('province_id').notEmpty().withMessage(_faSr.required.province),
body('city_id').notEmpty().withMessage(_faSr.required.city),
body('address')
.notEmpty()
.withMessage(_faSr.required.address)
.bail()
.custom((value, { req }) => {
if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
else return true
}),
body('postal_code')
.notEmpty()
.withMessage(_faSr.required.postal_code)
.bail()
.isNumeric()
.withMessage(_faSr.format.number),
body('tel_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.isNumeric()
.withMessage(_faSr.format.phone_number),
body('mobile_number')
.notEmpty()
.withMessage(_faSr.required.phone_number)
.bail()
.custom((value, { req }) => {
const mobileNStatus = checkMobileNumber(value)
if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
else return Promise.resolve()
})
.bail()
.custom((value, { req }) => {
const mobileNumber = value.trim()
return User.findOne({ mobile_number: mobileNumber }).then(user => {
if (user && user.mobile_number === mobileNumber && user._id.toString() !== req.body._id)
return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
else return true
})
}),
body('email')
.notEmpty()
.withMessage(_faSr.required.email)
.bail()
.isEmail()
.withMessage(_faSr.format.email)
.bail()
.custom((value, { req }) => {
const email = value.trim().toLowerCase()
return User.findOne({ email }).then(user => {
if (user && user.email === email && user._id.toString() !== req.body._id)
return Promise.reject(_faSr.duplicated.email)
else return true
})
})
],
checkValidations(validationResult),
async (req, res) => {
const {
first_name,
last_name,
national_code,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
mobile_number,
email,
store_name
} = req.body
const MongoData = {
last_name,
province_id,
province_name,
city_id,
city_name,
address,
postal_code,
tel_number,
store_name
}
MongoData.first_name = nameOptimizer(first_name)
MongoData.national_code = national_code.trim()
MongoData.mobile_number = normalizeMobileNumber(mobile_number)
MongoData.email = email.trim().toLowerCase()
try {
const user_id = req.params.id
const user = await User.findById(user_id)
if (!user) return res404(res, _faSr.not_found.user_id)
// check if need to ferify email or mobile
const newEmail = user.email !== MongoData.email
const newMobile = user.mobile_number !== MongoData.mobile_number
if (newEmail) user.email_confirmed = false
if (newMobile) user.mobile_confirmed = false
// save new info to arpa
await updateUserOnArpa(MongoData, user.verity_businessID, 'verity')
await updateUserOnArpa(MongoData, user.panatech_businessID, 'panatech')
// save new info to user
user.first_name = MongoData.first_name
user.last_name = MongoData.last_name
user.national_code = MongoData.national_code
user.province_id = MongoData.province_id
user.province_name = MongoData.province_name
user.city_id = MongoData.city_id
user.city_name = MongoData.city_name
user.address = MongoData.address
user.postal_code = MongoData.postal_code
user.tel_number = MongoData.tel_number
user.store_name = MongoData.store_name
user.mobile_number = MongoData.mobile_number
user.email = MongoData.email
await user.save()
// send confirmation key after user saved
if (newEmail) await sendConfirmationEmail(user_id)
if (newMobile) await sendConfirmationSMS(user_id)
return res.json({ message: _faSr.response.success_save })
} catch (e) {
console.log('Arpa Error during update user --', e)
return res500(res, _faSr.response.problem)
}
}
]
/// //////////////////////////////////////////////////////////////////// authentication
/// //////// Login
module.exports.webLogin = [
[
body('username')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.bail()
.if((value, { req }) => {
return req.body.password && req.body.password.length >= 4
})
.custom((value, { req }) => {
return User.findOne({
$or: [{ email: value.toLowerCase().trim() }, { national_code: value }, { username: value }]
}).then(user => {
if (!user) return Promise.reject(_faSr.not_found.password)
else return true
})
}),
body('password')
.notEmpty()
.withMessage(_faSr.required.password)
.bail()
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4),
body('remember_me')
.exists()
.withMessage(_faSr.required.remember_me)
.bail()
.isBoolean()
.withMessage(_faSr.format.boolean)
],
checkValidations(validationResult),
async (req, res) => {
try {
const { username, password } = req.body
const user = await User.findOne({
$or: [{ email: username.toLowerCase().trim() }, { national_code: username }, { username }]
})
if (!user) throw new Error('err')
const passwordMatch = await bcrypt.compare(password, user.password)
if (!passwordMatch) throw new Error('err')
const token = await jwt.sign({ _id: user._id }, secretKey, {
expiresIn: req.body.remember_me ? '30d' : '7d'
})
user.token = token
await user.save()
return res.json({ token })
} catch (err) {
return res.status(422).json({
validation: { username: { msg: _faSr.not_found.password } }
})
}
}
]
module.exports.appLogin = [
[
body('username')
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
.bail()
.if((value, { req }) => {
return req.body.password && req.body.password.length >= 4
})
.custom((value, { req }) => {
return User.findOne({
$or: [{ email: value.toLowerCase().trim() }, { national_code: value }]
}).then(user => {
if (!user) return Promise.reject(_faSr.not_found.password)
else return true
})
}),
body('password')
.notEmpty()
.withMessage(_faSr.required.password)
.bail()
.isLength({ min: 4 })
.withMessage(_faSr.min_char.min4)
],
checkValidations(validationResult),
async (req, res) => {
try {
const { username, password } = req.body
const user = await User.findOne({
$or: [{ email: username.toLowerCase().trim() }, { national_code: username }]
})
if (!user) throw new Error('err')
const passwordMatch = await bcrypt.compare(password, user.password)
if (!passwordMatch) throw new Error('err')
const token = await jwt.sign({ _id: user._id }, secretKey, {
expiresIn: '60d'
})
user.appToken = token
await user.save()
return res.json({ token })
} catch (err) {
return res.status(422).json({
validation: { username: { msg: _faSr.not_found.password } }
})
}
}
]
/// //////// logout
module.exports.webLogout = [
async (req, res) => {
try {
const token = req.headers?.authorization
if (!token) throw new Error('err')
const user = await User.findOne({ token })
if (!user) throw new Error('err')
user.token = null
await user.save()
return res.json({ message: _faSr.response.logged_out })
} catch (err) {
return res.status(401).json({ message: _faSr.response.not_logged_in })
}
}
]
module.exports.appLogout = [
async (req, res) => {
try {
const token = req.headers?.authorization
if (!token) throw new Error('err')
const user = await User.findOne({ appToken: token })
if (!user) throw new Error('err')
user.appToken = null
await user.save()
return res.json({ message: _faSr.response.logged_out })
} catch (err) {
return res.status(401).json({ message: _faSr.response.not_logged_in })
}
}
]
/// //////// get user
module.exports.getUser = [
async (req, res) => {
try {
const token = req.headers?.authorization
if (!token) throw new Error('err')
const decoded = jwt.verify(token, secretKey)
if (!decoded) throw new Error('err')
const user = await User.findById(decoded?._id).select(
'-token -appToken -emailConfirmationKey -mobileConfirmationKey -password -resetPassToken'
)
return res.status(200).json({ user })
} catch (err) {
console.log('🚀 ~ file: userController.js ~ line 1580 ~ err', err)
return res.status(401).json({ message: 'unauthenticated' })
}
}
]