ваше сообщение коммита

This commit is contained in:
2025-04-13 18:18:04 +03:00
parent 9bb84c8297
commit 617bcd19be
10 changed files with 663 additions and 563 deletions

View File

@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS verification_codes (
code VARCHAR(6) NOT NULL, code VARCHAR(6) NOT NULL,
provider VARCHAR(50) NOT NULL, -- 'telegram', 'email' provider VARCHAR(50) NOT NULL, -- 'telegram', 'email'
provider_id VARCHAR(255) NOT NULL, -- telegram_id или email provider_id VARCHAR(255) NOT NULL, -- telegram_id или email
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, user_id INTEGER NULL REFERENCES users(id) ON DELETE CASCADE, -- Может быть NULL
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL,
used BOOLEAN DEFAULT FALSE used BOOLEAN DEFAULT FALSE

View File

@@ -0,0 +1,12 @@
-- Изменяем ограничение для поля user_id в таблице verification_codes
ALTER TABLE verification_codes
ALTER COLUMN user_id DROP NOT NULL;
-- Обновляем комментарий в информационной схеме
COMMENT ON COLUMN verification_codes.user_id IS 'ID пользователя (может быть NULL для временных кодов)';
-- Логирование для отслеживания выполнения миграции
DO $$
BEGIN
RAISE NOTICE 'Migration 012: Updated verification_codes table to allow NULL values for user_id';
END $$;

View File

@@ -17,6 +17,7 @@ const { initTelegramAuth } = require('../services/telegramBot');
const emailAuth = require('../services/emailAuth'); const emailAuth = require('../services/emailAuth');
const verificationService = require('../services/verification-service'); const verificationService = require('../services/verification-service');
const { processGuestMessages } = require('./chat'); // Импортируем функцию обработки гостевых сообщений const { processGuestMessages } = require('./chat'); // Импортируем функцию обработки гостевых сообщений
const nonceStore = {};
// Создайте лимитер для попыток аутентификации // Создайте лимитер для попыток аутентификации
const authLimiter = rateLimit({ const authLimiter = rateLimit({
@@ -221,138 +222,89 @@ router.post('/verify', async (req, res) => {
// Аутентификация через Telegram // Аутентификация через Telegram
router.post('/telegram/verify', async (req, res) => { router.post('/telegram/verify', async (req, res) => {
try { try {
const { code } = req.body; const { telegramId, verificationCode } = req.body;
logger.info(`[telegram/verify] Verifying code: ${code}`); if (!telegramId || !verificationCode) {
return res.status(400).json({
// Проверяем код верификации success: false,
const verificationResult = await verifyTelegramCode(code); error: 'Missing required fields'
if (!verificationResult.success) {
return res.status(400).json({
success: false,
error: 'Неверный код подтверждения'
}); });
} }
const { telegramId, authData } = verificationResult;
logger.info(`[telegram/verify] Code verified successfully for telegramId: ${telegramId}`);
// Сохраняем гостевые ID до проверки
const guestId = req.session.guestId;
let userId;
// Если пользователь уже аутентифицирован, добавляем Telegram к существующему аккаунту
if (req.session.authenticated && req.session.userId) {
userId = req.session.userId;
// Проверяем не связан ли Telegram с другим аккаунтом
const existingTelegram = await db.query(`
SELECT user_id FROM user_identities
WHERE provider = 'telegram' AND provider_id = $1
`, [telegramId]);
if (existingTelegram.rows.length > 0) {
return res.status(400).json({
success: false,
error: 'Этот Telegram аккаунт уже связан с другим пользователем'
});
}
// Добавляем Telegram к существующему пользователю
await saveUserIdentity(userId, 'telegram', telegramId, true);
logger.info(`[telegram/verify] Added Telegram identity ${telegramId} to existing user ${userId}`);
} else {
// Ищем существующего пользователя по Telegram ID
const existingUser = await db.query(`
SELECT u.* FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'telegram' AND ui.provider_id = $1
`, [telegramId]);
if (existingUser.rows.length > 0) {
// Используем существующего пользователя
userId = existingUser.rows[0].id;
logger.info(`[telegram/verify] Found existing user with ID ${userId} for telegramId ${telegramId}`);
} else {
// Создаем нового пользователя
const newUser = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
userId = newUser.rows[0].id;
// Добавляем Telegram идентификатор
await saveUserIdentity(userId, 'telegram', telegramId, true);
logger.info(`[telegram/verify] Created new user with ID ${userId} for telegramId ${telegramId}`);
}
}
// Если есть гостевые сообщения, переносим их logger.info(`[telegram/verify] Verifying Telegram auth for ID: ${telegramId}`);
if (guestId && !req.session.processedGuestIds?.includes(guestId)) {
await processGuestMessages(userId, guestId); // Сохраняем гостевой ID из текущей сессии
// Сохраняем обработанный guestId чтобы избежать повторной обработки const guestId = req.session.guestId;
if (!req.session.processedGuestIds) {
req.session.processedGuestIds = [];
}
req.session.processedGuestIds.push(guestId);
logger.info(`[telegram/verify] Processed guest messages for user ${userId} with guest ID ${guestId}`);
}
// Создаем новую сессию // Передаем сессию в метод верификации
const verificationResult = await authService.verifyTelegramAuth(
telegramId,
verificationCode,
req.session
);
if (!verificationResult.success) {
return res.status(400).json({
success: false,
error: verificationResult.error || 'Verification failed'
});
}
// Создаем новую сессию для этого telegramId
req.session.regenerate(async (err) => { req.session.regenerate(async (err) => {
if (err) { if (err) {
logger.error('Error regenerating session:', err); logger.error('[telegram/verify] Error regenerating session:', err);
return res.status(500).json({ success: false, error: 'Server error' }); return res.status(500).json({
success: false,
error: 'Session regeneration failed'
});
} }
// Устанавливаем данные новой сессии // Устанавливаем данные в новой сессии
req.session.authenticated = true; req.session.userId = verificationResult.userId;
req.session.userId = userId;
req.session.telegramId = telegramId; req.session.telegramId = telegramId;
req.session.authType = 'telegram'; req.session.authType = 'telegram';
req.session.authenticated = true;
req.session.role = verificationResult.role;
// Сохраняем список обработанных гостевых ID // Восстанавливаем гостевой ID, если он был
if (req.session.processedGuestIds?.length > 0) { if (guestId) {
req.session.processedGuestIds = [...req.session.processedGuestIds]; req.session.guestId = guestId;
} }
// Удаляем временные данные
delete req.session.tempUserId;
delete req.session.guestId;
delete req.session.pendingTelegramId;
if (authData.first_name) {
req.session.telegramFirstName = authData.first_name;
}
if (authData.username) {
req.session.telegramUsername = authData.username;
}
// Сохраняем сессию // Сохраняем сессию
req.session.save((err) => { await new Promise((resolve, reject) => {
if (err) { req.session.save(err => {
logger.error('Error saving session:', err); if (err) {
return res.status(500).json({ success: false, error: 'Server error' }); logger.error('[telegram/verify] Error saving session:', err);
} reject(err);
} else {
res.json({ logger.info(`[telegram/verify] Session saved for user ${verificationResult.userId}`);
success: true, resolve();
userId, }
telegramId,
authenticated: true,
authType: 'telegram',
username: authData.username,
firstName: authData.first_name
}); });
}); });
// Связываем гостевые сообщения только если это новый пользователь
if (verificationResult.isNewUser && guestId) {
const linkResults = await authService.linkGuestMessagesAfterAuth(verificationResult.userId, guestId);
logger.info(`[telegram/verify] Guest messages linking results for new user:`, linkResults);
}
return res.json({
success: true,
userId: verificationResult.userId,
role: verificationResult.role,
telegramId,
isNewUser: verificationResult.isNewUser
});
}); });
} catch (error) { } catch (error) {
logger.error('[telegram/verify] Error:', error); logger.error('[telegram/verify] Error:', error);
res.status(500).json({ success: false, error: 'Server error' }); return res.status(500).json({
success: false,
error: 'Internal server error'
});
} }
}); });
@@ -729,29 +681,46 @@ router.get('/check', async (req, res) => {
}); });
// Выход из системы // Выход из системы
router.post('/logout', (req, res) => { router.post('/logout', async (req, res) => {
try { try {
// Сохраняем важные данные перед уничтожением сессии // Очищаем все идентификаторы сессии
const guestId = req.session.guestId; req.session.authenticated = false;
req.session.userId = null;
// Полностью уничтожаем сессию req.session.address = null;
req.session.destroy((err) => { req.session.telegramId = null;
if (err) { req.session.email = null;
logger.error('Error destroying session:', err); req.session.isAdmin = false;
return res.status(500).json({ success: false, error: 'Server error' }); req.session.guestId = null;
} req.session.previousGuestId = null;
req.session.processedGuestIds = [];
// Очищаем куки сессии req.session.pendingEmail = null;
res.clearCookie('connect.sid'); req.session.authType = null;
res.json({ // Сохраняем изменения в сессии
success: true, await new Promise((resolve, reject) => {
guestId // Возвращаем guestId для фронтенда req.session.save(err => {
if (err) {
logger.error('[logout] Error saving session:', err);
reject(err);
} else {
logger.info('[logout] Session cleared successfully');
resolve();
}
}); });
}); });
// Уничтожаем сессию полностью
req.session.destroy((err) => {
if (err) {
logger.error('[logout] Error destroying session:', err);
return res.status(500).json({ success: false, error: 'Error during logout' });
}
res.clearCookie('connect.sid');
res.json({ success: true, message: 'Logged out successfully' });
});
} catch (error) { } catch (error) {
logger.error('Error in logout:', error); logger.error('[logout] Error:', error);
res.status(500).json({ success: false, error: 'Server error' }); res.status(500).json({ success: false, error: 'Internal server error during logout' });
} }
}); });
@@ -773,30 +742,20 @@ router.get('/telegram', (req, res) => {
// Маршрут для получения кода подтверждения Telegram // Маршрут для получения кода подтверждения Telegram
router.get('/telegram/code', authLimiter, async (req, res) => { router.get('/telegram/code', authLimiter, async (req, res) => {
try { try {
// Создаем или получаем ID пользователя // Создаем код через сервис телеграм авторизации
let userId; const authData = await initTelegramAuth(req.session);
if (req.session.authenticated && req.session.userId) { if (!authData.verificationCode) {
userId = req.session.userId; return res.status(500).json({
} else { success: false,
const userResult = await db.query( error: 'Failed to generate verification code'
'INSERT INTO users (created_at) VALUES (NOW()) RETURNING id' });
);
userId = userResult.rows[0].id;
req.session.tempUserId = userId;
} }
// Создаем код через сервис верификации
const code = await verificationService.createVerificationCode(
'telegram',
req.session.guestId || 'temp',
userId
);
res.json({ res.json({
success: true, success: true,
message: 'Отправьте этот код боту @' + process.env.TELEGRAM_BOT_USERNAME, message: 'Отправьте этот код боту @' + process.env.TELEGRAM_BOT_USERNAME,
code, code: authData.verificationCode,
botUsername: process.env.TELEGRAM_BOT_USERNAME || 'YourDAppBot' botUsername: process.env.TELEGRAM_BOT_USERNAME || 'YourDAppBot'
}); });
} catch (error) { } catch (error) {

View File

@@ -393,60 +393,63 @@ class AuthService {
/** /**
* Проверка Telegram аутентификации * Проверка Telegram аутентификации
*/ */
async verifyTelegramAuth(telegramId, verificationCode) { async verifyTelegramAuth(telegramId, verificationCode, session) {
try { try {
logger.info(`Verifying Telegram auth for ID: ${telegramId} with code: ${verificationCode}`); logger.info(`[verifyTelegramAuth] Starting for telegramId: ${telegramId}`);
// Находим или создаем пользователя // Проверяем, существует ли уже пользователь с таким Telegram ID
const userResult = await db.query( const existingUserResult = await db.query(
`SELECT u.* FROM users u `SELECT u.*, ui.provider, ui.provider_id
FROM users u
JOIN user_identities ui ON u.id = ui.user_id JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'telegram' AND ui.provider_id = $1`, WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
[telegramId] [telegramId]
); );
let userId; let userId;
if (userResult.rows.length > 0) { let isNewUser = false;
userId = userResult.rows[0].id;
logger.info(`Found existing user ${userId} for Telegram ID ${telegramId}`); // Если пользователь существует с таким telegramId, используем его
if (existingUserResult.rows.length > 0) {
const existingUser = existingUserResult.rows[0];
userId = existingUser.id;
logger.info(`[verifyTelegramAuth] Found existing user ${userId} for Telegram ID ${telegramId}`);
} else { } else {
// Создаем нового пользователя с ролью user // Создаем нового пользователя для нового telegramId
const newUserResult = await db.query( const newUserResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id', 'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user'] ['user']
); );
userId = newUserResult.rows[0].id; userId = newUserResult.rows[0].id;
isNewUser = true;
// Добавляем Telegram идентификатор // Добавляем Telegram идентификатор
await db.query( await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)', 'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
[userId, 'telegram', telegramId] [userId, 'telegram', telegramId]
); );
logger.info(`Created new user ${userId} for Telegram ID ${telegramId}`);
logger.info(`[verifyTelegramAuth] Created new user ${userId} for Telegram ID ${telegramId}`);
} }
// Проверяем наличие кошелька и определяем роль // Если есть гостевой ID в сессии, сохраняем его для нового пользователя
const wallet = await this.getLinkedWallet(userId); if (session.guestId && isNewUser) {
let role = 'user'; // Базовая роль для доступа к чату await db.query(
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING',
if (wallet) { [userId, 'guest', session.guestId]
// Если есть кошелек, проверяем баланс токенов );
const isAdmin = await this.checkAdminRole(wallet); logger.info(`[verifyTelegramAuth] Saved guest ID ${session.guestId} for user ${userId}`);
role = isAdmin ? 'admin' : 'user';
logger.info(`User ${userId} has wallet ${wallet}, role set to ${role}`);
} else {
logger.info(`User ${userId} has no wallet, using basic user role`);
} }
return { return {
success: true, success: true,
userId, userId,
role, role: 'user',
telegramId, telegramId,
wallet: wallet || null isNewUser
}; };
} catch (error) { } catch (error) {
logger.error('Error in Telegram auth verification:', error); logger.error('[verifyTelegramAuth] Error:', error);
throw error; throw error;
} }
} }
@@ -574,68 +577,104 @@ class AuthService {
return { success: true, message: 'No guest ID to process' }; return { success: true, message: 'No guest ID to process' };
} }
// Получаем все гостевые сообщения для этого ID // Проверяем, не привязаны ли уже эти гостевые сообщения к другому пользователю
const guestMessagesResult = await db.query( const existingMessagesCheck = await db.query(
'SELECT * FROM guest_messages WHERE guest_id = $1 ORDER BY created_at ASC', `SELECT DISTINCT user_id
FROM messages
WHERE guest_message_id IN (
SELECT id FROM guest_messages WHERE guest_id = $1
)`,
[currentGuestId] [currentGuestId]
); );
if (guestMessagesResult.rows.length === 0) { if (existingMessagesCheck.rows.length > 0) {
logger.info(`[linkGuestMessagesAfterAuth] No messages found for guest ID ${currentGuestId}`); const existingUserId = existingMessagesCheck.rows[0].user_id;
return { success: true, message: 'No messages found' }; if (existingUserId !== userId) {
} logger.warn(`[linkGuestMessagesAfterAuth] Guest messages for ${currentGuestId} are already linked to user ${existingUserId}`);
return {
const guestMessages = guestMessagesResult.rows; success: false,
logger.info(`[linkGuestMessagesAfterAuth] Found ${guestMessages.length} messages for guest ID ${currentGuestId}`); error: 'Guest messages are already linked to another user'
};
// Создаем одну беседу для всех сообщений этого гостевого ID
const firstMessage = guestMessages[0];
const title = firstMessage.content.length > 30
? `${firstMessage.content.substring(0, 30)}...`
: firstMessage.content;
const newConversationResult = await db.query(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
[userId, title]
);
const conversation = newConversationResult.rows[0];
logger.info(`[linkGuestMessagesAfterAuth] Created conversation ${conversation.id} for user ${userId}`);
// Переносим все сообщения в новую беседу
for (const guestMessage of guestMessages) {
await db.query(
`INSERT INTO messages
(conversation_id, content, sender_type, role, channel, guest_message_id, created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7)`,
[
conversation.id,
guestMessage.content,
guestMessage.is_ai ? 'assistant' : 'user',
guestMessage.is_ai ? 'assistant' : 'user',
'web',
guestMessage.id,
guestMessage.created_at
]
);
}
// Удаляем гостевой идентификатор после успешной привязки
await db.query(
'DELETE FROM user_identities WHERE user_id = $1 AND identity_type = $2 AND identity_value = $3',
[userId, 'guest', currentGuestId]
);
logger.info(`[linkGuestMessagesAfterAuth] Deleted guest identity ${currentGuestId}`);
return {
success: true,
result: {
conversationId: conversation.id,
message: `Processed ${guestMessages.length} guest messages`,
success: true
} }
}; }
// Блокируем таблицу guest_messages для атомарной операции
await db.query('BEGIN');
try {
// Получаем все гостевые сообщения для этого ID
const guestMessagesResult = await db.query(
'SELECT * FROM guest_messages WHERE guest_id = $1 ORDER BY created_at ASC FOR UPDATE',
[currentGuestId]
);
if (guestMessagesResult.rows.length === 0) {
await db.query('COMMIT');
logger.info(`[linkGuestMessagesAfterAuth] No messages found for guest ID ${currentGuestId}`);
return { success: true, message: 'No messages found' };
}
const guestMessages = guestMessagesResult.rows;
logger.info(`[linkGuestMessagesAfterAuth] Found ${guestMessages.length} messages for guest ID ${currentGuestId}`);
// Создаем одну беседу для всех сообщений этого гостевого ID
const firstMessage = guestMessages[0];
const title = firstMessage.content.length > 30
? `${firstMessage.content.substring(0, 30)}...`
: firstMessage.content;
const newConversationResult = await db.query(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
[userId, title]
);
const conversation = newConversationResult.rows[0];
logger.info(`[linkGuestMessagesAfterAuth] Created conversation ${conversation.id} for user ${userId}`);
// Переносим все сообщения в новую беседу
for (const guestMessage of guestMessages) {
await db.query(
`INSERT INTO messages
(conversation_id, content, sender_type, role, channel, guest_message_id, created_at, user_id)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8)`,
[
conversation.id,
guestMessage.content,
guestMessage.is_ai ? 'assistant' : 'user',
guestMessage.is_ai ? 'assistant' : 'user',
'web',
guestMessage.id,
guestMessage.created_at,
userId
]
);
}
// Удаляем обработанные гостевые сообщения
await db.query('DELETE FROM guest_messages WHERE guest_id = $1', [currentGuestId]);
// Удаляем гостевой идентификатор
await db.query(
'DELETE FROM user_identities WHERE user_id = $1 AND provider = $2 AND provider_id = $3',
[userId, 'guest', currentGuestId]
);
await db.query('COMMIT');
logger.info(`[linkGuestMessagesAfterAuth] Successfully processed guest ID ${currentGuestId}`);
return {
success: true,
result: {
conversationId: conversation.id,
message: `Processed ${guestMessages.length} guest messages`,
success: true
}
};
} catch (error) {
await db.query('ROLLBACK');
throw error;
}
} catch (error) { } catch (error) {
logger.error('[linkGuestMessagesAfterAuth] Error:', error); logger.error('[linkGuestMessagesAfterAuth] Error:', error);
throw error; throw error;

View File

@@ -3,6 +3,7 @@ const logger = require('../utils/logger');
const db = require('../db'); const db = require('../db');
const authService = require('./auth-service'); const authService = require('./auth-service');
const verificationService = require('./verification-service'); const verificationService = require('./verification-service');
const crypto = require('crypto');
let botInstance = null; let botInstance = null;
@@ -38,7 +39,7 @@ async function getBot() {
const verification = codeResult.rows[0]; const verification = codeResult.rows[0];
const providerId = verification.provider_id; const providerId = verification.provider_id;
let userId = verification.user_id; let userId;
// Отмечаем код как использованный // Отмечаем код как использованный
await db.query( await db.query(
@@ -57,40 +58,37 @@ async function getBot() {
); );
if (existingTelegramUser.rows.length > 0) { if (existingTelegramUser.rows.length > 0) {
// Если пользователь с таким Telegram ID уже существует, // Если пользователь с таким Telegram ID уже существует, используем его
// используем его ID вместо создания нового связывания userId = existingTelegramUser.rows[0].user_id;
const existingUserId = existingTelegramUser.rows[0].user_id; logger.info(`Using existing user ${userId} for Telegram account ${ctx.from.id}`);
} else {
// Создаем нового пользователя, если нет существующего с этим Telegram ID
const userResult = await db.query(
'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
['user']
);
userId = userResult.rows[0].id;
// Связываем гостевой ID с существующим пользователем, если его еще нет // Связываем Telegram с новым пользователем
const guestIdentity = await db.query( await db.query(
`SELECT * FROM user_identities `INSERT INTO user_identities
WHERE user_id = $1 AND provider = 'guest' AND provider_id = $2`, (user_id, provider, provider_id, created_at)
[existingUserId, providerId] VALUES ($1, $2, $3, NOW())`,
[userId, 'telegram', ctx.from.id.toString()]
); );
if (guestIdentity.rows.length === 0 && providerId) { // Если был гостевой ID, связываем его с новым пользователем
if (providerId) {
await db.query( await db.query(
`INSERT INTO user_identities `INSERT INTO user_identities
(user_id, provider, provider_id, created_at) (user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW()) VALUES ($1, $2, $3, NOW())
ON CONFLICT (provider, provider_id) DO UPDATE SET user_id = $1`, ON CONFLICT (provider, provider_id) DO NOTHING`,
[existingUserId, 'guest', providerId] [userId, 'guest', providerId]
); );
} }
userId = existingUserId; logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
logger.info(`Using existing user ${userId} for Telegram account ${ctx.from.id}`);
} else {
// Связываем Telegram с пользователем
await db.query(
`INSERT INTO user_identities
(user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (provider, provider_id) DO UPDATE SET user_id = $1`,
[userId, 'telegram', ctx.from.id.toString()]
);
logger.info(`User ${userId} successfully linked Telegram account ${ctx.from.id}`);
} }
// Обновляем сессию в базе данных // Обновляем сессию в базе данных
@@ -149,58 +147,19 @@ async function stopBot() {
// Инициализация процесса аутентификации // Инициализация процесса аутентификации
async function initTelegramAuth(session) { async function initTelegramAuth(session) {
try { try {
// Создаем или получаем ID пользователя // Используем временный идентификатор для создания кода верификации
let userId; // Реальный пользователь будет создан или найден при проверке кода через бота
const tempId = crypto.randomBytes(16).toString('hex');
if (session.authenticated && session.userId) { // Создаем код через сервис верификации с временным идентификатором
// Если пользователь уже аутентифицирован, используем его ID
userId = session.userId;
} else if (session.guestId) {
// Проверяем, есть ли уже пользователь с этим guestId
const existingUser = await db.query(
`SELECT u.id
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'guest' AND ui.provider_id = $1`,
[session.guestId]
);
if (existingUser.rows.length > 0) {
// Используем существующего пользователя
userId = existingUser.rows[0].id;
} else {
// Создаем нового пользователя
const userResult = await db.query(
'INSERT INTO users (created_at) VALUES (NOW()) RETURNING id'
);
userId = userResult.rows[0].id;
// Связываем гостевой ID с пользователем
await db.query(
`INSERT INTO user_identities
(user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW())`,
[userId, 'guest', session.guestId]
);
}
session.tempUserId = userId;
} else {
// Создаем нового пользователя без гостевого ID
const userResult = await db.query(
'INSERT INTO users (created_at) VALUES (NOW()) RETURNING id'
);
userId = userResult.rows[0].id;
session.tempUserId = userId;
}
// Создаем код через сервис верификации
const code = await verificationService.createVerificationCode( const code = await verificationService.createVerificationCode(
'telegram', 'telegram',
session.guestId || 'temp', session.guestId || tempId,
userId null // Не привязываем к конкретному userId на этом этапе
); );
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}`);
return { return {
verificationCode: code, verificationCode: code,
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}` botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`

View File

@@ -20,14 +20,24 @@ class VerificationService {
const expiresAt = new Date(Date.now() + this.expirationMinutes * 60 * 1000); const expiresAt = new Date(Date.now() + this.expirationMinutes * 60 * 1000);
try { try {
logger.info(`Creating verification code for ${provider}:${providerId}, userId: ${userId}`); logger.info(`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`);
await db.query( // Если userId не указан, добавляем запись без ссылки на пользователя
`INSERT INTO verification_codes if (userId === null || userId === undefined) {
(code, provider, provider_id, user_id, expires_at) await db.query(
VALUES ($1, $2, $3, $4, $5)`, `INSERT INTO verification_codes
[code, provider, providerId, userId, expiresAt] (code, provider, provider_id, expires_at)
); VALUES ($1, $2, $3, $4)`,
[code, provider, providerId, expiresAt]
);
} else {
await db.query(
`INSERT INTO verification_codes
(code, provider, provider_id, user_id, expires_at)
VALUES ($1, $2, $3, $4, $5)`,
[code, provider, providerId, userId, expiresAt]
);
}
logger.info(`Verification code created successfully for ${provider}:${providerId}`); logger.info(`Verification code created successfully for ${provider}:${providerId}`);
return code; return code;

View File

@@ -0,0 +1,77 @@
// Скрипт для обновления таблицы verification_codes
const { Pool } = require('pg');
require('dotenv').config();
// Создаем подключение к базе данных
const pool = new Pool({
host: process.env.POSTGRES_HOST || 'localhost',
port: process.env.POSTGRES_PORT || 5432,
database: process.env.POSTGRES_DB || 'dapp_business',
user: process.env.POSTGRES_USER || 'postgres',
password: process.env.POSTGRES_PASSWORD || 'postgres',
});
async function updateVerificationTable() {
try {
console.log('Начинаем обновление таблицы verification_codes...');
// Проверяем, существует ли таблица
const checkTableResult = await pool.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'verification_codes'
);
`);
const tableExists = checkTableResult.rows[0].exists;
if (!tableExists) {
console.log('Таблица verification_codes не существует. Пропускаем обновление.');
return;
}
// Проверяем, разрешает ли уже колонка null значения
const checkColumnResult = await pool.query(`
SELECT is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'verification_codes'
AND column_name = 'user_id';
`);
if (checkColumnResult.rows.length > 0 && checkColumnResult.rows[0].is_nullable === 'YES') {
console.log('Колонка user_id уже разрешает NULL значения. Пропускаем обновление.');
return;
}
// Начинаем транзакцию
await pool.query('BEGIN');
// Изменяем ограничение для поля user_id
await pool.query(`
ALTER TABLE verification_codes
ALTER COLUMN user_id DROP NOT NULL;
`);
// Добавляем комментарий к колонке
await pool.query(`
COMMENT ON COLUMN verification_codes.user_id IS 'ID пользователя (может быть NULL для временных кодов)';
`);
// Фиксируем транзакцию
await pool.query('COMMIT');
console.log('Таблица verification_codes успешно обновлена!');
} catch (error) {
// Откатываем транзакцию в случае ошибки
await pool.query('ROLLBACK');
console.error('Ошибка при обновлении таблицы verification_codes:', error);
} finally {
// Закрываем соединение с базой данных
await pool.end();
}
}
// Выполняем обновление
updateVerificationTable();

View File

@@ -3,6 +3,13 @@
font-family: 'Roboto', 'Helvetica Neue', Arial, sans-serif; font-family: 'Roboto', 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #fff;
} }
/* Стили для монопространственных шрифтов (код, верификация) */ /* Стили для монопространственных шрифтов (код, верификация) */
@@ -35,10 +42,9 @@ input, textarea {
.app-container { .app-container {
display: flex; display: flex;
height: 100vh; flex-direction: column;
width: 100%; min-height: 100vh;
background-color: #f5f5f5; background-color: #fff;
position: relative;
} }
/* Стили для боковой панели */ /* Стили для боковой панели */
@@ -160,14 +166,10 @@ input, textarea {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 190px; /* 40px + 110px (sidebar) + 40px (button) */
margin-right: 190px; /* 40px + 110px (sidebar) + 40px (button) */
transition: margin 0.3s ease;
max-width: 1200px; max-width: 1200px;
margin: 0 auto;
padding: 0 20px; padding: 0 20px;
height: 100vh; width: 100%;
position: relative;
box-sizing: border-box;
} }
.sidebar-expanded ~ .main-content { .sidebar-expanded ~ .main-content {
@@ -188,27 +190,18 @@ input, textarea {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; margin: 20px 0;
height: calc(100vh - 140px); min-height: 0;
position: relative;
box-sizing: border-box;
} }
.chat-messages { .chat-messages {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
background-color: white;
border-radius: 8px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); background: #fff;
border-radius: 8px;
border: 1px solid #e0e0e0;
margin-bottom: 20px; margin-bottom: 20px;
width: 100%;
box-sizing: border-box;
position: absolute;
top: 0;
bottom: 120px; /* Увеличиваем отступ для возможного расширения chat-input */
left: 0;
right: 0;
} }
.message { .message {
@@ -282,57 +275,45 @@ input, textarea {
border: 1px solid #F44336; border: 1px solid #F44336;
} }
/* Стили для ввода сообщений */
.chat-input { .chat-input {
display: flex; display: flex;
background-color: white; gap: 10px;
padding: 20px;
background: #fff;
border-radius: 8px; border-radius: 8px;
padding: 15px; border: 1px solid #e0e0e0;
width: 100%;
box-sizing: border-box;
position: absolute;
bottom: 40px;
left: 0;
right: 0;
min-height: 70px;
max-height: 200px; /* Максимальная высота */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
} }
.chat-input textarea { .chat-input textarea {
flex: 1; flex: 1;
padding: 12px;
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
resize: vertical; /* Разрешаем вертикальное изменение размера */
padding: 10px;
min-height: 40px;
max-height: 170px; /* Максимальная высота минус padding */
font-family: inherit;
border-radius: 4px; border-radius: 4px;
background-color: white; resize: none;
line-height: 1.4; font-size: 14px;
line-height: 1.5;
min-height: 60px;
max-height: 200px;
} }
.chat-input button { .chat-input button {
background-color: white;
color: #333;
border: 1px solid #333;
border-radius: 4px;
padding: 0 20px; padding: 0 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer; cursor: pointer;
height: 40px;
margin-left: 10px;
align-self: flex-start;
transition: background-color 0.2s;
font-size: 14px; font-size: 14px;
transition: background-color 0.3s;
} }
.chat-input button:hover:not(:disabled) { .chat-input button:hover:not(:disabled) {
background-color: #f0f0f0; background: #45a049;
} }
.chat-input button:disabled { .chat-input button:disabled {
background-color: #f5f5f5; background: #ccc;
color: #999;
border-color: #ddd;
cursor: not-allowed; cursor: not-allowed;
} }
@@ -744,43 +725,63 @@ input, textarea {
word-break: break-all; word-break: break-all;
} }
/* Медиа-запрос для узких экранов */ /* Медиа-запросы для адаптивности */
@media (max-width: 1300px) { @media (max-width: 1200px) {
.wallet-sidebar { .header-content,
width: 286px; .main-content {
min-width: 286px; max-width: 100%;
padding: 15px; padding: 0 15px;
} }
}
.verification-code code {
font-size: 14px; /* Медиа-запросы для мобильных устройств */
padding: 6px 10px; @media screen and (max-width: 768px) {
.header-content {
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 10px;
} }
.auth-btn, .bot-link, .cancel-btn { .header-text {
padding: 10px 8px; flex: 1;
font-size: 13px; margin-right: 10px;
min-width: 0; /* Важно для корректной работы text-overflow */
} }
.verify-btn, .send-email-btn { .title {
padding: 0 10px; white-space: nowrap;
font-size: 13px; overflow: hidden;
text-overflow: ellipsis;
font-size: 1.2rem;
margin: 0;
} }
.main-content:not(.no-right-sidebar) { .subtitle {
margin-right: 286px; white-space: normal;
font-size: 0.9rem;
margin: 0;
line-height: 1.2;
} }
.close-wallet-sidebar { .header-wallet-btn {
width: 26px; flex-shrink: 0;
height: 26px; margin-left: 10px;
font-size: 18px;
top: 8px;
right: 8px;
} }
}
.wallet-buttons {
margin-top: 35px; /* Дополнительные стили для очень маленьких экранов */
@media screen and (max-width: 480px) {
.header-content {
padding: 8px;
}
.title {
font-size: 1.1rem;
}
.subtitle {
font-size: 0.8rem;
} }
} }
@@ -999,43 +1000,119 @@ input, textarea {
} }
.header { .header {
background: #fff;
border-bottom: 1px solid #e0e0e0;
padding: 20px 0; padding: 20px 0;
margin-bottom: 24px; /* Уменьшенный отступ после заголовка */ position: sticky;
width: 100%; top: 0;
z-index: 100;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-text {
flex: 1;
} }
.title { .title {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: 500;
margin: 0; color: #333;
line-height: 1.2; margin-bottom: 5px;
} }
.subtitle { .subtitle {
font-size: 14px; font-size: 16px;
color: #666; color: #666;
margin: 5px 0 0 0;
} }
/* Стили для правой панели */ .header-wallet-btn {
.wallet-sidebar { margin-left: 20px;
position: fixed; padding: 10px;
top: 0; background: transparent;
right: 0; color: #333;
width: 300px; border: none;
height: 100vh; cursor: pointer;
background: white; display: flex;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1); align-items: center;
padding: 20px; gap: 10px;
overflow-y: auto; transition: background-color 0.3s;
z-index: 1000; position: relative;
width: 40px;
height: 40px;
justify-content: center;
} }
/* Стили для основного контента */ .header-wallet-btn:hover {
.content-container { background: rgba(0, 0, 0, 0.05);
padding: 20px 15px; }
margin-right: 40px; /* Одинаковый отступ справа */
margin-left: 40px; /* Одинаковый отступ слева */ .header-wallet-btn .nav-btn-number {
display: none;
}
.header-wallet-btn .nav-btn-text {
display: none;
}
.header-wallet-btn::before,
.header-wallet-btn::after,
.header-wallet-btn .hamburger-line {
content: '';
position: absolute;
width: 24px;
height: 2px;
background-color: #333;
transition: all 0.3s ease;
}
.header-wallet-btn::before {
top: 12px;
}
.header-wallet-btn::after {
bottom: 12px;
}
.header-wallet-btn .hamburger-line {
top: 50%;
transform: translateY(-50%);
}
/* Анимация при наведении */
.header-wallet-btn:hover::before {
top: 11px;
}
.header-wallet-btn:hover::after {
bottom: 11px;
}
/* Анимация при активном состоянии */
.header-wallet-btn.active::before {
transform: rotate(45deg);
top: 50%;
}
.header-wallet-btn.active::after {
transform: rotate(-45deg);
bottom: 50%;
}
.header-wallet-btn.active .hamburger-line {
opacity: 0;
}
.header-wallet-btn .hamburger-line {
top: 50%;
transform: translateY(-50%);
} }
.footer { .footer {
@@ -1232,3 +1309,42 @@ input, textarea {
padding: 0; padding: 0;
border-left: none; border-left: none;
} }
/* Стили для кнопок в чате */
.chat-buttons {
display: flex;
gap: 10px;
margin-top: 10px;
}
.chat-buttons button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.chat-buttons button:first-child {
background-color: #4CAF50;
color: white;
}
.chat-buttons button:first-child:hover:not(:disabled) {
background-color: #45a049;
}
.chat-buttons .clear-btn {
background-color: #f44336;
color: white;
}
.chat-buttons .clear-btn:hover:not(:disabled) {
background-color: #da190b;
}
.chat-buttons button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}

View File

@@ -274,11 +274,7 @@ export function useAuth() {
const disconnect = async () => { const disconnect = async () => {
try { 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'); await axios.post('/api/auth/logout');
// Обновляем состояние в памяти // Обновляем состояние в памяти
@@ -297,17 +293,22 @@ export function useAuth() {
// Очищаем списки идентификаторов // Очищаем списки идентификаторов
identities.value = []; identities.value = [];
processedGuestIds.value = [];
// Очищаем localStorage кроме guestId // Очищаем localStorage полностью
localStorage.removeItem('isAuthenticated'); localStorage.removeItem('isAuthenticated');
localStorage.removeItem('userId'); localStorage.removeItem('userId');
localStorage.removeItem('address'); localStorage.removeItem('address');
localStorage.removeItem('isAdmin'); localStorage.removeItem('isAdmin');
localStorage.removeItem('guestId');
localStorage.removeItem('guestMessages');
localStorage.removeItem('telegramId');
localStorage.removeItem('email');
// Удаляем класс подключенного кошелька // Удаляем класс подключенного кошелька
document.body.classList.remove('wallet-connected'); document.body.classList.remove('wallet-connected');
console.log('User disconnected successfully'); console.log('User disconnected successfully and all identifiers cleared');
return { success: true }; return { success: true };
} catch (error) { } catch (error) {

View File

@@ -1,51 +1,21 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<!-- Боковая панель / Меню -->
<div class="sidebar" :class="{ 'sidebar-expanded': showSidebar }">
<button class="menu-button" @click="toggleSidebar">
<div class="hamburger"></div>
</button>
<div class="nav-buttons">
<button class="nav-btn" @click="navigateTo('page1')">
<div class="nav-btn-number">1</div>
<div class="nav-btn-text">Кнопка 1</div>
</button>
<button class="nav-btn" @click="navigateTo('page2')">
<div class="nav-btn-number">2</div>
<div class="nav-btn-text">Кнопка 2</div>
</button>
<button class="nav-btn" @click="navigateTo('page3')">
<div class="nav-btn-number">3</div>
<div class="nav-btn-text">Кнопка 3</div>
</button>
<button class="nav-btn" @click="navigateTo('page4')">
<div class="nav-btn-number">4</div>
<div class="nav-btn-text">Кнопка 4</div>
</button>
<button class="nav-btn" @click="navigateTo('page5')">
<div class="nav-btn-number">5</div>
<div class="nav-btn-text">Кнопка 5</div>
</button>
<button class="nav-btn" @click="navigateTo('page6')">
<div class="nav-btn-number">6</div>
<div class="nav-btn-text">Кнопка 6</div>
</button>
</div>
<!-- Кнопка 7 в нижней части боковой панели -->
<button class="nav-btn sidebar-bottom-btn" @click="toggleWalletSidebar">
<div class="nav-btn-number">7</div>
<div class="nav-btn-text">{{ showWalletSidebar ? 'Скрыть панель' : 'Подключиться' }}</div>
</button>
</div>
<!-- Основной контент --> <!-- Основной контент -->
<div class="main-content" :class="{ 'no-right-sidebar': !showWalletSidebar }"> <div class="main-content" :class="{ 'no-right-sidebar': !showWalletSidebar }">
<div class="header"> <div class="header">
<h1 class="title">HB3 - Accelerator DLE (Digital Legal Entity - DAO Fork)</h1> <div class="header-content">
<p class="subtitle">Венчурный фонд и поставщик программного обеспечения</p> <div class="header-text">
<h1 class="title">HB3 - Accelerator DLE</h1>
<p class="subtitle">Венчурный фонд и поставщик программного обеспечения</p>
</div>
<button class="nav-btn header-wallet-btn" @click="toggleWalletSidebar" :class="{ active: showWalletSidebar }">
<div class="hamburger-line"></div>
<div class="nav-btn-number">7</div>
<div class="nav-btn-text">{{ showWalletSidebar ? 'Скрыть панель' : 'Подключиться' }}</div>
</button>
</div> </div>
</div>
<div class="chat-container"> <div class="chat-container">
<div class="chat-messages" ref="messagesContainer"> <div class="chat-messages" ref="messagesContainer">
<div v-for="message in messages" :key="message.id" <div v-for="message in messages" :key="message.id"
@@ -77,10 +47,15 @@
rows="3" rows="3"
autofocus autofocus
></textarea> ></textarea>
<button @click="handleMessage(newMessage)" :disabled="isLoading || !newMessage.trim()"> <div class="chat-buttons">
{{ isLoading ? 'Отправка...' : 'Отправить' }} <button @click="handleMessage(newMessage)" :disabled="isLoading || !newMessage.trim()">
</button> {{ isLoading ? 'Отправка...' : 'Отправить' }}
</div> </button>
<button @click="clearGuestMessages" class="clear-btn" :disabled="isLoading">
Очистить
</button>
</div>
</div>
</div> </div>
</div> </div>
@@ -281,18 +256,6 @@
<span class="token-symbol">{{ TOKEN_CONTRACTS.polygon.symbol }}</span> <span class="token-symbol">{{ TOKEN_CONTRACTS.polygon.symbol }}</span>
</div> </div>
</div> </div>
<!-- Блок отладочной информации (только для гостей) -->
<div v-if="!isAuthenticated" class="debug-info">
<h4>Гостевой идентификатор:</h4>
<div class="debug-item">
<code>{{ guestIdValue || 'Не задан' }}</code>
</div>
<div class="debug-buttons">
<button @click="refreshGuestId" class="small-button">Обновить ID</button>
<button @click="clearGuestMessages" class="small-button">Очистить сообщения</button>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -371,6 +334,7 @@ const isLoadingMore = ref(false);
const hasMoreMessages = ref(false); const hasMoreMessages = ref(false);
const offset = ref(0); const offset = ref(0);
const limit = ref(30); const limit = ref(30);
const isMessageLoadingInProgress = ref(false); // Добавляем флаг для отслеживания процесса загрузки
// Состояния для верификации // Состояния для верификации
const showTelegramVerification = ref(false); const showTelegramVerification = ref(false);
@@ -400,8 +364,7 @@ const successMessage = ref('');
const showSuccessMessage = ref(false); const showSuccessMessage = ref(false);
// Состояния для сайдбара // Состояния для сайдбара
const showSidebar = ref(false); const showWalletSidebar = ref(false);
const currentPage = ref('home');
// Добавляем состояние для балансов // Добавляем состояние для балансов
const tokenBalances = ref({ const tokenBalances = ref({
@@ -411,8 +374,8 @@ const tokenBalances = ref({
polygon: '0' polygon: '0'
}); });
// Состояние для отображения правой панели // Добавляем состояние для отслеживания привязки гостевых сообщений
const showWalletSidebar = ref(false); const isLinkingGuestMessages = ref(false);
// Вычисленное свойство для фильтрации идентификаторов // Вычисленное свойство для фильтрации идентификаторов
const filteredIdentities = computed(() => { const filteredIdentities = computed(() => {
@@ -444,17 +407,6 @@ function formatIdentityProvider(provider) {
return providers[provider] || provider; return providers[provider] || provider;
} }
// Функция для управления сайдбаром
const toggleSidebar = () => {
showSidebar.value = !showSidebar.value;
document.querySelector('.app-container').classList.toggle('menu-open');
};
const navigateTo = (page) => {
currentPage.value = page;
console.log(`Навигация на страницу: ${page}`);
};
// Функция для переключения отображения правой панели // Функция для переключения отображения правой панели
const toggleWalletSidebar = () => { const toggleWalletSidebar = () => {
showWalletSidebar.value = !showWalletSidebar.value; showWalletSidebar.value = !showWalletSidebar.value;
@@ -560,38 +512,26 @@ const sendEmailVerification = async () => {
// Функция для обработки загрузки сообщений после аутентификации // Функция для обработки загрузки сообщений после аутентификации
const handlePostAuthMessageLoading = async (authType) => { const handlePostAuthMessageLoading = async (authType) => {
try { try {
console.log(`Обработка загрузки сообщений после аутентификации через ${authType}`); isMessageLoadingInProgress.value = true;
// Сохраняем текущее количество сообщений для отслеживания // Загружаем историю сообщений напрямую через API
let currentMessageCount = 0; const response = await axios.get('/api/chat/history');
try { if (response.data.success) {
const countResponse = await axios.get('/api/chat/history?count_only=true'); messages.value = response.data.messages || [];
if (countResponse.data.success) { console.log(`Загружено ${messages.value.length} сообщений после аутентификации`);
currentMessageCount = countResponse.data.count || 0;
console.log(`Текущее количество сообщений перед обработкой: ${currentMessageCount}`);
}
} catch (error) {
console.warn('Ошибка при получении текущего количества сообщений:', error);
}
// Загружаем историю сообщений после успешной авторизации
await loadChatHistory();
// Получаем новое количество сообщений для отслеживания новых ответов
const newCountResponse = await axios.get('/api/chat/history?count_only=true');
if (newCountResponse.data.success) {
const newCount = newCountResponse.data.count || 0;
// Настраиваем отслеживание только если есть разница в количестве сообщений // Очищаем локальное хранилище гостевых сообщений
if (newCount !== currentMessageCount) { localStorage.removeItem('guestMessages');
console.log(`Количество сообщений изменилось: ${currentMessageCount} -> ${newCount}`); localStorage.removeItem('guestId');
setupMessagePolling(newCount);
} else { // Прокручиваем к последнему сообщению
console.log('Количество сообщений не изменилось, отслеживание не требуется'); await nextTick();
} scrollToBottom();
} }
} catch (error) { } catch (error) {
console.error(`Ошибка при обработке сообщений после аутентификации через ${authType}:`, error); console.error(`Ошибка при обработке сообщений после аутентификации через ${authType}:`, error);
} finally {
isMessageLoadingInProgress.value = false;
} }
}; };
@@ -864,38 +804,40 @@ const clearGuestMessages = () => {
// Метод для загрузки истории чата // Метод для загрузки истории чата
const loadChatHistory = async () => { const loadChatHistory = async () => {
// Сбрасываем текущие сообщения и загружаем новую историю
isLoading.value = true; isLoading.value = true;
try { try {
// Сначала получаем общее количество сообщений // Если пользователь аутентифицирован и есть гостевые сообщения,
// но привязка уже выполняется - ждем её завершения
if (auth.isAuthenticated.value && isLinkingGuestMessages.value) {
await new Promise(resolve => {
const checkInterval = setInterval(() => {
if (!isLinkingGuestMessages.value) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
}
// Получаем общее количество сообщений
const countResponse = await axios.get('/api/chat/history?count_only=true'); const countResponse = await axios.get('/api/chat/history?count_only=true');
if (countResponse.data.success) { if (countResponse.data.success) {
const messageCount = countResponse.data.count; const messageCount = countResponse.data.count;
console.log(`История содержит ${messageCount} сообщений`); console.log(`История содержит ${messageCount} сообщений`);
// Рассчитываем смещение для получения последних сообщений
const effectiveOffset = Math.max(0, messageCount - limit.value); const effectiveOffset = Math.max(0, messageCount - limit.value);
// Загружаем историю сообщений
const response = await axios.get(`/api/chat/history?offset=${effectiveOffset}&limit=${limit.value}`); const response = await axios.get(`/api/chat/history?offset=${effectiveOffset}&limit=${limit.value}`);
if (response && response.data.success) { if (response && response.data.success) {
// Очищаем локальные гостевые сообщения при успешной загрузке истории аутентифицированного пользователя
if (auth.isAuthenticated.value) {
removeFromStorage('guestMessages');
}
messages.value = response.data.messages; messages.value = response.data.messages;
console.log(`Загружено ${messages.value.length} сообщений из истории`); console.log(`Загружено ${messages.value.length} сообщений из истории`);
// Отправляем событие об обновлении сообщений
window.dispatchEvent(new CustomEvent('messages-updated', { window.dispatchEvent(new CustomEvent('messages-updated', {
detail: { count: messages.value.length } detail: { count: messages.value.length }
})); }));
// Прокручиваем к последнему сообщению
await nextTick(); await nextTick();
scrollToBottom(); scrollToBottom();
} }
@@ -1258,10 +1200,6 @@ const disconnectWallet = async () => {
// Останавливаем обновление балансов // Останавливаем обновление балансов
stopBalanceUpdates(); stopBalanceUpdates();
// Сохраняем гостевой ID для продолжения работы после выхода
const guestId = getFromStorage('guestId') || generateUniqueId();
setToStorage('guestId', guestId);
// Отправляем запрос на выход // Отправляем запрос на выход
await axios.post('/api/auth/logout'); await axios.post('/api/auth/logout');
@@ -1275,25 +1213,14 @@ const disconnectWallet = async () => {
// Обновляем отображение UI // Обновляем отображение UI
document.body.classList.remove('wallet-connected'); document.body.classList.remove('wallet-connected');
// Очищаем историю сообщений, кроме гостевых // Очищаем все сообщения и состояния
messages.value = []; messages.value = [];
offset.value = 0; offset.value = 0;
hasMoreMessages.value = true; hasMoreMessages.value = true;
// Загружаем только гостевые сообщения после выхода // Очищаем localStorage от всех сообщений
try { localStorage.removeItem('guestMessages');
// Проверяем наличие сообщений в localStorage localStorage.removeItem('hasUserSentMessage');
const storedMessages = getFromStorage('guestMessages');
if (storedMessages) {
const parsedMessages = JSON.parse(storedMessages);
if (parsedMessages.length > 0) {
console.log(`Найдено ${parsedMessages.length} сохраненных гостевых сообщений`);
messages.value = [...messages.value, ...parsedMessages];
}
}
} catch (e) {
console.error('Ошибка загрузки сообщений из localStorage:', e);
}
console.log('Выход из системы выполнен успешно'); console.log('Выход из системы выполнен успешно');
} catch (error) { } catch (error) {