2025-05-21 22:13:09 +07:00
|
|
|
|
const mongoose = require('mongoose');
|
|
|
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
|
|
|
|
|
|
const userSchema = new mongoose.Schema(
|
|
|
|
|
{
|
|
|
|
|
name: {
|
|
|
|
|
type: String,
|
|
|
|
|
required: [true, 'Пожалуйста, укажите ваше имя'],
|
|
|
|
|
trim: true,
|
|
|
|
|
},
|
|
|
|
|
email: {
|
|
|
|
|
type: String,
|
|
|
|
|
required: [true, 'Пожалуйста, укажите ваш email'],
|
|
|
|
|
unique: true,
|
|
|
|
|
lowercase: true,
|
|
|
|
|
trim: true,
|
|
|
|
|
match: [
|
|
|
|
|
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
|
|
|
|
|
'Пожалуйста, укажите корректный email',
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
password: {
|
|
|
|
|
type: String,
|
|
|
|
|
required: [true, 'Пожалуйста, укажите пароль'],
|
|
|
|
|
minlength: [6, 'Пароль должен быть не менее 6 символов'],
|
|
|
|
|
select: false,
|
|
|
|
|
},
|
|
|
|
|
dateOfBirth: {
|
|
|
|
|
type: Date,
|
2025-05-25 01:35:58 +07:00
|
|
|
|
required: [true, 'Пожалуйста, укажите дату рождения'],
|
2025-05-21 22:13:09 +07:00
|
|
|
|
},
|
|
|
|
|
gender: {
|
|
|
|
|
type: String,
|
|
|
|
|
enum: ['male', 'female', 'other'],
|
|
|
|
|
},
|
|
|
|
|
bio: {
|
|
|
|
|
type: String,
|
|
|
|
|
trim: true,
|
|
|
|
|
maxlength: [500, 'Описание не должно превышать 500 символов'],
|
|
|
|
|
},
|
|
|
|
|
photos: [
|
|
|
|
|
{
|
|
|
|
|
url: String,
|
|
|
|
|
public_id: String, // Добавляем public_id
|
|
|
|
|
isProfilePhoto: { type: Boolean, default: false },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
location: {
|
|
|
|
|
city: String,
|
|
|
|
|
country: String,
|
|
|
|
|
},
|
|
|
|
|
preferences: {
|
|
|
|
|
gender: { type: String, enum: ['male', 'female', 'other', 'any'], default: 'any' },
|
|
|
|
|
ageRange: {
|
|
|
|
|
min: { type: Number, default: 18 },
|
|
|
|
|
max: { type: Number, default: 99 },
|
|
|
|
|
},
|
2025-05-25 00:57:08 +07:00
|
|
|
|
// Добавляем предпочтения по городу
|
|
|
|
|
cityPreferences: {
|
|
|
|
|
sameCity: { type: Boolean, default: true }, // Показывать только из того же города
|
|
|
|
|
allowedCities: [{ type: String }] // Дополнительные разрешенные города
|
|
|
|
|
}
|
2025-05-21 22:13:09 +07:00
|
|
|
|
},
|
|
|
|
|
isActive: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true,
|
|
|
|
|
},
|
2025-05-25 23:11:02 +07:00
|
|
|
|
isAdmin: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
2025-05-21 22:13:09 +07:00
|
|
|
|
lastSeen: {
|
|
|
|
|
type: Date,
|
|
|
|
|
default: Date.now,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// --- НОВЫЕ ПОЛЯ ДЛЯ ЛАЙКОВ, ПРОПУСКОВ И МЭТЧЕЙ ---
|
|
|
|
|
liked: [{ // Массив ID пользователей, которых лайкнул текущий пользователь
|
|
|
|
|
type: mongoose.Schema.Types.ObjectId,
|
|
|
|
|
ref: 'User' // Ссылка на модель User
|
|
|
|
|
}],
|
|
|
|
|
passed: [{ // Массив ID пользователей, которых пропустил/дизлайкнул текущий пользователь
|
|
|
|
|
type: mongoose.Schema.Types.ObjectId,
|
|
|
|
|
ref: 'User'
|
|
|
|
|
}],
|
|
|
|
|
matches: [{ // Массив ID пользователей, с которыми у текущего пользователя взаимный лайк (мэтч)
|
|
|
|
|
type: mongoose.Schema.Types.ObjectId,
|
|
|
|
|
ref: 'User'
|
|
|
|
|
}]
|
|
|
|
|
// ----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
timestamps: true,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-22 01:03:48 +07:00
|
|
|
|
// Middleware pre('save') для хеширования пароля и установки страны по умолчанию
|
2025-05-21 22:13:09 +07:00
|
|
|
|
userSchema.pre('save', async function (next) {
|
|
|
|
|
if (!this.isModified('password')) {
|
|
|
|
|
return next();
|
|
|
|
|
}
|
2025-05-22 01:03:48 +07:00
|
|
|
|
// Если город указан, а страна нет, устанавливаем страну по умолчанию
|
|
|
|
|
if (this.isModified('location.city') && this.location.city && !this.location.country) {
|
|
|
|
|
this.location.country = 'Россия';
|
|
|
|
|
}
|
2025-05-21 22:13:09 +07:00
|
|
|
|
const salt = await bcrypt.genSalt(10);
|
|
|
|
|
this.password = await bcrypt.hash(this.password, salt);
|
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
userSchema.methods.matchPassword = async function (enteredPassword) {
|
|
|
|
|
return await bcrypt.compare(enteredPassword, this.password);
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-26 18:42:01 +07:00
|
|
|
|
// Статический метод для получения устройств пользователя
|
|
|
|
|
userSchema.statics.findUserDevices = async function(userId) {
|
|
|
|
|
try {
|
|
|
|
|
// Получаем сессии и авторизации пользователя
|
|
|
|
|
const user = await this.findById(userId).select('authHistory devices sessions');
|
|
|
|
|
|
|
|
|
|
if (!user) return [];
|
|
|
|
|
|
|
|
|
|
// Объединяем все возможные источники устройств
|
|
|
|
|
let devices = [];
|
|
|
|
|
|
|
|
|
|
// Из истории авторизаций
|
|
|
|
|
if (user.authHistory && user.authHistory.length > 0) {
|
|
|
|
|
devices = devices.concat(user.authHistory.map(auth => ({
|
|
|
|
|
fingerprint: auth.deviceFingerprint,
|
|
|
|
|
userAgent: auth.userAgent,
|
|
|
|
|
ipAddress: auth.ipAddress,
|
|
|
|
|
lastUsed: auth.timestamp,
|
|
|
|
|
type: 'auth'
|
|
|
|
|
})));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Из списка устройств
|
|
|
|
|
if (user.devices && user.devices.length > 0) {
|
|
|
|
|
devices = devices.concat(user.devices.map(device => ({
|
|
|
|
|
fingerprint: device.fingerprint,
|
|
|
|
|
userAgent: device.userAgent,
|
|
|
|
|
platform: device.platform,
|
|
|
|
|
lastUsed: device.lastActive,
|
|
|
|
|
type: 'device'
|
|
|
|
|
})));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Из активных сессий
|
|
|
|
|
if (user.sessions && user.sessions.length > 0) {
|
|
|
|
|
devices = devices.concat(user.sessions.map(session => ({
|
|
|
|
|
fingerprint: session.deviceFingerprint,
|
|
|
|
|
userAgent: session.userAgent,
|
|
|
|
|
ipAddress: session.ipAddress,
|
|
|
|
|
lastUsed: session.lastActive,
|
|
|
|
|
type: 'session'
|
|
|
|
|
})));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Удаляем дубликаты по fingerprint
|
|
|
|
|
const uniqueDevices = Array.from(
|
|
|
|
|
devices.reduce((map, device) => {
|
|
|
|
|
if (device.fingerprint && !map.has(device.fingerprint)) {
|
|
|
|
|
map.set(device.fingerprint, device);
|
|
|
|
|
}
|
|
|
|
|
return map;
|
|
|
|
|
}, new Map()).values()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return uniqueDevices;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Ошибка при поиске устройств пользователя:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-05-21 22:13:09 +07:00
|
|
|
|
|
|
|
|
|
const User = mongoose.model('User', userSchema);
|
|
|
|
|
|
|
|
|
|
module.exports = User;
|