ваше сообщение коммита
This commit is contained in:
@@ -5,6 +5,8 @@ CREATE OR REPLACE FUNCTION link_guest_messages(
|
|||||||
DECLARE
|
DECLARE
|
||||||
v_conversation_id INTEGER;
|
v_conversation_id INTEGER;
|
||||||
v_count INTEGER;
|
v_count INTEGER;
|
||||||
|
v_first_message TEXT;
|
||||||
|
v_title TEXT;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Логируем входные параметры
|
-- Логируем входные параметры
|
||||||
RAISE NOTICE 'Linking messages for user_id: %, guest_id: %', p_user_id, p_guest_id;
|
RAISE NOTICE 'Linking messages for user_id: %, guest_id: %', p_user_id, p_guest_id;
|
||||||
@@ -15,13 +17,32 @@ BEGIN
|
|||||||
WHERE guest_id = p_guest_id;
|
WHERE guest_id = p_guest_id;
|
||||||
|
|
||||||
RAISE NOTICE 'Found % guest messages', v_count;
|
RAISE NOTICE 'Found % guest messages', v_count;
|
||||||
|
|
||||||
|
-- Если нет гостевых сообщений, выходим
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
RAISE NOTICE 'No guest messages found, exiting';
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Получаем первое сообщение для названия беседы
|
||||||
|
SELECT content INTO v_first_message
|
||||||
|
FROM guest_messages
|
||||||
|
WHERE guest_id = p_guest_id AND NOT is_ai
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- Формируем название диалога на основе первого сообщения
|
||||||
|
v_title := CASE
|
||||||
|
WHEN length(v_first_message) > 30 THEN substring(v_first_message from 1 for 30) || '...'
|
||||||
|
ELSE v_first_message
|
||||||
|
END;
|
||||||
|
|
||||||
-- Создаем новую беседу
|
-- Создаем новую беседу
|
||||||
INSERT INTO conversations (user_id, created_at, updated_at)
|
INSERT INTO conversations (user_id, title, created_at, updated_at)
|
||||||
VALUES (p_user_id, NOW(), NOW())
|
VALUES (p_user_id, v_title, NOW(), NOW())
|
||||||
RETURNING id INTO v_conversation_id;
|
RETURNING id INTO v_conversation_id;
|
||||||
|
|
||||||
RAISE NOTICE 'Created conversation with id: %', v_conversation_id;
|
RAISE NOTICE 'Created conversation with id: % and title: %', v_conversation_id, v_title;
|
||||||
|
|
||||||
-- Копируем сообщения пользователя
|
-- Копируем сообщения пользователя
|
||||||
WITH inserted_messages AS (
|
WITH inserted_messages AS (
|
||||||
@@ -46,11 +67,19 @@ BEGIN
|
|||||||
created_at
|
created_at
|
||||||
FROM guest_messages
|
FROM guest_messages
|
||||||
WHERE guest_id = p_guest_id
|
WHERE guest_id = p_guest_id
|
||||||
|
-- Проверка, чтобы избежать дублирования сообщений
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM messages m
|
||||||
|
WHERE m.guest_message_id = guest_messages.id
|
||||||
|
)
|
||||||
ORDER BY created_at
|
ORDER BY created_at
|
||||||
RETURNING id
|
RETURNING id
|
||||||
)
|
)
|
||||||
SELECT COUNT(*) INTO v_count FROM inserted_messages;
|
SELECT COUNT(*) INTO v_count FROM inserted_messages;
|
||||||
|
|
||||||
RAISE NOTICE 'Inserted % messages', v_count;
|
RAISE NOTICE 'Inserted % messages into conversation', v_count;
|
||||||
|
|
||||||
|
-- НЕ удаляем гостевые сообщения, позволяем им существовать на всякий случай
|
||||||
|
-- до автоматической очистки по cron job
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ const { requireAuth, requireAdmin } = require('../middleware/auth');
|
|||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { saveGuestMessageToDatabase } = require('../db');
|
const { saveGuestMessageToDatabase } = require('../db');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
// Функция для обработки гостевых сообщений после аутентификации
|
// Функция для обработки гостевых сообщений после аутентификации
|
||||||
async function processGuestMessages(userId, guestId) {
|
async function processGuestMessages(userId, guestId) {
|
||||||
@@ -26,6 +27,30 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
const guestMessages = guestMessagesResult.rows;
|
const guestMessages = guestMessagesResult.rows;
|
||||||
console.log(`Found ${guestMessages.length} guest messages`);
|
console.log(`Found ${guestMessages.length} guest messages`);
|
||||||
|
|
||||||
|
// Получаем идентификаторы пользователя
|
||||||
|
const userIdentities = await db.query(
|
||||||
|
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||||
|
[userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Found ${userIdentities.rows.length} identities for user ${userId}`, userIdentities.rows);
|
||||||
|
|
||||||
|
// Сохраняем guestId как отдельный идентификатор для пользователя
|
||||||
|
// если он еще не привязан к пользователю
|
||||||
|
const existingGuestIdentity = userIdentities.rows.find(
|
||||||
|
identity => identity.provider === 'guest' && identity.provider_id === guestId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existingGuestIdentity) {
|
||||||
|
await db.query(
|
||||||
|
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||||
|
VALUES ($1, 'guest', $2)
|
||||||
|
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
||||||
|
[userId, guestId]
|
||||||
|
);
|
||||||
|
console.log(`Linked guest ID ${guestId} to user ${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Создаем новый диалог для этих сообщений
|
// Создаем новый диалог для этих сообщений
|
||||||
const firstMessage = guestMessages[0];
|
const firstMessage = guestMessages[0];
|
||||||
const title = firstMessage.content.length > 30
|
const title = firstMessage.content.length > 30
|
||||||
@@ -44,23 +69,23 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
for (const guestMessage of guestMessages) {
|
for (const guestMessage of guestMessages) {
|
||||||
console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`);
|
console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`);
|
||||||
|
|
||||||
// Проверяем существование гостевого сообщения перед созданием связанного сообщения
|
// Проверяем, не было ли это сообщение уже обработано
|
||||||
const checkGuestMessage = await db.query(
|
const existingMessage = await db.query(
|
||||||
'SELECT id FROM guest_messages WHERE id = $1',
|
'SELECT id FROM messages WHERE guest_message_id = $1',
|
||||||
[guestMessage.id]
|
[guestMessage.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (checkGuestMessage.rows.length === 0) {
|
if (existingMessage.rows.length > 0) {
|
||||||
console.log(`Guest message ${guestMessage.id} no longer exists, skipping`);
|
console.log(`Guest message ${guestMessage.id} already processed, skipping`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем сообщение пользователя
|
// Сохраняем сообщение пользователя
|
||||||
const userMessageResult = await db.query(
|
const userMessageResult = await db.query(
|
||||||
`INSERT INTO messages
|
`INSERT INTO messages
|
||||||
(conversation_id, content, sender_type, role, channel, created_at)
|
(conversation_id, content, sender_type, role, channel, guest_message_id, created_at)
|
||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5, $6)
|
($1, $2, $3, $4, $5, $6, $7)
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
conversation.id,
|
conversation.id,
|
||||||
@@ -68,40 +93,39 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
'user',
|
'user',
|
||||||
'user',
|
'user',
|
||||||
'web',
|
'web',
|
||||||
|
guestMessage.id,
|
||||||
guestMessage.created_at
|
guestMessage.created_at
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`);
|
console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`);
|
||||||
|
|
||||||
// Получаем ответ от ИИ
|
// Получаем ответ от ИИ только для сообщений пользователя (не AI)
|
||||||
console.log('Getting AI response for:', guestMessage.content);
|
if (!guestMessage.is_ai) {
|
||||||
const language = guestMessage.language || 'auto';
|
console.log('Getting AI response for:', guestMessage.content);
|
||||||
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
|
const language = guestMessage.language || 'auto';
|
||||||
console.log('AI response received:', aiResponse);
|
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
|
||||||
|
console.log('AI response received:', aiResponse);
|
||||||
// Сохраняем ответ от ИИ
|
|
||||||
const aiMessageResult = await db.query(
|
// Сохраняем ответ от ИИ
|
||||||
`INSERT INTO messages
|
const aiMessageResult = await db.query(
|
||||||
(conversation_id, content, sender_type, role, channel, created_at)
|
`INSERT INTO messages
|
||||||
VALUES
|
(conversation_id, content, sender_type, role, channel, created_at)
|
||||||
($1, $2, $3, $4, $5, $6)
|
VALUES
|
||||||
RETURNING *`,
|
($1, $2, $3, $4, $5, $6)
|
||||||
[
|
RETURNING *`,
|
||||||
conversation.id,
|
[
|
||||||
aiResponse,
|
conversation.id,
|
||||||
'assistant',
|
aiResponse,
|
||||||
'assistant',
|
'assistant',
|
||||||
'web',
|
'assistant',
|
||||||
new Date()
|
'web',
|
||||||
]
|
new Date()
|
||||||
);
|
]
|
||||||
|
);
|
||||||
console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
|
|
||||||
|
console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
|
||||||
// Удаляем обработанное гостевое сообщение
|
}
|
||||||
await db.query('DELETE FROM guest_messages WHERE id = $1', [guestMessage.id]);
|
|
||||||
console.log(`Deleted processed guest message ${guestMessage.id}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -255,6 +279,24 @@ router.get('/history', async (req, res) => {
|
|||||||
const limit = parseInt(req.query.limit) || 50;
|
const limit = parseInt(req.query.limit) || 50;
|
||||||
const offset = parseInt(req.query.offset) || 0;
|
const offset = parseInt(req.query.offset) || 0;
|
||||||
|
|
||||||
|
// Если пользователь аутентифицирован и у него есть гостевые сообщения,
|
||||||
|
// автоматически связываем их перед получением истории
|
||||||
|
if (req.session.authenticated && req.session.userId && req.session.guestId) {
|
||||||
|
try {
|
||||||
|
console.log('Automatically linking guest messages before fetching history');
|
||||||
|
await processGuestMessages(req.session.userId, req.session.guestId);
|
||||||
|
|
||||||
|
// Очищаем guestId из сессии после связывания
|
||||||
|
req.session.guestId = null;
|
||||||
|
await req.session.save();
|
||||||
|
|
||||||
|
console.log('Guest messages automatically linked');
|
||||||
|
} catch (linkError) {
|
||||||
|
console.error('Error auto-linking guest messages:', linkError);
|
||||||
|
// Продолжаем выполнение, даже если связывание не удалось
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let messages = [];
|
let messages = [];
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
|
||||||
@@ -288,214 +330,19 @@ router.get('/history', async (req, res) => {
|
|||||||
messages = result.rows;
|
messages = result.rows;
|
||||||
console.log(`Found ${messages.length} messages for authenticated user`);
|
console.log(`Found ${messages.length} messages for authenticated user`);
|
||||||
}
|
}
|
||||||
// Если есть guestId, получаем гостевые сообщения
|
|
||||||
else if (req.session.guestId) {
|
|
||||||
const countResult = await db.query(
|
|
||||||
`SELECT COUNT(*) as total FROM guest_messages
|
|
||||||
WHERE guest_id = $1`,
|
|
||||||
[req.session.guestId]
|
|
||||||
);
|
|
||||||
total = parseInt(countResult.rows[0].total) || 0;
|
|
||||||
|
|
||||||
const result = await db.query(
|
|
||||||
`SELECT
|
|
||||||
id,
|
|
||||||
content,
|
|
||||||
'user' as sender_type,
|
|
||||||
'user' as role,
|
|
||||||
created_at,
|
|
||||||
guest_id as user_id,
|
|
||||||
NULL as conversation_id
|
|
||||||
FROM guest_messages
|
|
||||||
WHERE guest_id = $1
|
|
||||||
ORDER BY created_at ASC
|
|
||||||
LIMIT $2 OFFSET $3`,
|
|
||||||
[req.session.guestId, limit, offset]
|
|
||||||
);
|
|
||||||
|
|
||||||
messages = result.rows;
|
|
||||||
console.log(`Found ${messages.length} guest messages`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
total: total
|
total: total
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting chat history:', error);
|
logger.error('Error getting chat history:', error);
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Маршрут для получения всех диалогов (только для админов)
|
// Экспортируем маршрутизатор и функцию processGuestMessages отдельно
|
||||||
router.get('/admin/history', requireAdmin, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { limit = 50, offset = 0, userId } = req.query;
|
|
||||||
|
|
||||||
let query = `
|
|
||||||
SELECT ch.id, ch.user_id, u.username, ch.channel,
|
|
||||||
ch.sender_type, ch.content, ch.metadata, ch.created_at
|
|
||||||
FROM chat_history ch
|
|
||||||
LEFT JOIN users u ON ch.user_id = u.id
|
|
||||||
`;
|
|
||||||
|
|
||||||
const params = [];
|
|
||||||
let paramIndex = 1;
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
query += ` WHERE ch.user_id = $${paramIndex}`;
|
|
||||||
params.push(userId);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
query += ` ORDER BY ch.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
|
||||||
params.push(limit, offset);
|
|
||||||
|
|
||||||
const result = await db.query(query, params);
|
|
||||||
|
|
||||||
res.json(result.rows);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching admin chat history:', error);
|
|
||||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчик для связывания гостевых сообщений с пользователем
|
|
||||||
router.post('/link-guest-messages', requireAuth, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { userId } = req.session;
|
|
||||||
const guestId = req.session.guestId;
|
|
||||||
|
|
||||||
console.log('Linking messages:', { userId, guestId });
|
|
||||||
|
|
||||||
if (!guestId) {
|
|
||||||
console.log('No guestId in session');
|
|
||||||
return res.json({ success: true, message: 'No guest messages to link' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем наличие гостевых сообщений
|
|
||||||
const guestMessagesCheck = await db.query(
|
|
||||||
'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)',
|
|
||||||
[guestId]
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('Guest messages check:', guestMessagesCheck.rows[0]);
|
|
||||||
|
|
||||||
if (!guestMessagesCheck.rows[0].exists) {
|
|
||||||
console.log('No guest messages found for guestId:', guestId);
|
|
||||||
return res.json({ success: true, message: 'No guest messages to link' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Обрабатываем гостевые сообщения для получения ответов от AI
|
|
||||||
console.log('Processing guest messages to get AI responses');
|
|
||||||
const result = await processGuestMessages(userId, guestId);
|
|
||||||
console.log('Guest messages processed:', result);
|
|
||||||
|
|
||||||
// Очищаем guestId из сессии после связывания
|
|
||||||
req.session.guestId = null;
|
|
||||||
await req.session.save();
|
|
||||||
|
|
||||||
console.log('Messages linked and processed successfully');
|
|
||||||
return res.json({
|
|
||||||
success: true,
|
|
||||||
message: 'Guest messages linked and processed',
|
|
||||||
result
|
|
||||||
});
|
|
||||||
} catch (processError) {
|
|
||||||
console.error('Error processing guest messages:', processError);
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Error processing guest messages',
|
|
||||||
details: processError.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error linking guest messages:', error);
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Internal server error',
|
|
||||||
details: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем маршрут верификации Telegram
|
|
||||||
router.post('/auth/telegram/verify', async (req, res) => {
|
|
||||||
// ... существующий код ...
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
// Если есть гостевые сообщения, обрабатываем их
|
|
||||||
if (req.session.guestId) {
|
|
||||||
await processGuestMessages(userId, req.session.guestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
userId: userId,
|
|
||||||
telegramId: result.telegramId,
|
|
||||||
isAdmin: req.session.isAdmin || false,
|
|
||||||
authenticated: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Маршрут для удаления сообщений
|
|
||||||
router.delete('/message/:id', requireAuth, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const messageId = req.params.id;
|
|
||||||
const userId = req.session.userId;
|
|
||||||
|
|
||||||
// Проверяем права на удаление
|
|
||||||
const messageCheck = await db.query(
|
|
||||||
`SELECT m.id
|
|
||||||
FROM messages m
|
|
||||||
JOIN conversations c ON m.conversation_id = c.id
|
|
||||||
WHERE m.id = $1 AND c.user_id = $2`,
|
|
||||||
[messageId, userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (messageCheck.rows.length === 0) {
|
|
||||||
return res.status(403).json({ error: 'Forbidden' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удаляем сообщение
|
|
||||||
await db.query(
|
|
||||||
'DELETE FROM messages WHERE id = $1',
|
|
||||||
[messageId]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({ success: true });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error deleting message:', error);
|
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Маршрут для проверки и инициализации сессии
|
|
||||||
router.get('/check-session', async (req, res) => {
|
|
||||||
try {
|
|
||||||
// Если у пользователя нет guestId, создаем его
|
|
||||||
if (!req.session.guestId) {
|
|
||||||
req.session.guestId = crypto.randomBytes(16).toString('hex');
|
|
||||||
await req.session.save();
|
|
||||||
console.log('Created new guestId:', req.session.guestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
guestId: req.session.guestId,
|
|
||||||
isAuthenticated: req.session.authenticated || false
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking session:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Internal server error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
module.exports.processGuestMessages = processGuestMessages;
|
||||||
@@ -8,6 +8,7 @@ const { app, nonceStore } = require('./app');
|
|||||||
const usersRouter = require('./routes/users');
|
const usersRouter = require('./routes/users');
|
||||||
const authRouter = require('./routes/auth');
|
const authRouter = require('./routes/auth');
|
||||||
const identitiesRouter = require('./routes/identities');
|
const identitiesRouter = require('./routes/identities');
|
||||||
|
const chatRouter = require('./routes/chat');
|
||||||
const { pool } = require('./db');
|
const { pool } = require('./db');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const { getBot, stopBot } = require('./services/telegramBot');
|
const { getBot, stopBot } = require('./services/telegramBot');
|
||||||
@@ -79,6 +80,7 @@ app.use(session({
|
|||||||
app.use('/api/users', usersRouter);
|
app.use('/api/users', usersRouter);
|
||||||
app.use('/api/auth', authRouter);
|
app.use('/api/auth', authRouter);
|
||||||
app.use('/api/identities', identitiesRouter);
|
app.use('/api/identities', identitiesRouter);
|
||||||
|
app.use('/api/chat', chatRouter);
|
||||||
|
|
||||||
// Эндпоинт для проверки состояния сервера
|
// Эндпоинт для проверки состояния сервера
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
|
|||||||
@@ -391,6 +391,42 @@ class AuthService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем псевдоним функции checkAdminRole для обратной совместимости
|
||||||
|
async checkAdminTokens(address) {
|
||||||
|
if (!address) return false;
|
||||||
|
|
||||||
|
console.log(`Checking admin tokens for address: ${address}`);
|
||||||
|
const isAdmin = await this.checkAdminRole(address);
|
||||||
|
console.log(`Admin token check result for ${address}: ${isAdmin}`);
|
||||||
|
|
||||||
|
// Обновляем роль пользователя в базе данных, если есть админские токены
|
||||||
|
if (isAdmin) {
|
||||||
|
try {
|
||||||
|
// Находим userId по адресу
|
||||||
|
const userResult = await db.query(`
|
||||||
|
SELECT u.id FROM users u
|
||||||
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
|
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
||||||
|
[address.toLowerCase()]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userResult.rows.length > 0) {
|
||||||
|
const userId = userResult.rows[0].id;
|
||||||
|
// Обновляем роль пользователя
|
||||||
|
await db.query(
|
||||||
|
'UPDATE users SET role = $1 WHERE id = $2',
|
||||||
|
['admin', userId]
|
||||||
|
);
|
||||||
|
console.log(`Updated user ${userId} role to admin based on token holdings`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating user role:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAdmin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем и экспортируем единственный экземпляр
|
// Создаем и экспортируем единственный экземпляр
|
||||||
|
|||||||
@@ -73,16 +73,37 @@ class EmailAuth {
|
|||||||
const userId = result.userId || session.tempUserId;
|
const userId = result.userId || session.tempUserId;
|
||||||
const email = session.pendingEmail;
|
const email = session.pendingEmail;
|
||||||
|
|
||||||
// Добавляем email в базу данных
|
// Проверяем, существует ли уже этот email в user_identities
|
||||||
await db.query(
|
const existingUserQuery = await db.query(
|
||||||
`INSERT INTO user_identities
|
`SELECT user_id FROM user_identities
|
||||||
(user_id, provider, provider_id)
|
WHERE provider = 'email' AND provider_id = $1`,
|
||||||
VALUES ($1, $2, $3)
|
[email.toLowerCase()]
|
||||||
ON CONFLICT (provider, provider_id)
|
|
||||||
DO UPDATE SET user_id = $1`,
|
|
||||||
[userId, 'email', email.toLowerCase()]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let finalUserId = userId;
|
||||||
|
|
||||||
|
// Если email уже связан с другим пользователем
|
||||||
|
if (existingUserQuery.rows.length > 0) {
|
||||||
|
finalUserId = existingUserQuery.rows[0].user_id;
|
||||||
|
logger.info(`Using existing user ID ${finalUserId} for email ${email}`);
|
||||||
|
|
||||||
|
// Обновляем идентификатор пользователя в сессии
|
||||||
|
if (userId !== finalUserId) {
|
||||||
|
logger.info(`Changing user ID from ${userId} to ${finalUserId} based on existing email identity`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Добавляем email в базу данных для нового пользователя
|
||||||
|
await db.query(
|
||||||
|
`INSERT INTO user_identities
|
||||||
|
(user_id, provider, provider_id)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (provider, provider_id)
|
||||||
|
DO UPDATE SET user_id = $1`,
|
||||||
|
[finalUserId, 'email', email.toLowerCase()]
|
||||||
|
);
|
||||||
|
logger.info(`Added new email identity ${email} for user ${finalUserId}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Очищаем временные данные
|
// Очищаем временные данные
|
||||||
delete session.pendingEmail;
|
delete session.pendingEmail;
|
||||||
if (session.tempUserId) {
|
if (session.tempUserId) {
|
||||||
@@ -91,7 +112,7 @@ class EmailAuth {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
verified: true,
|
verified: true,
|
||||||
userId,
|
userId: finalUserId,
|
||||||
email: email.toLowerCase()
|
email: email.toLowerCase()
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -9,15 +9,136 @@ export function useAuth() {
|
|||||||
const telegramId = ref(null);
|
const telegramId = ref(null);
|
||||||
const isAdmin = ref(false);
|
const isAdmin = ref(false);
|
||||||
const email = ref(null);
|
const email = ref(null);
|
||||||
|
const processedGuestIds = ref([]);
|
||||||
|
const identities = ref([]);
|
||||||
|
|
||||||
|
// Функция для обновления списка идентификаторов
|
||||||
|
const updateIdentities = async () => {
|
||||||
|
if (!isAuthenticated.value || !userId.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/auth/identities');
|
||||||
|
if (response.data.success) {
|
||||||
|
identities.value = response.data.identities;
|
||||||
|
console.log('User identities updated:', identities.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user identities:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateAuth = ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin, email: newEmail }) => {
|
const updateAuth = ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin, email: newEmail }) => {
|
||||||
isAuthenticated.value = authenticated;
|
const wasAuthenticated = isAuthenticated.value;
|
||||||
authType.value = newAuthType;
|
const previousUserId = userId.value;
|
||||||
userId.value = newUserId;
|
|
||||||
address.value = newAddress;
|
console.log('updateAuth called with:', {
|
||||||
telegramId.value = newTelegramId;
|
authenticated,
|
||||||
isAdmin.value = newIsAdmin;
|
newAuthType,
|
||||||
email.value = newEmail;
|
newUserId,
|
||||||
|
newAddress,
|
||||||
|
newTelegramId,
|
||||||
|
newIsAdmin,
|
||||||
|
newEmail
|
||||||
|
});
|
||||||
|
|
||||||
|
// Убедимся, что переменные являются реактивными
|
||||||
|
isAuthenticated.value = authenticated === true;
|
||||||
|
authType.value = newAuthType || null;
|
||||||
|
userId.value = newUserId || null;
|
||||||
|
address.value = newAddress || null;
|
||||||
|
telegramId.value = newTelegramId || null;
|
||||||
|
isAdmin.value = newIsAdmin === true;
|
||||||
|
email.value = newEmail || null;
|
||||||
|
|
||||||
|
console.log('Auth updated:', {
|
||||||
|
authenticated: isAuthenticated.value,
|
||||||
|
userId: userId.value,
|
||||||
|
address: address.value,
|
||||||
|
telegramId: telegramId.value,
|
||||||
|
email: email.value,
|
||||||
|
isAdmin: isAdmin.value
|
||||||
|
});
|
||||||
|
|
||||||
|
// Если пользователь только что аутентифицировался или сменил аккаунт,
|
||||||
|
// пробуем связать сообщения и обновить идентификаторы
|
||||||
|
if (authenticated && (!wasAuthenticated || (previousUserId && previousUserId !== newUserId))) {
|
||||||
|
console.log('Auth change detected, linking messages and updating identities');
|
||||||
|
linkMessages();
|
||||||
|
updateIdentities();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для связывания сообщений после успешной авторизации
|
||||||
|
const linkMessages = async () => {
|
||||||
|
try {
|
||||||
|
if (isAuthenticated.value) {
|
||||||
|
console.log('Linking messages after authentication');
|
||||||
|
|
||||||
|
// Создаем объект с идентификаторами для передачи на сервер
|
||||||
|
const identifiersData = {
|
||||||
|
userId: userId.value
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавляем все доступные идентификаторы
|
||||||
|
if (address.value) identifiersData.address = address.value;
|
||||||
|
if (email.value) identifiersData.email = email.value;
|
||||||
|
if (telegramId.value) identifiersData.telegramId = telegramId.value;
|
||||||
|
|
||||||
|
// Сохраняем предыдущий guestId из localStorage, если есть
|
||||||
|
const localGuestId = localStorage.getItem('guestId');
|
||||||
|
if (localGuestId && !processedGuestIds.value.includes(localGuestId)) {
|
||||||
|
console.log('Found local guestId:', localGuestId);
|
||||||
|
// Добавляем guestId в идентификаторы
|
||||||
|
identifiersData.guestId = localGuestId;
|
||||||
|
// Добавляем guestId в список обработанных
|
||||||
|
processedGuestIds.value.push(localGuestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Логируем попытку связывания сообщений
|
||||||
|
console.log('Sending link-guest-messages request with data:', identifiersData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Отправляем запрос на связывание сообщений
|
||||||
|
const response = await axios.post('/api/auth/link-guest-messages', identifiersData);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
console.log('Messages linked successfully:', response.data);
|
||||||
|
|
||||||
|
// Если в ответе есть обработанные guestIds, добавляем их в список
|
||||||
|
if (response.data.results && Array.isArray(response.data.results)) {
|
||||||
|
const newProcessedIds = response.data.results
|
||||||
|
.filter(result => result.guestId)
|
||||||
|
.map(result => result.guestId);
|
||||||
|
|
||||||
|
if (newProcessedIds.length > 0) {
|
||||||
|
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...newProcessedIds])];
|
||||||
|
console.log('Updated processed guest IDs:', processedGuestIds.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очищаем гостевые сообщения из localStorage после успешного связывания
|
||||||
|
localStorage.removeItem('guestMessages');
|
||||||
|
localStorage.removeItem('guestId');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
processedIds: processedGuestIds.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error linking messages:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, message: 'Not authenticated' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in linkMessages:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
@@ -25,13 +146,33 @@ export function useAuth() {
|
|||||||
const response = await axios.get('/api/auth/check');
|
const response = await axios.get('/api/auth/check');
|
||||||
console.log('Auth check response:', response.data);
|
console.log('Auth check response:', response.data);
|
||||||
|
|
||||||
isAuthenticated.value = response.data.authenticated;
|
const wasAuthenticated = isAuthenticated.value;
|
||||||
userId.value = response.data.userId;
|
const previousUserId = userId.value;
|
||||||
isAdmin.value = response.data.isAdmin;
|
|
||||||
authType.value = response.data.authType;
|
// Обновляем данные авторизации через updateAuth вместо прямого изменения
|
||||||
address.value = response.data.address;
|
updateAuth({
|
||||||
telegramId.value = response.data.telegramId;
|
authenticated: response.data.authenticated,
|
||||||
email.value = response.data.email;
|
authType: response.data.authType,
|
||||||
|
userId: response.data.userId,
|
||||||
|
address: response.data.address,
|
||||||
|
telegramId: response.data.telegramId,
|
||||||
|
email: response.data.email,
|
||||||
|
isAdmin: response.data.isAdmin
|
||||||
|
});
|
||||||
|
|
||||||
|
// Если пользователь аутентифицирован, обновляем список идентификаторов и связываем сообщения
|
||||||
|
if (response.data.authenticated) {
|
||||||
|
// Сначала обновляем идентификаторы, чтобы иметь актуальные данные
|
||||||
|
await updateIdentities();
|
||||||
|
|
||||||
|
// Если пользователь только что аутентифицировался или сменил аккаунт,
|
||||||
|
// связываем гостевые сообщения с его аккаунтом
|
||||||
|
if (!wasAuthenticated || (previousUserId && previousUserId !== response.data.userId)) {
|
||||||
|
// Немедленно связываем сообщения
|
||||||
|
const linkResult = await linkMessages();
|
||||||
|
console.log('Link messages result on auth change:', linkResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -42,6 +183,11 @@ 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');
|
||||||
updateAuth({
|
updateAuth({
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
@@ -53,7 +199,10 @@ export function useAuth() {
|
|||||||
isAdmin: false
|
isAdmin: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Очищаем localStorage
|
// Очищаем списки идентификаторов
|
||||||
|
identities.value = [];
|
||||||
|
|
||||||
|
// Очищаем localStorage кроме guestId
|
||||||
localStorage.removeItem('isAuthenticated');
|
localStorage.removeItem('isAuthenticated');
|
||||||
localStorage.removeItem('userId');
|
localStorage.removeItem('userId');
|
||||||
localStorage.removeItem('address');
|
localStorage.removeItem('address');
|
||||||
@@ -66,6 +215,13 @@ export function useAuth() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Обновляем список обработанных guestIds
|
||||||
|
const updateProcessedGuestIds = (ids) => {
|
||||||
|
if (Array.isArray(ids)) {
|
||||||
|
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...ids])];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
});
|
});
|
||||||
@@ -78,8 +234,13 @@ export function useAuth() {
|
|||||||
isAdmin,
|
isAdmin,
|
||||||
telegramId,
|
telegramId,
|
||||||
email,
|
email,
|
||||||
|
identities,
|
||||||
|
processedGuestIds,
|
||||||
updateAuth,
|
updateAuth,
|
||||||
checkAuth,
|
checkAuth,
|
||||||
disconnect
|
disconnect,
|
||||||
|
linkMessages,
|
||||||
|
updateIdentities,
|
||||||
|
updateProcessedGuestIds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -179,18 +179,39 @@
|
|||||||
<!-- Блок информации о пользователе -->
|
<!-- Блок информации о пользователе -->
|
||||||
<div v-if="isAuthenticated" class="user-info">
|
<div v-if="isAuthenticated" class="user-info">
|
||||||
<h3>Идентификаторы:</h3>
|
<h3>Идентификаторы:</h3>
|
||||||
<div v-if="auth.address?.value" class="user-info-item">
|
<!-- Основные идентификаторы из auth объекта -->
|
||||||
<span class="user-info-label">Кошелек:</span>
|
<template v-if="auth.address?.value">
|
||||||
<span class="user-info-value">{{ truncateAddress(auth.address.value) }}</span>
|
<div class="user-info-item">
|
||||||
</div>
|
<span class="user-info-label">Кошелек:</span>
|
||||||
<div v-if="auth.telegramId" class="user-info-item">
|
<span class="user-info-value">{{ truncateAddress(auth.address.value) }}</span>
|
||||||
<span class="user-info-label">Telegram:</span>
|
</div>
|
||||||
<span class="user-info-value">{{ auth.telegramId }}</span>
|
</template>
|
||||||
</div>
|
|
||||||
<div v-if="auth.email" class="user-info-item">
|
<template v-if="auth.telegramId?.value">
|
||||||
<span class="user-info-label">Email:</span>
|
<div class="user-info-item">
|
||||||
<span class="user-info-value">{{ auth.email }}</span>
|
<span class="user-info-label">Telegram:</span>
|
||||||
</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,7 +244,7 @@ const userLanguage = ref('ru');
|
|||||||
const isLoadingMore = ref(false);
|
const isLoadingMore = ref(false);
|
||||||
const hasMoreMessages = ref(false);
|
const hasMoreMessages = ref(false);
|
||||||
const offset = ref(0);
|
const offset = ref(0);
|
||||||
const limit = ref(20);
|
const limit = ref(30);
|
||||||
|
|
||||||
// Состояния для верификации
|
// Состояния для верификации
|
||||||
const showTelegramVerification = ref(false);
|
const showTelegramVerification = ref(false);
|
||||||
@@ -266,6 +287,32 @@ const tokenBalances = ref({
|
|||||||
// Состояние для отображения правой панели
|
// Состояние для отображения правой панели
|
||||||
const showWalletSidebar = ref(false);
|
const showWalletSidebar = ref(false);
|
||||||
|
|
||||||
|
// Вычисленное свойство для фильтрации идентификаторов
|
||||||
|
const filteredIdentities = computed(() => {
|
||||||
|
if (!auth.identities?.value) return [];
|
||||||
|
|
||||||
|
// Фильтруем и оставляем только постоянные идентификаторы
|
||||||
|
return auth.identities.value.filter(identity =>
|
||||||
|
identity.provider !== 'guest' && // Исключаем guest идентификаторы
|
||||||
|
// Также исключаем дубликаты, если идентификатор уже показан выше
|
||||||
|
!(identity.provider === 'wallet' && auth.address?.value) &&
|
||||||
|
!(identity.provider === 'telegram' && auth.telegramId?.value) &&
|
||||||
|
!(identity.provider === 'email' && auth.email?.value)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Функция для форматирования названий провайдеров
|
||||||
|
const formatIdentityProvider = (provider) => {
|
||||||
|
const providers = {
|
||||||
|
'wallet': 'Кошелек',
|
||||||
|
'telegram': 'Telegram',
|
||||||
|
'email': 'Email',
|
||||||
|
'guest': 'Гость'
|
||||||
|
};
|
||||||
|
|
||||||
|
return providers[provider] || provider;
|
||||||
|
};
|
||||||
|
|
||||||
// Функция для управления сайдбаром
|
// Функция для управления сайдбаром
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
showSidebar.value = !showSidebar.value;
|
showSidebar.value = !showSidebar.value;
|
||||||
@@ -551,86 +598,44 @@ const cancelTelegramAuth = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Загружаем сообщения при изменении аутентификации
|
// Вычисленное свойство для определения, нужно ли загружать историю
|
||||||
watch(() => isAuthenticated.value, async (newValue, oldValue) => {
|
const shouldLoadHistory = computed(() => {
|
||||||
// Если пользователь только что авторизовался
|
// Загружаем историю только если пользователь авторизован или есть гостевой ID
|
||||||
if (newValue && !oldValue) {
|
return isAuthenticated.value ||
|
||||||
try {
|
(localStorage.getItem('guestId') && localStorage.getItem('guestId').length > 0);
|
||||||
// Связываем гостевые сообщения только один раз при первой авторизации
|
|
||||||
const response = await api.post('/api/chat/link-guest-messages');
|
|
||||||
console.log('Guest messages linking response:', response.data);
|
|
||||||
} catch (linkError) {
|
|
||||||
console.error('Error linking guest messages:', linkError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// В любом случае перезагружаем сообщения
|
|
||||||
messages.value = [];
|
|
||||||
offset.value = 0;
|
|
||||||
hasMoreMessages.value = true;
|
|
||||||
await loadMoreMessages();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
scrollToBottom();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик для Telegram аутентификации
|
// Следим за изменением авторизации
|
||||||
const handleTelegramAuth = async () => {
|
watch(() => auth.isAuthenticated.value, async (newValue, oldValue) => {
|
||||||
try {
|
console.log('Auth state changed:', {
|
||||||
const { data } = await axios.post('/api/auth/telegram/init');
|
from: oldValue,
|
||||||
const { verificationCode, botLink } = data;
|
to: newValue,
|
||||||
|
userId: auth.userId.value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newValue === true) {
|
||||||
|
// Если пользователь только что авторизовался
|
||||||
|
console.log('User authenticated, loading message history');
|
||||||
|
|
||||||
// Показываем код верификации
|
// Сначала связываем сообщения, затем загружаем историю
|
||||||
showTelegramVerification.value = true;
|
try {
|
||||||
telegramVerificationCode.value = verificationCode;
|
// Сбрасываем текущие сообщения
|
||||||
telegramBotLink.value = botLink;
|
messages.value = [];
|
||||||
|
offset.value = 0;
|
||||||
// Запускаем проверку статуса аутентификации
|
|
||||||
telegramAuthCheckInterval.value = setInterval(async () => {
|
// Сначала связываем гостевые сообщения
|
||||||
try {
|
await auth.linkMessages();
|
||||||
const response = await axios.get('/api/auth/check');
|
|
||||||
console.log('Проверка авторизации:', response.data);
|
// Затем загружаем историю сообщений после небольшой задержки,
|
||||||
|
// чтобы сервер успел обработать связанные сообщения
|
||||||
if (response.data.authenticated) {
|
setTimeout(async () => {
|
||||||
// Обновляем состояние аутентификации с полным набором данных
|
await loadMoreMessages(true);
|
||||||
auth.updateAuth({
|
}, 1000);
|
||||||
isAuthenticated: true,
|
} catch (error) {
|
||||||
authenticated: true,
|
console.error('Error loading history after auth:', error);
|
||||||
authType: response.data.authType,
|
}
|
||||||
userId: response.data.userId,
|
|
||||||
telegramId: response.data.telegramId,
|
|
||||||
isAdmin: response.data.isAdmin,
|
|
||||||
address: response.data.address
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Telegram authentication successful:', response.data);
|
|
||||||
|
|
||||||
// Обновляем баланс токенов
|
|
||||||
await updateBalances();
|
|
||||||
|
|
||||||
clearInterval(telegramAuthCheckInterval.value);
|
|
||||||
telegramAuthCheckInterval.value = null;
|
|
||||||
showTelegramVerification.value = false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking auth status:', error);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
// Очищаем интервал через 5 минут
|
|
||||||
setTimeout(() => {
|
|
||||||
if (telegramAuthCheckInterval.value) {
|
|
||||||
clearInterval(telegramAuthCheckInterval.value);
|
|
||||||
telegramAuthCheckInterval.value = null;
|
|
||||||
showTelegramVerification.value = false;
|
|
||||||
}
|
|
||||||
}, 5 * 60 * 1000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error initializing Telegram auth:', error);
|
|
||||||
alert('Ошибка при инициализации Telegram аутентификации');
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// Функция для сокращения адреса кошелька
|
// Функция для сокращения адреса кошелька
|
||||||
const truncateAddress = (address) => {
|
const truncateAddress = (address) => {
|
||||||
@@ -641,63 +646,100 @@ const truncateAddress = (address) => {
|
|||||||
// Функция прокрутки к последнему сообщению
|
// Функция прокрутки к последнему сообщению
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
if (messagesContainer.value) {
|
if (messagesContainer.value) {
|
||||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
setTimeout(() => {
|
||||||
|
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Загрузка сообщений
|
// Функция для загрузки сообщений
|
||||||
const loadMoreMessages = async () => {
|
const loadMoreMessages = async (silent = false) => {
|
||||||
|
if (isLoadingMore.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isLoadingMore.value = true;
|
isLoadingMore.value = true;
|
||||||
|
if (!silent) isLoading.value = true;
|
||||||
|
|
||||||
console.log('Fetching chat history...');
|
console.log('Fetching chat history...');
|
||||||
|
|
||||||
// Всегда запрашиваем историю, так как на сервере проверяется наличие
|
// Проверяем сессию перед загрузкой истории
|
||||||
// userId или guestId в сессии и возвращаются соответствующие сообщения
|
try {
|
||||||
const response = await api.get('/api/chat/history', {
|
const sessionCheck = await axios.get('/api/auth/check');
|
||||||
|
console.log('Session check:', sessionCheck.data);
|
||||||
|
|
||||||
|
// Проверяем, что пользователь все еще аутентифицирован
|
||||||
|
if (!sessionCheck.data.authenticated && !shouldLoadHistory.value) {
|
||||||
|
console.warn('User is not authenticated, skipping message history load');
|
||||||
|
isLoadingMore.value = false;
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Session check failed, but continuing anyway:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сначала запрашиваем общее количество сообщений
|
||||||
|
const countResponse = await axios.get('/api/chat/history', {
|
||||||
params: {
|
params: {
|
||||||
limit: limit.value,
|
count_only: true
|
||||||
offset: offset.value
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Chat history response:', response.data);
|
if (!countResponse.data.success) {
|
||||||
|
throw new Error('Failed to get message count');
|
||||||
if (response.data.success) {
|
|
||||||
const newMessages = response.data.messages.map(msg => {
|
|
||||||
console.log('Processing message:', msg);
|
|
||||||
return {
|
|
||||||
id: msg.id,
|
|
||||||
content: msg.content,
|
|
||||||
sender_type: msg.sender_type || (msg.role === 'assistant' ? 'assistant' : 'user'),
|
|
||||||
role: msg.role || (msg.sender_type === 'assistant' ? 'assistant' : 'user'),
|
|
||||||
timestamp: msg.created_at,
|
|
||||||
showAuthOptions: false
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Processed messages:', newMessages);
|
|
||||||
|
|
||||||
// Объединяем сообщения и сортируем их по timestamp
|
|
||||||
const allMessages = [...messages.value, ...newMessages];
|
|
||||||
allMessages.sort((a, b) => {
|
|
||||||
const timeA = new Date(a.timestamp || a.created_at).getTime();
|
|
||||||
const timeB = new Date(b.timestamp || b.created_at).getTime();
|
|
||||||
return timeA - timeB;
|
|
||||||
});
|
|
||||||
|
|
||||||
messages.value = allMessages;
|
|
||||||
console.log('Updated messages array:', messages.value);
|
|
||||||
hasMoreMessages.value = response.data.total > messages.value.length;
|
|
||||||
offset.value += newMessages.length;
|
|
||||||
|
|
||||||
// Прокручиваем к последнему сообщению
|
|
||||||
await nextTick();
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalMessages = countResponse.data.total || 0;
|
||||||
|
console.log(`Total messages in history: ${totalMessages}`);
|
||||||
|
|
||||||
|
// Рассчитываем offset так, чтобы получить последние сообщения
|
||||||
|
// при первой загрузке (когда offset = 0)
|
||||||
|
let effectiveOffset = offset.value;
|
||||||
|
if (offset.value === 0 && totalMessages > limit.value) {
|
||||||
|
// Загружаем последние сообщения вместо первых
|
||||||
|
effectiveOffset = Math.max(0, totalMessages - limit.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем историю сообщений с параметрами пагинации
|
||||||
|
const response = await axios.get('/api/chat/history', {
|
||||||
|
params: {
|
||||||
|
offset: effectiveOffset,
|
||||||
|
limit: limit.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Chat history response:', response.data);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
// Если это первая загрузка, заменяем сообщения
|
||||||
|
// Иначе, добавляем полученные сообщения к существующим
|
||||||
|
if (offset.value === 0) {
|
||||||
|
messages.value = response.data.messages || [];
|
||||||
|
} else if (response.data.messages && response.data.messages.length) {
|
||||||
|
messages.value = [...messages.value, ...response.data.messages];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем offset для следующей загрузки
|
||||||
|
offset.value = effectiveOffset + (response.data.messages?.length || 0);
|
||||||
|
|
||||||
|
// Проверяем, есть ли еще сообщения для загрузки
|
||||||
|
hasMoreMessages.value = offset.value < totalMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// После загрузки первой порции сообщений считаем, что пользователь уже отправлял сообщения
|
||||||
|
if (messages.value.length > 0) {
|
||||||
|
hasUserSentMessage.value = true;
|
||||||
|
localStorage.setItem('hasUserSentMessage', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Прокручиваем контейнер с сообщениями вниз
|
||||||
|
await nextTick();
|
||||||
|
scrollToBottom();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading chat history:', error);
|
console.error('Error loading chat history:', error);
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingMore.value = false;
|
isLoadingMore.value = false;
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -786,22 +828,33 @@ const formatMessage = (text) => {
|
|||||||
return DOMPurify.sanitize(rawHtml);
|
return DOMPurify.sanitize(rawHtml);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Функция для проверки наличия гостевых сообщений
|
// Проверяет наличие гостевых сообщений
|
||||||
const checkGuestMessages = async () => {
|
const checkGuestMessages = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get('/api/chat/check-session');
|
// Проверяем сессию через auth/check вместо chat/check-session
|
||||||
console.log('Session check response:', response.data);
|
const sessionCheck = await axios.get('/api/auth/check');
|
||||||
|
console.log('Session auth check response:', sessionCheck.data);
|
||||||
|
|
||||||
// После инициализации сессии загружаем сообщения
|
// Проверяем наличие сообщений в localStorage
|
||||||
if (!isAuthenticated.value) {
|
const storedMessages = localStorage.getItem('guestMessages');
|
||||||
// Если пользователь не авторизован, попробуем загрузить гостевые сообщения
|
if (storedMessages) {
|
||||||
await loadMoreMessages();
|
const parsedMessages = JSON.parse(storedMessages);
|
||||||
|
if (Array.isArray(parsedMessages) && parsedMessages.length > 0) {
|
||||||
|
// Если есть сообщения и пользователь не аутентифицирован, показываем их
|
||||||
|
if (!auth.isAuthenticated.value) {
|
||||||
|
console.log('Found guest messages in localStorage:', parsedMessages);
|
||||||
|
messages.value = [...messages.value, ...parsedMessages];
|
||||||
|
hasUserSentMessage.value = true;
|
||||||
|
localStorage.setItem('hasUserSentMessage', 'true');
|
||||||
|
} else {
|
||||||
|
// Если пользователь аутентифицирован, удаляем гостевые сообщения из localStorage
|
||||||
|
console.log('User is authenticated, removing guest messages from localStorage');
|
||||||
|
localStorage.removeItem('guestMessages');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking guest messages:', error);
|
console.error('Error checking guest messages:', error);
|
||||||
return { success: false };
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -852,6 +905,21 @@ watch(() => auth.isAuthenticated.value, async (newValue) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Отслеживаем изменения в идентификаторах
|
||||||
|
watch(() => auth.telegramId?.value, (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
console.log('Telegram ID изменился:', newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Отслеживаем успешную авторизацию через Telegram
|
||||||
|
watch(() => auth.authType?.value, (newValue) => {
|
||||||
|
if (newValue === 'telegram') {
|
||||||
|
console.log('Авторизация через Telegram завершена, authType:', newValue);
|
||||||
|
console.log('Текущий telegramId:', auth.telegramId?.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// Удаляем слушатель
|
// Удаляем слушатель
|
||||||
if (messagesContainer.value) {
|
if (messagesContainer.value) {
|
||||||
@@ -862,4 +930,79 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
document.querySelector('.app-container')?.classList.remove('menu-open');
|
document.querySelector('.app-container')?.classList.remove('menu-open');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Обработчик для Telegram аутентификации
|
||||||
|
const handleTelegramAuth = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post('/api/auth/telegram/init');
|
||||||
|
const { verificationCode, botLink } = data;
|
||||||
|
|
||||||
|
// Показываем код верификации
|
||||||
|
showTelegramVerification.value = true;
|
||||||
|
telegramVerificationCode.value = verificationCode;
|
||||||
|
telegramBotLink.value = botLink;
|
||||||
|
|
||||||
|
// Запускаем проверку статуса аутентификации
|
||||||
|
telegramAuthCheckInterval.value = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/auth/check');
|
||||||
|
console.log('Проверка авторизации:', response.data);
|
||||||
|
|
||||||
|
if (response.data.authenticated && response.data.telegramId) {
|
||||||
|
clearInterval(telegramAuthCheckInterval.value);
|
||||||
|
telegramAuthCheckInterval.value = null;
|
||||||
|
showTelegramVerification.value = false;
|
||||||
|
|
||||||
|
console.log('Telegram ID получен:', response.data.telegramId);
|
||||||
|
|
||||||
|
// Обновляем информацию об авторизации через метод updateAuth
|
||||||
|
auth.updateAuth({
|
||||||
|
authenticated: true,
|
||||||
|
authType: response.data.authType,
|
||||||
|
userId: response.data.userId,
|
||||||
|
telegramId: response.data.telegramId,
|
||||||
|
isAdmin: response.data.isAdmin
|
||||||
|
});
|
||||||
|
|
||||||
|
// Сначала обновляем идентификаторы, затем связываем сообщения и обновляем историю
|
||||||
|
await auth.checkAuth();
|
||||||
|
|
||||||
|
// Весь процесс загрузки истории будет выполнен через watch на isAuthenticated
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking auth status:', error);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
// Очищаем интервал через 5 минут
|
||||||
|
setTimeout(() => {
|
||||||
|
if (telegramAuthCheckInterval.value) {
|
||||||
|
clearInterval(telegramAuthCheckInterval.value);
|
||||||
|
telegramAuthCheckInterval.value = null;
|
||||||
|
showTelegramVerification.value = false;
|
||||||
|
}
|
||||||
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing Telegram auth:', error);
|
||||||
|
alert('Ошибка при инициализации Telegram аутентификации');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Отслеживаем изменения в сообщениях
|
||||||
|
watch(() => messages.value.length, (newLength, oldLength) => {
|
||||||
|
if (newLength > 0) {
|
||||||
|
// Сортируем сообщения по дате/времени
|
||||||
|
messages.value.sort((a, b) => {
|
||||||
|
const dateA = new Date(a.timestamp || a.created_at);
|
||||||
|
const dateB = new Date(b.timestamp || b.created_at);
|
||||||
|
return dateA - dateB;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Прокручиваем к последнему сообщению
|
||||||
|
nextTick(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user