4.1.3
This commit is contained in:
@@ -71,6 +71,23 @@ const adminSchema = new mongoose.Schema({
|
||||
last_online: {
|
||||
type: Date, // ذخیره تاریخ و زمان آخرین بازدید
|
||||
default: Date.now // تنظیم تاریخ به تاریخ فعلی
|
||||
},
|
||||
// super_admin: به همهچیز دسترسی داره (بدون نیاز به لیست permissions). staff: فقط به
|
||||
// مواردی که توی permissions هست. پیشفرض super_admin تا ادمینهای از قبل موجود بعد
|
||||
// از این تغییر دسترسیشون رو از دست ندن
|
||||
role: {
|
||||
type: String,
|
||||
enum: ['super_admin', 'staff'],
|
||||
default: 'super_admin'
|
||||
},
|
||||
permissions: {
|
||||
type: [String],
|
||||
default: []
|
||||
},
|
||||
createdBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Admin',
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
66
models/AnalyticsEventModel.js
Normal file
66
models/AnalyticsEventModel.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
// رویدادهای خام رفتار کاربر (بازدید صفحه، کلیک، شروع/پایان نشست) — برای بخش
|
||||
// «آنالیتیکس» پنل. هر رکورد سبک نگه داشته میشه چون حجم بالایی داره؛ تجمیع/گزارشگیری
|
||||
// توی کنترلرهای panel/analytics انجام میشه، نه اینجا
|
||||
const AnalyticsEventSchema = new mongoose.Schema(
|
||||
{
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
default: null,
|
||||
index: true
|
||||
},
|
||||
sessionId: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['session_start', 'page_view', 'click'],
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
// page_view/click: مسیر صفحهای که رویداد توش رخ داده
|
||||
path: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// فقط page_view: چند میلیثانیه کاربر روی این صفحه مونده (موقع رفتن به صفحهی
|
||||
// بعدی یا بستن تب محاسبه و ارسال میشه)
|
||||
durationMs: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
// فقط click: شناسهی قابلخوندنِ عنصر کلیکشده (data-analytics-id یا برچسب/متن آن)
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
platform: {
|
||||
type: String,
|
||||
enum: ['web', 'android', 'ios'],
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
device: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
browser: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
os: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
{ timestamps: true }
|
||||
)
|
||||
|
||||
AnalyticsEventSchema.index({ createdAt: -1 })
|
||||
AnalyticsEventSchema.index({ type: 1, createdAt: -1 })
|
||||
|
||||
module.exports = mongoose.model('AnalyticsEvent', AnalyticsEventSchema)
|
||||
44
models/AuditLogModel.js
Normal file
44
models/AuditLogModel.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
// تاریخچهی سیستم — هر عملِ نوشتاری (POST/PUT/PATCH/DELETE) که یه ادمین توی پنل انجام
|
||||
// میده خودکار اینجا ثبت میشه (middlewares/auditLogger.js)، بدون نیاز به لاگ دستی
|
||||
// توی تکتکِ کنترلرها
|
||||
const AuditLogSchema = new mongoose.Schema(
|
||||
{
|
||||
adminId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Admin',
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
adminUserName: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
method: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
statusCode: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
// خلاصهی بدنهی درخواست (پسورد/توکن حذف میشه) — برای اینکه معلوم باشه دقیقاً
|
||||
// چه تغییری داده شده
|
||||
body: {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
{ timestamps: true }
|
||||
)
|
||||
|
||||
AuditLogSchema.index({ createdAt: -1 })
|
||||
AuditLogSchema.index({ adminId: 1, createdAt: -1 })
|
||||
|
||||
module.exports = mongoose.model('AuditLog', AuditLogSchema)
|
||||
@@ -23,7 +23,7 @@ const offerSchema = new mongoose.Schema({
|
||||
ref: 'Payment',
|
||||
default: null
|
||||
},
|
||||
// سهم ۳۰٪ گیرنده که فقط با پاسخگویی ظرف ۲۴ ساعت به اولین پیام دایرکت واریز میشود
|
||||
// سهم ۳۰٪ گیرنده که فقط با پاسخگویی ظرف ۱۲ ساعت به اولین پیام دایرکت واریز میشود —
|
||||
pendingCommissionAmount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
@@ -43,6 +43,13 @@ const offerSchema = new mongoose.Schema({
|
||||
commissionCredited: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// وقتی گیرنده ظرف مهلت مقرر به اولین پیام دایرکت پاسخ نداد، سهم ۳۰٪ بهجای گیرنده
|
||||
// به کیف پول فرستنده (کسی که پرداخت کرده) برمیگردد — این فلگ از بازگشتِ دوباره
|
||||
// در اجراهای بعدیِ cron جلوگیری میکند
|
||||
commissionRefunded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -5,11 +5,21 @@ const timestamp = require('mongoose-timestamp')
|
||||
// type مشخص میکنه اورلی متنیه یا لینکِ یکی از موارد قابللینک (پکیج/پروژه/بیلبورد/فروشگاه/
|
||||
// کالا/رزرو، همونهایی که توی ثبت پست هم قابللینکان)
|
||||
const overlaySchema = new mongoose.Schema({
|
||||
type: { type: String, enum: ['text', 'link'], default: 'text' },
|
||||
type: { type: String, enum: ['text', 'link', 'mention', 'hashtag', 'location', 'sticker'], default: 'text' },
|
||||
// text: متن آزاد (type:'text') | متن تگ بدون # (type:'hashtag') | نام مکان (type:'location') |
|
||||
// خودِ ایموجی (type:'sticker')
|
||||
text: { type: String, default: '' },
|
||||
x: { type: Number, default: 0.5 },
|
||||
y: { type: Number, default: 0.5 },
|
||||
font_size: { type: Number, default: 0.06 },
|
||||
// فقط برای type:'text' — کدوم واریانت فونتِ برندِ اپ (Vazir/IRANSansX) استفاده بشه؛
|
||||
// چون اپ فعلاً فقط یک خانوادهفونت با وزنهای مختلف داره (نه چند تایپفیس واقعیِ جدا مثل
|
||||
// اینستاگرام)، این همون گزینههای واقعاً موجودیه
|
||||
font_family: {
|
||||
type: String,
|
||||
enum: ['light', 'regular', 'medium', 'bold', 'black'],
|
||||
default: 'bold'
|
||||
},
|
||||
color: { type: String, default: '#ffffff' },
|
||||
background: { type: String, default: '' },
|
||||
rotation: { type: Number, default: 0 },
|
||||
@@ -17,7 +27,10 @@ const overlaySchema = new mongoose.Schema({
|
||||
// فقط برای type:'link'
|
||||
link_kind: { type: String, default: null },
|
||||
link_id: { type: String, default: null },
|
||||
link_title: { type: String, default: null }
|
||||
link_title: { type: String, default: null },
|
||||
// فقط برای type:'mention'
|
||||
mention_user_id: { type: String, default: null },
|
||||
mention_username: { type: String, default: null }
|
||||
}, { _id: false })
|
||||
|
||||
const storySchema = new mongoose.Schema({
|
||||
|
||||
@@ -258,7 +258,159 @@ const userSchema = new mongoose.Schema({
|
||||
},
|
||||
marital_status: {
|
||||
type: String,
|
||||
enum: ['single', 'divorced'],
|
||||
enum: ['single', 'divorced', 'widowed', 'not_in_relationship', 'prefer_not_to_say'],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
relationship_goal: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: [
|
||||
'friendship',
|
||||
'serious_partner',
|
||||
'marriage_intro',
|
||||
'emotional_relationship',
|
||||
'travel_event_companion',
|
||||
'social_networking',
|
||||
'not_sure'
|
||||
]
|
||||
}],
|
||||
required: false,
|
||||
default: []
|
||||
},
|
||||
seeking_gender: {
|
||||
type: String,
|
||||
enum: ['female', 'male', 'both', 'not_important'],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
desired_age_range: {
|
||||
type: String,
|
||||
enum: ['18_24', '25_30', '31_35', '36_40', '41_50', 'over_50', 'not_important'],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
personality_traits: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: [
|
||||
'kind',
|
||||
'loyal',
|
||||
'funny',
|
||||
'calm',
|
||||
'energetic',
|
||||
'responsible',
|
||||
'ambitious',
|
||||
'emotional',
|
||||
'logical',
|
||||
'patient',
|
||||
'honest',
|
||||
'romantic',
|
||||
'independent',
|
||||
'family_oriented',
|
||||
'adventurous',
|
||||
'introvert',
|
||||
'extrovert'
|
||||
]
|
||||
}],
|
||||
required: false,
|
||||
default: []
|
||||
},
|
||||
lifestyle: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: [
|
||||
'active_healthy',
|
||||
'homebody_calm',
|
||||
'social_outgoing',
|
||||
'busy_career',
|
||||
'traveler',
|
||||
'night_owl',
|
||||
'early_riser',
|
||||
'minimalist',
|
||||
'luxury_lover'
|
||||
]
|
||||
}],
|
||||
required: false,
|
||||
default: []
|
||||
},
|
||||
interests: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: [
|
||||
'movies_series',
|
||||
'music',
|
||||
'books_reading',
|
||||
'sports',
|
||||
'cooking',
|
||||
'photography',
|
||||
'travel',
|
||||
'nature',
|
||||
'gaming',
|
||||
'art_painting',
|
||||
'fashion',
|
||||
'pets',
|
||||
'dancing',
|
||||
'language_learning'
|
||||
]
|
||||
}],
|
||||
required: false,
|
||||
default: []
|
||||
},
|
||||
values: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: [
|
||||
'family',
|
||||
'career_growth',
|
||||
'religion_spirituality',
|
||||
'personal_freedom',
|
||||
'financial_stability',
|
||||
'health_fitness',
|
||||
'personal_growth',
|
||||
'excitement_new_experiences'
|
||||
]
|
||||
}],
|
||||
required: false,
|
||||
default: []
|
||||
},
|
||||
relationship_view: {
|
||||
type: String,
|
||||
enum: [
|
||||
'traditional',
|
||||
'modern',
|
||||
'long_term',
|
||||
'friendship_first',
|
||||
'marriage_soon',
|
||||
'friendship_only_for_now'
|
||||
],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
geo_preference: {
|
||||
type: String,
|
||||
enum: [
|
||||
'same_city_only',
|
||||
'nearby_cities',
|
||||
'whole_province',
|
||||
'whole_iran',
|
||||
'even_abroad',
|
||||
'online_is_fine'
|
||||
],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
children_status: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: ['no_children', 'has_children', 'open_to_children', 'not_now', 'prefer_not_to_say']
|
||||
}],
|
||||
required: false,
|
||||
default: []
|
||||
},
|
||||
smoking_status: {
|
||||
type: String,
|
||||
enum: ['none', 'sometimes', 'yes', 'doesnt_matter', 'prefer_not_to_say'],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
@@ -292,7 +444,7 @@ const userSchema = new mongoose.Schema({
|
||||
},
|
||||
personal_style: {
|
||||
type: String,
|
||||
enum: ['formal', 'sporty'],
|
||||
enum: ['casual', 'classic', 'minimal', 'formal', 'street', 'romantic'],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
@@ -306,18 +458,13 @@ const userSchema = new mongoose.Schema({
|
||||
},
|
||||
income_range: {
|
||||
type: String,
|
||||
enum: ['under_5m', '5m_10m', '10m_20m', 'over_20m'],
|
||||
enum: [
|
||||
'under_40m', '40m_50m', '50m_60m', '60m_70m', '70m_80m',
|
||||
'80m_90m', '90m_100m', '100m_150m', '150m_200m', 'over_200m'
|
||||
],
|
||||
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,
|
||||
@@ -618,6 +765,30 @@ const userSchema = new mongoose.Schema({
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// بکآپ کلید خصوصیِ E2EE رمزنگاریشده با رمز/PIN انتخابیِ کاربر (سرور هیچوقت PIN یا
|
||||
// کلید خام رو نمیبینه، فقط این بلاکِ رمزنگاریشده رو ذخیره میکنه) — برای بازیابیِ
|
||||
// هویتِ رمزنگاری وقتی هیچ دستگاه دیگهای لینکشده در دسترس نیست
|
||||
e2ee_key_backup: {
|
||||
type: {
|
||||
salt: String,
|
||||
iv: String,
|
||||
ciphertext: String,
|
||||
iterations: Number
|
||||
},
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// یک رشتهی تصادفیِ ۳۲بایتیِ per-account که با درخواستِ احرازهویتشده به همین
|
||||
// اکانت، از هر دستگاهی (وب/اندروید) قابلدریافته — بهجای PIN دستی، بهعنوانِ
|
||||
// رمزِ رمزنگاریِ e2ee_key_backup استفاده میشه تا بازیابیِ هویتِ E2EE روی دستگاهِ
|
||||
// جدید کاملاً خودکار و بدون دخالتِ کاربر انجام بشه. توجه: این یعنی تضمینِ
|
||||
// end-to-end دیگه واقعی نیست — هرکسی که به این فیلد (یا سرور) دسترسی پیدا کنه
|
||||
// میتونه بکآپ رو باز کنه؛ این تصمیم آگاهانه به نفعِ هیچوقتگمنشدنِ پیامها گرفته شده
|
||||
e2ee_auto_backup_secret: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
pinned_post_ids: {
|
||||
type: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
|
||||
Reference in New Issue
Block a user