2025-05-26 12:32:53 +07:00
|
|
|
|
const mongoose = require('mongoose');
|
2025-05-25 23:11:02 +07:00
|
|
|
|
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 user = await User.findById(userId);
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
return res.status(404).json({ message: 'Пользователь не найден' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Проверка, чтобы нельзя было заблокировать админа
|
|
|
|
|
if (user.isAdmin) {
|
|
|
|
|
return res.status(403).json({ message: 'Невозможно заблокировать администратора' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Изменяем статус активности на противоположный
|
|
|
|
|
user.isActive = !user.isActive;
|
|
|
|
|
await user.save();
|
|
|
|
|
|
|
|
|
|
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 {
|
2025-05-26 12:32:53 +07:00
|
|
|
|
console.log('[ADMIN] Запрос списка диалогов');
|
2025-05-25 23:11:02 +07:00
|
|
|
|
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 пользователя' });
|
|
|
|
|
}
|
2025-05-25 23:11:02 +07:00
|
|
|
|
filter.participants = userId;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-26 12:32:53 +07:00
|
|
|
|
console.log(`[ADMIN] Поиск диалогов с фильтром:`, filter);
|
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
// Получаем диалоги с пагинацией и данными участников
|
|
|
|
|
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}`);
|
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
// Получаем общее количество диалогов для пагинации
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
res.json({
|
2025-05-26 12:32:53 +07:00
|
|
|
|
conversations: conversationsWithInfo,
|
2025-05-25 23:11:02 +07:00
|
|
|
|
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);
|
2025-05-25 23:11:02 +07:00
|
|
|
|
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 диалога' });
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
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}`);
|
2025-05-25 23:11:02 +07:00
|
|
|
|
const conversation = await Conversation.findById(conversationId);
|
2025-05-26 12:32:53 +07:00
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
if (!conversation) {
|
2025-05-26 12:32:53 +07:00
|
|
|
|
console.log(`[ADMIN] Диалог с ID ${conversationId} не найден в базе данных`);
|
2025-05-25 23:11:02 +07:00
|
|
|
|
return res.status(404).json({ message: 'Диалог не найден' });
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-26 12:32:53 +07:00
|
|
|
|
console.log(`[ADMIN] Диалог найден, участники: ${conversation.participants}`);
|
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
// Получаем сообщения с пагинацией
|
|
|
|
|
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}`);
|
|
|
|
|
|
2025-05-25 23:11:02 +07:00
|
|
|
|
// Получаем общее количество сообщений для пагинации
|
|
|
|
|
const total = await Message.countDocuments({ conversationId });
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
messages,
|
2025-05-26 12:32:53 +07:00
|
|
|
|
conversation: {
|
|
|
|
|
id: conversation._id,
|
|
|
|
|
participants: conversation.participants
|
|
|
|
|
},
|
2025-05-25 23:11:02 +07:00
|
|
|
|
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);
|
2025-05-25 23:11:02 +07:00
|
|
|
|
res.status(500).json({ message: 'Ошибка сервера при получении сообщений диалога' });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
getAllUsers,
|
|
|
|
|
getUserDetails,
|
|
|
|
|
toggleUserActive,
|
|
|
|
|
getAppStatistics,
|
|
|
|
|
getAllConversations,
|
|
|
|
|
getConversationMessages
|
|
|
|
|
};
|