This commit is contained in:
Professional 2025-05-24 02:10:15 +07:00
parent 12a5d0a928
commit dc66c8041c
6 changed files with 143 additions and 21 deletions

View File

@ -1,6 +1,7 @@
const User = require('../models/User');
const cloudinary = require('../config/cloudinaryConfig'); // Импортируем настроенный Cloudinary SDK
const path = require('path'); // Может понадобиться для получения расширения файла
const ProfileView = require('../models/ProfileView');
// @desc Обновить профиль пользователя
// @route PUT /api/users/profile
@ -140,9 +141,7 @@ const getUsersForSwiping = async (req, res, next) => {
gender: user.gender,
bio: user.bio,
mainPhotoUrl: mainPhotoUrl, // <--- Добавляем URL главной фотографии
// photos: user.photos, // Отправлять весь массив фото здесь, возможно, избыточно для карточки свайпа
// Если нужна только одна фото, то mainPhotoUrl достаточно.
// Если нужны несколько превью - можно вернуть ограниченное количество.
photos: user.photos, // Возвращаем все фото для карусели
};
});
@ -393,6 +392,65 @@ const getUserById = async (req, res, next) => {
}
};
// @desc Записать просмотр профиля пользователя
// @route POST /api/users/:userId/view
// @access Private
const recordProfileView = async (req, res, next) => {
try {
const { userId } = req.params;
const viewerId = req.user._id;
const { source = 'other' } = req.body;
// Проверяем, что пользователь не пытается просматривать свой собственный профиль
if (viewerId.equals(userId)) {
return res.status(200).json({ message: 'Просмотр собственного профиля не записывается' });
}
// Проверяем, существует ли пользователь, чей профиль просматривают
const profileOwner = await User.findById(userId);
if (!profileOwner) {
const error = new Error('Пользователь не найден.');
error.statusCode = 404;
return next(error);
}
// Проверяем, был ли уже просмотр от этого пользователя в последние 5 минут
// Это предотвращает спам просмотров при обновлении страницы
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
const recentView = await ProfileView.findOne({
viewer: viewerId,
profileOwner: userId,
viewedAt: { $gte: fiveMinutesAgo }
});
if (recentView) {
console.log(`[USER_CTRL] Недавний просмотр уже существует для пользователя ${viewerId} -> ${userId}`);
return res.status(200).json({ message: 'Просмотр уже записан' });
}
// Создаем новую запись просмотра
const newView = new ProfileView({
viewer: viewerId,
profileOwner: userId,
source: source,
viewedAt: new Date()
});
await newView.save();
console.log(`[USER_CTRL] Записан просмотр профиля ${userId} пользователем ${viewerId}, источник: ${source}`);
res.status(200).json({
message: 'Просмотр профиля записан',
viewId: newView._id
});
} catch (error) {
console.error('[USER_CTRL] Ошибка при записи просмотра профиля:', error.message);
next(error);
}
};
// @desc Получить статистику пользователя для главной страницы
// @route GET /api/users/stats
// @access Private
@ -428,10 +486,10 @@ const getUserStats = async (req, res, next) => {
readBy: { $ne: currentUserId } // Которые текущий пользователь не читал
});
// 4. Для просмотров профиля пока возвращаем заглушку,
// так как у нас нет модели для отслеживания просмотров
// В будущем можно добавить отдельную коллекцию ProfileViews
const profileViewsCount = 0; // TODO: Реализовать отслеживание просмотров
// 4. Получаем количество просмотров профиля
const profileViewsCount = await ProfileView.countDocuments({
profileOwner: currentUserId
});
const stats = {
profileViews: profileViewsCount,
@ -457,5 +515,6 @@ module.exports = {
setMainPhoto,
deletePhoto,
getUserById,
getUserStats, // <--- Добавляем новую функцию
getUserStats,
recordProfileView, // <--- Добавляем новую функцию
};

View File

@ -0,0 +1,38 @@
const mongoose = require('mongoose');
const profileViewSchema = new mongoose.Schema({
// Кто просматривал
viewer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
// Чей профиль просматривали
profileOwner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
// Дата и время просмотра
viewedAt: {
type: Date,
default: Date.now
},
// Источник просмотра (например, 'swipe', 'profile_link', 'chat', 'match')
source: {
type: String,
enum: ['swipe', 'profile_link', 'chat', 'match', 'other'],
default: 'other'
}
}, {
timestamps: true
});
// Составной индекс для предотвращения дублирования просмотров от одного пользователя в короткий период времени
// и для быстрого поиска просмотров конкретного профиля
profileViewSchema.index({ viewer: 1, profileOwner: 1, viewedAt: 1 });
// Индекс для быстрого подсчета просмотров конкретного пользователя
profileViewSchema.index({ profileOwner: 1, viewedAt: -1 });
module.exports = mongoose.model('ProfileView', profileViewSchema);

View File

@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
const { updateUserProfile, getUsersForSwiping, uploadUserProfilePhoto, setMainPhoto, deletePhoto, getUserById, getUserStats } = require('../controllers/userController');
const { updateUserProfile, getUsersForSwiping, uploadUserProfilePhoto, setMainPhoto, deletePhoto, getUserById, getUserStats, recordProfileView } = require('../controllers/userController');
const { protect } = require('../middleware/authMiddleware'); // Нам нужен protect для защиты маршрута
const multer = require('multer'); // 1. Импортируем multer
const path = require('path'); // Может понадобиться для фильтрации файлов
@ -46,6 +46,10 @@ router.post('/profile/photo', protect, upload.single('profilePhoto'), uploadUser
router.put('/profile/photo/:photoId/set-main', protect, setMainPhoto); // <--- НОВЫЙ МАРШРУТ
router.delete('/profile/photo/:photoId', protect, deletePhoto); // <--- НОВЫЙ МАРШРУТ
// Маршрут для записи просмотра профиля
// POST /api/users/:userId/view
router.post('/:userId/view', protect, recordProfileView); // <--- НОВЫЙ МАРШРУТ
// Маршрут для получения профиля по ID (например, для просмотра чужих профилей, если это нужно)
// GET /api/users/:id
router.get('/:userId', protect, getUserById); // <--- НОВЫЙ МАРШРУТ

View File

@ -116,5 +116,10 @@ export default {
// Новый метод для получения статистики пользователя
getUserStats() {
return apiClient.get('/users/stats');
},
// Новый метод для записи просмотра профиля
recordProfileView(userId, source = 'other') {
return apiClient.post(`/users/${userId}/view`, { source });
}
};

View File

@ -741,6 +741,20 @@ onUnmounted(() => {
watch(suggestions, () => {
initPhotoIndices();
}, { deep: true });
// Следим за изменением текущего пользователя для записи просмотров
watch(currentUserToSwipe, async (newUser, oldUser) => {
if (newUser && newUser._id && (!oldUser || newUser._id !== oldUser._id)) {
// Записываем просмотр профиля для нового пользователя
try {
await api.recordProfileView(newUser._id, 'swipe');
console.log('[SwipeView] Просмотр профиля записан для пользователя:', newUser.name, newUser._id);
} catch (viewError) {
console.warn('[SwipeView] Не удалось записать просмотр профиля:', viewError);
// Не показываем ошибку пользователю, так как это не критично
}
}
});
</script>
<style scoped>

View File

@ -222,25 +222,27 @@ const userAge = computed(() => {
// Methods
const loadUser = async () => {
const userId = route.params.userId;
if (!userId) {
error.value = 'ID пользователя не найден';
if (!route.params.id) {
error.value = 'ID пользователя не указан';
loading.value = false;
return;
}
loading.value = true;
error.value = '';
try {
loading.value = true;
error.value = '';
console.log('[UserProfileView] Загрузка пользователя:', userId);
const response = await api.getUserById(userId);
console.log('[UserProfileView] Загрузка пользователя:', route.params.id);
const response = await api.getUserById(route.params.id);
user.value = response.data;
// Инициализируем индекс фото
if (user.value.photos && user.value.photos.length > 0) {
const profilePhotoIndex = user.value.photos.findIndex(p => p.isProfilePhoto);
currentPhotoIndex.value = profilePhotoIndex >= 0 ? profilePhotoIndex : 0;
// Записываем просмотр профиля
try {
await api.recordProfileView(route.params.id, 'profile_link');
console.log('[UserProfileView] Просмотр профиля записан');
} catch (viewError) {
console.warn('[UserProfileView] Не удалось записать просмотр профиля:', viewError);
// Не показываем ошибку пользователю, так как это не критично
}
console.log('[UserProfileView] Пользователь загружен:', user.value);