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,11 +1,23 @@
const MessageModel = require('../../../models/MessageModel')
const UserModel = require('../../../models/UserModel')
const StoryModel = require('../../../models/StoryModel')
const jwt = require('jsonwebtoken')
const moment = require('moment-jalaali')
const {
viewerBlockedUser,
userBlockedViewer,
} = require('../../../utils/blockVisibility')
const { formatChatUserLabel } = require('../../../utils/accountLifecycle')
const { formatLastOnlineStatus } = require('../../../utils/onlineStatus')
const { getShopChatsForUser } = require('../shop-chat/shopChatController')
const { getOrSetCache } = require('../../../utils/cache')
const { unreadMessagesCacheKey } = require('../../../utils/unreadMessagesCache')
// TTLِ کوتاه (نه یه شمارنده‌ی incremental) عمداً — چون خوانده‌شدن/رسیدنِ پیام از
// چندین مسیرِ مختلف اتفاق می‌افته (index.js، سوکت)، نگه‌داشتنِ یه شمارنده‌ی دقیق
// نیاز به ردیابیِ همه‌ی اون نقاط داره و راحت drift پیدا می‌کنه؛ یه کشِ کوتاه‌مدت با
// invalidate صریح توی نقاطِ اصلی (پایین) هم سریعه هم امن
const UNREAD_COUNT_CACHE_TTL = 20
const getMessages = async (req, res, next) => {
try {
const token = req.header('Authorization').split(' ')[1]
@@ -57,25 +69,34 @@ const getMessages = async (req, res, next) => {
]
}
// استوری فعال طرف‌های گفتگو — برای نمایش حلقه‌ی استوری دور آواتار توی لیست چت
const activeStories = await StoryModel.find({
user_id: { $in: userIdsArray },
expires_at: { $gt: new Date() }
})
.select('user_id viewers')
.lean()
const storiesByAuthor = {}
activeStories.forEach((s) => {
const key = String(s.user_id)
if (!storiesByAuthor[key]) storiesByAuthor[key] = []
storiesByAuthor[key].push(s)
})
const hasStorySet = new Set(Object.keys(storiesByAuthor))
const unviewedStorySet = new Set(
Object.keys(storiesByAuthor).filter((authorId) =>
storiesByAuthor[authorId].some(
(s) => !s.viewers?.some((v) => String(v.user_id) === String(userId))
)
)
)
// پیدا کردن اطلاعات کاربران مرتبط بر اساس فیلتر جستجو و شمارش تعداد پیام‌های خوانده نشده برای هر کاربر
const usersData = await UserModel.find(userFilter)
const usersWithUnreadCount = await Promise.all(usersData.map(async (user) => {
const unreadMessagesCount = await MessageModel.countDocuments({ receiverId: userId, senderId: user._id, readStatus: 0 })
let lastOnlineStatus
const now = moment()
const lastOnline = moment(user.last_online)
const diffMinutes = now.diff(lastOnline, 'minutes')
if (diffMinutes < 1) {
lastOnlineStatus = 'آنلاین'
} else if (diffMinutes < 60) {
lastOnlineStatus = `${diffMinutes} دقیقه پیش`
} else if (diffMinutes < 1440) {
lastOnlineStatus = `${Math.floor(diffMinutes / 60)} ساعت پیش`
} else {
lastOnlineStatus = `${Math.floor(diffMinutes / 1440)} روز پیش`
}
let lastOnlineStatus = formatLastOnlineStatus(user.last_online, user.ghost_mode)
// بررسی آیا کاربر فعلی در لیست بلاک‌شده‌های این کاربر است یا خیر
const blockedByCurrentUser = viewerBlockedUser(user, userId)
@@ -85,9 +106,14 @@ const getMessages = async (req, res, next) => {
first_name: user.first_name,
last_name: user.last_name,
user_name: user.user_name,
display_name: formatChatUserLabel(user),
account_deactivated: user.account_status === 'deactivated',
is_verified: user.is_verified,
verify_badge: user.verify_badge,
profile_image: user.profile_image,
last_online: lastOnlineStatus,
has_story: hasStorySet.has(String(user._id)),
has_unviewed_story: unviewedStorySet.has(String(user._id)),
_id: user._id,
unread_messages_count: unreadMessagesCount,
is_blocked: blockedByCurrentUser,
@@ -96,10 +122,21 @@ const getMessages = async (req, res, next) => {
}
}))
// حذف کاربر فعلی از آرایه و سورت کردن کاربران براساس آخرین پیام
const filteredUsersData = usersWithUnreadCount
// حذف کاربر فعلی از آرایه
const directChats = usersWithUnreadCount
.filter(user => user._id.toString() !== userId)
.sort((a, b) => new Date(b.last_message_date) - new Date(a.last_message_date))
.map(user => ({ ...user, chat_type: 'user' }))
// ادغام گفتگوهای فروشگاهی (ساختار کاملاً جدا، رمزنگاری‌نشده) در همان لیست پیام‌ها
let shopChats = await getShopChatsForUser(userId)
if (search) {
const regex = new RegExp(search, 'i')
shopChats = shopChats.filter(chat => regex.test(chat.display_name || ''))
}
const filteredUsersData = [...directChats, ...shopChats].sort(
(a, b) => new Date(b.last_message_date) - new Date(a.last_message_date)
)
res.status(200).json({
filteredUsersData,
@@ -119,7 +156,11 @@ const getUnreadMessages = async (req, res, next) => {
const userId = decodedToken.id
// پیدا کردن تمام پیام‌های کاربر که هنوز خوانده نشده‌اند
const unreadMessagesCount = await MessageModel.countDocuments({ receiverId: userId, readStatus: 0 })
const unreadMessagesCount = await getOrSetCache(
unreadMessagesCacheKey(userId),
UNREAD_COUNT_CACHE_TTL,
() => MessageModel.countDocuments({ receiverId: userId, readStatus: 0 })
)
res.status(200).json({ unread_messages_count: unreadMessagesCount })
} catch (error) {