179 lines
5.9 KiB
JavaScript
179 lines
5.9 KiB
JavaScript
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,
|
||
required: [true, 'Пожалуйста, укажите дату рождения'],
|
||
},
|
||
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 },
|
||
},
|
||
// Добавляем предпочтения по городу
|
||
cityPreferences: {
|
||
sameCity: { type: Boolean, default: true }, // Показывать только из того же города
|
||
allowedCities: [{ type: String }] // Дополнительные разрешенные города
|
||
}
|
||
},
|
||
isActive: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
isAdmin: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
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,
|
||
}
|
||
);
|
||
|
||
// Middleware pre('save') для хеширования пароля и установки страны по умолчанию
|
||
userSchema.pre('save', async function (next) {
|
||
if (!this.isModified('password')) {
|
||
return next();
|
||
}
|
||
// Если город указан, а страна нет, устанавливаем страну по умолчанию
|
||
if (this.isModified('location.city') && this.location.city && !this.location.country) {
|
||
this.location.country = 'Россия';
|
||
}
|
||
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);
|
||
};
|
||
|
||
// Статический метод для получения устройств пользователя
|
||
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 [];
|
||
}
|
||
};
|
||
|
||
const User = mongoose.model('User', userSchema);
|
||
|
||
module.exports = User; |