This commit is contained in:
payacom
2026-08-31 20:45:41 +03:30
parent fa9f23c61d
commit 3f416a7a60
367 changed files with 621895 additions and 6443 deletions

View File

@@ -1,5 +1,5 @@
/* eslint-disable camelcase */
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
const { CountryModel, ProvinceModel, CityModel } = require('../../../models/StateCity')
const ProjectModel = require('../../../models/ProjectModel')
const UserModel = require('../../../models/UserModel')
const { check, validationResult } = require('express-validator')
@@ -8,45 +8,78 @@ const {
canCreateProject,
respondProjectProfileIncomplete
} = require('../../../utils/projectProfile')
async function activateFreeProjectIfEligible(user, project) {
if (project.project_type !== 'free') return { activated: false }
if (!user.daily_free_request || user.daily_free_request <= 0) {
return {
activated: false,
error: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.'
}
}
user.daily_free_request -= 1
await user.save()
project.payment_status = 'done'
project.status = 'temp_accept'
await project.save()
return { activated: true }
}
function normalizeRoles(roles) {
if (!Array.isArray(roles)) return []
return roles.slice(0, 5).map((role) => ({
expertise: role.expertise,
sub_expertise: Array.isArray(role.sub_expertise)
? role.sub_expertise
: (role.subExpertise || []),
number_of_person: Number(role.number_of_person || role.numberOfPerson || 1) || 1,
height_min: role.height_min ?? role.heightMin ?? null,
height_max: role.height_max ?? role.heightMax ?? null,
weight_min: role.weight_min ?? role.weightMin ?? null,
weight_max: role.weight_max ?? role.weightMax ?? null,
size_min: role.size_min ?? role.sizeMin ?? null,
size_max: role.size_max ?? role.sizeMax ?? null,
eye_color: role.eye_color || role.eyeColor || null,
hair_color: role.hair_color || role.hairColor || null,
portfolio_required: Boolean(
role.portfolio_required ?? role.portfolioRequired ?? false
)
})).filter((role) => role.expertise)
}
function buildAgeLabel(age, ageMin, ageMax) {
if (age) return age
if (ageMin != null && ageMax != null) return `${ageMin}-${ageMax}`
if (ageMin != null) return `${ageMin}+`
if (ageMax != null) return `تا ${ageMax}`
return 'all'
}
const createProjectValidationRules = () => {
return [
check('title').notEmpty().withMessage('عنوان نمی‌تواند خالی باشد'),
check('expertise').notEmpty().withMessage('تخصص نمی‌تواند خالی باشد'),
check('gender')
.isIn(['male', 'female'])
.withMessage('جنسیت باید male یا female باشد'),
check('age').notEmpty().withMessage('سن نمی‌تواند خالی باشد'),
check('conversation_projects')
.notEmpty()
.withMessage('پروژه‌های مکالمه نمی‌تواند خالی باشد')
.isBoolean()
.withMessage('مقدار پروژه‌های مکالمه باید یک مقدار boolean باشد'),
check('province').notEmpty().withMessage('استان نمی‌تواند خالی باشد'),
check('city').notEmpty().withMessage('شهر نمی‌تواند خالی باشد'),
check('offer_time')
.notEmpty()
.withMessage('زمان پروژه نمی‌تواند خالی باشد'),
check('offer_price')
.notEmpty()
.withMessage('قیمت پیشنهادی نمی‌تواند خالی باشد'),
check('description')
.notEmpty()
.withMessage('توضیحات نمی‌تواند خالی باشد'),
check('province').notEmpty().withMessage('استان نمی‌تواند خالی باشد'),
check('city').notEmpty().withMessage('شهر نمی‌تواند خالی باشد'),
check('offer_time')
.notEmpty()
.withMessage('زمان پروژه نمی‌تواند خالی باشد'),
check('offer_price')
.notEmpty()
.withMessage('قیمت پیشنهادی نمی‌تواند خالی باشد'),
check('project_type')
.notEmpty()
.withMessage('نوع پروژه نمی‌تواند خالی باشد')
.isIn(['free', 'normal', 'force', 'highlight'])
.withMessage('نوع پروژه باید normal یا force یا highlight باشد')
.withMessage('نوع پروژه باید normal یا force یا highlight باشد'),
check('gender')
.optional({ nullable: true, checkFalsy: true })
.isIn(['male', 'female', 'any'])
.withMessage('جنسیت باید male یا female یا any باشد')
]
}
@@ -66,33 +99,55 @@ const createProject = async (req, res, next) => {
if (!canCreateProject(user)) {
return respondProjectProfileIncomplete(res)
}
// اعتبارسنجی درخواست
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
const {
title,
expertise,
sub_expertise,
gender,
age,
age_min,
age_max,
conversation_projects,
country,
province,
city,
lat,
lng,
neighborhood,
address,
offer_time,
offer_price,
description,
project_type,
public_status,
created_for_user,
number_of_person
number_of_person,
roles,
payment_method,
start_date,
work_hours_start,
work_hours_end,
collaboration_type,
experience
} = req.body
const normalizedRoles = normalizeRoles(roles)
const primaryExpertise =
expertise || (normalizedRoles[0] && normalizedRoles[0].expertise)
const primarySubExpertise =
sub_expertise ||
(normalizedRoles[0] && normalizedRoles[0].sub_expertise) ||
[]
if (
!title ||
!expertise ||
!age ||
conversation_projects === null ||
!primaryExpertise ||
!province ||
!city ||
!offer_time ||
@@ -106,110 +161,137 @@ const createProject = async (req, res, next) => {
})
}
if (normalizedRoles.length === 0) {
return res.status(422).json({
error: true,
message: 'حداقل یک نقش باید ثبت شود'
})
}
const countryFind = country
? await CountryModel.findOne({ id: country }, { _id: 0 })
: null
const provinceFind = await ProvinceModel.findOne(
{ id: province },
{ _id: 0 }
)
const cityFind = await CityModel.findOne({ id: city })
// اگر پروژه برای یک کاربر خاص باید ساخته شود
const ageLabel = buildAgeLabel(age, age_min, age_max)
const genderValue = gender || 'any'
const conversationValue =
conversation_projects === undefined || conversation_projects === null
? true
: conversation_projects
const sharedFields = {
title,
expertise: primaryExpertise,
sub_expertise: primarySubExpertise,
gender: genderValue,
age: ageLabel,
age_min: age_min != null && age_min !== '' ? Number(age_min) : null,
age_max: age_max != null && age_max !== '' ? Number(age_max) : null,
conversation_projects: conversationValue,
country: countryFind
? { id: countryFind.id, name: countryFind.name_fa, iso2: countryFind.iso2, slug: countryFind.slug }
: null,
province: provinceFind || province,
city: cityFind || city,
lat: lat != null && lat !== '' ? Number(lat) : null,
lng: lng != null && lng !== '' ? Number(lng) : null,
neighborhood: neighborhood || null,
address: address || null,
offer_time,
offer_price,
description,
...(payment_method ? { payment_method } : {}),
start_date: start_date || null,
work_hours_start: work_hours_start || null,
work_hours_end: work_hours_end || null,
...(collaboration_type ? { collaboration_type } : {}),
experience: experience || null,
roles: normalizedRoles,
creator_id: user._id,
public_status: public_status || 'public',
created_for_user: created_for_user || null
}
// private project for a specific user
if (public_status && public_status === 'private' && created_for_user) {
const createdUser = await UserModel.findById(created_for_user)
if (!createdUser) {
return res.status(422).json({
error: true, message: 'کاربر مورد نظر برای ساخت پروژه یافت نشد'
error: true,
message: 'کاربر مورد نظر برای ساخت پروژه یافت نشد'
})
}
// ایجاد شیء پروژه
const projectData = {
title,
expertise,
sub_expertise,
gender,
age,
conversation_projects,
province,
city,
offer_time,
offer_price,
description,
project_type,
creator_id: user._id, // ارتباط با کاربر
public_status,
created_for_user
}
// ساخت شیء پروژه
const project = new ProjectModel(projectData)
// ذخیره کردن رکورد در پایگاه داده
const project = new ProjectModel({
...sharedFields,
project_type
})
await project.save()
res.status(201).json({ message: 'پروژه با موفقیت ساخته شد', id: project._id }
)
} else {
if (number_of_person > 1) {
for (let i = 1; i <= number_of_person; i++) {
let currentProjectType
const freeResult = await activateFreeProjectIfEligible(user, project)
if (freeResult.error) {
return res.status(403).json({ error: true, message: freeResult.error })
}
if (project_type === 'free' && i === 1) {
currentProjectType = 'free'
} else if (project_type === 'highlight' || project_type === 'force') {
currentProjectType = project_type
} else {
currentProjectType = 'normal'
}
return res.status(201).json({
message: 'پروژه با موفقیت ساخته شد',
id: project._id
})
}
const newProject = new ProjectModel({
title: `${title}_${i}`,
expertise,
sub_expertise,
gender,
age,
conversation_projects,
province: provinceFind,
city: cityFind,
offer_time,
offer_price,
description,
creator_id: user._id, // ارتباط با کاربر
project_type: currentProjectType,
public_status,
created_for_user
})
// ذخیره کردن رکورد در پایگاه داده
await newProject.save()
// Legacy: multiple projects by headcount when no multi-role payload intent
const headcount = Number(number_of_person) || 1
if (headcount > 1 && normalizedRoles.length <= 1) {
for (let i = 1; i <= headcount; i++) {
let currentProjectType
if (project_type === 'free' && i === 1) {
currentProjectType = 'free'
} else if (project_type === 'highlight' || project_type === 'force') {
currentProjectType = project_type
} else {
currentProjectType = 'normal'
}
res.status(201).json({
message: 'پروژه‌ها با موفقیت ساخته شدند'
})
} else {
const project = new ProjectModel({
title,
expertise,
sub_expertise,
gender,
age,
conversation_projects,
province: provinceFind,
city: cityFind,
offer_time,
offer_price,
description,
creator_id: user._id, // ارتباط با کاربر
project_type
const newProject = new ProjectModel({
...sharedFields,
title: `${title}_${i}`,
project_type: currentProjectType
})
await newProject.save()
// ذخیره کردن رکورد در پایگاه داده
await project.save()
res.status(201).json({
message: 'پروژه با موفقیت ساخته شد',
id: project._id
})
if (currentProjectType === 'free') {
const freeResult = await activateFreeProjectIfEligible(user, newProject)
if (freeResult.error) {
return res.status(403).json({ error: true, message: freeResult.error })
}
}
}
return res.status(201).json({
message: 'پروژه‌ها با موفقیت ساخته شدند'
})
}
const project = new ProjectModel({
...sharedFields,
project_type
})
await project.save()
const freeResult = await activateFreeProjectIfEligible(user, project)
if (freeResult.error) {
return res.status(403).json({ error: true, message: freeResult.error })
}
return res.status(201).json({
message: 'پروژه با موفقیت ساخته شد',
id: project._id
})
} catch (error) {
next(error)
}

View File

@@ -1,6 +1,7 @@
const TypeAndPriceModel = require('../../../models/TypeAndPriceModel')
const jwt = require('jsonwebtoken')
const UserModel = require('../../../models/UserModel')
const { canUseFreeCreation } = require('../../../utils/freeCreationLimit')
const getProjectTypes = async (req, res, next) => {
try {
@@ -24,7 +25,7 @@ const getProjectTypes = async (req, res, next) => {
return res.status(404).json({ message: 'انواع پروژه یافت نشد.' })
}
// اگر کاربر درخواست رایگان روزانه نداشته باشد، نوع پروژه رایگان را حذف کنید
if (user.daily_free_request <= 0) {
if (user.daily_free_request <= 0 || !canUseFreeCreation(user)) {
projectTypes = projectTypes.filter(projectType => projectType.name !== 'free')
}
// ارسال انواع پروژه به کاربر

View File

@@ -1,18 +1,56 @@
/* eslint-disable camelcase */
const { default: axios } = require('axios')
const fs = require('fs')
const path = require('path')
const NotificationModel = require('../../../models/NotificationModel')
const ProjectModel = require('../../../models/ProjectModel')
const RequestModel = require('../../../models/RequestModel')
const UserModel = require('../../../models/UserModel')
const { check, validationResult } = require('express-validator')
const jwt = require('jsonwebtoken')
const portfolioStorageDir = path.join(
process.cwd(),
'public',
'uploads',
'project-requests'
)
async function savePortfolioFiles(files, userId) {
if (!Array.isArray(files) || files.length === 0) return []
if (!fs.existsSync(portfolioStorageDir)) {
fs.mkdirSync(portfolioStorageDir, { recursive: true })
}
const saved = []
const limited = files.slice(0, 5)
for (let i = 0; i < limited.length; i++) {
const file = limited[i]
const raw = file?.data || file
if (typeof raw !== 'string' || !raw.includes('base64')) continue
const isVideo =
file?.type === 'video' ||
String(file?.mime || '').startsWith('video/') ||
raw.startsWith('data:video')
const buffer = Buffer.from(raw.split(',')[1] || raw, 'base64')
const ext = isVideo ? '.mp4' : '.webp'
const filename = `${Date.now()}-${userId}-${i}${ext}`
const filePath = path.join(portfolioStorageDir, filename)
await fs.promises.writeFile(filePath, buffer)
saved.push({
path: `/uploads/project-requests/${filename}`,
type: isVideo ? 'video' : 'image'
})
}
return saved
}
const requestProjectValidationRules = () => {
return [
check('project_id')
.notEmpty().withMessage('آیدی پروژه نمی‌تواند خالی باشد'),
check('request_time')
.notEmpty().withMessage('زمان پروژه نمی‌تواند خالی باشد'),
check('offer_price')
.notEmpty().withMessage('قیمت پیشنهادی نمی‌تواند خالی باشد')
]
@@ -29,19 +67,21 @@ const requestProject = async (req, res, next) => {
error: true, message: 'شما دسترسی به این بخش ندارید'
})
}
if (user.is_verified !== 'verified') {
return res.status(422).json({
error: true, message: 'مدارک شما تایید نشده است'
})
}
// اعتبارسنجی درخواست
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
const { request_time, offer_price, project_id } = req.body
if (!project_id || !offer_price || !request_time
) {
const {
request_time,
offer_price,
project_id,
role_id,
portfolio
} = req.body
if (!project_id || !offer_price || !request_time) {
return res.status(422).json({
error: true,
message: 'اطلاعات ارسالی اشتباه است'
@@ -62,26 +102,48 @@ const requestProject = async (req, res, next) => {
})
}
// چک کردن برای وجود درخواست قبلی
const existingRequest = await RequestModel.findOne({ project: project_id, user: user._id })
const existingRequest = await RequestModel.findOne({
project: project_id,
user: user._id,
...(role_id ? { role_id } : {})
})
if (existingRequest) {
return res.status(422).json({
error: true,
message: 'شما قبلاً برای این پروژه درخواست داده‌اید'
})
}
// ایجاد یک درخواست جدید
let selectedRole = null
if (role_id && Array.isArray(project.roles) && project.roles.length) {
selectedRole = project.roles.id(role_id) ||
project.roles.find((r) => String(r._id) === String(role_id))
}
if (!selectedRole && Array.isArray(project.roles) && project.roles.length === 1) {
selectedRole = project.roles[0]
}
const portfolioRequired = Boolean(selectedRole?.portfolio_required)
const portfolioFiles = await savePortfolioFiles(portfolio, user._id)
if (portfolioRequired && portfolioFiles.length < 1) {
return res.status(422).json({
error: true,
message: 'ارسال حداقل یک نمونه کار الزامی است'
})
}
const request = new RequestModel({
project: project_id,
user: user._id,
time: request_time,
price: offer_price
price: offer_price,
role_id: selectedRole?._id || null,
role_expertise: selectedRole?.expertise || project.expertise || null,
portfolio: portfolioFiles
})
await request.save()
// ثبت اعلان برای سازنده پروژه
const creatorId = project.creator_id
const notification = new NotificationModel({
user_id: creatorId,
project_post_id: project_id,
@@ -91,35 +153,6 @@ const requestProject = async (req, res, next) => {
})
await notification.save()
const userReciver = await UserModel.findById(project.creator_id)
const data = JSON.stringify({
mobile: userReciver?.mobile,
templateId: '876533',
parameters: [
{ name: 'EMPLOYER', value: userReciver?.first_name + ' ' + userReciver?.last_name },
{ name: 'PROJECT', value: project?.title }
]
})
const config = {
method: 'post',
url: 'https://api.sms.ir/v1/send/verify',
headers: {
'Content-Type': 'application/json',
Accept: 'text/plain',
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
},
data
}
axios(config)
.then(function (response) {
})
.catch(function (error) {
console.log(error)
})
// به روز رسانی پروژه با افزودن کاربر به لیست کاربران درخواست دهنده
const updatedProject = await ProjectModel.findOneAndUpdate(
{ _id: project_id },
{ $push: { requested_users: user._id } },
@@ -131,8 +164,7 @@ const requestProject = async (req, res, next) => {
return res.status(404).json({ error: true, message: 'پروژه پیدا نشد' })
}
res.status(201).json({ message: 'درخواست با موفقیت شد', id: project_id }
)
res.status(201).json({ message: 'درخواست با موفقیت شد', id: project_id })
} catch (error) {
next(error)
}
@@ -149,38 +181,34 @@ const editRequestProject = async (req, res, next) => {
error: true, message: 'شما دسترسی به این بخش ندارید'
})
}
if (user.is_verified !== 'verified') {
return res.status(422).json({
error: true, message: 'مدارک شما تایید نشده است'
})
}
// اعتبارسنجی درخواست
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
const { request_time, offer_price, project_id } = req.body
if (!project_id || !offer_price || !request_time
) {
if (!project_id || !offer_price || !request_time) {
return res.status(422).json({
error: true,
message: 'اطلاعات ارسالی اشتباه است'
})
}
// چک کردن برای وجود درخواست قبلی
const existingRequest = await RequestModel.findOne({ project: project_id, user: user._id })
const existingRequest = await RequestModel.findOne({
project: project_id,
user: user._id
})
if (!existingRequest) {
return res.status(422).json({
error: true,
message: 'شما قبلاً برای این پروژه درخواست نداده‌اید'
})
}
// اکنون می‌توانید اطلاعات درخواست را ویرایش کنید
existingRequest.time = request_time
existingRequest.price = offer_price
await existingRequest.save()
// ثبت اعلان برای سازنده پروژه
const project = await ProjectModel.findById(project_id)
const creatorId = project.creator_id
@@ -200,6 +228,7 @@ const editRequestProject = async (req, res, next) => {
next(error)
}
}
const acceptProject = async (req, res, next) => { }
module.exports = {
requestProjectValidationRules,

View File

@@ -3,18 +3,18 @@ const ProjectModel = require('../../../models/ProjectModel')
const jwt = require('jsonwebtoken')
const UserModel = require('../../../models/UserModel')
const { check, validationResult } = require('express-validator')
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
const { CountryModel, ProvinceModel, CityModel } = require('../../../models/StateCity')
const NotificationModel = require('../../../models/NotificationModel')
const CommentModel = require('../../../models/CommentModel')
const {
resolveRatingForComment,
recalculateUserRating
} = require('../../../utils/commentRating')
const { saveCommentWithModerationStatus } = require('../../../utils/commentStatusSave')
const {
canCreateProject,
respondProjectProfileIncomplete
} = require('../../../utils/projectProfile')
const saveProjectComment = async ({
targetUserId,
projectId,
@@ -37,16 +37,18 @@ const saveProjectComment = async ({
return { error: { status: 400, message: 'Rating must be between 1 and 5' } }
}
const newComment = new CommentModel({
user: targetUserId,
project: projectId,
creator: creatorId,
rating: ratingValue,
comment,
comment_for: commentFor,
status: 'pending'
})
await newComment.save()
const newComment = await saveCommentWithModerationStatus(
CommentModel,
{
user: targetUserId,
project: projectId,
creator: creatorId,
rating: ratingValue,
comment,
comment_for: commentFor,
},
'comment'
)
if (isNewRating && commentFor === 'user') {
await recalculateUserRating(targetUserId)
@@ -139,6 +141,10 @@ const doneProjectWeb = async (req, res, next) => {
return res.status(403).send({ message: 'Access denied: You are not the creator of this project.' })
}
if (project.selected_user && Number(project.final_price) > 0 && !project.completionShareCredited) {
return res.status(422).send({ message: 'ابتدا باید مبلغ باقی‌مانده پروژه را پرداخت کنید' })
}
project.status = 'done'
await project.save()
@@ -218,36 +224,21 @@ const cancleProject = async (req, res, next) => {
const editProjectValidationRules = () => {
return [
check('title').notEmpty().withMessage('عنوان نمی‌تواند خالی باشد'),
check('expertise').notEmpty().withMessage('تخصص نمی‌تواند خالی باشد'),
check('gender')
.isIn(['male', 'female'])
.withMessage('جنسیت باید male یا female باشد'),
check('age').notEmpty().withMessage('سن نمی‌تواند خالی باشد'),
check('conversation_projects')
.notEmpty()
.withMessage('پروژه‌های مکالمه نمی‌تواند خالی باشد')
.isBoolean()
.withMessage('مقدار پروژه‌های مکالمه باید یک مقدار boolean باشد'),
check('province').notEmpty().withMessage('استان نمی‌تواند خالی باشد'),
check('city').notEmpty().withMessage('شهر نمی‌تواند خالی باشد'),
check('offer_time')
.notEmpty()
.withMessage('زمان پروژه نمی‌تواند خالی باشد'),
check('offer_price')
.notEmpty()
.withMessage('قیمت پیشنهادی نمی‌تواند خالی باشد'),
check('description')
.notEmpty()
.withMessage('توضیحات نمی‌تواند خالی باشد')
.withMessage('توضیحات نمی‌تواند خالی باشد'),
check('gender')
.optional({ nullable: true, checkFalsy: true })
.isIn(['male', 'female', 'any'])
.withMessage('جنسیت باید male یا female یا any باشد')
]
}
@@ -279,13 +270,28 @@ const editProject = async (req, res, next) => {
sub_expertise,
gender,
age,
age_min,
age_max,
conversation_projects,
country,
province,
city,
lat,
lng,
neighborhood,
address,
offer_time,
offer_price,
description,
projectId
projectId,
roles,
payment_method,
start_date,
work_hours_start,
work_hours_end,
collaboration_type,
experience,
project_type
} = req.body
const project = await ProjectModel.findById(projectId)
@@ -302,18 +308,36 @@ const editProject = async (req, res, next) => {
message: 'شما نمیتوانید این پروژه را ویرایش کنید'
})
}
if (project.status !== 'paid' && project.status !== 'pre_payment') {
if (project.status !== 'paid' && project.status !== 'temp_accept' && project.status !== 'pre_payment') {
return res.status(422).json({
error: true,
message: 'شما نمیتوانید این پروژه را ویرایش کنید'
})
}
// اعتبارسنجی اطلاعات ویرایش شده
const normalizedRoles = Array.isArray(roles)
? roles.slice(0, 5).map((role) => ({
expertise: role.expertise,
sub_expertise: Array.isArray(role.sub_expertise) ? role.sub_expertise : [],
number_of_person: Number(role.number_of_person || 1) || 1,
height_min: role.height_min ?? null,
height_max: role.height_max ?? null,
weight_min: role.weight_min ?? null,
weight_max: role.weight_max ?? null,
size_min: role.size_min ?? null,
size_max: role.size_max ?? null,
eye_color: role.eye_color || null,
hair_color: role.hair_color || null,
portfolio_required: Boolean(role.portfolio_required)
})).filter((role) => role.expertise)
: null
const primaryExpertise =
expertise || (normalizedRoles && normalizedRoles[0] && normalizedRoles[0].expertise)
if (
!title ||
!expertise ||
!age ||
conversation_projects === null ||
!primaryExpertise ||
!province ||
!city ||
!offer_time ||
@@ -325,24 +349,50 @@ const editProject = async (req, res, next) => {
message: 'اطلاعات ارسالی اشتباه است'
})
}
const countryFind = country
? await CountryModel.findOne({ id: country }, { _id: 0 })
: null
const provinceFind = await ProvinceModel.findOne(
{ id: province },
{ _id: 0 }
)
const cityFind = await CityModel.findOne({ id: city })
// ویرایش یک نسخه از پروژه
project.title = title
project.expertise = expertise
project.sub_expertise = sub_expertise
project.gender = gender
project.age = age
project.conversation_projects = conversation_projects
project.expertise = primaryExpertise
project.sub_expertise =
sub_expertise ||
(normalizedRoles && normalizedRoles[0] && normalizedRoles[0].sub_expertise) ||
[]
project.gender = gender || 'any'
project.age = age || 'all'
if (age_min != null && age_min !== '') project.age_min = Number(age_min)
if (age_max != null && age_max !== '') project.age_max = Number(age_max)
project.conversation_projects =
conversation_projects === undefined || conversation_projects === null
? true
: conversation_projects
if (countryFind) {
project.country = { id: countryFind.id, name: countryFind.name_fa, iso2: countryFind.iso2, slug: countryFind.slug }
}
project.province = provinceFind
project.city = cityFind
if (lat != null && lat !== '') project.lat = Number(lat)
if (lng != null && lng !== '') project.lng = Number(lng)
project.neighborhood = neighborhood || null
project.address = address || null
project.offer_time = offer_time
project.offer_price = offer_price
project.description = description
// ذخیره کردن تغییرات در پایگاه داده
if (payment_method) project.payment_method = payment_method
project.start_date = start_date || null
project.work_hours_start = work_hours_start || null
project.work_hours_end = work_hours_end || null
if (collaboration_type) project.collaboration_type = collaboration_type
project.experience = experience || null
if (normalizedRoles) project.roles = normalizedRoles
if (project_type) project.project_type = project_type
await project.save()
res.status(200).json({
message: 'پروژه با موفقیت ویرایش شد',