This commit is contained in:
payacom
2026-09-13 21:32:36 +03:30
parent 90c5f94b57
commit c302aea177
49 changed files with 4063 additions and 920 deletions

View File

@@ -53,8 +53,11 @@ 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
// مثل country بالا — نام فارسی (name_fa) رو ترجیح بده، وگرنه استان/شهر با نام
// انگلیسیِ خام (name) ذخیره می‌شد؛ قبلاً کل داکیومنت خام province/city (که name_fa
// جدا داشت ولی field نمایشیِ name همچنان انگلیسی بود) مستقیم ذخیره می‌شد
user.province = { id: province.id, name: province.name_fa || province.name, slug: province.slug }
user.city = { id: city.id, name: city.name_fa || city.name, slug: city.slug }
user.address = address
user.lat = lat
user.lng = lng
@@ -125,8 +128,11 @@ 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
// مثل country بالا — نام فارسی (name_fa) رو ترجیح بده، وگرنه استان/شهر با نام
// انگلیسیِ خام (name) ذخیره می‌شد؛ قبلاً کل داکیومنت خام province/city (که name_fa
// جدا داشت ولی field نمایشیِ name همچنان انگلیسی بود) مستقیم ذخیره می‌شد
user.province = { id: province.id, name: province.name_fa || province.name, slug: province.slug }
user.city = { id: city.id, name: city.name_fa || city.name, slug: city.slug }
user.address = address
user.lat = lat
user.lng = lng

View File

@@ -41,6 +41,49 @@ const setGender = async (req, res, next) => {
}
}
module.exports = {
setGender
// نسخه‌ی PATCH — عیناً همون منطقِ setGender، فقط طبق قراردادِ بقیه‌ی فیلدهای
// «مشخصات فردی» (نگاه کنید personalDetailsController.js: set=POST برای ثبت‌نامِ
// اولیه، update=PATCH برای ویرایش از تنظیمات) که settings/edit/gender/page.tsx
// باید صداش بزنه، نه POST که مخصوص ویزارد ثبت‌نامه
const updateGender = async (req, res, next) => {
try {
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const { gender } = req.body
if (!gender) {
return res.status(422).json({
error: true,
message: 'اطلاعات ورودی اشتباه است'
})
}
if (!['male', 'female'].includes(gender)) {
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) {
return res.status(404).json({
error: true,
message: 'کاربری یافت نشد'
})
}
user.gender = gender.toLowerCase()
await user.save()
return res.json({
message: 'جنسیت با موفقیت به‌روزرسانی شد',
gender: user.gender
})
} catch (error) {
next(error)
}
}
module.exports = {
setGender,
updateGender
}

View File

@@ -2,11 +2,118 @@
const UserModel = require('../../../models/UserModel')
const jwt = require('jsonwebtoken')
const MARITAL_STATUS_VALUES = ['single', 'divorced']
const MARITAL_STATUS_VALUES = ['single', 'divorced', 'widowed', 'not_in_relationship', 'prefer_not_to_say']
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']
const PERSONAL_STYLE_VALUES = ['casual', 'classic', 'minimal', 'formal', 'street', 'romantic']
const INCOME_RANGE_VALUES = [
'under_40m', '40m_50m', '50m_60m', '60m_70m', '70m_80m',
'80m_90m', '90m_100m', '100m_150m', '150m_200m', 'over_200m'
]
const RELATIONSHIP_GOAL_VALUES = [
'friendship',
'serious_partner',
'marriage_intro',
'emotional_relationship',
'travel_event_companion',
'social_networking',
'not_sure'
]
const SEEKING_GENDER_VALUES = ['female', 'male', 'both', 'not_important']
const DESIRED_AGE_RANGE_VALUES = ['18_24', '25_30', '31_35', '36_40', '41_50', 'over_50', 'not_important']
const PERSONALITY_TRAITS_VALUES = [
'kind',
'loyal',
'funny',
'calm',
'energetic',
'responsible',
'ambitious',
'emotional',
'logical',
'patient',
'honest',
'romantic',
'independent',
'family_oriented',
'adventurous',
'introvert',
'extrovert'
]
const LIFESTYLE_VALUES = [
'active_healthy',
'homebody_calm',
'social_outgoing',
'busy_career',
'traveler',
'night_owl',
'early_riser',
'minimalist',
'luxury_lover'
]
const INTERESTS_VALUES = [
'movies_series',
'music',
'books_reading',
'sports',
'cooking',
'photography',
'travel',
'nature',
'gaming',
'art_painting',
'fashion',
'pets',
'dancing',
'language_learning'
]
const VALUES_VALUES = [
'family',
'career_growth',
'religion_spirituality',
'personal_freedom',
'financial_stability',
'health_fitness',
'personal_growth',
'excitement_new_experiences'
]
const RELATIONSHIP_VIEW_VALUES = [
'traditional',
'modern',
'long_term',
'friendship_first',
'marriage_soon',
'friendship_only_for_now'
]
const GEO_PREFERENCE_VALUES = [
'same_city_only',
'nearby_cities',
'whole_province',
'whole_iran',
'even_abroad',
'online_is_fine'
]
const CHILDREN_STATUS_VALUES = ['no_children', 'has_children', 'open_to_children', 'not_now', 'prefer_not_to_say']
const SMOKING_STATUS_VALUES = ['none', 'sometimes', 'yes', 'doesnt_matter', 'prefer_not_to_say']
/** Validates a multi-select field: must be an array and every item must be
* in `allowedValues` (no cap on how many may be selected). Returns a
* Persian error message on failure, or null on success. */
function validateMultiSelect (value, allowedValues, errorMessage) {
if (!Array.isArray(value)) return errorMessage
if (!value.every((item) => allowedValues.includes(item))) return errorMessage
return null
}
/** All fields are optional profile-enrichment fields — only the ones present
* in the request body are validated/applied, unlike sizes/cooperation-type
@@ -54,12 +161,96 @@ function applyPersonalDetails (user, body) {
}
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)
}
if (body.relationship_goal !== undefined) {
const error = validateMultiSelect(
body.relationship_goal, RELATIONSHIP_GOAL_VALUES,
'هدف از آشنایی نامعتبر است'
)
if (error) return error
user.relationship_goal = body.relationship_goal
}
if (body.seeking_gender !== undefined) {
if (body.seeking_gender !== null && !SEEKING_GENDER_VALUES.includes(body.seeking_gender)) {
return 'جنسیت مورد نظر نامعتبر است'
}
user.seeking_gender = body.seeking_gender
}
if (body.desired_age_range !== undefined) {
if (body.desired_age_range !== null && !DESIRED_AGE_RANGE_VALUES.includes(body.desired_age_range)) {
return 'بازه سنی مورد نظر نامعتبر است'
}
user.desired_age_range = body.desired_age_range
}
if (body.personality_traits !== undefined) {
const error = validateMultiSelect(
body.personality_traits, PERSONALITY_TRAITS_VALUES,
'ویژگی‌های شخصیتی نامعتبر است'
)
if (error) return error
user.personality_traits = body.personality_traits
}
if (body.lifestyle !== undefined) {
const error = validateMultiSelect(
body.lifestyle, LIFESTYLE_VALUES,
'سبک زندگی نامعتبر است'
)
if (error) return error
user.lifestyle = body.lifestyle
}
if (body.interests !== undefined) {
const error = validateMultiSelect(
body.interests, INTERESTS_VALUES,
'علایق نامعتبر است'
)
if (error) return error
user.interests = body.interests
}
if (body.values !== undefined) {
const error = validateMultiSelect(
body.values, VALUES_VALUES,
'ارزش‌ها نامعتبر است'
)
if (error) return error
user.values = body.values
}
if (body.relationship_view !== undefined) {
if (body.relationship_view !== null && !RELATIONSHIP_VIEW_VALUES.includes(body.relationship_view)) {
return 'دیدگاه نسبت به رابطه نامعتبر است'
}
user.relationship_view = body.relationship_view
}
if (body.geo_preference !== undefined) {
if (body.geo_preference !== null && !GEO_PREFERENCE_VALUES.includes(body.geo_preference)) {
return 'محدوده جغرافیایی مورد نظر نامعتبر است'
}
user.geo_preference = body.geo_preference
}
if (body.children_status !== undefined) {
const error = validateMultiSelect(
body.children_status, CHILDREN_STATUS_VALUES,
'وضعیت فرزند نامعتبر است'
)
if (error) return error
user.children_status = body.children_status
}
if (body.smoking_status !== undefined) {
if (body.smoking_status !== null && !SMOKING_STATUS_VALUES.includes(body.smoking_status)) {
return 'وضعیت مصرف دخانیات نامعتبر است'
}
user.smoking_status = body.smoking_status
}
// فیلد سبک‌وزن برای ثبت تاریخ تولد از همین اندپوینت (بدون نیاز به شبا/کد ملی که
// POST /verify/auth الزامی می‌کنه) — مثلاً وقتی کاربر فقط سن رو توی «مشخصات فردی»
// اضافه می‌کنه، نه کل احراز هویت بانکی/ملی
if (body.birthday !== undefined) {
if (body.birthday !== null && Number.isNaN(Date.parse(body.birthday))) {
return 'تاریخ تولد نامعتبر است'
}
user.birthday = body.birthday
}
return null
}
@@ -123,8 +314,19 @@ const updatePersonalDetails = async (req, res, next) => {
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
personal_details_public: user.personal_details_public === true,
relationship_goal: user.relationship_goal,
seeking_gender: user.seeking_gender,
desired_age_range: user.desired_age_range,
personality_traits: user.personality_traits,
lifestyle: user.lifestyle,
interests: user.interests,
values: user.values,
relationship_view: user.relationship_view,
geo_preference: user.geo_preference,
children_status: user.children_status,
smoking_status: user.smoking_status,
birthday: user.birthday
})
} catch (error) {
next(error)