This commit is contained in:
payacom
2026-09-24 21:03:27 +03:30
parent 7f0ddfde70
commit 806471241e
43 changed files with 2166 additions and 144 deletions

View File

@@ -1,88 +1,91 @@
const mongoose = require('mongoose');
const CourseModel = require('../models/CourseModel');
const AcademyModel = require('../models/AcademyModel');
const ProjectModel = require('../models/ProjectModel');
const AdvertisingModel = require('../models/AdvertisingModel');
const ShopModel = require('../models/ShopModel');
const ShopProductListingModel = require('../models/ShopProductListingModel');
const BookingConfigModel = require('../models/BookingConfigModel');
const { PUBLIC_COURSE_STATUSES, PUBLIC_BILLBOARD_STATUSES, PUBLIC_PROJECT_STATUSES } = require('./moderationStatus');
const {
PUBLIC_COURSE_STATUSES,
PUBLIC_BILLBOARD_STATUSES,
PUBLIC_PROJECT_STATUSES,
} = require('./moderationStatus');
// اعتبارسنجی مشترک «آیتم قابل‌لینک‌شدن» — هم پست (linked_*_id) هم استوری (لینک‌اورلی) از
// همین یه تابع استفاده می‌کنن تا شرط مالکیت+وضعیت هرکدوم دوبار نوشته نشه. خروجی: شناسه‌ی
// معتبری که باید ذخیره بشه، یا null اگه کاربر مالک نیست/وضعیتش پذیرفته‌شده نیست.
async function resolveLinkedEntity(kind, id, userId) {
function sameId(a, b) {
if (a == null || b == null) return false;
const sa = typeof a === 'object' && a._id != null ? String(a._id) : String(a);
const sb = typeof b === 'object' && b._id != null ? String(b._id) : String(b);
return sa === sb;
}
// اعتبارسنجی مشترک «آیتم قابل‌لینک‌شدن»
// options.requirePublic (پیش‌فرض true): برای لینک داخل پست/استوری وضعیت عمومی لازم است.
// برای صفحه «اشتراک‌گذاری لینک» فقط مالکیت کافی است (requirePublic: false).
async function resolveLinkedEntity(kind, id, userId, options = {}) {
const requirePublic = options.requirePublic !== false;
if (!id || !mongoose.Types.ObjectId.isValid(id)) return null;
switch (kind) {
case 'pkg': {
const course = await CourseModel.findById(id).select('_id user_id status').lean();
// با همون آستانه‌ی «عمومی قابل‌نمایش» که همه‌جای اپ برای پکیج آموزشی استفاده می‌شه
// (temp_accept هم شامل می‌شه) — نه فقط accept نهایی؛ وگرنه هر پکیجی که کاربر تازه
// ثبت کرده (وضعیت پیش‌فرضش temp_accept) توی پیکر قابل‌انتخاب بود ولی همیشه رد می‌شد
if (
course &&
String(course.user_id) === String(userId) &&
PUBLIC_COURSE_STATUSES.includes(course.status)
) {
return String(course._id);
const course = await CourseModel.findById(id)
.select('_id user_id academyId status')
.lean();
if (!course) return null;
if (requirePublic && !PUBLIC_COURSE_STATUSES.includes(course.status)) return null;
// مالکیت: user_id مستقیم روی دوره، یا مالک آکادمیِ دوره
if (sameId(course.user_id, userId)) return String(course._id);
if (course.academyId) {
const academy = await AcademyModel.findById(course.academyId).select('userId').lean();
if (academy && sameId(academy.userId, userId)) return String(course._id);
}
return null;
}
case 'project': {
const project = await ProjectModel.findById(id).select('_id creator_id status').lean();
// عین pkg/billboard — هر وضعیتِ واقعی/متعهدشده و غیرِ رد/لغوشده رو می‌پذیریم، نه فقط
// done/accepted؛ وگرنه تقریباً هیچ پروژه‌ی واقعی‌ای (که بیشترِ عمرش pending/ongoing/
// temp_accept هست، نه دقیقاً done/accepted) قابل‌لینک‌شدن نبود
if (
project &&
String(project.creator_id) === String(userId) &&
PUBLIC_PROJECT_STATUSES.includes(project.status)
) {
return String(project._id);
}
const project = await ProjectModel.findById(id)
.select('_id creator_id status')
.lean();
if (!project) return null;
if (requirePublic && !PUBLIC_PROJECT_STATUSES.includes(project.status)) return null;
if (sameId(project.creator_id, userId)) return String(project._id);
return null;
}
case 'billboard': {
const ad = await AdvertisingModel.findById(id).select('_id creator_id status').lean();
// عین pkg — همون آستانه‌ی «عمومی قابل‌نمایش» بیلبورد (شامل temp_accept/paid هم می‌شه)
if (
ad &&
String(ad.creator_id) === String(userId) &&
PUBLIC_BILLBOARD_STATUSES.includes(ad.status)
) {
return String(ad._id);
}
const ad = await AdvertisingModel.findById(id)
.select('_id creator_id status')
.lean();
if (!ad) return null;
if (requirePublic && !PUBLIC_BILLBOARD_STATUSES.includes(ad.status)) return null;
if (sameId(ad.creator_id, userId)) return String(ad._id);
return null;
}
case 'shop': {
const shop = await ShopModel.findById(id).select('_id owner status').lean();
// draft/rejected هنوز واقعاً وجود عمومی ندارن، ولی pending_review (ثبت‌شده، منتظرِ
// تایید) هم مثل بقیه‌ی موارد بالا باید قابل‌لینک باشه — فقط active بیش‌ازحد سخت‌گیرانه بود
if (
shop &&
String(shop.owner) === String(userId) &&
['active', 'pending_review'].includes(shop.status)
) {
return String(shop._id);
}
if (!shop) return null;
if (requirePublic && !['active', 'pending_review'].includes(shop.status)) return null;
if (sameId(shop.owner, userId)) return String(shop._id);
return null;
}
case 'shopproduct': {
const listing = await ShopProductListingModel.findById(id).select('_id shop status').lean();
if (!listing || !['active', 'pending_shop_approval'].includes(listing.status)) return null;
const shop = await ShopModel.findById(listing.shop).select('_id owner').lean();
if (shop && String(shop.owner) === String(userId)) {
return String(listing._id);
const listing = await ShopProductListingModel.findById(id)
.select('_id shop status')
.lean();
if (!listing) return null;
if (requirePublic && !['active', 'pending_shop_approval'].includes(listing.status)) {
return null;
}
const shop = await ShopModel.findById(listing.shop).select('_id owner').lean();
if (shop && sameId(shop.owner, userId)) return String(listing._id);
return null;
}
case 'booking': {
// برای رزرو، id همون userId مالک کانفیگه (نه _id خودِ BookingConfig) — چون صفحه‌ی
// عمومی رزرو (/booking/[userId]) با شناسه‌ی کاربر مسیردهی می‌شه، نه شناسه‌ی کانفیگ
if (String(id) !== String(userId)) return null;
const config = await BookingConfigModel.findOne({ owner: userId, status: 'active' })
.select('_id')
.lean();
// ref_id برای رزرو = userId مالک کانفیگ (نه _id خودِ BookingConfig)
if (!sameId(id, userId)) return null;
const query = requirePublic
? { owner: userId, status: 'active' }
: { owner: userId };
const config = await BookingConfigModel.findOne(query).select('_id').lean();
return config ? String(userId) : null;
}
default:
@@ -90,4 +93,4 @@ async function resolveLinkedEntity(kind, id, userId) {
}
}
module.exports = { resolveLinkedEntity };
module.exports = { resolveLinkedEntity };