ваше сообщение коммита
This commit is contained in:
@@ -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
|
||||||
12
backend/db/migrations/013_update_verification_codes.sql
Normal file
12
backend/db/migrations/013_update_verification_codes.sql
Normal 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 $$;
|
||||||
@@ -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,
|
||||||
|
error: 'Missing required fields'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Проверяем код верификации
|
logger.info(`[telegram/verify] Verifying Telegram auth for ID: ${telegramId}`);
|
||||||
const verificationResult = await verifyTelegramCode(code);
|
|
||||||
|
// Сохраняем гостевой ID из текущей сессии
|
||||||
|
const guestId = req.session.guestId;
|
||||||
|
|
||||||
|
// Передаем сессию в метод верификации
|
||||||
|
const verificationResult = await authService.verifyTelegramAuth(
|
||||||
|
telegramId,
|
||||||
|
verificationCode,
|
||||||
|
req.session
|
||||||
|
);
|
||||||
|
|
||||||
if (!verificationResult.success) {
|
if (!verificationResult.success) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Неверный код подтверждения'
|
error: verificationResult.error || 'Verification failed'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { telegramId, authData } = verificationResult;
|
// Создаем новую сессию для этого telegramId
|
||||||
logger.info(`[telegram/verify] Code verified successfully for telegramId: ${telegramId}`);
|
req.session.regenerate(async (err) => {
|
||||||
|
if (err) {
|
||||||
// Сохраняем гостевые ID до проверки
|
logger.error('[telegram/verify] Error regenerating session:', err);
|
||||||
const guestId = req.session.guestId;
|
return res.status(500).json({
|
||||||
|
|
||||||
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,
|
success: false,
|
||||||
error: 'Этот Telegram аккаунт уже связан с другим пользователем'
|
error: 'Session regeneration failed'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем Telegram к существующему пользователю
|
// Устанавливаем данные в новой сессии
|
||||||
await saveUserIdentity(userId, 'telegram', telegramId, true);
|
req.session.userId = verificationResult.userId;
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если есть гостевые сообщения, переносим их
|
|
||||||
if (guestId && !req.session.processedGuestIds?.includes(guestId)) {
|
|
||||||
await processGuestMessages(userId, guestId);
|
|
||||||
// Сохраняем обработанный 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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем новую сессию
|
|
||||||
req.session.regenerate(async (err) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error('Error regenerating session:', err);
|
|
||||||
return res.status(500).json({ success: false, error: 'Server error' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем данные новой сессии
|
|
||||||
req.session.authenticated = true;
|
|
||||||
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.telegramId = null;
|
||||||
|
req.session.email = null;
|
||||||
|
req.session.isAdmin = false;
|
||||||
|
req.session.guestId = null;
|
||||||
|
req.session.previousGuestId = null;
|
||||||
|
req.session.processedGuestIds = [];
|
||||||
|
req.session.pendingEmail = null;
|
||||||
|
req.session.authType = null;
|
||||||
|
|
||||||
// Полностью уничтожаем сессию
|
// Сохраняем изменения в сессии
|
||||||
req.session.destroy((err) => {
|
await new Promise((resolve, reject) => {
|
||||||
if (err) {
|
req.session.save(err => {
|
||||||
logger.error('Error destroying session:', err);
|
if (err) {
|
||||||
return res.status(500).json({ success: false, error: 'Server error' });
|
logger.error('[logout] Error saving session:', err);
|
||||||
}
|
reject(err);
|
||||||
|
} else {
|
||||||
// Очищаем куки сессии
|
logger.info('[logout] Session cleared successfully');
|
||||||
res.clearCookie('connect.sid');
|
resolve();
|
||||||
|
}
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
guestId // Возвращаем guestId для фронтенда
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Уничтожаем сессию полностью
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
77
backend/update_verification_table.js
Normal file
77
backend/update_verification_table.js
Normal 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();
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Медиа-запросы для мобильных устройств */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.header-content {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.verification-code code {
|
.header-text {
|
||||||
font-size: 14px;
|
flex: 1;
|
||||||
padding: 6px 10px;
|
margin-right: 10px;
|
||||||
|
min-width: 0; /* Важно для корректной работы text-overflow */
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-btn, .bot-link, .cancel-btn {
|
.title {
|
||||||
padding: 10px 8px;
|
white-space: nowrap;
|
||||||
font-size: 13px;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.verify-btn, .send-email-btn {
|
.subtitle {
|
||||||
padding: 0 10px;
|
white-space: normal;
|
||||||
font-size: 13px;
|
font-size: 0.9rem;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content:not(.no-right-sidebar) {
|
.header-wallet-btn {
|
||||||
margin-right: 286px;
|
flex-shrink: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Дополнительные стили для очень маленьких экранов */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.header-content {
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-wallet-sidebar {
|
.title {
|
||||||
width: 26px;
|
font-size: 1.1rem;
|
||||||
height: 26px;
|
|
||||||
font-size: 18px;
|
|
||||||
top: 8px;
|
|
||||||
right: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-buttons {
|
.subtitle {
|
||||||
margin-top: 35px;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1,50 +1,20 @@
|
|||||||
<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">
|
||||||
@@ -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();
|
localStorage.removeItem('guestMessages');
|
||||||
|
localStorage.removeItem('guestId');
|
||||||
|
|
||||||
// Получаем новое количество сообщений для отслеживания новых ответов
|
// Прокручиваем к последнему сообщению
|
||||||
const newCountResponse = await axios.get('/api/chat/history?count_only=true');
|
await nextTick();
|
||||||
if (newCountResponse.data.success) {
|
scrollToBottom();
|
||||||
const newCount = newCountResponse.data.count || 0;
|
|
||||||
|
|
||||||
// Настраиваем отслеживание только если есть разница в количестве сообщений
|
|
||||||
if (newCount !== currentMessageCount) {
|
|
||||||
console.log(`Количество сообщений изменилось: ${currentMessageCount} -> ${newCount}`);
|
|
||||||
setupMessagePolling(newCount);
|
|
||||||
} else {
|
|
||||||
console.log('Количество сообщений не изменилось, отслеживание не требуется');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} 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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user