full
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
|
||||
const { CountryModel, ProvinceModel, CityModel } = require('../../../models/StateCity')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
@@ -7,7 +7,7 @@ const setAddress = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { province_id, city_id, address, lat, lng, show_location } = req.body
|
||||
const { country_id, province_id, city_id, address, lat, lng, show_location } = req.body
|
||||
// اعتبارسنجی دادهها
|
||||
if (
|
||||
!province_id ||
|
||||
@@ -37,9 +37,12 @@ const setAddress = async (req, res, next) => {
|
||||
})
|
||||
}
|
||||
|
||||
// یافتن استان و شهر
|
||||
const province = await ProvinceModel.findOne({ id: province_id })
|
||||
const city = await CityModel.findOne({ id: city_id })
|
||||
// یافتن کشور، استان و شهر
|
||||
const [country, province, city] = await Promise.all([
|
||||
country_id ? CountryModel.findOne({ id: country_id }) : null,
|
||||
ProvinceModel.findOne({ id: province_id }),
|
||||
CityModel.findOne({ id: city_id })
|
||||
])
|
||||
if (!province || !city) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
@@ -47,6 +50,9 @@ const setAddress = async (req, res, next) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (country) {
|
||||
user.country = { id: country.id, name: country.name_fa || country.name, iso2: country.iso2, slug: country.slug }
|
||||
}
|
||||
user.province = province
|
||||
user.city = city
|
||||
user.address = address
|
||||
@@ -69,7 +75,7 @@ const updateAddress = async (req, res, next) => {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { province_id, city_id, address, lat, lng, show_location } = req.body
|
||||
const { country_id, province_id, city_id, address, lat, lng, show_location } = req.body
|
||||
|
||||
// اعتبارسنجی دادهها
|
||||
if (
|
||||
@@ -102,9 +108,12 @@ const updateAddress = async (req, res, next) => {
|
||||
})
|
||||
}
|
||||
|
||||
// یافتن استان و شهر با استفاده از شناسه استان و شهر
|
||||
const province = await ProvinceModel.findOne({ id: province_id })
|
||||
const city = await CityModel.findOne({ id: city_id })
|
||||
// یافتن کشور، استان و شهر با استفاده از شناسهها
|
||||
const [country, province, city] = await Promise.all([
|
||||
country_id ? CountryModel.findOne({ id: country_id }) : null,
|
||||
ProvinceModel.findOne({ id: province_id }),
|
||||
CityModel.findOne({ id: city_id })
|
||||
])
|
||||
if (!province || !city) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
@@ -113,6 +122,9 @@ const updateAddress = async (req, res, next) => {
|
||||
}
|
||||
|
||||
// بهروزرسانی اطلاعات آدرس کاربر
|
||||
if (country) {
|
||||
user.country = { id: country.id, name: country.name_fa || country.name, iso2: country.iso2, slug: country.slug }
|
||||
}
|
||||
user.province = province
|
||||
user.city = city
|
||||
user.address = address
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { isValidIranSheba, isValidIranNationalCode } = require('../../../utils/iranIdentity')
|
||||
|
||||
const saveAuthValidationRules = () => {
|
||||
return [
|
||||
check('shaba').notEmpty().withMessage('شماره شبا نمیتواند خالی باشد')
|
||||
.matches(/^(?=.{24}$)[0-9]*$/).withMessage('فرمت شماره شبا صحیح نیست'),
|
||||
.matches(/^(?=.{24}$)[0-9]*$/).withMessage('فرمت شماره شبا صحیح نیست')
|
||||
.custom((value) => isValidIranSheba(value)).withMessage('شماره شبا معتبر نیست'),
|
||||
check('birthday').notEmpty().withMessage('تاریخ تولد نمیتواند خالی باشد'),
|
||||
check('national_code').notEmpty().withMessage('کد ملی نمیتواند خالی باشد')
|
||||
.isLength({ min: 10, max: 10 }).withMessage('کد ملی باید 10 رقم باشد')
|
||||
.custom((value) => isValidIranNationalCode(value)).withMessage('کد ملی معتبر نیست')
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { syncVerifyBadge } = require('../../../utils/verifyBadge')
|
||||
|
||||
const completeRegistration = async (req, res, next) => {
|
||||
try {
|
||||
@@ -18,15 +19,33 @@ const completeRegistration = async (req, res, next) => {
|
||||
|
||||
user.is_Register = true
|
||||
|
||||
if (!user.is_verified || user.is_verified === 'none') {
|
||||
user.is_verified = 'pending'
|
||||
// سطح کاربر قبلاً فقط بعد از اولین کامنت امتیازدار (recalculateUserRating) ست
|
||||
// میشد، پس کاربرهایی که هیچوقت امتیاز نگرفته بودن سطح نداشتن (null). صریحاً
|
||||
// اینجا هم ست میشه تا هر کاربر از لحظهی تکمیل ثبتنام سطح واقعی داشته باشه
|
||||
if (!user.user_level) {
|
||||
user.user_level = 'تازه وارد'
|
||||
}
|
||||
|
||||
// تیک خاکستری فقط وقتی کارت ملی ارسال شده باشد
|
||||
const hasNationalCard = Boolean(
|
||||
user.national_card_image && String(user.national_card_image).trim()
|
||||
)
|
||||
|
||||
if (hasNationalCard) {
|
||||
if (!user.is_verified || user.is_verified === 'none') {
|
||||
user.is_verified = 'pending'
|
||||
}
|
||||
} else if (user.is_verified === 'pending') {
|
||||
user.is_verified = 'none'
|
||||
}
|
||||
|
||||
syncVerifyBadge(user)
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'ثبت نام تکمیل شد',
|
||||
is_verified: user.is_verified,
|
||||
verify_badge: user.verify_badge,
|
||||
is_Register: user.is_Register
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,11 +2,30 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
function normalizeSubLabel(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
|
||||
function resolveDisplaySubExpertise(subList, displayValue) {
|
||||
const normalizedSub = (Array.isArray(subList) ? subList : [])
|
||||
.map(normalizeSubLabel)
|
||||
.filter(Boolean)
|
||||
if (!normalizedSub.length) return null
|
||||
|
||||
const display = normalizeSubLabel(displayValue)
|
||||
if (display) {
|
||||
const match = normalizedSub.find((item) => item === display)
|
||||
if (match) return match
|
||||
}
|
||||
|
||||
return normalizedSub[0]
|
||||
}
|
||||
|
||||
const setExpertise = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { expertise, sub_expertise } = req.body
|
||||
const { expertise, sub_expertise, display_sub_expertise } = req.body
|
||||
if (!expertise || !sub_expertise) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
@@ -14,6 +33,21 @@ const setExpertise = async (req, res, next) => {
|
||||
})
|
||||
}
|
||||
|
||||
const normalizedSub = Array.isArray(sub_expertise)
|
||||
? sub_expertise.map(normalizeSubLabel).filter(Boolean)
|
||||
: []
|
||||
if (!normalizedSub.length) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'حداقل یک زیرتخصص را انتخاب کنید'
|
||||
})
|
||||
}
|
||||
|
||||
const displayItem = resolveDisplaySubExpertise(
|
||||
normalizedSub,
|
||||
display_sub_expertise
|
||||
)
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
@@ -26,11 +60,15 @@ const setExpertise = async (req, res, next) => {
|
||||
|
||||
// آپدیت نام کاربری به صورت lowercase
|
||||
user.expertise = expertise
|
||||
user.sub_expertise = sub_expertise
|
||||
user.sub_expertise = normalizedSub
|
||||
user.display_sub_expertise = displayItem
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'تخصص با موفقیت بهروزرسانی شد'
|
||||
message: 'تخصص با موفقیت بهروزرسانی شد',
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
display_sub_expertise: user.display_sub_expertise,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
@@ -42,7 +80,7 @@ const updateExpertise = async (req, res, next) => {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { expertise, sub_expertise } = req.body
|
||||
const { expertise, sub_expertise, display_sub_expertise } = req.body
|
||||
|
||||
// اعتبارسنجی دادهها
|
||||
if (!expertise || !sub_expertise) {
|
||||
@@ -52,6 +90,21 @@ const updateExpertise = async (req, res, next) => {
|
||||
})
|
||||
}
|
||||
|
||||
const normalizedSub = Array.isArray(sub_expertise)
|
||||
? sub_expertise.map(normalizeSubLabel).filter(Boolean)
|
||||
: []
|
||||
if (!normalizedSub.length) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'حداقل یک زیرتخصص را انتخاب کنید'
|
||||
})
|
||||
}
|
||||
|
||||
const displayItem = resolveDisplaySubExpertise(
|
||||
normalizedSub,
|
||||
display_sub_expertise
|
||||
)
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
|
||||
@@ -66,11 +119,15 @@ const updateExpertise = async (req, res, next) => {
|
||||
|
||||
// بهروزرسانی اطلاعات تخصص و زیرتخصص
|
||||
user.expertise = expertise
|
||||
user.sub_expertise = sub_expertise
|
||||
user.sub_expertise = normalizedSub
|
||||
user.display_sub_expertise = displayItem
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'تخصص با موفقیت بهروزرسانی شد'
|
||||
message: 'تخصص با موفقیت بهروزرسانی شد',
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
display_sub_expertise: user.display_sub_expertise,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
||||
137
controllers/application/verify/personalDetailsController.js
Normal file
137
controllers/application/verify/personalDetailsController.js
Normal file
@@ -0,0 +1,137 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const MARITAL_STATUS_VALUES = ['single', 'divorced']
|
||||
const RESIDENCE_TYPE_VALUES = ['owner', 'tenant']
|
||||
const VEHICLE_TYPE_VALUES = ['none', 'car', 'motorcycle']
|
||||
const PERSONAL_STYLE_VALUES = ['formal', 'sporty']
|
||||
const INCOME_RANGE_VALUES = ['under_5m', '5m_10m', '10m_20m', 'over_20m']
|
||||
|
||||
/** All fields are optional profile-enrichment fields — only the ones present
|
||||
* in the request body are validated/applied, unlike sizes/cooperation-type
|
||||
* where the whole group is required together. */
|
||||
function applyPersonalDetails (user, body) {
|
||||
if (body.marital_status !== undefined) {
|
||||
if (body.marital_status !== null && !MARITAL_STATUS_VALUES.includes(body.marital_status)) {
|
||||
return 'وضعیت تاهل نامعتبر است'
|
||||
}
|
||||
user.marital_status = body.marital_status
|
||||
}
|
||||
if (body.residence_type !== undefined) {
|
||||
if (body.residence_type !== null && !RESIDENCE_TYPE_VALUES.includes(body.residence_type)) {
|
||||
return 'نوع سکونت نامعتبر است'
|
||||
}
|
||||
user.residence_type = body.residence_type
|
||||
}
|
||||
if (body.residence_area !== undefined) {
|
||||
user.residence_area = body.residence_area
|
||||
}
|
||||
if (body.vehicle_type !== undefined) {
|
||||
if (body.vehicle_type !== null && !VEHICLE_TYPE_VALUES.includes(body.vehicle_type)) {
|
||||
return 'نوع وسیله نقلیه نامعتبر است'
|
||||
}
|
||||
user.vehicle_type = body.vehicle_type
|
||||
if (body.vehicle_type === 'none' || body.vehicle_type === null) {
|
||||
user.vehicle_details = null
|
||||
}
|
||||
}
|
||||
if (body.vehicle_details !== undefined && user.vehicle_type && user.vehicle_type !== 'none') {
|
||||
user.vehicle_details = body.vehicle_details
|
||||
}
|
||||
if (body.personal_style !== undefined) {
|
||||
if (body.personal_style !== null && !PERSONAL_STYLE_VALUES.includes(body.personal_style)) {
|
||||
return 'استایل نامعتبر است'
|
||||
}
|
||||
user.personal_style = body.personal_style
|
||||
}
|
||||
if (body.job !== undefined) {
|
||||
user.job = body.job
|
||||
}
|
||||
if (body.income_range !== undefined) {
|
||||
if (body.income_range !== null && !INCOME_RANGE_VALUES.includes(body.income_range)) {
|
||||
return 'بازه درآمد نامعتبر است'
|
||||
}
|
||||
user.income_range = body.income_range
|
||||
}
|
||||
if (body.moral_traits !== undefined) {
|
||||
user.moral_traits = body.moral_traits
|
||||
}
|
||||
if (body.personal_details_public !== undefined) {
|
||||
user.personal_details_public = Boolean(body.personal_details_public)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const setPersonalDetails = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
const validationError = applyPersonalDetails(user, req.body)
|
||||
if (validationError) {
|
||||
return res.status(422).json({ error: true, message: validationError })
|
||||
}
|
||||
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'مشخصات فردی با موفقیت ذخیره شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updatePersonalDetails = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
const validationError = applyPersonalDetails(user, req.body)
|
||||
if (validationError) {
|
||||
return res.status(422).json({ error: true, message: validationError })
|
||||
}
|
||||
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'مشخصات فردی با موفقیت بهروزرسانی شد',
|
||||
marital_status: user.marital_status,
|
||||
residence_type: user.residence_type,
|
||||
residence_area: user.residence_area,
|
||||
vehicle_type: user.vehicle_type,
|
||||
vehicle_details: user.vehicle_details,
|
||||
personal_style: user.personal_style,
|
||||
job: user.job,
|
||||
income_range: user.income_range,
|
||||
moral_traits: user.moral_traits,
|
||||
personal_details_public: user.personal_details_public === true
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setPersonalDetails,
|
||||
updatePersonalDetails
|
||||
}
|
||||
@@ -8,14 +8,7 @@ const setProfileImage = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { mobile } = req.body
|
||||
const { profile_image } = req.files
|
||||
if (!mobile) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
|
||||
Reference in New Issue
Block a user