ваше сообщение коммита
This commit is contained in:
@@ -5,7 +5,7 @@ const db = require('../db');
|
||||
const logger = require('../utils/logger');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { checkRole, requireAuth } = require('../middleware/auth');
|
||||
const { checkRole, requireAuth, auth } = require('../middleware/auth');
|
||||
const { pool } = require('../db');
|
||||
const authService = require('../services/auth-service');
|
||||
const { SiweMessage } = require('siwe');
|
||||
@@ -1746,115 +1746,27 @@ router.post('/wallet', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для связывания гостевых сообщений с пользователем
|
||||
// Маршрут для обработки гостевых сообщений после аутентификации
|
||||
router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.session;
|
||||
|
||||
logger.info(`[link-guest-messages] Request for user ${userId}`);
|
||||
|
||||
// Проверка кэша обработанных 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
|
||||
const userId = req.user.id;
|
||||
const { currentGuestId } = req.body;
|
||||
|
||||
if (!currentGuestId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Guest ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await authService.linkGuestMessagesAfterAuth(userId, currentGuestId);
|
||||
|
||||
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
|
||||
});
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.error('[link-guest-messages] Error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
details: error.message
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
@@ -96,10 +96,11 @@ class AuthService {
|
||||
let foundTokens = false;
|
||||
const balances = {};
|
||||
|
||||
for (const contract of ADMIN_CONTRACTS) {
|
||||
// Создаем массив промисов для параллельной проверки балансов
|
||||
const checkPromises = ADMIN_CONTRACTS.map(async (contract) => {
|
||||
try {
|
||||
const provider = this.providers[contract.network];
|
||||
if (!provider) continue;
|
||||
if (!provider) return null;
|
||||
|
||||
const tokenContract = new ethers.Contract(
|
||||
contract.address,
|
||||
@@ -107,7 +108,14 @@ class AuthService {
|
||||
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);
|
||||
balances[contract.network] = formattedBalance;
|
||||
|
||||
@@ -122,6 +130,8 @@ class AuthService {
|
||||
logger.info(`Found admin tokens on ${contract.network}`);
|
||||
foundTokens = true;
|
||||
}
|
||||
|
||||
return { network: contract.network, balance: formattedBalance };
|
||||
} catch (error) {
|
||||
logger.error(`Error checking balance in ${contract.network}:`, {
|
||||
address,
|
||||
@@ -129,8 +139,12 @@ class AuthService {
|
||||
error: error.message
|
||||
});
|
||||
balances[contract.network] = 'Error';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ждем выполнения всех проверок
|
||||
await Promise.all(checkPromises);
|
||||
|
||||
if (foundTokens) {
|
||||
logger.info(`Admin role summary for ${address}:`, {
|
||||
@@ -162,6 +176,7 @@ class AuthService {
|
||||
}
|
||||
|
||||
const balances = {};
|
||||
const timeout = 3000; // 3 секунды таймаут
|
||||
|
||||
for (const contract of ADMIN_CONTRACTS) {
|
||||
try {
|
||||
@@ -178,12 +193,20 @@ class AuthService {
|
||||
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);
|
||||
|
||||
logger.info(`Token balance for ${address} on ${contract.network}:`, {
|
||||
contract: contract.address,
|
||||
balance: formattedBalance
|
||||
balance: formattedBalance,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
balances[contract.network] = formattedBalance;
|
||||
@@ -191,12 +214,18 @@ class AuthService {
|
||||
logger.error(`Error getting balance for ${contract.network}:`, {
|
||||
address,
|
||||
contract: contract.address,
|
||||
error: error.message
|
||||
error: error.message || 'Unknown error',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
balances[contract.network] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Token balances fetched for ${address}:`, {
|
||||
...balances,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
return balances;
|
||||
}
|
||||
|
||||
@@ -427,6 +456,161 @@ class AuthService {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем и экспортируем единственный экземпляр
|
||||
|
||||
Reference in New Issue
Block a user