Описание изменений
This commit is contained in:
@@ -25,10 +25,25 @@ const query = (text, params) => {
|
||||
return pool.query(text, params);
|
||||
};
|
||||
|
||||
// Функция для сохранения гостевого сообщения в базе данных
|
||||
async function saveGuestMessageToDatabase(message, language, guestId) {
|
||||
try {
|
||||
await query(`
|
||||
INSERT INTO guest_messages (guest_id, content, language, created_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
`, [guestId, message, language]);
|
||||
console.log('Гостевое сообщение успешно сохранено:', message);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при сохранении гостевого сообщения:', error);
|
||||
throw error; // Пробрасываем ошибку дальше
|
||||
}
|
||||
}
|
||||
|
||||
// Экспортируем функции для работы с базой данных
|
||||
module.exports = {
|
||||
query,
|
||||
pool,
|
||||
saveGuestMessageToDatabase,
|
||||
};
|
||||
|
||||
// Функция для создания временного хранилища данных в памяти
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const db = require('../db');
|
||||
const logger = require('../utils/logger');
|
||||
const { ethers } = require('ethers');
|
||||
const { processMessage } = require('./ai-assistant'); // Используем AI Assistant
|
||||
|
||||
// В начале файла auth-service.js
|
||||
const getProvider = (network) => {
|
||||
@@ -246,6 +247,108 @@ class AuthService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает гостевые сообщения после аутентификации
|
||||
*/
|
||||
async processGuestMessages(userId, guestId) {
|
||||
try {
|
||||
logger.info(`Processing guest messages for user ${userId} with guestId ${guestId}`);
|
||||
|
||||
// Сначала обновляем 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]
|
||||
);
|
||||
|
||||
// Получаем все гостевые сообщения без ответов
|
||||
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
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM messages
|
||||
WHERE conversation_id = m.conversation_id
|
||||
AND sender_type = 'assistant'
|
||||
)
|
||||
ORDER BY m.created_at ASC`,
|
||||
[guestId]
|
||||
);
|
||||
|
||||
logger.info(`Found ${guestMessages.rows.length} unprocessed guest messages`);
|
||||
|
||||
// Обрабатываем каждое гостевое сообщение
|
||||
for (const msg of guestMessages.rows) {
|
||||
logger.info(`Processing guest message ${msg.id}: ${msg.content}`);
|
||||
|
||||
// Получаем язык из метаданных
|
||||
const metadata = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata;
|
||||
const language = metadata?.language || 'ru';
|
||||
|
||||
// Используем AI Assistant для обработки сообщения
|
||||
const aiResponse = await processMessage(userId, 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]
|
||||
);
|
||||
|
||||
logger.info(`Saved AI response for message ${msg.id}`);
|
||||
}
|
||||
|
||||
// Обновляем метаданные сообщений, чтобы показать, что они обработаны
|
||||
await db.query(
|
||||
`UPDATE messages m
|
||||
SET metadata = jsonb_set(
|
||||
CASE
|
||||
WHEN m.metadata IS NULL THEN '{}'::jsonb
|
||||
ELSE m.metadata::jsonb
|
||||
END,
|
||||
'{processed}',
|
||||
'true'
|
||||
)
|
||||
WHERE m.metadata->>'guest_id' = $1`,
|
||||
[guestId]
|
||||
);
|
||||
|
||||
logger.info(`Successfully processed all guest messages for user ${userId}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Error processing guest messages:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
try {
|
||||
// Очищаем состояние аутентификации
|
||||
this.isAuthenticated = false;
|
||||
this.userId = null;
|
||||
this.address = null;
|
||||
this.isAdmin = false;
|
||||
this.authType = null;
|
||||
|
||||
// Очищаем сессию
|
||||
localStorage.removeItem('auth');
|
||||
|
||||
// Очищаем guestId
|
||||
localStorage.removeItem('guestId');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Error during disconnect:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuthService();
|
||||
@@ -1,273 +1,218 @@
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const logger = require('../utils/logger');
|
||||
const { pool } = require('../db');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Создаем бота
|
||||
const token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
let bot = null;
|
||||
|
||||
// Добавим хранилище для кодов подтверждения
|
||||
const verificationCodes = new Map(); // Формат: { telegramId: { code: '123456', token: 'auth_token', expires: timestamp } }
|
||||
/**
|
||||
* Функция для отправки кода подтверждения
|
||||
*/
|
||||
async function sendVerificationCode(chatId) {
|
||||
try {
|
||||
// Генерируем код и токен
|
||||
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
const authToken = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
// Создаем пользователя и сохраняем код в базу данных
|
||||
const result = await pool.query(
|
||||
`WITH new_user AS (
|
||||
INSERT INTO users (created_at)
|
||||
VALUES (NOW())
|
||||
RETURNING id
|
||||
)
|
||||
INSERT INTO telegram_auth_tokens
|
||||
(user_id, token, verification_code, telegram_id, expires_at)
|
||||
VALUES (
|
||||
(SELECT id FROM new_user),
|
||||
$1, $2, $3,
|
||||
NOW() + INTERVAL '5 minutes'
|
||||
)
|
||||
RETURNING user_id`,
|
||||
[authToken, code, chatId.toString()]
|
||||
);
|
||||
|
||||
// Отправляем код с инлайн-кнопкой
|
||||
const sentMessage = await bot.sendMessage(chatId,
|
||||
'Привет! Я бот для аутентификации в DApp for Business.\n\n' +
|
||||
'🔐 Ваш код подтверждения:\n\n' +
|
||||
`<code>${code}</code>\n\n` +
|
||||
'Введите этот код на сайте для завершения авторизации.\n' +
|
||||
'Код действителен в течение 5 минут.',
|
||||
{
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔄 Получить новый код', callback_data: 'new_code' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Удаляем сообщение через 30 секунд
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await bot.deleteMessage(chatId, sentMessage.message_id);
|
||||
await bot.sendMessage(chatId,
|
||||
'Для получения нового кода используйте команду /start или меню команд',
|
||||
{
|
||||
reply_markup: {
|
||||
keyboard: [
|
||||
[{ text: '/start' }]
|
||||
],
|
||||
resize_keyboard: true,
|
||||
persistent: true
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error deleting message:', error);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
return { code, token: authToken, userId: result.rows[0].user_id };
|
||||
} catch (error) {
|
||||
console.error('Error sending verification code:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция для проверки кода
|
||||
*/
|
||||
async function verifyCode(code) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT token, telegram_id, user_id
|
||||
FROM telegram_auth_tokens
|
||||
WHERE verification_code = $1
|
||||
AND expires_at > NOW()
|
||||
AND NOT used`,
|
||||
[code]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return { success: false, error: 'Неверный или истекший код' };
|
||||
}
|
||||
|
||||
const { token, telegram_id, user_id } = result.rows[0];
|
||||
|
||||
// Помечаем токен как использованный
|
||||
await pool.query(
|
||||
'UPDATE telegram_auth_tokens SET used = true WHERE token = $1',
|
||||
[token]
|
||||
);
|
||||
|
||||
// Добавляем Telegram ID в таблицу идентификаторов
|
||||
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`,
|
||||
[user_id, telegram_id]
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
telegramId: telegram_id,
|
||||
userId: user_id
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error verifying code:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация Telegram бота
|
||||
* @returns {Object|null} - Объект с методами для работы с ботом или null, если инициализация не удалась
|
||||
*/
|
||||
function initTelegramBot() {
|
||||
if (!token) {
|
||||
console.warn('TELEGRAM_BOT_TOKEN not set, Telegram integration disabled');
|
||||
console.warn('TELEGRAM_BOT_TOKEN not set');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Создаем бота с опцией polling
|
||||
bot = new TelegramBot(token, { polling: true });
|
||||
// Создаем бота с опцией обработки ошибок
|
||||
bot = new TelegramBot(token, {
|
||||
polling: {
|
||||
autoStart: true,
|
||||
params: {
|
||||
timeout: 10
|
||||
}
|
||||
},
|
||||
request: {
|
||||
timeout: 30000, // увеличиваем таймаут до 30 секунд
|
||||
proxy: process.env.HTTPS_PROXY // используем прокси если есть
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Telegram bot initialized');
|
||||
|
||||
// Регистрируем обработчики событий
|
||||
registerHandlers();
|
||||
// Очищаем все предыдущие обработчики
|
||||
bot.removeAllListeners();
|
||||
|
||||
// Устанавливаем команды бота с обработкой ошибок
|
||||
bot.setMyCommands([
|
||||
{ command: '/start', description: 'Получить код подтверждения' },
|
||||
{ command: '/help', description: 'Показать справку' }
|
||||
]).catch(error => {
|
||||
console.warn('Error setting bot commands:', error);
|
||||
// Продолжаем работу даже если не удалось установить команды
|
||||
});
|
||||
|
||||
// Обработчик команды /start
|
||||
bot.onText(/\/start/, async (msg) => {
|
||||
const chatId = msg.chat.id;
|
||||
try {
|
||||
await sendVerificationCode(chatId);
|
||||
} catch (error) {
|
||||
console.error('Error handling /start:', error);
|
||||
await bot.sendMessage(chatId, 'Произошла ошибка. Пожалуйста, попробуйте позже.')
|
||||
.catch(err => console.error('Error sending error message:', err));
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик ошибок polling
|
||||
bot.on('polling_error', (error) => {
|
||||
console.error('Telegram bot polling error:', error);
|
||||
// Перезапускаем polling при ошибке
|
||||
setTimeout(() => {
|
||||
try {
|
||||
bot.startPolling();
|
||||
} catch (e) {
|
||||
console.error('Error restarting polling:', e);
|
||||
}
|
||||
}, 10000); // пробуем перезапустить через 10 секунд
|
||||
});
|
||||
|
||||
// Обработчик остановки polling
|
||||
bot.on('stop', () => {
|
||||
console.log('Bot polling stopped');
|
||||
// Пробуем перезапустить
|
||||
setTimeout(() => {
|
||||
try {
|
||||
bot.startPolling();
|
||||
} catch (e) {
|
||||
console.error('Error restarting polling after stop:', e);
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
return bot;
|
||||
|
||||
return {
|
||||
bot,
|
||||
sendMessage: (chatId, text) => bot.sendMessage(chatId, text)
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error initializing Telegram bot:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация обработчиков событий для бота
|
||||
*/
|
||||
function registerHandlers() {
|
||||
// Обработчик /start
|
||||
bot.onText(/\/start(.*)/, async (msg, match) => {
|
||||
const chatId = msg.chat.id;
|
||||
const param = match[1] ? match[1].trim() : '';
|
||||
|
||||
console.log(`Получена команда /start с параметром: "${param}" от пользователя ${chatId}`);
|
||||
|
||||
if (param.startsWith('auth_')) {
|
||||
// Это токен авторизации через deep link
|
||||
const authToken = param.replace('auth_', '');
|
||||
console.log(`Обработка токена авторизации: ${authToken}`);
|
||||
|
||||
try {
|
||||
// Проверяем, существует ли токен
|
||||
const { pool } = require('../db');
|
||||
const tokenResult = await pool.query(
|
||||
'SELECT user_id, expires_at FROM telegram_auth_tokens WHERE token = $1',
|
||||
[authToken]
|
||||
);
|
||||
|
||||
if (tokenResult.rows.length === 0 || new Date(tokenResult.rows[0].expires_at) < new Date()) {
|
||||
bot.sendMessage(chatId, '❌ Недействительный или истекший токен авторизации.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Генерируем код подтверждения
|
||||
const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-значный код
|
||||
|
||||
// Сохраняем в хранилище
|
||||
verificationCodes.set(chatId.toString(), {
|
||||
code: verificationCode,
|
||||
token: authToken,
|
||||
expires: Date.now() + 5 * 60 * 1000 // Срок действия 5 минут
|
||||
});
|
||||
|
||||
// Отправляем код пользователю
|
||||
bot.sendMessage(chatId,
|
||||
'🔐 Для завершения связывания аккаунта, пожалуйста, введите этот код:\n\n' +
|
||||
`<code>${verificationCode}</code>\n\n` +
|
||||
'Код действителен в течение 5 минут.',
|
||||
{ parse_mode: 'HTML' }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error processing auth token:', error);
|
||||
bot.sendMessage(chatId, '❌ Произошла ошибка при обработке запроса авторизации.');
|
||||
}
|
||||
} else {
|
||||
// Получаем последний активный токен для этого чата, если есть
|
||||
const { pool } = require('../db');
|
||||
try {
|
||||
const lastTokenResult = await pool.query(`
|
||||
SELECT token FROM telegram_auth_tokens
|
||||
WHERE expires_at > NOW() AND used = FALSE
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
`);
|
||||
|
||||
if (lastTokenResult.rows.length > 0) {
|
||||
const authToken = lastTokenResult.rows[0].token;
|
||||
|
||||
// Генерируем код подтверждения
|
||||
const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-значный код
|
||||
|
||||
// Сохраняем в хранилище
|
||||
verificationCodes.set(chatId.toString(), {
|
||||
code: verificationCode,
|
||||
token: authToken,
|
||||
expires: Date.now() + 5 * 60 * 1000 // Срок действия 5 минут
|
||||
});
|
||||
|
||||
// Отправляем код пользователю
|
||||
bot.sendMessage(chatId,
|
||||
'🔐 Для завершения связывания аккаунта, пожалуйста, введите этот код:\n\n' +
|
||||
`<code>${verificationCode}</code>\n\n` +
|
||||
'Код действителен в течение 5 минут.',
|
||||
{ parse_mode: 'HTML' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking last token:', error);
|
||||
}
|
||||
|
||||
// Если нет активного токена, отправляем стандартное сообщение
|
||||
bot.sendMessage(chatId,
|
||||
'Привет! Я бот для аутентификации в DApp for Business.\n\n' +
|
||||
'Для связи с вашим аккаунтом используйте кнопку на сайте.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для проверки кода подтверждения
|
||||
bot.on('message', async (msg) => {
|
||||
const chatId = msg.chat.id;
|
||||
const text = msg.text;
|
||||
|
||||
// Игнорируем команды
|
||||
if (text && text.startsWith('/')) return;
|
||||
|
||||
// Проверяем, есть ли ожидающая верификация для этого чата
|
||||
const verificationData = verificationCodes.get(chatId.toString());
|
||||
|
||||
if (verificationData && text === verificationData.code) {
|
||||
// Код верный, проверяем срок действия
|
||||
if (Date.now() > verificationData.expires) {
|
||||
bot.sendMessage(chatId, '❌ Срок действия кода истек. Пожалуйста, начните процесс заново.');
|
||||
verificationCodes.delete(chatId.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Код верный и актуальный, завершаем аутентификацию
|
||||
try {
|
||||
const result = await linkTelegramAccount(chatId.toString(), verificationData.token);
|
||||
|
||||
if (result.success) {
|
||||
bot.sendMessage(chatId,
|
||||
'✅ Аутентификация успешна!\n\n' +
|
||||
'Ваш Telegram аккаунт связан с DApp for Business.\n' +
|
||||
'Теперь вы можете использовать бота для общения с системой.'
|
||||
);
|
||||
} else {
|
||||
bot.sendMessage(chatId,
|
||||
'❌ Ошибка аутентификации: ' + (result.error || 'неизвестная ошибка')
|
||||
);
|
||||
}
|
||||
|
||||
// Удаляем данные верификации
|
||||
verificationCodes.delete(chatId.toString());
|
||||
} catch (error) {
|
||||
console.error('Error completing authentication:', error);
|
||||
bot.sendMessage(chatId, '❌ Произошла ошибка при завершении аутентификации.');
|
||||
}
|
||||
} else if (verificationData) {
|
||||
// Есть ожидающая верификация, но код неверный
|
||||
bot.sendMessage(chatId, '❌ Неверный код. Пожалуйста, попробуйте еще раз.');
|
||||
} else {
|
||||
// Нет ожидающей верификации
|
||||
bot.sendMessage(chatId, 'Я могу помочь с аутентификацией. Используйте кнопку на сайте для начала процесса.');
|
||||
}
|
||||
});
|
||||
|
||||
// Добавить обработку прямых команд аутентификации
|
||||
bot.onText(/\/auth (.+)/, async (msg, match) => {
|
||||
const chatId = msg.chat.id;
|
||||
const authToken = match[1].trim();
|
||||
|
||||
console.log(`Получена прямая команда авторизации с токеном: ${authToken}`);
|
||||
|
||||
try {
|
||||
// Связываем Telegram ID с аккаунтом по токену
|
||||
const result = await linkTelegramAccount(chatId.toString(), authToken);
|
||||
console.log(`Результат связывания: ${JSON.stringify(result)}`);
|
||||
|
||||
if (result.success) {
|
||||
bot.sendMessage(chatId,
|
||||
'✅ Аутентификация успешна!\n\n' +
|
||||
'Ваш Telegram аккаунт связан с DApp for Business.\n' +
|
||||
'Теперь вы можете использовать бота для общения с системой.'
|
||||
);
|
||||
} else {
|
||||
bot.sendMessage(chatId,
|
||||
'❌ Ошибка аутентификации: ' + (result.error || 'неизвестная ошибка')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error linking telegram account:', error);
|
||||
bot.sendMessage(chatId, '❌ Произошла ошибка при связывании аккаунта.');
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка ошибок
|
||||
bot.on('polling_error', (error) => {
|
||||
logger.error(`[polling_error] ${JSON.stringify(error)}`);
|
||||
});
|
||||
|
||||
console.log('Telegram bot handlers registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Связывание Telegram ID с аккаунтом пользователя
|
||||
* @param {string} telegramId - ID пользователя в Telegram
|
||||
* @param {string} authToken - Токен авторизации
|
||||
* @returns {Promise<Object>} - Результат операции
|
||||
*/
|
||||
async function linkTelegramAccount(telegramId, authToken) {
|
||||
try {
|
||||
console.log(`Попытка связать Telegram ID ${telegramId} с токеном ${authToken}`);
|
||||
|
||||
// Здесь должен быть код для связывания через API или напрямую с БД
|
||||
const { pool } = require('../db');
|
||||
|
||||
// Проверяем токен авторизации
|
||||
const tokenResult = await pool.query(
|
||||
'SELECT user_id, expires_at FROM telegram_auth_tokens WHERE token = $1',
|
||||
[authToken]
|
||||
);
|
||||
|
||||
console.log(`Результат запроса токена: ${JSON.stringify(tokenResult.rows)}`);
|
||||
|
||||
if (tokenResult.rows.length === 0 || new Date(tokenResult.rows[0].expires_at) < new Date()) {
|
||||
console.log('Токен не найден или истек');
|
||||
return { success: false, error: 'Недействительный или истекший токен' };
|
||||
}
|
||||
|
||||
const userId = tokenResult.rows[0].user_id;
|
||||
console.log(`Найден пользователь с ID: ${userId}`);
|
||||
|
||||
// Добавляем идентификатор Telegram для пользователя
|
||||
await pool.query(
|
||||
'INSERT INTO user_identities (user_id, identity_type, identity_value, verified, created_at) ' +
|
||||
'VALUES ($1, $2, $3, true, NOW()) ' +
|
||||
'ON CONFLICT (identity_type, identity_value) ' +
|
||||
'DO UPDATE SET user_id = $1, verified = true',
|
||||
[userId, 'telegram', telegramId]
|
||||
);
|
||||
|
||||
// Отмечаем токен как использованный
|
||||
await pool.query(
|
||||
'UPDATE telegram_auth_tokens SET used = true WHERE token = $1',
|
||||
[authToken]
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error in linkTelegramAccount:', error);
|
||||
return { success: false, error: 'Внутренняя ошибка сервера' };
|
||||
}
|
||||
}
|
||||
|
||||
// Экспортируем функции
|
||||
module.exports = {
|
||||
initTelegramBot
|
||||
initTelegramBot,
|
||||
verifyCode,
|
||||
sendVerificationCode
|
||||
};
|
||||
Reference in New Issue
Block a user