Files
asan-service/server/controllers/userController.js
T
2025-02-01 14:48:22 +03:30

1761 lines
55 KiB
JavaScript

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 = `
<div style="direction: rtl;background-color: #065495;overflow: hidden;">
<div
style="width: 70%;border-radius: 10px;margin: 150px auto;background: #fff;text-align: center;padding: 20px;box-shadow: 0 15px 30px #000;"
>
<h1 style="color: #000;font-size: 25px;margin-bottom: 20px;">
<span> این ایمیل در وبسایت</span>
<strong style="color: #065495"> آسان سرویس </strong>
<span> ثبت شده است. </span>
</h1>
<p style="color: #000;">کد تایید آدرس ایمیل شما:</p>
<div style="text-align: center;margin-top: 5px;">
<p style="color: #065495;text-align: center;font-weight: bold;font-size: 20px;direction: ltr;">
${emailKey}
</p>
</div>
</div>
</div>
`
// send mail with defined transport object
await transporter.sendMail({
from: '"AsanService" <confirmation@asan-service.com>', // 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()
const smsMsg = [
'این شماره در وبسایت آسان سرویس جهت ارائه خدمات گارانتی ثبت گردیده.',
' کد تایید شماره همراه: ',
mobileKey
]
await SMS([user.mobile_number], smsMsg.join(''))
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)
const user = await User.findOne(query)
if (!user) throw new Error('err')
const resetPassKey = await crypto.randomBytes(20).toString('hex')
user.resetPassToken = resetPassKey
const hostURL = 'www.asan-service.com'
if (type === 'email') {
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="https://${hostURL}/auth/resetpass-newpass/${resetPassKey}"
target="_blank"
style="display: inline-block;padding: 15px;background: #065495;color: #fff;border-radius: 5px;text-decoration: none;"
>بازیابی کلمه عبور</a
>
<p style="font-size: 10px;color: #b1b1b1;margin-top: 40px">
چنانچه قادر به کلیلک روی لینک بالا نیستید آدرس زیر را در مرورگر خود باز کنید.
</p>
<p style="font-size: 10px;color: #b1b1b1;">https://${hostURL}/auth/resetpass-newpass/${resetPassKey}</p>
</div>
</div>
</div>
`
// send mail with defined transport object
await transporter.sendMail({
from: '"AsanService" <resetpassword@asan-service.com>', // sender address
to: email, // list of receivers
subject: 'لینک بازیابی کلمه عبور',
text: emailHTMLTemplate,
html: emailHTMLTemplate
})
} else {
const smsMsg = [
' لینک بازیابی کلمه عبور آسان سرویس: ',
`https://${hostURL}/auth/resetpass-newpass/${resetPassKey}`
]
await SMS([user.mobile_number], smsMsg.join(''))
}
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') 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' })
}
}
]