Initial commit
This commit is contained in:
157
index.js
157
index.js
@@ -98,14 +98,22 @@ const io = require('socket.io')(http, {
|
||||
app.use(express.json({ limit: '1000mb' }));
|
||||
app.use(express.urlencoded({ limit: '1000mb', extended: true }));
|
||||
|
||||
app.use('/storage', express.static(path.join(__dirname, 'storage')));
|
||||
// مسیرهای مشخص قبل از /storage عمومی — پروفایل از هر دو محل (قدیم و جدید) سرو میشود
|
||||
app.use('/storage/profiles', express.static(path.join(__dirname, '../storage/profiles')));
|
||||
app.use('/storage/profiles', express.static(path.join(__dirname, 'storage/profiles')));
|
||||
app.use('/storage/carts', express.static(path.join(__dirname, '../storage/carts')));
|
||||
app.use('/storage/carts', express.static(path.join(__dirname, 'storage/carts')));
|
||||
app.use('/storage/posts', express.static(path.join(__dirname, 'storage/posts')));
|
||||
app.use('/storage/posts', express.static(path.join(__dirname, '../storage/posts')));
|
||||
app.use('/storage/messages', express.static(path.join(__dirname, '../storage/messages')));
|
||||
app.use('/storage/messages', express.static(path.join(__dirname, 'storage/messages')));
|
||||
app.use('/storage/tickets', express.static(path.join(__dirname, '../storage/tickets')));
|
||||
app.use('/storage/tickets', express.static(path.join(__dirname, 'storage/tickets')));
|
||||
app.use('/storage/advertising', express.static(path.join(__dirname, '../storage/advertising')));
|
||||
app.use('/storage/advertising', express.static(path.join(__dirname, 'storage/advertising')));
|
||||
app.use('/storage/services', express.static(path.join(__dirname, '../storage/services')));
|
||||
app.use('/storage/services', express.static(path.join(__dirname, 'storage/services')));
|
||||
app.use('/storage', express.static(path.join(__dirname, 'storage')));
|
||||
|
||||
require('./boot');
|
||||
|
||||
@@ -146,19 +154,50 @@ app.set('trust proxy', 'loopback');
|
||||
require('./routes')(app);
|
||||
|
||||
const { enrichMessage, emitChatEvents, parseForwardedContent } = require('./helpers/chatHelpers')
|
||||
const {
|
||||
parseSelfDestructSeconds,
|
||||
buildExpiresAt,
|
||||
activeMessageFilter,
|
||||
purgeExpiredMessages,
|
||||
scheduleMessageExpiry,
|
||||
deleteMessagesAndNotify
|
||||
} = require('./helpers/expiredMessages')
|
||||
const {
|
||||
parseViewOnce,
|
||||
isViewOnceMediaType,
|
||||
sanitizeMessageForViewer,
|
||||
openViewOnceMessage,
|
||||
completeViewOnceMessage
|
||||
} = require('./helpers/viewOnceMessages')
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
app.get('/api/v1/chat', async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId, limit, page } = req.query
|
||||
|
||||
if (!senderId || !receiverId) {
|
||||
return res.status(400).json({ error: 'senderId and receiverId are required' })
|
||||
}
|
||||
|
||||
if (
|
||||
!mongoose.Types.ObjectId.isValid(String(senderId)) ||
|
||||
!mongoose.Types.ObjectId.isValid(String(receiverId))
|
||||
) {
|
||||
return res.status(400).json({ error: 'Invalid senderId or receiverId' })
|
||||
}
|
||||
|
||||
const sid = new mongoose.Types.ObjectId(String(senderId))
|
||||
const rid = new mongoose.Types.ObjectId(String(receiverId))
|
||||
const pageNumber = parseInt(page) || 1
|
||||
const limitPerPage = parseInt(limit) || 40
|
||||
|
||||
const filter = {
|
||||
const filter = activeMessageFilter({
|
||||
$or: [
|
||||
{ senderId, receiverId },
|
||||
{ senderId: receiverId, receiverId: senderId }
|
||||
{ senderId: sid, receiverId: rid },
|
||||
{ senderId: rid, receiverId: sid }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const totalMessagesCount = await MessageModel.countDocuments(filter)
|
||||
const totalPages = Math.ceil(totalMessagesCount / limitPerPage) || 1
|
||||
@@ -170,11 +209,15 @@ app.get('/api/v1/chat', async (req, res) => {
|
||||
.limit(limitPerPage)
|
||||
|
||||
await MessageModel.updateMany(
|
||||
{ _id: { $in: messages.map((m) => m._id) }, receiverId: senderId },
|
||||
{ _id: { $in: messages.map((m) => m._id) }, receiverId: sid },
|
||||
{ $set: { readStatus: 1 } }
|
||||
)
|
||||
|
||||
const enriched = await Promise.all(messages.map((m) => enrichMessage(m)))
|
||||
const enriched = await Promise.all(
|
||||
messages.map((m) =>
|
||||
enrichMessage(sanitizeMessageForViewer(m, sid))
|
||||
)
|
||||
)
|
||||
res.json({ messages: enriched.reverse(), totalPages })
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error)
|
||||
@@ -184,15 +227,17 @@ app.get('/api/v1/chat', async (req, res) => {
|
||||
|
||||
app.post('/api/v1/chat', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId, content, replyToId, forwardedFrom: fwdBody } = req.body
|
||||
const { senderId, receiverId, content, replyToId, forwardedFrom: fwdBody, selfDestructSeconds } = req.body
|
||||
const { body, forwardedFrom: fwdParsed } = parseForwardedContent(content)
|
||||
const destructSec = parseSelfDestructSeconds(selfDestructSeconds)
|
||||
|
||||
const payload = {
|
||||
senderId,
|
||||
receiverId,
|
||||
content: body || content,
|
||||
replyToId: replyToId || undefined,
|
||||
forwardedFrom: fwdBody || fwdParsed || undefined
|
||||
forwardedFrom: fwdBody || fwdParsed || undefined,
|
||||
expiresAt: buildExpiresAt(destructSec)
|
||||
}
|
||||
|
||||
const newMessage = new MessageModel(payload)
|
||||
@@ -201,6 +246,7 @@ app.post('/api/v1/chat', [blockCheck], async (req, res) => {
|
||||
|
||||
res.status(201).json({ data: formatted })
|
||||
emitChatEvents(io, formatted, senderId, receiverId)
|
||||
scheduleMessageExpiry(io, newMessage)
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
@@ -263,7 +309,14 @@ app.post("/api/v1/notification/send-sms", async (req, res) => {
|
||||
|
||||
app.post('/api/v1/chat/file', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId, content, replyToId, fileType } = req.body
|
||||
const { senderId, receiverId, content, replyToId, fileType, selfDestructSeconds, viewOnce } = req.body
|
||||
const destructSec = parseSelfDestructSeconds(selfDestructSeconds)
|
||||
const viewOnceFlag = parseViewOnce(viewOnce)
|
||||
const resolvedFileType = fileType || undefined
|
||||
|
||||
if (viewOnceFlag && !isViewOnceMediaType(resolvedFileType)) {
|
||||
return res.status(422).json({ error: 'View once is only for image, video, and voice' })
|
||||
}
|
||||
const { file } = req.files
|
||||
let fileUrl = null
|
||||
if (file) {
|
||||
@@ -282,19 +335,94 @@ app.post('/api/v1/chat/file', [blockCheck], async (req, res) => {
|
||||
receiverId,
|
||||
content: content || '',
|
||||
file: fileUrl,
|
||||
fileType: fileType || undefined,
|
||||
replyToId: replyToId || undefined
|
||||
fileType: resolvedFileType,
|
||||
replyToId: replyToId || undefined,
|
||||
expiresAt: buildExpiresAt(destructSec),
|
||||
viewOnce: viewOnceFlag
|
||||
})
|
||||
await newMessage.save()
|
||||
const formatted = await enrichMessage(newMessage)
|
||||
|
||||
res.status(201).json({ data: formatted })
|
||||
emitChatEvents(io, formatted, senderId, receiverId)
|
||||
scheduleMessageExpiry(io, newMessage)
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/v1/chat/view-once/open', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const userId = String(req.user._id)
|
||||
const { messageId } = req.body
|
||||
if (!messageId) {
|
||||
return res.status(422).json({ error: 'messageId is required' })
|
||||
}
|
||||
|
||||
const result = await openViewOnceMessage(messageId, userId)
|
||||
if (result.error) {
|
||||
return res.status(result.status || 400).json({ error: result.error })
|
||||
}
|
||||
|
||||
res.json({
|
||||
file: result.file,
|
||||
fileType: result.fileType,
|
||||
messageId: String(result.message._id)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('view-once open error:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/v1/chat/view-once/complete', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const userId = String(req.user._id)
|
||||
const { messageId } = req.body
|
||||
if (!messageId) {
|
||||
return res.status(422).json({ error: 'messageId is required' })
|
||||
}
|
||||
|
||||
const result = await completeViewOnceMessage(io, messageId, userId)
|
||||
if (result.error) {
|
||||
return res.status(result.status || 400).json({ error: result.error })
|
||||
}
|
||||
|
||||
res.json({ deletedIds: result.deletedIds })
|
||||
} catch (error) {
|
||||
console.error('view-once complete error:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
app.delete('/api/v1/chat', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const userId = String(req.user._id)
|
||||
const { messageIds } = req.body
|
||||
|
||||
if (!Array.isArray(messageIds) || messageIds.length === 0) {
|
||||
return res.status(422).json({ message: 'پیامی انتخاب نشده است' })
|
||||
}
|
||||
|
||||
const messages = await MessageModel.find({
|
||||
_id: { $in: messageIds },
|
||||
senderId: userId
|
||||
})
|
||||
|
||||
if (!messages.length) {
|
||||
return res.status(403).json({ message: 'فقط پیامهای خودتان قابل حذف هستند' })
|
||||
}
|
||||
|
||||
const deletedIds = await deleteMessagesAndNotify(io, messages, { deletedBy: userId })
|
||||
|
||||
res.json({ deletedIds, message: 'پیامها حذف شدند' })
|
||||
} catch (error) {
|
||||
console.error('Error deleting messages:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
// Errors
|
||||
require('./middlewares/exception')(app)
|
||||
require('./middlewares/404')(app)
|
||||
@@ -336,6 +464,11 @@ io.on('connection', (socket) => {
|
||||
// Free Project
|
||||
require('./services/AdvertisingCron')
|
||||
require('./services/ProjectCron')
|
||||
|
||||
cron.schedule('*/30 * * * * *', () => {
|
||||
purgeExpiredMessages(io).catch((err) => console.error('expired messages cron:', err))
|
||||
})
|
||||
|
||||
cron.schedule('0 0 1 * *', async () => {
|
||||
try {
|
||||
await UserModel.updateMany({}, {
|
||||
|
||||
Reference in New Issue
Block a user