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,5 +1,6 @@
/* eslint-disable eqeqeq */
/* eslint-disable camelcase */
const mongoose = require('mongoose')
const jwt = require('jsonwebtoken')
const { check, validationResult } = require('express-validator')
const { billboardPublicStatusFilter } = require('../../../utils/moderationStatus')
@@ -25,6 +26,8 @@ const {
createRatingNotification
} = require('../../../utils/commentNotification')
const { buildUnifiedInterestScore, blendFreshIntoRanked } = require('../../../utils/exploreAlgorithm')
const { resolveStoryFlags } = require('../../../utils/storyRingFlags')
const { findActiveAd, buildAdBillboardItem, trackAdEvent } = require('../../../utils/adDelivery')
/** Billboards matching the viewer's cross-content interest profile, for blending into the default (unsorted, unfiltered) listing. */
async function getInterestMatchedAds (userId, filter, excludeIds, limit = 5) {
@@ -540,6 +543,24 @@ const getAdvertisingsWeb = async (req, res, next) => {
} catch {
/* non-blocking: personalization is a bonus, never break the listing */
}
// تبلیغِ نوع billboard — عینِ الگوی first_post (adDelivery.js): فقط صفحه‌ی اول،
// فقط برای کاربرِ واردشده، خطاش هیچ‌وقت نباید لیستِ بیلبوردها رو بشکنه
try {
const viewer = await UserModel.findById(userId)
.select('country province city gender expertise sub_expertise')
.lean()
const sponsoredAd = await findActiveAd('billboard', { ...viewer, _id: userId })
if (sponsoredAd) {
const adBillboard = await buildAdBillboardItem(sponsoredAd)
if (adBillboard) {
newAds = [adBillboard, ...newAds]
trackAdEvent(sponsoredAd._id, 'view').catch(() => {})
}
}
} catch (adError) {
console.error('billboard ad injection error:', adError.message)
}
}
res.status(200).json({
@@ -831,12 +852,26 @@ const addComment = async (req, res, next) => {
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const user = await UserModel.findById(decodedToken.id)
// مثل شاپ‌پروداکت/آموزشگاه: امتیاز فقط روی اولین کامنتِ هر کاربر روی این آگهی مجازه
let rateValue = null
const requestedRate = Number(req.body.rate)
if (requestedRate >= 1 && requestedRate <= 5) {
const existingRating = await AdvertisingComment.findOne({
advertisingId,
userId,
rate: { $ne: null, $gt: 0 }
}).select('_id')
if (!existingRating) rateValue = requestedRate
}
const newComment = await saveCommentWithModerationStatus(
AdvertisingComment,
{
advertisingId,
userId,
text
text,
rate: rateValue
},
'text',
user?.verify_badge
@@ -872,11 +907,62 @@ const getComments = async (req, res, next) => {
populate: [{ path: 'userId', select: '_id profile_image user_type user_name first_name last_name is_verified verify_badge' }]
}
const comments = await AdvertisingComment.paginate({ advertisingId, status: commentStatusFilter() }, options)
const [comments, ratingAgg] = await Promise.all([
AdvertisingComment.paginate({ advertisingId, status: commentStatusFilter() }, options),
AdvertisingComment.aggregate([
{ $match: { advertisingId: new mongoose.Types.ObjectId(advertisingId), rate: { $ne: null, $gt: 0 } } },
{ $group: { _id: null, average: { $avg: '$rate' }, count: { $sum: 1 } } }
])
])
// بررسی وجود توکن (اختیاری — لیست کامنت‌ها بدون لاگین هم قابل مشاهده‌ست)
let currentUserId = null
const authHeader = req.header('Authorization')
if (authHeader) {
const tokenParts = authHeader.split(' ')
if (tokenParts.length === 2) {
try {
currentUserId = jwt.verify(tokenParts[1], process.env.APP_SECRET).id
} catch (err) {
console.error('Invalid Token:', err.message)
}
}
}
let hasRated = false
if (currentUserId) {
const existingRating = await AdvertisingComment.findOne({
advertisingId,
userId: currentUserId,
rate: { $ne: null, $gt: 0 }
}).select('_id')
hasRated = Boolean(existingRating)
}
const commenterIds = comments.docs.map((c) => c.userId?._id).filter(Boolean)
const storyFlags = await resolveStoryFlags(commenterIds, currentUserId)
const commentsWithStoryFlags = comments.docs.map((c) => {
const obj = c.toObject ? c.toObject() : c
if (obj.userId) {
const flags = storyFlags[String(obj.userId._id)] || {}
obj.userId = {
...obj.userId,
has_story: flags.has_story || false,
has_unviewed_story: flags.has_unviewed_story || false
}
}
return obj
})
res.status(200).json({
comments: comments.docs,
comments: commentsWithStoryFlags,
totalPages: comments.totalPages,
totalItems: comments.totalDocs
totalItems: comments.totalDocs,
has_rated: hasRated,
rating: {
average: ratingAgg[0]?.average || 0,
count: ratingAgg[0]?.count || 0
}
})
} catch (error) {
next(error)