This commit is contained in:
payacom
2026-10-02 18:13:34 +03:30
parent d4ebc20aa8
commit fade8543e2
9 changed files with 180 additions and 63 deletions

View File

@@ -12,6 +12,7 @@ try {
console.warn('[index] postVideoProcessing unavailable:', error.message);
}
const http = require('http').createServer(app);
const multer = require('multer');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const moment = require('moment-jalaali');
@@ -417,7 +418,31 @@ app.post('/api/v1/notification/send-sms', [blockCheck], async (req, res) => {
})
})
app.post('/api/v1/chat/file', [blockCheck, chatPeerBlock], async (req, res) => {
// قبلاً express-form-data به‌صورتِ global بدنه‌ی هر درخواستِ multipart رو می‌خوند و
// req.files رو پر می‌کرد؛ وقتی اون middleware (به‌خاطرِ تداخل با multer توی روت‌های
// دیگه مثل posts) حذف شد، این روت که مستقیماً به همون req.files تکیه داشت بدونِ هیچ
// parserِ جایگزینی موند — یعنی req.files همیشه undefined بود و هر ارسالِ عکس/ویدیو/
// فایل/ویس توی چت (وب و اندروید، چون هر دو همین یه اندپوینتِ مشترک رو صدا می‌زنن) با
// «Server error» شکست می‌خورد. همون الگوی multer.diskStorage که posts استفاده می‌کنه،
// اینجا هم (مستقیماً روی پوشه‌ی نهایی، بدونِ نیاز به fs.move جداگانه) وصل شد
const chatFileUploadDir = path.join(__dirname, '../storage/messages')
if (!fs.existsSync(chatFileUploadDir)) {
fs.mkdirSync(chatFileUploadDir, { recursive: true })
}
const chatFileUpload = multer({
storage: multer.diskStorage({
destination: (req, file, cb) => cb(null, chatFileUploadDir),
filename: (req, file, cb) => {
// اسمِ فایل هیچ‌وقت مستقیم از ورودیِ کاربر (file.originalname) گرفته نمی‌شه —
// فقط پسوندش؛ وگرنه یه originalname مثل ../../../../etc/passwd می‌تونست از
// uploadDir بیرون بزنه (path traversal / نوشتنِ فایلِ دلخواه روی سرور)
cb(null, `${Date.now()}-${crypto.randomUUID()}${path.extname(file.originalname || '')}`)
}
}),
limits: { fileSize: 200 * 1024 * 1024 }
}).single('file')
app.post('/api/v1/chat/file', [blockCheck, chatFileUpload, chatPeerBlock], async (req, res) => {
try {
const senderId = String(req.user._id)
const { receiverId, content, replyToId, fileType, selfDestructSeconds, viewOnce } = req.body
@@ -448,27 +473,17 @@ app.post('/api/v1/chat/file', [blockCheck, chatPeerBlock], async (req, res) => {
if (viewOnceFlag && !isViewOnceMediaType(resolvedFileType)) {
return res.status(422).json({ error: 'View once is only for image, video, and voice' })
}
const { file } = req.files
const file = req.file
let fileUrl = null
if (file) {
const uploadDir = path.join(__dirname, '../storage/messages')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true })
}
// اسمِ فایل هیچ‌وقت مستقیم از ورودیِ کاربر (file.name) گرفته نمی‌شه — فقط
// پسوندش؛ وگرنه یه filename مثل ../../../../etc/passwd می‌تونست از uploadDir
// بیرون بزنه (path traversal / نوشتنِ فایلِ دلخواه روی سرور)
const uniqueFileName = `${Date.now()}-${crypto.randomUUID()}${path.extname(file.name || '')}`
const filePath = path.join(uploadDir, uniqueFileName)
await fs.move(file.path, filePath)
if (resolvedFileType === 'image' || (file.type || '').startsWith('image/')) {
await optimizeImageFile(filePath)
} else if (resolvedFileType === 'video' || (file.type || '').startsWith('video/')) {
optimizeVideoFaststart(filePath).catch((err) => {
if (resolvedFileType === 'image' || (file.mimetype || '').startsWith('image/')) {
await optimizeImageFile(file.path)
} else if (resolvedFileType === 'video' || (file.mimetype || '').startsWith('video/')) {
optimizeVideoFaststart(file.path).catch((err) => {
console.error('[chat/file] video optimize background error:', err.message)
})
}
fileUrl = `/messages/${uniqueFileName}`
fileUrl = `/messages/${file.filename}`
}
const newMessage = new MessageModel({