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

@@ -1,6 +1,17 @@
const UserModel = require('../../../models/UserModel')
const jwt = require('jsonwebtoken')
const NotificationModel = require('../../../models/NotificationModel')
const jMoment = require('moment-jalaali')
const { getOrSetCache, invalidateCache } = require('../../../utils/cache')
function unreadNotificationsCacheKey (userId) {
return `unread:notifications:${userId}`
}
// شمارنده‌ی نوتیف‌های نخونده خیلی پرتکرار خونده می‌شه (بج روی آیکون زنگوله، تقریباً
// هر بار باز شدنِ اپ) — TTLِ کوتاه به‌جای شمارنده‌ی incremental، چون نوتیف‌های جدید از
// ده‌ها نقطه‌ی مختلفِ کد (لایک/کامنت/فالو/...) ساخته می‌شن و ردیابیِ دقیقِ همه‌شون
// برای invalidate کردن عملی نیست؛ فقط نقطه‌ی "خونده‌شدنِ همه" (پایین) صریحاً invalidate می‌شه
const UNREAD_NOTIFICATIONS_CACHE_TTL = 20
const getNotifications = async (req, res, next) => {
try {
@@ -28,15 +39,43 @@ const getNotifications = async (req, res, next) => {
let notifications = await NotificationModel.paginate(filter, options)
const totalPages = notifications.totalPages
const totalItems = notifications.totalDocs
const actorIds = [
...new Set(
notifications.docs
.map((n) => n.actor_id)
.filter(Boolean)
.map(String)
)
]
const actors = actorIds.length
? await UserModel.find({ _id: { $in: actorIds } })
.select('user_name profile_image first_name last_name')
.lean()
: []
const actorMap = new Map(actors.map((a) => [String(a._id), a]))
notifications = notifications?.docs.map(notification => {
const jDate = jMoment(notification.createdAt).format('jYYYY-jMM-jDD HH:mm')
const actorDoc = notification.actor_id
? actorMap.get(String(notification.actor_id))
: null
return {
...notification._doc,
createdAt: jDate
createdAt: jDate,
actor: actorDoc
? {
_id: String(actorDoc._id),
user_name: actorDoc.user_name,
profile_image: actorDoc.profile_image,
first_name: actorDoc.first_name,
last_name: actorDoc.last_name
}
: null
}
})
// تغییر وضعیت "read" اعلان‌ها به "true"
await NotificationModel.updateMany({ user_id: userId }, { read: true })
invalidateCache(unreadNotificationsCacheKey(userId)).catch(() => {})
res.json({
notifications,
totalPages, // ارسال تعداد کل صفحات
@@ -55,7 +94,11 @@ const unreadNotifications = async (req, res, next) => {
const userId = decodedToken.id
// دریافت تعداد اعلان‌های جدید برای کاربر
const newNotificationsCount = await NotificationModel.countDocuments({ user_id: userId, read: false }).exec()
const newNotificationsCount = await getOrSetCache(
unreadNotificationsCacheKey(userId),
UNREAD_NOTIFICATIONS_CACHE_TTL,
() => NotificationModel.countDocuments({ user_id: userId, read: false }).exec()
)
res.json({ newNotificationsCount })
} catch (error) {
@@ -63,6 +106,77 @@ const unreadNotifications = async (req, res, next) => {
}
}
module.exports = {
getNotifications, unreadNotifications
const registerPushToken = async (req, res, next) => {
try {
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const { fcmToken, platform } = req.body
if (!fcmToken) return res.status(400).json({ message: 'fcmToken الزامی است' })
// توکن‌های وب (مرورگر) جدا از توکن‌های اندروید ذخیره می‌شن
const field = platform === 'web' ? 'fcm_web_tokens' : 'fcm_tokens'
// یه توکنِ دستگاه (fcmToken) مختص یه اکانتِ خاص نیست — وقتی کاربر از یه اکانت خارج
// و با اکانت دیگه‌ای وارد می‌شه، همون توکن قبلی دوباره ثبت می‌شه، ولی تا الان از
// داکیومنت اکانت قبلی هیچ‌وقت پاک نمی‌شد؛ نتیجه‌ش این بود که اعلان‌های اکانت قبلی
// هم همچنان روی همین دستگاه می‌رسید. اول از همه‌ی کاربرهای دیگه پاک می‌شه
await UserModel.updateMany(
{ _id: { $ne: userId } },
{ $pull: { fcm_tokens: fcmToken, fcm_web_tokens: fcmToken } }
)
// اضافه‌کردن توکن جدید و نگه‌داشتن حداکثر ۵ توکن آخر (چند دستگاه، ولی بدون رشد بی‌نهایت)
await UserModel.updateOne({ _id: userId }, { $pull: { [field]: fcmToken } })
await UserModel.updateOne(
{ _id: userId },
{ $push: { [field]: { $each: [fcmToken], $slice: -5 } } }
)
res.json({ ok: true })
} catch (error) {
next(error)
}
}
const markBroadcastSeen = async (req, res, next) => {
try {
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const { id } = req.params
await NotificationModel.updateOne(
{ user_id: userId, broadcast_id: id },
{ push_delivered_seen: true }
)
res.json({ ok: true })
} catch (error) {
next(error)
}
}
const markBroadcastClicked = async (req, res, next) => {
try {
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const userId = decodedToken.id
const { id } = req.params
await NotificationModel.updateOne(
{ user_id: userId, broadcast_id: id },
{ push_clicked: true }
)
res.json({ ok: true })
} catch (error) {
next(error)
}
}
module.exports = {
getNotifications, unreadNotifications, registerPushToken, markBroadcastSeen, markBroadcastClicked
}