Reflex/backend/controllers/userController.js
2025-05-21 22:13:09 +07:00

238 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};