From f45fd2715fd0cd42d248e2d6a58b49b227fa50b5 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 3 Apr 2025 12:46:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D0=B0=D1=88=D0=B5=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../functions/link_guest_messages.sql | 37 +- backend/routes/auth.js | 910 +++++++++++++----- backend/routes/chat.js | 315 ++---- backend/server.js | 2 + backend/services/auth-service.js | 36 + backend/services/emailAuth.js | 39 +- frontend/src/composables/useAuth.js | 193 +++- frontend/src/views/HomeView.vue | 425 +++++--- 8 files changed, 1314 insertions(+), 643 deletions(-) diff --git a/backend/db/migrations/functions/link_guest_messages.sql b/backend/db/migrations/functions/link_guest_messages.sql index 0afc11b..5c37f1c 100644 --- a/backend/db/migrations/functions/link_guest_messages.sql +++ b/backend/db/migrations/functions/link_guest_messages.sql @@ -5,6 +5,8 @@ CREATE OR REPLACE FUNCTION link_guest_messages( DECLARE v_conversation_id INTEGER; v_count INTEGER; + v_first_message TEXT; + v_title TEXT; BEGIN -- Логируем входные параметры RAISE NOTICE 'Linking messages for user_id: %, guest_id: %', p_user_id, p_guest_id; @@ -15,13 +17,32 @@ BEGIN WHERE guest_id = p_guest_id; RAISE NOTICE 'Found % guest messages', v_count; + + -- Если нет гостевых сообщений, выходим + IF v_count = 0 THEN + RAISE NOTICE 'No guest messages found, exiting'; + RETURN; + END IF; + -- Получаем первое сообщение для названия беседы + SELECT content INTO v_first_message + FROM guest_messages + WHERE guest_id = p_guest_id AND NOT is_ai + ORDER BY created_at ASC + LIMIT 1; + + -- Формируем название диалога на основе первого сообщения + v_title := CASE + WHEN length(v_first_message) > 30 THEN substring(v_first_message from 1 for 30) || '...' + ELSE v_first_message + END; + -- Создаем новую беседу - INSERT INTO conversations (user_id, created_at, updated_at) - VALUES (p_user_id, NOW(), NOW()) + INSERT INTO conversations (user_id, title, created_at, updated_at) + VALUES (p_user_id, v_title, NOW(), NOW()) RETURNING id INTO v_conversation_id; - RAISE NOTICE 'Created conversation with id: %', v_conversation_id; + RAISE NOTICE 'Created conversation with id: % and title: %', v_conversation_id, v_title; -- Копируем сообщения пользователя WITH inserted_messages AS ( @@ -46,11 +67,19 @@ BEGIN created_at FROM guest_messages WHERE guest_id = p_guest_id + -- Проверка, чтобы избежать дублирования сообщений + AND NOT EXISTS ( + SELECT 1 FROM messages m + WHERE m.guest_message_id = guest_messages.id + ) ORDER BY created_at RETURNING id ) SELECT COUNT(*) INTO v_count FROM inserted_messages; - RAISE NOTICE 'Inserted % messages', v_count; + RAISE NOTICE 'Inserted % messages into conversation', v_count; + + -- НЕ удаляем гостевые сообщения, позволяем им существовать на всякий случай + -- до автоматической очистки по cron job END; $$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 5a7cb0d..1232677 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -16,6 +16,7 @@ const { ethers } = require('ethers'); const { initTelegramAuth } = require('../services/telegramBot'); const emailAuth = require('../services/emailAuth'); const verificationService = require('../services/verification-service'); +const { processGuestMessages } = require('./chat'); // Импортируем функцию обработки гостевых сообщений // Создайте лимитер для попыток аутентификации const authLimiter = rateLimit({ @@ -76,18 +77,30 @@ router.post('/verify', async (req, res) => { try { const { address, message, signature } = req.body; + logger.info(`[verify] Verifying signature for address: ${address}`); + + // Сохраняем гостевые ID до проверки + const guestId = req.session.guestId; + const previousGuestId = req.session.previousGuestId; + + logger.info(`[verify] Guest context: guestId=${guestId}, previousGuestId=${previousGuestId}`); + // Проверяем подпись const isValid = await authService.verifySignature(message, signature, address); if (!isValid) { + logger.warn(`[verify] Invalid signature for address: ${address}`); return res.status(401).json({ success: false, error: 'Invalid signature' }); } // Проверяем nonce const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [address.toLowerCase()]); if (nonceResult.rows.length === 0 || nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]) { + logger.warn(`[verify] Invalid nonce for address: ${address}`); return res.status(401).json({ success: false, error: 'Invalid nonce' }); } + logger.info(`[verify] Signature and nonce verified for address: ${address}`); + // Находим или создаем пользователя let userId, isAdmin; @@ -102,6 +115,7 @@ router.post('/verify', async (req, res) => { // Пользователь найден по кошельку userId = userResult.rows[0].id; isAdmin = userResult.rows[0].role === 'admin'; + logger.info(`[verify] Found existing user: ID=${userId}, isAdmin=${isAdmin}`); } else if (req.session.guestId) { // Проверяем, есть ли пользователь с текущим guestId const guestUserResult = await db.query(` @@ -114,12 +128,11 @@ router.post('/verify', async (req, res) => { // Используем существующего пользователя с guestId userId = guestUserResult.rows[0].id; isAdmin = guestUserResult.rows[0].role === 'admin'; + logger.info(`[verify] Found user by guestId: ID=${userId}, isAdmin=${isAdmin}`); // Добавляем идентификатор кошелька к существующему пользователю - await db.query( - 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', - [userId, 'wallet', address.toLowerCase()] - ); + await saveUserIdentity(userId, 'wallet', address.toLowerCase(), true); + logger.info(`[verify] Added wallet identity ${address.toLowerCase()} to existing user ${userId}`); } else { // Создаем нового пользователя const newUserResult = await db.query( @@ -129,18 +142,15 @@ router.post('/verify', async (req, res) => { userId = newUserResult.rows[0].id; isAdmin = false; + logger.info(`[verify] Created new user: ID=${userId}`); // Добавляем идентификатор кошелька - await db.query( - 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', - [userId, 'wallet', address.toLowerCase()] - ); + await saveUserIdentity(userId, 'wallet', address.toLowerCase(), true); + logger.info(`[verify] Added wallet identity ${address.toLowerCase()} to new user ${userId}`); // Добавляем идентификатор гостя - await db.query( - 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', - [userId, 'guest', req.session.guestId] - ); + await saveUserIdentity(userId, 'guest', req.session.guestId, true); + logger.info(`[verify] Added guest identity ${req.session.guestId} to new user ${userId}`); } } else { // Создаем нового пользователя без гостевого ID @@ -151,12 +161,19 @@ router.post('/verify', async (req, res) => { userId = newUserResult.rows[0].id; isAdmin = false; + logger.info(`[verify] Created new user without guest ID: ID=${userId}`); // Добавляем идентификатор кошелька - await db.query( - 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', - [userId, 'wallet', address.toLowerCase()] - ); + await saveUserIdentity(userId, 'wallet', address.toLowerCase(), true); + logger.info(`[verify] Added wallet identity ${address.toLowerCase()} to new user ${userId}`); + } + + // Проверяем наличие админских токенов + const adminStatus = await authService.checkAdminTokens(address.toLowerCase()); + if (adminStatus) { + isAdmin = true; + await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]); + logger.info(`[verify] Updated user ${userId} to admin role based on token check`); } // Обновляем сессию @@ -166,17 +183,23 @@ router.post('/verify', async (req, res) => { req.session.isAdmin = isAdmin; req.session.address = address.toLowerCase(); - // Сохраняем сессию + // Сохраняем сессию перед связыванием сообщений await new Promise((resolve, reject) => { - req.session.save((err) => { + req.session.save(err => { if (err) { + logger.error('[verify] Error saving session:', err); reject(err); } else { + logger.info(`[verify] Session saved successfully for user ${userId}`); resolve(); } }); }); + // Связываем гостевые сообщения с пользователем + const linkResults = await linkGuestMessagesAfterAuth(req.session, userId); + logger.info(`[verify] Guest messages linking results:`, linkResults); + // Возвращаем успешный ответ return res.json({ success: true, @@ -187,17 +210,36 @@ router.post('/verify', async (req, res) => { }); } catch (error) { - console.error('Error in /verify:', error); + logger.error('[verify] Error:', error); res.status(500).json({ success: false, error: 'Server error' }); } }); // Аутентификация через Telegram -router.post('/telegram', async (req, res) => { +router.post('/telegram/verify', async (req, res) => { try { const { telegramId, authData } = req.body; - // Здесь должна быть проверка данных от Telegram + logger.info(`[telegram/verify] Authentication request with telegramId: ${telegramId}`); + + // Сохраняем гостевые ID до проверки + const guestId = req.session.guestId; + const previousGuestId = req.session.previousGuestId; + + logger.info(`[telegram/verify] Guest context: guestId=${guestId}, previousGuestId=${previousGuestId}`); + + // Проверяем данные от Telegram + const isValid = await authService.verifyTelegramAuth(authData); + + if (!isValid) { + logger.warn(`[telegram/verify] Invalid Telegram authentication data for user ${telegramId}`); + return res.status(401).json({ + success: false, + error: 'Ошибка верификации Telegram' + }); + } + + logger.info(`[telegram/verify] Telegram authentication data verified for user ${telegramId}`); // Получаем или создаем пользователя let userId = await authService.getUserIdByIdentity('telegram', telegramId); @@ -205,34 +247,29 @@ router.post('/telegram', async (req, res) => { if (!userId) { // Создаем нового пользователя const userResult = await db.query( - 'INSERT INTO users (created_at, role_id) VALUES (NOW(), (SELECT id FROM roles WHERE name = $1)) RETURNING id', - ['user'] + 'INSERT INTO users (created_at) VALUES (NOW()) RETURNING id' ); userId = userResult.rows[0].id; - - // Добавляем идентификатор Telegram - await db.query( - 'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())', - [userId, 'telegram', telegramId] - ); + logger.info(`[telegram/verify] Created new user with ID ${userId} for Telegram user ${telegramId}`); + } else { + logger.info(`[telegram/verify] Found existing user ID ${userId} for Telegram user ${telegramId}`); } - // Проверяем связанные аккаунты - const identitiesResult = await db.query(` - SELECT identity_type, identity_value - FROM user_identities ui - WHERE user_id = $1 - `, [userId]); - const identities = identitiesResult.rows; + // Явно сохраняем идентификатор Telegram + await saveUserIdentity(userId, 'telegram', telegramId, true); + logger.info(`[telegram/verify] Saved Telegram identity ${telegramId} for user ${userId}`); - // Если есть связанный кошелек, проверяем токены - if (identities.wallet) { - await authService.checkTokensAndUpdateRole(identities.wallet); + // Если есть гостевые ID, сохраняем их + if (guestId) { + await saveUserIdentity(userId, 'guest', guestId, true); + logger.info(`[telegram/verify] Saved guest ID ${guestId} for user ${userId}`); } - // Получаем текущую роль - const isAdmin = await authService.isAdmin(userId); + if (previousGuestId && previousGuestId !== guestId) { + await saveUserIdentity(userId, 'guest', previousGuestId, true); + logger.info(`[telegram/verify] Saved previous guest ID ${previousGuestId} for user ${userId}`); + } // Устанавливаем сессию req.session.userId = userId; @@ -240,16 +277,46 @@ router.post('/telegram', async (req, res) => { req.session.authType = 'telegram'; req.session.authenticated = true; - res.json({ - authenticated: true, - userId, - isAdmin, - authType: 'telegram', - identities + // Дополнительно устанавливаем данные из Telegram, если они есть + if (authData.first_name) { + req.session.telegramFirstName = authData.first_name; + } + + if (authData.username) { + req.session.telegramUsername = authData.username; + } + + // Сохраняем сессию перед связыванием сообщений + await new Promise((resolve, reject) => { + req.session.save(err => { + if (err) { + logger.error('[telegram/verify] Error saving session:', err); + reject(err); + } else { + logger.info(`[telegram/verify] Session saved successfully for telegramId ${telegramId}, userId ${userId}`); + resolve(); + } + }); }); + + // Связываем гостевые сообщения с пользователем + const linkResults = await linkGuestMessagesAfterAuth(req.session, userId); + logger.info(`[telegram/verify] Guest messages linking results:`, linkResults); + + // Возвращаем успешный ответ + return res.json({ + success: true, + userId, + telegramId, + authenticated: true + }); + } catch (error) { - logger.error(`Telegram auth error: ${error.message}`); - res.status(500).json({ error: 'Ошибка сервера' }); + logger.error('[telegram/verify] Error:', error); + res.status(500).json({ + success: false, + error: 'Ошибка сервера при аутентификации через Telegram' + }); } }); @@ -395,6 +462,9 @@ router.post('/email/verify', async (req, res) => { }); }); + // Связываем гостевые сообщения с пользователем + await linkGuestMessagesAfterAuth(req.session, userId); + return res.json({ success: true, userId, @@ -410,98 +480,6 @@ router.post('/email/verify', async (req, res) => { } }); -// Аутентификация через Email -router.post('/email', async (req, res) => { - try { - const { email, verificationCode } = req.body; - - // Здесь должна быть проверка кода подтверждения - - // Получаем или создаем пользователя - let userId = await authService.getUserIdByIdentity('email', email); - - if (!userId) { - // Создаем нового пользователя - const userResult = await db.query( - 'INSERT INTO users (created_at, role_id) VALUES (NOW(), (SELECT id FROM roles WHERE name = $1)) RETURNING id', - ['user'] - ); - - userId = userResult.rows[0].id; - - // Добавляем идентификатор Email - await db.query( - 'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())', - [userId, 'email', email] - ); - } - - // Получаем связанные идентификаторы - const identitiesResult = await db.query(` - SELECT identity_type, identity_value - FROM user_identities ui - WHERE ui.user_id = $1 - `, [userId]); - - const identities = identitiesResult.rows; - - // Формируем объект с идентификаторами по типам - const identitiesMap = {}; - for (const identity of identities) { - identitiesMap[identity.identity_type] = identity.identity_value; - } - - // Проверяем, есть ли связанный кошелек - let isAdmin = false; - if (identitiesMap.wallet) { - // Если есть связанный кошелек, проверяем токены - const walletAddress = identitiesMap.wallet; - isAdmin = await authService.checkAdminTokens(walletAddress); - - // Обновляем статус администратора в БД, если необходимо - await db.query('UPDATE users SET is_admin = $1 WHERE id = $2', [isAdmin, userId]); - } else { - // Если нет связанного кошелька, проверяем текущий статус администратора - isAdmin = await authService.isAdmin(userId); - } - - // Устанавливаем сессию - req.session.userId = userId; - req.session.email = email; - req.session.authType = 'email'; - req.session.authenticated = true; - req.session.isAdmin = isAdmin; - - if (identitiesMap.wallet) { - req.session.address = identitiesMap.wallet; - } - - // Сохраняем сессию перед отправкой ответа - await new Promise((resolve, reject) => { - req.session.save(err => { - if (err) { - console.error('Error saving session:', err); - reject(err); - } else { - console.log('Session saved successfully'); - resolve(); - } - }); - }); - - res.json({ - authenticated: true, - userId, - isAdmin, - authType: 'email', - identities: identitiesMap - }); - } catch (error) { - logger.error(`Email auth error: ${error.message}`); - res.status(500).json({ error: 'Ошибка сервера' }); - } -}); - // Связывание аккаунтов router.post('/link-identity', async (req, res) => { try { @@ -556,31 +534,36 @@ router.post('/link-identity', async (req, res) => { // Проверка статуса аутентификации router.get('/check', async (req, res) => { try { - console.log('Сессия при проверке:', req.session); + console.log('Check auth, session data:', { + userId: req.session.userId, + authenticated: req.session.authenticated, + authType: req.session.authType, + telegramId: req.session.telegramId + }); - // Если пользователь не аутентифицирован, возвращаем базовую информацию - if (!req.session.authenticated) { + // Если пользователь не аутентифицирован + if (!req.session.authenticated || !req.session.userId) { return res.json({ - authenticated: false, - userId: null, - authType: null, - address: null, - telegramId: null, - email: null, - isAdmin: false + authenticated: false }); } // Возвращаем полную информацию об аутентифицированном пользователе - res.json({ + const userData = { authenticated: true, userId: req.session.userId, authType: req.session.authType, - address: req.session.address, - telegramId: req.session.telegramId, - email: req.session.email, isAdmin: req.session.isAdmin || false - }); + }; + + // Добавляем идентификаторы, если они есть в сессии + if (req.session.address) userData.address = req.session.address; + if (req.session.telegramId) userData.telegramId = req.session.telegramId; + if (req.session.email) userData.email = req.session.email; + + console.log('Returning auth data:', userData); + + res.json(userData); } catch (error) { console.error('Error checking auth status:', error); res.status(500).json({ error: 'Internal server error' }); @@ -613,75 +596,6 @@ router.get('/telegram', (req, res) => { res.json({ authUrl }); }); -// Обработка верификации Telegram -router.post('/telegram/verify', async (req, res) => { - try { - const { telegramId, verificationCode } = req.body; - - if (!telegramId || !verificationCode) { - return res.status(400).json({ - success: false, - error: 'Missing required fields' - }); - } - - // Проверяем верификацию через сервис - const result = await authService.verifyTelegramAuth(telegramId, verificationCode); - - if (!result.success) { - return res.status(400).json({ - success: false, - error: 'Invalid verification' - }); - } - - // Обновляем сессию - req.session.userId = result.userId; - req.session.authenticated = true; - req.session.authType = 'telegram'; - req.session.telegramId = result.telegramId; - req.session.role = result.role; - - if (result.wallet) { - req.session.address = result.wallet; - } - - // Удаляем временные данные - if (req.session.tempUserId) { - delete req.session.tempUserId; - } - - // Сохраняем сессию - await new Promise((resolve, reject) => { - req.session.save(err => { - if (err) { - logger.error('Error saving session:', err); - reject(err); - } else { - logger.info(`Session saved successfully for user ${result.userId}`); - resolve(); - } - }); - }); - - return res.json({ - success: true, - userId: result.userId, - role: result.role, - telegramId: result.telegramId, - wallet: result.wallet, - message: 'Authentication successful' - }); - - } catch (error) { - logger.error('Error in Telegram verification:', error); - res.status(500).json({ - success: false, - error: 'Internal server error' - }); - } -}); - // Маршрут для получения кода подтверждения Telegram router.get('/telegram/code', authLimiter, async (req, res) => { try { @@ -1114,11 +1028,20 @@ router.post('/email/verify-code', authLimiter, async (req, res) => { error: 'Email и код обязательны' }); } + + logger.info(`[email/verify-code] Verifying code for email: ${email}`); + + // Сохраняем гостевой ID до проверки кода + const guestId = req.session.guestId; + const previousGuestId = req.session.previousGuestId; + + logger.info(`[email/verify-code] Guest context: guestId=${guestId}, previousGuestId=${previousGuestId}`); // Проверяем код через сервис верификации const result = await verificationService.verifyCode(code, 'email', email.toLowerCase()); if (!result.success) { + logger.warn(`[email/verify-code] Invalid code for email ${email}: ${result.error}`); return res.status(400).json({ success: false, error: result.error || 'Неверный код подтверждения' @@ -1126,15 +1049,21 @@ router.post('/email/verify-code', authLimiter, async (req, res) => { } const userId = result.userId; + logger.info(`[email/verify-code] Code verified successfully for email ${email}, userId: ${userId}`); - // Добавляем email в базу данных - await db.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, 'email', email.toLowerCase()] - ); + // Явно сохраняем email в таблицу user_identities + await saveUserIdentity(userId, 'email', email.toLowerCase(), true); + + // Если есть гостевые ID, сохраняем их до установки аутентификации + if (guestId) { + await saveUserIdentity(userId, 'guest', guestId, true); + logger.info(`[email/verify-code] Saved guest ID ${guestId} for user ${userId}`); + } + + if (previousGuestId && previousGuestId !== guestId) { + await saveUserIdentity(userId, 'guest', previousGuestId, true); + logger.info(`[email/verify-code] Saved previous guest ID ${previousGuestId} for user ${userId}`); + } // Устанавливаем аутентификацию пользователя req.session.authenticated = true; @@ -1147,6 +1076,23 @@ router.post('/email/verify-code', authLimiter, async (req, res) => { delete req.session.tempUserId; } + // Сохраняем сессию перед связыванием сообщений + await new Promise((resolve, reject) => { + req.session.save(err => { + if (err) { + logger.error('[email/verify-code] Error saving session:', err); + reject(err); + } else { + logger.info(`[email/verify-code] Session saved successfully for user ${userId}`); + resolve(); + } + }); + }); + + // Связываем гостевые сообщения с пользователем + const linkResults = await linkGuestMessagesAfterAuth(req.session, userId); + logger.info(`[email/verify-code] Guest messages linking results:`, linkResults); + return res.json({ success: true, userId, @@ -1155,7 +1101,7 @@ router.post('/email/verify-code', authLimiter, async (req, res) => { }); } catch (error) { - logger.error('Error verifying email code:', error); + logger.error('[email/verify-code] Error:', error); return res.status(500).json({ success: false, error: 'Ошибка сервера' @@ -1265,6 +1211,9 @@ router.post('/email/init', async (req, res) => { }); }); + // Связываем гостевые сообщения с пользователем + await linkGuestMessagesAfterAuth(req.session, userId); + return res.json({ success: true, message: 'Код верификации отправлен на email' @@ -1296,10 +1245,9 @@ router.post('/email/verify', requireAuth, async (req, res) => { }); // Проверка кода верификации email -router.all('/check-email-verification', async (req, res) => { +router.post('/email/check-verification', async (req, res) => { try { - // Получаем код из query параметров (GET) или тела запроса (POST) - const code = req.method === 'POST' ? req.body.code : req.query.code; + const { code } = req.body; if (!code) { return res.status(400).json({ @@ -1308,10 +1256,17 @@ router.all('/check-email-verification', async (req, res) => { }); } + // Сохраняем гостевой ID до проверки кода + const guestId = req.session.guestId; + const previousGuestId = req.session.previousGuestId; + + logger.info(`[email/check-verification] Checking verification with code ${code}, guest context: guestId=${guestId}, previousGuestId=${previousGuestId}`); + // Проверяем код через сервис верификации const result = await emailAuth.checkEmailVerification(code, req.session); if (!result.verified) { + logger.warn(`[email/check-verification] Invalid code: ${result.message}`); // Преобразуем ответ для совместимости с фронтендом return res.json({ success: false, @@ -1319,12 +1274,25 @@ router.all('/check-email-verification', async (req, res) => { }); } + logger.info(`[email/check-verification] Email verification successful for userId: ${result.userId}, email: ${result.email}`); + // Код верный, обновляем сессию req.session.authenticated = true; req.session.userId = result.userId; req.session.authType = 'email'; req.session.email = result.email; + // Восстанавливаем гостевой ID, если он был потерян в процессе верификации + if (!req.session.guestId && guestId) { + req.session.guestId = guestId; + logger.info(`[email/check-verification] Restored guestId ${guestId}`); + } + + if (!req.session.previousGuestId && previousGuestId) { + req.session.previousGuestId = previousGuestId; + logger.info(`[email/check-verification] Restored previous guest ID ${previousGuestId}`); + } + // Получаем роль пользователя const roleResult = await db.query( 'SELECT role FROM users WHERE id = $1', @@ -1333,29 +1301,40 @@ router.all('/check-email-verification', async (req, res) => { if (roleResult.rows.length > 0) { req.session.userRole = roleResult.rows[0].role || 'user'; + logger.info(`[email/check-verification] User role: ${req.session.userRole}`); } else { req.session.userRole = 'user'; + logger.info(`[email/check-verification] User role not found, setting default: user`); } + // Явно добавляем email в таблицу user_identities + await saveUserIdentity(result.userId, 'email', result.email.toLowerCase(), true); + logger.info(`[email/check-verification] Email identity ${result.email} saved for user ${result.userId}`); + // Сохраняем сессию await new Promise((resolve, reject) => { req.session.save(err => { if (err) { - logger.error('Error saving session:', err); + logger.error('[email/check-verification] Error saving session:', err); reject(err); } else { + logger.info(`[email/check-verification] Session saved successfully for email ${result.email}`); resolve(); } }); }); + // Связываем гостевые сообщения с пользователем + const linkResults = await linkGuestMessagesAfterAuth(req.session, result.userId); + logger.info(`[email/check-verification] Guest messages linking results:`, linkResults); + return res.json({ success: true, userId: result.userId, email: result.email }); } catch (error) { - logger.error('Error checking email verification:', error); + logger.error('[email/check-verification] Error:', error); return res.status(500).json({ success: false, message: 'Ошибка при проверке кода верификации' @@ -1385,4 +1364,457 @@ router.post('/email/send', async (req, res) => { } }); +// Обертка для функции processGuestMessages с правильным порядком аргументов +async function processGuestMessagesWrapper(guestId, userId) { + try { + logger.info(`[processGuestMessagesWrapper] Correcting order of arguments: userId=${userId}, guestId=${guestId}`); + return await processGuestMessages(userId, guestId); + } catch (error) { + logger.error(`[processGuestMessagesWrapper] Error: ${error.message}`, error); + throw error; + } +} + +// Функция для сохранения идентификатора пользователя в базу данных +async function saveUserIdentity(userId, provider, providerId, verified = true) { + try { + logger.info(`[saveUserIdentity] Saving identity for user ${userId}: ${provider}:${providerId}`); + + // Проверяем, существует ли уже такой идентификатор + const existingResult = await db.query( + `SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`, + [provider, providerId] + ); + + if (existingResult.rows.length > 0) { + const existingUserId = existingResult.rows[0].user_id; + + // Если идентификатор уже принадлежит этому пользователю, ничего не делаем + if (existingUserId === userId) { + logger.info(`[saveUserIdentity] Identity ${provider}:${providerId} already exists for user ${userId}`); + } else { + // Если идентификатор принадлежит другому пользователю, логируем это + logger.warn(`[saveUserIdentity] Identity ${provider}:${providerId} already belongs to user ${existingUserId}, not user ${userId}`); + return { + success: false, + error: `Identity already belongs to another user (${existingUserId})` + }; + } + } else { + // Создаем новую запись + await db.query( + `INSERT INTO user_identities (user_id, provider, provider_id) + VALUES ($1, $2, $3)`, + [userId, provider, providerId] + ); + logger.info(`[saveUserIdentity] Created new identity ${provider}:${providerId} for user ${userId}`); + } + + return { success: true }; + } catch (error) { + logger.error(`[saveUserIdentity] Error saving identity ${provider}:${providerId} for user ${userId}:`, error); + return { success: false, error: error.message }; + } +} + +// Функция для связывания гостевых сообщений после аутентификации +async function linkGuestMessagesAfterAuth(session, userId) { + try { + const guestId = session.guestId; + const previousGuestId = session.previousGuestId; + + logger.info(`[linkGuestMessagesAfterAuth] Starting for user ${userId} with guestId=${guestId}, previousGuestId=${previousGuestId}`); + + // Проверяем, есть ли идентификаторы для обработки + if (!guestId && !previousGuestId) { + logger.debug('[linkGuestMessagesAfterAuth] No guest IDs to process'); + return { success: true, message: 'No guest IDs to process' }; + } + + // Получаем список постоянных идентификаторов пользователя + const permanentIdentitiesResult = await db.query( + `SELECT provider, provider_id FROM user_identities WHERE user_id = $1 AND provider IN ('wallet', 'email', 'telegram')`, + [userId] + ); + + // Логирование найденных постоянных идентификаторов + logger.info(`[linkGuestMessagesAfterAuth] Found ${permanentIdentitiesResult.rows.length} permanent identities for user ${userId}`); + permanentIdentitiesResult.rows.forEach(identity => { + logger.info(`[linkGuestMessagesAfterAuth] - ${identity.provider}:${identity.provider_id}`); + }); + + // Сохраняем все постоянные идентификаторы из сессии + if (session.email) { + await saveUserIdentity(userId, 'email', session.email, true); + } + + if (session.address) { + await saveUserIdentity(userId, 'wallet', session.address, true); + } + + if (session.telegramId) { + await saveUserIdentity(userId, 'telegram', session.telegramId, true); + } + + let results = []; + + // Обрабатываем текущий гостевой ID + if (guestId) { + logger.info(`[linkGuestMessagesAfterAuth] Processing current guest ID ${guestId} for user ${userId}`); + + try { + // Сохраняем гостевой ID как идентификатор пользователя + await saveUserIdentity(userId, 'guest', guestId, true); + + // Связываем сообщения с пользователем через обертку с правильным порядком аргументов + const result = await processGuestMessagesWrapper(guestId, userId); + results.push({ guestId, result }); + logger.info(`[linkGuestMessagesAfterAuth] Successfully processed guest ID ${guestId}`); + } catch (error) { + logger.error(`[linkGuestMessagesAfterAuth] Error processing guest ID ${guestId}:`, error); + results.push({ guestId, error: error.message }); + } + } + + // Обрабатываем предыдущий гостевой ID + if (previousGuestId && previousGuestId !== guestId) { + logger.info(`[linkGuestMessagesAfterAuth] Processing previous guest ID ${previousGuestId} for user ${userId}`); + + try { + // Сохраняем предыдущий гостевой ID как идентификатор пользователя + await saveUserIdentity(userId, 'guest', previousGuestId, true); + + // Связываем сообщения с пользователем через обертку с правильным порядком аргументов + const result = await processGuestMessagesWrapper(previousGuestId, userId); + results.push({ guestId: previousGuestId, result }); + logger.info(`[linkGuestMessagesAfterAuth] Successfully processed previous guest ID ${previousGuestId}`); + } catch (error) { + logger.error(`[linkGuestMessagesAfterAuth] Error processing previous guest ID ${previousGuestId}:`, error); + results.push({ guestId: previousGuestId, error: error.message }); + } + } + + // Очищаем гостевые идентификаторы из сессии + delete session.guestId; + delete session.previousGuestId; + + // Сохраняем сессию + await new Promise((resolve, reject) => { + session.save(err => { + if (err) { + logger.error('[linkGuestMessagesAfterAuth] Error saving session after guest ID processing:', err); + reject(err); + } else { + logger.info('[linkGuestMessagesAfterAuth] Session saved successfully after guest ID processing'); + resolve(); + } + }); + }); + + return { success: true, results }; + } catch (error) { + logger.error(`[linkGuestMessagesAfterAuth] Error: ${error.message}`, error); + return { success: false, error: error.message }; + } +} + +// Маршрут для получения всех идентификаторов пользователя +router.get('/identities', requireAuth, async (req, res) => { + try { + const { userId } = req.session; + + // Получаем все идентификаторы пользователя + const identitiesResult = await db.query( + `SELECT provider, provider_id FROM user_identities WHERE user_id = $1`, + [userId] + ); + + const identities = identitiesResult.rows; + + res.json({ + success: true, + identities + }); + } catch (error) { + logger.error('Error getting user identities:', error); + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Аутентификация через wallet +router.post('/wallet', async (req, res) => { + try { + const { address, nonce, signature } = req.body; + + if (!address || !nonce || !signature) { + return res.status(400).json({ + success: false, + error: 'Missing required fields' + }); + } + + logger.info(`[wallet] Authentication request for address: ${address}`); + + // Сохраняем гостевые ID до аутентификации + const guestId = req.session.guestId; + const previousGuestId = req.session.previousGuestId; + + logger.info(`[wallet] Guest context: guestId=${guestId}, previousGuestId=${previousGuestId}`); + + // Формируем сообщение для проверки + const message = `Sign this message to authenticate with HB3 DApp: ${nonce}`; + + // Проверяем подпись + const validSignature = await authService.verifySignature(message, signature, address); + if (!validSignature) { + logger.warn(`[wallet] Invalid signature for address: ${address}`); + return res.status(401).json({ + success: false, + error: 'Invalid signature' + }); + } + + logger.info(`[wallet] Valid signature from address: ${address}`); + + // Получаем или создаем пользователя + const { userId } = await authService.findOrCreateUser(address); + logger.info(`[wallet] User ID for address ${address}: ${userId}`); + + // Проверяем наличие админских токенов + const isAdmin = await authService.checkAdminTokens(address); + logger.info(`[wallet] Admin status for ${address}: ${isAdmin}`); + + // Обновляем роль пользователя в базе данных, если нужно + if (isAdmin) { + try { + await db.query( + 'UPDATE users SET role = $1 WHERE id = $2', + ['admin', userId] + ); + logger.info(`[wallet] Updated user ${userId} role to admin`); + } catch (updateError) { + logger.error(`[wallet] Error updating user role:`, updateError); + } + } + + // Явно сохраняем wallet адрес как идентификатор пользователя + await saveUserIdentity(userId, 'wallet', address.toLowerCase(), true); + logger.info(`[wallet] Saved wallet identity ${address.toLowerCase()} for user ${userId}`); + + // Если есть гостевые ID, сохраняем их + if (guestId) { + await saveUserIdentity(userId, 'guest', guestId, true); + logger.info(`[wallet] Saved guest ID ${guestId} for user ${userId}`); + } + + if (previousGuestId && previousGuestId !== guestId) { + await saveUserIdentity(userId, 'guest', previousGuestId, true); + logger.info(`[wallet] Saved previous guest ID ${previousGuestId} for user ${userId}`); + } + + // Устанавливаем сессию + req.session.userId = userId; + req.session.address = address.toLowerCase(); + req.session.authType = 'wallet'; + req.session.authenticated = true; + req.session.isAdmin = isAdmin; + + // Сохраняем сессию перед связыванием сообщений + await new Promise((resolve, reject) => { + req.session.save(err => { + if (err) { + logger.error('[wallet] Error saving session:', err); + reject(err); + } else { + logger.info(`[wallet] Session saved successfully for user ${userId}, address ${address}`); + resolve(); + } + }); + }); + + // Связываем гостевые сообщения с пользователем + const linkResults = await linkGuestMessagesAfterAuth(req.session, userId); + logger.info(`[wallet] Guest messages linking results:`, linkResults); + + // Возвращаем успешный ответ + return res.json({ + success: true, + userId, + address, + isAdmin, + authenticated: true + }); + + } catch (error) { + logger.error('[wallet] Error:', error); + res.status(500).json({ + success: false, + error: 'Server error during wallet authentication' + }); + } +}); + +// Обработчик для связывания гостевых сообщений с пользователем +router.post('/link-guest-messages', requireAuth, async (req, res) => { + try { + const { userId } = req.session; + + logger.info(`[link-guest-messages] Request for user ${userId}`); + + // Получаем все идентификаторы пользователя + const userIdentitiesResult = await db.query( + `SELECT provider, provider_id FROM user_identities WHERE user_id = $1`, + [userId] + ); + + const userIdentities = userIdentitiesResult.rows; + logger.info(`[link-guest-messages] Found ${userIdentities.length} identities for user ${userId}`); + userIdentities.forEach(identity => { + logger.info(`[link-guest-messages] - ${identity.provider}:${identity.provider_id}`); + }); + + // Собираем все guestId, связанные с идентификаторами пользователя + let guestIds = []; + + // Добавляем текущий guestId из сессии + if (req.session.guestId) { + guestIds.push(req.session.guestId); + logger.info(`[link-guest-messages] Added session guestId: ${req.session.guestId}`); + } + + // Добавляем guestId из тела запроса, если есть + if (req.body.guestId) { + guestIds.push(req.body.guestId); + logger.info(`[link-guest-messages] Added request body guestId: ${req.body.guestId}`); + } + + // Добавляем массив guestIds из тела запроса, если есть + if (req.body.guestIds && Array.isArray(req.body.guestIds)) { + logger.info(`[link-guest-messages] Adding ${req.body.guestIds.length} guestIds from request body`); + guestIds = [...guestIds, ...req.body.guestIds]; + } + + // Добавляем guestId из идентификаторов пользователя + const guestIdentities = userIdentities.filter(identity => identity.provider === 'guest'); + if (guestIdentities.length > 0) { + logger.info(`[link-guest-messages] Adding ${guestIdentities.length} guest identities from user_identities`); + guestIds = [...guestIds, ...guestIdentities.map(identity => identity.provider_id)]; + } + + // Убираем дубликаты + guestIds = [...new Set(guestIds)]; + + logger.info(`[link-guest-messages] Final list of unique guestIds to process: ${guestIds.length}`); + guestIds.forEach(id => logger.info(`[link-guest-messages] - ${id}`)); + + if (guestIds.length === 0) { + logger.info('[link-guest-messages] No guest IDs found for user'); + return res.json({ success: true, message: 'No guest messages to link' }); + } + + const results = []; + + // Обрабатываем каждый guestId + for (const guestId of guestIds) { + // Пропускаем пустые или невалидные ID + if (!guestId || typeof guestId !== 'string') { + logger.warn(`[link-guest-messages] Skipping invalid guest ID: ${guestId}`); + continue; + } + + // Проверяем, не обрабатывали ли мы уже этот ID + if (req.session.processedGuestIds && req.session.processedGuestIds.includes(guestId)) { + logger.info(`[link-guest-messages] Guest ID ${guestId} already processed, skipping`); + results.push({ guestId, result: { success: true, message: 'Already processed' } }); + continue; + } + + // Проверяем наличие гостевых сообщений + try { + const guestMessagesCheck = await db.query( + 'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)', + [guestId] + ); + + if (guestMessagesCheck.rows[0].exists) { + logger.info(`[link-guest-messages] Found messages for guest ID ${guestId}, processing`); + try { + // Используем обертку с правильным порядком аргументов + const result = await processGuestMessagesWrapper(guestId, userId); + results.push({ guestId, result }); + logger.info(`[link-guest-messages] Successfully processed guest ID ${guestId}`); + } catch (error) { + logger.error(`[link-guest-messages] Error processing guest ID ${guestId}:`, error); + results.push({ guestId, error: error.message }); + } + } else { + logger.info(`[link-guest-messages] No guest messages found for guest ID ${guestId}`); + results.push({ guestId, result: { success: true, message: 'No messages found' } }); + } + } catch (error) { + logger.error(`[link-guest-messages] Error checking guest messages for ${guestId}:`, error); + results.push({ guestId, error: error.message }); + } + } + + // Сохраняем все обработанные guestId, чтобы не обрабатывать их снова + if (!req.session.processedGuestIds) { + req.session.processedGuestIds = []; + } + + // Добавляем только успешно обработанные ID + const successfulGuestIds = results + .filter(r => r.result && r.result.success) + .map(r => r.guestId); + + req.session.processedGuestIds = [...new Set([...req.session.processedGuestIds, ...successfulGuestIds])]; + logger.info(`[link-guest-messages] Updated processed guest IDs: ${req.session.processedGuestIds.join(', ')}`); + + // Очищаем текущий guestId из сессии + req.session.guestId = null; + await req.session.save(); + logger.info('[link-guest-messages] Session updated, guestId cleared'); + + return res.json({ + success: true, + message: `Processed ${results.length} guest IDs`, + results + }); + } catch (error) { + logger.error('[link-guest-messages] Error:', error); + return res.status(500).json({ + success: false, + error: 'Internal server error', + details: error.message + }); + } +}); + +// Маршрут для проверки и инициализации сессии гостя +router.get('/check-session', async (req, res) => { + try { + // Если у пользователя нет guestId, создаем его + if (!req.session.guestId && !req.session.authenticated) { + req.session.guestId = crypto.randomBytes(16).toString('hex'); + await req.session.save(); + logger.info('Created new guestId:', req.session.guestId); + } + + res.json({ + success: true, + guestId: req.session.guestId, + isAuthenticated: req.session.authenticated || false + }); + } catch (error) { + logger.error('Error checking session:', error); + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/backend/routes/chat.js b/backend/routes/chat.js index 495dff1..81f7506 100644 --- a/backend/routes/chat.js +++ b/backend/routes/chat.js @@ -6,6 +6,7 @@ const { requireAuth, requireAdmin } = require('../middleware/auth'); const logger = require('../utils/logger'); const crypto = require('crypto'); const { saveGuestMessageToDatabase } = require('../db'); +const { v4: uuidv4 } = require('uuid'); // Функция для обработки гостевых сообщений после аутентификации async function processGuestMessages(userId, guestId) { @@ -26,6 +27,30 @@ async function processGuestMessages(userId, guestId) { const guestMessages = guestMessagesResult.rows; console.log(`Found ${guestMessages.length} guest messages`); + // Получаем идентификаторы пользователя + const userIdentities = await db.query( + `SELECT provider, provider_id FROM user_identities WHERE user_id = $1`, + [userId] + ); + + console.log(`Found ${userIdentities.rows.length} identities for user ${userId}`, userIdentities.rows); + + // Сохраняем guestId как отдельный идентификатор для пользователя + // если он еще не привязан к пользователю + const existingGuestIdentity = userIdentities.rows.find( + identity => identity.provider === 'guest' && identity.provider_id === guestId + ); + + if (!existingGuestIdentity) { + await db.query( + `INSERT INTO user_identities (user_id, provider, provider_id) + VALUES ($1, 'guest', $2) + ON CONFLICT (provider, provider_id) DO NOTHING`, + [userId, guestId] + ); + console.log(`Linked guest ID ${guestId} to user ${userId}`); + } + // Создаем новый диалог для этих сообщений const firstMessage = guestMessages[0]; const title = firstMessage.content.length > 30 @@ -44,23 +69,23 @@ async function processGuestMessages(userId, guestId) { for (const guestMessage of guestMessages) { console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`); - // Проверяем существование гостевого сообщения перед созданием связанного сообщения - const checkGuestMessage = await db.query( - 'SELECT id FROM guest_messages WHERE id = $1', + // Проверяем, не было ли это сообщение уже обработано + const existingMessage = await db.query( + 'SELECT id FROM messages WHERE guest_message_id = $1', [guestMessage.id] ); - if (checkGuestMessage.rows.length === 0) { - console.log(`Guest message ${guestMessage.id} no longer exists, skipping`); + if (existingMessage.rows.length > 0) { + console.log(`Guest message ${guestMessage.id} already processed, skipping`); continue; } // Сохраняем сообщение пользователя const userMessageResult = await db.query( `INSERT INTO messages - (conversation_id, content, sender_type, role, channel, created_at) + (conversation_id, content, sender_type, role, channel, guest_message_id, created_at) VALUES - ($1, $2, $3, $4, $5, $6) + ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, [ conversation.id, @@ -68,40 +93,39 @@ async function processGuestMessages(userId, guestId) { 'user', 'user', 'web', + guestMessage.id, guestMessage.created_at ] ); console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`); - // Получаем ответ от ИИ - console.log('Getting AI response for:', guestMessage.content); - const language = guestMessage.language || 'auto'; - const aiResponse = await aiAssistant.getResponse(guestMessage.content, language); - console.log('AI response received:', aiResponse); - - // Сохраняем ответ от ИИ - const aiMessageResult = await db.query( - `INSERT INTO messages - (conversation_id, content, sender_type, role, channel, created_at) - VALUES - ($1, $2, $3, $4, $5, $6) - RETURNING *`, - [ - conversation.id, - aiResponse, - 'assistant', - 'assistant', - 'web', - new Date() - ] - ); - - console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`); - - // Удаляем обработанное гостевое сообщение - await db.query('DELETE FROM guest_messages WHERE id = $1', [guestMessage.id]); - console.log(`Deleted processed guest message ${guestMessage.id}`); + // Получаем ответ от ИИ только для сообщений пользователя (не AI) + if (!guestMessage.is_ai) { + console.log('Getting AI response for:', guestMessage.content); + const language = guestMessage.language || 'auto'; + const aiResponse = await aiAssistant.getResponse(guestMessage.content, language); + console.log('AI response received:', aiResponse); + + // Сохраняем ответ от ИИ + const aiMessageResult = await db.query( + `INSERT INTO messages + (conversation_id, content, sender_type, role, channel, created_at) + VALUES + ($1, $2, $3, $4, $5, $6) + RETURNING *`, + [ + conversation.id, + aiResponse, + 'assistant', + 'assistant', + 'web', + new Date() + ] + ); + + console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`); + } } return { @@ -255,6 +279,24 @@ router.get('/history', async (req, res) => { const limit = parseInt(req.query.limit) || 50; const offset = parseInt(req.query.offset) || 0; + // Если пользователь аутентифицирован и у него есть гостевые сообщения, + // автоматически связываем их перед получением истории + if (req.session.authenticated && req.session.userId && req.session.guestId) { + try { + console.log('Automatically linking guest messages before fetching history'); + await processGuestMessages(req.session.userId, req.session.guestId); + + // Очищаем guestId из сессии после связывания + req.session.guestId = null; + await req.session.save(); + + console.log('Guest messages automatically linked'); + } catch (linkError) { + console.error('Error auto-linking guest messages:', linkError); + // Продолжаем выполнение, даже если связывание не удалось + } + } + let messages = []; let total = 0; @@ -288,214 +330,19 @@ router.get('/history', async (req, res) => { messages = result.rows; console.log(`Found ${messages.length} messages for authenticated user`); } - // Если есть guestId, получаем гостевые сообщения - else if (req.session.guestId) { - const countResult = await db.query( - `SELECT COUNT(*) as total FROM guest_messages - WHERE guest_id = $1`, - [req.session.guestId] - ); - total = parseInt(countResult.rows[0].total) || 0; - - const result = await db.query( - `SELECT - id, - content, - 'user' as sender_type, - 'user' as role, - created_at, - guest_id as user_id, - NULL as conversation_id - FROM guest_messages - WHERE guest_id = $1 - ORDER BY created_at ASC - LIMIT $2 OFFSET $3`, - [req.session.guestId, limit, offset] - ); - - messages = result.rows; - console.log(`Found ${messages.length} guest messages`); - } - + return res.json({ success: true, messages: messages, total: total }); - + } catch (error) { logger.error('Error getting chat history:', error); return res.status(500).json({ error: 'Internal server error' }); } }); -// Маршрут для получения всех диалогов (только для админов) -router.get('/admin/history', requireAdmin, async (req, res) => { - try { - const { limit = 50, offset = 0, userId } = req.query; - - let query = ` - SELECT ch.id, ch.user_id, u.username, ch.channel, - ch.sender_type, ch.content, ch.metadata, ch.created_at - FROM chat_history ch - LEFT JOIN users u ON ch.user_id = u.id - `; - - const params = []; - let paramIndex = 1; - - if (userId) { - query += ` WHERE ch.user_id = $${paramIndex}`; - params.push(userId); - paramIndex++; - } - - query += ` ORDER BY ch.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`; - params.push(limit, offset); - - const result = await db.query(query, params); - - res.json(result.rows); - } catch (error) { - logger.error('Error fetching admin chat history:', error); - res.status(500).json({ error: 'Внутренняя ошибка сервера' }); - } -}); - -// Обработчик для связывания гостевых сообщений с пользователем -router.post('/link-guest-messages', requireAuth, async (req, res) => { - try { - const { userId } = req.session; - const guestId = req.session.guestId; - - console.log('Linking messages:', { userId, guestId }); - - if (!guestId) { - console.log('No guestId in session'); - return res.json({ success: true, message: 'No guest messages to link' }); - } - - // Проверяем наличие гостевых сообщений - const guestMessagesCheck = await db.query( - 'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)', - [guestId] - ); - - console.log('Guest messages check:', guestMessagesCheck.rows[0]); - - if (!guestMessagesCheck.rows[0].exists) { - console.log('No guest messages found for guestId:', guestId); - return res.json({ success: true, message: 'No guest messages to link' }); - } - - try { - // Обрабатываем гостевые сообщения для получения ответов от AI - console.log('Processing guest messages to get AI responses'); - const result = await processGuestMessages(userId, guestId); - console.log('Guest messages processed:', result); - - // Очищаем guestId из сессии после связывания - req.session.guestId = null; - await req.session.save(); - - console.log('Messages linked and processed successfully'); - return res.json({ - success: true, - message: 'Guest messages linked and processed', - result - }); - } catch (processError) { - console.error('Error processing guest messages:', processError); - return res.status(500).json({ - success: false, - error: 'Error processing guest messages', - details: processError.message - }); - } - } catch (error) { - console.error('Error linking guest messages:', error); - return res.status(500).json({ - success: false, - error: 'Internal server error', - details: error.message - }); - } -}); - -// Обновляем маршрут верификации 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 - }); - } -}); - -// Маршрут для удаления сообщений -router.delete('/message/:id', requireAuth, async (req, res) => { - try { - const messageId = req.params.id; - const userId = req.session.userId; - - // Проверяем права на удаление - const messageCheck = await db.query( - `SELECT m.id - FROM messages m - JOIN conversations c ON m.conversation_id = c.id - WHERE m.id = $1 AND c.user_id = $2`, - [messageId, userId] - ); - - if (messageCheck.rows.length === 0) { - return res.status(403).json({ error: 'Forbidden' }); - } - - // Удаляем сообщение - await db.query( - 'DELETE FROM messages WHERE id = $1', - [messageId] - ); - - res.json({ success: true }); - } catch (error) { - logger.error('Error deleting message:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Маршрут для проверки и инициализации сессии -router.get('/check-session', async (req, res) => { - try { - // Если у пользователя нет guestId, создаем его - if (!req.session.guestId) { - req.session.guestId = crypto.randomBytes(16).toString('hex'); - await req.session.save(); - console.log('Created new guestId:', req.session.guestId); - } - - res.json({ - success: true, - guestId: req.session.guestId, - isAuthenticated: req.session.authenticated || false - }); - } catch (error) { - console.error('Error checking session:', error); - res.status(500).json({ - success: false, - error: 'Internal server error' - }); - } -}); - +// Экспортируем маршрутизатор и функцию processGuestMessages отдельно module.exports = router; +module.exports.processGuestMessages = processGuestMessages; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 52ef5a6..6db0cd4 100644 --- a/backend/server.js +++ b/backend/server.js @@ -8,6 +8,7 @@ const { app, nonceStore } = require('./app'); const usersRouter = require('./routes/users'); const authRouter = require('./routes/auth'); const identitiesRouter = require('./routes/identities'); +const chatRouter = require('./routes/chat'); const { pool } = require('./db'); const helmet = require('helmet'); const { getBot, stopBot } = require('./services/telegramBot'); @@ -79,6 +80,7 @@ app.use(session({ app.use('/api/users', usersRouter); app.use('/api/auth', authRouter); app.use('/api/identities', identitiesRouter); +app.use('/api/chat', chatRouter); // Эндпоинт для проверки состояния сервера app.get('/api/health', (req, res) => { diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index 029f6e1..5a38d6c 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -391,6 +391,42 @@ class AuthService { throw error; } } + + // Добавляем псевдоним функции checkAdminRole для обратной совместимости + async checkAdminTokens(address) { + if (!address) return false; + + console.log(`Checking admin tokens for address: ${address}`); + const isAdmin = await this.checkAdminRole(address); + console.log(`Admin token check result for ${address}: ${isAdmin}`); + + // Обновляем роль пользователя в базе данных, если есть админские токены + if (isAdmin) { + try { + // Находим userId по адресу + const userResult = await db.query(` + SELECT u.id FROM users u + JOIN user_identities ui ON u.id = ui.user_id + WHERE ui.provider = 'wallet' AND ui.provider_id = $1`, + [address.toLowerCase()] + ); + + if (userResult.rows.length > 0) { + const userId = userResult.rows[0].id; + // Обновляем роль пользователя + await db.query( + 'UPDATE users SET role = $1 WHERE id = $2', + ['admin', userId] + ); + console.log(`Updated user ${userId} role to admin based on token holdings`); + } + } catch (error) { + console.error('Error updating user role:', error); + } + } + + return isAdmin; + } } // Создаем и экспортируем единственный экземпляр diff --git a/backend/services/emailAuth.js b/backend/services/emailAuth.js index 2baa407..70d62e4 100644 --- a/backend/services/emailAuth.js +++ b/backend/services/emailAuth.js @@ -73,16 +73,37 @@ class EmailAuth { const userId = result.userId || session.tempUserId; const email = session.pendingEmail; - // Добавляем email в базу данных - await db.query( - `INSERT INTO user_identities - (user_id, provider, provider_id) - VALUES ($1, $2, $3) - ON CONFLICT (provider, provider_id) - DO UPDATE SET user_id = $1`, - [userId, 'email', email.toLowerCase()] + // Проверяем, существует ли уже этот email в user_identities + const existingUserQuery = await db.query( + `SELECT user_id FROM user_identities + WHERE provider = 'email' AND provider_id = $1`, + [email.toLowerCase()] ); + let finalUserId = userId; + + // Если email уже связан с другим пользователем + if (existingUserQuery.rows.length > 0) { + finalUserId = existingUserQuery.rows[0].user_id; + logger.info(`Using existing user ID ${finalUserId} for email ${email}`); + + // Обновляем идентификатор пользователя в сессии + if (userId !== finalUserId) { + logger.info(`Changing user ID from ${userId} to ${finalUserId} based on existing email identity`); + } + } else { + // Добавляем email в базу данных для нового пользователя + await db.query( + `INSERT INTO user_identities + (user_id, provider, provider_id) + VALUES ($1, $2, $3) + ON CONFLICT (provider, provider_id) + DO UPDATE SET user_id = $1`, + [finalUserId, 'email', email.toLowerCase()] + ); + logger.info(`Added new email identity ${email} for user ${finalUserId}`); + } + // Очищаем временные данные delete session.pendingEmail; if (session.tempUserId) { @@ -91,7 +112,7 @@ class EmailAuth { return { verified: true, - userId, + userId: finalUserId, email: email.toLowerCase() }; } catch (error) { diff --git a/frontend/src/composables/useAuth.js b/frontend/src/composables/useAuth.js index 819a9a4..b9b48b3 100644 --- a/frontend/src/composables/useAuth.js +++ b/frontend/src/composables/useAuth.js @@ -9,15 +9,136 @@ export function useAuth() { const telegramId = ref(null); const isAdmin = ref(false); const email = ref(null); + const processedGuestIds = ref([]); + const identities = ref([]); + + // Функция для обновления списка идентификаторов + const updateIdentities = async () => { + if (!isAuthenticated.value || !userId.value) return; + + try { + const response = await axios.get('/api/auth/identities'); + if (response.data.success) { + identities.value = response.data.identities; + console.log('User identities updated:', identities.value); + } + } catch (error) { + console.error('Error fetching user identities:', error); + } + }; const updateAuth = ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin, email: newEmail }) => { - isAuthenticated.value = authenticated; - authType.value = newAuthType; - userId.value = newUserId; - address.value = newAddress; - telegramId.value = newTelegramId; - isAdmin.value = newIsAdmin; - email.value = newEmail; + const wasAuthenticated = isAuthenticated.value; + const previousUserId = userId.value; + + console.log('updateAuth called with:', { + authenticated, + newAuthType, + newUserId, + newAddress, + newTelegramId, + newIsAdmin, + newEmail + }); + + // Убедимся, что переменные являются реактивными + isAuthenticated.value = authenticated === true; + authType.value = newAuthType || null; + userId.value = newUserId || null; + address.value = newAddress || null; + telegramId.value = newTelegramId || null; + isAdmin.value = newIsAdmin === true; + email.value = newEmail || null; + + console.log('Auth updated:', { + authenticated: isAuthenticated.value, + userId: userId.value, + address: address.value, + telegramId: telegramId.value, + email: email.value, + isAdmin: isAdmin.value + }); + + // Если пользователь только что аутентифицировался или сменил аккаунт, + // пробуем связать сообщения и обновить идентификаторы + if (authenticated && (!wasAuthenticated || (previousUserId && previousUserId !== newUserId))) { + console.log('Auth change detected, linking messages and updating identities'); + linkMessages(); + updateIdentities(); + } + }; + + // Функция для связывания сообщений после успешной авторизации + const linkMessages = async () => { + try { + if (isAuthenticated.value) { + console.log('Linking messages after authentication'); + + // Создаем объект с идентификаторами для передачи на сервер + const identifiersData = { + userId: userId.value + }; + + // Добавляем все доступные идентификаторы + if (address.value) identifiersData.address = address.value; + if (email.value) identifiersData.email = email.value; + if (telegramId.value) identifiersData.telegramId = telegramId.value; + + // Сохраняем предыдущий guestId из localStorage, если есть + const localGuestId = localStorage.getItem('guestId'); + if (localGuestId && !processedGuestIds.value.includes(localGuestId)) { + console.log('Found local guestId:', localGuestId); + // Добавляем guestId в идентификаторы + identifiersData.guestId = localGuestId; + // Добавляем guestId в список обработанных + processedGuestIds.value.push(localGuestId); + } + + // Логируем попытку связывания сообщений + console.log('Sending link-guest-messages request with data:', identifiersData); + + try { + // Отправляем запрос на связывание сообщений + const response = await axios.post('/api/auth/link-guest-messages', identifiersData); + + if (response.data.success) { + console.log('Messages linked successfully:', response.data); + + // Если в ответе есть обработанные guestIds, добавляем их в список + if (response.data.results && Array.isArray(response.data.results)) { + const newProcessedIds = response.data.results + .filter(result => result.guestId) + .map(result => result.guestId); + + if (newProcessedIds.length > 0) { + processedGuestIds.value = [...new Set([...processedGuestIds.value, ...newProcessedIds])]; + console.log('Updated processed guest IDs:', processedGuestIds.value); + } + } + + // Очищаем гостевые сообщения из localStorage после успешного связывания + localStorage.removeItem('guestMessages'); + localStorage.removeItem('guestId'); + + return { + success: true, + processedIds: processedGuestIds.value + }; + } + } catch (error) { + console.error('Error linking messages:', error); + return { + success: false, + error: error.message + }; + } + } + + return { success: false, message: 'Not authenticated' }; + } catch (error) { + console.error('Error in linkMessages:', error); + return { success: false, error: error.message }; + } }; const checkAuth = async () => { @@ -25,13 +146,33 @@ export function useAuth() { const response = await axios.get('/api/auth/check'); console.log('Auth check response:', response.data); - isAuthenticated.value = response.data.authenticated; - userId.value = response.data.userId; - isAdmin.value = response.data.isAdmin; - authType.value = response.data.authType; - address.value = response.data.address; - telegramId.value = response.data.telegramId; - email.value = response.data.email; + const wasAuthenticated = isAuthenticated.value; + const previousUserId = userId.value; + + // Обновляем данные авторизации через updateAuth вместо прямого изменения + updateAuth({ + authenticated: response.data.authenticated, + authType: response.data.authType, + userId: response.data.userId, + address: response.data.address, + telegramId: response.data.telegramId, + email: response.data.email, + isAdmin: response.data.isAdmin + }); + + // Если пользователь аутентифицирован, обновляем список идентификаторов и связываем сообщения + if (response.data.authenticated) { + // Сначала обновляем идентификаторы, чтобы иметь актуальные данные + await updateIdentities(); + + // Если пользователь только что аутентифицировался или сменил аккаунт, + // связываем гостевые сообщения с его аккаунтом + if (!wasAuthenticated || (previousUserId && previousUserId !== response.data.userId)) { + // Немедленно связываем сообщения + const linkResult = await linkMessages(); + console.log('Link messages result on auth change:', linkResult); + } + } return response.data; } catch (error) { @@ -42,6 +183,11 @@ export function useAuth() { const disconnect = async () => { try { + // Сохраняем текущий guestId перед выходом + const newGuestId = crypto.randomUUID(); + localStorage.setItem('guestId', newGuestId); + console.log('Created new guestId for future session:', newGuestId); + await axios.post('/api/auth/logout'); updateAuth({ authenticated: false, @@ -53,7 +199,10 @@ export function useAuth() { isAdmin: false }); - // Очищаем localStorage + // Очищаем списки идентификаторов + identities.value = []; + + // Очищаем localStorage кроме guestId localStorage.removeItem('isAuthenticated'); localStorage.removeItem('userId'); localStorage.removeItem('address'); @@ -66,6 +215,13 @@ export function useAuth() { } }; + // Обновляем список обработанных guestIds + const updateProcessedGuestIds = (ids) => { + if (Array.isArray(ids)) { + processedGuestIds.value = [...new Set([...processedGuestIds.value, ...ids])]; + } + }; + onMounted(async () => { await checkAuth(); }); @@ -78,8 +234,13 @@ export function useAuth() { isAdmin, telegramId, email, + identities, + processedGuestIds, updateAuth, checkAuth, - disconnect + disconnect, + linkMessages, + updateIdentities, + updateProcessedGuestIds }; } \ No newline at end of file diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index ee48355..b065db0 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -179,18 +179,39 @@

Идентификаторы:

- - - + + + + + + + + +
@@ -223,7 +244,7 @@ const userLanguage = ref('ru'); const isLoadingMore = ref(false); const hasMoreMessages = ref(false); const offset = ref(0); -const limit = ref(20); +const limit = ref(30); // Состояния для верификации const showTelegramVerification = ref(false); @@ -266,6 +287,32 @@ const tokenBalances = ref({ // Состояние для отображения правой панели const showWalletSidebar = ref(false); +// Вычисленное свойство для фильтрации идентификаторов +const filteredIdentities = computed(() => { + if (!auth.identities?.value) return []; + + // Фильтруем и оставляем только постоянные идентификаторы + return auth.identities.value.filter(identity => + identity.provider !== 'guest' && // Исключаем guest идентификаторы + // Также исключаем дубликаты, если идентификатор уже показан выше + !(identity.provider === 'wallet' && auth.address?.value) && + !(identity.provider === 'telegram' && auth.telegramId?.value) && + !(identity.provider === 'email' && auth.email?.value) + ); +}); + +// Функция для форматирования названий провайдеров +const formatIdentityProvider = (provider) => { + const providers = { + 'wallet': 'Кошелек', + 'telegram': 'Telegram', + 'email': 'Email', + 'guest': 'Гость' + }; + + return providers[provider] || provider; +}; + // Функция для управления сайдбаром const toggleSidebar = () => { showSidebar.value = !showSidebar.value; @@ -551,86 +598,44 @@ const cancelTelegramAuth = () => { } }; -// Загружаем сообщения при изменении аутентификации -watch(() => isAuthenticated.value, async (newValue, oldValue) => { - // Если пользователь только что авторизовался - if (newValue && !oldValue) { - try { - // Связываем гостевые сообщения только один раз при первой авторизации - const response = await api.post('/api/chat/link-guest-messages'); - console.log('Guest messages linking response:', response.data); - } catch (linkError) { - console.error('Error linking guest messages:', linkError); - } - } - - // В любом случае перезагружаем сообщения - messages.value = []; - offset.value = 0; - hasMoreMessages.value = true; - await loadMoreMessages(); - - await nextTick(); - scrollToBottom(); +// Вычисленное свойство для определения, нужно ли загружать историю +const shouldLoadHistory = computed(() => { + // Загружаем историю только если пользователь авторизован или есть гостевой ID + return isAuthenticated.value || + (localStorage.getItem('guestId') && localStorage.getItem('guestId').length > 0); }); -// Обработчик для Telegram аутентификации -const handleTelegramAuth = async () => { - try { - const { data } = await axios.post('/api/auth/telegram/init'); - const { verificationCode, botLink } = data; +// Следим за изменением авторизации +watch(() => auth.isAuthenticated.value, async (newValue, oldValue) => { + console.log('Auth state changed:', { + from: oldValue, + to: newValue, + userId: auth.userId.value + }); + + if (newValue === true) { + // Если пользователь только что авторизовался + console.log('User authenticated, loading message history'); - // Показываем код верификации - showTelegramVerification.value = true; - telegramVerificationCode.value = verificationCode; - telegramBotLink.value = botLink; - - // Запускаем проверку статуса аутентификации - telegramAuthCheckInterval.value = setInterval(async () => { - try { - const response = await axios.get('/api/auth/check'); - console.log('Проверка авторизации:', response.data); - - if (response.data.authenticated) { - // Обновляем состояние аутентификации с полным набором данных - auth.updateAuth({ - isAuthenticated: true, - authenticated: true, - authType: response.data.authType, - userId: response.data.userId, - telegramId: response.data.telegramId, - isAdmin: response.data.isAdmin, - address: response.data.address - }); - - console.log('Telegram authentication successful:', response.data); - - // Обновляем баланс токенов - await updateBalances(); - - clearInterval(telegramAuthCheckInterval.value); - telegramAuthCheckInterval.value = null; - showTelegramVerification.value = false; - } - } catch (error) { - console.error('Error checking auth status:', error); - } - }, 2000); - - // Очищаем интервал через 5 минут - setTimeout(() => { - if (telegramAuthCheckInterval.value) { - clearInterval(telegramAuthCheckInterval.value); - telegramAuthCheckInterval.value = null; - showTelegramVerification.value = false; - } - }, 5 * 60 * 1000); - - } catch (error) { - console.error('Error initializing Telegram auth:', error); - alert('Ошибка при инициализации Telegram аутентификации'); + // Сначала связываем сообщения, затем загружаем историю + try { + // Сбрасываем текущие сообщения + messages.value = []; + offset.value = 0; + + // Сначала связываем гостевые сообщения + await auth.linkMessages(); + + // Затем загружаем историю сообщений после небольшой задержки, + // чтобы сервер успел обработать связанные сообщения + setTimeout(async () => { + await loadMoreMessages(true); + }, 1000); + } catch (error) { + console.error('Error loading history after auth:', error); + } } -}; +}); // Функция для сокращения адреса кошелька const truncateAddress = (address) => { @@ -641,63 +646,100 @@ const truncateAddress = (address) => { // Функция прокрутки к последнему сообщению const scrollToBottom = () => { if (messagesContainer.value) { - messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; + setTimeout(() => { + messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; + }, 100); } }; -// Загрузка сообщений -const loadMoreMessages = async () => { +// Функция для загрузки сообщений +const loadMoreMessages = async (silent = false) => { + if (isLoadingMore.value) return; + try { isLoadingMore.value = true; + if (!silent) isLoading.value = true; + console.log('Fetching chat history...'); - // Всегда запрашиваем историю, так как на сервере проверяется наличие - // userId или guestId в сессии и возвращаются соответствующие сообщения - const response = await api.get('/api/chat/history', { + // Проверяем сессию перед загрузкой истории + try { + const sessionCheck = await axios.get('/api/auth/check'); + console.log('Session check:', sessionCheck.data); + + // Проверяем, что пользователь все еще аутентифицирован + if (!sessionCheck.data.authenticated && !shouldLoadHistory.value) { + console.warn('User is not authenticated, skipping message history load'); + isLoadingMore.value = false; + isLoading.value = false; + return; + } + } catch (error) { + console.warn('Session check failed, but continuing anyway:', error); + } + + // Сначала запрашиваем общее количество сообщений + const countResponse = await axios.get('/api/chat/history', { params: { - limit: limit.value, - offset: offset.value + count_only: true } }); - - console.log('Chat history response:', response.data); - - if (response.data.success) { - const newMessages = response.data.messages.map(msg => { - console.log('Processing message:', msg); - return { - id: msg.id, - content: msg.content, - sender_type: msg.sender_type || (msg.role === 'assistant' ? 'assistant' : 'user'), - role: msg.role || (msg.sender_type === 'assistant' ? 'assistant' : 'user'), - timestamp: msg.created_at, - showAuthOptions: false - }; - }); - - console.log('Processed messages:', newMessages); - - // Объединяем сообщения и сортируем их по timestamp - const allMessages = [...messages.value, ...newMessages]; - allMessages.sort((a, b) => { - const timeA = new Date(a.timestamp || a.created_at).getTime(); - const timeB = new Date(b.timestamp || b.created_at).getTime(); - return timeA - timeB; - }); - - messages.value = allMessages; - console.log('Updated messages array:', messages.value); - hasMoreMessages.value = response.data.total > messages.value.length; - offset.value += newMessages.length; - - // Прокручиваем к последнему сообщению - await nextTick(); - scrollToBottom(); + + if (!countResponse.data.success) { + throw new Error('Failed to get message count'); } + + const totalMessages = countResponse.data.total || 0; + console.log(`Total messages in history: ${totalMessages}`); + + // Рассчитываем offset так, чтобы получить последние сообщения + // при первой загрузке (когда offset = 0) + let effectiveOffset = offset.value; + if (offset.value === 0 && totalMessages > limit.value) { + // Загружаем последние сообщения вместо первых + effectiveOffset = Math.max(0, totalMessages - limit.value); + } + + // Получаем историю сообщений с параметрами пагинации + const response = await axios.get('/api/chat/history', { + params: { + offset: effectiveOffset, + limit: limit.value + } + }); + + console.log('Chat history response:', response.data); + + if (response.data.success) { + // Если это первая загрузка, заменяем сообщения + // Иначе, добавляем полученные сообщения к существующим + if (offset.value === 0) { + messages.value = response.data.messages || []; + } else if (response.data.messages && response.data.messages.length) { + messages.value = [...messages.value, ...response.data.messages]; + } + + // Обновляем offset для следующей загрузки + offset.value = effectiveOffset + (response.data.messages?.length || 0); + + // Проверяем, есть ли еще сообщения для загрузки + hasMoreMessages.value = offset.value < totalMessages; + } + + // После загрузки первой порции сообщений считаем, что пользователь уже отправлял сообщения + if (messages.value.length > 0) { + hasUserSentMessage.value = true; + localStorage.setItem('hasUserSentMessage', 'true'); + } + + // Прокручиваем контейнер с сообщениями вниз + await nextTick(); + scrollToBottom(); } catch (error) { console.error('Error loading chat history:', error); } finally { isLoadingMore.value = false; + isLoading.value = false; } }; @@ -786,22 +828,33 @@ const formatMessage = (text) => { return DOMPurify.sanitize(rawHtml); }; -// Функция для проверки наличия гостевых сообщений +// Проверяет наличие гостевых сообщений const checkGuestMessages = async () => { try { - const response = await api.get('/api/chat/check-session'); - console.log('Session check response:', response.data); + // Проверяем сессию через auth/check вместо chat/check-session + const sessionCheck = await axios.get('/api/auth/check'); + console.log('Session auth check response:', sessionCheck.data); - // После инициализации сессии загружаем сообщения - if (!isAuthenticated.value) { - // Если пользователь не авторизован, попробуем загрузить гостевые сообщения - await loadMoreMessages(); + // Проверяем наличие сообщений в localStorage + const storedMessages = localStorage.getItem('guestMessages'); + if (storedMessages) { + const parsedMessages = JSON.parse(storedMessages); + if (Array.isArray(parsedMessages) && parsedMessages.length > 0) { + // Если есть сообщения и пользователь не аутентифицирован, показываем их + if (!auth.isAuthenticated.value) { + console.log('Found guest messages in localStorage:', parsedMessages); + messages.value = [...messages.value, ...parsedMessages]; + hasUserSentMessage.value = true; + localStorage.setItem('hasUserSentMessage', 'true'); + } else { + // Если пользователь аутентифицирован, удаляем гостевые сообщения из localStorage + console.log('User is authenticated, removing guest messages from localStorage'); + localStorage.removeItem('guestMessages'); + } + } } - - return response.data; } catch (error) { console.error('Error checking guest messages:', error); - return { success: false }; } }; @@ -852,6 +905,21 @@ watch(() => auth.isAuthenticated.value, async (newValue) => { } }); +// Отслеживаем изменения в идентификаторах +watch(() => auth.telegramId?.value, (newValue) => { + if (newValue) { + console.log('Telegram ID изменился:', newValue); + } +}); + +// Отслеживаем успешную авторизацию через Telegram +watch(() => auth.authType?.value, (newValue) => { + if (newValue === 'telegram') { + console.log('Авторизация через Telegram завершена, authType:', newValue); + console.log('Текущий telegramId:', auth.telegramId?.value); + } +}); + onBeforeUnmount(() => { // Удаляем слушатель if (messagesContainer.value) { @@ -862,4 +930,79 @@ onBeforeUnmount(() => { } document.querySelector('.app-container')?.classList.remove('menu-open'); }); + +// Обработчик для Telegram аутентификации +const handleTelegramAuth = async () => { + try { + const { data } = await axios.post('/api/auth/telegram/init'); + const { verificationCode, botLink } = data; + + // Показываем код верификации + showTelegramVerification.value = true; + telegramVerificationCode.value = verificationCode; + telegramBotLink.value = botLink; + + // Запускаем проверку статуса аутентификации + telegramAuthCheckInterval.value = setInterval(async () => { + try { + const response = await axios.get('/api/auth/check'); + console.log('Проверка авторизации:', response.data); + + if (response.data.authenticated && response.data.telegramId) { + clearInterval(telegramAuthCheckInterval.value); + telegramAuthCheckInterval.value = null; + showTelegramVerification.value = false; + + console.log('Telegram ID получен:', response.data.telegramId); + + // Обновляем информацию об авторизации через метод updateAuth + auth.updateAuth({ + authenticated: true, + authType: response.data.authType, + userId: response.data.userId, + telegramId: response.data.telegramId, + isAdmin: response.data.isAdmin + }); + + // Сначала обновляем идентификаторы, затем связываем сообщения и обновляем историю + await auth.checkAuth(); + + // Весь процесс загрузки истории будет выполнен через watch на isAuthenticated + } + } catch (error) { + console.error('Error checking auth status:', error); + } + }, 2000); + + // Очищаем интервал через 5 минут + setTimeout(() => { + if (telegramAuthCheckInterval.value) { + clearInterval(telegramAuthCheckInterval.value); + telegramAuthCheckInterval.value = null; + showTelegramVerification.value = false; + } + }, 5 * 60 * 1000); + + } catch (error) { + console.error('Error initializing Telegram auth:', error); + alert('Ошибка при инициализации Telegram аутентификации'); + } +}; + +// Отслеживаем изменения в сообщениях +watch(() => messages.value.length, (newLength, oldLength) => { + if (newLength > 0) { + // Сортируем сообщения по дате/времени + messages.value.sort((a, b) => { + const dateA = new Date(a.timestamp || a.created_at); + const dateB = new Date(b.timestamp || b.created_at); + return dateA - dateB; + }); + + // Прокручиваем к последнему сообщению + nextTick(() => { + scrollToBottom(); + }); + } +});