ваше сообщение коммита
This commit is contained in:
@@ -5,7 +5,7 @@ const db = require('../db');
|
|||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const rateLimit = require('express-rate-limit');
|
const rateLimit = require('express-rate-limit');
|
||||||
const { checkRole, requireAuth } = require('../middleware/auth');
|
const { checkRole, requireAuth, auth } = require('../middleware/auth');
|
||||||
const { pool } = require('../db');
|
const { pool } = require('../db');
|
||||||
const authService = require('../services/auth-service');
|
const authService = require('../services/auth-service');
|
||||||
const { SiweMessage } = require('siwe');
|
const { SiweMessage } = require('siwe');
|
||||||
@@ -1746,115 +1746,27 @@ router.post('/wallet', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик для связывания гостевых сообщений с пользователем
|
// Маршрут для обработки гостевых сообщений после аутентификации
|
||||||
router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.session;
|
const userId = req.user.id;
|
||||||
|
const { currentGuestId } = req.body;
|
||||||
|
|
||||||
logger.info(`[link-guest-messages] Request for user ${userId}`);
|
if (!currentGuestId) {
|
||||||
|
return res.status(400).json({
|
||||||
// Проверка кэша обработанных ID в сессии
|
|
||||||
if (!req.session.processedGuestIds) {
|
|
||||||
req.session.processedGuestIds = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем все идентификаторы пользователя
|
|
||||||
const userIdentitiesResult = await db.query(
|
|
||||||
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const userIdentities = userIdentitiesResult.rows;
|
|
||||||
logger.info(`[link-guest-messages] Found ${userIdentities.length} identities for user ${userId}`);
|
|
||||||
|
|
||||||
// Получаем только гостевые идентификаторы и фильтруем по неообработанным
|
|
||||||
const guestIdentities = userIdentities
|
|
||||||
.filter(identity => identity.provider === 'guest')
|
|
||||||
.filter(identity => !req.session.processedGuestIds.includes(identity.provider_id));
|
|
||||||
|
|
||||||
// Добавляем текущий guestId из сессии если он еще не обработан
|
|
||||||
if (req.session.guestId && !req.session.processedGuestIds.includes(req.session.guestId)) {
|
|
||||||
guestIdentities.push({ provider: 'guest', provider_id: req.session.guestId });
|
|
||||||
logger.info(`[link-guest-messages] Added session guestId: ${req.session.guestId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем guestId из тела запроса, если есть и еще не обработан
|
|
||||||
if (req.body.guestId && !req.session.processedGuestIds.includes(req.body.guestId)) {
|
|
||||||
guestIdentities.push({ provider: 'guest', provider_id: req.body.guestId });
|
|
||||||
logger.info(`[link-guest-messages] Added request body guestId: ${req.body.guestId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Убираем дубликаты
|
|
||||||
const uniqueGuestIds = [...new Set(guestIdentities.map(i => i.provider_id))];
|
|
||||||
|
|
||||||
// Если все ID уже обработаны, сразу возвращаем успех
|
|
||||||
if (uniqueGuestIds.length === 0) {
|
|
||||||
logger.info('[link-guest-messages] No new guest IDs to process');
|
|
||||||
return res.json({
|
|
||||||
success: true,
|
|
||||||
message: 'All guest IDs already processed',
|
|
||||||
processedIds: req.session.processedGuestIds
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`[link-guest-messages] Found ${uniqueGuestIds.length} new guestIds to process`);
|
|
||||||
uniqueGuestIds.forEach(id => logger.info(`[link-guest-messages] - ${id}`));
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
// Обрабатываем каждый новый guestId
|
|
||||||
for (const guestId of uniqueGuestIds) {
|
|
||||||
// Проверяем наличие гостевых сообщений
|
|
||||||
try {
|
|
||||||
const guestMessagesCheck = await db.query(
|
|
||||||
'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)',
|
|
||||||
[guestId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (guestMessagesCheck.rows[0].exists) {
|
|
||||||
logger.info(`[link-guest-messages] Found messages for guest ID ${guestId}, processing`);
|
|
||||||
try {
|
|
||||||
// Используем обертку с правильным порядком аргументов
|
|
||||||
const result = await processGuestMessagesWrapper(guestId, userId);
|
|
||||||
results.push({ guestId, result });
|
|
||||||
|
|
||||||
// Добавляем в список обработанных
|
|
||||||
req.session.processedGuestIds.push(guestId);
|
|
||||||
|
|
||||||
logger.info(`[link-guest-messages] Successfully processed guest ID ${guestId}`);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[link-guest-messages] Error processing guest ID ${guestId}:`, error);
|
|
||||||
results.push({ guestId, error: error.message });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.info(`[link-guest-messages] No guest messages found for guest ID ${guestId}`);
|
|
||||||
// Всё равно добавляем в обработанные, чтобы не проверять снова
|
|
||||||
req.session.processedGuestIds.push(guestId);
|
|
||||||
results.push({ guestId, result: { success: true, message: 'No messages found' } });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[link-guest-messages] Error checking guest messages for ${guestId}:`, error);
|
|
||||||
results.push({ guestId, error: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Очищаем текущий guestId из сессии
|
|
||||||
req.session.guestId = null;
|
|
||||||
await req.session.save();
|
|
||||||
logger.info('[link-guest-messages] Session updated, guestId cleared');
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
success: true,
|
|
||||||
message: `Processed ${results.length} guest IDs`,
|
|
||||||
results,
|
|
||||||
processedIds: req.session.processedGuestIds
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[link-guest-messages] Error:', error);
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Internal server error',
|
error: 'Guest ID is required'
|
||||||
details: error.message
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await authService.linkGuestMessagesAfterAuth(userId, currentGuestId);
|
||||||
|
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error in /link-guest-messages:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to process guest messages'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1883,4 +1795,54 @@ router.get('/check-session', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Маршрут для проверки баланса токенов
|
||||||
|
router.get('/check-tokens/:address', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { address } = req.params;
|
||||||
|
|
||||||
|
// Проверяем баланс токенов на разных сетях
|
||||||
|
const balances = {
|
||||||
|
eth: '0',
|
||||||
|
bsc: '0',
|
||||||
|
arbitrum: '0',
|
||||||
|
polygon: '0'
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
balances.eth = await authService.getTokenBalance(address, 'eth');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error checking ETH balance: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
balances.bsc = await authService.getTokenBalance(address, 'bsc');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error checking BSC balance: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
balances.arbitrum = await authService.getTokenBalance(address, 'arbitrum');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error checking Arbitrum balance: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
balances.polygon = await authService.getTokenBalance(address, 'polygon');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error checking Polygon balance: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
balances
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error checking token balances:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -45,4 +45,31 @@ router.post('/link', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Получение балансов токенов
|
||||||
|
router.get('/token-balances', requireAuth, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const userId = req.session.userId;
|
||||||
|
if (!userId) {
|
||||||
|
return res.status(401).json({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем связанный кошелек
|
||||||
|
const wallet = await authService.getLinkedWallet(userId);
|
||||||
|
if (!wallet) {
|
||||||
|
return res.status(404).json({ error: 'No wallet linked' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем балансы токенов
|
||||||
|
const balances = await authService.getTokenBalances(wallet);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
balances
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting token balances:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -96,10 +96,11 @@ class AuthService {
|
|||||||
let foundTokens = false;
|
let foundTokens = false;
|
||||||
const balances = {};
|
const balances = {};
|
||||||
|
|
||||||
for (const contract of ADMIN_CONTRACTS) {
|
// Создаем массив промисов для параллельной проверки балансов
|
||||||
|
const checkPromises = ADMIN_CONTRACTS.map(async (contract) => {
|
||||||
try {
|
try {
|
||||||
const provider = this.providers[contract.network];
|
const provider = this.providers[contract.network];
|
||||||
if (!provider) continue;
|
if (!provider) return null;
|
||||||
|
|
||||||
const tokenContract = new ethers.Contract(
|
const tokenContract = new ethers.Contract(
|
||||||
contract.address,
|
contract.address,
|
||||||
@@ -107,7 +108,14 @@ class AuthService {
|
|||||||
provider
|
provider
|
||||||
);
|
);
|
||||||
|
|
||||||
const balance = await tokenContract.balanceOf(address);
|
// Создаем промис с таймаутом
|
||||||
|
const balancePromise = tokenContract.balanceOf(address);
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Timeout')), 3000)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ждем первый выполненный промис
|
||||||
|
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
||||||
const formattedBalance = ethers.formatUnits(balance, 18);
|
const formattedBalance = ethers.formatUnits(balance, 18);
|
||||||
balances[contract.network] = formattedBalance;
|
balances[contract.network] = formattedBalance;
|
||||||
|
|
||||||
@@ -122,6 +130,8 @@ class AuthService {
|
|||||||
logger.info(`Found admin tokens on ${contract.network}`);
|
logger.info(`Found admin tokens on ${contract.network}`);
|
||||||
foundTokens = true;
|
foundTokens = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { network: contract.network, balance: formattedBalance };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error checking balance in ${contract.network}:`, {
|
logger.error(`Error checking balance in ${contract.network}:`, {
|
||||||
address,
|
address,
|
||||||
@@ -129,8 +139,12 @@ class AuthService {
|
|||||||
error: error.message
|
error: error.message
|
||||||
});
|
});
|
||||||
balances[contract.network] = 'Error';
|
balances[contract.network] = 'Error';
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
// Ждем выполнения всех проверок
|
||||||
|
await Promise.all(checkPromises);
|
||||||
|
|
||||||
if (foundTokens) {
|
if (foundTokens) {
|
||||||
logger.info(`Admin role summary for ${address}:`, {
|
logger.info(`Admin role summary for ${address}:`, {
|
||||||
@@ -162,6 +176,7 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const balances = {};
|
const balances = {};
|
||||||
|
const timeout = 3000; // 3 секунды таймаут
|
||||||
|
|
||||||
for (const contract of ADMIN_CONTRACTS) {
|
for (const contract of ADMIN_CONTRACTS) {
|
||||||
try {
|
try {
|
||||||
@@ -178,12 +193,20 @@ class AuthService {
|
|||||||
provider
|
provider
|
||||||
);
|
);
|
||||||
|
|
||||||
const balance = await tokenContract.balanceOf(address);
|
// Создаем промис с таймаутом
|
||||||
|
const balancePromise = tokenContract.balanceOf(address);
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Timeout')), timeout)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ждем первый выполненный промис
|
||||||
|
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
||||||
const formattedBalance = ethers.formatUnits(balance, 18);
|
const formattedBalance = ethers.formatUnits(balance, 18);
|
||||||
|
|
||||||
logger.info(`Token balance for ${address} on ${contract.network}:`, {
|
logger.info(`Token balance for ${address} on ${contract.network}:`, {
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
balance: formattedBalance
|
balance: formattedBalance,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
balances[contract.network] = formattedBalance;
|
balances[contract.network] = formattedBalance;
|
||||||
@@ -191,12 +214,18 @@ class AuthService {
|
|||||||
logger.error(`Error getting balance for ${contract.network}:`, {
|
logger.error(`Error getting balance for ${contract.network}:`, {
|
||||||
address,
|
address,
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
error: error.message
|
error: error.message || 'Unknown error',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
balances[contract.network] = '0';
|
balances[contract.network] = '0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`Token balances fetched for ${address}:`, {
|
||||||
|
...balances,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
return balances;
|
return balances;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,6 +456,161 @@ class AuthService {
|
|||||||
|
|
||||||
return isAdmin;
|
return isAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка старых гостевых идентификаторов
|
||||||
|
* @param {number} userId - ID пользователя
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async cleanupGuestIdentities(userId) {
|
||||||
|
try {
|
||||||
|
// Получаем все идентификаторы пользователя
|
||||||
|
const identities = await this.getUserIdentities(userId);
|
||||||
|
|
||||||
|
// Фильтруем только гостевые идентификаторы
|
||||||
|
const guestIdentities = identities.filter(id => id.identity_type === 'guest');
|
||||||
|
|
||||||
|
// Если гостевых идентификаторов больше 3, удаляем старые
|
||||||
|
if (guestIdentities.length > 3) {
|
||||||
|
// Сортируем по дате создания (новые первые)
|
||||||
|
guestIdentities.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
|
||||||
|
// Оставляем только 3 последних идентификатора
|
||||||
|
const identitiesToDelete = guestIdentities.slice(3);
|
||||||
|
|
||||||
|
// Удаляем старые идентификаторы
|
||||||
|
for (const identity of identitiesToDelete) {
|
||||||
|
await db.query(
|
||||||
|
'DELETE FROM user_identities WHERE id = $1',
|
||||||
|
[identity.id]
|
||||||
|
);
|
||||||
|
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error cleaning up guest identities:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение всех идентификаторов пользователя
|
||||||
|
* @param {number} userId - ID пользователя
|
||||||
|
* @returns {Promise<Array>} - Массив идентификаторов
|
||||||
|
*/
|
||||||
|
async getUserIdentities(userId) {
|
||||||
|
try {
|
||||||
|
const result = await db.query(
|
||||||
|
'SELECT * FROM user_identities WHERE user_id = $1 ORDER BY created_at DESC',
|
||||||
|
[userId]
|
||||||
|
);
|
||||||
|
return result.rows;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[getUserIdentities] Error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка баланса токенов в сети Arbitrum с оптимизированным таймаутом
|
||||||
|
* @param {string} address - Адрес кошелька
|
||||||
|
* @returns {Promise<Object>} - Результат проверки баланса
|
||||||
|
*/
|
||||||
|
async checkArbitrumBalance(address) {
|
||||||
|
const timeout = 2000; // Уменьшаем таймаут до 2 секунд
|
||||||
|
try {
|
||||||
|
const balance = await Promise.race([
|
||||||
|
this.getTokenBalance(address, ADMIN_CONTRACTS.ARBITRUM),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('TIMEOUT')), timeout)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
return { balance, hasTokens: balance > 0 };
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`[checkArbitrumBalance] Timeout or error for ${address}:`, error);
|
||||||
|
return { balance: 0, hasTokens: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка гостевых сообщений после аутентификации
|
||||||
|
*/
|
||||||
|
async linkGuestMessagesAfterAuth(userId, currentGuestId, previousGuestId) {
|
||||||
|
try {
|
||||||
|
logger.info(`[linkGuestMessagesAfterAuth] Starting for user ${userId} with guestId=${currentGuestId}`);
|
||||||
|
|
||||||
|
// Проверяем, есть ли идентификатор для обработки
|
||||||
|
if (!currentGuestId) {
|
||||||
|
logger.debug('[linkGuestMessagesAfterAuth] No guest ID to process');
|
||||||
|
return { success: true, message: 'No guest ID to process' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все гостевые сообщения для этого ID
|
||||||
|
const guestMessagesResult = await db.query(
|
||||||
|
'SELECT * FROM guest_messages WHERE guest_id = $1 ORDER BY created_at ASC',
|
||||||
|
[currentGuestId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (guestMessagesResult.rows.length === 0) {
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[linkGuestMessagesAfterAuth] Error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем и экспортируем единственный экземпляр
|
// Создаем и экспортируем единственный экземпляр
|
||||||
|
|||||||
@@ -1174,3 +1174,61 @@ input, textarea {
|
|||||||
.small-button:hover {
|
.small-button:hover {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для блоков информации о пользователе и баланса токенов */
|
||||||
|
.user-info, .token-balances {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info h3, .token-balances h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-item, .token-balance {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-label, .token-name {
|
||||||
|
min-width: 80px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-value, .token-amount {
|
||||||
|
flex: 1;
|
||||||
|
color: #333;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-symbol {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для правой панели */
|
||||||
|
.right-sidebar {
|
||||||
|
width: 250px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-sidebar.collapsed {
|
||||||
|
width: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export function useAuth() {
|
|||||||
const email = ref(null);
|
const email = ref(null);
|
||||||
const processedGuestIds = ref([]);
|
const processedGuestIds = ref([]);
|
||||||
const identities = ref([]);
|
const identities = ref([]);
|
||||||
|
const tokenBalances = ref([]);
|
||||||
|
|
||||||
// Функция для обновления списка идентификаторов
|
// Функция для обновления списка идентификаторов
|
||||||
const updateIdentities = async () => {
|
const updateIdentities = async () => {
|
||||||
@@ -27,7 +28,21 @@ export function useAuth() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAuth = ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin, email: newEmail }) => {
|
const checkTokenBalances = async (address) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/auth/check-tokens/${address}`);
|
||||||
|
if (response.data.success) {
|
||||||
|
tokenBalances.value = response.data.balances;
|
||||||
|
return response.data.balances;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking token balances:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAuth = async ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin, email: newEmail }) => {
|
||||||
const wasAuthenticated = isAuthenticated.value;
|
const wasAuthenticated = isAuthenticated.value;
|
||||||
const previousUserId = userId.value;
|
const previousUserId = userId.value;
|
||||||
|
|
||||||
@@ -50,6 +65,22 @@ export function useAuth() {
|
|||||||
isAdmin.value = newIsAdmin === true;
|
isAdmin.value = newIsAdmin === true;
|
||||||
email.value = newEmail || null;
|
email.value = newEmail || null;
|
||||||
|
|
||||||
|
// Кэшируем данные аутентификации
|
||||||
|
localStorage.setItem('authData', JSON.stringify({
|
||||||
|
authenticated,
|
||||||
|
authType: newAuthType,
|
||||||
|
userId: newUserId,
|
||||||
|
address: newAddress,
|
||||||
|
telegramId: newTelegramId,
|
||||||
|
isAdmin: newIsAdmin,
|
||||||
|
email: newEmail
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Если аутентификация через кошелек, проверяем баланс токенов только при изменении адреса
|
||||||
|
if (authenticated && newAuthType === 'wallet' && newAddress && newAddress !== address.value) {
|
||||||
|
await checkTokenBalances(newAddress);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Auth updated:', {
|
console.log('Auth updated:', {
|
||||||
authenticated: isAuthenticated.value,
|
authenticated: isAuthenticated.value,
|
||||||
userId: userId.value,
|
userId: userId.value,
|
||||||
@@ -159,7 +190,7 @@ export function useAuth() {
|
|||||||
const previousAuthType = authType.value;
|
const previousAuthType = authType.value;
|
||||||
|
|
||||||
// Обновляем данные авторизации через updateAuth вместо прямого изменения
|
// Обновляем данные авторизации через updateAuth вместо прямого изменения
|
||||||
updateAuth({
|
await updateAuth({
|
||||||
authenticated: response.data.authenticated,
|
authenticated: response.data.authenticated,
|
||||||
authType: response.data.authType,
|
authType: response.data.authType,
|
||||||
userId: response.data.userId,
|
userId: response.data.userId,
|
||||||
@@ -323,6 +354,7 @@ export function useAuth() {
|
|||||||
email,
|
email,
|
||||||
identities,
|
identities,
|
||||||
processedGuestIds,
|
processedGuestIds,
|
||||||
|
tokenBalances,
|
||||||
updateAuth,
|
updateAuth,
|
||||||
checkAuth,
|
checkAuth,
|
||||||
disconnect,
|
disconnect,
|
||||||
|
|||||||
@@ -172,14 +172,108 @@
|
|||||||
<button class="close-error" @click="clearEmailError">×</button>
|
<button class="close-error" @click="clearEmailError">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Блок информации о пользователе -->
|
||||||
|
<div v-if="isAuthenticated" class="user-info">
|
||||||
|
<h3>Идентификаторы:</h3>
|
||||||
|
<div class="user-info-item">
|
||||||
|
<span class="user-info-label">Кошелек:</span>
|
||||||
|
<span v-if="auth.address?.value" class="user-info-value">{{ truncateAddress(auth.address.value) }}</span>
|
||||||
|
<button v-else @click="handleWalletAuth" class="connect-btn">
|
||||||
|
Подключить кошелек
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-item">
|
||||||
|
<span class="user-info-label">Telegram:</span>
|
||||||
|
<span v-if="auth.telegramId?.value" class="user-info-value">{{ auth.telegramId.value }}</span>
|
||||||
|
<button v-else @click="handleTelegramAuth" class="connect-btn">
|
||||||
|
Подключить Telegram
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-item">
|
||||||
|
<span class="user-info-label">Email:</span>
|
||||||
|
<span v-if="auth.email?.value" class="user-info-value">{{ auth.email.value }}</span>
|
||||||
|
<button v-else @click="handleEmailAuth" class="connect-btn">
|
||||||
|
Подключить Email
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Блок форм подключения -->
|
||||||
|
<div v-if="showEmailForm || showTelegramVerification || showEmailVerificationInput" class="connect-forms">
|
||||||
|
<!-- Форма для Email верификации -->
|
||||||
|
<div v-if="showEmailForm" class="email-form">
|
||||||
|
<p>Введите ваш email для получения кода подтверждения:</p>
|
||||||
|
<div class="email-form-container">
|
||||||
|
<input
|
||||||
|
v-model="emailInput"
|
||||||
|
type="email"
|
||||||
|
placeholder="Ваш email"
|
||||||
|
class="email-input"
|
||||||
|
:class="{ 'email-input-error': emailFormatError }"
|
||||||
|
/>
|
||||||
|
<button @click="sendEmailVerification" class="send-email-btn" :disabled="isEmailSending">
|
||||||
|
{{ isEmailSending ? 'Отправка...' : 'Отправить код' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button @click="cancelEmailAuth" class="cancel-btn">Отмена</button>
|
||||||
|
<p v-if="emailFormatError" class="email-format-error">Пожалуйста, введите корректный email</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Форма для ввода кода верификации Email -->
|
||||||
|
<div v-if="showEmailVerificationInput" class="email-verification-form">
|
||||||
|
<p>На ваш email <strong>{{ emailVerificationEmail }}</strong> отправлен код подтверждения.</p>
|
||||||
|
<div class="email-form-container">
|
||||||
|
<input
|
||||||
|
v-model="emailVerificationCode"
|
||||||
|
type="text"
|
||||||
|
placeholder="Введите код верификации"
|
||||||
|
maxlength="6"
|
||||||
|
class="email-input"
|
||||||
|
/>
|
||||||
|
<button @click="verifyEmailCode" class="send-email-btn" :disabled="isVerifying">
|
||||||
|
{{ isVerifying ? 'Проверка...' : 'Подтвердить' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button @click="cancelEmailAuth" class="cancel-btn">Отмена</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Форма для Telegram верификации -->
|
||||||
|
<div v-if="showTelegramVerification" class="verification-block">
|
||||||
|
<div class="verification-code">
|
||||||
|
<span>Код верификации:</span>
|
||||||
|
<code @click="copyCode(telegramVerificationCode)">{{ telegramVerificationCode }}</code>
|
||||||
|
<span v-if="codeCopied" class="copied-message">Скопировано!</span>
|
||||||
|
</div>
|
||||||
|
<a :href="telegramBotLink" target="_blank" class="bot-link">Открыть бота Telegram</a>
|
||||||
|
<button @click="cancelTelegramAuth" class="cancel-btn">Отмена</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Сообщения об ошибках -->
|
||||||
|
<div v-if="telegramError" class="error-message">
|
||||||
|
{{ telegramError }}
|
||||||
|
<button class="close-error" @click="telegramError = ''">×</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="emailError" class="error-message">
|
||||||
|
{{ emailError }}
|
||||||
|
<button class="close-error" @click="clearEmailError">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Блок баланса токенов -->
|
<!-- Блок баланса токенов -->
|
||||||
<div class="balance-container">
|
<div v-if="isAuthenticated && auth.address?.value" class="token-balances">
|
||||||
<h3>Баланс:</h3>
|
<h3>Баланс токенов:</h3>
|
||||||
<div class="token-balance">
|
<div class="token-balance">
|
||||||
<span class="token-name">ETH:</span>
|
<span class="token-name">ETH:</span>
|
||||||
<span class="token-amount">{{ tokenBalances.eth }}</span>
|
<span class="token-amount">{{ tokenBalances.eth }}</span>
|
||||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.eth.symbol }}</span>
|
<span class="token-symbol">{{ TOKEN_CONTRACTS.eth.symbol }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="token-balance">
|
||||||
|
<span class="token-name">BSC:</span>
|
||||||
|
<span class="token-amount">{{ tokenBalances.bsc }}</span>
|
||||||
|
<span class="token-symbol">{{ TOKEN_CONTRACTS.bsc.symbol }}</span>
|
||||||
|
</div>
|
||||||
<div class="token-balance">
|
<div class="token-balance">
|
||||||
<span class="token-name">ARB:</span>
|
<span class="token-name">ARB:</span>
|
||||||
<span class="token-amount">{{ tokenBalances.arbitrum }}</span>
|
<span class="token-amount">{{ tokenBalances.arbitrum }}</span>
|
||||||
@@ -190,49 +284,6 @@
|
|||||||
<span class="token-amount">{{ tokenBalances.polygon }}</span>
|
<span class="token-amount">{{ tokenBalances.polygon }}</span>
|
||||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.polygon.symbol }}</span>
|
<span class="token-symbol">{{ TOKEN_CONTRACTS.polygon.symbol }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="token-balance">
|
|
||||||
<span class="token-name">BNB:</span>
|
|
||||||
<span class="token-amount">{{ tokenBalances.bsc }}</span>
|
|
||||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.bsc.symbol }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Блок информации о пользователе -->
|
|
||||||
<div v-if="isAuthenticated" class="user-info">
|
|
||||||
<h3>Идентификаторы:</h3>
|
|
||||||
<!-- Основные идентификаторы из auth объекта -->
|
|
||||||
<template v-if="auth.address?.value">
|
|
||||||
<div class="user-info-item">
|
|
||||||
<span class="user-info-label">Кошелек:</span>
|
|
||||||
<span class="user-info-value">{{ truncateAddress(auth.address.value) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="auth.telegramId?.value">
|
|
||||||
<div class="user-info-item">
|
|
||||||
<span class="user-info-label">Telegram:</span>
|
|
||||||
<span class="user-info-value">{{ auth.telegramId.value }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="auth.email?.value">
|
|
||||||
<div class="user-info-item">
|
|
||||||
<span class="user-info-label">Email:</span>
|
|
||||||
<span class="user-info-value">{{ auth.email.value }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Дополнительные идентификаторы из списка identities (кроме уже отображенных) -->
|
|
||||||
<template v-for="identity in filteredIdentities" :key="`${identity.provider}-${identity.provider_id}`">
|
|
||||||
<div class="user-info-item">
|
|
||||||
<span class="user-info-label">{{ formatIdentityProvider(identity.provider) }}:</span>
|
|
||||||
<span class="user-info-value">{{
|
|
||||||
identity.provider === 'wallet'
|
|
||||||
? truncateAddress(identity.provider_id)
|
|
||||||
: identity.provider_id
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Блок отладочной информации (только для гостей) -->
|
<!-- Блок отладочной информации (только для гостей) -->
|
||||||
@@ -584,6 +635,37 @@ const handleTelegramAuth = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция для обновления балансов
|
||||||
|
const updateBalances = async () => {
|
||||||
|
if (auth.isAuthenticated.value && auth.address?.value) {
|
||||||
|
try {
|
||||||
|
const balances = await fetchTokenBalances();
|
||||||
|
tokenBalances.value = balances;
|
||||||
|
console.log('Token balances updated:', balances);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating balances:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для периодического обновления балансов
|
||||||
|
let balanceUpdateInterval = null;
|
||||||
|
|
||||||
|
const startBalanceUpdates = () => {
|
||||||
|
// Обновляем балансы сразу
|
||||||
|
updateBalances();
|
||||||
|
|
||||||
|
// Увеличиваем интервал обновления до 5 минут
|
||||||
|
balanceUpdateInterval = setInterval(updateBalances, 300000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopBalanceUpdates = () => {
|
||||||
|
if (balanceUpdateInterval) {
|
||||||
|
clearInterval(balanceUpdateInterval);
|
||||||
|
balanceUpdateInterval = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Функция для подключения кошелька - обновленная версия
|
// Функция для подключения кошелька - обновленная версия
|
||||||
const handleWalletAuth = async () => {
|
const handleWalletAuth = async () => {
|
||||||
if (isConnecting.value || isAuthenticated.value) return;
|
if (isConnecting.value || isAuthenticated.value) return;
|
||||||
@@ -602,6 +684,9 @@ const handleWalletAuth = async () => {
|
|||||||
|
|
||||||
// Обрабатываем загрузку сообщений после успешной аутентификации
|
// Обрабатываем загрузку сообщений после успешной аутентификации
|
||||||
await handlePostAuthMessageLoading('wallet');
|
await handlePostAuthMessageLoading('wallet');
|
||||||
|
|
||||||
|
// Запускаем обновление балансов
|
||||||
|
startBalanceUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем небольшую задержку перед сбросом состояния isConnecting
|
// Добавляем небольшую задержку перед сбросом состояния isConnecting
|
||||||
@@ -1064,18 +1149,6 @@ const handleScroll = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Функция получения балансов
|
|
||||||
const updateBalances = async () => {
|
|
||||||
if (auth.isAuthenticated.value && auth.address?.value) {
|
|
||||||
try {
|
|
||||||
const balances = await fetchTokenBalances();
|
|
||||||
tokenBalances.value = balances;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating balances:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция для отмены аутентификации через Telegram
|
// Функция для отмены аутентификации через Telegram
|
||||||
const cancelTelegramAuth = () => {
|
const cancelTelegramAuth = () => {
|
||||||
// Очищаем интервал проверки
|
// Очищаем интервал проверки
|
||||||
@@ -1180,6 +1253,9 @@ const disconnectWallet = async () => {
|
|||||||
try {
|
try {
|
||||||
console.log('Выполняется выход из системы...');
|
console.log('Выполняется выход из системы...');
|
||||||
|
|
||||||
|
// Останавливаем обновление балансов
|
||||||
|
stopBalanceUpdates();
|
||||||
|
|
||||||
// Сохраняем гостевой ID для продолжения работы после выхода
|
// Сохраняем гостевой ID для продолжения работы после выхода
|
||||||
const guestId = getFromStorage('guestId') || generateUniqueId();
|
const guestId = getFromStorage('guestId') || generateUniqueId();
|
||||||
setToStorage('guestId', guestId);
|
setToStorage('guestId', guestId);
|
||||||
@@ -1401,6 +1477,24 @@ onMounted(async () => {
|
|||||||
setToStorage('hasUserSentMessage', 'true');
|
setToStorage('hasUserSentMessage', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяем аутентификацию только если нет данных в localStorage
|
||||||
|
const cachedAuth = localStorage.getItem('authData');
|
||||||
|
if (!cachedAuth) {
|
||||||
|
const { data: sessionData } = await api.get('/api/auth/check');
|
||||||
|
console.log('Проверка сессии:', sessionData);
|
||||||
|
|
||||||
|
if (sessionData.authenticated && sessionData.authType === 'wallet') {
|
||||||
|
// Запускаем обновление балансов
|
||||||
|
startBalanceUpdates();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Используем кэшированные данные
|
||||||
|
const authData = JSON.parse(cachedAuth);
|
||||||
|
if (authData.authenticated && authData.authType === 'wallet') {
|
||||||
|
startBalanceUpdates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Прокручиваем к последнему сообщению
|
// Прокручиваем к последнему сообщению
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
});
|
});
|
||||||
@@ -1411,5 +1505,8 @@ onBeforeUnmount(() => {
|
|||||||
messagesContainer.value.removeEventListener('scroll', handleScroll);
|
messagesContainer.value.removeEventListener('scroll', handleScroll);
|
||||||
}
|
}
|
||||||
window.removeEventListener('load-chat-history', loadChatHistory);
|
window.removeEventListener('load-chat-history', loadChatHistory);
|
||||||
|
|
||||||
|
// Останавливаем обновление балансов
|
||||||
|
stopBalanceUpdates();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user