const router = require('express').Router(); const { getSupabaseClient } = require('./supabaseClient'); const { getIo } = require('../../../io'); // Импортируем Socket.IO console.log(`📦 [Messages Module] Загружаем модуль messages.js...`); console.log(`📦 [Messages Module] Время загрузки: ${new Date().toISOString()}`); try { const { moderationText } = require('./chat-ai-agent/chat-moderation'); // Импортируем функцию модерации console.log(`✅ [Messages Module] Функция модерации загружена успешно`); } catch (error) { console.error(`❌ [Messages Module] Ошибка загрузки функции модерации:`, error); } try { const MODERATION_CONFIG = require('./chat-ai-agent/moderation-config'); // Импортируем конфигурацию модерации console.log(`✅ [Messages Module] Конфигурация модерации загружена:`, MODERATION_CONFIG); } catch (error) { console.error(`❌ [Messages Module] Ошибка загрузки конфигурации модерации:`, error); } const { moderationText } = require('./chat-ai-agent/chat-moderation'); // Импортируем функцию модерации const MODERATION_CONFIG = require('./chat-ai-agent/moderation-config'); // Импортируем конфигурацию модерации console.log(`📦 [Messages Module] Модуль messages.js загружен полностью`); // Добавляем middleware для логирования всех запросов к messages роутеру // Тестовый эндпоинт для проверки работы роутера router.get('/messages/test', (req, res) => { console.log(`🧪 [Messages Test] Тестовый эндпоинт вызван`); console.log(`🧪 [Messages Test] Время: ${new Date().toISOString()}`); res.json({ status: 'OK', message: 'Messages router работает', timestamp: new Date().toISOString(), moderation_config: MODERATION_CONFIG }); }); // Тестовый эндпоинт для немедленной проверки AI агента router.post('/messages/test-ai', async (req, res) => { console.log(`🧪 [AI Test] === ТЕСТИРОВАНИЕ AI АГЕНТА ===`); console.log(`🧪 [AI Test] Время: ${new Date().toISOString()}`); const { text } = req.body; if (!text) { return res.status(400).json({ error: 'text is required' }); } console.log(`🧪 [AI Test] Тестируем текст: "${text}"`); console.log(`🧪 [AI Test] Функция moderationText доступна: ${typeof moderationText}`); try { console.log(`🧪 [AI Test] Вызываем AI агент напрямую...`); const startTime = Date.now(); const result = await moderationText('', text); const endTime = Date.now(); console.log(`🧪 [AI Test] Результат от AI агента:`, result); console.log(`🧪 [AI Test] Время выполнения: ${endTime - startTime}мс`); const [comment, isApproved, finalMessage] = result; res.json({ success: true, text: text, comment: comment, isApproved: isApproved, finalMessage: finalMessage, processingTime: endTime - startTime, timestamp: new Date().toISOString() }); } catch (error) { console.error(`❌ [AI Test] Ошибка тестирования AI агента:`, error); console.error(`❌ [AI Test] Stack:`, error.stack); res.status(500).json({ success: false, error: error.message, stack: error.stack, timestamp: new Date().toISOString() }); } }); // Получить все сообщения в чате с информацией о пользователе router.get('/messages', async (req, res) => { try { const { chat_id, limit = 50, offset = 0 } = req.query; if (!chat_id) { return res.status(400).json({ error: 'chat_id is required' }); } const supabase = getSupabaseClient(); const { data, error } = await supabase .from('messages') .select(` *, user_profiles ( id, full_name, avatar_url ) `) .eq('chat_id', chat_id) .order('created_at', { ascending: true }) .range(offset, offset + limit - 1); if (error) { return res.status(500).json({ error: 'Failed to fetch messages' }); } // Получаем уникальные ID пользователей из сообщений, у которых нет профиля const messagesWithoutProfiles = data.filter(msg => !msg.user_profiles); const userIds = [...new Set(messagesWithoutProfiles.map(msg => msg.user_id))]; if (userIds.length > 0) { const { data: profiles, error: profilesError } = await supabase .from('user_profiles') .select('id, full_name, avatar_url') .in('id', userIds); if (!profilesError && profiles) { // Добавляем профили к сообщениям data.forEach(message => { if (!message.user_profiles) { message.user_profiles = profiles.find(profile => profile.id === message.user_id) || null; } }); } } res.json(data); } catch (err) { res.status(500).json({ error: 'Unexpected error occurred' }); } }); // Создать новое сообщение router.post('/messages', async (req, res) => { console.log(`🚀 [Message Send] === ВХОД В POST /messages ЭНДПОИНТ ===`); console.log(`🚀 [Message Send] Время входа: ${new Date().toISOString()}`); console.log(`🚀 [Message Send] Request method: ${req.method}`); console.log(`🚀 [Message Send] Request URL: ${req.originalUrl || req.url}`); console.log(`🚀 [Message Send] Request body:`, JSON.stringify(req.body, null, 2)); console.log(`🔌 [Message Send] Получаем Supabase клиент...`); let supabase; try { supabase = getSupabaseClient(); console.log(`✅ [Message Send] Supabase клиент получен успешно`); } catch (error) { console.error(`❌ [Message Send] Ошибка получения Supabase клиента:`, error); return res.status(500).json({ error: 'Database connection error' }); } const { chat_id, user_id, text } = req.body; console.log(`📤 [Message Send] Получен запрос на отправку сообщения:`); console.log(`📤 [Message Send] Chat ID: ${chat_id}`); console.log(`📤 [Message Send] User ID: ${user_id}`); console.log(`📤 [Message Send] Text length: ${text ? text.length : 0} символов`); console.log(`📤 [Message Send] Text preview: "${text ? (text.length > 100 ? text.substring(0, 100) + '...' : text) : 'empty'}"`); if (!chat_id || !user_id || !text) { console.log(`❌ [Message Send] Отклонен: отсутствуют обязательные поля`); console.log(`❌ [Message Send] chat_id: ${chat_id}, user_id: ${user_id}, text: ${text}`); return res.status(400).json({ error: 'chat_id, user_id, and text are required' }); } console.log(`💾 [Message Send] Сохраняем сообщение в Supabase...`); // Создаем сообщение const { data: newMessage, error } = await supabase .from('messages') .insert({ chat_id, user_id, text }) .select('*') .single(); if (error) { console.error(`❌ [Message Send] Ошибка сохранения в Supabase:`, error); return res.status(400).json({ error: error.message }); } console.log(`✅ [Message Send] Сообщение сохранено. ID: ${newMessage.id}`); console.log(`📅 [Message Send] Время создания: ${newMessage.created_at}`); console.log(`👤 [Message Send] Получаем профиль пользователя ${user_id}...`); // Получаем профиль пользователя const { data: userProfile, error: profileError } = await supabase .from('user_profiles') .select('id, full_name, avatar_url') .eq('id', user_id) .single(); if (profileError) { console.log(`⚠️ [Message Send] Профиль пользователя не найден:`, profileError); } else { console.log(`✅ [Message Send] Профиль пользователя получен: ${userProfile.full_name || 'No name'}`); } // Объединяем сообщение с профилем const data = { ...newMessage, user_profiles: userProfile || null }; console.log(`📊 [Message Send] Итоговые данные сообщения подготовлены`); // === МОДЕРАЦИЯ ЧЕРЕЗ SUPABASE REAL-TIME === console.log(`🔄 [Message Send] Модерация будет выполняться через Supabase Real-time подписку`); console.log(`🔄 [Message Send] Статус модерации: ${MODERATION_CONFIG.MODERATION_ENABLED ? 'включена' : 'отключена'}`); console.log(`🔄 [Message Send] Задержка модерации: ${MODERATION_CONFIG.MODERATION_DELAY}мс`); console.log(`🔄 [Message Send] После создания сообщения в БД сработает Supabase Real-time подписка в polling-chat.js`); // Отправка через Socket.IO теперь происходит автоматически через Supabase Real-time подписку // Это предотвращает дублирование сообщений console.log(`✅ [Message Send] Сообщение успешно отправлено. Возвращаем ответ клиенту`); console.log(`📤 [Message Send] === Процесс отправки сообщения завершен ===`); res.json(data); }); // Функция отложенной модерации сообщения async function moderateMessage(messageId, messageText, chatId) { const moderationStartTime = Date.now(); try { console.log(`🔍 [Moderation] === НАЧАЛО МОДЕРАЦИИ СООБЩЕНИЯ ${messageId} ===`); console.log(`🔍 [Moderation] Chat ID: ${chatId}`); console.log(`🔍 [Moderation] Длина текста: ${messageText.length} символов`); console.log(`🔍 [Moderation] Превью текста: "${messageText.length > 100 ? messageText.substring(0, 100) + '...' : messageText}"`); console.log(`🔍 [Moderation] Время запуска: ${new Date().toISOString()}`); // Вызываем функцию модерации console.log(`🔍 [Moderation] Передаем сообщение AI агенту для анализа...`); console.log(`🔍 [Moderation] Функция moderationText доступна: ${typeof moderationText}`); console.log(`🔍 [Moderation] Тип сообщения: ${typeof messageText}`); console.log(`🔍 [Moderation] Текст сообщения: "${messageText}"`); let comment, isApproved, finalMessage; try { const result = await moderationText('', messageText); console.log(`🔍 [Moderation] Результат от AI агента получен:`, result); [comment, isApproved, finalMessage] = result; console.log(`🔍 [Moderation] Распакованные значения: comment="${comment}", isApproved=${isApproved}, finalMessage="${finalMessage}"`); } catch (moderationError) { console.error(`❌ [Moderation] Ошибка при вызове AI агента:`, moderationError); console.error(`❌ [Moderation] Stack trace:`, moderationError.stack); // В случае ошибки одобряем сообщение comment = ''; isApproved = true; finalMessage = messageText; console.log(`⚠️ [Moderation] Используем fallback значения из-за ошибки`); } const moderationTime = Date.now() - moderationStartTime; console.log(`📝 [Moderation] === РЕЗУЛЬТАТ МОДЕРАЦИИ СООБЩЕНИЯ ${messageId} ===`); console.log(`📝 [Moderation] Время модерации: ${moderationTime}мс`); console.log(`📝 [Moderation] Решение: ${isApproved ? '✅ ОДОБРЕНО' : '❌ ОТКЛОНЕНО'}`); console.log(`📝 [Moderation] Комментарий: "${comment || 'отсутствует'}"`); console.log(`📝 [Moderation] Финальный текст: "${finalMessage}"`); if (isApproved) { console.log(`📝 [Moderation] Действие: сообщение остается без изменений`); } else { console.log(`📝 [Moderation] Действие: сообщение будет заменено в базе данных`); } // Если сообщение не прошло модерацию, обновляем его в базе данных if (!isApproved) { console.log(`💾 [Moderation] Начинаем обновление сообщения в базе данных...`); const supabase = getSupabaseClient(); // Сначала получаем информацию о сообщении для получения chat_id console.log(`💾 [Moderation] Получаем данные сообщения из базы...`); const { data: messageData, error: fetchError } = await supabase .from('messages') .select('chat_id, user_id') .eq('id', messageId) .single(); if (fetchError) { console.error(`❌ [Moderation] Ошибка получения данных сообщения ${messageId}:`, fetchError); return; } console.log(`💾 [Moderation] Данные получены. Chat ID: ${messageData.chat_id}, User ID: ${messageData.user_id}`); // Обновляем текст сообщения console.log(`💾 [Moderation] Обновляем текст сообщения на: "${MODERATION_CONFIG.BLOCKED_MESSAGE_TEXT}"`); const { data: updatedMessage, error } = await supabase .from('messages') .update({ text: MODERATION_CONFIG.BLOCKED_MESSAGE_TEXT }) .eq('id', messageId) .select('*') .single(); if (error) { console.error(`❌ [Moderation] Ошибка обновления сообщения ${messageId}:`, error); console.error(`❌ [Moderation] Детали ошибки:`, error); } else { console.log(`✅ [Moderation] Сообщение ${messageId} успешно обновлено в базе данных`); console.log(`✅ [Moderation] Старый текст заменен на: "${updatedMessage.text}"`); console.log(`✅ [Moderation] Время обновления: ${updatedMessage.updated_at || 'не указано'}`); // Отправляем обновление через Socket.IO всем клиентам в чате console.log(`📡 [Moderation] Начинаем отправку обновления через Socket.IO...`); try { const io = getIo(); if (io) { console.log(`📡 [Moderation] Socket.IO подключение активно`); // Получаем профиль пользователя для полной информации console.log(`📡 [Moderation] Получаем профиль пользователя для обновления...`); const { data: userProfile, error: profileError } = await supabase .from('user_profiles') .select('id, full_name, avatar_url') .eq('id', messageData.user_id) .single(); if (profileError) { console.log(`⚠️ [Moderation] Ошибка получения профиля пользователя:`, profileError); } else { console.log(`✅ [Moderation] Профиль пользователя получен: ${userProfile.full_name || 'No name'}`); } const messageWithProfile = { ...updatedMessage, user_profiles: userProfile || null }; console.log(`📡 [Moderation] Отправляем обновление в комнату chat_${messageData.chat_id}...`); io.to(`chat_${messageData.chat_id}`).emit('message_updated', messageWithProfile); console.log(`📤 [Moderation] ✅ Обновление сообщения отправлено в чат ${messageData.chat_id}`); console.log(`📤 [Moderation] Событие: message_updated`); console.log(`📤 [Moderation] Получатели: все участники чата ${messageData.chat_id}`); } else { console.log(`⚠️ [Moderation] Socket.IO подключение недоступно`); } } catch (socketError) { console.error(`❌ [Moderation] Ошибка отправки через Socket.IO:`, socketError); console.error(`❌ [Moderation] Детали ошибки Socket.IO:`, socketError.message); } } } else { console.log(`✅ [Moderation] Сообщение ${messageId} прошло модерацию - никаких действий не требуется`); } const totalTime = Date.now() - moderationStartTime; console.log(`🔍 [Moderation] === МОДЕРАЦИЯ СООБЩЕНИЯ ${messageId} ЗАВЕРШЕНА ===`); console.log(`🔍 [Moderation] Общее время процесса: ${totalTime}мс`); console.log(`🔍 [Moderation] Время завершения: ${new Date().toISOString()}`); } catch (error) { const totalTime = Date.now() - moderationStartTime; console.error(`❌ [Moderation] === ОШИБКА МОДЕРАЦИИ СООБЩЕНИЯ ${messageId} ===`); console.error(`❌ [Moderation] Время до ошибки: ${totalTime}мс`); console.error(`❌ [Moderation] Тип ошибки: ${error.name || 'Unknown'}`); console.error(`❌ [Moderation] Сообщение ошибки: ${error.message || 'Unknown error'}`); console.error(`❌ [Moderation] Полная ошибка:`, error); console.error(`❌ [Moderation] Стек ошибки:`, error.stack); console.error(`❌ [Moderation] === КОНЕЦ ОБРАБОТКИ ОШИБКИ ===`); } } // Получить конкретное сообщение router.get('/messages/:message_id', async (req, res) => { const supabase = getSupabaseClient(); const { message_id } = req.params; // Получаем сообщение const { data: message, error } = await supabase .from('messages') .select('*') .eq('id', message_id) .single(); if (error) return res.status(400).json({ error: error.message }); // Получаем профиль пользователя const { data: userProfile } = await supabase .from('user_profiles') .select('id, full_name, avatar_url') .eq('id', message.user_id) .single(); // Объединяем сообщение с профилем const data = { ...message, user_profiles: userProfile || null }; res.json(data); }); // Получить последние сообщения для каждого чата (для списка чатов) router.get('/chats/last-messages', async (req, res) => { const supabase = getSupabaseClient(); const { building_id } = req.query; if (!building_id) { return res.status(400).json({ error: 'building_id required' }); } // Получаем чаты и их последние сообщения через обычные запросы const { data: chats, error: chatsError } = await supabase .from('chats') .select('*') .eq('building_id', building_id); if (chatsError) return res.status(400).json({ error: chatsError.message }); // Для каждого чата получаем последнее сообщение const chatsWithMessages = await Promise.all( chats.map(async (chat) => { const { data: lastMessage } = await supabase .from('messages') .select(` *, user_profiles:user_id ( id, full_name, avatar_url ) `) .eq('chat_id', chat.id) .order('created_at', { ascending: false }) .limit(1) .single(); return { ...chat, last_message: lastMessage || null }; }) ); res.json(chatsWithMessages); }); // Удалить сообщение (только для автора) router.delete('/messages/:message_id', async (req, res) => { const supabase = getSupabaseClient(); const { message_id } = req.params; const { user_id } = req.body; if (!user_id) { return res.status(400).json({ error: 'user_id required' }); } // Проверяем, что пользователь является автором сообщения const { data: message, error: fetchError } = await supabase .from('messages') .select('user_id') .eq('id', message_id) .single(); if (fetchError) return res.status(400).json({ error: fetchError.message }); if (message.user_id !== user_id) { return res.status(403).json({ error: 'You can only delete your own messages' }); } const { error } = await supabase .from('messages') .delete() .eq('id', message_id); if (error) return res.status(400).json({ error: error.message }); res.json({ success: true, message: 'Message deleted successfully' }); }); module.exports = router;