full app
This commit is contained in:
@@ -14,6 +14,9 @@ const modelSchema = new mongoose.Schema({
|
||||
payment_authority: { type: String },
|
||||
payment_ref_id: { type: String },
|
||||
status: { type: String, enum: ["pending", "success", "failed", "settled"], default: "success" },
|
||||
// فقط «قصدِ» استفاده از کیف پول رو نگه میداره تا لحظهی تاییدِ verify — کسرِ واقعی
|
||||
// اونجا انجام میشه (utils/walletSplit.js)
|
||||
walletAmountUsed: { type: Number, default: 0 },
|
||||
|
||||
|
||||
});
|
||||
|
||||
72
models/AdModel.js
Normal file
72
models/AdModel.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
// تبلیغِ کاربری — نوعش تعیین میکنه محتواش کجا تزریق میشه: first_post (اول فید)،
|
||||
// first_story (اول نوار استوری)، notification (پوش مستقیم به کاربرهای هدف)، project/
|
||||
// product/shop/billboard (اول لیستِ مربوطه، از utils/adDelivery.js:findActiveAd با همون
|
||||
// ad_type resolve میشه). هدفگذاری (لوکیشن/تخصص/جنسیت) روی همون فیلدهایی که
|
||||
// User/Post/exploreAlgorithm.js از قبل استفاده میکنن سوار شده (province.id عددیه، نه
|
||||
// ObjectId — utils/adPricing.js همین رو resolve میکنه)
|
||||
const AD_TYPES = ['first_post', 'first_story', 'notification', 'project', 'product', 'shop', 'billboard']
|
||||
|
||||
const adSchema = new mongoose.Schema({
|
||||
user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
|
||||
ad_type: { type: String, enum: AD_TYPES, required: true },
|
||||
|
||||
// محتوا — بسته به ad_type فقط یکی از این پر میشه
|
||||
content_post_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', default: null },
|
||||
content_project_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Project', default: null },
|
||||
content_shop_product_id: { type: mongoose.Schema.Types.ObjectId, ref: 'ShopProductListing', default: null },
|
||||
content_shop_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Shop', default: null },
|
||||
content_billboard_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Advertising', default: null },
|
||||
// استوریهای واقعی معمولاً ۲۴ساعته منقضی میشن (StoryModel: expires_at) و از فیدِ
|
||||
// استوریِ معمولی حذف میشن، ولی یه کمپینِ تبلیغاتی میتونه چند روز فعال بمونه — پس
|
||||
// مدیا/اورلیها رو موقعِ ساختِ تبلیغ اینجا کپی/اسنپشات میکنیم تا مستقل از عمرِ
|
||||
// طبیعیِ خودِ استوری باشه (content_story_id فقط برای رفرنسِ منبع نگه داشته میشه)
|
||||
content_story_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Story', default: null },
|
||||
content_media_path: { type: String, default: null },
|
||||
content_media_type: { type: String, default: null },
|
||||
content_overlays: { type: mongoose.Schema.Types.Mixed, default: null },
|
||||
notification_title: { type: String, trim: true, default: null },
|
||||
notification_body: { type: String, trim: true, default: null },
|
||||
|
||||
// هدفگذاری
|
||||
target_country_id: { type: Number, default: null },
|
||||
target_province_id: { type: Number, default: null },
|
||||
target_city_id: { type: Number, default: null },
|
||||
target_expertise: { type: String, trim: true, default: null },
|
||||
target_sub_expertise: { type: [String], default: [] },
|
||||
target_gender: { type: String, enum: ['female', 'male', 'any'], default: 'any' },
|
||||
|
||||
// مالی
|
||||
price: { type: Number, required: true },
|
||||
walletAmountUsed: { type: Number, default: 0 },
|
||||
payment_authority: { type: String, default: null },
|
||||
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending_payment', 'active', 'expired', 'failed'],
|
||||
default: 'pending_payment'
|
||||
},
|
||||
started_at: { type: Date, default: null },
|
||||
// عین بقیهی کمپینهای اپ، یه مدتزمانِ ثابت (AD_CAMPAIGN_DURATION_DAYS توی
|
||||
// adsController.js) بعد از فعالشدن — بعدش دیگه توی تزریقِ فید/استوری/نوتیف لحاظ نمیشه
|
||||
expires_at: { type: Date, default: null },
|
||||
|
||||
views_count: { type: Number, default: 0 },
|
||||
clicks_count: { type: Number, default: 0 }
|
||||
})
|
||||
|
||||
// تزریق (سرچِ یه تبلیغِ فعالِ منطبق روی فید/استوری) و لیستِ «تبلیغهای من» دو مصرفِ
|
||||
// اصلیِ ایندکسن
|
||||
adSchema.index({ status: 1, ad_type: 1, expires_at: 1 })
|
||||
adSchema.index({ user_id: 1, createdAt: -1 })
|
||||
|
||||
adSchema.plugin(timestamp)
|
||||
adSchema.plugin(mongoosePaginate)
|
||||
const AdModel = mongoose.model('Ad', adSchema)
|
||||
// روی خودِ مدل بهعنوان استاتیک — تا adsController.js/adsPanelController.js بدونِ
|
||||
// require جدا به لیستِ معتبرِ ad_type دسترسی داشته باشن (module.exports همون مدله، نه یه آبجکت)
|
||||
AdModel.AD_TYPES = AD_TYPES
|
||||
module.exports = AdModel
|
||||
24
models/AdPricingRuleModel.js
Normal file
24
models/AdPricingRuleModel.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
// قانونِ قیمتگذاریِ ادمین برای یه «سناریو»ی تبلیغ — هر فیلدِ null یعنی وایلدکارت
|
||||
// (روی هر مقداری از اون بعد صدق میکنه)؛ utils/adPricing.js دقیقترین قانونِ منطبق
|
||||
// (بیشترین فیلدِ غیرِnull) رو انتخاب میکنه
|
||||
const adPricingRuleSchema = new mongoose.Schema({
|
||||
ad_type: {
|
||||
type: String,
|
||||
enum: ['first_post', 'first_story', 'notification', 'project', 'product', 'shop', 'billboard'],
|
||||
required: true
|
||||
},
|
||||
province_id: { type: Number, default: null },
|
||||
gender: { type: String, enum: ['female', 'male'], default: null },
|
||||
expertise: { type: String, trim: true, default: null },
|
||||
sub_expertise: { type: String, trim: true, default: null },
|
||||
price: { type: Number, required: true }
|
||||
})
|
||||
|
||||
adPricingRuleSchema.index({ ad_type: 1 })
|
||||
|
||||
adPricingRuleSchema.plugin(timestamp)
|
||||
const AdPricingRuleModel = mongoose.model('AdPricingRule', adPricingRuleSchema)
|
||||
module.exports = AdPricingRuleModel
|
||||
@@ -19,6 +19,11 @@ const adminBroadcastSchema = new mongoose.Schema({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
sentBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Admin',
|
||||
|
||||
@@ -18,6 +18,11 @@ const advertisingCommentSchema = new mongoose.Schema({
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
rate: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'temp_accept', 'accepted', 'rejected'],
|
||||
|
||||
24
models/AutoDirectRuleModel.js
Normal file
24
models/AutoDirectRuleModel.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const autoDirectRuleSchema = new mongoose.Schema({
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
trigger_text: { type: String, required: true, trim: true },
|
||||
direct_message: { type: String, required: true, trim: true },
|
||||
reply_caption: { type: String, required: true, trim: true },
|
||||
is_active: { type: Boolean, default: true },
|
||||
// هر بار این قانون واقعاً اجرا بشه (دایرکت ارسال + کامنتِ پاسخ ثبت بشه) یکی زیاد میشه —
|
||||
// برای نمایشِ «تعداد کامنتهای ارسالی» توی لیستِ AutoDirectScreen.tsx
|
||||
sent_count: { type: Number, default: 0 }
|
||||
})
|
||||
|
||||
// autoDirectTrigger.js هر کامنتِ تازه رو در برابرِ همهی قانونهای فعالِ صاحبِ پست چک میکنه
|
||||
autoDirectRuleSchema.index({ user_id: 1, is_active: 1 })
|
||||
|
||||
autoDirectRuleSchema.plugin(timestamp)
|
||||
const AutoDirectRuleModel = mongoose.model('AutoDirectRule', autoDirectRuleSchema)
|
||||
module.exports = AutoDirectRuleModel
|
||||
@@ -33,6 +33,13 @@ const notificationSchema = new mongoose.Schema({
|
||||
ref: 'User',
|
||||
required: false
|
||||
},
|
||||
// آواتار فرستنده (برای نوتیفهای مربوط به یه کاربر خاص) یا بنرِ دستیِ ادمین
|
||||
// (برای admin_broadcast) — هم توی لیستِ داخلِ اپ و هم توی خودِ پوش FCM استفاده میشه
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// فقط برای اعلانهای همگانی پنل — جدا از read (که یعنی صفحه اعلانها باز شده)،
|
||||
// seen یعنی پوش واقعاً به دستگاه رسیده و clicked یعنی کاربر روش ضربه زده
|
||||
broadcast_id: {
|
||||
|
||||
28
models/ShareableLinkModel.js
Normal file
28
models/ShareableLinkModel.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
// اشتراکگذاری لینک: کاربر مشخص میکنه کدوم محتوای خودش (پکیج/پروژه/بیلبورد/فروشگاه/
|
||||
// کالا/رزرو) وقتی یکی دیگه توی ثبت پست تگش میکنه، توی لیست لینکهای قابلانتخاب اون
|
||||
// کاربر (بلاگر) نشون داده بشه — shareableLinksController.js/for-users همین رکوردها رو میخونه
|
||||
const shareableLinkSchema = new mongoose.Schema({
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
kind: {
|
||||
type: String,
|
||||
enum: ['pkg', 'project', 'billboard', 'shop', 'shopproduct', 'booking'],
|
||||
required: true
|
||||
},
|
||||
ref_id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
shareableLinkSchema.index({ user_id: 1, kind: 1, ref_id: 1 }, { unique: true })
|
||||
|
||||
shareableLinkSchema.plugin(timestamp)
|
||||
const ShareableLinkModel = mongoose.model('ShareableLink', shareableLinkSchema)
|
||||
module.exports = ShareableLinkModel
|
||||
Reference in New Issue
Block a user