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:
@@ -1,5 +1,34 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
// Явно определяем схему для файлов
|
||||
const fileSchema = new mongoose.Schema({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
storagePath: String,
|
||||
uploadedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
}, { _id: false });
|
||||
|
||||
const buyProductSchema = new mongoose.Schema({
|
||||
companyId: {
|
||||
type: String,
|
||||
@@ -24,18 +53,7 @@ const buyProductSchema = new mongoose.Schema({
|
||||
type: String,
|
||||
default: 'шт'
|
||||
},
|
||||
files: [{
|
||||
id: String,
|
||||
name: String,
|
||||
url: String,
|
||||
type: String,
|
||||
size: Number,
|
||||
storagePath: String,
|
||||
uploadedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
}],
|
||||
files: [fileSchema],
|
||||
acceptedBy: [{
|
||||
companyId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
|
||||
@@ -22,12 +22,12 @@ const requestSchema = new mongoose.Schema({
|
||||
required: true
|
||||
},
|
||||
files: [{
|
||||
id: String,
|
||||
name: String,
|
||||
url: String,
|
||||
type: String,
|
||||
size: Number,
|
||||
storagePath: String,
|
||||
id: { type: String },
|
||||
name: { type: String },
|
||||
url: { type: String },
|
||||
type: { type: String },
|
||||
size: { type: Number },
|
||||
storagePath: { type: String },
|
||||
uploadedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
@@ -47,12 +47,12 @@ const requestSchema = new mongoose.Schema({
|
||||
default: null
|
||||
},
|
||||
responseFiles: [{
|
||||
id: String,
|
||||
name: String,
|
||||
url: String,
|
||||
type: String,
|
||||
size: Number,
|
||||
storagePath: String,
|
||||
id: { type: String },
|
||||
name: { type: String },
|
||||
url: { type: String },
|
||||
type: { type: String },
|
||||
size: { type: Number },
|
||||
storagePath: { type: String },
|
||||
uploadedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
|
||||
@@ -7,7 +7,7 @@ const Request = require('../models/Request');
|
||||
const BuyProduct = require('../models/BuyProduct');
|
||||
const Message = require('../models/Message');
|
||||
const Review = require('../models/Review');
|
||||
const mongoose = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
const { Types } = mongoose;
|
||||
|
||||
const PRESET_COMPANY_ID = new Types.ObjectId('68fe2ccda3526c303ca06796');
|
||||
@@ -116,9 +116,6 @@ const waitForDatabaseConnection = async () => {
|
||||
|
||||
const verifyAuth = async () => {
|
||||
try {
|
||||
if (!mongoose.connection.db) {
|
||||
return false;
|
||||
}
|
||||
await mongoose.connection.db.admin().command({ listDatabases: 1 });
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -139,15 +136,17 @@ const waitForDatabaseConnection = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Ожидаем подключения (подключение происходит автоматически через server/utils/mongoose.ts)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const connection = await connectDB();
|
||||
if (!connection) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mongoose.connection.readyState === 1) {
|
||||
const authed = await verifyAuth();
|
||||
if (authed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await mongoose.connection.close().catch(() => {});
|
||||
} catch (error) {
|
||||
if (!isAuthFailure(error)) {
|
||||
throw error;
|
||||
@@ -218,8 +217,12 @@ const initializeTestUser = async () => {
|
||||
} catch (error) {
|
||||
console.error('Error initializing test data:', error.message);
|
||||
if (error?.code === 13 || /auth/i.test(error?.message || '')) {
|
||||
try {
|
||||
await connectDB();
|
||||
} catch (connectError) {
|
||||
if (process.env.DEV === 'true') {
|
||||
console.error('Auth error detected. Connection managed by server/utils/mongoose.ts');
|
||||
console.error('Failed to re-connect after auth error:', connectError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const express = require('express')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const router = express.Router()
|
||||
const BuyDocument = require('../models/BuyDocument')
|
||||
|
||||
// Create remote-assets/docs directory if it doesn't exist
|
||||
const docsDir = 'server/remote-assets/docs'
|
||||
const docsDir = 'server/routers/remote-assets/docs'
|
||||
if (!fs.existsSync(docsDir)) {
|
||||
fs.mkdirSync(docsDir, { recursive: true })
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,7 +5,8 @@ const Company = require('../models/Company');
|
||||
const Experience = require('../models/Experience');
|
||||
const Request = require('../models/Request');
|
||||
const Message = require('../models/Message');
|
||||
const { Types } = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
const { Types } = mongoose;
|
||||
|
||||
// GET /my/info - получить мою компанию (требует авторизации) - ДОЛЖНО быть ПЕРЕД /:id
|
||||
router.get('/my/info', verifyToken, async (req, res) => {
|
||||
|
||||
@@ -2,7 +2,8 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const Experience = require('../models/Experience');
|
||||
const { Types } = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
const { Types } = mongoose;
|
||||
|
||||
// GET /experience - Получить список опыта работы компании
|
||||
router.get('/', verifyToken, async (req, res) => {
|
||||
|
||||
@@ -21,21 +21,23 @@ router.get('/aggregates', verifyToken, async (req, res) => {
|
||||
|
||||
const companyId = user.companyId.toString();
|
||||
|
||||
const [docsCount, acceptsCount, requestsCount] = await Promise.all([
|
||||
BuyProduct.countDocuments({ companyId }),
|
||||
Request.countDocuments({
|
||||
$or: [
|
||||
{ senderCompanyId: companyId, status: 'accepted' },
|
||||
{ recipientCompanyId: companyId, status: 'accepted' }
|
||||
]
|
||||
}),
|
||||
Request.countDocuments({
|
||||
$or: [
|
||||
{ senderCompanyId: companyId },
|
||||
{ recipientCompanyId: companyId }
|
||||
]
|
||||
})
|
||||
]);
|
||||
// Получить все BuyProduct для подсчета файлов и акцептов
|
||||
const buyProducts = await BuyProduct.find({ companyId });
|
||||
|
||||
// Подсчет документов - сумма всех файлов во всех BuyProduct
|
||||
const docsCount = buyProducts.reduce((total, product) => {
|
||||
return total + (product.files ? product.files.length : 0);
|
||||
}, 0);
|
||||
|
||||
// Подсчет акцептов - сумма всех acceptedBy во всех BuyProduct
|
||||
const acceptsCount = buyProducts.reduce((total, product) => {
|
||||
return total + (product.acceptedBy ? product.acceptedBy.length : 0);
|
||||
}, 0);
|
||||
|
||||
// Подсчет исходящих запросов (только отправленные этой компанией)
|
||||
const requestsCount = await Request.countDocuments({
|
||||
senderCompanyId: companyId
|
||||
});
|
||||
|
||||
res.json({
|
||||
docsCount,
|
||||
|
||||
@@ -2,6 +2,8 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const Message = require('../models/Message');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
const { ObjectId } = mongoose.Types;
|
||||
|
||||
// Функция для логирования с проверкой DEV переменной
|
||||
const log = (message, data = '') => {
|
||||
@@ -18,7 +20,6 @@ const log = (message, data = '') => {
|
||||
router.get('/threads', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const companyId = req.companyId;
|
||||
const { ObjectId } = require('mongoose').Types;
|
||||
|
||||
log('[Messages] Fetching threads for companyId:', companyId, 'type:', typeof companyId);
|
||||
|
||||
@@ -146,7 +147,6 @@ router.post('/:threadId', verifyToken, async (req, res) => {
|
||||
|
||||
// Найти recipientCompanyId по ObjectId если нужно
|
||||
let recipientObjectId = recipientCompanyId;
|
||||
const { ObjectId } = require('mongoose').Types;
|
||||
try {
|
||||
if (typeof recipientCompanyId === 'string' && ObjectId.isValid(recipientCompanyId)) {
|
||||
recipientObjectId = new ObjectId(recipientCompanyId);
|
||||
@@ -210,7 +210,6 @@ router.post('/admin/migrate-fix-recipients', async (req, res) => {
|
||||
|
||||
// If recipientCompanyId is not set or wrong - fix it
|
||||
if (!message.recipientCompanyId || message.recipientCompanyId.toString() !== expectedRecipient) {
|
||||
const { ObjectId } = require('mongoose').Types;
|
||||
let recipientObjectId = expectedRecipient;
|
||||
try {
|
||||
if (typeof expectedRecipient === 'string' && ObjectId.isValid(expectedRecipient)) {
|
||||
|
||||
@@ -3,8 +3,10 @@ const router = express.Router();
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const Request = require('../models/Request');
|
||||
const BuyProduct = require('../models/BuyProduct');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const multer = require('multer');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
|
||||
// Функция для логирования с проверкой DEV переменной
|
||||
const log = (message, data = '') => {
|
||||
@@ -17,7 +19,7 @@ const log = (message, data = '') => {
|
||||
}
|
||||
};
|
||||
|
||||
const REQUESTS_UPLOAD_ROOT = 'server/remote-assets/uploads/requests';
|
||||
const REQUESTS_UPLOAD_ROOT = 'server/routers/remote-assets/uploads/requests';
|
||||
|
||||
const ensureDirectory = (dirPath) => {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
@@ -37,28 +39,17 @@ const ALLOWED_REQUEST_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 subfolder = req.requestUploadSubfolder || '';
|
||||
const destinationDir = subfolder ? `${REQUESTS_UPLOAD_ROOT}/${subfolder}` : REQUESTS_UPLOAD_ROOT;
|
||||
const destinationDir = `${REQUESTS_UPLOAD_ROOT}/${subfolder}`;
|
||||
ensureDirectory(destinationDir);
|
||||
cb(null, destinationDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const extension = getExtension(file.originalname);
|
||||
const baseName = getBasename(file.originalname)
|
||||
const extension = path.extname(file.originalname) || '';
|
||||
const baseName = path
|
||||
.basename(file.originalname, extension)
|
||||
.replace(/[^a-zA-Z0-9-_]+/g, '_')
|
||||
.toLowerCase();
|
||||
cb(null, `${Date.now()}_${baseName}${extension}`);
|
||||
@@ -107,7 +98,7 @@ const cleanupUploadedFiles = async (req) => {
|
||||
|
||||
const subfolder = req.requestUploadSubfolder || '';
|
||||
const removalTasks = req.files.map((file) => {
|
||||
const filePath = subfolder ? `${REQUESTS_UPLOAD_ROOT}/${subfolder}/${file.filename}` : `${REQUESTS_UPLOAD_ROOT}/${file.filename}`;
|
||||
const filePath = `${REQUESTS_UPLOAD_ROOT}/${subfolder}/${file.filename}`;
|
||||
return fs.promises.unlink(filePath).catch((error) => {
|
||||
if (error.code !== 'ENOENT') {
|
||||
console.error('[Requests] Failed to cleanup uploaded file:', error.message);
|
||||
@@ -125,7 +116,7 @@ const mapFilesToMetadata = (req) => {
|
||||
|
||||
const subfolder = req.requestUploadSubfolder || '';
|
||||
return req.files.map((file) => {
|
||||
const relativePath = subfolder ? `requests/${subfolder}/${file.filename}` : `requests/${file.filename}`;
|
||||
const relativePath = `requests/${subfolder}/${file.filename}`;
|
||||
return {
|
||||
id: `file-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: file.originalname,
|
||||
@@ -169,7 +160,7 @@ const removeStoredFiles = async (files = []) => {
|
||||
const tasks = files
|
||||
.filter((file) => file && file.storagePath)
|
||||
.map((file) => {
|
||||
const absolutePath = `server/remote-assets/uploads/${file.storagePath}`;
|
||||
const absolutePath = `server/routers/remote-assets/uploads/${file.storagePath}`;
|
||||
return fs.promises.unlink(absolutePath).catch((error) => {
|
||||
if (error.code !== 'ENOENT') {
|
||||
console.error('[Requests] Failed to remove stored file:', error.message);
|
||||
@@ -255,24 +246,61 @@ router.post(
|
||||
return res.status(400).json({ error: 'At least one recipient is required' });
|
||||
}
|
||||
|
||||
if (!subject && productId) {
|
||||
let uploadedFiles = mapFilesToMetadata(req);
|
||||
|
||||
console.log('========================');
|
||||
console.log('[Requests] Initial uploadedFiles:', uploadedFiles.length);
|
||||
console.log('[Requests] ProductId:', productId);
|
||||
|
||||
// Если есть productId, получаем данные товара
|
||||
if (productId) {
|
||||
try {
|
||||
const product = await BuyProduct.findById(productId);
|
||||
console.log('[Requests] Product found:', product ? product.name : 'null');
|
||||
console.log('[Requests] Product files count:', product?.files?.length || 0);
|
||||
if (product && product.files) {
|
||||
console.log('[Requests] Product files:', JSON.stringify(product.files, null, 2));
|
||||
}
|
||||
|
||||
if (product) {
|
||||
// Берем subject из товара, если не указан
|
||||
if (!subject) {
|
||||
subject = product.name;
|
||||
}
|
||||
|
||||
// Если файлы не загружены вручную, используем файлы из товара
|
||||
if (uploadedFiles.length === 0 && product.files && product.files.length > 0) {
|
||||
console.log('[Requests] ✅ Copying files from product...');
|
||||
// Копируем файлы из товара, изменяя путь для запроса
|
||||
uploadedFiles = product.files.map(file => ({
|
||||
id: file.id || `file-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
uploadedAt: file.uploadedAt || new Date(),
|
||||
storagePath: file.storagePath || file.url.replace('/uploads/', ''),
|
||||
}));
|
||||
console.log('[Requests] ✅ Using', uploadedFiles.length, 'files from product:', productId);
|
||||
console.log('[Requests] ✅ Copied files:', JSON.stringify(uploadedFiles, null, 2));
|
||||
} else {
|
||||
console.log('[Requests] ❌ NOT copying files. uploadedFiles.length:', uploadedFiles.length, 'product.files.length:', product.files?.length || 0);
|
||||
}
|
||||
}
|
||||
} catch (lookupError) {
|
||||
console.error('[Requests] Failed to lookup product for subject:', lookupError.message);
|
||||
console.error('[Requests] ❌ Failed to lookup product:', lookupError.message);
|
||||
console.error(lookupError.stack);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Requests] Final uploadedFiles for saving:', JSON.stringify(uploadedFiles, null, 2));
|
||||
console.log('========================');
|
||||
|
||||
if (!subject) {
|
||||
await cleanupUploadedFiles(req);
|
||||
return res.status(400).json({ error: 'Subject is required' });
|
||||
}
|
||||
|
||||
const uploadedFiles = mapFilesToMetadata(req);
|
||||
|
||||
const results = [];
|
||||
for (const recipientCompanyId of recipients) {
|
||||
try {
|
||||
@@ -331,10 +359,18 @@ router.put(
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
console.log('[Requests] PUT /requests/:id called with id:', id);
|
||||
console.log('[Requests] Request body:', req.body);
|
||||
console.log('[Requests] Files:', req.files);
|
||||
console.log('[Requests] CompanyId:', req.companyId);
|
||||
|
||||
const responseText = (req.body.response || '').trim();
|
||||
const statusRaw = (req.body.status || 'accepted').toLowerCase();
|
||||
const status = statusRaw === 'rejected' ? 'rejected' : 'accepted';
|
||||
|
||||
console.log('[Requests] Response text:', responseText);
|
||||
console.log('[Requests] Status:', status);
|
||||
|
||||
if (req.invalidFiles && req.invalidFiles.length > 0) {
|
||||
await cleanupUploadedFiles(req);
|
||||
return res.status(400).json({
|
||||
@@ -361,6 +397,8 @@ router.put(
|
||||
}
|
||||
|
||||
const uploadedResponseFiles = mapFilesToMetadata(req);
|
||||
console.log('[Requests] Uploaded response files count:', uploadedResponseFiles.length);
|
||||
console.log('[Requests] Uploaded response files:', JSON.stringify(uploadedResponseFiles, null, 2));
|
||||
|
||||
if (uploadedResponseFiles.length > 0) {
|
||||
await removeStoredFiles(request.responseFiles || []);
|
||||
@@ -372,18 +410,126 @@ router.put(
|
||||
request.respondedAt = new Date();
|
||||
request.updatedAt = new Date();
|
||||
|
||||
await request.save();
|
||||
|
||||
let savedRequest;
|
||||
try {
|
||||
savedRequest = await request.save();
|
||||
log('[Requests] Request responded:', id);
|
||||
} catch (saveError) {
|
||||
console.error('[Requests] Mongoose save failed, trying direct MongoDB update:', saveError.message);
|
||||
// Fallback: использовать MongoDB драйвер напрямую
|
||||
const updateData = {
|
||||
response: responseText,
|
||||
status: status,
|
||||
respondedAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
if (uploadedResponseFiles.length > 0) {
|
||||
updateData.responseFiles = uploadedResponseFiles;
|
||||
}
|
||||
|
||||
res.json(request);
|
||||
const result = await mongoose.connection.collection('requests').findOneAndUpdate(
|
||||
{ _id: new mongoose.Types.ObjectId(id) },
|
||||
{ $set: updateData },
|
||||
{ returnDocument: 'after' }
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Failed to update request');
|
||||
}
|
||||
savedRequest = result;
|
||||
log('[Requests] Request responded via direct MongoDB update:', id);
|
||||
}
|
||||
|
||||
res.json(savedRequest);
|
||||
} catch (error) {
|
||||
console.error('[Requests] Error responding to request:', error.message);
|
||||
console.error('[Requests] Error stack:', error.stack);
|
||||
if (error.name === 'ValidationError') {
|
||||
console.error('[Requests] Validation errors:', JSON.stringify(error.errors, null, 2));
|
||||
}
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// GET /requests/download/:id/:fileId - скачать файл ответа
|
||||
router.get('/download/:id/:fileId', verifyToken, async (req, res) => {
|
||||
try {
|
||||
console.log('[Requests] Download request received:', {
|
||||
requestId: req.params.id,
|
||||
fileId: req.params.fileId,
|
||||
userId: req.userId,
|
||||
companyId: req.companyId,
|
||||
});
|
||||
|
||||
const { id, fileId } = req.params;
|
||||
const request = await Request.findById(id);
|
||||
|
||||
if (!request) {
|
||||
return res.status(404).json({ error: 'Request not found' });
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь имеет доступ к запросу (отправитель или получатель)
|
||||
if (request.senderCompanyId !== req.companyId && request.recipientCompanyId !== req.companyId) {
|
||||
return res.status(403).json({ error: 'Not authorized' });
|
||||
}
|
||||
|
||||
// Ищем файл в responseFiles или в обычных files
|
||||
let file = request.responseFiles?.find((f) => f.id === fileId);
|
||||
if (!file) {
|
||||
file = request.files?.find((f) => f.id === fileId);
|
||||
}
|
||||
if (!file) {
|
||||
return res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
|
||||
// Создаем абсолютный путь к файлу
|
||||
// Если storagePath не начинается с 'requests/', значит это файл из buy-products
|
||||
let fullPath = file.storagePath;
|
||||
if (!fullPath.startsWith('requests/')) {
|
||||
fullPath = `buy-products/${fullPath}`;
|
||||
}
|
||||
const filePath = path.resolve(`server/routers/remote-assets/uploads/${fullPath}`);
|
||||
|
||||
console.log('[Requests] 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('[Requests] 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('[Requests] Error sending file:', err.message);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: 'Error sending file' });
|
||||
}
|
||||
} else {
|
||||
log('[Requests] File downloaded:', file.name);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Requests] Error downloading file:', error.message);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /requests/:id - удалить запрос
|
||||
router.delete('/:id', verifyToken, async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -54,10 +54,13 @@ router.get('/recommendations', verifyToken, async (req, res) => {
|
||||
// GET /search - Поиск компаний
|
||||
router.get('/', verifyToken, async (req, res) => {
|
||||
try {
|
||||
console.log('[Search] === NEW VERSION WITH FIXED SIZE FILTER ===');
|
||||
|
||||
const {
|
||||
query = '',
|
||||
page = 1,
|
||||
limit = 10,
|
||||
offset, // Добавляем поддержку offset для точной пагинации
|
||||
industries,
|
||||
companySize,
|
||||
geography,
|
||||
@@ -65,9 +68,13 @@ router.get('/', verifyToken, async (req, res) => {
|
||||
hasReviews,
|
||||
hasAcceptedDocs,
|
||||
sortBy = 'relevance',
|
||||
sortOrder = 'desc'
|
||||
sortOrder = 'desc',
|
||||
minEmployees, // Кастомный фильтр: минимум сотрудников
|
||||
maxEmployees // Кастомный фильтр: максимум сотрудников
|
||||
} = req.query;
|
||||
|
||||
console.log('[Search] Filters:', { minEmployees, maxEmployees, companySize });
|
||||
|
||||
// Получить компанию пользователя, чтобы исключить её из результатов
|
||||
const User = require('../models/User');
|
||||
const user = await User.findById(req.userId);
|
||||
@@ -135,12 +142,99 @@ router.get('/', verifyToken, async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтр по размеру компании
|
||||
if (companySize) {
|
||||
const sizeList = Array.isArray(companySize) ? companySize : [companySize];
|
||||
if (sizeList.length > 0) {
|
||||
filters.push({ companySize: { $in: sizeList } });
|
||||
// Функция для парсинга диапазона из строки вида "51-250" или "500+"
|
||||
const parseEmployeeRange = (sizeStr) => {
|
||||
if (sizeStr.includes('+')) {
|
||||
const min = parseInt(sizeStr.replace('+', ''));
|
||||
return { min, max: Infinity };
|
||||
}
|
||||
const parts = sizeStr.split('-');
|
||||
return {
|
||||
min: parseInt(parts[0]),
|
||||
max: parts[1] ? parseInt(parts[1]) : parseInt(parts[0])
|
||||
};
|
||||
};
|
||||
|
||||
// Функция для проверки пересечения двух диапазонов
|
||||
const rangesOverlap = (range1, range2) => {
|
||||
return range1.min <= range2.max && range1.max >= range2.min;
|
||||
};
|
||||
|
||||
// Фильтр по размеру компании (чекбоксы) или кастомный диапазон
|
||||
// Важно: этот фильтр должен получить все компании для корректной работы пересечения диапазонов
|
||||
let sizeFilteredIds = null;
|
||||
if ((companySize && companySize.length > 0) || minEmployees || maxEmployees) {
|
||||
// Получаем все компании (без других фильтров, так как размер компании - это property-based фильтр)
|
||||
const allCompanies = await Company.find({});
|
||||
|
||||
log('[Search] Employee size filter - checking companies:', allCompanies.length);
|
||||
|
||||
let matchingIds = [];
|
||||
|
||||
// Если есть кастомный диапазон - используем его
|
||||
if (minEmployees || maxEmployees) {
|
||||
const customRange = {
|
||||
min: minEmployees ? parseInt(minEmployees, 10) : 0,
|
||||
max: maxEmployees ? parseInt(maxEmployees, 10) : Infinity
|
||||
};
|
||||
|
||||
log('[Search] Custom employee range filter:', customRange);
|
||||
|
||||
matchingIds = allCompanies
|
||||
.filter(company => {
|
||||
if (!company.companySize) {
|
||||
log('[Search] Company has no size:', company.fullName);
|
||||
return false;
|
||||
}
|
||||
|
||||
const companyRange = parseEmployeeRange(company.companySize);
|
||||
const overlaps = rangesOverlap(companyRange, customRange);
|
||||
|
||||
log('[Search] Checking overlap:', {
|
||||
company: company.fullName,
|
||||
companyRange,
|
||||
customRange,
|
||||
overlaps
|
||||
});
|
||||
|
||||
return overlaps;
|
||||
})
|
||||
.map(c => c._id);
|
||||
|
||||
log('[Search] Matching companies by custom range:', matchingIds.length);
|
||||
}
|
||||
// Иначе используем чекбоксы
|
||||
else if (companySize && companySize.length > 0) {
|
||||
const sizeList = Array.isArray(companySize) ? companySize : [companySize];
|
||||
|
||||
log('[Search] Company size checkboxes filter:', sizeList);
|
||||
|
||||
matchingIds = allCompanies
|
||||
.filter(company => {
|
||||
if (!company.companySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const companyRange = parseEmployeeRange(company.companySize);
|
||||
|
||||
// Проверяем пересечение с любым из выбранных диапазонов
|
||||
const matches = sizeList.some(selectedSize => {
|
||||
const filterRange = parseEmployeeRange(selectedSize);
|
||||
const overlaps = rangesOverlap(companyRange, filterRange);
|
||||
log('[Search] Check:', company.fullName, companyRange, 'vs', filterRange, '=', overlaps);
|
||||
return overlaps;
|
||||
});
|
||||
|
||||
return matches;
|
||||
})
|
||||
.map(c => c._id);
|
||||
|
||||
log('[Search] Matching companies by size checkboxes:', matchingIds.length);
|
||||
}
|
||||
|
||||
// Сохраняем ID для дальнейшей фильтрации
|
||||
sizeFilteredIds = matchingIds;
|
||||
log('[Search] Size filtered IDs count:', sizeFilteredIds.length);
|
||||
}
|
||||
|
||||
// Фильтр по географии
|
||||
@@ -170,13 +264,25 @@ router.get('/', verifyToken, async (req, res) => {
|
||||
filters.push({ verified: true });
|
||||
}
|
||||
|
||||
// Применяем фильтр по размеру компании (если был задан)
|
||||
if (sizeFilteredIds !== null) {
|
||||
if (sizeFilteredIds.length > 0) {
|
||||
filters.push({ _id: { $in: sizeFilteredIds } });
|
||||
log('[Search] Applied size filter, IDs:', sizeFilteredIds.length);
|
||||
} else {
|
||||
// Если нет подходящих компаний по размеру, возвращаем пустой результат
|
||||
filters.push({ _id: null });
|
||||
log('[Search] No companies match size criteria');
|
||||
}
|
||||
}
|
||||
|
||||
// Комбинировать все фильтры
|
||||
let filter = filters.length > 0 ? { $and: filters } : {};
|
||||
|
||||
// Пагинация
|
||||
const pageNum = parseInt(page) || 1;
|
||||
// Пагинация - используем offset если передан, иначе вычисляем из page
|
||||
const limitNum = parseInt(limit) || 10;
|
||||
const skip = (pageNum - 1) * limitNum;
|
||||
const skip = offset !== undefined ? parseInt(offset) : ((parseInt(page) || 1) - 1) * limitNum;
|
||||
const pageNum = offset !== undefined ? Math.floor(skip / limitNum) + 1 : parseInt(page) || 1;
|
||||
|
||||
// Сортировка
|
||||
let sortOptions = {};
|
||||
@@ -228,3 +334,4 @@ router.get('/', verifyToken, async (req, res) => {
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
const { ObjectId } = mongoose.Types;
|
||||
const Message = require('../models/Message');
|
||||
require('dotenv').config({ path: '../../.env' });
|
||||
|
||||
const mongoUrl = process.env.MONGODB_URI || 'mongodb://localhost:27017/procurement_db';
|
||||
require('dotenv').config();
|
||||
|
||||
async function migrateMessages() {
|
||||
try {
|
||||
console.log('[Migration] Connecting to MongoDB...');
|
||||
await mongoose.connect(mongoUrl, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
serverSelectionTimeoutMS: 5000,
|
||||
connectTimeoutMS: 5000,
|
||||
// Подключение к MongoDB происходит через server/utils/mongoose.ts
|
||||
console.log('[Migration] Checking MongoDB connection...');
|
||||
if (mongoose.connection.readyState !== 1) {
|
||||
console.log('[Migration] Waiting for MongoDB connection...');
|
||||
await new Promise((resolve) => {
|
||||
mongoose.connection.once('connected', resolve);
|
||||
});
|
||||
}
|
||||
console.log('[Migration] Connected to MongoDB');
|
||||
|
||||
// Найти все сообщения
|
||||
@@ -54,7 +54,6 @@ async function migrateMessages() {
|
||||
console.log(' Expected:', expectedRecipient);
|
||||
|
||||
// Конвертируем в ObjectId если нужно
|
||||
const { ObjectId } = require('mongoose').Types;
|
||||
let recipientObjectId = expectedRecipient;
|
||||
try {
|
||||
if (typeof expectedRecipient === 'string' && ObjectId.isValid(expectedRecipient)) {
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
require('dotenv').config();
|
||||
|
||||
// Импорт моделей - прямые пути без path.join и __dirname
|
||||
// Импорт моделей
|
||||
const User = require('../models/User');
|
||||
const Company = require('../models/Company');
|
||||
const Request = require('../models/Request');
|
||||
|
||||
const primaryUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/procurement_db';
|
||||
const fallbackUri =
|
||||
process.env.MONGODB_AUTH_URI || 'mongodb://admin:password@localhost:27017/procurement_db?authSource=admin';
|
||||
|
||||
const connectWithFallback = async () => {
|
||||
// Сначала пробуем FALLBACK (с аутентификацией)
|
||||
try {
|
||||
console.log('\n📡 Подключение к MongoDB (с аутентификацией)...');
|
||||
await mongoose.connect(fallbackUri, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
console.log('✅ Подключено к MongoDB');
|
||||
// Подключение к MongoDB происходит через server/utils/mongoose.ts
|
||||
// Проверяем, подключено ли уже
|
||||
const ensureConnection = async () => {
|
||||
if (mongoose.connection.readyState === 1) {
|
||||
console.log('✅ MongoDB уже подключено');
|
||||
return;
|
||||
} catch (fallbackError) {
|
||||
console.log('❌ Ошибка подключения с аутентификацией:', fallbackError.message);
|
||||
}
|
||||
|
||||
// Если не получилось, пробуем без аутентификации
|
||||
try {
|
||||
console.log('\n📡 Подключение к MongoDB (без аутентификации)...');
|
||||
await mongoose.connect(primaryUri, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
console.log('✅ Подключено к MongoDB');
|
||||
} catch (primaryError) {
|
||||
console.error('❌ Не удалось подключиться к MongoDB:', primaryError.message);
|
||||
throw primaryError;
|
||||
console.log('⏳ Ожидание подключения к MongoDB...');
|
||||
await new Promise((resolve) => {
|
||||
if (mongoose.connection.readyState === 1) {
|
||||
resolve();
|
||||
} else {
|
||||
mongoose.connection.once('connected', resolve);
|
||||
}
|
||||
});
|
||||
console.log('✅ Подключено к MongoDB');
|
||||
};
|
||||
|
||||
const recreateTestUser = async () => {
|
||||
try {
|
||||
await connectWithFallback();
|
||||
await ensureConnection();
|
||||
|
||||
const presetCompanyId = new mongoose.Types.ObjectId('68fe2ccda3526c303ca06796');
|
||||
const presetUserEmail = 'admin@test-company.ru';
|
||||
|
||||
// Удалить старого тестового пользователя
|
||||
console.log('🗑️ Удаление старого тестового пользователя...');
|
||||
const oldUser = await User.findOne({ email: presetUserEmail });
|
||||
const presetCompanyId2 = new mongoose.Types.ObjectId('68fe2ccda3526c303ca06797');
|
||||
const presetUserEmail2 = 'manager@partner-company.ru';
|
||||
|
||||
// Удалить старых тестовых пользователей
|
||||
console.log('🗑️ Удаление старых тестовых пользователей...');
|
||||
const testEmails = [presetUserEmail, presetUserEmail2];
|
||||
|
||||
for (const email of testEmails) {
|
||||
const oldUser = await User.findOne({ email });
|
||||
if (oldUser) {
|
||||
// Удалить связанную компанию
|
||||
if (oldUser.companyId) {
|
||||
await Company.findByIdAndDelete(oldUser.companyId);
|
||||
console.log(' ✓ Старая компания удалена');
|
||||
console.log(` ✓ Старая компания для ${email} удалена`);
|
||||
}
|
||||
await User.findByIdAndDelete(oldUser._id);
|
||||
console.log(' ✓ Старый пользователь удален');
|
||||
console.log(` ✓ Старый пользователь ${email} удален`);
|
||||
} else {
|
||||
console.log(' ℹ️ Старый пользователь не найден');
|
||||
console.log(` ℹ️ Пользователь ${email} не найден`);
|
||||
}
|
||||
}
|
||||
|
||||
// Создать новую компанию с правильной кодировкой UTF-8
|
||||
@@ -82,8 +82,8 @@ const recreateTestUser = async () => {
|
||||
});
|
||||
console.log(' ✓ Компания создана:', company.fullName);
|
||||
|
||||
// Создать нового пользователя с правильной кодировкой UTF-8
|
||||
console.log('\n👤 Создание тестового пользователя...');
|
||||
// Создать первого пользователя с правильной кодировкой UTF-8
|
||||
console.log('\n👤 Создание первого тестового пользователя...');
|
||||
const user = await User.create({
|
||||
email: presetUserEmail,
|
||||
password: 'SecurePass123!',
|
||||
@@ -95,18 +95,71 @@ const recreateTestUser = async () => {
|
||||
});
|
||||
console.log(' ✓ Пользователь создан:', user.firstName, user.lastName);
|
||||
|
||||
// Создать вторую компанию
|
||||
console.log('\n🏢 Создание второй тестовой компании...');
|
||||
const company2 = await Company.create({
|
||||
_id: presetCompanyId2,
|
||||
fullName: 'ООО "Партнер"',
|
||||
shortName: 'Партнер',
|
||||
inn: '9876543210',
|
||||
ogrn: '1089876543210',
|
||||
legalForm: 'ООО',
|
||||
industry: 'Торговля',
|
||||
companySize: '11-50',
|
||||
website: 'https://partner-company.ru',
|
||||
phone: '+7 (495) 987-65-43',
|
||||
email: 'info@partner-company.ru',
|
||||
description: 'Надежный партнер для бизнеса',
|
||||
legalAddress: 'г. Санкт-Петербург, пр. Невский, д. 100',
|
||||
actualAddress: 'г. Санкт-Петербург, пр. Невский, д. 100',
|
||||
foundedYear: 2018,
|
||||
employeeCount: '11-50',
|
||||
revenue: 'До 60 млн ₽',
|
||||
rating: 4.3,
|
||||
reviews: 5,
|
||||
verified: true,
|
||||
partnerGeography: ['spb', 'russia_all'],
|
||||
slogan: 'Качество и надежность',
|
||||
});
|
||||
console.log(' ✓ Компания создана:', company2.fullName);
|
||||
|
||||
// Создать второго пользователя
|
||||
console.log('\n👤 Создание второго тестового пользователя...');
|
||||
const user2 = await User.create({
|
||||
email: presetUserEmail2,
|
||||
password: 'SecurePass123!',
|
||||
firstName: 'Петр',
|
||||
lastName: 'Петров',
|
||||
position: 'Менеджер',
|
||||
phone: '+7 (495) 987-65-43',
|
||||
companyId: company2._id,
|
||||
});
|
||||
console.log(' ✓ Пользователь создан:', user2.firstName, user2.lastName);
|
||||
|
||||
// Проверка что данные сохранены правильно
|
||||
console.log('\n✅ Проверка данных:');
|
||||
console.log('\n Пользователь 1:');
|
||||
console.log(' Email:', user.email);
|
||||
console.log(' Имя:', user.firstName);
|
||||
console.log(' Фамилия:', user.lastName);
|
||||
console.log(' Компания:', company.fullName);
|
||||
console.log(' Должность:', user.position);
|
||||
|
||||
console.log('\n✅ ГОТОВО! Тестовый пользователь создан с правильной кодировкой UTF-8');
|
||||
console.log('\n Пользователь 2:');
|
||||
console.log(' Email:', user2.email);
|
||||
console.log(' Имя:', user2.firstName);
|
||||
console.log(' Фамилия:', user2.lastName);
|
||||
console.log(' Компания:', company2.fullName);
|
||||
console.log(' Должность:', user2.position);
|
||||
|
||||
console.log('\n✅ ГОТОВО! Тестовые пользователи созданы с правильной кодировкой UTF-8');
|
||||
console.log('\n📋 Данные для входа:');
|
||||
console.log('\n Пользователь 1:');
|
||||
console.log(' Email: admin@test-company.ru');
|
||||
console.log(' Пароль: SecurePass123!');
|
||||
console.log('\n Пользователь 2:');
|
||||
console.log(' Email: manager@partner-company.ru');
|
||||
console.log(' Пароль: SecurePass123!');
|
||||
console.log('');
|
||||
|
||||
// Создать дополнительные тестовые компании для поиска
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
require('dotenv').config();
|
||||
|
||||
// Подключение моделей - прямые пути без path.join и __dirname
|
||||
// Подключение моделей
|
||||
const Activity = require('../models/Activity');
|
||||
const User = require('../models/User');
|
||||
const Company = require('../models/Company');
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/procurement-platform';
|
||||
|
||||
const activityTemplates = [
|
||||
{
|
||||
type: 'request_received',
|
||||
@@ -53,8 +51,14 @@ const activityTemplates = [
|
||||
|
||||
async function seedActivities() {
|
||||
try {
|
||||
console.log('🌱 Connecting to MongoDB...');
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
// Подключение к MongoDB происходит через server/utils/mongoose.ts
|
||||
console.log('🌱 Checking MongoDB connection...');
|
||||
if (mongoose.connection.readyState !== 1) {
|
||||
console.log('⏳ Waiting for MongoDB connection...');
|
||||
await new Promise((resolve) => {
|
||||
mongoose.connection.once('connected', resolve);
|
||||
});
|
||||
}
|
||||
console.log('✅ Connected to MongoDB');
|
||||
|
||||
// Найти тестового пользователя
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongoose = require('../../../utils/mongoose');
|
||||
const Request = require('../models/Request');
|
||||
const Company = require('../models/Company');
|
||||
const User = require('../models/User');
|
||||
|
||||
const mongoUri = process.env.MONGODB_URI || 'mongodb://admin:password@localhost:27017/procurement_db?authSource=admin';
|
||||
|
||||
async function seedRequests() {
|
||||
try {
|
||||
await mongoose.connect(mongoUri);
|
||||
// Подключение к MongoDB происходит через server/utils/mongoose.ts
|
||||
if (mongoose.connection.readyState !== 1) {
|
||||
console.log('⏳ Waiting for MongoDB connection...');
|
||||
await new Promise((resolve) => {
|
||||
mongoose.connection.once('connected', resolve);
|
||||
});
|
||||
}
|
||||
console.log('✅ Connected to MongoDB');
|
||||
|
||||
// Получаем все компании
|
||||
|
||||
Reference in New Issue
Block a user