Add shop-chat push notifications and a "send shop to users" endpoint

- shop-chat messages (buyer <-> shop owner) never sent any push notification
  at all — sendShopChatPushNotification (chatHelpers.js) now fires on every
  send, mirroring the existing regular-chat push path; data payload carries
  shop_id/buyer_id/sender_role so clients can route directly to the thread
- New POST /shops/:id/send, mirroring the existing shop-product send
  endpoint — lets a user share a shop itself (not just one product) into a
  regular chat message as a sharedShop payload

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
payacom
2026-09-16 14:05:19 +03:30
parent 2e14db1ccd
commit 1954b2661d
4 changed files with 133 additions and 5 deletions

View File

@@ -7,6 +7,7 @@ const ShopModel = require('../../../models/ShopModel')
const ShopChatMessageModel = require('../../../models/ShopChatMessageModel')
const UserModel = require('../../../models/UserModel')
const { optimizeImageFile } = require('../../../utils/imageOptimize')
const { sendShopChatPushNotification } = require('../../../helpers/chatHelpers')
let optimizeVideoFaststart = async () => {}
try {
({ optimizeVideoFaststart } = require('../../../utils/postVideoProcessing'))
@@ -200,16 +201,27 @@ const sendShopChatMessage = async (req, res, next) => {
const { content } = req.body
if (!content || !content.trim()) return chatError(res, 422, 'متن پیام الزامی است')
const senderRole = isOwner ? 'shop' : 'buyer'
const message = await ShopChatMessageModel.create({
shop: shop._id,
buyer: buyerId,
senderRole: isOwner ? 'shop' : 'buyer',
senderRole,
senderId: req.user._id,
content: content.trim(),
readByShop: isOwner,
readByBuyer: !isOwner
})
sendShopChatPushNotification({
shop,
senderId: req.user._id,
senderRole,
buyerId,
receiverId: isOwner ? buyerId : shop.owner
}).catch((err) => {
console.error('[shopChatController] push notification failed:', err.message)
})
return res.status(201).json({ message })
} catch (error) {
next(error)
@@ -251,10 +263,11 @@ const sendShopChatFileMessage = async (req, res, next) => {
}
const fileUrl = `/shop-chat/${uniqueFileName}`
const senderRole = isOwner ? 'shop' : 'buyer'
const message = await ShopChatMessageModel.create({
shop: shop._id,
buyer: buyerId,
senderRole: isOwner ? 'shop' : 'buyer',
senderRole,
senderId: req.user._id,
content: content && content.trim() ? content.trim() : null,
file: fileUrl,
@@ -263,6 +276,16 @@ const sendShopChatFileMessage = async (req, res, next) => {
readByBuyer: !isOwner
})
sendShopChatPushNotification({
shop,
senderId: req.user._id,
senderRole,
buyerId,
receiverId: isOwner ? buyerId : shop.owner
}).catch((err) => {
console.error('[shopChatController] push notification failed:', err.message)
})
return res.status(201).json({ message })
} catch (error) {
next(error)

View File

@@ -1,4 +1,5 @@
/* eslint-disable camelcase */
const mongoose = require('mongoose')
const fs = require('fs-extra')
const path = require('path')
const jwt = require('jsonwebtoken')
@@ -7,8 +8,12 @@ const ShopProductListingModel = require('../../../models/ShopProductListingModel
const ShopRatingModel = require('../../../models/ShopRatingModel')
const ShopFollowModel = require('../../../models/ShopFollowModel')
const OrderModel = require('../../../models/OrderModel')
const MessageModel = require('../../../models/MessageModel')
const UserModel = require('../../../models/UserModel')
const { maskPhoneNumbers } = require('../../../utils/phoneNumberMasking')
const { optimizeImageFile } = require('../../../utils/imageOptimize')
const { viewerIsBlockedBy } = require('../../../utils/blockVisibility')
const { enrichMessage, emitChatEvents } = require('../../../helpers/chatHelpers')
const MS_DAY = 24 * 60 * 60 * 1000
const ANALYTICS_RANGE_DAYS = { '7d': 7, '30d': 30, '90d': 90, all: null }
@@ -400,6 +405,76 @@ const submitShop = async (req, res, next) => {
}
}
/** عینِ sendProductToUsers توی shopProductController.js — پیام‌های چتِ معمولی
* (MessageModel) با یه پیامِ sharedShop برای هرکدوم از گیرنده‌ها؛ برای ارسالِ خودِ
* فروشگاه (نه یه کالای خاص) از دکمه‌ی «ارسال» کنارِ اشتراک‌گذاری */
const sendShopToUsers = async (req, res, next) => {
try {
const senderId = req.user._id
const { receiverIds } = req.body
if (!Array.isArray(receiverIds) || !receiverIds.length) {
return shopError(res, 400, 'حداقل یک گیرنده انتخاب کنید')
}
const uniqueReceivers = [
...new Set(
receiverIds
.map(String)
.filter((id) => mongoose.Types.ObjectId.isValid(id) && id !== String(senderId))
)
].slice(0, 50)
if (!uniqueReceivers.length) {
return shopError(res, 400, 'گیرنده معتبر یافت نشد')
}
const shop = await ShopModel.findById(req.params.id)
if (!shop) return shopError(res, 404, 'فروشگاه یافت نشد')
const sharedShop = {
shopId: String(shop._id),
name: shop.name,
logo: shop.logo || '',
url: `https://modstagram.com/shops/profile/${shop._id}`
}
const content = JSON.stringify({ sharedShop })
const io = req.app.get('io')
let sentCount = 0
for (const receiverId of uniqueReceivers) {
const receiver = await UserModel.findById(receiverId).select('blocked_users').lean()
if (!receiver) continue
if (viewerIsBlockedBy(receiver, senderId)) continue
const newMessage = new MessageModel({
senderId,
receiverId,
content
})
await newMessage.save()
if (io) {
const formatted = await enrichMessage(newMessage)
emitChatEvents(io, formatted, senderId, receiverId)
}
sentCount += 1
}
if (!sentCount) {
return shopError(res, 400, 'ارسال به هیچ گیرنده‌ای انجام نشد')
}
return res.status(200).json({
message: `فروشگاه به ${sentCount} نفر ارسال شد`,
sentCount
})
} catch (error) {
next(error)
}
}
module.exports = {
createDraftShop,
getMyShops,
@@ -412,5 +487,6 @@ module.exports = {
updateShopLocation,
updateShopContact,
toggleShopFollow,
submitShop
submitShop,
sendShopToUsers
}