Reflex/backend/controllers/adminController.js
Professional 920168e7d1 фикс
2025-05-26 16:56:49 +07:00

435 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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');
// Изменяем статус активности на противоположный
const wasActive = user.isActive;
user.isActive = !user.isActive;
await user.save();
// Если пользователь был активным и теперь блокируется
if (wasActive && !user.isActive) {
console.log(`[AdminController] Блокировка пользователя ${userId}. Причина: ${reason || 'Не указана'}`);
// Находим все активные сокеты пользователя - новый подход с более надежным поиском
const activeSockets = Array.from(io.sockets.sockets.values()).filter(socket => {
return socket &&
socket.handshake &&
socket.handshake.query &&
socket.handshake.query.userId === userId;
});
console.log(`[AdminController] Найдено активных сокетов пользователя: ${activeSockets.length}`);
if (activeSockets.length > 0) {
// Отправляем уведомление по всем активным сокетам пользователя
activeSockets.forEach(socket => {
console.log(`[AdminController] Отправка уведомления о блокировке на сокет: ${socket.id}`);
socket.emit("account_blocked", {
message: 'Ваш аккаунт был заблокирован администратором.',
reason: reason || 'Нарушение правил сервиса',
timestamp: new Date().toISOString(),
blocked: true
});
});
// Принудительно отключаем все сокеты пользователя
setTimeout(() => {
activeSockets.forEach(socket => {
console.log(`[AdminController] Принудительное отключение сокета: ${socket.id}`);
socket.disconnect(true);
});
}, 1000); // Небольшая задержка, чтобы сообщение о блокировке успело доставиться
} else {
console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях`);
}
}
// Если пользователь был неактивным и теперь разблокируется
else if (!wasActive && user.isActive) {
console.log(`[AdminController] Разблокировка пользователя ${userId}`);
// То же самое для поиска сокетов пользователя
const activeSockets = Array.from(io.sockets.sockets.values()).filter(socket => {
return socket &&
socket.handshake &&
socket.handshake.query &&
socket.handshake.query.userId === userId;
});
if (activeSockets.length > 0) {
activeSockets.forEach(socket => {
console.log(`[AdminController] Отправка уведомления о разблокировке на сокет: ${socket.id}`);
socket.emit("account_unblocked", {
message: 'Ваш аккаунт был разблокирован администратором.',
timestamp: new Date().toISOString()
});
});
} else {
console.log(`[AdminController] Пользователь ${userId} не найден в активных соединениях для отправки уведомления о разблокировке`);
}
}
res.json({
message: user.isActive ? 'Пользователь разблокирован' : 'Пользователь заблокирован',
isActive: user.isActive
});
} 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
};