const User = require('../models/User'); const cloudinary = require('../config/cloudinaryConfig'); // Импортируем настроенный Cloudinary SDK const path = require('path'); // Может понадобиться для получения расширения файла // @desc Обновить профиль пользователя // @route PUT /api/users/profile // @access Private (пользователь должен быть аутентифицирован) const updateUserProfile = async (req, res, next) => { try { console.log('Запрос на обновление профиля от пользователя:', req.user._id); console.log('Данные для обновления:', req.body); // req.user должен быть доступен благодаря middleware 'protect' const user = await User.findById(req.user._id); if (user) { // Обновляем только те поля, которые пользователь может изменять user.name = req.body.name || user.name; // Обрабатываем dateOfBirth, учитывая null и пустые строки if (req.body.dateOfBirth === null || req.body.dateOfBirth === '') { user.dateOfBirth = undefined; // Удаляем поле, если оно пустое или null } else if (req.body.dateOfBirth) { user.dateOfBirth = req.body.dateOfBirth; } // Обрабатываем gender, учитывая null и пустые строки if (req.body.gender === null || req.body.gender === '') { user.gender = undefined; // Удаляем поле, если оно пустое или null } else if (req.body.gender) { user.gender = req.body.gender; } // Обрабатываем bio, учитывая null и пустые строки if (req.body.bio === null) { user.bio = ''; // Устанавливаем пустую строку, если null } else { user.bio = req.body.bio || user.bio; } // Обновление местоположения (если передано) if (req.body.location) { user.location.city = req.body.location.city || user.location.city; user.location.country = req.body.location.country || user.location.country; } // Обновление предпочтений (если переданы) if (req.body.preferences) { if (req.body.preferences.gender) { user.preferences.gender = req.body.preferences.gender; } if (req.body.preferences.ageRange) { user.preferences.ageRange.min = req.body.preferences.ageRange.min || user.preferences.ageRange.min; user.preferences.ageRange.max = req.body.preferences.ageRange.max || user.preferences.ageRange.max; } } console.log('Данные пользователя перед сохранением:', user); const updatedUser = await user.save(); console.log('Профиль успешно обновлен:', updatedUser); res.status(200).json({ _id: updatedUser._id, name: updatedUser.name, email: updatedUser.email, dateOfBirth: updatedUser.dateOfBirth, gender: updatedUser.gender, bio: updatedUser.bio, photos: updatedUser.photos, location: updatedUser.location, preferences: updatedUser.preferences, message: 'Профиль успешно обновлен!' }); } else { res.status(404); throw new Error('Пользователь не найден.'); } } catch (error) { console.error(`Ошибка в ${req.method} ${req.originalUrl}:`, error.message); // Более информативный лог // Устанавливаем statusCode на объекте ошибки, если он еще не установлен // или если мы хотим его переопределить для этого конкретного случая if (error.name === 'ValidationError' || error.name === 'CastError') { error.statusCode = 400; } else if (error.message === 'Пользователь не найден.') { // Если мы сами выбросили эту ошибку с throw error.statusCode = 404; } else if (!error.statusCode) { // Если это какая-то другая ошибка без явно установленного statusCode, // можно считать ее серверной ошибкой. error.statusCode = 500; } // Передаем ошибку (с установленным error.statusCode) в наш центральный errorHandler next(error); } }; const getUsersForSwiping = async (req, res, next) => { try { const currentUserId = req.user._id; // TODO: Более сложная логика фильтрации и сортировки const users = await User.find({ _id: { $ne: currentUserId }, // Можно добавить условие, чтобы у пользователя было хотя бы одно фото // 'photos.0': { $exists: true } // Если нужно показывать только тех, у кого есть фото }) .select('name dateOfBirth gender bio photos preferences.ageRange'); // photos все еще нужны для выбора главной const usersWithAgeAndPhoto = users.map(user => { let age = null; if (user.dateOfBirth) { const birthDate = new Date(user.dateOfBirth); const today = new Date(); age = today.getFullYear() - birthDate.getFullYear(); const m = today.getMonth() - birthDate.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) { age--; } } // --- Логика выбора главной фотографии --- let mainPhotoUrl = null; if (user.photos && user.photos.length > 0) { // Ищем фото, помеченное как главное const profilePic = user.photos.find(photo => photo.isProfilePhoto === true); if (profilePic) { mainPhotoUrl = profilePic.url; } else { // Если нет явно помеченного главного фото, берем первое из массива mainPhotoUrl = user.photos[0].url; } } // ----------------------------------------- return { _id: user._id, name: user.name, age: age, gender: user.gender, bio: user.bio, mainPhotoUrl: mainPhotoUrl, // <--- Добавляем URL главной фотографии // photos: user.photos, // Отправлять весь массив фото здесь, возможно, избыточно для карточки свайпа // Если нужна только одна фото, то mainPhotoUrl достаточно. // Если нужны несколько превью - можно вернуть ограниченное количество. }; }); res.status(200).json(usersWithAgeAndPhoto); } catch (error) { console.error('Ошибка при получении пользователей для свайпа:', error.message); next(error); } }; const uploadUserProfilePhoto = async (req, res, next) => { try { const user = await User.findById(req.user._id); if (!user) { const error = new Error('Пользователь не найден.'); error.statusCode = 404; return next(error); } if (!req.file) { // req.file будет добавлен multer'ом const error = new Error('Файл для загрузки не найден.'); error.statusCode = 400; return next(error); } console.log('[USER_CTRL] Получен файл для загрузки:', req.file.originalname, 'Размер:', req.file.size); // Проверяем инициализацию Cloudinary if (!cloudinary || !cloudinary.uploader) { console.error('[USER_CTRL] Ошибка: Cloudinary не настроен'); const error = new Error('Ошибка настройки службы хранения файлов.'); error.statusCode = 500; return next(error); } // Загрузка файла в Cloudinary const b64 = Buffer.from(req.file.buffer).toString("base64"); let dataURI = "data:" + req.file.mimetype + ";base64," + b64; const result = await cloudinary.uploader.upload(dataURI, { folder: `dating_app/user_photos/${user._id}`, resource_type: 'image', }); console.log('[USER_CTRL] Файл успешно загружен в Cloudinary:', result.secure_url); const newPhoto = { url: result.secure_url, public_id: result.public_id, isProfilePhoto: user.photos.length === 0, }; // Добавляем новое фото в массив фотографий пользователя user.photos.push(newPhoto); await user.save(); // Получаем обновленного пользователя с обновленным массивом фотографий const updatedUser = await User.findById(req.user._id); // Важно! Явно создаем массив allPhotos для ответа, чтобы убедиться, что структура каждого объекта фото совпадает const allPhotos = updatedUser.photos.map(photo => ({ url: photo.url, public_id: photo.public_id, isProfilePhoto: photo.isProfilePhoto })); // Возвращаем ответ с точной структурой, ожидаемой тестами res.status(200).json({ message: 'Фотография успешно загружена!', photo: newPhoto, allPhotos: allPhotos }); } catch (error) { console.error('[USER_CTRL] Ошибка при загрузке фотографии:', error.message, error.stack); if (error.http_code) { error.statusCode = error.http_code; } next(error); } }; // Другие возможные функции для userController (например, getUserById, getAllUsers для админа и т.д.) // const getUserById = async (req, res, next) => { ... }; module.exports = { updateUserProfile, getUsersForSwiping, uploadUserProfilePhoto, // getUserById, };