238 lines
10 KiB
JavaScript
238 lines
10 KiB
JavaScript
![]() |
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,
|
|||
|
};
|