Refactor file handling in BuyProduct and Request models; implement file schema for better structure. Update routes to handle file uploads and downloads with improved error handling and logging. Adjust MongoDB connection management across scripts and routes for consistency.
This commit is contained in:
@@ -2,9 +2,10 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const BuyProduct = require('../models/BuyProduct');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const multer = require('multer');
|
||||
const UPLOADS_ROOT = 'server/remote-assets/uploads/buy-products';
|
||||
const UPLOADS_ROOT = 'server/routers/remote-assets/uploads/buy-products';
|
||||
const ensureDirectory = (dirPath) => {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
@@ -23,18 +24,6 @@ const ALLOWED_MIME_TYPES = new Set([
|
||||
'text/csv',
|
||||
]);
|
||||
|
||||
const getExtension = (filename) => {
|
||||
const lastDot = filename.lastIndexOf('.');
|
||||
return lastDot > 0 ? filename.slice(lastDot) : '';
|
||||
};
|
||||
|
||||
const getBasename = (filename) => {
|
||||
const lastDot = filename.lastIndexOf('.');
|
||||
const name = lastDot > 0 ? filename.slice(0, lastDot) : filename;
|
||||
const lastSlash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
|
||||
return lastSlash >= 0 ? name.slice(lastSlash + 1) : name;
|
||||
};
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const productId = req.params.id || 'common';
|
||||
@@ -43,10 +32,12 @@ const storage = multer.diskStorage({
|
||||
cb(null, productDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const originalExtension = getExtension(file.originalname);
|
||||
const baseName = getBasename(file.originalname)
|
||||
.replace(/[^a-zA-Z0-9-_]+/g, '_')
|
||||
.toLowerCase();
|
||||
// Исправляем кодировку имени файла из Latin1 в UTF-8
|
||||
const fixedName = Buffer.from(file.originalname, 'latin1').toString('utf8');
|
||||
const originalExtension = path.extname(fixedName) || '';
|
||||
const baseName = path
|
||||
.basename(fixedName, originalExtension)
|
||||
.replace(/[<>:"/\\|?*\x00-\x1F]+/g, '_'); // Убираем только недопустимые символы Windows, оставляем кириллицу
|
||||
cb(null, `${Date.now()}_${baseName}${originalExtension}`);
|
||||
},
|
||||
});
|
||||
@@ -241,7 +232,16 @@ router.post('/:id/files', verifyToken, handleSingleFileUpload, async (req, res)
|
||||
}
|
||||
|
||||
// Только владелец товара может добавить файл
|
||||
if (product.companyId.toString() !== req.companyId.toString()) {
|
||||
const productCompanyId = product.companyId?.toString() || product.companyId;
|
||||
const requestCompanyId = req.companyId?.toString() || req.companyId;
|
||||
|
||||
console.log('[BuyProducts] Comparing company IDs:', {
|
||||
productCompanyId,
|
||||
requestCompanyId,
|
||||
match: productCompanyId === requestCompanyId
|
||||
});
|
||||
|
||||
if (productCompanyId !== requestCompanyId) {
|
||||
return res.status(403).json({ error: 'Not authorized' });
|
||||
}
|
||||
|
||||
@@ -253,28 +253,75 @@ router.post('/:id/files', verifyToken, handleSingleFileUpload, async (req, res)
|
||||
return res.status(400).json({ error: 'File is required' });
|
||||
}
|
||||
|
||||
const relativePath = `buy-products/${id}/${req.file.filename}`;
|
||||
// Исправляем кодировку имени файла из Latin1 в UTF-8
|
||||
const fixedFileName = Buffer.from(req.file.originalname, 'latin1').toString('utf8');
|
||||
|
||||
// Извлекаем timestamp из имени файла, созданного multer (формат: {timestamp}_{name}.ext)
|
||||
const fileTimestamp = req.file.filename.split('_')[0];
|
||||
|
||||
// storagePath относительно UPLOADS_ROOT (который уже включает 'buy-products')
|
||||
const relativePath = `${id}/${req.file.filename}`;
|
||||
const file = {
|
||||
id: `file-${Date.now()}`,
|
||||
name: req.file.originalname,
|
||||
url: `/uploads/${relativePath}`,
|
||||
id: `file-${fileTimestamp}`, // Используем тот же timestamp, что и в имени файла
|
||||
name: fixedFileName,
|
||||
url: `/uploads/buy-products/${relativePath}`,
|
||||
type: req.file.mimetype,
|
||||
size: req.file.size,
|
||||
uploadedAt: new Date(),
|
||||
storagePath: relativePath,
|
||||
};
|
||||
|
||||
product.files.push(file);
|
||||
await product.save();
|
||||
console.log('[BuyProducts] Adding file to product:', {
|
||||
productId: id,
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
filePath: relativePath
|
||||
});
|
||||
|
||||
console.log('[BuyProducts] File object:', JSON.stringify(file, null, 2));
|
||||
|
||||
// Используем findByIdAndUpdate вместо save() для избежания проблем с валидацией
|
||||
let updatedProduct;
|
||||
try {
|
||||
console.log('[BuyProducts] Calling findByIdAndUpdate with id:', id);
|
||||
updatedProduct = await BuyProduct.findByIdAndUpdate(
|
||||
id,
|
||||
{
|
||||
$push: { files: file },
|
||||
$set: { updatedAt: new Date() }
|
||||
},
|
||||
{ new: true, runValidators: false }
|
||||
);
|
||||
console.log('[BuyProducts] findByIdAndUpdate completed');
|
||||
} catch (updateError) {
|
||||
console.error('[BuyProducts] findByIdAndUpdate error:', {
|
||||
message: updateError.message,
|
||||
name: updateError.name,
|
||||
code: updateError.code
|
||||
});
|
||||
throw updateError;
|
||||
}
|
||||
|
||||
if (!updatedProduct) {
|
||||
throw new Error('Failed to update product with file');
|
||||
}
|
||||
|
||||
console.log('[BuyProducts] File added successfully to product:', id);
|
||||
|
||||
log('[BuyProducts] File added to product:', id, file.name);
|
||||
|
||||
res.json(product);
|
||||
res.json(updatedProduct);
|
||||
} catch (error) {
|
||||
console.error('[BuyProducts] Error adding file:', error.message);
|
||||
console.error('[BuyProducts] Error stack:', error.stack);
|
||||
console.error('[BuyProducts] Error name:', error.name);
|
||||
if (error.errors) {
|
||||
console.error('[BuyProducts] Validation errors:', JSON.stringify(error.errors, null, 2));
|
||||
}
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: error.message,
|
||||
details: error.errors || {},
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -303,7 +350,7 @@ router.delete('/:id/files/:fileId', verifyToken, async (req, res) => {
|
||||
await product.save();
|
||||
|
||||
const storedPath = fileToRemove.storagePath || fileToRemove.url.replace(/^\/uploads\//, '');
|
||||
const absolutePath = `server/remote-assets/uploads/${storedPath}`;
|
||||
const absolutePath = `server/routers/remote-assets/uploads/${storedPath}`;
|
||||
|
||||
fs.promises.unlink(absolutePath).catch((unlinkError) => {
|
||||
if (unlinkError && unlinkError.code !== 'ENOENT') {
|
||||
@@ -391,4 +438,65 @@ router.get('/:id/acceptances', verifyToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /buy-products/download/:id/:fileId - скачать файл
|
||||
router.get('/download/:id/:fileId', verifyToken, async (req, res) => {
|
||||
try {
|
||||
console.log('[BuyProducts] Download request received:', {
|
||||
productId: req.params.id,
|
||||
fileId: req.params.fileId,
|
||||
userId: req.userId,
|
||||
companyId: req.companyId,
|
||||
headers: req.headers.authorization
|
||||
});
|
||||
|
||||
const { id, fileId } = req.params;
|
||||
const product = await BuyProduct.findById(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({ error: 'Product not found' });
|
||||
}
|
||||
|
||||
const file = product.files.find((f) => f.id === fileId);
|
||||
if (!file) {
|
||||
return res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
|
||||
// Создаем абсолютный путь к файлу
|
||||
const filePath = path.resolve(UPLOADS_ROOT, file.storagePath);
|
||||
|
||||
console.log('[BuyProducts] Trying to download file:', {
|
||||
fileId: file.id,
|
||||
fileName: file.name,
|
||||
storagePath: file.storagePath,
|
||||
absolutePath: filePath,
|
||||
exists: fs.existsSync(filePath)
|
||||
});
|
||||
|
||||
// Проверяем существование файла
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error('[BuyProducts] File not found on disk:', filePath);
|
||||
return res.status(404).json({ error: 'File not found on disk' });
|
||||
}
|
||||
|
||||
// Устанавливаем правильные заголовки для скачивания с поддержкой кириллицы
|
||||
const encodedFileName = encodeURIComponent(file.name);
|
||||
res.setHeader('Content-Type', file.type || 'application/octet-stream');
|
||||
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFileName}`);
|
||||
res.setHeader('Content-Length', file.size);
|
||||
|
||||
// Отправляем файл
|
||||
res.sendFile(filePath, (err) => {
|
||||
if (err) {
|
||||
console.error('[BuyProducts] Error sending file:', err.message);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: 'Error downloading file' });
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[BuyProducts] Error downloading file:', error.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user