ваше сообщение коммита
This commit is contained in:
@@ -5,6 +5,8 @@ CREATE OR REPLACE FUNCTION link_guest_messages(
|
||||
DECLARE
|
||||
v_conversation_id INTEGER;
|
||||
v_count INTEGER;
|
||||
v_first_message TEXT;
|
||||
v_title TEXT;
|
||||
BEGIN
|
||||
-- Логируем входные параметры
|
||||
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;
|
||||
|
||||
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)
|
||||
VALUES (p_user_id, NOW(), NOW())
|
||||
INSERT INTO conversations (user_id, title, created_at, updated_at)
|
||||
VALUES (p_user_id, v_title, NOW(), NOW())
|
||||
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 (
|
||||
@@ -46,11 +67,19 @@ BEGIN
|
||||
created_at
|
||||
FROM guest_messages
|
||||
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
|
||||
RETURNING id
|
||||
)
|
||||
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;
|
||||
$$ 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 crypto = require('crypto');
|
||||
const { saveGuestMessageToDatabase } = require('../db');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// Функция для обработки гостевых сообщений после аутентификации
|
||||
async function processGuestMessages(userId, guestId) {
|
||||
@@ -26,6 +27,30 @@ async function processGuestMessages(userId, guestId) {
|
||||
const guestMessages = guestMessagesResult.rows;
|
||||
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 title = firstMessage.content.length > 30
|
||||
@@ -44,23 +69,23 @@ async function processGuestMessages(userId, guestId) {
|
||||
for (const guestMessage of guestMessages) {
|
||||
console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`);
|
||||
|
||||
// Проверяем существование гостевого сообщения перед созданием связанного сообщения
|
||||
const checkGuestMessage = await db.query(
|
||||
'SELECT id FROM guest_messages WHERE id = $1',
|
||||
// Проверяем, не было ли это сообщение уже обработано
|
||||
const existingMessage = await db.query(
|
||||
'SELECT id FROM messages WHERE guest_message_id = $1',
|
||||
[guestMessage.id]
|
||||
);
|
||||
|
||||
if (checkGuestMessage.rows.length === 0) {
|
||||
console.log(`Guest message ${guestMessage.id} no longer exists, skipping`);
|
||||
if (existingMessage.rows.length > 0) {
|
||||
console.log(`Guest message ${guestMessage.id} already processed, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Сохраняем сообщение пользователя
|
||||
const userMessageResult = await db.query(
|
||||
`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
|
||||
($1, $2, $3, $4, $5, $6)
|
||||
($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *`,
|
||||
[
|
||||
conversation.id,
|
||||
@@ -68,40 +93,39 @@ async function processGuestMessages(userId, guestId) {
|
||||
'user',
|
||||
'user',
|
||||
'web',
|
||||
guestMessage.id,
|
||||
guestMessage.created_at
|
||||
]
|
||||
);
|
||||
|
||||
console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`);
|
||||
|
||||
// Получаем ответ от ИИ
|
||||
console.log('Getting AI response for:', guestMessage.content);
|
||||
const language = guestMessage.language || 'auto';
|
||||
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
|
||||
console.log('AI response received:', aiResponse);
|
||||
|
||||
// Сохраняем ответ от ИИ
|
||||
const aiMessageResult = await db.query(
|
||||
`INSERT INTO messages
|
||||
(conversation_id, content, sender_type, role, channel, created_at)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *`,
|
||||
[
|
||||
conversation.id,
|
||||
aiResponse,
|
||||
'assistant',
|
||||
'assistant',
|
||||
'web',
|
||||
new Date()
|
||||
]
|
||||
);
|
||||
|
||||
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}`);
|
||||
// Получаем ответ от ИИ только для сообщений пользователя (не AI)
|
||||
if (!guestMessage.is_ai) {
|
||||
console.log('Getting AI response for:', guestMessage.content);
|
||||
const language = guestMessage.language || 'auto';
|
||||
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
|
||||
console.log('AI response received:', aiResponse);
|
||||
|
||||
// Сохраняем ответ от ИИ
|
||||
const aiMessageResult = await db.query(
|
||||
`INSERT INTO messages
|
||||
(conversation_id, content, sender_type, role, channel, created_at)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *`,
|
||||
[
|
||||
conversation.id,
|
||||
aiResponse,
|
||||
'assistant',
|
||||
'assistant',
|
||||
'web',
|
||||
new Date()
|
||||
]
|
||||
);
|
||||
|
||||
console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -255,6 +279,24 @@ router.get('/history', async (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 50;
|
||||
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 total = 0;
|
||||
|
||||
@@ -288,214 +330,19 @@ router.get('/history', async (req, res) => {
|
||||
messages = result.rows;
|
||||
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({
|
||||
success: true,
|
||||
messages: messages,
|
||||
total: total
|
||||
});
|
||||
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error getting chat history:', error);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения всех диалогов (только для админов)
|
||||
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'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Экспортируем маршрутизатор и функцию processGuestMessages отдельно
|
||||
module.exports = router;
|
||||
module.exports.processGuestMessages = processGuestMessages;
|
||||
@@ -8,6 +8,7 @@ const { app, nonceStore } = require('./app');
|
||||
const usersRouter = require('./routes/users');
|
||||
const authRouter = require('./routes/auth');
|
||||
const identitiesRouter = require('./routes/identities');
|
||||
const chatRouter = require('./routes/chat');
|
||||
const { pool } = require('./db');
|
||||
const helmet = require('helmet');
|
||||
const { getBot, stopBot } = require('./services/telegramBot');
|
||||
@@ -79,6 +80,7 @@ app.use(session({
|
||||
app.use('/api/users', usersRouter);
|
||||
app.use('/api/auth', authRouter);
|
||||
app.use('/api/identities', identitiesRouter);
|
||||
app.use('/api/chat', chatRouter);
|
||||
|
||||
// Эндпоинт для проверки состояния сервера
|
||||
app.get('/api/health', (req, res) => {
|
||||
|
||||
@@ -391,6 +391,42 @@ class AuthService {
|
||||
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 email = session.pendingEmail;
|
||||
|
||||
// Добавляем 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`,
|
||||
[userId, 'email', email.toLowerCase()]
|
||||
// Проверяем, существует ли уже этот email в user_identities
|
||||
const existingUserQuery = await db.query(
|
||||
`SELECT user_id FROM user_identities
|
||||
WHERE provider = 'email' AND provider_id = $1`,
|
||||
[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;
|
||||
if (session.tempUserId) {
|
||||
@@ -91,7 +112,7 @@ class EmailAuth {
|
||||
|
||||
return {
|
||||
verified: true,
|
||||
userId,
|
||||
userId: finalUserId,
|
||||
email: email.toLowerCase()
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user