619 lines
21 KiB
JavaScript
619 lines
21 KiB
JavaScript
const router = require('express').Router();
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const timer = (time = 300) => (req, res, next) => setTimeout(next, time);
|
|
|
|
// Настройка кодировки UTF-8
|
|
router.use((req, res, next) => {
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
next();
|
|
});
|
|
|
|
router.use(timer());
|
|
|
|
// Загружаем моки из JSON файлов
|
|
const loadMockData = (filename) => {
|
|
try {
|
|
const filePath = path.join(__dirname, 'mocks', filename);
|
|
|
|
// Проверяем существование файла
|
|
if (!fs.existsSync(filePath)) {
|
|
console.error(`Файл ${filename} не найден по пути: ${filePath}`);
|
|
return {};
|
|
}
|
|
|
|
const data = fs.readFileSync(filePath, 'utf8');
|
|
|
|
// Проверяем, что файл не пустой
|
|
if (!data || data.trim() === '') {
|
|
console.error(`Файл ${filename} пустой`);
|
|
return {};
|
|
}
|
|
|
|
const parsedData = JSON.parse(data);
|
|
console.log(`Успешно загружен файл ${filename}`);
|
|
return parsedData;
|
|
} catch (error) {
|
|
console.error(`Ошибка загрузки ${filename}:`, error.message);
|
|
return {};
|
|
}
|
|
};
|
|
|
|
// Загружаем все моки
|
|
const userMocks = loadMockData('user.json');
|
|
const companyMocks = loadMockData('companies.json');
|
|
const productMocks = loadMockData('products.json');
|
|
const searchMocks = loadMockData('search.json');
|
|
const authMocks = loadMockData('auth.json');
|
|
|
|
// Логируем загруженные данные для отладки
|
|
console.log('SearchMocks loaded:', searchMocks);
|
|
console.log('Suggestions:', searchMocks.suggestions);
|
|
|
|
// Вспомогательные функции для генерации динамических данных
|
|
const generateTimestamp = () => Date.now();
|
|
const generateDate = (daysAgo) => new Date(Date.now() - 86400000 * daysAgo).toISOString();
|
|
|
|
// Функция для замены плейсхолдеров в данных
|
|
const processMockData = (data) => {
|
|
// Проверяем, что данные существуют
|
|
if (data === undefined || data === null) {
|
|
console.warn('processMockData: получены undefined или null данные');
|
|
return data;
|
|
}
|
|
|
|
const timestamp = generateTimestamp();
|
|
const jsonString = JSON.stringify(data);
|
|
|
|
// Проверяем, что JSON.stringify вернул валидную строку
|
|
if (jsonString === undefined || jsonString === null) {
|
|
console.warn('processMockData: JSON.stringify вернул undefined или null');
|
|
return data;
|
|
}
|
|
|
|
const processedData = jsonString
|
|
.replace(/{{timestamp}}/g, timestamp)
|
|
.replace(/{{date-(\d+)-days?}}/g, (match, days) => generateDate(parseInt(days)))
|
|
.replace(/{{date-1-day}}/g, generateDate(1))
|
|
.replace(/{{date-2-days}}/g, generateDate(2))
|
|
.replace(/{{date-3-days}}/g, generateDate(3))
|
|
.replace(/{{date-4-days}}/g, generateDate(4))
|
|
.replace(/{{date-5-days}}/g, generateDate(5))
|
|
.replace(/{{date-6-days}}/g, generateDate(6))
|
|
.replace(/{{date-7-days}}/g, generateDate(7))
|
|
.replace(/{{date-8-days}}/g, generateDate(8))
|
|
.replace(/{{date-10-days}}/g, generateDate(10))
|
|
.replace(/{{date-12-days}}/g, generateDate(12))
|
|
.replace(/{{date-15-days}}/g, generateDate(15))
|
|
.replace(/{{date-18-days}}/g, generateDate(18))
|
|
.replace(/{{date-20-days}}/g, generateDate(20))
|
|
.replace(/{{date-21-days}}/g, generateDate(21))
|
|
.replace(/{{date-25-days}}/g, generateDate(25))
|
|
.replace(/{{date-28-days}}/g, generateDate(28))
|
|
.replace(/{{date-30-days}}/g, generateDate(30))
|
|
.replace(/{{date-35-days}}/g, generateDate(35));
|
|
|
|
try {
|
|
return JSON.parse(processedData);
|
|
} catch (error) {
|
|
console.error('processMockData: ошибка при парсинге JSON:', error);
|
|
return data; // Возвращаем исходные данные в случае ошибки
|
|
}
|
|
};
|
|
|
|
// Auth endpoints
|
|
router.post('/auth/login', (req, res) => {
|
|
const { email, password } = req.body;
|
|
|
|
if (!email || !password) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
message: authMocks?.errorMessages?.validationFailed || 'Email и пароль обязательны'
|
|
});
|
|
}
|
|
|
|
// Имитация неверных учетных данных
|
|
if (password === 'wrong') {
|
|
return res.status(401).json({
|
|
error: 'Unauthorized',
|
|
message: authMocks?.errorMessages?.invalidCredentials || 'Неверный email или пароль'
|
|
});
|
|
}
|
|
|
|
// Проверяем, что данные загружены
|
|
if (!authMocks?.mockAuthResponse) {
|
|
console.error('authMocks.mockAuthResponse не загружен');
|
|
return res.status(500).json({
|
|
error: 'Internal Server Error',
|
|
message: 'Ошибка загрузки данных аутентификации'
|
|
});
|
|
}
|
|
|
|
const authResponse = processMockData(authMocks.mockAuthResponse);
|
|
res.status(200).json(authResponse);
|
|
});
|
|
|
|
router.post('/auth/register', (req, res) => {
|
|
const { email, password, inn, agreeToTerms } = req.body;
|
|
|
|
if (!email || !password || !inn) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
message: authMocks.errorMessages?.validationFailed || 'Заполните все обязательные поля'
|
|
});
|
|
}
|
|
|
|
if (!agreeToTerms) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
message: authMocks.errorMessages?.termsRequired || 'Необходимо принять условия использования'
|
|
});
|
|
}
|
|
|
|
// Создаем нового пользователя с данными из регистрации
|
|
const newUser = {
|
|
id: 'user-' + generateTimestamp(),
|
|
email: email,
|
|
firstName: req.body.firstName || 'Иван',
|
|
lastName: req.body.lastName || 'Петров',
|
|
position: req.body.position || 'Директор'
|
|
};
|
|
|
|
const newCompany = {
|
|
id: 'company-' + generateTimestamp(),
|
|
name: req.body.fullName || companyMocks.mockCompany?.name,
|
|
inn: req.body.inn,
|
|
ogrn: req.body.ogrn || companyMocks.mockCompany?.ogrn,
|
|
fullName: req.body.fullName || companyMocks.mockCompany?.fullName,
|
|
shortName: req.body.shortName,
|
|
legalForm: req.body.legalForm || 'ООО',
|
|
industry: req.body.industry || 'Другое',
|
|
companySize: req.body.companySize || '1-10',
|
|
website: req.body.website || '',
|
|
verified: false,
|
|
rating: 0
|
|
};
|
|
|
|
res.status(201).json({
|
|
user: newUser,
|
|
company: newCompany,
|
|
tokens: {
|
|
accessToken: 'mock-access-token-' + generateTimestamp(),
|
|
refreshToken: 'mock-refresh-token-' + generateTimestamp()
|
|
}
|
|
});
|
|
});
|
|
|
|
router.post('/auth/logout', (req, res) => {
|
|
res.status(200).json({
|
|
message: authMocks.successMessages?.logoutSuccess || 'Успешный выход'
|
|
});
|
|
});
|
|
|
|
router.post('/auth/refresh', (req, res) => {
|
|
const { refreshToken } = req.body;
|
|
|
|
if (!refreshToken) {
|
|
return res.status(401).json({
|
|
error: 'Unauthorized',
|
|
message: authMocks.errorMessages?.refreshTokenRequired || 'Refresh token обязателен'
|
|
});
|
|
}
|
|
|
|
res.status(200).json({
|
|
accessToken: 'mock-access-token-refreshed-' + generateTimestamp(),
|
|
refreshToken: 'mock-refresh-token-refreshed-' + generateTimestamp()
|
|
});
|
|
});
|
|
|
|
router.get('/auth/verify-email/:token', (req, res) => {
|
|
res.status(200).json({
|
|
message: authMocks.successMessages?.emailVerified || 'Email успешно подтвержден'
|
|
});
|
|
});
|
|
|
|
router.post('/auth/request-password-reset', (req, res) => {
|
|
const { email } = req.body;
|
|
|
|
if (!email) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
message: authMocks.errorMessages?.emailRequired || 'Email обязателен'
|
|
});
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: authMocks.successMessages?.passwordResetSent || 'Письмо для восстановления пароля отправлено'
|
|
});
|
|
});
|
|
|
|
router.post('/auth/reset-password', (req, res) => {
|
|
const { token, newPassword } = req.body;
|
|
|
|
if (!token || !newPassword) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
message: authMocks.errorMessages?.validationFailed || 'Token и новый пароль обязательны'
|
|
});
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: authMocks.successMessages?.passwordResetSuccess || 'Пароль успешно изменен'
|
|
});
|
|
});
|
|
|
|
// Companies endpoints
|
|
router.get('/companies/my/stats', (req, res) => {
|
|
res.status(200).json({
|
|
profileViews: 142,
|
|
profileViewsChange: 12,
|
|
sentRequests: 8,
|
|
sentRequestsChange: 2,
|
|
receivedRequests: 15,
|
|
receivedRequestsChange: 5,
|
|
newMessages: 3,
|
|
rating: 4.5
|
|
});
|
|
});
|
|
|
|
router.get('/companies/:id', (req, res) => {
|
|
const company = processMockData(companyMocks.mockCompany);
|
|
res.status(200).json(company);
|
|
});
|
|
|
|
router.patch('/companies/:id', (req, res) => {
|
|
const updatedCompany = {
|
|
...processMockData(companyMocks.mockCompany),
|
|
...req.body,
|
|
id: req.params.id
|
|
};
|
|
|
|
res.status(200).json(updatedCompany);
|
|
});
|
|
|
|
router.get('/companies/:id/stats', (req, res) => {
|
|
res.status(200).json({
|
|
profileViews: 142,
|
|
profileViewsChange: 12,
|
|
sentRequests: 8,
|
|
sentRequestsChange: 2,
|
|
receivedRequests: 15,
|
|
receivedRequestsChange: 5,
|
|
newMessages: 3,
|
|
rating: 4.5
|
|
});
|
|
});
|
|
|
|
router.post('/companies/:id/logo', (req, res) => {
|
|
res.status(200).json({
|
|
logoUrl: 'https://via.placeholder.com/200x200/4299E1/FFFFFF?text=Logo'
|
|
});
|
|
});
|
|
|
|
router.get('/companies/check-inn/:inn', (req, res) => {
|
|
const inn = req.params.inn;
|
|
|
|
// Имитация проверки ИНН
|
|
if (inn.length !== 10 && inn.length !== 12) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
message: authMocks.errorMessages?.innValidation || 'ИНН должен содержать 10 или 12 цифр'
|
|
});
|
|
}
|
|
|
|
const mockINNData = companyMocks.mockINNData || {};
|
|
const companyData = mockINNData[inn] || {
|
|
name: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ТЕСТОВАЯ КОМПАНИЯ ' + inn + '"',
|
|
ogrn: '10277' + inn,
|
|
legal_form: 'ООО'
|
|
};
|
|
|
|
res.status(200).json({ data: companyData });
|
|
});
|
|
|
|
// Products endpoints
|
|
router.get('/products/my', (req, res) => {
|
|
const products = processMockData(productMocks.mockProducts);
|
|
res.status(200).json(products);
|
|
});
|
|
|
|
router.get('/products', (req, res) => {
|
|
const products = processMockData(productMocks.mockProducts);
|
|
res.status(200).json({
|
|
items: products,
|
|
total: products.length,
|
|
page: 1,
|
|
pageSize: 20
|
|
});
|
|
});
|
|
|
|
router.post('/products', (req, res) => {
|
|
const newProduct = {
|
|
id: 'prod-' + generateTimestamp(),
|
|
...req.body,
|
|
companyId: 'company-123',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
res.status(201).json(newProduct);
|
|
});
|
|
|
|
router.get('/products/:id', (req, res) => {
|
|
const products = processMockData(productMocks.mockProducts);
|
|
const product = products.find(p => p.id === req.params.id);
|
|
|
|
if (product) {
|
|
res.status(200).json(product);
|
|
} else {
|
|
res.status(200).json({
|
|
id: req.params.id,
|
|
name: 'Продукт ' + req.params.id,
|
|
description: 'Описание продукта',
|
|
category: 'Категория',
|
|
type: 'sell',
|
|
companyId: 'company-123',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
});
|
|
}
|
|
});
|
|
|
|
router.patch('/products/:id', (req, res) => {
|
|
const products = processMockData(productMocks.mockProducts);
|
|
const product = products.find(p => p.id === req.params.id);
|
|
|
|
const updatedProduct = {
|
|
...(product || {}),
|
|
...req.body,
|
|
id: req.params.id,
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
res.status(200).json(updatedProduct);
|
|
});
|
|
|
|
router.delete('/products/:id', (req, res) => {
|
|
res.status(204).send();
|
|
});
|
|
|
|
// Тестовый endpoint для проверки данных
|
|
router.get('/test-data', (req, res) => {
|
|
res.status(200).json({
|
|
companiesCount: companyMocks.mockCompanies?.length || 0,
|
|
suggestionsCount: searchMocks.suggestions?.length || 0,
|
|
firstCompany: companyMocks.mockCompanies?.[0] || null,
|
|
firstSuggestion: searchMocks.suggestions?.[0] || null,
|
|
allSuggestions: searchMocks.suggestions || []
|
|
});
|
|
});
|
|
router.get('/search', (req, res) => {
|
|
const {
|
|
query,
|
|
industries,
|
|
companySize,
|
|
geography,
|
|
minRating,
|
|
type,
|
|
sortBy = 'relevance',
|
|
sortOrder = 'desc',
|
|
page = 1,
|
|
limit = 20
|
|
} = req.query;
|
|
|
|
console.log('Search query:', query);
|
|
console.log('Search params:', req.query);
|
|
|
|
const companies = processMockData(companyMocks.mockCompanies);
|
|
console.log('Companies loaded:', companies.length);
|
|
console.log('First company:', companies[0]);
|
|
|
|
let filtered = [...companies];
|
|
|
|
// Поиск по тексту
|
|
if (query) {
|
|
const q = query.toLowerCase().trim();
|
|
console.log('Searching for:', q);
|
|
|
|
filtered = filtered.filter(c => {
|
|
const fullName = (c.fullName || '').toLowerCase();
|
|
const shortName = (c.shortName || '').toLowerCase();
|
|
const industry = (c.industry || '').toLowerCase();
|
|
const slogan = (c.slogan || '').toLowerCase();
|
|
const legalAddress = (c.legalAddress || '').toLowerCase();
|
|
|
|
const matches = fullName.includes(q) ||
|
|
shortName.includes(q) ||
|
|
industry.includes(q) ||
|
|
slogan.includes(q) ||
|
|
legalAddress.includes(q);
|
|
|
|
if (matches) {
|
|
console.log('Found match:', c.shortName, 'in', { fullName, shortName, industry, slogan, legalAddress });
|
|
}
|
|
|
|
return matches;
|
|
});
|
|
|
|
console.log('Filtered results:', filtered.length);
|
|
}
|
|
|
|
// Фильтр по отраслям
|
|
if (industries && industries.length > 0) {
|
|
const industriesArray = Array.isArray(industries) ? industries : [industries];
|
|
filtered = filtered.filter(c => industriesArray.includes(c.industry));
|
|
}
|
|
|
|
// Фильтр по размеру компании
|
|
if (companySize && companySize.length > 0) {
|
|
const sizeArray = Array.isArray(companySize) ? companySize : [companySize];
|
|
filtered = filtered.filter(c => sizeArray.includes(c.companySize));
|
|
}
|
|
|
|
// Фильтр по рейтингу
|
|
if (minRating) {
|
|
filtered = filtered.filter(c => c.rating >= parseFloat(minRating));
|
|
}
|
|
|
|
// Сортировка
|
|
filtered.sort((a, b) => {
|
|
let comparison = 0;
|
|
|
|
switch (sortBy) {
|
|
case 'rating':
|
|
comparison = a.rating - b.rating;
|
|
break;
|
|
case 'name':
|
|
comparison = (a.shortName || a.fullName).localeCompare(b.shortName || b.fullName);
|
|
break;
|
|
case 'relevance':
|
|
default:
|
|
// Для релевантности используем рейтинг как основной критерий
|
|
comparison = a.rating - b.rating;
|
|
break;
|
|
}
|
|
|
|
return sortOrder === 'asc' ? comparison : -comparison;
|
|
});
|
|
|
|
const total = filtered.length;
|
|
const totalPages = Math.ceil(total / limit);
|
|
const startIndex = (page - 1) * limit;
|
|
const endIndex = startIndex + parseInt(limit);
|
|
const paginatedResults = filtered.slice(startIndex, endIndex);
|
|
|
|
res.status(200).json({
|
|
companies: paginatedResults,
|
|
total,
|
|
page: parseInt(page),
|
|
totalPages
|
|
});
|
|
});
|
|
|
|
router.post('/search/ai', (req, res) => {
|
|
const { query } = req.body;
|
|
|
|
// Простая логика AI поиска на основе ключевых слов
|
|
const companies = processMockData(companyMocks.mockCompanies);
|
|
let aiResults = [...companies];
|
|
const q = query.toLowerCase();
|
|
|
|
// Определяем приоритетные отрасли на основе запроса
|
|
if (q.includes('строитель') || q.includes('строй') || q.includes('дом') || q.includes('здание')) {
|
|
aiResults = aiResults.filter(c => c.industry === 'Строительство');
|
|
} else if (q.includes('металл') || q.includes('сталь') || q.includes('производств') || q.includes('завод')) {
|
|
aiResults = aiResults.filter(c => c.industry === 'Производство');
|
|
} else if (q.includes('логистик') || q.includes('доставк') || q.includes('транспорт')) {
|
|
aiResults = aiResults.filter(c => c.industry === 'Логистика');
|
|
} else if (q.includes('торговл') || q.includes('продаж') || q.includes('снабжени')) {
|
|
aiResults = aiResults.filter(c => c.industry === 'Торговля');
|
|
} else if (q.includes('it') || q.includes('программ') || q.includes('технолог') || q.includes('софт')) {
|
|
aiResults = aiResults.filter(c => c.industry === 'IT');
|
|
} else if (q.includes('услуг') || q.includes('консалт') || q.includes('помощь')) {
|
|
aiResults = aiResults.filter(c => c.industry === 'Услуги');
|
|
}
|
|
|
|
// Сортируем по рейтингу и берем топ-5
|
|
aiResults.sort((a, b) => b.rating - a.rating);
|
|
const topResults = aiResults.slice(0, 5);
|
|
|
|
// Генерируем AI предложение
|
|
let aiSuggestion = `На основе вашего запроса "${query}" мы нашли ${topResults.length} подходящих партнеров. `;
|
|
|
|
if (topResults.length > 0) {
|
|
const industries = [...new Set(topResults.map(c => c.industry))];
|
|
aiSuggestion += `Рекомендуем обратить внимание на компании в сфере ${industries.join(', ')}. `;
|
|
aiSuggestion += `Все предложенные партнеры имеют высокий рейтинг (от ${Math.min(...topResults.map(c => c.rating)).toFixed(1)} до ${Math.max(...topResults.map(c => c.rating)).toFixed(1)}) и подтвержденный статус.`;
|
|
} else {
|
|
aiSuggestion += 'Попробуйте изменить формулировку запроса или использовать фильтры для более точного поиска.';
|
|
}
|
|
|
|
res.status(200).json({
|
|
companies: topResults,
|
|
total: topResults.length,
|
|
page: 1,
|
|
totalPages: 1,
|
|
aiSuggestion
|
|
});
|
|
});
|
|
|
|
router.get('/search/suggestions', (req, res) => {
|
|
const { q } = req.query;
|
|
|
|
const suggestions = searchMocks.suggestions || [];
|
|
console.log('Suggestions loaded:', suggestions);
|
|
console.log('Query:', q);
|
|
|
|
const filtered = q
|
|
? suggestions.filter(s => s.toLowerCase().includes(q.toLowerCase()))
|
|
: suggestions.slice(0, 10); // Показываем только первые 10 если нет запроса
|
|
|
|
console.log('Filtered suggestions:', filtered);
|
|
res.status(200).json(filtered);
|
|
});
|
|
|
|
router.get('/search/recommendations', (req, res) => {
|
|
// Динамически генерируем рекомендации на основе топовых компаний
|
|
const companies = processMockData(companyMocks.mockCompanies);
|
|
const topCompanies = companies
|
|
.filter(c => c.verified && c.rating >= 4.5)
|
|
.sort((a, b) => b.rating - a.rating)
|
|
.slice(0, 6);
|
|
|
|
const recommendations = topCompanies.map(company => ({
|
|
id: company.id,
|
|
name: company.shortName || company.fullName,
|
|
industry: company.industry,
|
|
logo: company.logo,
|
|
matchScore: Math.floor(company.rating * 20), // Конвертируем рейтинг в проценты
|
|
reason: getRecommendationReason(company, searchMocks.recommendationReasons)
|
|
}));
|
|
|
|
res.status(200).json(recommendations);
|
|
});
|
|
|
|
// Вспомогательная функция для генерации причин рекомендаций
|
|
function getRecommendationReason(company, reasons) {
|
|
return reasons?.[company.industry] || 'Проверенный партнер с высоким рейтингом';
|
|
}
|
|
|
|
router.get('/search/history', (req, res) => {
|
|
const history = processMockData(searchMocks.searchHistory);
|
|
res.status(200).json(history);
|
|
});
|
|
|
|
router.get('/search/saved', (req, res) => {
|
|
const savedSearches = processMockData(searchMocks.savedSearches);
|
|
res.status(200).json(savedSearches);
|
|
});
|
|
|
|
router.post('/search/saved', (req, res) => {
|
|
const { name, params } = req.body;
|
|
|
|
res.status(201).json({
|
|
id: 'saved-' + generateTimestamp(),
|
|
name,
|
|
params,
|
|
createdAt: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
router.delete('/search/saved/:id', (req, res) => {
|
|
res.status(204).send();
|
|
});
|
|
|
|
router.post('/search/favorites/:companyId', (req, res) => {
|
|
res.status(200).json({
|
|
message: authMocks.successMessages?.addedToFavorites || 'Добавлено в избранное'
|
|
});
|
|
});
|
|
|
|
router.delete('/search/favorites/:companyId', (req, res) => {
|
|
res.status(204).send();
|
|
});
|
|
|
|
module.exports = router; |