Reflex/backend/controllers/adminController.js

506 lines
20 KiB
JavaScript
Raw Normal View History

2025-05-26 12:32:53 +07:00
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;
2025-05-26 20:01:25 +07:00
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: 'Невозможно заблокировать администратора' });
}
2025-05-26 15:52:26 +07:00
// Получаем доступ к io для отправки уведомлений
const io = req.app.get('io');
2025-05-26 17:02:46 +07:00
if (!io) {
console.error('[AdminController] Ошибка: экземпляр Socket.IO не доступен через req.app.get("io")');
return res.status(500).json({ message: 'Внутренняя ошибка сервера: Socket.IO недоступен' });
}
2025-05-26 15:52:26 +07:00
2025-05-26 20:01:25 +07:00
// Изменяем статус блокировки на противоположный
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();
2025-05-26 15:52:26 +07:00
2025-05-26 17:02:46 +07:00
// Получаем глобальный массив активных пользователей из server.js
const activeUsers = req.app.get('activeUsers') || [];
console.log(`[AdminController] Активные пользователи:`, activeUsers);
2025-05-26 20:01:25 +07:00
// Если пользователь теперь заблокирован
if (!wasBlocked && user.blocked) {
console.log(`[AdminController] Блокировка пользователя ${userId}. Причина: ${user.blockReason}`);
2025-05-26 15:52:26 +07:00
2025-05-26 19:07:11 +07:00
// Создаем объект с информацией о блокировке
const blockData = {
message: 'Ваш аккаунт был заблокирован администратором.',
2025-05-26 20:01:25 +07:00
reason: user.blockReason,
2025-05-26 19:07:11 +07:00
timestamp: new Date().toISOString(),
blocked: true,
userId: user._id.toString()
};
// Отправляем глобальное событие блокировки всем клиентам
io.emit('global_user_blocked', {
userId: user._id.toString(),
timestamp: new Date().toISOString()
});
2025-05-26 17:02:46 +07:00
// Ищем пользователя среди активных пользователей
const userSockets = activeUsers.filter(u => u.userId === userId);
console.log(`[AdminController] Найдено активных записей пользователя: ${userSockets.length}`);
2025-05-26 19:07:11 +07:00
// Пытаемся отправить уведомление через все возможные сокеты
let notificationSent = false;
2025-05-26 17:02:46 +07:00
if (userSockets.length > 0) {
2025-05-26 19:07:11 +07:00
for (const userSocket of userSockets) {
2025-05-26 17:02:46 +07:00
try {
2025-05-26 19:07:11 +07:00
// Безопасно получаем сокет и проверяем его существование
if (io.sockets && io.sockets.sockets) {
const socket = io.sockets.sockets.get(userSocket.socketId);
2025-05-26 17:02:46 +07:00
2025-05-26 19:07:11 +07:00
if (socket && socket.connected) {
console.log(`[AdminController] Отправка уведомления о блокировке на сокет: ${userSocket.socketId}`);
socket.emit("account_blocked", blockData);
notificationSent = true;
} else {
console.log(`[AdminController] Сокет не найден или не подключен для ID: ${userSocket.socketId}`);
}
2025-05-26 17:02:46 +07:00
} else {
2025-05-26 19:07:11 +07:00
console.log(`[AdminController] Коллекция сокетов недоступна`);
2025-05-26 17:02:46 +07:00
}
} catch (socketError) {
console.error(`[AdminController] Ошибка при обработке сокета ${userSocket.socketId}:`, socketError);
}
2025-05-26 19:07:11 +07:00
}
2025-05-26 15:52:26 +07:00
} else {
2025-05-26 16:56:49 +07:00
console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях`);
2025-05-26 15:52:26 +07:00
}
2025-05-26 19:07:11 +07:00
if (!notificationSent) {
console.log(`[AdminController] Уведомление о блокировке не было отправлено напрямую, но было отправлено глобальное уведомление`);
}
2025-05-26 15:52:26 +07:00
}
2025-05-26 20:01:25 +07:00
// Если пользователь был заблокирован и теперь разблокируется
else if (wasBlocked && !user.blocked) {
2025-05-26 16:56:49 +07:00
console.log(`[AdminController] Разблокировка пользователя ${userId}`);
2025-05-26 15:52:26 +07:00
// Формируем объект с информацией о разблокировке для отправки через сокет
const unblockInfo = {
message: 'Ваш аккаунт был разблокирован администратором.',
timestamp: new Date().toISOString(),
userId: user._id.toString(),
2025-05-26 20:01:25 +07:00
unblockType: 'admin_action',
userName: user.name,
unblockData: {
2025-05-26 20:01:25 +07:00
canRestoreSession: true,
}
};
2025-05-26 20:01:25 +07:00
// Отправляем событие разблокировки всем клиентам
io.emit('global_user_unblocked', {
userId: user._id.toString(),
timestamp: new Date().toISOString()
});
2025-05-26 20:01:25 +07:00
// Ищем пользователя среди активных пользователей
2025-05-26 17:02:46 +07:00
const userSockets = activeUsers.filter(u => u.userId === userId);
2025-05-26 19:07:11 +07:00
let notificationSent = false;
2025-05-26 15:52:26 +07:00
2025-05-26 17:02:46 +07:00
if (userSockets.length > 0) {
2025-05-26 19:07:11 +07:00
for (const userSocket of userSockets) {
2025-05-26 17:02:46 +07:00
try {
2025-05-26 19:07:11 +07:00
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}`);
}
2025-05-26 17:02:46 +07:00
} else {
2025-05-26 19:07:11 +07:00
console.log(`[AdminController] Коллекция сокетов недоступна`);
2025-05-26 17:02:46 +07:00
}
} catch (socketError) {
console.error(`[AdminController] Ошибка при обработке сокета ${userSocket.socketId}:`, socketError);
}
2025-05-26 19:07:11 +07:00
}
2025-05-26 15:52:26 +07:00
} else {
2025-05-26 16:56:49 +07:00
console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях для отправки уведомления о разблокировке`);
2025-05-26 15:52:26 +07:00
}
2025-05-26 19:07:11 +07:00
if (!notificationSent) {
console.log(`[AdminController] Уведомление о разблокировке не было отправлено напрямую, но было отправлено глобальное уведомление`);
}
2025-05-26 15:52:26 +07:00
}
res.json({
2025-05-26 20:01:25 +07:00
message: user.blocked ? 'Пользователь заблокирован' : 'Пользователь разблокирован',
blocked: user.blocked,
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 {
2025-05-26 12:32:53 +07:00
console.log('[ADMIN] Запрос списка диалогов');
const { page = 1, limit = 20, userId } = req.query;
const skip = (page - 1) * limit;
// Создание фильтра
const filter = {};
// Фильтр по пользователю, если указан
if (userId) {
2025-05-26 12:32:53 +07:00
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;
}
2025-05-26 12:32:53 +07:00
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 });
2025-05-26 12:32:53 +07:00
console.log(`[ADMIN] Найдено диалогов: ${conversations.length}`);
// Получаем общее количество диалогов для пагинации
const total = await Conversation.countDocuments(filter);
2025-05-26 12:32:53 +07:00
// Добавляем дополнительную информацию о диалогах
const conversationsWithInfo = conversations.map(conv => {
const convObj = conv.toObject();
convObj._id = convObj._id.toString(); // Преобразуем ObjectId в строку для безопасности
return convObj;
});
res.json({
2025-05-26 12:32:53 +07:00
conversations: conversationsWithInfo,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
2025-05-26 12:32:53 +07:00
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;
2025-05-26 12:32:53 +07:00
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;
2025-05-26 12:32:53 +07:00
// Проверяем существование диалога с подробным логированием
console.log(`[ADMIN] Поиск диалога с ID: ${conversationId}`);
const conversation = await Conversation.findById(conversationId);
2025-05-26 12:32:53 +07:00
if (!conversation) {
2025-05-26 12:32:53 +07:00
console.log(`[ADMIN] Диалог с ID ${conversationId} не найден в базе данных`);
return res.status(404).json({ message: 'Диалог не найден' });
}
2025-05-26 12:32:53 +07:00
console.log(`[ADMIN] Диалог найден, участники: ${conversation.participants}`);
// Получаем сообщения с пагинацией
const messages = await Message.find({ conversationId })
.populate('sender', 'name email')
.skip(skip)
.limit(parseInt(limit))
.sort({ createdAt: -1 });
2025-05-26 12:32:53 +07:00
console.log(`[ADMIN] Найдено сообщений: ${messages.length}`);
// Получаем общее количество сообщений для пагинации
const total = await Message.countDocuments({ conversationId });
res.json({
messages,
2025-05-26 12:32:53 +07:00
conversation: {
id: conversation._id,
participants: conversation.participants
},
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
2025-05-26 12:32:53 +07:00
console.error('[ADMIN] Ошибка при получении сообщений диалога:', error);
res.status(500).json({ message: 'Ошибка сервера при получении сообщений диалога' });
}
};
2025-05-26 13:27:06 +07:00
/**
* @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,
2025-05-26 13:27:06 +07:00
getConversationById,
getConversationMessages
};