This commit is contained in:
payacom
2026-08-31 20:45:41 +03:30
parent fa9f23c61d
commit 3f416a7a60
367 changed files with 621895 additions and 6443 deletions

View File

@@ -6,7 +6,8 @@ const userSchema = new mongoose.Schema({
type: String,
required: false,
trim: true,
unique: true
unique: true,
sparse: true
},
otp: {
type: String,
@@ -21,6 +22,93 @@ const userSchema = new mongoose.Schema({
required: false,
default: null
},
email: {
type: String,
required: false,
trim: true,
lowercase: true,
unique: true,
sparse: true
},
google_id: {
type: String,
required: false,
trim: true,
unique: true,
sparse: true
},
auth_provider: {
type: String,
required: false,
enum: ['mobile', 'google', 'instagram'],
default: 'mobile'
},
instagram_id: {
type: String,
required: false,
trim: true,
unique: true,
sparse: true
},
instagram_username: {
type: String,
required: false,
trim: true,
default: null
},
instagram_account_type: {
type: String,
required: false,
enum: ['BUSINESS', 'MEDIA_CREATOR', null],
default: null
},
instagram_access_token_enc: {
type: String,
required: false,
default: null
},
instagram_token_expires_at: {
type: Date,
required: false,
default: null
},
instagram_connected_at: {
type: Date,
required: false,
default: null
},
instagram_last_synced_at: {
type: Date,
required: false,
default: null
},
instagram_sync_prefs: {
bio: { type: Boolean, default: false },
posts: { type: Boolean, default: false },
reels: { type: Boolean, default: false },
profile_picture: { type: Boolean, default: false },
auto_update: { type: Boolean, default: false }
},
email_verified: {
type: Boolean,
required: false,
default: false
},
totp_secret: {
type: String,
required: false,
default: null
},
totp_pending_secret: {
type: String,
required: false,
default: null
},
totp_enabled: {
type: Boolean,
required: false,
default: false
},
user_name: {
type: String,
required: false,
@@ -69,6 +157,21 @@ const userSchema = new mongoose.Schema({
maxLength: 255,
default: 'none'
},
license_verified: {
type: Boolean,
required: false,
default: false
},
gold_badge: {
type: Boolean,
required: false,
default: false
},
verify_badge: {
type: String,
enum: ['none', 'gray', 'blue', 'green', 'gold'],
default: 'none'
},
profile_image: {
type: String,
required: false,
@@ -97,6 +200,14 @@ const userSchema = new mongoose.Schema({
maxLength: 255,
default: null
},
display_sub_expertise: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
gender: {
type: String,
required: false,
@@ -145,6 +256,73 @@ const userSchema = new mongoose.Schema({
maxLength: 255,
default: null
},
marital_status: {
type: String,
enum: ['single', 'divorced'],
required: false,
default: null
},
residence_type: {
type: String,
enum: ['owner', 'tenant'],
required: false,
default: null
},
residence_area: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
vehicle_type: {
type: String,
enum: ['none', 'car', 'motorcycle'],
required: false,
default: null
},
vehicle_details: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
personal_style: {
type: String,
enum: ['formal', 'sporty'],
required: false,
default: null
},
job: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
income_range: {
type: String,
enum: ['under_5m', '5m_10m', '10m_20m', 'over_20m'],
required: false,
default: null
},
moral_traits: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1024,
default: null
},
personal_details_public: {
type: Boolean,
required: false,
default: false
},
cooperation_type: {
type: String,
required: false,
@@ -203,6 +381,10 @@ const userSchema = new mongoose.Schema({
maxLength: 512,
default: null
},
country: {
type: Object,
default: null
},
province: {
type: Object,
default: null
@@ -263,7 +445,10 @@ const userSchema = new mongoose.Schema({
trim: true,
minLength: 1,
maxLength: 512,
default: null
// پیش‌فرض قبلاً null بود — یعنی تا اولین کامنت امتیازدار روی کاربر (recalculateUserRating
// در utils/commentRating.js)، هیچ سطحی نداشت. الان از همون رشته‌ای که getUserLevel با
// صفر ستاره برمی‌گردونه استفاده می‌کنیم، تا هر کاربر از ابتدا سطح واقعی داشته باشه
default: 'تازه وارد'
},
user_score: {
type: String,
@@ -322,6 +507,15 @@ const userSchema = new mongoose.Schema({
required: false,
default: null
},
block_permanent: {
type: Boolean,
required: false,
default: false
},
lifetime_free_creations: {
type: Number,
default: 0
},
advertising_profiles: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'AdvertisingProfile'
@@ -362,7 +556,78 @@ const userSchema = new mongoose.Schema({
last_free_offer_date: {
type: Date,
default: null
}
},
account_status: {
type: String,
enum: ['active', 'deactivated'],
default: 'active',
index: true
},
deactivated_at: {
type: Date,
default: null
},
reactivation_locked_until: {
type: Date,
default: null
},
scheduled_purge_at: {
type: Date,
default: null
},
allow_save_posts: {
type: Boolean,
default: true
},
ghost_mode: {
type: Boolean,
default: false
},
post_location_enabled: {
type: Boolean,
default: false
},
is_private: {
type: Boolean,
default: false
},
// تنظیمات کیف پول — امکان دریافت هدیه (پروفایل + چت) و اینکه آیا درخواست همکاری
// (offer) به این کاربر رایگانه یا طبق قیمت معمول پرداختی می‌شه
gift_enabled: {
type: Boolean,
default: true
},
offer_payment_enabled: {
type: Boolean,
default: true
},
preferred_language: {
type: String,
enum: require('../utils/appLanguages').APP_LANGUAGE_CODES,
default: 'fa'
},
e2ee_public_key: {
type: String,
required: false,
default: null
},
pinned_post_ids: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}],
default: []
},
// توکن‌های FCM دستگاه‌های کاربر — آرایه چون یک کاربر می‌تونه چند دستگاه اندروید داشته باشه
fcm_tokens: {
type: [String],
default: []
},
// توکن‌های FCM Web Push (مرورگر) — جدا از fcm_tokens چون توکن‌های وب و اندروید فرمت/طول عمر متفاوتی دارن
fcm_web_tokens: {
type: [String],
default: []
},
// contactInfo: {
// phone: String,
// mobile: String,
@@ -387,5 +652,25 @@ const userSchema = new mongoose.Schema({
userSchema.plugin(timestamp)
userSchema.plugin(mongoosePaginate)
// وقتی فیلدهای مرتبط با «یافتن همکار» تغییر می‌کنن (یا ثبت‌نام کامل می‌شه)، جستجوهای
// ذخیره‌شده‌ی فعال رو چک می‌کنه — همه‌ی کنترلرهای ویرایش پروفایل در نهایت save() صدا
// می‌زنن، پس اینجا تک‌نقطه‌ای‌ترین جاییه که بدون تکرار در هر کنترلر جدا کار می‌کنه.
//
// isModified() باید در pre('save') خونده بشه، نه post('save') — مونگوس وضعیت
// «تغییر کرده» رو به‌عنوان بخشی از خود save() پاک می‌کنه، پس تا زمانی که به
// post('save') برسیم isModified() برای همه‌ی فیلدها false برمی‌گرده. مقدار رو
// از طریق $locals بین pre و post منتقل می‌کنیم.
userSchema.pre('save', function (next) {
this.$locals.partnerAlertsRelevant = require('../utils/partnerMatch').shouldCheck(this)
next()
})
userSchema.post('save', function (doc) {
if (doc.$locals.partnerAlertsRelevant) {
require('../utils/partnerMatch').checkPartnerAlerts(doc).catch(() => {})
}
})
const UserModel = mongoose.model('User', userSchema)
module.exports = UserModel