diff --git a/controllers/application/academy/academyControllers.js b/controllers/application/academy/academyControllers.js index 7493548..700febb 100644 --- a/controllers/application/academy/academyControllers.js +++ b/controllers/application/academy/academyControllers.js @@ -197,7 +197,7 @@ const academyProfile = async (req, res, next) => { }) } - const { profile_image } = req.files + const profile_image = req.file const { name, sheba, bio, tag } = req.body if (!profile_image) { @@ -227,7 +227,7 @@ const academyProfile = async (req, res, next) => { } const uniqueFileName = `${name}-${Date.now()}${path.extname( - profile_image.name + profile_image.originalname )}` const filePath = path.join(uploadDir, uniqueFileName) @@ -329,7 +329,7 @@ const creatCourse = async (req, res, next) => { const isFreePackage = is_free === 'true' || is_free === true const contactInfo = parseCourseContactInfo(req.body) - const { course_image } = req.files + const course_image = req.file if (!course_image) { return res.status(422).json({ @@ -344,7 +344,7 @@ const creatCourse = async (req, res, next) => { } const uniqueFileName = `${cuorse_name}-${Date.now()}${path.extname( - course_image.name + course_image.originalname )}` const filePath = path.join(uploadDir, uniqueFileName) @@ -422,7 +422,7 @@ const updateCourse = async (req, res, next) => { } // ========== آپلود عکس جدید (اختیاری) ========== - const { course_image } = req.files || {} + const course_image = req.file if (course_image) { // فقط اگه عکس جدید آپلود شده بود، آپلود کن @@ -433,7 +433,7 @@ const updateCourse = async (req, res, next) => { // استفاده از id به جای نام دوره برای نام فایل const safeFileName = `course-${courseId}-${Date.now()}${path.extname( - course_image.name + course_image.originalname )}` const filePath = path.join(uploadDir, safeFileName) diff --git a/controllers/application/advertising/advertisingController.js b/controllers/application/advertising/advertisingController.js index 10f4e42..df51fe0 100644 --- a/controllers/application/advertising/advertisingController.js +++ b/controllers/application/advertising/advertisingController.js @@ -60,12 +60,13 @@ const createAdvertisingValidationRules = () => { check('city').notEmpty().withMessage('شهر نمی‌تواند خالی باشد') ] } -// تابع کمکی برای ذخیره تصاویر +// تابع کمکی برای ذخیره تصاویر — file یه آبجکتِ multer‌ه (originalname/path)، نه +// express-fileupload (name/path) const saveFile = async (file, folder, userName) => { const uploadDir = path.join(__dirname, '../../../../storage', folder) if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true }) - const uniqueFileName = `${userName}-${Date.now()}${Math.random().toString().slice(2, 11)}${path.extname(file.name)}` + const uniqueFileName = `${userName}-${Date.now()}${Math.random().toString().slice(2, 11)}${path.extname(file.originalname)}` const filePath = path.join(uploadDir, uniqueFileName) await fs.move(file.path, filePath) return `/${folder}/${uniqueFileName}` @@ -129,15 +130,16 @@ const createAdvertising = async (req, res, next) => { return res.status(422).json({ error: true, message: 'فرمت خدمات اشتباه است' }) } + // multer (routes/application/advertising/index.js) با .any() همه‌ی فایل‌ها رو + // به‌صورتِ آرایه می‌ده (چون serviceImages[id] اسمِ فیلدش به‌ازای هر خدمت پویاست) + const uploadedFiles = Array.isArray(req.files) ? req.files : [] + const servicesWithImages = [] for (const service of parsedServices) { const serviceImages = [] - const serviceId = service.id - const serviceFiles = req.files?.serviceImages?.[serviceId] - const imageFiles = Array.isArray(serviceFiles) ? serviceFiles : serviceFiles ? [serviceFiles] : [] - - for (const file of imageFiles) { - const imageUrl = await saveFile(file, 'services', user.user_name) + const serviceFile = uploadedFiles.find((f) => f.fieldname === `serviceImages[${service.id}]`) + if (serviceFile) { + const imageUrl = await saveFile(serviceFile, 'services', user.user_name) serviceImages.push(imageUrl) service.image = imageUrl } @@ -145,7 +147,7 @@ const createAdvertising = async (req, res, next) => { } // ✅ مدیریت تصاویر تبلیغ - const imageFiles = Array.isArray(req.files?.images) ? req.files.images : req.files?.images ? [req.files.images] : [] + const imageFiles = uploadedFiles.filter((f) => f.fieldname === 'images') const images = [] for (const file of imageFiles) { const imageUrl = await saveFile(file, 'advertising', user.user_name) @@ -1337,6 +1339,10 @@ const editAdvertising = async (req, res, next) => { const provinceFind = await ProvinceModel.findOne({ id: province }, { _id: 0 }) const cityFind = await CityModel.findOne({ id: city }) + // multer (routes/application/advertising/index.js) با .any() همه‌ی فایل‌ها رو + // به‌صورتِ آرایه می‌ده (چون serviceImages[id] اسمِ فیلدش به‌ازای هر خدمت پویاست) + const uploadedFiles = Array.isArray(req.files) ? req.files : [] + // مدیریت تصاویر برای خدمات const servicesWithImages = [] const parsedServices = JSON.parse(services) @@ -1345,24 +1351,12 @@ const editAdvertising = async (req, res, next) => { if (typeof service.image === 'string' && service.image.startsWith('/services/')) { servicesWithImages.push(service) } else { - const serviceImages = [] - const serviceId = service.id - if (req.files && req.files.serviceImages && req.files.serviceImages[serviceId]) { - const imageFiles = Array.isArray(req.files.serviceImages[serviceId]) ? req.files.serviceImages[serviceId] : [req.files.serviceImages[serviceId]] - const uploadDir = path.join(__dirname, '../../../../storage/services') - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }) - } - for (const imageFile of imageFiles) { - const uniqueFileName = `${user.user_name}-${Date.now()}${Math.random().toString().slice(2, 11)}${path.extname(imageFile.name)}` - const filePath = path.join(uploadDir, uniqueFileName) - await fs.move(imageFile.path, filePath) - const imageUrl = `/services/${uniqueFileName}` - serviceImages.push(imageUrl) - service.image = imageUrl - } + const serviceFile = uploadedFiles.find((f) => f.fieldname === `serviceImages[${service.id}]`) + if (serviceFile) { + const imageUrl = await saveFile(serviceFile, 'services', user.user_name) + service.image = imageUrl } - servicesWithImages.push({ ...service, image: serviceImages[0] }) + servicesWithImages.push(service) } } } @@ -1379,22 +1373,10 @@ const editAdvertising = async (req, res, next) => { } // اضافه کردن تصاویر جدید - if (req.files && req.files.images) { - const imageFiles = Array.isArray(req.files.images) ? req.files.images : [req.files.images] - const uploadDir = path.join(__dirname, '../../../../storage/advertising') - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }) - } - for (const imageFile of imageFiles) { - // بررسی فرمت تصویر - if (imageFile && typeof imageFile === 'object' && imageFile.path) { - const uniqueFileName = `${user.user_name}-${Date.now()}${Math.random().toString().slice(2, 11)}${path.extname(imageFile.name)}` - const filePath = path.join(uploadDir, uniqueFileName) - await fs.move(imageFile.path, filePath) - const imageUrl = `/advertising/${uniqueFileName}` - images.push(imageUrl) - } - } + const imageFiles = uploadedFiles.filter((f) => f.fieldname === 'images') + for (const file of imageFiles) { + const imageUrl = await saveFile(file, 'advertising', user.user_name) + images.push(imageUrl) } const parsedFeatures = JSON.parse(features) @@ -1421,8 +1403,8 @@ const editAdvertising = async (req, res, next) => { await advertising.save() if (contactInfo && contactInfo.saveInfoForNextAds) { - user.contactInfo = contactInfo - await user.save() + // updateOne اتمیک به‌جای user.save() — همون دلیلِ فیکسِ setProfileImageController.js + await UserModel.updateOne({ _id: user._id }, { contactInfo }) } res.status(200).json({ message: 'ویترین با موفقیت ویرایش شد', @@ -1646,7 +1628,7 @@ const updateUserAdvertisingProfile = async (req, res, next) => { }) } - const { profile_image } = req.files + const profile_image = req.file const { // profile_image, vitrine_name, @@ -1670,7 +1652,7 @@ const updateUserAdvertisingProfile = async (req, res, next) => { if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }) } - const uniqueFileName = `${advertisingProfile?.vitrine_name}-${Date.now()}${path.extname(profile_image.name)}` + const uniqueFileName = `${advertisingProfile?.vitrine_name}-${Date.now()}${path.extname(profile_image.originalname)}` const filePath = path.join(uploadDir, uniqueFileName) await fs.move(profile_image.path, filePath) updateFields.profile_image = `/profiles/${uniqueFileName}` diff --git a/controllers/application/shop-products/shopProductController.js b/controllers/application/shop-products/shopProductController.js index 476b624..2bb56a9 100644 --- a/controllers/application/shop-products/shopProductController.js +++ b/controllers/application/shop-products/shopProductController.js @@ -42,10 +42,10 @@ function productError (res, status, message) { async function saveFile (file, folder, prefix) { const uploadDir = path.join(__dirname, '../../../storage', folder) fs.ensureDirSync(uploadDir) - const uniqueFileName = `${prefix}-${Date.now()}${Math.random().toString().slice(2, 9)}${path.extname(file.name)}` + const uniqueFileName = `${prefix}-${Date.now()}${Math.random().toString().slice(2, 9)}${path.extname(file.originalname)}` const filePath = path.join(uploadDir, uniqueFileName) await fs.move(file.path, filePath, { overwrite: true }) - if ((file.mimetype || file.type || '').startsWith('image/')) { + if ((file.mimetype || '').startsWith('image/')) { await optimizeImageFile(filePath) } return `/${folder}/${uniqueFileName}` @@ -132,11 +132,7 @@ const createShopProduct = async (req, res, next) => { }) } - const imageFiles = Array.isArray(req.files?.images) - ? req.files.images - : req.files?.images - ? [req.files.images] - : [] + const imageFiles = Array.isArray(req.files) ? req.files : [] const images = [] for (const file of imageFiles) { @@ -232,11 +228,7 @@ const updateShopProduct = async (req, res, next) => { keptImages = Array.isArray(existing) ? existing : [] } - const imageFiles = Array.isArray(req.files?.images) - ? req.files.images - : req.files?.images - ? [req.files.images] - : [] + const imageFiles = Array.isArray(req.files) ? req.files : [] const newImages = [] for (const file of imageFiles) { const url = await saveFile(file, 'shop-products', shop._id) diff --git a/controllers/application/subUsers/subUserController.js b/controllers/application/subUsers/subUserController.js index 4de5280..a567e1c 100644 --- a/controllers/application/subUsers/subUserController.js +++ b/controllers/application/subUsers/subUserController.js @@ -22,14 +22,13 @@ async function findOwnedSubUser (ownerId, subUserId) { return subUser } -async function saveAvatarIfProvided (user, files) { - const profile_image = files?.profile_image +async function saveAvatarIfProvided (user, profile_image) { if (!profile_image) return const uploadDir = path.join(__dirname, '../../../storage/profiles') if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }) } - const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(profile_image.name)}` + const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(profile_image.originalname)}` const filePath = path.join(uploadDir, uniqueFileName) await fs.move(profile_image.path, filePath) await optimizeImageFile(filePath, { maxDimension: AVATAR_MAX_DIMENSION }) @@ -78,7 +77,7 @@ const createSubUser = async (req, res, next) => { sub_user_permissions: sanitizeSubUserPermissions(permissions) }) - await saveAvatarIfProvided(subUser, req.files) + await saveAvatarIfProvided(subUser, req.file) await subUser.save() return res.json({ @@ -167,7 +166,7 @@ const updateSubUser = async (req, res, next) => { subUser.sub_user_permissions = sanitizeSubUserPermissions(permissions) } - await saveAvatarIfProvided(subUser, req.files) + await saveAvatarIfProvided(subUser, req.file) await subUser.save() return res.json({ diff --git a/controllers/application/tickets/ticketsController.js b/controllers/application/tickets/ticketsController.js index b78f3b0..bad5e13 100644 --- a/controllers/application/tickets/ticketsController.js +++ b/controllers/application/tickets/ticketsController.js @@ -74,7 +74,7 @@ const addUserMessageToTicket = async (req, res) => { const { ticketId } = req.query const { text } = req.body - const { file } = req.files + const file = req.file let fileUrl = null if (file) { // اگر فایل ارسال شده است، آن را ذخیره کنید @@ -83,8 +83,8 @@ const addUserMessageToTicket = async (req, res) => { fs.mkdirSync(uploadDir, { recursive: true }) } - // اسمِ فایل هیچ‌وقت مستقیم از ورودیِ کاربر (file.name) گرفته نمی‌شه — فقط پسوندش - const uniqueFileName = `${Date.now()}-${crypto.randomUUID()}${path.extname(file.name || '')}` + // اسمِ فایل هیچ‌وقت مستقیم از ورودیِ کاربر (file.originalname) گرفته نمی‌شه — فقط پسوندش + const uniqueFileName = `${Date.now()}-${crypto.randomUUID()}${path.extname(file.originalname || '')}` const filePath = path.join(uploadDir, uniqueFileName) await fs.move(file.path, filePath) diff --git a/controllers/application/verify/servicesController.js b/controllers/application/verify/servicesController.js index 6ce2826..3b44e27 100644 --- a/controllers/application/verify/servicesController.js +++ b/controllers/application/verify/servicesController.js @@ -1,8 +1,6 @@ /* eslint-disable camelcase */ const UserModel = require('../../../models/UserModel') const jwt = require('jsonwebtoken') -const fs = require('fs-extra') -const path = require('path') const setServices = async (req, res, next) => { try { @@ -10,13 +8,6 @@ const setServices = async (req, res, next) => { 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(422).json({ - error: true, - message: 'شما دسترسی به این بخش ندارید' - }) - } const { services } = req.body if (!services) { return res.status(422).json({ @@ -25,38 +16,37 @@ const setServices = async (req, res, next) => { }) } - // مدیریت تصاویر برای خدمات - const servicesWithImages = [] + // مدیریت تصاویر برای خدمات — multer (routes/application/verify/index.js، با + // .any() چون اسمِ فیلد به ازای هر خدمت پویاست: serviceImages[id]) فایل‌ها رو + // به‌صورتِ آرایه می‌ده، با fieldname مشخص می‌کنیم مالِ کدوم خدمته const parsedServices = JSON.parse(services) // پارس کردن رشته JSON به آرایه جاوا اسکریپت - if (parsedServices && parsedServices.length > 0) { - for (const service of parsedServices) { - const serviceImages = [] - const serviceId = service.id - if (req.files && req.files.serviceImages && req.files.serviceImages[serviceId]) { - const imageFiles = Array.isArray(req.files.serviceImages[serviceId]) ? req.files.serviceImages[serviceId] : [req.files.serviceImages[serviceId]] - const uploadDir = path.join(__dirname, '../../../../storage/services') - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }) - } - for (const imageFile of imageFiles) { - const uniqueFileName = `${user.user_name}-${Date.now()}${Math.random().toString().slice(2, 11)}${path.extname(imageFile.name)}` - const filePath = path.join(uploadDir, uniqueFileName) - // await fs.promises.rename(imageFile.path, filePath) - await fs.move(imageFile.path, filePath) - const imageUrl = `/services/${uniqueFileName}` - serviceImages.push(imageUrl) - // به‌روزرسانی مسیر تصویر در آبجکت service - service.image = imageUrl - } - } else { - console.log('sss') - } - servicesWithImages.push({ ...service, images: serviceImages }) + const uploadedFiles = Array.isArray(req.files) ? req.files : [] + const servicesWithImages = (parsedServices || []).map((service) => { + const uploaded = uploadedFiles.find( + (f) => f.fieldname === `serviceImages[${service.id}]` + ) + if (uploaded) { + const imageUrl = `/services/${uploaded.filename}` + return { ...service, image: imageUrl, images: [imageUrl] } } - } + // بدون عکسِ تازه — هر مسیرِ عکسِ قبلی که خودِ کلاینت توی همین آیتم فرستاده + // (حالتِ ویرایش، نگاه کن به EditServicesScreen.tsx/web) دست‌نخورده می‌مونه + return { ...service, images: service.image ? [service.image] : [] } + }) - user.services = servicesWithImages - await user.save() + // updateOne اتمیک به‌جای findById+user.save() — یه enum نامعتبرِ قدیمی روی یه + // فیلدِ کاملاً بی‌ربطِ همین کاربر با save() کلِ سند رو re-validate می‌کرد و همین + // آپدیتِ بی‌ربط رو می‌شکست (نگاه کن به همین فیکس توی setProfileImageController.js) + const result = await UserModel.updateOne( + { _id: decodedToken.id }, + { services: servicesWithImages } + ) + if (result.matchedCount === 0) { + return res.status(422).json({ + error: true, + message: 'شما دسترسی به این بخش ندارید' + }) + } return res.json({ message: 'خدمات با موفقیت ثبت شد' diff --git a/controllers/application/verify/setNationalCardImageController.js b/controllers/application/verify/setNationalCardImageController.js index d054ae5..45e894e 100644 --- a/controllers/application/verify/setNationalCardImageController.js +++ b/controllers/application/verify/setNationalCardImageController.js @@ -1,6 +1,4 @@ /* eslint-disable camelcase */ -const fs = require('fs-extra') -const path = require('path') const UserModel = require('../../../models/UserModel') const jwt = require('jsonwebtoken') @@ -8,16 +6,8 @@ const setNationalCardImage = async (req, res, next) => { try { const token = req.header('Authorization').split(' ')[1] if (!token) return res.status(401).send('Access Denied') - const { national_card_image } = req.files - // جستجوی کاربر با شماره موبایل ارسالی + const national_card_image = req.file 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: 'کاربری با این شماره موبایل یافت نشد' - }) - } // بررسی آیا فایل آپلود شده است if (!national_card_image) { return res.status(400).json({ @@ -26,25 +16,27 @@ const setNationalCardImage = async (req, res, next) => { }) } - // ذخیره فایل عکس پروفایل - const uploadDir = path.join(__dirname, '../../../../storage/carts') - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }) + // multer (routes/application/verify/index.js) فایل رو مستقیم با اسمِ یکتا توی + // storage/carts ذخیره کرده — نیازی به move جداگانه نیست + const imagePath = `/carts/${national_card_image.filename}` + + // updateOne اتمیک به‌جای findById+user.save() — یه enum نامعتبرِ قدیمی روی یه + // فیلدِ بی‌ربطِ همین کاربر با save() کلِ سند رو re-validate و همین آپدیت رو + // می‌شکست (نگاه کن به همین فیکس توی setProfileImageController.js) + const result = await UserModel.updateOne( + { _id: decodedToken.id }, + { national_card_image: imagePath, is_Register: true, is_verified: 'pending' } + ) + if (result.matchedCount === 0) { + return res.status(404).json({ + error: true, + message: 'کاربری با این شماره موبایل یافت نشد' + }) } - const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(national_card_image.name)}` - const filePath = path.join(uploadDir, uniqueFileName) - await fs.move(national_card_image.path, filePath) - - // ذخیره مسیر فایل در دیتابیس - user.national_card_image = `/carts/${uniqueFileName}` - user.is_Register = true - user.is_verified = 'pending' - await user.save() - return res.json({ message: 'عکس کارت ملی با موفقیت ذخیره شد', - national_card_image: user.national_card_image + national_card_image: imagePath }) } catch (error) { next(error) diff --git a/controllers/application/verify/setProfileImageController.js b/controllers/application/verify/setProfileImageController.js index 0f55692..e2d7fff 100644 --- a/controllers/application/verify/setProfileImageController.js +++ b/controllers/application/verify/setProfileImageController.js @@ -1,6 +1,4 @@ /* eslint-disable camelcase */ -const fs = require('fs-extra') -const path = require('path') const UserModel = require('../../../models/UserModel') const jwt = require('jsonwebtoken') const { optimizeImageFile } = require('../../../utils/imageOptimize') @@ -13,15 +11,8 @@ const setProfileImage = async (req, res, next) => { try { const token = req.header('Authorization').split(' ')[1] if (!token) return res.status(401).send('Access Denied') - const { profile_image } = req.files + const profile_image = req.file 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: 'کاربری با این شماره موبایل یافت نشد' - }) - } // بررسی آیا فایل آپلود شده است if (!profile_image) { return res.status(400).json({ @@ -30,23 +21,28 @@ const setProfileImage = async (req, res, next) => { }) } - // ذخیره فایل عکس پروفایل - const uploadDir = path.join(__dirname, '../../../storage/profiles') - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }) - } - const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(profile_image.name)}` - const filePath = path.join(uploadDir, uniqueFileName) + // multer (routes/application/verify/index.js) فایل رو مستقیم با اسمِ یکتا توی + // storage/profiles ذخیره کرده — نیازی به move جداگانه نیست + await optimizeImageFile(profile_image.path, { maxDimension: AVATAR_MAX_DIMENSION }) + const imagePath = `/profiles/${profile_image.filename}` - await fs.move(profile_image.path, filePath) - await optimizeImageFile(filePath, { maxDimension: AVATAR_MAX_DIMENSION }) - // ذخیره مسیر فایل در دیتابیس - user.profile_image = `/profiles/${uniqueFileName}` - await user.save() + // updateOne اتمیک به‌جای findById+user.save() — یه enum نامعتبرِ قدیمی روی یه + // فیلدِ کاملاً بی‌ربطِ همین کاربر (مثلاً personal_style، از داده‌های قدیمی) با + // save() کلِ سند رو re-validate می‌کرد و همین آپدیتِ بی‌ربط رو می‌شکست + const result = await UserModel.updateOne( + { _id: decodedToken.id }, + { profile_image: imagePath } + ) + if (result.matchedCount === 0) { + return res.status(404).json({ + error: true, + message: 'کاربری با این شماره موبایل یافت نشد' + }) + } return res.json({ message: 'عکس پروفایل با موفقیت ذخیره شد', - profile_image: user.profile_image + profile_image: imagePath }) } catch (error) { next(error) @@ -59,7 +55,7 @@ const updateProfileImage = async (req, res, next) => { if (!token) return res.status(401).send('Access Denied') const decodedToken = jwt.verify(token, process.env.APP_SECRET) const userId = decodedToken.id - const { profile_image } = req.files + const profile_image = req.file if (!profile_image) { return res.status(422).json({ @@ -68,33 +64,23 @@ const updateProfileImage = async (req, res, next) => { }) } - const user = await UserModel.findById(userId) + // multer (routes/application/verify/index.js) فایل رو مستقیم با اسمِ یکتا توی + // storage/profiles ذخیره کرده — نیازی به move جداگانه نیست + await optimizeImageFile(profile_image.path, { maxDimension: AVATAR_MAX_DIMENSION }) + const imagePath = `/profiles/${profile_image.filename}` - if (!user) { + // updateOne اتمیک به‌جای findById+user.save() — همون دلیلِ setProfileImage بالا + const result = await UserModel.updateOne({ _id: userId }, { profile_image: imagePath }) + if (result.matchedCount === 0) { return res.status(404).json({ error: true, message: 'کاربری با این شماره موبایل یافت نشد' }) } - const uploadDir = path.join(__dirname, '../../../storage/profiles') - - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }) - } - - const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(profile_image.name)}` - const filePath = path.join(uploadDir, uniqueFileName) - - await fs.move(profile_image.path, filePath) - await optimizeImageFile(filePath, { maxDimension: AVATAR_MAX_DIMENSION }) - - user.profile_image = `/profiles/${uniqueFileName}` - await user.save() - return res.json({ message: 'عکس پروفایل با موفقیت به‌روزرسانی شد', - profile_image: user.profile_image + profile_image: imagePath }) } catch (error) { next(error) diff --git a/routes/application/academy/index.js b/routes/application/academy/index.js index aaa96c9..6305f33 100644 --- a/routes/application/academy/index.js +++ b/routes/application/academy/index.js @@ -74,18 +74,19 @@ router.get("/", academyController.findAcademy); // یافتن آکادمی با شناسه router.post("/findAcademyById", academyController.findAcademyById); -// تکمیل پروفایل آکادمی -router.post("/academy/profile", academyController.academyProfile); +// تکمیل پروفایل آکادمی — همون باگِ req.files که توی verify/* پیدا شد (middlewareِ +// global قدیمی که req.files رو پر می‌کرد حذف شده بود، این روت بدونِ جایگزین مونده بود) +router.post("/academy/profile", upload.single("profile_image"), academyController.academyProfile); // ========================================== // 📚 مسیرهای مدیریت دوره‌ها (Courses) // ========================================== -// ایجاد دوره جدید -router.post("/academy/creat/course", [auth, blockCheck, requireSubUserPermission('academy_packages')], academyController.creatCourse); +// ایجاد دوره جدید — همون باگِ req.files بالا +router.post("/academy/creat/course", [auth, blockCheck, requireSubUserPermission('academy_packages')], upload.single("course_image"), academyController.creatCourse); -// ویرایش دوره -router.patch("/academy/update/course", [auth, blockCheck, requireSubUserPermission('academy_packages')], academyController.updateCourse); +// ویرایش دوره — همون باگِ req.files بالا +router.patch("/academy/update/course", [auth, blockCheck, requireSubUserPermission('academy_packages')], upload.single("course_image"), academyController.updateCourse); // حذف دوره (تغییر وضعیت به reject) router.delete("/academy/delete/course", academyController.deleteCourse); diff --git a/routes/application/advertising/index.js b/routes/application/advertising/index.js index 2c8f8c4..55e2461 100644 --- a/routes/application/advertising/index.js +++ b/routes/application/advertising/index.js @@ -1,4 +1,7 @@ const express = require('express') +const multer = require('multer') +const fs = require('fs') +const path = require('path') const blockCheck = require('../../../middlewares/blockCheck') const { createAdvertisingValidationRules, createAdvertising, getAdvertisingTypes, getAdvertisingCategory, getSingleAdvertising, getAdvertisingFeatures, getAdvertisings, toggleLike, addComment, getComments, rateAdvertising, getUserAdvertisings, getAdvertisingAnalytics, editAdvertising, getUserSingleAdvertising, getUserAdvertisingProfile, updateUserAdvertisingProfile, getAdvertisingsWeb, getSingleAdvertisingWeb, getUserAdvertisingProfileWeb, recordAdvertisingView, getUserLikedAdvertisings, getUserAdvertisingComments } = require('../../../controllers/application/advertising/advertisingController') const auth = require('../../../middlewares/auth') @@ -6,16 +9,38 @@ const requireSubUserPermission = require('../../../middlewares/requireSubUserPer const { initiateAdvertisingPayment, handleAdvertisingPaymentCallback, handleAdvertisingRepublishPaymentCallback, republishAdvertisingPayment } = require('../../../controllers/application/payment/paymentController') const { initiateAdvertisingPaymentWeb, handleAdvertisingPaymentCallbackWeb, republishAdvertisingPaymentWeb, handleAdvertisingRepublishPaymentCallbackWeb } = require('../../../controllers/application/payment/paymentControllerWeb') const router = express.Router() + +// قبلاً express-form-data به‌صورتِ global این فیلدها رو تویِ req.files پر می‌کرد؛ +// وقتی اون middleware حذف شد (تداخل با multer توی posts/chat)، این روت‌ها بدونِ هیچ +// parserِ جایگزینی موندن — یعنی آپلودِ عکسِ بیلبورد/خدمات/پروفایلِ ویترین همیشه +// خاموش بودن (createAdvertising حتی بدون خطا، فقط عکس‌ها رو نادیده می‌گرفت) +const advertisingTmpDir = '/tmp/modstagram-uploads' +if (!fs.existsSync(advertisingTmpDir)) { + fs.mkdirSync(advertisingTmpDir, { recursive: true }) +} +const advertisingTmpUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, advertisingTmpDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 50 * 1024 * 1024 } +}) +// اسمِ فیلدِ عکسِ هر خدمت پویاست (serviceImages[id])، پس .any() به‌جای .fields() +// با لیستِ ثابت — کنترلر با file.fieldname تشخیص می‌ده مالِ کدوم خدمت/بخشه +const advertisingImagesUpload = advertisingTmpUpload.any() +const advertisingProfileImageUpload = advertisingTmpUpload.single('profile_image') router.get('/', [auth], getAdvertisings) router.get('/web', getAdvertisingsWeb) router.get('/profile', [auth], getUserAdvertisingProfile) router.get('/profile-web', getUserAdvertisingProfileWeb) -router.patch('/profile', [auth], [blockCheck], updateUserAdvertisingProfile) +router.patch('/profile', [auth], [blockCheck], advertisingProfileImageUpload, updateUserAdvertisingProfile) router.get('/user-advertising', [auth], getUserAdvertisings) router.get('/analytics', [auth], getAdvertisingAnalytics) router.get('/user-liked', [auth], getUserLikedAdvertisings) router.get('/user-comments', [auth], getUserAdvertisingComments) -router.post('/create', [auth], [blockCheck], requireSubUserPermission('billboards'), createAdvertisingValidationRules(), createAdvertising) +router.post('/create', [auth], [blockCheck], requireSubUserPermission('billboards'), advertisingImagesUpload, createAdvertisingValidationRules(), createAdvertising) router.get('/types', [auth], getAdvertisingTypes) router.get('/categories', getAdvertisingCategory) router.get('/features', [auth], getAdvertisingFeatures) @@ -35,6 +60,6 @@ router.post('/like', [blockCheck], [auth], toggleLike) router.post('/comment', [blockCheck], [auth], addComment) router.get('/comments', getComments) router.post('/rate', [blockCheck], [auth], rateAdvertising) -router.post('/edit/:id', [blockCheck], [auth], requireSubUserPermission('billboards'), editAdvertising) +router.post('/edit/:id', [blockCheck], [auth], requireSubUserPermission('billboards'), advertisingImagesUpload, editAdvertising) module.exports = router diff --git a/routes/application/shop-products/index.js b/routes/application/shop-products/index.js index 0ba4074..dc8baa6 100644 --- a/routes/application/shop-products/index.js +++ b/routes/application/shop-products/index.js @@ -1,6 +1,26 @@ const express = require('express') +const multer = require('multer') +const fs = require('fs') +const path = require('path') const blockCheck = require('../../../middlewares/blockCheck') const requireSubUserPermission = require('../../../middlewares/requireSubUserPermission') + +// قبلاً express-form-data به‌صورتِ global این فیلد رو تویِ req.files پر می‌کرد؛ وقتی +// اون middleware حذف شد (تداخل با multer توی posts/chat)، این روت‌ها بدونِ هیچ parserِ +// جایگزینی موندن — یعنی آپلودِ عکسِ محصول همیشه خاموش بود +const shopProductsTmpDir = '/tmp/modstagram-uploads' +if (!fs.existsSync(shopProductsTmpDir)) { + fs.mkdirSync(shopProductsTmpDir, { recursive: true }) +} +const shopProductImagesUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, shopProductsTmpDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 50 * 1024 * 1024 } +}).array('images', 10) const { searchCatalog, createShopProduct, @@ -25,8 +45,8 @@ router.get('/:id', getShopProductById) router.get('/:id/comments', getShopProductComments) // Seller-only -router.post('/', [blockCheck], requireSubUserPermission('shop_products'), createShopProduct) -router.patch('/:id', [blockCheck], requireSubUserPermission('shop_products'), updateShopProduct) +router.post('/', [blockCheck], requireSubUserPermission('shop_products'), shopProductImagesUpload, createShopProduct) +router.patch('/:id', [blockCheck], requireSubUserPermission('shop_products'), shopProductImagesUpload, updateShopProduct) // Authenticated buyer actions router.post('/:id/comments', [blockCheck], createShopProductComment) diff --git a/routes/application/sub-users/index.js b/routes/application/sub-users/index.js index 8ca5cbc..d67af11 100644 --- a/routes/application/sub-users/index.js +++ b/routes/application/sub-users/index.js @@ -1,8 +1,28 @@ const express = require('express') +const multer = require('multer') +const fs = require('fs') +const path = require('path') const router = express.Router() const auth = require('../../../middlewares/auth') const blockCheck = require('../../../middlewares/blockCheck') + +// قبلاً express-form-data به‌صورتِ global این فیلد رو تویِ req.files پر می‌کرد؛ وقتی +// اون middleware حذف شد (تداخل با multer توی posts/chat)، این روت‌ها بدونِ هیچ parserِ +// جایگزینی موندن — یعنی آپلودِ آواتارِ کاربرِ زیرمجموعه همیشه خاموش بود +const subUserTmpDir = '/tmp/modstagram-uploads' +if (!fs.existsSync(subUserTmpDir)) { + fs.mkdirSync(subUserTmpDir, { recursive: true }) +} +const subUserAvatarUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, subUserTmpDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 20 * 1024 * 1024 } +}).single('profile_image') const { createSubUser, listSubUsers, @@ -23,10 +43,10 @@ function ownerOnly (req, res, next) { next() } -router.post('/', [auth, blockCheck, ownerOnly], createSubUser) +router.post('/', [auth, blockCheck, ownerOnly], subUserAvatarUpload, createSubUser) router.get('/', [auth, blockCheck, ownerOnly], listSubUsers) router.get('/:id', [auth, blockCheck, ownerOnly], getSubUser) -router.patch('/:id', [auth, blockCheck, ownerOnly], updateSubUser) +router.patch('/:id', [auth, blockCheck, ownerOnly], subUserAvatarUpload, updateSubUser) router.patch('/:id/active', [auth, blockCheck, ownerOnly], setSubUserActive) module.exports = router diff --git a/routes/application/tickets/index.js b/routes/application/tickets/index.js index 564f8d3..75277ef 100644 --- a/routes/application/tickets/index.js +++ b/routes/application/tickets/index.js @@ -1,9 +1,30 @@ const express = require('express') +const multer = require('multer') +const fs = require('fs') +const path = require('path') const { getUserTickets, createTicket, addUserMessageToTicket, getTicketMessages } = require('../../../controllers/application/tickets/ticketsController') const router = express.Router() + +// قبلاً express-form-data به‌صورتِ global این فیلد رو تویِ req.files پر می‌کرد؛ وقتی +// اون middleware حذف شد (تداخل با multer توی posts/chat)، این روت بدونِ هیچ parserِ +// جایگزینی مونده بود — یعنی پیوستِ فایلِ پیامِ تیکت همیشه خاموش بود +const ticketTmpDir = '/tmp/modstagram-uploads' +if (!fs.existsSync(ticketTmpDir)) { + fs.mkdirSync(ticketTmpDir, { recursive: true }) +} +const ticketFileUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, ticketTmpDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 50 * 1024 * 1024 } +}).single('file') + router.get('/', getUserTickets) router.get('/messages', getTicketMessages) router.post('/create', createTicket) -router.post('/message', addUserMessageToTicket) +router.post('/message', ticketFileUpload, addUserMessageToTicket) module.exports = router diff --git a/routes/application/verify/index.js b/routes/application/verify/index.js index 06ae33b..2820843 100644 --- a/routes/application/verify/index.js +++ b/routes/application/verify/index.js @@ -1,4 +1,8 @@ const express = require('express') +const multer = require('multer') +const crypto = require('crypto') +const fs = require('fs') +const path = require('path') const { setProfileImage, updateProfileImage } = require('../../../controllers/application/verify/setProfileImageController') const { setExpertise, updateExpertise } = require('../../../controllers/application/verify/expertiseController') const { setGender, updateGender } = require('../../../controllers/application/verify/genderController') @@ -16,16 +20,54 @@ const { setServices, updateServices } = require('../../../controllers/applicatio const { setPersonalDetails, updatePersonalDetails } = require('../../../controllers/application/verify/personalDetailsController') const router = express.Router() -router.post('/profile_image', setProfileImage) -router.patch('/profile_image', [blockCheck], [auth], updateProfileImage) +// قبلاً express-form-data به‌صورتِ global این فیلدها رو تویِ req.files پر می‌کرد؛ +// وقتی اون middleware حذف شد (تداخل با multer توی posts/chat)، این روت بدونِ هیچ +// parserِ جایگزینی موند — یعنی req.files همیشه undefined بود و هر آپلودِ عکسِ +// پروفایل با «خطایی رخ داده است» شکست می‌خورد (همون الگویی که قبلاً برای +// /api/v1/chat/file پیدا و با multer.diskStorage رفع شد — نگاه کن به index.js) +const profileImageDir = path.join(__dirname, '../../../storage/profiles') +if (!fs.existsSync(profileImageDir)) { + fs.mkdirSync(profileImageDir, { recursive: true }) +} +const profileImageUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, profileImageDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${crypto.randomUUID()}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 20 * 1024 * 1024 } +}).single('profile_image') + +router.post('/profile_image', profileImageUpload, setProfileImage) +router.patch('/profile_image', [blockCheck], [auth], profileImageUpload, updateProfileImage) router.post('/expertise', setExpertise) router.patch('/expertise', [blockCheck], [auth], updateExpertise) router.post('/gender', setGender) router.patch('/gender', [blockCheck], [auth], updateGender) router.post('/sizes', setSizes) router.patch('/sizes', [blockCheck], [auth], updateSizes) -router.post('/services', setServices) -router.patch('/services', [blockCheck], [auth], updateServices) +// همون باگِ req.files (نگاه کن به کامنتِ profileImageUpload بالا) اینجا هم بود؛ +// فرق با بقیه اینه که اسمِ فیلدِ عکسِ هر خدمت پویاست (serviceImages[serviceId]، +// یه آیدی متفاوت برای هر خدمت) پس به‌جای .single()/.fields() با اسمِ ثابت، از +// .any() استفاده می‌شه — همه‌ی فایل‌ها با هر اسمی قبول می‌شه، بعد توی کنترلر با +// file.fieldname تطبیق داده می‌شه +const servicesImagesDir = path.join(__dirname, '../../../storage/services') +if (!fs.existsSync(servicesImagesDir)) { + fs.mkdirSync(servicesImagesDir, { recursive: true }) +} +const servicesUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, servicesImagesDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${crypto.randomUUID()}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 20 * 1024 * 1024 } +}).any() + +router.post('/services', servicesUpload, setServices) +router.patch('/services', [blockCheck], [auth], servicesUpload, updateServices) router.post('/colors', setColors) router.patch('/colors', [blockCheck], [auth], updateColors) router.post('/personal_details', [blockCheck], [auth], setPersonalDetails) @@ -38,7 +80,22 @@ router.post('/auth', saveAuthValidationRules(), saveAuth) router.patch('/shaba', [blockCheck], [auth], updateShaba) router.post('/address', setAddress) router.patch('/address', [blockCheck], [auth], updateAddress) -router.post('/national_card_image', setNationalCardImage) +// همون باگِ req.files (نگاه کن به کامنتِ profileImageUpload بالا) اینجا هم بود +const nationalCardDir = path.join(__dirname, '../../../storage/carts') +if (!fs.existsSync(nationalCardDir)) { + fs.mkdirSync(nationalCardDir, { recursive: true }) +} +const nationalCardUpload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => cb(null, nationalCardDir), + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${crypto.randomUUID()}${path.extname(file.originalname || '')}`) + } + }), + limits: { fileSize: 20 * 1024 * 1024 } +}).single('national_card_image') + +router.post('/national_card_image', nationalCardUpload, setNationalCardImage) router.post('/complete', completeRegistration) module.exports = router