фикс
This commit is contained in:
parent
12a5d0a928
commit
dc66c8041c
@ -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, // <--- Добавляем новую функцию
|
||||
};
|
38
backend/models/ProfileView.js
Normal file
38
backend/models/ProfileView.js
Normal 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);
|
@ -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); // <--- НОВЫЙ МАРШРУТ
|
||||
|
@ -116,5 +116,10 @@ export default {
|
||||
// Новый метод для получения статистики пользователя
|
||||
getUserStats() {
|
||||
return apiClient.get('/users/stats');
|
||||
},
|
||||
|
||||
// Новый метод для записи просмотра профиля
|
||||
recordProfileView(userId, source = 'other') {
|
||||
return apiClient.post(`/users/${userId}/view`, { source });
|
||||
}
|
||||
};
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
console.log('[UserProfileView] Загрузка пользователя:', userId);
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await api.getUserById(userId);
|
||||
try {
|
||||
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user