const mongoose = require('mongoose'); const User = require('../models/User'); const Conversation = require('../models/Conversation'); const Message = require('../models/Message'); const ProfileView = require('../models/ProfileView'); /** * @desc Получить список всех пользователей * @route GET /api/admin/users * @access Admin */ const getAllUsers = async (req, res) => { try { const { search, page = 1, limit = 20, isActive } = req.query; const skip = (page - 1) * limit; // Создание фильтра const filter = {}; // Добавляем поиск по имени или email, если указан if (search) { filter.$or = [ { name: { $regex: search, $options: 'i' } }, { email: { $regex: search, $options: 'i' } } ]; } // Фильтр по статусу активности, если указан if (isActive !== undefined) { filter.isActive = isActive === 'true'; } // Исключаем админа из результатов выдачи filter.isAdmin = { $ne: true }; // Получаем пользователей с пагинацией const users = await User.find(filter) .select('-password') .skip(skip) .limit(parseInt(limit)) .sort({ createdAt: -1 }); // Получаем общее количество пользователей для пагинации const total = await User.countDocuments(filter); res.json({ users, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { console.error('Ошибка при получении списка пользователей:', error); res.status(500).json({ message: 'Ошибка сервера при получении списка пользователей' }); } }; /** * @desc Получить детальную информацию о пользователе * @route GET /api/admin/users/:id * @access Admin */ const getUserDetails = async (req, res) => { try { const userId = req.params.id; const user = await User.findById(userId) .select('-password') .lean(); if (!user) { return res.status(404).json({ message: 'Пользователь не найден' }); } // Получаем статистику для пользователя const messagesCount = await Message.countDocuments({ sender: userId }); const conversationsCount = await Conversation.countDocuments({ participants: userId }); const profileViewsCount = await ProfileView.countDocuments({ profileOwner: userId }); const matchesCount = user.matches ? user.matches.length : 0; const likesGivenCount = user.liked ? user.liked.length : 0; // Добавляем статистику к данным пользователя const userWithStats = { ...user, stats: { messagesCount, conversationsCount, profileViewsCount, matchesCount, likesGivenCount } }; res.json(userWithStats); } catch (error) { console.error('Ошибка при получении информации о пользователе:', error); res.status(500).json({ message: 'Ошибка сервера при получении информации о пользователе' }); } }; /** * @desc Заблокировать/разблокировать пользователя * @route PUT /api/admin/users/:id/toggle-active * @access Admin */ const toggleUserActive = async (req, res) => { try { const userId = req.params.id; const { reason } = req.body; // Причина блокировки const user = await User.findById(userId); if (!user) { return res.status(404).json({ message: 'Пользователь не найден' }); } // Проверка, чтобы нельзя было заблокировать админа if (user.isAdmin) { return res.status(403).json({ message: 'Невозможно заблокировать администратора' }); } // Получаем доступ к io для отправки уведомлений const io = req.app.get('io'); if (!io) { console.error('[AdminController] Ошибка: экземпляр Socket.IO не доступен через req.app.get("io")'); return res.status(500).json({ message: 'Внутренняя ошибка сервера: Socket.IO недоступен' }); } // Изменяем статус блокировки на противоположный const wasBlocked = user.blocked; user.blocked = !user.blocked; if (user.blocked) { // Устанавливаем данные блокировки user.blockReason = reason || 'Нарушение правил сервиса'; user.blockedAt = new Date(); user.blockedBy = req.user._id; } else { // Очищаем данные блокировки user.blockReason = null; user.blockedAt = null; user.blockedBy = null; } await user.save(); // Получаем глобальный массив активных пользователей из server.js const activeUsers = req.app.get('activeUsers') || []; console.log(`[AdminController] Активные пользователи:`, activeUsers); // Если пользователь теперь заблокирован if (!wasBlocked && user.blocked) { console.log(`[AdminController] Блокировка пользователя ${userId}. Причина: ${user.blockReason}`); // Создаем объект с информацией о блокировке const blockData = { message: 'Ваш аккаунт был заблокирован администратором.', reason: user.blockReason, timestamp: new Date().toISOString(), blocked: true, userId: user._id.toString() }; // Отправляем глобальное событие блокировки всем клиентам io.emit('global_user_blocked', { userId: user._id.toString(), timestamp: new Date().toISOString() }); // Ищем пользователя среди активных пользователей const userSockets = activeUsers.filter(u => u.userId === userId); console.log(`[AdminController] Найдено активных записей пользователя: ${userSockets.length}`); // Пытаемся отправить уведомление через все возможные сокеты let notificationSent = false; if (userSockets.length > 0) { for (const userSocket of userSockets) { try { // Безопасно получаем сокет и проверяем его существование if (io.sockets && io.sockets.sockets) { const socket = io.sockets.sockets.get(userSocket.socketId); if (socket && socket.connected) { console.log(`[AdminController] Отправка уведомления о блокировке на сокет: ${userSocket.socketId}`); socket.emit("account_blocked", blockData); notificationSent = true; } else { console.log(`[AdminController] Сокет не найден или не подключен для ID: ${userSocket.socketId}`); } } else { console.log(`[AdminController] Коллекция сокетов недоступна`); } } catch (socketError) { console.error(`[AdminController] Ошибка при обработке сокета ${userSocket.socketId}:`, socketError); } } } else { console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях`); } if (!notificationSent) { console.log(`[AdminController] Уведомление о блокировке не было отправлено напрямую, но было отправлено глобальное уведомление`); } } // Если пользователь был заблокирован и теперь разблокируется else if (wasBlocked && !user.blocked) { console.log(`[AdminController] Разблокировка пользователя ${userId}`); // Формируем объект с информацией о разблокировке для отправки через сокет const unblockInfo = { message: 'Ваш аккаунт был разблокирован администратором.', timestamp: new Date().toISOString(), userId: user._id.toString(), unblockType: 'admin_action', userName: user.name, unblockData: { canRestoreSession: true, } }; // Отправляем событие разблокировки всем клиентам io.emit('global_user_unblocked', { userId: user._id.toString(), timestamp: new Date().toISOString() }); // Ищем пользователя среди активных пользователей const userSockets = activeUsers.filter(u => u.userId === userId); let notificationSent = false; if (userSockets.length > 0) { for (const userSocket of userSockets) { try { if (io.sockets && io.sockets.sockets) { const socket = io.sockets.sockets.get(userSocket.socketId); if (socket && socket.connected) { console.log(`[AdminController] Отправка уведомления о разблокировке на сокет: ${userSocket.socketId}`); socket.emit("account_unblocked", unblockInfo); notificationSent = true; } else { console.log(`[AdminController] Сокет не найден или не подключен для ID: ${userSocket.socketId}`); } } else { console.log(`[AdminController] Коллекция сокетов недоступна`); } } catch (socketError) { console.error(`[AdminController] Ошибка при обработке сокета ${userSocket.socketId}:`, socketError); } } } else { console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях для отправки уведомления о разблокировке`); } if (!notificationSent) { console.log(`[AdminController] Уведомление о разблокировке не было отправлено напрямую, но было отправлено глобальное уведомление`); } } res.json({ message: user.blocked ? 'Пользователь заблокирован' : 'Пользователь разблокирован', blocked: user.blocked, isActive: !user.blocked, // Добавляем isActive для совместимости с клиентом blockReason: user.blockReason }); } catch (error) { console.error('Ошибка при изменении статуса пользователя:', error); res.status(500).json({ message: 'Ошибка сервера при изменении статуса пользователя' }); } }; /** * @desc Получить статистику приложения * @route GET /api/admin/statistics * @access Admin */ const getAppStatistics = async (req, res) => { try { // Общая статистика пользователей const totalUsers = await User.countDocuments({ isAdmin: { $ne: true } }); const activeUsers = await User.countDocuments({ isActive: true, isAdmin: { $ne: true } }); const inactiveUsers = await User.countDocuments({ isActive: false, isAdmin: { $ne: true } }); // Статистика по полу const maleUsers = await User.countDocuments({ gender: 'male' }); const femaleUsers = await User.countDocuments({ gender: 'female' }); const otherGenderUsers = await User.countDocuments({ gender: 'other' }); // Статистика сообщений и диалогов const totalMessages = await Message.countDocuments(); const totalConversations = await Conversation.countDocuments(); // Статистика просмотров профилей const totalProfileViews = await ProfileView.countDocuments(); // Средние значения const messagesPerUser = totalUsers > 0 ? totalMessages / totalUsers : 0; const conversationsPerUser = totalUsers > 0 ? totalConversations / totalUsers : 0; // Новые пользователи за последние 30 дней const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const newUsers30Days = await User.countDocuments({ createdAt: { $gte: thirtyDaysAgo }, isAdmin: { $ne: true } }); res.json({ users: { total: totalUsers, active: activeUsers, inactive: inactiveUsers, newIn30Days: newUsers30Days, genderDistribution: { male: maleUsers, female: femaleUsers, other: otherGenderUsers } }, activity: { totalMessages, totalConversations, totalProfileViews, averages: { messagesPerUser: messagesPerUser.toFixed(2), conversationsPerUser: conversationsPerUser.toFixed(2) } } }); } catch (error) { console.error('Ошибка при получении статистики приложения:', error); res.status(500).json({ message: 'Ошибка сервера при получении статистики приложения' }); } }; /** * @desc Получить список всех диалогов * @route GET /api/admin/conversations * @access Admin */ const getAllConversations = async (req, res) => { try { console.log('[ADMIN] Запрос списка диалогов'); const { page = 1, limit = 20, userId } = req.query; const skip = (page - 1) * limit; // Создание фильтра const filter = {}; // Фильтр по пользователю, если указан if (userId) { console.log(`[ADMIN] Фильтр по пользователю: ${userId}`); if (!mongoose.Types.ObjectId.isValid(userId)) { console.log(`[ADMIN] Недопустимый формат ID пользователя: ${userId}`); return res.status(400).json({ message: 'Недопустимый формат ID пользователя' }); } filter.participants = userId; } console.log(`[ADMIN] Поиск диалогов с фильтром:`, filter); // Получаем диалоги с пагинацией и данными участников const conversations = await Conversation.find(filter) .populate('participants', 'name email photos') .populate('lastMessage') .skip(skip) .limit(parseInt(limit)) .sort({ updatedAt: -1 }); console.log(`[ADMIN] Найдено диалогов: ${conversations.length}`); // Получаем общее количество диалогов для пагинации const total = await Conversation.countDocuments(filter); // Добавляем дополнительную информацию о диалогах const conversationsWithInfo = conversations.map(conv => { const convObj = conv.toObject(); convObj._id = convObj._id.toString(); // Преобразуем ObjectId в строку для безопасности return convObj; }); res.json({ conversations: conversationsWithInfo, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { console.error('[ADMIN] Ошибка при получении списка диалогов:', error); res.status(500).json({ message: 'Ошибка сервера при получении списка диалогов' }); } }; /** * @desc Получить сообщения диалога * @route GET /api/admin/conversations/:id/messages * @access Admin */ const getConversationMessages = async (req, res) => { try { const conversationId = req.params.id; console.log(`[ADMIN] Запрос сообщений для диалога с ID: ${conversationId}`); if (!mongoose.Types.ObjectId.isValid(conversationId)) { console.log(`[ADMIN] Недопустимый формат ID диалога: ${conversationId}`); return res.status(400).json({ message: 'Недопустимый формат ID диалога' }); } const { page = 1, limit = 50 } = req.query; const skip = (page - 1) * limit; // Проверяем существование диалога с подробным логированием console.log(`[ADMIN] Поиск диалога с ID: ${conversationId}`); const conversation = await Conversation.findById(conversationId); if (!conversation) { console.log(`[ADMIN] Диалог с ID ${conversationId} не найден в базе данных`); return res.status(404).json({ message: 'Диалог не найден' }); } console.log(`[ADMIN] Диалог найден, участники: ${conversation.participants}`); // Получаем сообщения с пагинацией const messages = await Message.find({ conversationId }) .populate('sender', 'name email') .skip(skip) .limit(parseInt(limit)) .sort({ createdAt: -1 }); console.log(`[ADMIN] Найдено сообщений: ${messages.length}`); // Получаем общее количество сообщений для пагинации const total = await Message.countDocuments({ conversationId }); res.json({ messages, conversation: { id: conversation._id, participants: conversation.participants }, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { console.error('[ADMIN] Ошибка при получении сообщений диалога:', error); res.status(500).json({ message: 'Ошибка сервера при получении сообщений диалога' }); } }; /** * @desc Получить диалог по ID * @route GET /api/admin/conversations/:id * @access Admin */ const getConversationById = async (req, res) => { try { const conversationId = req.params.id; console.log(`[ADMIN] Запрос диалога с ID: ${conversationId}`); if (!mongoose.Types.ObjectId.isValid(conversationId)) { console.log(`[ADMIN] Недопустимый формат ID диалога: ${conversationId}`); return res.status(400).json({ message: 'Недопустимый формат ID диалога' }); } // Получаем диалог с данными участников const conversation = await Conversation.findById(conversationId) .populate('participants', 'name email photos') .populate('lastMessage'); if (!conversation) { console.log(`[ADMIN] Диалог с ID ${conversationId} не найден в базе данных`); return res.status(404).json({ message: 'Диалог не найден' }); } console.log(`[ADMIN] Диалог найден, возвращаем данные`); res.json(conversation); } catch (error) { console.error('[ADMIN] Ошибка при получении диалога:', error); res.status(500).json({ message: 'Ошибка сервера при получении диалога' }); } }; module.exports = { getAllUsers, getUserDetails, toggleUserActive, getAppStatistics, getAllConversations, getConversationById, getConversationMessages };