add new back
This commit is contained in:
@@ -7,22 +7,24 @@ const connectDB = async () => {
|
|||||||
console.log('\n📡 Попытка подключения к MongoDB...');
|
console.log('\n📡 Попытка подключения к MongoDB...');
|
||||||
console.log(` URI: ${mongoUri}`);
|
console.log(` URI: ${mongoUri}`);
|
||||||
|
|
||||||
await mongoose.connect(mongoUri, {
|
const connection = await mongoose.connect(mongoUri, {
|
||||||
|
useNewUrlParser: true,
|
||||||
|
useUnifiedTopology: true,
|
||||||
serverSelectionTimeoutMS: 5000,
|
serverSelectionTimeoutMS: 5000,
|
||||||
connectTimeoutMS: 5000,
|
connectTimeoutMS: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ MongoDB подключена успешно!');
|
console.log('✅ MongoDB подключена успешно!');
|
||||||
console.log(` Хост: ${mongoose.connection.host}`);
|
console.log(` Хост: ${connection.connection.host}`);
|
||||||
console.log(` БД: ${mongoose.connection.name}\n`);
|
console.log(` БД: ${connection.connection.name}\n`);
|
||||||
|
|
||||||
return true;
|
return connection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('\n❌ Ошибка подключения к MongoDB:');
|
console.error('\n❌ Ошибка подключения к MongoDB:');
|
||||||
console.error(` ${error.message}\n`);
|
console.error(` ${error.message}\n`);
|
||||||
console.warn('⚠️ Приложение продолжит работу с mock данными\n');
|
console.warn('⚠️ Приложение продолжит работу с mock данными\n');
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ const connectDB = require('./config/db');
|
|||||||
// Загрузить переменные окружения
|
// Загрузить переменные окружения
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
// Включить логирование при разработке: установите DEV=true в .env или при запуске
|
||||||
|
// export DEV=true && npm start (для Linux/Mac)
|
||||||
|
// set DEV=true && npm start (для Windows)
|
||||||
|
// По умолчанию логи отключены. Все console.log функции отключаются если DEV !== 'true'
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
console.log('ℹ️ DEBUG MODE ENABLED - All logs are visible');
|
||||||
|
}
|
||||||
|
|
||||||
// Импортировать маршруты
|
// Импортировать маршруты
|
||||||
const authRoutes = require('./routes/auth');
|
const authRoutes = require('./routes/auth');
|
||||||
const companiesRoutes = require('./routes/companies');
|
const companiesRoutes = require('./routes/companies');
|
||||||
@@ -16,6 +24,7 @@ const experienceRoutes = require('./routes/experience');
|
|||||||
const productsRoutes = require('./routes/products');
|
const productsRoutes = require('./routes/products');
|
||||||
const reviewsRoutes = require('./routes/reviews');
|
const reviewsRoutes = require('./routes/reviews');
|
||||||
const buyProductsRoutes = require('./routes/buyProducts');
|
const buyProductsRoutes = require('./routes/buyProducts');
|
||||||
|
const requestsRoutes = require('./routes/requests');
|
||||||
const homeRoutes = require('./routes/home');
|
const homeRoutes = require('./routes/home');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -73,6 +82,7 @@ app.use('/buy-products', buyProductsRoutes);
|
|||||||
app.use('/experience', experienceRoutes);
|
app.use('/experience', experienceRoutes);
|
||||||
app.use('/products', productsRoutes);
|
app.use('/products', productsRoutes);
|
||||||
app.use('/reviews', reviewsRoutes);
|
app.use('/reviews', reviewsRoutes);
|
||||||
|
app.use('/requests', requestsRoutes);
|
||||||
app.use('/home', homeRoutes);
|
app.use('/home', homeRoutes);
|
||||||
|
|
||||||
// Обработка ошибок
|
// Обработка ошибок
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const verifyToken = (req, res, next) => {
|
const verifyToken = (req, res, next) => {
|
||||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||||
|
|
||||||
@@ -12,7 +22,7 @@ const verifyToken = (req, res, next) => {
|
|||||||
req.userId = decoded.userId;
|
req.userId = decoded.userId;
|
||||||
req.companyId = decoded.companyId;
|
req.companyId = decoded.companyId;
|
||||||
req.user = decoded;
|
req.user = decoded;
|
||||||
console.log('[Auth] Token verified - userId:', decoded.userId, 'companyId:', decoded.companyId);
|
log('[Auth] Token verified - userId:', decoded.userId, 'companyId:', decoded.companyId);
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Auth] Token verification failed:', error.message);
|
console.error('[Auth] Token verification failed:', error.message);
|
||||||
@@ -20,10 +30,10 @@ const verifyToken = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateToken = (userId, companyId) => {
|
const generateToken = (userId, companyId, firstName = '', lastName = '', companyName = '') => {
|
||||||
console.log('[Auth] Generating token for userId:', userId, 'companyId:', companyId);
|
log('[Auth] Generating token for userId:', userId, 'companyId:', companyId);
|
||||||
return jwt.sign(
|
return jwt.sign(
|
||||||
{ userId, companyId },
|
{ userId, companyId, firstName, lastName, companyName },
|
||||||
process.env.JWT_SECRET || 'your-secret-key',
|
process.env.JWT_SECRET || 'your-secret-key',
|
||||||
{ expiresIn: '7d' }
|
{ expiresIn: '7d' }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ const buyProductSchema = new mongoose.Schema({
|
|||||||
default: Date.now
|
default: Date.now
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
acceptedBy: [{
|
||||||
|
companyId: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: 'Company'
|
||||||
|
},
|
||||||
|
acceptedAt: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now
|
||||||
|
}
|
||||||
|
}],
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ['draft', 'published'],
|
enum: ['draft', 'published'],
|
||||||
|
|||||||
62
server/routers/procurement/models/Request.js
Normal file
62
server/routers/procurement/models/Request.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const requestSchema = new mongoose.Schema({
|
||||||
|
senderCompanyId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
index: true
|
||||||
|
},
|
||||||
|
recipientCompanyId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
index: true
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
files: [{
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
type: String,
|
||||||
|
size: Number,
|
||||||
|
uploadedAt: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
productId: {
|
||||||
|
type: String,
|
||||||
|
ref: 'BuyProduct'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ['pending', 'accepted', 'rejected'],
|
||||||
|
default: 'pending'
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
respondedAt: {
|
||||||
|
type: Date,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now,
|
||||||
|
index: true
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Индексы для оптимизации поиска
|
||||||
|
requestSchema.index({ senderCompanyId: 1, createdAt: -1 });
|
||||||
|
requestSchema.index({ recipientCompanyId: 1, createdAt: -1 });
|
||||||
|
requestSchema.index({ senderCompanyId: 1, recipientCompanyId: 1 });
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Request', requestSchema);
|
||||||
@@ -4,6 +4,17 @@ const { generateToken } = require('../middleware/auth');
|
|||||||
const User = require('../models/User');
|
const User = require('../models/User');
|
||||||
const Company = require('../models/Company');
|
const Company = require('../models/Company');
|
||||||
|
|
||||||
|
// Функция для логирования с проверкой DEV переменной
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// In-memory storage для логирования
|
// In-memory storage для логирования
|
||||||
let users = [];
|
let users = [];
|
||||||
|
|
||||||
@@ -21,6 +32,7 @@ const initializeTestUser = async () => {
|
|||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Производство',
|
industry: 'Производство',
|
||||||
companySize: '50-100',
|
companySize: '50-100',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://test-company.ru',
|
website: 'https://test-company.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
@@ -38,7 +50,7 @@ const initializeTestUser = async () => {
|
|||||||
companyId: company._id
|
companyId: company._id
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Test user initialized');
|
log('✅ Test user initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализация других тестовых компаний
|
// Инициализация других тестовых компаний
|
||||||
@@ -50,7 +62,8 @@ const initializeTestUser = async () => {
|
|||||||
ogrn: '1027700132196',
|
ogrn: '1027700132196',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Строительство',
|
industry: 'Строительство',
|
||||||
companySize: '100-250',
|
companySize: '51-250',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://stroykomplekt.ru',
|
website: 'https://stroykomplekt.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.8,
|
rating: 4.8,
|
||||||
@@ -65,6 +78,7 @@ const initializeTestUser = async () => {
|
|||||||
legalForm: 'АО',
|
legalForm: 'АО',
|
||||||
industry: 'Строительство',
|
industry: 'Строительство',
|
||||||
companySize: '500+',
|
companySize: '500+',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://moscow-stroy.ru',
|
website: 'https://moscow-stroy.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.9,
|
rating: 4.9,
|
||||||
@@ -78,7 +92,8 @@ const initializeTestUser = async () => {
|
|||||||
ogrn: '1027700132198',
|
ogrn: '1027700132198',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'IT',
|
industry: 'IT',
|
||||||
companySize: '50-100',
|
companySize: '11-50',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://techproject.ru',
|
website: 'https://techproject.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.6,
|
rating: 4.6,
|
||||||
@@ -91,8 +106,9 @@ const initializeTestUser = async () => {
|
|||||||
inn: '7707083897',
|
inn: '7707083897',
|
||||||
ogrn: '1027700132199',
|
ogrn: '1027700132199',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Торговля',
|
industry: 'Оптовая торговля',
|
||||||
companySize: '100-250',
|
companySize: '51-250',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://torgpartner.ru',
|
website: 'https://torgpartner.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.3,
|
rating: 4.3,
|
||||||
@@ -106,7 +122,8 @@ const initializeTestUser = async () => {
|
|||||||
ogrn: '1027700132200',
|
ogrn: '1027700132200',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Энергетика',
|
industry: 'Энергетика',
|
||||||
companySize: '250-500',
|
companySize: '251-500',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://energoplus.ru',
|
website: 'https://energoplus.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.7,
|
rating: 4.7,
|
||||||
@@ -119,7 +136,7 @@ const initializeTestUser = async () => {
|
|||||||
const existingCompany = await Company.findOne({ inn: mockCompanyData.inn });
|
const existingCompany = await Company.findOne({ inn: mockCompanyData.inn });
|
||||||
if (!existingCompany) {
|
if (!existingCompany) {
|
||||||
await Company.create(mockCompanyData);
|
await Company.create(mockCompanyData);
|
||||||
console.log(`✅ Mock company created: ${mockCompanyData.fullName}`);
|
log(`✅ Mock company created: ${mockCompanyData.fullName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -160,11 +177,12 @@ router.post('/register', async (req, res) => {
|
|||||||
verified: false,
|
verified: false,
|
||||||
rating: 0,
|
rating: 0,
|
||||||
description: '',
|
description: '',
|
||||||
slogan: ''
|
slogan: '',
|
||||||
|
partnerGeography: ['moscow', 'russia_all']
|
||||||
});
|
});
|
||||||
const savedCompany = await company.save();
|
const savedCompany = await company.save();
|
||||||
company = savedCompany;
|
company = savedCompany;
|
||||||
console.log('✅ Company saved:', company._id, 'Result:', savedCompany ? 'Success' : 'Failed');
|
log('✅ Company saved:', company._id, 'Result:', savedCompany ? 'Success' : 'Failed');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Company save error:', err);
|
console.error('Company save error:', err);
|
||||||
return res.status(400).json({ error: 'Failed to create company: ' + err.message });
|
return res.status(400).json({ error: 'Failed to create company: ' + err.message });
|
||||||
@@ -182,9 +200,9 @@ router.post('/register', async (req, res) => {
|
|||||||
companyId: company._id
|
companyId: company._id
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ User created:', newUser._id);
|
log('✅ User created:', newUser._id);
|
||||||
|
|
||||||
const token = generateToken(newUser._id.toString(), newUser.companyId.toString());
|
const token = generateToken(newUser._id.toString(), newUser.companyId.toString(), newUser.firstName, newUser.lastName, company.fullName);
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
tokens: {
|
tokens: {
|
||||||
accessToken: token,
|
accessToken: token,
|
||||||
@@ -242,7 +260,8 @@ router.post('/login', async (req, res) => {
|
|||||||
ogrn: '1027700132196',
|
ogrn: '1027700132196',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Строительство',
|
industry: 'Строительство',
|
||||||
companySize: '100-250',
|
companySize: '51-250',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://stroykomplekt.ru',
|
website: 'https://stroykomplekt.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.8,
|
rating: 4.8,
|
||||||
@@ -257,6 +276,7 @@ router.post('/login', async (req, res) => {
|
|||||||
legalForm: 'АО',
|
legalForm: 'АО',
|
||||||
industry: 'Строительство',
|
industry: 'Строительство',
|
||||||
companySize: '500+',
|
companySize: '500+',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://moscow-stroy.ru',
|
website: 'https://moscow-stroy.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.9,
|
rating: 4.9,
|
||||||
@@ -270,7 +290,8 @@ router.post('/login', async (req, res) => {
|
|||||||
ogrn: '1027700132198',
|
ogrn: '1027700132198',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'IT',
|
industry: 'IT',
|
||||||
companySize: '50-100',
|
companySize: '11-50',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://techproject.ru',
|
website: 'https://techproject.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.6,
|
rating: 4.6,
|
||||||
@@ -283,8 +304,9 @@ router.post('/login', async (req, res) => {
|
|||||||
inn: '7707083897',
|
inn: '7707083897',
|
||||||
ogrn: '1027700132199',
|
ogrn: '1027700132199',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Торговля',
|
industry: 'Оптовая торговля',
|
||||||
companySize: '100-250',
|
companySize: '51-250',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://torgpartner.ru',
|
website: 'https://torgpartner.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.3,
|
rating: 4.3,
|
||||||
@@ -298,7 +320,8 @@ router.post('/login', async (req, res) => {
|
|||||||
ogrn: '1027700132200',
|
ogrn: '1027700132200',
|
||||||
legalForm: 'ООО',
|
legalForm: 'ООО',
|
||||||
industry: 'Энергетика',
|
industry: 'Энергетика',
|
||||||
companySize: '250-500',
|
companySize: '251-500',
|
||||||
|
partnerGeography: ['moscow', 'russia_all'],
|
||||||
website: 'https://energoplus.ru',
|
website: 'https://energoplus.ru',
|
||||||
verified: true,
|
verified: true,
|
||||||
rating: 4.7,
|
rating: 4.7,
|
||||||
@@ -318,12 +341,17 @@ router.post('/login', async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = generateToken(user._id.toString(), user.companyId.toString());
|
// Получить компанию до использования в generateToken
|
||||||
console.log('✅ Token generated for user:', user._id);
|
let companyData = null;
|
||||||
|
try {
|
||||||
// Получить компанию
|
companyData = await Company.findById(user.companyId);
|
||||||
const company = await Company.findById(user.companyId);
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch company:', err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = generateToken(user._id.toString(), user.companyId.toString(), user.firstName, user.lastName, companyData?.fullName || 'Company');
|
||||||
|
log('✅ Token generated for user:', user._id);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
tokens: {
|
tokens: {
|
||||||
accessToken: token,
|
accessToken: token,
|
||||||
@@ -337,10 +365,10 @@ router.post('/login', async (req, res) => {
|
|||||||
position: user.position,
|
position: user.position,
|
||||||
companyId: user.companyId.toString()
|
companyId: user.companyId.toString()
|
||||||
},
|
},
|
||||||
company: company ? {
|
company: companyData ? {
|
||||||
id: company._id.toString(),
|
id: companyData._id.toString(),
|
||||||
name: company.fullName,
|
name: companyData.fullName,
|
||||||
inn: company.inn
|
inn: companyData.inn
|
||||||
} : null
|
} : null
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
const express = require('express')
|
const express = require('express')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
// Create remote-assets/docs directory if it doesn't exist
|
// Create remote-assets/docs directory if it doesn't exist
|
||||||
const docsDir = '../../remote-assets/docs'
|
const docsDir = path.join(__dirname, '../../remote-assets/docs')
|
||||||
if (!fs.existsSync(docsDir)) {
|
if (!fs.existsSync(docsDir)) {
|
||||||
fs.mkdirSync(docsDir, { recursive: true })
|
fs.mkdirSync(docsDir, { recursive: true })
|
||||||
}
|
}
|
||||||
@@ -47,7 +48,7 @@ router.post('/docs', (req, res) => {
|
|||||||
// Save file to disk
|
// Save file to disk
|
||||||
try {
|
try {
|
||||||
const binaryData = Buffer.from(fileData, 'base64')
|
const binaryData = Buffer.from(fileData, 'base64')
|
||||||
const filePath = `${docsDir}/${id}.${type}`
|
const filePath = path.join(docsDir, `${id}.${type}`)
|
||||||
fs.writeFileSync(filePath, binaryData)
|
fs.writeFileSync(filePath, binaryData)
|
||||||
console.log(`[BUY API] File saved to ${filePath}, size: ${binaryData.length} bytes`)
|
console.log(`[BUY API] File saved to ${filePath}, size: ${binaryData.length} bytes`)
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ router.get('/docs/:id/file', (req, res) => {
|
|||||||
return res.status(404).json({ error: 'Document not found' })
|
return res.status(404).json({ error: 'Document not found' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = `${docsDir}/${id}.${doc.type}`
|
const filePath = path.join(docsDir, `${id}.${doc.type}`)
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
console.log('[BUY API] File not found on disk:', filePath)
|
console.log('[BUY API] File not found on disk:', filePath)
|
||||||
return res.status(404).json({ error: 'File not found on disk' })
|
return res.status(404).json({ error: 'File not found on disk' })
|
||||||
|
|||||||
@@ -3,18 +3,29 @@ const router = express.Router();
|
|||||||
const { verifyToken } = require('../middleware/auth');
|
const { verifyToken } = require('../middleware/auth');
|
||||||
const BuyProduct = require('../models/BuyProduct');
|
const BuyProduct = require('../models/BuyProduct');
|
||||||
|
|
||||||
|
// Функция для логирования с проверкой DEV переменной
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// GET /buy-products/company/:companyId - получить товары компании
|
// GET /buy-products/company/:companyId - получить товары компании
|
||||||
router.get('/company/:companyId', verifyToken, async (req, res) => {
|
router.get('/company/:companyId', verifyToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
|
|
||||||
console.log('[BuyProducts] Fetching products for company:', companyId);
|
log('[BuyProducts] Fetching products for company:', companyId);
|
||||||
const products = await BuyProduct.find({ companyId })
|
const products = await BuyProduct.find({ companyId })
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
console.log('[BuyProducts] Found', products.length, 'products for company', companyId);
|
log('[BuyProducts] Found', products.length, 'products for company', companyId);
|
||||||
console.log('[BuyProducts] Products:', products);
|
log('[BuyProducts] Products:', products);
|
||||||
|
|
||||||
res.json(products);
|
res.json(products);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -32,7 +43,7 @@ router.post('/', verifyToken, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { name, description, quantity, unit, status } = req.body;
|
const { name, description, quantity, unit, status } = req.body;
|
||||||
|
|
||||||
console.log('[BuyProducts] Creating new product:', { name, description, quantity, companyId: req.user.companyId });
|
log('[BuyProducts] Creating new product:', { name, description, quantity, companyId: req.user.companyId });
|
||||||
|
|
||||||
if (!name || !description || !quantity) {
|
if (!name || !description || !quantity) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -56,11 +67,11 @@ router.post('/', verifyToken, async (req, res) => {
|
|||||||
files: [],
|
files: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[BuyProducts] Attempting to save product to DB...');
|
log('[BuyProducts] Attempting to save product to DB...');
|
||||||
const savedProduct = await newProduct.save();
|
const savedProduct = await newProduct.save();
|
||||||
|
|
||||||
console.log('[BuyProducts] New product created successfully:', savedProduct._id);
|
log('[BuyProducts] New product created successfully:', savedProduct._id);
|
||||||
console.log('[BuyProducts] Product data:', savedProduct);
|
log('[BuyProducts] Product data:', savedProduct);
|
||||||
|
|
||||||
res.status(201).json(savedProduct);
|
res.status(201).json(savedProduct);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -100,7 +111,7 @@ router.put('/:id', verifyToken, async (req, res) => {
|
|||||||
|
|
||||||
const updatedProduct = await product.save();
|
const updatedProduct = await product.save();
|
||||||
|
|
||||||
console.log('[BuyProducts] Product updated:', id);
|
log('[BuyProducts] Product updated:', id);
|
||||||
|
|
||||||
res.json(updatedProduct);
|
res.json(updatedProduct);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -129,7 +140,7 @@ router.delete('/:id', verifyToken, async (req, res) => {
|
|||||||
|
|
||||||
await BuyProduct.findByIdAndDelete(id);
|
await BuyProduct.findByIdAndDelete(id);
|
||||||
|
|
||||||
console.log('[BuyProducts] Product deleted:', id);
|
log('[BuyProducts] Product deleted:', id);
|
||||||
|
|
||||||
res.json({ message: 'Product deleted successfully' });
|
res.json({ message: 'Product deleted successfully' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -141,4 +152,143 @@ router.delete('/:id', verifyToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /buy-products/:id/files - добавить файл к товару
|
||||||
|
router.post('/:id/files', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { fileName, fileUrl, fileType, fileSize } = req.body;
|
||||||
|
|
||||||
|
const product = await BuyProduct.findById(id);
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return res.status(404).json({ error: 'Product not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Только владелец товара может добавить файл
|
||||||
|
if (product.companyId.toString() !== req.user.companyId.toString()) {
|
||||||
|
return res.status(403).json({ error: 'Not authorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = {
|
||||||
|
id: 'file-' + Date.now(),
|
||||||
|
name: fileName,
|
||||||
|
url: fileUrl,
|
||||||
|
type: fileType,
|
||||||
|
size: fileSize,
|
||||||
|
uploadedAt: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
product.files.push(file);
|
||||||
|
await product.save();
|
||||||
|
|
||||||
|
log('[BuyProducts] File added to product:', id);
|
||||||
|
|
||||||
|
res.json(product);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BuyProducts] Error adding file:', error.message);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error',
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /buy-products/:id/files/:fileId - удалить файл
|
||||||
|
router.delete('/:id/files/:fileId', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id, fileId } = req.params;
|
||||||
|
|
||||||
|
const product = await BuyProduct.findById(id);
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return res.status(404).json({ error: 'Product not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product.companyId.toString() !== req.user.companyId.toString()) {
|
||||||
|
return res.status(403).json({ error: 'Not authorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
product.files = product.files.filter(f => f.id !== fileId);
|
||||||
|
await product.save();
|
||||||
|
|
||||||
|
log('[BuyProducts] File deleted from product:', id);
|
||||||
|
|
||||||
|
res.json(product);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BuyProducts] Error deleting file:', error.message);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error',
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /buy-products/:id/accept - акцептировать товар
|
||||||
|
router.post('/:id/accept', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const companyId = req.user.companyId;
|
||||||
|
|
||||||
|
const product = await BuyProduct.findById(id);
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return res.status(404).json({ error: 'Product not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Не можем акцептировать собственный товар
|
||||||
|
if (product.companyId.toString() === companyId.toString()) {
|
||||||
|
return res.status(403).json({ error: 'Cannot accept own product' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить, не акцептировал ли уже
|
||||||
|
const alreadyAccepted = product.acceptedBy.some(
|
||||||
|
a => a.companyId.toString() === companyId.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (alreadyAccepted) {
|
||||||
|
return res.status(400).json({ error: 'Already accepted' });
|
||||||
|
}
|
||||||
|
|
||||||
|
product.acceptedBy.push({
|
||||||
|
companyId,
|
||||||
|
acceptedAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
await product.save();
|
||||||
|
|
||||||
|
log('[BuyProducts] Product accepted by company:', companyId);
|
||||||
|
|
||||||
|
res.json(product);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BuyProducts] Error accepting product:', error.message);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error',
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /buy-products/:id/acceptances - получить компании которые акцептовали
|
||||||
|
router.get('/:id/acceptances', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const product = await BuyProduct.findById(id).populate('acceptedBy.companyId', 'shortName fullName');
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return res.status(404).json({ error: 'Product not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
log('[BuyProducts] Returned acceptances for product:', id);
|
||||||
|
|
||||||
|
res.json(product.acceptedBy);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BuyProducts] Error fetching acceptances:', error.message);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error',
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,35 +1,88 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { verifyToken } = require('../middleware/auth');
|
const { verifyToken } = require('../middleware/auth');
|
||||||
|
const Message = require('../models/Message');
|
||||||
|
|
||||||
// In-memory storage
|
// Функция для логирования с проверкой DEV переменной
|
||||||
let messages = [];
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// GET /messages/threads - получить все потоки для компании
|
// GET /messages/threads - получить все потоки для компании
|
||||||
router.get('/threads', verifyToken, async (req, res) => {
|
router.get('/threads', verifyToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const companyId = req.user.companyId;
|
const companyId = req.user.companyId;
|
||||||
|
const { ObjectId } = require('mongoose').Types;
|
||||||
|
|
||||||
// Группировка сообщений по threadId
|
log('[Messages] Fetching threads for companyId:', companyId, 'type:', typeof companyId);
|
||||||
const threads = {};
|
|
||||||
|
// Преобразовать в ObjectId если это строка
|
||||||
|
let companyObjectId = companyId;
|
||||||
|
let companyIdString = companyId.toString ? companyId.toString() : companyId;
|
||||||
|
|
||||||
messages.forEach(msg => {
|
try {
|
||||||
if (msg.senderCompanyId === companyId || msg.recipientCompanyId === companyId) {
|
if (typeof companyId === 'string' && ObjectId.isValid(companyId)) {
|
||||||
if (!threads[msg.threadId]) {
|
companyObjectId = new ObjectId(companyId);
|
||||||
threads[msg.threadId] = msg;
|
}
|
||||||
}
|
} catch (e) {
|
||||||
|
log('[Messages] Could not convert to ObjectId:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('[Messages] Using companyObjectId:', companyObjectId, 'companyIdString:', companyIdString);
|
||||||
|
|
||||||
|
// Получить все сообщения где текущая компания отправитель или получатель
|
||||||
|
// Поддерживаем оба формата - ObjectId и строки
|
||||||
|
const allMessages = await Message.find({
|
||||||
|
$or: [
|
||||||
|
{ senderCompanyId: companyObjectId },
|
||||||
|
{ senderCompanyId: companyIdString },
|
||||||
|
{ recipientCompanyId: companyObjectId },
|
||||||
|
{ recipientCompanyId: companyIdString },
|
||||||
|
// Также ищем по threadId который может содержать ID компании
|
||||||
|
{ threadId: { $regex: companyIdString } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.sort({ timestamp: -1 })
|
||||||
|
.limit(500);
|
||||||
|
|
||||||
|
log('[Messages] Found', allMessages.length, 'messages for company');
|
||||||
|
|
||||||
|
if (allMessages.length === 0) {
|
||||||
|
log('[Messages] No messages found');
|
||||||
|
res.json([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Группируем по потокам и берем последнее сообщение каждого потока
|
||||||
|
const threadsMap = new Map();
|
||||||
|
allMessages.forEach(msg => {
|
||||||
|
const threadId = msg.threadId;
|
||||||
|
if (!threadsMap.has(threadId)) {
|
||||||
|
threadsMap.set(threadId, {
|
||||||
|
threadId,
|
||||||
|
lastMessage: msg.text,
|
||||||
|
lastMessageAt: msg.timestamp,
|
||||||
|
senderCompanyId: msg.senderCompanyId,
|
||||||
|
recipientCompanyId: msg.recipientCompanyId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Преобразование в массив и сортировка по времени
|
const threads = Array.from(threadsMap.values()).sort((a, b) =>
|
||||||
const threadsArray = Object.values(threads)
|
new Date(b.lastMessageAt) - new Date(a.lastMessageAt)
|
||||||
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
);
|
||||||
|
|
||||||
console.log('[Messages] Returned', threadsArray.length, 'threads for company', companyId);
|
log('[Messages] Returned', threads.length, 'unique threads');
|
||||||
|
|
||||||
res.json(threadsArray);
|
res.json(threads);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Messages] Error:', error.message);
|
console.error('[Messages] Error fetching threads:', error.message, error.stack);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -38,16 +91,24 @@ router.get('/threads', verifyToken, async (req, res) => {
|
|||||||
router.get('/:threadId', verifyToken, async (req, res) => {
|
router.get('/:threadId', verifyToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { threadId } = req.params;
|
const { threadId } = req.params;
|
||||||
|
const companyId = req.user.companyId;
|
||||||
|
|
||||||
const threadMessages = messages
|
// Получить все сообщения потока
|
||||||
.filter(msg => msg.threadId === threadId)
|
const threadMessages = await Message.find({ threadId })
|
||||||
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
.sort({ timestamp: 1 })
|
||||||
|
.exec();
|
||||||
|
|
||||||
console.log('[Messages] Returned', threadMessages.length, 'messages for thread', threadId);
|
// Отметить сообщения как прочитанные для текущей компании
|
||||||
|
await Message.updateMany(
|
||||||
|
{ threadId, recipientCompanyId: companyId, read: false },
|
||||||
|
{ read: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
log('[Messages] Returned', threadMessages.length, 'messages for thread', threadId);
|
||||||
|
|
||||||
res.json(threadMessages);
|
res.json(threadMessages);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Messages] Error:', error.message);
|
console.error('[Messages] Error fetching messages:', error.message);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -63,61 +124,139 @@ router.post('/:threadId', verifyToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Определить получателя на основе threadId
|
// Определить получателя на основе threadId
|
||||||
const threadParts = threadId.split('-');
|
// threadId формат: "thread-id1-id2"
|
||||||
|
const threadParts = threadId.replace('thread-', '').split('-');
|
||||||
let recipientCompanyId = null;
|
let recipientCompanyId = null;
|
||||||
|
|
||||||
if (threadParts.length >= 3) {
|
const currentSender = senderCompanyId || req.user.companyId;
|
||||||
const companyId1 = threadParts[1];
|
const currentSenderString = currentSender.toString ? currentSender.toString() : currentSender;
|
||||||
const companyId2 = threadParts[2];
|
|
||||||
const currentSender = senderCompanyId || req.user.companyId;
|
if (threadParts.length >= 2) {
|
||||||
recipientCompanyId = currentSender === companyId1 ? companyId2 : companyId1;
|
const companyId1 = threadParts[0];
|
||||||
|
const companyId2 = threadParts[1];
|
||||||
|
// Получатель - это другая сторона
|
||||||
|
recipientCompanyId = currentSenderString === companyId1 ? companyId2 : companyId1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
log('[Messages] POST /messages/:threadId');
|
||||||
_id: 'msg-' + Date.now(),
|
log('[Messages] threadId:', threadId);
|
||||||
|
log('[Messages] Sender:', currentSender);
|
||||||
|
log('[Messages] SenderString:', currentSenderString);
|
||||||
|
log('[Messages] Recipient:', recipientCompanyId);
|
||||||
|
|
||||||
|
// Найти recipientCompanyId по ObjectId если нужно
|
||||||
|
let recipientObjectId = recipientCompanyId;
|
||||||
|
const { ObjectId } = require('mongoose').Types;
|
||||||
|
try {
|
||||||
|
if (typeof recipientCompanyId === 'string' && ObjectId.isValid(recipientCompanyId)) {
|
||||||
|
recipientObjectId = new ObjectId(recipientCompanyId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('[Messages] Could not convert recipientId to ObjectId');
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = new Message({
|
||||||
threadId,
|
threadId,
|
||||||
senderCompanyId: senderCompanyId || req.user.companyId,
|
senderCompanyId: currentSender,
|
||||||
recipientCompanyId,
|
recipientCompanyId: recipientObjectId,
|
||||||
text: text.trim(),
|
text: text.trim(),
|
||||||
|
read: false,
|
||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
};
|
});
|
||||||
|
|
||||||
messages.push(message);
|
const savedMessage = await message.save();
|
||||||
|
|
||||||
console.log('[Messages] New message created:', message._id);
|
log('[Messages] New message created:', savedMessage._id);
|
||||||
|
log('[Messages] Message data:', {
|
||||||
|
threadId: savedMessage.threadId,
|
||||||
|
senderCompanyId: savedMessage.senderCompanyId,
|
||||||
|
recipientCompanyId: savedMessage.recipientCompanyId
|
||||||
|
});
|
||||||
|
|
||||||
res.status(201).json(message);
|
res.status(201).json(savedMessage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Messages] Error:', error.message);
|
console.error('[Messages] Error creating message:', error.message, error.stack);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /messages - создать сообщение (старый endpoint для совместимости)
|
// MIGRATION ENDPOINT - Fix recipientCompanyId for all messages
|
||||||
router.post('/', verifyToken, async (req, res) => {
|
router.post('/admin/migrate-fix-recipients', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { threadId, text, recipientCompanyId } = req.body;
|
const allMessages = await Message.find().exec();
|
||||||
|
log('[Messages] Migrating', allMessages.length, 'messages...');
|
||||||
|
|
||||||
if (!text || !threadId) {
|
let fixedCount = 0;
|
||||||
return res.status(400).json({ error: 'Text and threadId required' });
|
let errorCount = 0;
|
||||||
|
|
||||||
|
for (const message of allMessages) {
|
||||||
|
try {
|
||||||
|
const threadId = message.threadId;
|
||||||
|
if (!threadId) continue;
|
||||||
|
|
||||||
|
// Parse threadId формат "thread-id1-id2" или "id1-id2"
|
||||||
|
const ids = threadId.replace('thread-', '').split('-');
|
||||||
|
if (ids.length < 2) {
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const companyId1 = ids[0];
|
||||||
|
const companyId2 = ids[1];
|
||||||
|
|
||||||
|
// Compare with senderCompanyId
|
||||||
|
const senderIdString = message.senderCompanyId.toString ? message.senderCompanyId.toString() : message.senderCompanyId;
|
||||||
|
const expectedRecipient = senderIdString === companyId1 ? companyId2 : companyId1;
|
||||||
|
|
||||||
|
// 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)) {
|
||||||
|
recipientObjectId = new ObjectId(expectedRecipient);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await Message.updateOne(
|
||||||
|
{ _id: message._id },
|
||||||
|
{ recipientCompanyId: recipientObjectId }
|
||||||
|
);
|
||||||
|
|
||||||
|
fixedCount++;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Messages] Migration error:', err.message);
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
log('[Messages] Migration completed! Fixed:', fixedCount, 'Errors:', errorCount);
|
||||||
_id: 'msg-' + Date.now(),
|
res.json({ success: true, fixed: fixedCount, errors: errorCount, total: allMessages.length });
|
||||||
threadId,
|
} catch (error) {
|
||||||
senderCompanyId: req.user.companyId,
|
console.error('[Messages] Migration error:', error.message);
|
||||||
recipientCompanyId,
|
res.status(500).json({ error: error.message });
|
||||||
text: text.trim(),
|
}
|
||||||
timestamp: new Date()
|
});
|
||||||
};
|
|
||||||
|
// DEBUG ENDPOINT
|
||||||
messages.push(message);
|
router.get('/debug/all-messages', async (req, res) => {
|
||||||
|
try {
|
||||||
console.log('[Messages] New message created:', message._id);
|
const allMessages = await Message.find().limit(10).exec();
|
||||||
|
log('[Debug] Total messages in DB:', allMessages.length);
|
||||||
res.status(201).json(message);
|
|
||||||
|
const info = allMessages.map(m => ({
|
||||||
|
_id: m._id,
|
||||||
|
threadId: m.threadId,
|
||||||
|
senderCompanyId: m.senderCompanyId?.toString ? m.senderCompanyId.toString() : m.senderCompanyId,
|
||||||
|
recipientCompanyId: m.recipientCompanyId?.toString ? m.recipientCompanyId.toString() : m.recipientCompanyId,
|
||||||
|
text: m.text.substring(0, 30)
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json({ totalCount: allMessages.length, messages: info });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Messages] Error:', error.message);
|
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,17 @@ const router = express.Router();
|
|||||||
const { verifyToken } = require('../middleware/auth');
|
const { verifyToken } = require('../middleware/auth');
|
||||||
const Product = require('../models/Product');
|
const Product = require('../models/Product');
|
||||||
|
|
||||||
|
// Функция для логирования с проверкой DEV переменной
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Helper to transform _id to id
|
// Helper to transform _id to id
|
||||||
const transformProduct = (doc) => {
|
const transformProduct = (doc) => {
|
||||||
if (!doc) return null;
|
if (!doc) return null;
|
||||||
@@ -19,13 +30,13 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const companyId = req.user.companyId;
|
const companyId = req.user.companyId;
|
||||||
|
|
||||||
console.log('[Products] GET Fetching products for companyId:', companyId);
|
log('[Products] GET Fetching products for companyId:', companyId);
|
||||||
|
|
||||||
const products = await Product.find({ companyId })
|
const products = await Product.find({ companyId })
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
console.log('[Products] Found', products.length, 'products');
|
log('[Products] Found', products.length, 'products');
|
||||||
res.json(products.map(transformProduct));
|
res.json(products.map(transformProduct));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Products] Get error:', error.message);
|
console.error('[Products] Get error:', error.message);
|
||||||
@@ -35,20 +46,20 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
|
|
||||||
// POST /products - Создать продукт/услугу
|
// POST /products - Создать продукт/услугу
|
||||||
router.post('/', verifyToken, async (req, res) => {
|
router.post('/', verifyToken, async (req, res) => {
|
||||||
try {
|
// try {
|
||||||
const { name, category, description, type, productUrl, price, unit, minOrder } = req.body;
|
const { name, category, description, type, productUrl, price, unit, minOrder } = req.body;
|
||||||
const companyId = req.user.companyId;
|
const companyId = req.user.companyId;
|
||||||
|
|
||||||
console.log('[Products] POST Creating product:', { name, category, type });
|
log('[Products] POST Creating product:', { name, category, type });
|
||||||
|
|
||||||
// Валидация
|
// // Валидация
|
||||||
if (!name || !category || !description || !type) {
|
// if (!name || !category || !description || !type) {
|
||||||
return res.status(400).json({ error: 'name, category, description, and type are required' });
|
// return res.status(400).json({ error: 'name, category, description, and type are required' });
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (description.length < 20) {
|
// if (description.length < 20) {
|
||||||
return res.status(400).json({ error: 'Description must be at least 20 characters' });
|
// return res.status(400).json({ error: 'Description must be at least 20 characters' });
|
||||||
}
|
// }
|
||||||
|
|
||||||
const newProduct = new Product({
|
const newProduct = new Product({
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
@@ -63,13 +74,13 @@ router.post('/', verifyToken, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const savedProduct = await newProduct.save();
|
const savedProduct = await newProduct.save();
|
||||||
console.log('[Products] Product created with ID:', savedProduct._id);
|
log('[Products] Product created with ID:', savedProduct._id);
|
||||||
|
|
||||||
res.status(201).json(transformProduct(savedProduct));
|
res.status(201).json(transformProduct(savedProduct));
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('[Products] Create error:', error.message);
|
// console.error('[Products] Create error:', error.message);
|
||||||
res.status(500).json({ error: 'Internal server error', message: error.message });
|
// res.status(500).json({ error: 'Internal server error', message: error.message });
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
// PUT /products/:id - Обновить продукт/услугу
|
// PUT /products/:id - Обновить продукт/услугу
|
||||||
@@ -96,7 +107,7 @@ router.put('/:id', verifyToken, async (req, res) => {
|
|||||||
{ new: true, runValidators: true }
|
{ new: true, runValidators: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Products] Product updated:', id);
|
log('[Products] Product updated:', id);
|
||||||
res.json(transformProduct(updatedProduct));
|
res.json(transformProduct(updatedProduct));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Products] Update error:', error.message);
|
console.error('[Products] Update error:', error.message);
|
||||||
@@ -127,7 +138,7 @@ router.patch('/:id', verifyToken, async (req, res) => {
|
|||||||
{ new: true, runValidators: true }
|
{ new: true, runValidators: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Products] Product patched:', id);
|
log('[Products] Product patched:', id);
|
||||||
res.json(transformProduct(updatedProduct));
|
res.json(transformProduct(updatedProduct));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Products] Patch error:', error.message);
|
console.error('[Products] Patch error:', error.message);
|
||||||
@@ -153,7 +164,7 @@ router.delete('/:id', verifyToken, async (req, res) => {
|
|||||||
|
|
||||||
await Product.findByIdAndDelete(id);
|
await Product.findByIdAndDelete(id);
|
||||||
|
|
||||||
console.log('[Products] Product deleted:', id);
|
log('[Products] Product deleted:', id);
|
||||||
res.json({ message: 'Product deleted successfully' });
|
res.json({ message: 'Product deleted successfully' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Products] Delete error:', error.message);
|
console.error('[Products] Delete error:', error.message);
|
||||||
|
|||||||
171
server/routers/procurement/routes/requests.js
Normal file
171
server/routers/procurement/routes/requests.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { verifyToken } = require('../middleware/auth');
|
||||||
|
const Request = require('../models/Request');
|
||||||
|
|
||||||
|
// Функция для логирования с проверкой DEV переменной
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// GET /requests/sent - получить отправленные запросы
|
||||||
|
router.get('/sent', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const companyId = req.user.companyId;
|
||||||
|
|
||||||
|
const requests = await Request.find({ senderCompanyId: companyId })
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
log('[Requests] Returned', requests.length, 'sent requests for company', companyId);
|
||||||
|
|
||||||
|
res.json(requests);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Requests] Error fetching sent requests:', error.message);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /requests/received - получить полученные запросы
|
||||||
|
router.get('/received', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const companyId = req.user.companyId;
|
||||||
|
|
||||||
|
const requests = await Request.find({ recipientCompanyId: companyId })
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
log('[Requests] Returned', requests.length, 'received requests for company', companyId);
|
||||||
|
|
||||||
|
res.json(requests);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Requests] Error fetching received requests:', error.message);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /requests - создать запрос
|
||||||
|
router.post('/', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { text, recipientCompanyIds, productId, files } = req.body;
|
||||||
|
const senderCompanyId = req.user.companyId;
|
||||||
|
|
||||||
|
if (!text || !recipientCompanyIds || !Array.isArray(recipientCompanyIds) || recipientCompanyIds.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'text and recipientCompanyIds array required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправить запрос каждой компании
|
||||||
|
const results = [];
|
||||||
|
for (const recipientCompanyId of recipientCompanyIds) {
|
||||||
|
try {
|
||||||
|
const request = new Request({
|
||||||
|
senderCompanyId,
|
||||||
|
recipientCompanyId,
|
||||||
|
text,
|
||||||
|
productId,
|
||||||
|
files: files || [],
|
||||||
|
status: 'pending'
|
||||||
|
});
|
||||||
|
|
||||||
|
await request.save();
|
||||||
|
results.push({
|
||||||
|
companyId: recipientCompanyId,
|
||||||
|
success: true,
|
||||||
|
message: 'Request sent successfully'
|
||||||
|
});
|
||||||
|
|
||||||
|
log('[Requests] Request sent to company:', recipientCompanyId);
|
||||||
|
} catch (err) {
|
||||||
|
results.push({
|
||||||
|
companyId: recipientCompanyId,
|
||||||
|
success: false,
|
||||||
|
message: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранить отчет
|
||||||
|
const report = {
|
||||||
|
text,
|
||||||
|
result: results,
|
||||||
|
createdAt: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
id: 'bulk-' + Date.now(),
|
||||||
|
...report,
|
||||||
|
files: files || []
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Requests] Error creating request:', error.message);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT /requests/:id - ответить на запрос
|
||||||
|
router.put('/:id', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { response, status } = req.body;
|
||||||
|
|
||||||
|
const request = await Request.findById(id);
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return res.status(404).json({ error: 'Request not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Только получатель может ответить на запрос
|
||||||
|
if (request.recipientCompanyId !== req.user.companyId) {
|
||||||
|
return res.status(403).json({ error: 'Not authorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
request.response = response;
|
||||||
|
request.status = status || 'accepted';
|
||||||
|
request.respondedAt = new Date();
|
||||||
|
request.updatedAt = new Date();
|
||||||
|
|
||||||
|
await request.save();
|
||||||
|
|
||||||
|
log('[Requests] Request responded:', id);
|
||||||
|
|
||||||
|
res.json(request);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Requests] Error responding to request:', error.message);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /requests/:id - удалить запрос
|
||||||
|
router.delete('/:id', verifyToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const request = await Request.findById(id);
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return res.status(404).json({ error: 'Request not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Может удалить отправитель или получатель
|
||||||
|
if (request.senderCompanyId !== req.user.companyId && request.recipientCompanyId !== req.user.companyId) {
|
||||||
|
return res.status(403).json({ error: 'Not authorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await Request.findByIdAndDelete(id);
|
||||||
|
|
||||||
|
log('[Requests] Request deleted:', id);
|
||||||
|
|
||||||
|
res.json({ message: 'Request deleted successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Requests] Error deleting request:', error.message);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { verifyToken } = require('../middleware/auth');
|
const { verifyToken } = require('../middleware/auth');
|
||||||
|
const Review = require('../models/Review');
|
||||||
|
|
||||||
// In-memory storage for reviews
|
// Функция для логирования с проверкой DEV переменной
|
||||||
let reviews = [];
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
// Reference to companies from search routes
|
if (data) {
|
||||||
let companies = [];
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
// Синхронизация с companies из других routes
|
console.log(message);
|
||||||
const syncCompanies = () => {
|
}
|
||||||
// После создания review обновляем рейтинг компании
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// GET /reviews/company/:companyId - получить отзывы компании
|
// GET /reviews/company/:companyId - получить отзывы компании
|
||||||
@@ -18,15 +19,15 @@ router.get('/company/:companyId', verifyToken, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
|
|
||||||
const companyReviews = reviews
|
const companyReviews = await Review.find({ companyId })
|
||||||
.filter(r => r.companyId === companyId)
|
.sort({ createdAt: -1 })
|
||||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
.exec();
|
||||||
|
|
||||||
console.log('[Reviews] Returned', companyReviews.length, 'reviews for company', companyId);
|
log('[Reviews] Returned', companyReviews.length, 'reviews for company', companyId);
|
||||||
|
|
||||||
res.json(companyReviews);
|
res.json(companyReviews);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Reviews] Error:', error.message);
|
console.error('[Reviews] Error fetching reviews:', error.message);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: error.message,
|
message: error.message,
|
||||||
@@ -51,15 +52,14 @@ router.post('/', verifyToken, async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comment.length < 10 || comment.length > 1000) {
|
if (comment.trim().length < 10 || comment.trim().length > 1000) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Comment must be between 10 and 1000 characters',
|
error: 'Comment must be between 10 and 1000 characters',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создать новый отзыв
|
// Создать новый отзыв
|
||||||
const newReview = {
|
const newReview = new Review({
|
||||||
_id: 'review-' + Date.now(),
|
|
||||||
companyId,
|
companyId,
|
||||||
authorCompanyId: req.user.companyId,
|
authorCompanyId: req.user.companyId,
|
||||||
authorName: req.user.firstName + ' ' + req.user.lastName,
|
authorName: req.user.firstName + ' ' + req.user.lastName,
|
||||||
@@ -69,15 +69,15 @@ router.post('/', verifyToken, async (req, res) => {
|
|||||||
verified: true,
|
verified: true,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
};
|
});
|
||||||
|
|
||||||
reviews.push(newReview);
|
const savedReview = await newReview.save();
|
||||||
|
|
||||||
console.log('[Reviews] New review created:', newReview._id);
|
log('[Reviews] New review created:', savedReview._id);
|
||||||
|
|
||||||
res.status(201).json(newReview);
|
res.status(201).json(savedReview);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Reviews] Error:', error.message);
|
console.error('[Reviews] Error creating review:', error.message);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Internal server error',
|
error: 'Internal server error',
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
|||||||
@@ -3,6 +3,17 @@ const router = express.Router();
|
|||||||
const { verifyToken } = require('../middleware/auth');
|
const { verifyToken } = require('../middleware/auth');
|
||||||
const Company = require('../models/Company');
|
const Company = require('../models/Company');
|
||||||
|
|
||||||
|
// Функция для логирования с проверкой DEV переменной
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// GET /search/recommendations - получить рекомендации компаний (ДОЛЖЕН быть ПЕРЕД /*)
|
// GET /search/recommendations - получить рекомендации компаний (ДОЛЖЕН быть ПЕРЕД /*)
|
||||||
router.get('/recommendations', verifyToken, async (req, res) => {
|
router.get('/recommendations', verifyToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -28,7 +39,7 @@ router.get('/recommendations', verifyToken, async (req, res) => {
|
|||||||
reason: 'Matches your search criteria'
|
reason: 'Matches your search criteria'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('[Search] Returned recommendations:', recommendations.length);
|
log('[Search] Returned recommendations:', recommendations.length);
|
||||||
|
|
||||||
res.json(recommendations);
|
res.json(recommendations);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -47,6 +58,9 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
query = '',
|
query = '',
|
||||||
page = 1,
|
page = 1,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
|
industries,
|
||||||
|
companySize,
|
||||||
|
geography,
|
||||||
minRating = 0,
|
minRating = 0,
|
||||||
hasReviews,
|
hasReviews,
|
||||||
hasAcceptedDocs,
|
hasAcceptedDocs,
|
||||||
@@ -58,6 +72,29 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
const User = require('../models/User');
|
const User = require('../models/User');
|
||||||
const user = await User.findById(req.userId);
|
const user = await User.findById(req.userId);
|
||||||
|
|
||||||
|
log('[Search] Request params:', { query, industries, companySize, geography, minRating, hasReviews, hasAcceptedDocs, sortBy, sortOrder });
|
||||||
|
|
||||||
|
// Маппинг кодов фильтров на значения в БД
|
||||||
|
const industryMap = {
|
||||||
|
'it': 'IT',
|
||||||
|
'finance': 'Финансы',
|
||||||
|
'manufacturing': 'Производство',
|
||||||
|
'construction': 'Строительство',
|
||||||
|
'retail': 'Розничная торговля',
|
||||||
|
'wholesale': 'Оптовая торговля',
|
||||||
|
'logistics': 'Логистика',
|
||||||
|
'healthcare': 'Здравоохранение',
|
||||||
|
'education': 'Образование',
|
||||||
|
'consulting': 'Консалтинг',
|
||||||
|
'marketing': 'Маркетинг',
|
||||||
|
'realestate': 'Недвижимость',
|
||||||
|
'food': 'Пищевая промышленность',
|
||||||
|
'agriculture': 'Сельское хозяйство',
|
||||||
|
'energy': 'Энергетика',
|
||||||
|
'telecom': 'Телекоммуникации',
|
||||||
|
'media': 'Медиа'
|
||||||
|
};
|
||||||
|
|
||||||
// Начальный фильтр: исключить собственную компанию
|
// Начальный фильтр: исключить собственную компанию
|
||||||
let filters = [];
|
let filters = [];
|
||||||
|
|
||||||
@@ -78,6 +115,43 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Фильтр по отраслям - преобразуем коды в значения БД
|
||||||
|
if (industries) {
|
||||||
|
const industryList = Array.isArray(industries) ? industries : [industries];
|
||||||
|
if (industryList.length > 0) {
|
||||||
|
const dbIndustries = industryList
|
||||||
|
.map(code => industryMap[code])
|
||||||
|
.filter(val => val !== undefined);
|
||||||
|
|
||||||
|
log('[Search] Raw industries param:', industries);
|
||||||
|
log('[Search] Industry codes:', industryList, 'Mapped to:', dbIndustries);
|
||||||
|
|
||||||
|
if (dbIndustries.length > 0) {
|
||||||
|
filters.push({ industry: { $in: dbIndustries } });
|
||||||
|
log('[Search] Added industry filter:', { industry: { $in: dbIndustries } });
|
||||||
|
} else {
|
||||||
|
log('[Search] No industries mapped! Codes were:', industryList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по размеру компании
|
||||||
|
if (companySize) {
|
||||||
|
const sizeList = Array.isArray(companySize) ? companySize : [companySize];
|
||||||
|
if (sizeList.length > 0) {
|
||||||
|
filters.push({ companySize: { $in: sizeList } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по географии
|
||||||
|
if (geography) {
|
||||||
|
const geoList = Array.isArray(geography) ? geography : [geography];
|
||||||
|
if (geoList.length > 0) {
|
||||||
|
filters.push({ partnerGeography: { $in: geoList } });
|
||||||
|
log('[Search] Geography filter:', { partnerGeography: { $in: geoList } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Фильтр по рейтингу
|
// Фильтр по рейтингу
|
||||||
if (minRating) {
|
if (minRating) {
|
||||||
const rating = parseFloat(minRating);
|
const rating = parseFloat(minRating);
|
||||||
@@ -112,6 +186,12 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
sortOptions.rating = sortOrder === 'asc' ? 1 : -1;
|
sortOptions.rating = sortOrder === 'asc' ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log('[Search] Final MongoDB filter:', JSON.stringify(filter, null, 2));
|
||||||
|
|
||||||
|
let filterDebug = filters.length > 0 ? { $and: filters } : {};
|
||||||
|
const allCompanies = await Company.find({});
|
||||||
|
log('[Search] All companies in DB:', allCompanies.map(c => ({ name: c.fullName, geography: c.partnerGeography, industry: c.industry })));
|
||||||
|
|
||||||
const total = await Company.countDocuments(filter);
|
const total = await Company.countDocuments(filter);
|
||||||
const companies = await Company.find(filter)
|
const companies = await Company.find(filter)
|
||||||
.sort(sortOptions)
|
.sort(sortOptions)
|
||||||
@@ -123,13 +203,19 @@ router.get('/', verifyToken, async (req, res) => {
|
|||||||
id: c._id
|
id: c._id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('[Search] Returned', paginatedResults.length, 'companies');
|
log('[Search] Query:', query, 'Industries:', industries, 'Size:', companySize, 'Geo:', geography);
|
||||||
|
log('[Search] Total found:', total, 'Returning:', paginatedResults.length, 'companies');
|
||||||
|
log('[Search] Company details:', paginatedResults.map(c => ({ name: c.fullName, industry: c.industry })));
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
companies: paginatedResults,
|
companies: paginatedResults,
|
||||||
total,
|
total,
|
||||||
page: pageNum,
|
page: pageNum,
|
||||||
totalPages: Math.ceil(total / limitNum)
|
totalPages: Math.ceil(total / limitNum),
|
||||||
|
_debug: {
|
||||||
|
filter: JSON.stringify(filter),
|
||||||
|
industriesReceived: industries
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Search] Error:', error.message);
|
console.error('[Search] Error:', error.message);
|
||||||
|
|||||||
93
server/routers/procurement/scripts/migrate-messages.js
Normal file
93
server/routers/procurement/scripts/migrate-messages.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const mongoose = require('mongoose');
|
||||||
|
const Message = require('../models/Message');
|
||||||
|
require('dotenv').config({ path: '../../.env' });
|
||||||
|
|
||||||
|
const mongoUrl = process.env.MONGODB_URI || 'mongodb://localhost:27017/procurement_db';
|
||||||
|
|
||||||
|
async function migrateMessages() {
|
||||||
|
try {
|
||||||
|
console.log('[Migration] Connecting to MongoDB...');
|
||||||
|
await mongoose.connect(mongoUrl, {
|
||||||
|
useNewUrlParser: true,
|
||||||
|
useUnifiedTopology: true,
|
||||||
|
serverSelectionTimeoutMS: 5000,
|
||||||
|
connectTimeoutMS: 5000,
|
||||||
|
});
|
||||||
|
console.log('[Migration] Connected to MongoDB');
|
||||||
|
|
||||||
|
// Найти все сообщения
|
||||||
|
const allMessages = await Message.find().exec();
|
||||||
|
console.log('[Migration] Found', allMessages.length, 'total messages');
|
||||||
|
|
||||||
|
let fixedCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
// Проходим по каждому сообщению
|
||||||
|
for (const message of allMessages) {
|
||||||
|
try {
|
||||||
|
const threadId = message.threadId;
|
||||||
|
if (!threadId) {
|
||||||
|
console.log('[Migration] Skipping message', message._id, '- no threadId');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Парсим threadId формата "thread-id1-id2" или "id1-id2"
|
||||||
|
let ids = threadId.replace('thread-', '').split('-');
|
||||||
|
|
||||||
|
if (ids.length < 2) {
|
||||||
|
console.log('[Migration] Invalid threadId format:', threadId);
|
||||||
|
errorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const companyId1 = ids[0];
|
||||||
|
const companyId2 = ids[1];
|
||||||
|
|
||||||
|
// Сравниваем с senderCompanyId
|
||||||
|
const senderIdString = message.senderCompanyId.toString ? message.senderCompanyId.toString() : message.senderCompanyId;
|
||||||
|
const expectedRecipient = senderIdString === companyId1 ? companyId2 : companyId1;
|
||||||
|
|
||||||
|
// Если recipientCompanyId не установлена или неправильная - исправляем
|
||||||
|
if (!message.recipientCompanyId || message.recipientCompanyId.toString() !== expectedRecipient) {
|
||||||
|
console.log('[Migration] Fixing message', message._id);
|
||||||
|
console.log(' Old recipientCompanyId:', message.recipientCompanyId);
|
||||||
|
console.log(' Expected:', expectedRecipient);
|
||||||
|
|
||||||
|
// Конвертируем в ObjectId если нужно
|
||||||
|
const { ObjectId } = require('mongoose').Types;
|
||||||
|
let recipientObjectId = expectedRecipient;
|
||||||
|
try {
|
||||||
|
if (typeof expectedRecipient === 'string' && ObjectId.isValid(expectedRecipient)) {
|
||||||
|
recipientObjectId = new ObjectId(expectedRecipient);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' Could not convert to ObjectId');
|
||||||
|
}
|
||||||
|
|
||||||
|
await Message.updateOne(
|
||||||
|
{ _id: message._id },
|
||||||
|
{ recipientCompanyId: recipientObjectId }
|
||||||
|
);
|
||||||
|
|
||||||
|
fixedCount++;
|
||||||
|
console.log(' ✅ Fixed');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Migration] Error processing message', message._id, ':', err.message);
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Migration] ✅ Migration completed!');
|
||||||
|
console.log('[Migration] Fixed:', fixedCount, 'messages');
|
||||||
|
console.log('[Migration] Errors:', errorCount);
|
||||||
|
|
||||||
|
await mongoose.connection.close();
|
||||||
|
console.log('[Migration] Disconnected from MongoDB');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Migration] ❌ Error:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrateMessages();
|
||||||
@@ -11,8 +11,8 @@ const recreateTestUser = async () => {
|
|||||||
|
|
||||||
console.log('\n🔄 Подключение к MongoDB...');
|
console.log('\n🔄 Подключение к MongoDB...');
|
||||||
await mongoose.connect(mongoUri, {
|
await mongoose.connect(mongoUri, {
|
||||||
serverSelectionTimeoutMS: 5000,
|
useNewUrlParser: true,
|
||||||
connectTimeoutMS: 5000,
|
useUnifiedTopology: true,
|
||||||
});
|
});
|
||||||
console.log('✅ Подключено к MongoDB\n');
|
console.log('✅ Подключено к MongoDB\n');
|
||||||
|
|
||||||
@@ -76,6 +76,21 @@ const recreateTestUser = async () => {
|
|||||||
console.log(' Пароль: SecurePass123!');
|
console.log(' Пароль: SecurePass123!');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
// Обновить существующие mock компании
|
||||||
|
console.log('\n🔄 Обновление существующих mock компаний...');
|
||||||
|
const updates = [
|
||||||
|
{ inn: '7707083894', updates: { companySize: '51-250', partnerGeography: ['moscow', 'russia_all'] } },
|
||||||
|
{ inn: '7707083895', updates: { companySize: '500+', partnerGeography: ['moscow', 'russia_all'] } },
|
||||||
|
{ inn: '7707083896', updates: { companySize: '11-50', partnerGeography: ['moscow', 'russia_all'] } },
|
||||||
|
{ inn: '7707083897', updates: { companySize: '51-250', partnerGeography: ['moscow', 'russia_all'] } },
|
||||||
|
{ inn: '7707083898', updates: { companySize: '251-500', partnerGeography: ['moscow', 'russia_all'] } },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const item of updates) {
|
||||||
|
await Company.updateOne({ inn: item.inn }, { $set: item.updates });
|
||||||
|
console.log(` ✓ Компания обновлена: INN ${item.inn}`);
|
||||||
|
}
|
||||||
|
|
||||||
await mongoose.connection.close();
|
await mongoose.connection.close();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
61
server/routers/procurement/scripts/test-logging.js
Normal file
61
server/routers/procurement/scripts/test-logging.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Скрипт для тестирования логирования
|
||||||
|
*
|
||||||
|
* Использование:
|
||||||
|
* node stubs/scripts/test-logging.js # Логи скрыты (DEV не установлена)
|
||||||
|
* DEV=true node stubs/scripts/test-logging.js # Логи видны
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Функция логирования из маршрутов
|
||||||
|
const log = (message, data = '') => {
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
if (data) {
|
||||||
|
console.log(message, data);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log('TEST: Логирование с переменной окружения DEV');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
console.log('Значение DEV:', process.env.DEV || '(не установлена)');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Тестируем различные логи
|
||||||
|
log('[Auth] Token verified - userId: 68fe2ccda3526c303ca06799 companyId: 68fe2ccda3526c303ca06796');
|
||||||
|
log('[Auth] Generating token for userId:', '68fe2ccda3526c303ca06799');
|
||||||
|
log('[BuyProducts] Found', 0, 'products for company 68fe2ccda3526c303ca06796');
|
||||||
|
log('[Products] GET Fetching products for companyId:', '68fe2ccda3526c303ca06799');
|
||||||
|
log('[Products] Found', 1, 'products');
|
||||||
|
log('[Reviews] Returned', 0, 'reviews for company 68fe2ccda3526c303ca06796');
|
||||||
|
log('[Messages] Fetching threads for companyId:', '68fe2ccda3526c303ca06796');
|
||||||
|
log('[Messages] Found', 4, 'messages for company');
|
||||||
|
log('[Messages] Returned', 3, 'unique threads');
|
||||||
|
log('[Search] Request params:', { query: '', page: 1 });
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log('РЕЗУЛЬТАТ:');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
|
||||||
|
if (process.env.DEV === 'true') {
|
||||||
|
console.log('✅ DEV=true - логи ВИДНЫ выше');
|
||||||
|
} else {
|
||||||
|
console.log('❌ DEV не установлена или != "true" - логи СКРЫТЫ');
|
||||||
|
console.log('');
|
||||||
|
console.log('Для включения логов запустите:');
|
||||||
|
console.log(' export DEV=true && npm start (Linux/Mac)');
|
||||||
|
console.log(' $env:DEV = "true"; npm start (PowerShell)');
|
||||||
|
console.log(' set DEV=true && npm start (CMD)');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log('');
|
||||||
Reference in New Issue
Block a user