Описание изменений

This commit is contained in:
2025-03-18 16:35:13 +03:00
parent 513db4e206
commit 31df93e8b1
15 changed files with 1636 additions and 1073 deletions

View File

@@ -12,6 +12,7 @@ const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/aut
const authService = require('../services/auth-service');
const { SiweMessage } = require('siwe');
const { sendEmail } = require('../services/emailBot');
const { verificationCodes } = require('../services/telegramBot');
// Создайте лимитер для попыток аутентификации
const authLimiter = rateLimit({
@@ -123,12 +124,20 @@ router.post('/verify', async (req, res) => {
const { userId, isAdmin } = await findOrCreateUser(address);
console.log('User found/created:', { userId, isAdmin });
// Сохраняем guestId перед обновлением сессии
const currentGuestId = req.session.guestId;
// Устанавливаем пользователя в сессии
req.session.userId = userId;
req.session.address = address;
req.session.isAdmin = isAdmin;
req.session.authenticated = true;
// Сохраняем guestId в новой сессии
if (currentGuestId) {
req.session.guestId = currentGuestId;
}
// Сохраняем сессию ПЕРЕД отправкой ответа
await new Promise((resolve, reject) => {
req.session.save(err => {
@@ -142,12 +151,20 @@ router.post('/verify', async (req, res) => {
});
});
// Добавляем задержку для гарантии сохранения сессии (временное решение)
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Authentication successful for user:', { userId, address, isAdmin });
console.log('Authentication successful for user:', {
userId,
address,
isAdmin,
guestId: currentGuestId
});
console.log('Session after save:', req.session);
// Обрабатываем гостевые сообщения, если они есть
if (currentGuestId) {
console.log(`Processing guest messages for guestId: ${currentGuestId}`);
await authService.processGuestMessages(userId, currentGuestId);
}
return res.json({
authenticated: true,
userId,
@@ -156,8 +173,8 @@ router.post('/verify', async (req, res) => {
authType: 'wallet'
});
} catch (error) {
console.error('Error verifying signature:', error);
return res.status(500).json({ error: 'Internal server error' });
console.error('Error during wallet verification:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
@@ -604,83 +621,120 @@ router.get('/telegram/code', async (req, res) => {
}
});
// Функция для проверки кода Telegram
async function verifyTelegramCode(code) {
try {
// Используем глобальное хранилище кодов
const verificationCodes = global.verificationCodes;
if (!verificationCodes) {
return { success: false, error: 'Система верификации не инициализирована' };
}
// Ищем chatId по коду
for (const [chatId, data] of verificationCodes.entries()) {
if (data.code === code) {
// Проверяем срок действия
if (Date.now() > data.expires) {
verificationCodes.delete(chatId);
return { success: false, error: 'Код истек' };
}
// Код верный и не истек
const telegramId = chatId;
verificationCodes.delete(chatId);
return {
success: true,
telegramId: telegramId
};
}
}
return { success: false, error: 'Неверный код' };
} catch (error) {
console.error('Error in verifyTelegramCode:', error);
throw error;
}
}
// Функция для проверки баланса токенов
async function checkTokenBalance(address) {
try {
const authService = require('../services/auth-service');
const isAdmin = await authService.checkTokensAndUpdateRole(address);
return isAdmin;
} catch (error) {
console.error('Error checking token balance:', error);
return false;
}
}
// Маршрут для верификации Telegram
router.post('/telegram/verify', async (req, res) => {
const { code } = req.body;
try {
const { telegramId, code } = req.body;
const verificationData = req.session.telegramVerificationData;
const telegramBot = require('../services/telegramBot');
const result = await telegramBot.verifyCode(code);
// Проверяем, что код существует и не истек
if (!verificationData ||
verificationData.code !== code ||
Date.now() > verificationData.expires) {
return res.status(400).json({
success: false,
error: 'Неверный или истекший код подтверждения'
if (result.success) {
// Проверяем, что у нас есть telegramId
if (!result.telegramId) {
return res.status(400).json({ error: 'Invalid Telegram ID' });
}
// Создаем или находим пользователя
const userResult = await pool.query(
`INSERT INTO users (created_at)
VALUES (NOW())
RETURNING id`,
);
const userId = userResult.rows[0].id;
// Добавляем Telegram идентификатор
await pool.query(
`INSERT INTO user_identities
(user_id, identity_type, identity_value, verified, created_at)
VALUES ($1, 'telegram', $2, true, NOW())
ON CONFLICT (identity_type, identity_value)
DO UPDATE SET verified = true
RETURNING user_id`,
[userId, result.telegramId]
);
// Обновляем сессию
req.session.userId = userId;
req.session.authenticated = true;
req.session.authType = 'telegram';
req.session.telegramId = result.telegramId;
// Если есть подключенный кошелек, проверяем баланс токенов
if (req.session.address) {
const isAdmin = await checkTokenBalance(req.session.address);
req.session.isAdmin = isAdmin;
}
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save(err => {
if (err) reject(err);
else resolve();
});
});
return res.json({
success: true,
userId: userId,
telegramId: result.telegramId,
isAdmin: req.session.isAdmin || false,
authenticated: true
});
}
// Ищем или создаем пользователя с этим Telegram ID
const result = await db.query(
'SELECT * FROM find_or_create_user_by_identity($1, $2)',
['telegram', telegramId]
);
const userId = result.rows[0].user_id;
const isNew = result.rows[0].is_new;
// Проверяем, есть ли у пользователя связанный кошелек
const walletResult = await db.query(`
SELECT identity_value
FROM user_identities ui
WHERE ui.user_id = $1 AND ui.identity_type = 'wallet'
`, [userId]);
const hasWallet = walletResult.rows.length > 0;
let walletAddress = null;
let isAdmin = false;
// Если есть кошелек, проверяем наличие токенов
if (hasWallet) {
walletAddress = walletResult.rows[0].identity_value;
const userResult = await db.query('SELECT is_admin FROM users WHERE id = $1', [userId]);
isAdmin = userResult.rows[0].is_admin;
}
// Устанавливаем сессию
req.session.authenticated = true;
req.session.userId = userId;
req.session.authType = 'telegram';
req.session.telegramId = telegramId;
req.session.isAdmin = isAdmin;
if (walletAddress) {
req.session.address = walletAddress;
}
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save(err => {
if (err) reject(err);
else resolve();
});
});
// Очищаем данные верификации
delete req.session.telegramVerificationData;
res.json({
success: true,
authenticated: true,
userId,
telegramId,
isAdmin,
hasWallet,
walletAddress,
isNew
});
res.status(400).json({ error: result.error || 'Invalid verification code' });
} catch (error) {
logger.error(`Error in telegram verification: ${error.message}`);
res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
console.error('Error in telegram verification:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
@@ -1019,7 +1073,7 @@ router.get('/email/auth-status/:token', async (req, res) => {
}
});
// Маршрут для прямой проверки кода, введенного пользователем
// Маршрут для проверки кода, введенного пользователем
router.post('/email/verify-code', async (req, res) => {
try {
const { email, code } = req.body;
@@ -1027,19 +1081,19 @@ router.post('/email/verify-code', async (req, res) => {
if (!email || !code) {
return res.status(400).json({ success: false, error: 'Email и код обязательны' });
}
const EmailBotService = require('../services/emailBot');
const emailBot = new EmailBotService(process.env.EMAIL_USER, process.env.EMAIL_PASSWORD);
// Проверяем код из хранилища
const verificationData = emailBot.verificationCodes.get(email.toLowerCase());
const verificationData = EmailBotService.verificationCodes.get(email.toLowerCase());
if (!verificationData) {
return res.status(400).json({ success: false, error: 'Код подтверждения не найден' });
}
if (Date.now() > verificationData.expires) {
emailBot.verificationCodes.delete(email.toLowerCase());
EmailBotService.verificationCodes.delete(email.toLowerCase());
return res.status(400).json({ success: false, error: 'Срок действия кода истек' });
}
@@ -1083,21 +1137,8 @@ router.post('/email/verify-code', async (req, res) => {
req.session.email = email.toLowerCase();
req.session.authType = 'email';
// Если был временный ID, удаляем его
if (req.session.tempUserId) {
delete req.session.tempUserId;
}
// Сохраняем сессию
await new Promise((resolve, reject) => {
req.session.save(err => {
if (err) reject(err);
else resolve();
});
});
// Удаляем код из хранилища
emailBot.verificationCodes.delete(email.toLowerCase());
EmailBotService.verificationCodes.delete(email.toLowerCase());
return res.json({
success: true,
@@ -1107,9 +1148,26 @@ router.post('/email/verify-code', async (req, res) => {
});
} catch (error) {
logger.error(`Error verifying email code: ${error.message}`);
console.error('Error verifying email code:', error);
return res.status(500).json({ success: false, error: 'Ошибка сервера' });
}
});
// Маршрут для очистки сессии
router.post('/clear-session', async (req, res) => {
try {
// Очищаем все данные сессии
req.session.destroy((err) => {
if (err) {
console.error('Error destroying session:', err);
return res.status(500).json({ error: 'Internal server error' });
}
res.json({ success: true });
});
} catch (error) {
console.error('Error clearing session:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;

View File

@@ -6,6 +6,7 @@ const db = require('../db');
const { requireAuth, requireAdmin } = require('../middleware/auth');
const logger = require('../utils/logger');
const crypto = require('crypto');
const { saveGuestMessageToDatabase } = require('../db');
// Добавьте эту функцию в начало файла chat.js
async function getAIResponse(message, language = 'ru') {
@@ -66,149 +67,147 @@ async function getAIResponse(message, language = 'ru') {
}
}
// Обработчик сообщений чата
router.post('/message', requireAuth, async (req, res) => {
console.log('Сессия в /api/chat/message:', req.session);
console.log('Аутентифицирован:', req.session.authenticated);
// Функция для обработки гостевых сообщений после аутентификации
async function processGuestMessages(userId, guestId) {
try {
const { message, language = 'ru' } = req.body;
const userId = typeof req.session.userId === 'object'
? req.session.userId.userId
: req.session.userId;
console.log(`Получено сообщение: ${message}, язык: ${language}, userId: ${userId}`);
console.log(`Starting to process guest messages for user ${userId} with guestId ${guestId}`);
// Проверяем, что userId существует
if (!userId) {
return res.status(400).json({ error: 'User ID is required' });
}
// Получаем все гостевые сообщения
const guestMessages = await db.query(
`SELECT m.id, m.content, m.conversation_id, m.metadata, m.created_at
FROM messages m
WHERE m.metadata->>'guest_id' = $1
ORDER BY m.created_at ASC`,
[guestId]
);
// Определяем язык сообщения, если не указан явно
let detectedLanguage = language;
if (!language || language === 'auto') {
// Простая эвристика для определения языка
const cyrillicPattern = /[а-яА-ЯёЁ]/;
detectedLanguage = cyrillicPattern.test(message) ? 'ru' : 'en';
}
console.log(`Found ${guestMessages.rows.length} guest messages to process`);
// Формируем системный промпт в зависимости от языка
let systemPrompt = '';
if (detectedLanguage === 'ru') {
systemPrompt = 'Вы - полезный ассистент. Отвечайте на русском языке.';
} else {
systemPrompt = 'You are a helpful assistant. Respond in English.';
}
// Обновляем user_id для всех бесед с гостевыми сообщениями
await db.query(
`UPDATE conversations c
SET user_id = $1
WHERE id IN (
SELECT DISTINCT conversation_id
FROM messages m
WHERE m.metadata->>'guest_id' = $2
)`,
[userId, guestId]
);
// Отправляем запрос к Ollama с указанием языка
console.log(`Отправка запроса к Ollama (модель: ${process.env.OLLAMA_MODEL || 'mistral'}, язык: ${detectedLanguage}): ${message}`);
// Проверяем доступность Ollama
console.log('Проверка доступности Ollama...');
try {
const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/tags`);
const data = await response.json();
console.log('Ollama доступен. Доступные модели:');
data.models.forEach(model => {
console.log(`- ${model.name}`);
});
} catch (error) {
console.error('Ошибка при проверке доступности Ollama:', error);
return res.status(500).json({ error: 'Сервис Ollama недоступен' });
}
// Создаем экземпляр ChatOllama
const chat = new ChatOllama({
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
model: process.env.OLLAMA_MODEL || 'mistral',
system: systemPrompt
});
console.log('Отправка запроса к Ollama...');
// Получаем ответ от модели
let aiResponse;
try {
const response = await chat.invoke(message);
aiResponse = response.content;
console.log('Ответ AI:', aiResponse);
} catch (error) {
console.error('Ошибка при вызове ChatOllama:', error);
// Обрабатываем каждое гостевое сообщение
for (const msg of guestMessages.rows) {
console.log(`Processing guest message ${msg.id}: ${msg.content}`);
// Альтернативный метод запроса через прямой API
try {
console.log('Пробуем альтернативный метод запроса...');
const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: process.env.OLLAMA_MODEL || 'mistral',
prompt: message,
system: systemPrompt,
stream: false
}),
});
const data = await response.json();
aiResponse = data.response;
console.log('Ответ AI (альтернативный метод):', aiResponse);
} catch (fallbackError) {
console.error('Ошибка при использовании альтернативного метода:', fallbackError);
throw error; // Выбрасываем исходную ошибку
}
// Получаем язык из метаданных
const metadata = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata;
const language = metadata?.language || 'ru';
// Получаем ответ от AI
console.log(`Getting AI response for message ${msg.id} in ${language}`);
const aiResponse = await getAIResponse(msg.content, language);
// Сохраняем ответ AI в ту же беседу
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at)
VALUES ($1, 'assistant', $2, 'chat', NOW())`,
[msg.conversation_id, aiResponse]
);
console.log(`Saved AI response for message ${msg.id}`);
}
// Получаем или создаем диалог
let conversationId;
const conversationResult = await db.query(`
SELECT id FROM conversations
WHERE user_id = $1
ORDER BY updated_at DESC
LIMIT 1
`, [userId]);
console.log(`Successfully processed all guest messages for user ${userId}`);
return true;
} catch (error) {
console.error('Error processing guest messages:', error);
return false;
}
}
if (conversationResult.rows.length === 0) {
// Создаем новый диалог
const newConversationResult = await db.query(`
INSERT INTO conversations (user_id, created_at, updated_at)
VALUES ($1, NOW(), NOW())
RETURNING id
`, [userId]);
conversationId = newConversationResult.rows[0].id;
console.log('Created new conversation:', conversationId);
} else {
conversationId = conversationResult.rows[0].id;
console.log('Using existing conversation:', conversationId);
}
// Обработчик для гостевых сообщений
router.post('/guest-message', async (req, res) => {
const { message, language } = req.body;
// Генерируем временный ID сессии, если его нет
if (!req.session.guestId) {
req.session.guestId = crypto.randomBytes(16).toString('hex');
}
try {
// Создаем запись в conversations для гостя
const conversationResult = await db.query(
`INSERT INTO conversations (created_at)
VALUES (NOW())
RETURNING id`
);
const conversationId = conversationResult.rows[0].id;
// Создаем метаданные
const metadata = {
guest_id: req.session.guestId,
language: language || 'en'
};
// Сохраняем только сообщение пользователя
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, metadata, created_at)
VALUES ($1, 'guest', $2, 'chat', $3, NOW())`,
[
conversationId,
message,
JSON.stringify(metadata)
]
);
res.json({ success: true });
} catch (error) {
console.error('Error processing message:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Маршрут для обычных сообщений (для аутентифицированных пользователей)
router.post('/message', requireAuth, async (req, res) => {
const { message, language } = req.body;
const userId = req.session.userId;
try {
// Создаем новую беседу или получаем существующую
const conversationResult = await db.query(
`INSERT INTO conversations (user_id, created_at)
VALUES ($1, NOW())
RETURNING id`,
[userId]
);
const conversationId = conversationResult.rows[0].id;
// Сохраняем сообщение пользователя
const userMessageResult = await db.query(`
INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel, created_at)
VALUES ($1, 'user', $2, $3, 'web', NOW())
RETURNING id
`, [conversationId, userId, message]);
console.log('Saved user message:', userMessageResult.rows[0].id);
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at)
VALUES ($1, 'user', $2, 'chat', NOW())`,
[conversationId, message]
);
// Сохраняем ответ ИИ
const aiMessageResult = await db.query(`
INSERT INTO messages (conversation_id, sender_type, content, channel, created_at)
VALUES ($1, 'ai', $2, 'web', NOW())
RETURNING id
`, [conversationId, aiResponse]);
console.log('Saved AI message:', aiMessageResult.rows[0].id);
// Получаем ответ от AI
const aiResponse = await getAIResponse(message, language);
// Обновляем время последнего сообщения в диалоге
await db.query(`
UPDATE conversations
SET updated_at = NOW()
WHERE id = $1
`, [conversationId]);
// Сохраняем ответ AI
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at)
VALUES ($1, 'assistant', $2, 'chat', NOW())`,
[conversationId, aiResponse]
);
res.json({
reply: aiResponse,
language: detectedLanguage
success: true,
message: aiResponse
});
} catch (error) {
console.error('Error processing message:', error);
@@ -233,82 +232,49 @@ router.get('/models', async (req, res) => {
});
// Получение истории сообщений
router.get('/history', requireAuth, async (req, res) => {
router.get('/history', async (req, res) => {
const limit = parseInt(req.query.limit) || 2; // По умолчанию только последнее сообщение и ответ
const offset = parseInt(req.query.offset) || 0;
try {
// Получаем ID пользователя из сессии или из объекта пользователя
const userId = req.session?.userId || req.user?.userId;
console.log('Запрос истории чата для пользователя:', userId);
console.log('User object from request:', req.user);
// Проверяем, что userId существует
if (!userId) {
console.error('Пользователь не аутентифицирован');
if (!req.session.authenticated || !req.session.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Получаем историю сообщений из базы данных
console.log('Querying chat history for user:', userId);
// Проверяем, существует ли таблица messages
try {
const tableCheck = await db.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'messages'
);
`);
console.log('Table messages exists:', tableCheck.rows[0].exists);
if (tableCheck.rows[0].exists) {
// Используем таблицу messages
const result = await db.query(`
SELECT m.*, c.user_id
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1
ORDER BY m.created_at ASC
`, [userId]);
console.log(`Найдено ${result.rows.length} сообщений для пользователя ${userId}`);
return res.json({ messages: result.rows });
} else {
// Проверяем, существует ли таблица chat_history
const chatHistoryCheck = await db.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'chat_history'
);
`);
console.log('Table chat_history exists:', chatHistoryCheck.rows[0].exists);
if (chatHistoryCheck.rows[0].exists) {
// Используем таблицу chat_history
const result = await db.query(`
SELECT * FROM chat_history
WHERE user_id = $1
ORDER BY created_at ASC
`, [userId]);
console.log(`Найдено ${result.rows.length} сообщений для пользователя ${userId}`);
return res.json({ messages: result.rows });
} else {
// Ни одна из таблиц не существует
console.log('No message tables found in database');
return res.json({ messages: [] });
}
}
} catch (error) {
console.error('Error checking tables:', error);
return res.json({ messages: [] });
}
// Получаем общее количество сообщений
const countResult = await db.query(
`SELECT COUNT(*) as total
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1
OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true')`,
[req.session.userId, req.session.guestId]
);
const total = parseInt(countResult.rows[0].total);
// Получаем сообщения с пагинацией
const result = await db.query(
`SELECT m.id, m.content, m.sender_type as role, m.created_at,
c.user_id, m.metadata
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1
OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true')
ORDER BY m.created_at DESC
LIMIT $3 OFFSET $4`,
[req.session.userId, req.session.guestId, limit, offset]
);
return res.json({
success: true,
messages: result.rows.reverse(),
total
});
} catch (error) {
console.error('Error fetching chat history:', error);
res.status(500).json({ error: 'Internal server error' });
console.error('Error getting chat history:', error);
return res.status(500).json({ error: 'Internal server error' });
}
});
@@ -345,39 +311,6 @@ router.get('/admin/history', requireAdmin, async (req, res) => {
}
});
// Обработчик для гостевых сообщений
router.post('/guest-message', async (req, res) => {
try {
const { message, language } = req.body;
console.log(`Получено гостевое сообщение: ${message} язык: ${language}`);
// Генерируем временный ID сессии, если его нет
if (!req.session.guestId) {
req.session.guestId = crypto.randomBytes(16).toString('hex');
}
// Сохраняем сообщение в базе данных с временным ID
await db.query(`
INSERT INTO guest_messages (guest_id, content, language, created_at)
VALUES ($1, $2, $3, NOW())
`, [req.session.guestId, message, language]);
// Отправляем запрос к AI
const aiResponse = await getAIResponse(message, language);
// Сохраняем ответ AI в базе данных
await db.query(`
INSERT INTO guest_messages (guest_id, content, language, created_at, is_ai)
VALUES ($1, $2, $3, NOW(), true)
`, [req.session.guestId, aiResponse, language]);
return res.json({ message: aiResponse, reply: aiResponse });
} catch (error) {
console.error('Error processing guest message:', error);
return res.status(500).json({ error: 'Internal server error' });
}
});
// Обработчик для связывания гостевых сообщений с пользователем
router.post('/link-guest-messages', requireAuth, async (req, res) => {
try {
@@ -413,4 +346,64 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => {
}
});
// Обновляем маршрут верификации кошелька
router.post('/verify', async (req, res) => {
const { address, signature, message } = req.body;
try {
// ... существующий код верификации ...
// После успешной верификации и создания пользователя
if (req.session.guestId) {
console.log('Found guest messages, processing...');
await processGuestMessages(userId, req.session.guestId);
}
// Сохраняем данные в сессии
req.session.userId = userId;
req.session.address = address;
req.session.isAdmin = isAdmin;
req.session.authenticated = true;
console.log('Authentication successful for user:', {
userId,
address,
isAdmin,
guestId: req.session.guestId
});
res.json({
authenticated: true,
userId: userId,
address: address,
isAdmin: isAdmin,
authType: 'wallet'
});
} catch (error) {
console.error('Error during wallet verification:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Обновляем маршрут верификации Telegram
router.post('/auth/telegram/verify', async (req, res) => {
// ... существующий код ...
if (result.success) {
// Если есть гостевые сообщения, обрабатываем их
if (req.session.guestId) {
await processGuestMessages(userId, req.session.guestId);
}
res.json({
success: true,
userId: userId,
telegramId: result.telegramId,
isAdmin: req.session.isAdmin || false,
authenticated: true
});
}
});
module.exports = router;