This commit is contained in:
payacom
2026-09-24 21:00:07 +03:30
parent 1954b2661d
commit 7f0ddfde70
113 changed files with 570 additions and 5049 deletions

View File

@@ -1,74 +0,0 @@
const AcademyCategoryModel = require('../../../models/AcademyCategoryModel');
// GET all categories
exports.getAll = async (req, res) => {
try {
const categories = await AcademyCategoryModel.find({}).sort({ createdAt: -1 });
res.status(200).json({ success: true, categories });
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
};
// CREATE category
exports.create = async (req, res) => {
try {
const { title, status } = req.body;
if (!title) {
return res.status(400).json({ success: false, message: "title is required" });
}
const newCategory = new AcademyCategoryModel({
title,
status: status ?? true,
});
await newCategory.save();
res.status(201).json({ success: true, category: newCategory });
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
};
// UPDATE category
exports.update = async (req, res) => {
try {
const { id } = req.params;
const { title, status } = req.body;
const updated = await AcademyCategoryModel.findByIdAndUpdate(
id,
{ title, status },
{ new: true, runValidators: true }
);
if (!updated) {
return res.status(404).json({ success: false, message: "category not found" });
}
res.status(200).json({ success: true, category: updated });
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
};
// DELETE
exports.remove = async (req, res) => {
try {
const { id } = req.params;
const deleted = await AcademyCategoryModel.findByIdAndDelete(id);
if (!deleted) {
return res.status(404).json({ success: false, message: "category not found" });
}
res.status(200).json({
success: true,
message: 'category deleted successfully'
});
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
};

View File

@@ -1,80 +0,0 @@
const moment = require('moment-jalaali')
const MessageModel = require('../models/MessageModel')
const UserModel = require('../models/UserModel')
function formatTimeOnly(date) {
return moment(date).locale('fa').format('HH:mm:ss')
}
async function enrichMessage(message) {
const doc = message._doc ? { ...message._doc } : { ...message }
let replyTo = null
if (doc.replyToId) {
const parent = await MessageModel.findById(doc.replyToId).lean()
if (parent) {
const parentUser =
String(parent.senderId) === String(doc.senderId)
? await UserModel.findById(doc.senderId).select('first_name last_name user_name').lean()
: await UserModel.findById(parent.senderId).select('first_name last_name user_name').lean()
replyTo = {
_id: parent._id,
content: (parent.content || 'پیام').slice(0, 200),
senderName: parentUser
? `${parentUser.first_name || ''} ${parentUser.last_name || ''}`.trim() || parentUser.user_name
: 'کاربر'
}
}
}
return {
...doc,
createdAt: formatTimeOnly(doc.createdAt),
createdAtIso: doc.createdAt ? new Date(doc.createdAt).toISOString() : undefined,
expiresAt: doc.expiresAt ? new Date(doc.expiresAt).toISOString() : undefined,
viewOnce: Boolean(doc.viewOnce),
viewOnceLocked: Boolean(doc.viewOnceLocked),
viewOnceExpired: Boolean(doc.viewOnceExpired),
viewOnceOpenedAt: doc.viewOnceOpenedAt
? new Date(doc.viewOnceOpenedAt).toISOString()
: undefined,
replyTo,
forwardedFrom: doc.forwardedFrom || undefined
}
}
function emitChatEvents(io, message, senderId, receiverId) {
const payload = message
const roomA = `chat:${senderId}:${receiverId}`
const roomB = `chat:${receiverId}:${senderId}`
io.to(roomA).to(roomB).emit('newMessage', payload)
io.to(`user:${senderId}`).to(`user:${receiverId}`).emit('chatListUpdate', {
senderId,
receiverId,
message: payload
})
}
function parseForwardedContent(content) {
if (!content) return { body: '', forwardedFrom: null }
try {
const line = content.split('\n')[0]
const j = JSON.parse(line)
if (j.forwardedFrom) {
return {
forwardedFrom: j.forwardedFrom,
body: content.replace(/^\{.*\}\n?/, '').trim()
}
}
} catch (_) {
/* not json */
}
return { body: content, forwardedFrom: null }
}
module.exports = {
formatTimeOnly,
enrichMessage,
emitChatEvents,
parseForwardedContent
}

View File

@@ -1,129 +0,0 @@
const path = require('path')
const fs = require('fs-extra')
const MessageModel = require('../models/MessageModel')
const ALLOWED_SELF_DESTRUCT_SECONDS = [10, 30, 60, 300, 3600]
function parseSelfDestructSeconds(raw) {
if (raw == null || raw === '' || raw === '0' || raw === 0) return null
const n = parseInt(String(raw), 10)
if (!Number.isFinite(n) || !ALLOWED_SELF_DESTRUCT_SECONDS.includes(n)) return null
return n
}
function buildExpiresAt(seconds) {
if (!seconds) return undefined
return new Date(Date.now() + seconds * 1000)
}
function activeMessageFilter(baseFilter) {
return {
$and: [
baseFilter,
{
$or: [
{ expiresAt: { $exists: false } },
{ expiresAt: null },
{ expiresAt: { $gt: new Date() } }
]
}
]
}
}
async function removeMessageFiles(messages) {
for (const msg of messages) {
if (!msg.file) continue
const relative = String(msg.file).replace(/^\//, '')
const filePath = path.join(__dirname, '../storage', relative)
if (fs.existsSync(filePath)) {
await fs.remove(filePath).catch(() => {})
}
}
}
async function deleteMessagesAndNotify(io, messages, options = {}) {
if (!messages?.length) return []
await removeMessageFiles(messages)
const deletedIds = messages.map((m) => String(m._id))
await MessageModel.deleteMany({ _id: { $in: deletedIds } })
if (io) {
const byThread = new Map()
messages.forEach((m) => {
const s = String(m.senderId)
const r = String(m.receiverId)
const key = [s, r].sort().join(':')
if (!byThread.has(key)) byThread.set(key, { s, r, ids: [] })
byThread.get(key).ids.push(String(m._id))
})
byThread.forEach(({ s, r, ids }) => {
io.to(`chat:${s}:${r}`)
.to(`chat:${r}:${s}`)
.emit('messagesDeleted', {
messageIds: ids,
deletedBy: options.deletedBy,
reason: options.reason || (options.deletedBy ? 'manual' : 'expired')
})
io.to(`user:${s}`).to(`user:${r}`).emit('chatListUpdate', { senderId: s, receiverId: r })
})
}
return deletedIds
}
async function purgeExpiredMessages(io) {
try {
const expired = await MessageModel.find({ expiresAt: { $lte: new Date() } })
if (!expired.length) return []
return deleteMessagesAndNotify(io, expired)
} catch (err) {
console.error('purgeExpiredMessages error:', err)
return []
}
}
const scheduledTimers = new Map()
function scheduleMessageExpiry(io, message) {
if (!message?.expiresAt || !message._id) return
const id = String(message._id)
if (scheduledTimers.has(id)) {
clearTimeout(scheduledTimers.get(id))
}
const ms = new Date(message.expiresAt).getTime() - Date.now()
if (ms <= 0) {
MessageModel.findById(id)
.then((doc) => doc && deleteMessagesAndNotify(io, [doc]))
.catch(() => {})
return
}
const timer = setTimeout(async () => {
scheduledTimers.delete(id)
try {
const doc = await MessageModel.findById(id)
if (doc && doc.expiresAt && new Date(doc.expiresAt) <= new Date()) {
await deleteMessagesAndNotify(io, [doc])
}
} catch (err) {
console.error('scheduleMessageExpiry error:', err)
}
}, ms)
scheduledTimers.set(id, timer)
}
module.exports = {
ALLOWED_SELF_DESTRUCT_SECONDS,
parseSelfDestructSeconds,
buildExpiresAt,
activeMessageFilter,
purgeExpiredMessages,
scheduleMessageExpiry,
deleteMessagesAndNotify
}

View File

@@ -1,104 +0,0 @@
const MessageModel = require('../models/MessageModel')
const { deleteMessagesAndNotify } = require('./expiredMessages')
function parseViewOnce(raw) {
if (raw == null || raw === '' || raw === false || raw === 'false' || raw === 0) {
return false
}
return raw === true || raw === 'true' || raw === 1 || raw === '1'
}
const VIEW_ONCE_MEDIA = new Set(['image', 'video', 'voice'])
function isViewOnceMediaType(fileType) {
return VIEW_ONCE_MEDIA.has(fileType)
}
/** Hide file URL for receiver until they open view-once media */
function sanitizeMessageForViewer(message, viewerId) {
const doc = message._doc ? { ...message._doc } : { ...message }
const viewer = String(viewerId)
const sender = String(doc.senderId)
const receiver = String(doc.receiverId)
if (!doc.viewOnce || !isViewOnceMediaType(doc.fileType)) {
return doc
}
if (viewer === sender) {
return doc
}
if (viewer === receiver) {
if (doc.viewOnceOpenedAt) {
return {
...doc,
file: '',
viewOnceExpired: true
}
}
return {
...doc,
file: '',
viewOnceLocked: true
}
}
return doc
}
async function openViewOnceMessage(messageId, userId) {
const msg = await MessageModel.findById(messageId)
if (!msg || !msg.viewOnce) {
return { error: 'پیام یافت نشد', status: 404 }
}
if (String(msg.receiverId) !== String(userId)) {
return { error: 'دسترسی مجاز نیست', status: 403 }
}
if (msg.viewOnceOpenedAt) {
return { error: 'این پیام قبلاً باز شده است', status: 410 }
}
msg.viewOnceOpenedAt = new Date()
await msg.save()
return {
message: msg.toObject(),
file: msg.file,
fileType: msg.fileType
}
}
async function completeViewOnceMessage(io, messageId, userId) {
const msg = await MessageModel.findById(messageId)
if (!msg || !msg.viewOnce) {
return { error: 'پیام یافت نشد', status: 404 }
}
const uid = String(userId)
const isReceiver = String(msg.receiverId) === uid
const isSender = String(msg.senderId) === uid
if (!isReceiver && !isSender) {
return { error: 'دسترسی مجاز نیست', status: 403 }
}
if (isReceiver && !msg.viewOnceOpenedAt) {
return { error: 'پیام هنوز باز نشده', status: 400 }
}
await deleteMessagesAndNotify(io, [msg], {
deletedBy: userId,
reason: 'viewOnce'
})
return { deletedIds: [String(msg._id)] }
}
module.exports = {
parseViewOnce,
isViewOnceMediaType,
sanitizeMessageForViewer,
openViewOnceMessage,
completeViewOnceMessage
}

View File

@@ -1,357 +0,0 @@
const express = require('express');
const app = express();
const path = require('path');
const http = require('http').createServer(app);
const cors = require('cors');
const moment = require('moment-jalaali');
const MessageModel = require('./models/MessageModel');
const fs = require('fs-extra');
const cron = require('node-cron');
const UserModel = require('./models/UserModel');
const blockCheck = require('./middlewares/blockCheck');
const rateLimit = require('express-rate-limit');
const { default: axios } = require('axios');
const allowedOrigins = [
'http://localhost:3000',
'http://localhost:3001', // اضافه شد — مهم برای توسعه فرانت
'http://localhost:3002',
'http://localhost:3003',
'http://localhost',
'https://modstagram.com',
'https://www.modstagram.com',
'https://api.modstagram.com',
'https://panel.modstagram.com',
'https://www.panel.modstagram.com',
'https://panel.modstagram.ir',
'https://www.panel.modstagram.ir',
'ionic://localhost',
'capacitor://localhost',
// IPهای محلی (برای تست در شبکه داخلی)
/^http:\/\/192\.168\.\d{1,3}\.\d{1,3}:\d+$/,
/^http:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/,
];
app.use(cors({
origin: (origin, callback) => {
// در محیط توسعه، همه originها مجاز باشن
if (process.env.NODE_ENV !== 'production') {
return callback(null, true);
}
// اگر origin وجود نداشته باشه (مثل درخواست از موبایل یا Postman) اجازه بده
if (!origin) {
return callback(null, true);
}
// چک لیست یا RegExp
const isAllowed = allowedOrigins.some(item => {
if (typeof item === 'string') return item === origin;
if (item instanceof RegExp) return item.test(origin);
return false;
});
if (isAllowed) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-no-toast'],
credentials: true,
optionsSuccessStatus: 200 // برای مرورگرهای قدیمی
}));
// Socket.IO CORS
const io = require('socket.io')(http, {
cors: {
origin: (origin, callback) => {
if (process.env.NODE_ENV !== 'production') {
return callback(null, true);
}
if (!origin) {
return callback(null, true);
}
const isAllowed = allowedOrigins.some(item => {
if (typeof item === 'string') return item === origin;
if (item instanceof RegExp) return item.test(origin);
return false;
});
if (isAllowed) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
credentials: true
}
});
// افزایش limit برای Base64 (1000mb کافیه)
app.use(express.json({ limit: '1000mb' }));
app.use(express.urlencoded({ limit: '1000mb', extended: true }));
app.use('/storage', express.static(path.join(__dirname, 'storage')));
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/posts', express.static(path.join(__dirname, '../storage/posts')));
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/advertising', express.static(path.join(__dirname, '../storage/advertising')));
app.use('/storage/services', express.static(path.join(__dirname, '../storage/services')));
require('./boot');
// غیرفعال کردن پارس‌کننده‌های بدنه برای روت‌های آپلود فایل
app.use((req, res, next) => {
if (req.path === '/api/v1/posts/create' && req.method === 'POST') {
console.log('DEBUG: Skipping body parsers for /api/v1/posts/create');
return next();
}
express.json()(req, res, next);
});
app.use((req, res, next) => {
if (req.path === '/api/v1/posts/create' && req.method === 'POST') {
return next();
}
express.urlencoded({ extended: true })(req, res, next);
});
require('./middlewares')(app);
const limiter = rateLimit({
windowMs: 3 * 60 * 1000,
max: 3000,
standardHeaders: true,
legacyHeaders: false,
message: 'تعداد درخواست‌های شما بیشتر از حد مجاز است، لطفاً بعداً دوباره تلاش کنید.',
handler: (req, res) => {
const retryAfter = Math.ceil((req.rateLimit.resetTime - Date.now()) / 1000);
res.set('Retry-After', retryAfter);
res.status(429).json({
error: `شما بیش از حد مجاز درخواست ارسال کرده‌اید. لطفاً بعد از ${retryAfter} ثانیه دوباره تلاش کنید.`
});
}
});
app.use(limiter);
app.set('trust proxy', 'loopback');
require('./routes')(app);
const { enrichMessage, emitChatEvents, parseForwardedContent } = require('./helpers/chatHelpers')
app.get('/api/v1/chat', async (req, res) => {
try {
const { senderId, receiverId, limit, page } = req.query
const pageNumber = parseInt(page) || 1
const limitPerPage = parseInt(limit) || 40
const filter = {
$or: [
{ senderId, receiverId },
{ senderId: receiverId, receiverId: senderId }
]
}
const totalMessagesCount = await MessageModel.countDocuments(filter)
const totalPages = Math.ceil(totalMessagesCount / limitPerPage) || 1
const skip = (pageNumber - 1) * limitPerPage
const messages = await MessageModel.find(filter)
.sort({ createdAt: -1 })
.skip(skip)
.limit(limitPerPage)
await MessageModel.updateMany(
{ _id: { $in: messages.map((m) => m._id) }, receiverId: senderId },
{ $set: { readStatus: 1 } }
)
const enriched = await Promise.all(messages.map((m) => enrichMessage(m)))
res.json({ messages: enriched.reverse(), totalPages })
} catch (error) {
console.error('Error fetching messages:', error)
res.status(500).json({ error: 'Server error' })
}
})
app.post('/api/v1/chat', [blockCheck], async (req, res) => {
try {
const { senderId, receiverId, content, replyToId, forwardedFrom: fwdBody } = req.body
const { body, forwardedFrom: fwdParsed } = parseForwardedContent(content)
const payload = {
senderId,
receiverId,
content: body || content,
replyToId: replyToId || undefined,
forwardedFrom: fwdBody || fwdParsed || undefined
}
const newMessage = new MessageModel(payload)
await newMessage.save()
const formatted = await enrichMessage(newMessage)
res.status(201).json({ data: formatted })
emitChatEvents(io, formatted, senderId, receiverId)
} catch (error) {
console.error('Error sending message:', error)
res.status(500).json({ error: 'Server error' })
}
})
app.get('/api/v1/chat/read', async (req, res) => {
try {
const { senderId, receiverId } = req.query // شناسه فرستنده و گیرنده از درخواست دریافت شود
// پیدا کردن تمام پیام‌هایی که کاربر دوم مشاهده کرده است و کاربر اول آنها را ارسال کرده است
const messages = await MessageModel.find(
{ senderId: receiverId, receiverId: senderId } // برای مواقعی که شناسه‌ها معکوس باشند
)
// به روزرسانی وضعیت خوانده شده یا نشده بودن پیام‌ها به "خوانده شده"
await MessageModel.updateMany({ _id: { $in: messages.map(message => message._id) } }, { $set: { readStatus: 1 } })
res.status(200).json({ message: 'Message read status updated successfully' })
} catch (error) {
console.error('Error updating message read status:', error)
res.status(500).json({ error: 'Server error' })
}
})
app.post("/api/v1/notification/send-sms", async (req, res) => {
const { receiverId, message } = req.body;
const user = await UserModel.findById(receiverId);
if (!user || !user.mobile) return res.status(404).json({ error: "User not found" });
console.log("3");
const mobile = user.mobile
const user_name = user.user_name
const data = JSON.stringify({
mobile,
templateId: '876533',
parameters: [
{ name: 'USER', value: user_name }
]
})
const config = {
method: 'post',
url: 'https://api.sms.ir/v1/send/verify',
headers: {
'Content-Type': 'application/json',
Accept: 'text/plain',
'x-api-key': process.env.SMS_IR_API_KEY
},
data
}
axios(config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error)
})
res.status(200).json({ success: true });
});
app.post('/api/v1/chat/file', [blockCheck], async (req, res) => {
try {
const { senderId, receiverId, content, replyToId, fileType } = req.body
const { file } = req.files
let fileUrl = null
if (file) {
const uploadDir = path.join(__dirname, '../storage/messages')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true })
}
const uniqueFileName = `${Date.now()}-${file.name}`
const filePath = path.join(uploadDir, uniqueFileName)
await fs.move(file.path, filePath)
fileUrl = `/messages/${uniqueFileName}`
}
const newMessage = new MessageModel({
senderId,
receiverId,
content: content || '',
file: fileUrl,
fileType: fileType || undefined,
replyToId: replyToId || undefined
})
await newMessage.save()
const formatted = await enrichMessage(newMessage)
res.status(201).json({ data: formatted })
emitChatEvents(io, formatted, senderId, receiverId)
} catch (error) {
console.error('Error sending message:', error)
res.status(500).json({ error: 'Server error' })
}
})
// Errors
require('./middlewares/exception')(app)
require('./middlewares/404')(app)
io.on('connection', (socket) => {
socket.on('joinUser', ({ userId }) => {
if (userId) socket.join(`user:${userId}`)
})
socket.on('joinChat', ({ userId, receiverId }) => {
if (userId && receiverId) {
socket.join(`chat:${userId}:${receiverId}`)
socket.join(`chat:${receiverId}:${userId}`)
}
})
socket.on('typing', ({ senderId, receiverId }) => {
io.to(`chat:${receiverId}:${senderId}`).emit('userTyping', { userId: senderId })
})
socket.on('stopTyping', ({ senderId, receiverId }) => {
io.to(`chat:${receiverId}:${senderId}`).emit('userStoppedTyping', { userId: senderId })
})
socket.on('messageSeen', async ({ messageId, senderId, receiverId }) => {
try {
if (messageId) {
await MessageModel.findByIdAndUpdate(messageId, { readStatus: 1 })
io.to(`chat:${receiverId}:${senderId}`).emit('messageStatusUpdate', {
messageId,
status: 'seen'
})
}
} catch (e) {
console.error('messageSeen error', e)
}
})
})
// Free Project
require('./services/AdvertisingCron')
require('./services/ProjectCron')
cron.schedule('0 0 1 * *', async () => {
try {
await UserModel.updateMany({}, {
daily_free_request: 1,
last_free_request_date: new Date(),
monthly_free_offer: 1,
last_free_offer_date: new Date()
})
console.log('Monthly free requests reset for all users.')
} catch (error) {
console.error('Error resetting daily free requests:', error)
}
}, {
scheduled: true,
timezone: 'Asia/Tehran'
})
module.exports = (port) => {
http.listen(port, () => {
console.log(`HTTP server is running on port ${port}`)
})
}

View File

@@ -1,18 +0,0 @@
[Wed Mar 13 00:31:27 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NjUHdHNE5ma0hGdHpQV040MkV0OUx0M285SkdBcGp6OFA5SGpoNHZHUzFEajkwc0ZWQWRWSlNBNFlOMWxxOC9k","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:31:27 2024] [VerificationCode] {"VerificationCodeId":634458365,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 00:34:14 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3M0WTRVeG9SeVNtN0xnMWUzQWo4WG1Oam9mNUw2SkJPV04rMGVOMUJ6Njhpb1RXMTl2LzFEdGRESXhZazBwaHBk","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:34:15 2024] [VerificationCode] {"VerificationCodeId":634459857,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 00:34:48 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NGN0NVV3V1K01VRTg3TGY1by9PcmtlSVpWaFpBa0l4RXNCOERHMzIxMDRkL1RYbWRhOXc5ZmovT0lFV0FDamJs","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:34:48 2024] [VerificationCode] {"VerificationCodeId":634460165,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 00:34:57 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NyNmxiRjUwVlF6UUlkSStnOWpOc0lLSyt5VHJjSjFRa0xsTkk0dFB2L3VSaE1PTDdQREY4WGlzcUFQeE01QWRP","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:34:57 2024] [VerificationCode] {"VerificationCodeId":634460241,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 00:39:39 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3N6MHErTmJEVzMydjZRcVJQaHNLb0R3MzJKcWk5bGJYNTh5UzFmSEpSSUY0b0Q4MFppdDRoeFlxWTZZSEQrL1hL","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:39:39 2024] [VerificationCode] {"VerificationCodeId":634462768,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 00:40:18 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NLRlVYejdBcTJxMGl6dXJVSTRsQkNiOGh2M241WTZrSGI3Z0JCY3kxeFZvUzVCTnRkdmVWalFzd25KQzIzV21I","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:40:18 2024] [VerificationCode] {"VerificationCodeId":634463121,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 00:41:38 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NOMUs3ZVN3eDU0OVBZdXR1VU1Yd3dhS2lyWmMrWkpjTGpUVVkzWDQ4ZU10VDBTWnFZc0gwYlVSZHArS1FBU2ZL","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 00:41:38 2024] [VerificationCode] {"VerificationCodeId":634463845,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 01:11:21 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3M4em1oSG05eU9XbVlna0U1TDRjRWdlRi8zTE10YnAzYVZia0hRTmQ3bWV4cEUyUUl4TCtPMncvWStrNi9TSjNt","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 01:11:21 2024] [VerificationCode] {"VerificationCodeId":634470192,"IsSuccessful":true,"Message":"your verification code is sent"}
[Wed Mar 13 02:55:34 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3MwSFZidjNtY0p1T3RwQVFlbkxJdjFHZ0xUaFpqZW9CdllCR21oNFNyaXRUczFaVUVSemZ3Rzk0TW0zRG50Q3VU","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
[Wed Mar 13 02:55:34 2024] [VerificationCode] {"VerificationCodeId":634481796,"IsSuccessful":true,"Message":"your verification code is sent"}

View File

@@ -1,9 +0,0 @@
module.exports = (app) => {
app.use((req, res, next) => {
res.status(404).send({
code: 'Not Found',
status: 404,
message: 'requested resource could not be found!'
})
})
}

View File

@@ -1,55 +0,0 @@
const AdminModel = require('../models/AdminModel')
const jwt = require('jsonwebtoken')
module.exports = async (req, res, next) => {
if (!('authorization' in req.headers)) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'you are not authorized!'
})
}
const token = req.header('Authorization').split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
let decodedToken
try {
decodedToken = jwt.verify(token, process.env.APP_SECRET)
} catch (err) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'Your token is not valid!',
error: err.message
})
}
if (!decodedToken) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'Your token is not valid!'
})
}
try {
// پیدا کردن کاربر با استفاده از اطلاعات موجود در توکن
const admin = await AdminModel.findOne({ _id: decodedToken.id })
if (!admin) {
return res.status(403).send({
status: 'error',
code: 403,
message: 'Access denied!'
})
}
// ادامه مسیر در صورتی که کاربر ادمین باشد
req.admin = admin
next()
} catch (error) {
return res.status(500).send({
status: 'error',
code: 500,
message: 'Internal Server Error',
error: error.message
})
}
}

View File

@@ -1,21 +0,0 @@
const TokenService = require('../services/TokenService')
module.exports = (req, res, next) => {
console.log(1);
if (!('authorization' in req.headers)) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'you are not authorized!'
})
}
const [, tokenValue] = req.headers.authorization.split(' ')
const token = TokenService.verify(tokenValue)
if (!token) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'your token is not valid!'
})
}
next()
}

View File

@@ -1,48 +0,0 @@
const UserModel = require('../models/UserModel')
const jwt = require('jsonwebtoken')
const {
expireBlockSuspensionIfNeeded,
getBlockedAccountResponse
} = require('../utils/blockSuspension')
module.exports = async (req, res, next) => {
try {
if (!('authorization' in req.headers)) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'You are not authorized!'
})
}
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)
if (!decodedToken) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'Your token is not valid!'
})
}
let user = await UserModel.findById(decodedToken.id)
user = await expireBlockSuspensionIfNeeded(user)
const blockResponse = getBlockedAccountResponse(user)
if (blockResponse) {
return res.status(403).send(blockResponse)
}
req.user = user
next()
} catch (error) {
console.error('Error in blockCheck middleware:', error)
res.status(500).send({
status: 'error',
code: 500,
message: 'Server error'
})
}
}

View File

@@ -1,11 +0,0 @@
module.exports = (app) => {
app.use((error, req, res, next) => {
const status = error.status || 500
res.status(500).send({
code: 'Eception',
status,
en_message: error.message,
message: 'خطایی در عملیات مورد نظر رخ داده است.'
})
})
}

View File

@@ -1,21 +0,0 @@
const bodyParser = require('body-parser')
const formData = require('express-form-data')
const session = require('express-session')
const lastOnline = require('./lastOnline')
module.exports = (app) => {
app.use(formData.parse())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(session({
secret: process.env.SESSION_SECRET || process.env.JWT_SECRET || 'change-me-in-production',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
},
}))
app.use(lastOnline)
}

View File

@@ -1,53 +0,0 @@
const UserModel = require('../models/UserModel') // مسیر درست به مدل کاربر
const jwt = require('jsonwebtoken')
module.exports = async (req, res, next) => {
console.log(3);
try {
console.log(31);
if (!('authorization' in req.headers)) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'You are not authorized!'
})
}
console.log(32);
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)
if (!decodedToken) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'Your token is not valid!'
})
}
console.log(33);
const user = await UserModel.findById(decodedToken.id)
if (!user.user_name ||
!user.first_name ||
!user.last_name ||
!user.user_type ||
!user.password) {
return res.status(403).send({
status: 'error',
code: 403,
message: 'لطفا از بخش تنظیمات، ثبت نام خود را کامل کنید',
type: 'not_register'
})
}
console.log(34);
req.user = user // کاربر را به درخواست اضافه کنید تا در سایر middleware ها و کنترلرها استفاده شود.
next()
} catch (error) {
console.log(35);
console.error('not_register middleware:', error)
res.status(500).send({
status: 'error',
code: 500,
message: 'Server error'
})
}
}

View File

@@ -1,50 +0,0 @@
const UserModel = require('../models/UserModel') // مسیر درست به مدل کاربر
const jwt = require('jsonwebtoken')
module.exports = async (req, res, next) => {
try {
if (!('authorization' in req.headers)) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'You are not authorized!'
})
}
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)
if (!decodedToken) {
return res.status(401).send({
status: 'error',
code: 401,
message: 'Your token is not valid!'
})
}
const user = await UserModel.findById(decodedToken.id)
if (!user.user_name ||
!user.first_name ||
!user.last_name ||
!user.password) {
return res.status(403).send({
status: 'error',
code: 403,
message: 'لطفا از بخش تنظیمات، ثبت نام خود را کامل کنید',
type: 'not_register'
})
}
req.user = user // کاربر را به درخواست اضافه کنید تا در سایر middleware ها و کنترلرها استفاده شود.
next()
} catch (error) {
console.error('not_register middleware:', error)
res.status(500).send({
status: 'error',
code: 500,
message: 'Server error'
})
}
}

View File

@@ -1,21 +0,0 @@
const jwt = require('jsonwebtoken')
const UserModel = require('../models/UserModel')
module.exports = async (req, res, next) => {
if (req.header && req.header('Authorization')) {
const token = await req.header('Authorization').split(' ')[1]
const decodedToken = await jwt.verify(token, process.env.APP_SECRET)
const user = await UserModel.findById(decodedToken.id)
if (user) {
try {
user.last_online = new Date()
await user.save() // ذخیره تغییرات در دیتابیس
} catch (error) {
// بررسی و پردازش خطاها
console.error('Error updating last online time:', error)
}
}
}
next()
}

View File

@@ -1,24 +0,0 @@
const multer = require('multer');
const path = require('path');
const fs = require('fs-extra');
const uploadDir = path.join(__dirname, '../storage/posts');
fs.ensureDirSync(uploadDir); // مطمئن میشه مسیر وجود داره
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, uploadDir),
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const name = file.originalname.replace(ext, '').replace(/\s/g, '_');
cb(null, `${Date.now()}-${name}${ext}`);
}
});
const fileFilter = (req, file, cb) => {
if (!file.mimetype.startsWith('image/')) return cb(new Error('فقط تصاویر مجاز است'));
cb(null, true);
};
const upload = multer({ storage, fileFilter, limits: { files: 10 } }); // حداکثر ۱۰ فایل
module.exports = upload;

View File

@@ -1,190 +0,0 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
const timestamp = require('mongoose-timestamp');
const AcademyCategorySchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'عنوان دسته‌بندی الزامی است'],
trim: true,
minLength: [2, 'عنوان باید حداقل ۲ کاراکتر باشد'],
maxLength: [100, 'عنوان باید حداکثر ۱۰۰ کاراکتر باشد'],
unique: true,
index: true,
},
slug: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
index: true,
},
description: {
type: String,
required: false,
trim: true,
maxLength: 500,
default: null,
},
icon: {
type: String,
required: false,
default: null,
},
color: {
type: String,
required: false,
default: '#3B82F6',
match: /^#[0-9A-F]{6}$/i,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'AcademyCategory',
required: false,
default: null,
index: true,
},
level: {
type: Number,
required: true,
default: 0,
min: 0,
max: 3,
},
order: {
type: Number,
required: true,
default: 0,
index: true,
},
image: {
type: String,
required: false,
default: null,
},
course_count: {
type: Number,
required: true,
default: 0,
min: 0,
},
status: {
type: String,
enum: ['active', 'inactive', 'deleted'],
required: true,
default: 'active',
index: true,
},
is_featured: {
type: Boolean,
required: true,
default: false,
index: true,
},
seo_title: {
type: String,
required: false,
trim: true,
maxLength: 70,
},
seo_description: {
type: String,
required: false,
trim: true,
maxLength: 160,
},
created_by: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
updated_by: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: false,
},
}, {
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Virtual برای زیردسته‌ها
AcademyCategorySchema.virtual('children', {
ref: 'AcademyCategory',
localField: '_id',
foreignField: 'parent',
justOne: false,
options: { sort: { order: 1, title: 1 } }
});
// Virtual برای دوره‌های این دسته
AcademyCategorySchema.virtual('courses', {
ref: 'Course',
localField: '_id',
foreignField: 'category_id',
justOne: false,
match: { status: 'accept' }
});
// Middleware: قبل از save
AcademyCategorySchema.pre('save', async function(next) {
if (this.isModified('title') || this.isNew) {
// تولید slug
this.slug = this.title
.replace(/[^\u0600-\u06FF\uFB8A\u067E\u0686\u06AF\u200C\uFB8E\u0698a-zA-Z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-')
.toLowerCase();
// اگه parent داره، سطح رو محاسبه کن
if (this.parent) {
const parent = await this.constructor.findById(this.parent);
if (parent) {
this.level = parent.level + 1;
}
} else {
this.level = 0;
}
}
next();
});
// استاتیک متدها
AcademyCategorySchema.statics = {
// دریافت درخت دسته‌بندی‌ها
async getTree() {
const categories = await this.find({ status: 'active', parent: null })
.sort({ order: 1, title: 1 })
.populate({
path: 'children',
match: { status: 'active' },
options: { sort: { order: 1, title: 1 } },
populate: {
path: 'children',
match: { status: 'active' },
options: { sort: { order: 1, title: 1 } }
}
});
return categories;
},
// افزایش تعداد دوره‌ها
async incrementCourseCount(categoryId, increment = 1) {
return this.findByIdAndUpdate(categoryId, {
$inc: { course_count: increment }
});
},
// دریافت دسته‌بندی با اسلاگ
async getBySlug(slug) {
return this.findOne({ slug, status: 'active' })
.populate('children')
.populate('parent');
}
};
AcademyCategorySchema.plugin(timestamp);
AcademyCategorySchema.plugin(mongoosePaginate);
module.exports = mongoose.model('AcademyCategory', AcademyCategorySchema);

View File

@@ -1,71 +0,0 @@
const mongoose = require('mongoose');
const timestamp = require('mongoose-timestamp');
const mongoosePaginate = require('mongoose-paginate-v2');
const modelSchema = new mongoose.Schema({
course_images: {
type: [String], // آرایه‌ای از مسیرهای تصاویر
required: false,
default: [],
},
course_video: {
type: String, // مسیر ویدئو
required: false,
trim: true,
default: null,
},
type: {
type: String, // نوع پست: 'image' یا 'video'
required: true,
enum: ['image', 'video'],
},
files: [
{
path: { type: String, required: false },
type: { type: String, enum: ['image', 'video'], required: false },
}
],
caption: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1000,
default: null,
},
status: {
type: String,
required: false,
trim: true,
enum: ['pending', 'accept', 'reject'],
default: 'accept',
},
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
likes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}],
comments: {
type: Object,
default: null,
},
courseId: {
type: String,
},
is_free: {
type: Boolean,
},
file_name: {
type: String,
},
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model('AcademyContent', modelSchema);
module.exports = PostModel;

View File

@@ -1,25 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
user_id: {
type: String,
required: true,
trim: true,
default: 0,
},
course_id: {
type: String,
required: true,
trim: true,
default: 0,
},
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("Likes", modelSchema);
module.exports = PostModel;

View File

@@ -1,55 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
academy_image: {
type: String,
required: false,
trim: true,
default: null,
},
academy_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null,
},
userId: {
type: String,
required: true,
},
rate: {
type: Number,
trim: true,
default: 0
},
number_of_rate: {
type: Number,
trim: true,
default: 0
},
sheba: {
type: String,
required: false,
default: null
},
bio: {
type: String,
required: false,
default: null
},
tag: {
type: Object,
required: false,
default: null
},
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("Academy", modelSchema);
module.exports = PostModel;

View File

@@ -1,24 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
price: { type: Number, required: true }, // قیمت نهایی شامل مالیات
discountAmount: { type: Number, default: 0 }, // مبلغ تخفیف
taxAmount: { type: Number, default: 0 }, // مبلغ مالیات
taxRate: { type: Number, default: 9 }, // درصد مالیات
course_name: { type: String, required: true },
course_id: { type: mongoose.Schema.Types.ObjectId, ref: "Course", required: true },
academy_id: { type: mongoose.Schema.Types.ObjectId, ref: "Academy", required: true },
user_id: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
payment_authority: { type: String },
payment_ref_id: { type: String },
status: { type: String, enum: ["pending", "success", "failed", "settled"], default: "success" },
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("AcademyPayment", modelSchema);
module.exports = PostModel;

View File

@@ -1,80 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const adminSchema = new mongoose.Schema({
mobile: {
type: String,
required: false,
trim: true,
unique: true
},
otp: {
type: String,
required: false,
trim: true,
minLength: 6,
maxLength: 6
},
user_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
password: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
first_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
last_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
user_type: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
profile_image: {
type: String,
required: false,
trim: true
},
gender: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
last_online: {
type: Date, // ذخیره تاریخ و زمان آخرین بازدید
default: Date.now // تنظیم تاریخ به تاریخ فعلی
}
})
adminSchema.plugin(timestamp)
adminSchema.plugin(mongoosePaginate)
const AdminModel = mongoose.model('Admin', adminSchema)
module.exports = AdminModel

View File

@@ -1,23 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const advertisingCategorySchema = new mongoose.Schema({
title: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
status: {
type: Boolean,
required: false,
default: true
}
})
advertisingCategorySchema.plugin(timestamp)
advertisingCategorySchema.plugin(mongoosePaginate)
const AdvertisingCategoryModel = mongoose.model('AdvertisingCategory', advertisingCategorySchema)
module.exports = AdvertisingCategoryModel

View File

@@ -1,32 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const advertisingCommentSchema = new mongoose.Schema({
advertisingId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Advertising',
required: true
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
text: {
type: String,
required: true,
trim: true
},
status: {
type: String,
enum: ['pending', 'accepted', 'rejected'],
default: 'pending'
}
})
advertisingCommentSchema.plugin(timestamp)
advertisingCommentSchema.plugin(mongoosePaginate)
const AdvertisingComment = mongoose.model('AdvertisingComment', advertisingCommentSchema)
module.exports = AdvertisingComment

View File

@@ -1,27 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const advertisingFeaturesSchema = new mongoose.Schema({
title: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
status: {
type: Boolean,
required: false,
default: true
},
type: {
type: String,
default: 'boolean'
}
})
advertisingFeaturesSchema.plugin(timestamp)
advertisingFeaturesSchema.plugin(mongoosePaginate)
const AdvertisingFeaturesModel = mongoose.model('AdvertisingFeatures', advertisingFeaturesSchema)
module.exports = AdvertisingFeaturesModel

View File

@@ -1,20 +0,0 @@
const mongoose = require('mongoose')
const advertisingLikeSchema = new mongoose.Schema({
advertisingId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Advertising',
required: true
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
})
// تعریف مدل AdvertisingLikeModel
const AdvertisingLikeModel = mongoose.model('AdvertisingLike', advertisingLikeSchema)
module.exports = AdvertisingLikeModel

View File

@@ -1,230 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const advertisingSchema = new mongoose.Schema({
title: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
category: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
description: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1024,
default: null
},
province: {
type: Object,
default: null
},
city: {
type: Object,
default: null
},
neighbourhood: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
address: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
lat: {
type: String,
trim: true,
minLength: 1,
maxLength: 256,
default: null
},
lng: {
type: String,
trim: true,
minLength: 1,
maxLength: 256,
default: null
},
images: [{
type: String,
required: false,
trim: true,
default: null
}],
services: [{
title: {
type: String,
required: true,
trim: true
},
originalPrice: {
type: String,
required: true
},
discountPrice: {
type: String,
required: false
},
discountPercentage: {
type: Number,
required: false
},
status: {
type: String,
default: "UnderReview",
required: false
},
image: {
type: String,
required: false,
trim: true
}
}],
features: [{
title: {
type: String,
required: false,
trim: true
},
value: {
type: Boolean,
required: true
}
}],
contactInfo: {
phone: {
type: String,
required: false,
trim: true,
default: null
},
mobile: {
type: String,
required: false,
trim: true,
default: null
},
telegramLink: {
type: String,
required: false,
trim: true,
default: null
},
whatsappNumber: {
type: String,
required: false,
trim: true,
default: null
},
instagramLink: {
type: String,
required: false,
trim: true,
default: null
},
saveInfoForNextAds: {
type: Boolean,
required: true,
default: false
}
},
type: {
type: String,
enum: ['free', 'normal', 'special', 'highlight'],
default: 'normal'
},
status: {
type: String,
enum: ['pre_payment', 'paid', 'accepted', 'rejected', 'expired'],
default: 'pre_payment'
},
payment_status: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
creator_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
reject_reason: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1024,
default: null
},
mostDiscountPercentage: {
type: Number,
required: false
},
showDiscount: {
type: Boolean,
required: false,
default: false
},
likesCount: {
type: Number,
default: 0
},
commentsCount: {
type: Number,
default: 0
},
ratings: [{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
rating: {
type: Number,
required: true,
min: 1,
max: 5
}
}],
averageRating: {
type: Number,
default: 0
},
ratingsCount: {
type: Number,
default: 0
},
viewCount: {
type: Number,
default: 0
},
acceptedAt: {
type: Date,
default: null
}
})
advertisingSchema.plugin(timestamp)
advertisingSchema.plugin(mongoosePaginate)
const AdvertisingModel = mongoose.model('Advertising', advertisingSchema)
module.exports = AdvertisingModel

View File

@@ -1,113 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const advertisingProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
profile_image: {
type: String,
required: false,
trim: true,
default: null
},
image_count: {
type: Number,
default: 0
},
like_count: {
type: Number,
default: 0
},
comment_count: {
type: Number,
default: 0
},
vitrine_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
category: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
about_us: {
type: String,
required: false,
minLength: 1,
maxLength: 1024,
default: null
},
province: {
type: Object,
default: null
},
city: {
type: Object,
default: null
},
lat: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
lng: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
address: {
type: String,
required: false,
trim: true,
default: null
},
neighbourhood: {
type: String,
required: false,
trim: true,
default: null
},
adTotalRatings: {
type: Number,
default: 0
},
adRatingsCount: {
type: Number,
default: 0
},
adAverageRating: {
type: Number,
default: 0
},
contactInfo: {
phone: String,
mobile: String,
telegramLink: String,
whatsappNumber: String,
instagramLink: String,
saveInfoForNextAds: { type: Boolean, default: false }
}
})
advertisingProfileSchema.plugin(timestamp)
advertisingProfileSchema.plugin(mongoosePaginate)
const AdvertisingProfileModel = mongoose.model('AdvertisingProfile', advertisingProfileSchema)
module.exports = AdvertisingProfileModel

View File

@@ -1,26 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const advertisingRatingSchema = new mongoose.Schema({
advertisingId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Advertising',
required: true
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
rating: {
type: Number,
required: true,
min: 1,
max: 5
}
})
advertisingRatingSchema.plugin(timestamp)
const AdvertisingRatingModel = mongoose.model('AdvertisingRating', advertisingRatingSchema)
module.exports = AdvertisingRatingModel

View File

@@ -1,25 +0,0 @@
const mongoose = require('mongoose')
const advertisingTypeSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
minLength: 1,
maxLength: 255
},
price: {
type: Number,
required: true,
trim: true,
minLength: 1,
maxLength: 255
},
status: {
type: String,
default: 'active'
}
})
const AdvertisingTypeModel = mongoose.model('AdvertisingType', advertisingTypeSchema)
module.exports = AdvertisingTypeModel

View File

@@ -1,55 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const commentSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
project: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Project',
required: false
},
offer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Offer',
required: false
},
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
rating: {
type: Number,
required: false,
default: null
},
comment: {
type: String,
trim: true
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
required: false
},
comment_for: {
type: String,
enum: ['user', 'project', 'post'],
required: true
},
status: {
type: String,
enum: ['pending', 'accepted', 'rejected'],
default: 'pending'
}
})
commentSchema.plugin(timestamp)
commentSchema.plugin(mongoosePaginate)
const CommentModel = mongoose.model('Comment', commentSchema)
module.exports = CommentModel

View File

@@ -1,40 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
user_id: {
type: String,
required: true,
trim: true,
},
course_id: {
type: String,
required: true,
trim: true,
},
comment: {
type: String,
required: true,
trim: true,
default: 0,
},
rate: {
type: String,
required: true,
trim: true,
default: 0,
},
status: {
type: String,
enum: ['pending', 'accepted', 'rejected'],
default: 'accepted'
}
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("CourseComment", modelSchema);
module.exports = PostModel;

View File

@@ -1,89 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
price: {
type: String,
required: true,
},
cuorse_name: {
type: String,
required: true,
},
category:{
type: String
},
offer: {
type: String
},
academyId:{
type: String
},
caption: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1000,
default: null,
},
status: {
type: String,
required: false,
trim: true,
enum: ['pending', 'accept', 'reject'],
default: 'accept',
},
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
likes: {
type: Number,
required: false,
trim: true,
default: 0,
},
comments: {
type: Object,
default: null,
},
type: {
type: String,
required: false,
trim: true,
enum: ['normal', 'pro', 'legend'],
default: "normal",
},
course_time: {
type: String
},
number_of_course_content: {
type: String
},
teacher_number: {
type: String
},
course_image: {
type: String
},
teacher_name: {
type: String
},
is_free: {
type: Boolean,
default: false,
},
contactInfo: {
mobile: { type: String, default: null },
telegramLink: { type: String, default: null },
whatsappNumber: { type: String, default: null },
instagramLink: { type: String, default: null },
},
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("Course", modelSchema);
module.exports = PostModel;

View File

@@ -1,22 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
price: {
type: String,
required: true,
},
cuorse_type: {
type: String,
required: false,
trim: true,
enum: ['normal', 'pro', 'legend'],
},
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("CoursePayment", modelSchema);
module.exports = PostModel;

View File

@@ -1,47 +0,0 @@
// const mongoose = require('mongoose')
// const timestamp = require('mongoose-timestamp')
// const subExpertiseSchema = new mongoose.Schema({
// id: {
// type: mongoose.Schema.Types.ObjectId,
// ref: 'ExpertiseModel'
// },
// name: {
// type: String
// }
// })
// const expertiseSchema = new mongoose.Schema({
// expertise: {
// type: String,
// unique: true
// },
// sub_expertise: [subExpertiseSchema]
// })
// expertiseSchema.plugin(timestamp)
// const ExpertiseModel = mongoose.model('Expertise', expertiseSchema)
// module.exports = ExpertiseModel
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const subExpertiseSchema = new mongoose.Schema({
name: {
type: String
}
})
const expertiseSchema = new mongoose.Schema({
expertise: {
type: String,
unique: true
},
sub_expertise: [subExpertiseSchema]
})
expertiseSchema.plugin(timestamp)
const ExpertiseModel = mongoose.model('Expertise', expertiseSchema)
module.exports = ExpertiseModel

View File

@@ -1,54 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const exploreInteractionSchema = new mongoose.Schema({
viewerId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
targetType: {
type: String,
enum: ['post', 'profile', 'academy'],
required: true
},
targetId: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
authorId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
default: null
},
contentType: {
type: String,
enum: ['image', 'video', 'academy'],
default: 'image'
},
action: {
type: String,
enum: [
'view',
'profile_visit',
'share',
'like',
'comment',
'offer',
'watch_complete',
'skip',
'dwell'
],
default: 'view'
},
dwellMs: {
type: Number,
default: null
}
})
exploreInteractionSchema.index({ viewerId: 1, createdAt: -1 })
exploreInteractionSchema.plugin(timestamp)
module.exports = mongoose.model('ExploreInteraction', exploreInteractionSchema)

View File

@@ -1,15 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
user_id: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
course_id: { type: mongoose.Schema.Types.ObjectId, ref: "Course", required: true },
price_paid: { type: Number, required: true }, // قیمت پرداخت شده
purchased_at: { type: Date, default: Date.now },
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("IsPaymentCourse", modelSchema);
module.exports = PostModel;

View File

@@ -1,25 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const likeSchema = new mongoose.Schema({
postId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
required: true
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
likes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
// تعریف مدل LikeModel
likeSchema.plugin(timestamp)
const LikeModel = mongoose.model('Like', likeSchema)
module.exports = LikeModel

View File

@@ -1,66 +0,0 @@
const mongoose = require('mongoose')
const { Schema } = mongoose
const messageSchema = new Schema({
senderId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
receiverId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
content: {
type: String,
required: false,
trim: true
},
file: {
type: String,
required: false
},
fileType: {
type: String,
enum: ['image', 'video', 'file', 'voice', 'location'],
required: false
},
replyToId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Message',
required: false
},
forwardedFrom: {
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
userName: String,
displayName: String
},
createdAt: {
type: Date,
default: Date.now
},
readStatus: {
type: Number,
default: 0
},
/** Auto-delete after this time (self-destruct messages) */
expiresAt: {
type: Date,
required: false,
index: true
},
/** View-once photo / video / voice */
viewOnce: {
type: Boolean,
default: false
},
viewOnceOpenedAt: {
type: Date,
required: false
}
})
const MessageModel = mongoose.model('Message', messageSchema)
module.exports = MessageModel

View File

@@ -1,37 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const notificationSchema = new mongoose.Schema({
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User', // مرجع به مدل کاربر
required: true
},
project_post_id: {
type: mongoose.Schema.Types.ObjectId,
required: false
},
type: {
type: String,
required: true
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
read: {
type: Boolean,
default: false // وضعیت خوانده شده یا نشده
}
})
notificationSchema.plugin(timestamp)
notificationSchema.plugin(mongoosePaginate)
const NotificationModel = mongoose.model('Notification', notificationSchema)
module.exports = NotificationModel

View File

@@ -1,32 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const offerSchema = new mongoose.Schema({
sender: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
receiver: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
status: {
type: String,
enum: ['pending', 'accepted', 'rejected'], // وضعیت درخواست
default: 'pending'
},
transaction_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Payment',
default: null
}
})
offerSchema.plugin(timestamp)
offerSchema.plugin(mongoosePaginate)
const OfferModel = mongoose.model('Offer', offerSchema)
module.exports = OfferModel

View File

@@ -1,21 +0,0 @@
const mongoose = require('mongoose')
const offerTypeModelSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
minLength: 1,
maxLength: 255
},
price: {
type: Number,
required: true,
trim: true,
minLength: 1,
maxLength: 255
}
})
const OfferTypeModel = mongoose.model('OfferTypeModel', offerTypeModelSchema)
module.exports = OfferTypeModel

View File

@@ -1,46 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const paymentSchema = new mongoose.Schema({
amount: {
type: Number,
required: true
},
status: {
type: String,
enum: ['pending', 'successful', 'failed'],
default: 'pending'
},
authority: {
type: String,
required: false
},
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
project_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Project'
},
advertising_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Advertising'
},
type: {
type: String,
required: true
},
installment_step: {
type: String,
required: false,
default: null
}
})
paymentSchema.plugin(timestamp)
paymentSchema.plugin(mongoosePaginate)
const PaymentModel = mongoose.model('Payment', paymentSchema)
module.exports = PaymentModel

View File

@@ -1,60 +0,0 @@
const mongoose = require('mongoose');
const timestamp = require('mongoose-timestamp');
const mongoosePaginate = require('mongoose-paginate-v2');
const modelSchema = new mongoose.Schema({
post_images: {
type: [String], // آرایه‌ای از مسیرهای تصاویر
required: false,
default: [],
},
post_video: {
type: String, // مسیر ویدئو
required: false,
trim: true,
default: null,
},
type: {
type: String, // نوع پست: 'image' یا 'video'
required: true,
enum: ['image', 'video'],
},
files: [
{
path: { type: String, required: true },
type: { type: String, enum: ['image', 'video'], required: true },
}
],
caption: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1000,
default: null,
},
status: {
type: String,
required: false,
trim: true,
enum: ['pending', 'accept', 'reject'],
default: 'pending',
},
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
likes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}],
comments: {
type: Object,
default: null,
},
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model('Post', modelSchema);
module.exports = PostModel;

View File

@@ -1,186 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const projectSchema = new mongoose.Schema({
title: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
expertise: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
sub_expertise: {
type: [String], // تغییر نوع به آرایه از استرینگ
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
gender: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
age: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
conversation_projects: {
type: Boolean,
required: false,
default: null
},
province: {
type: Object,
default: null
},
city: {
type: Object,
default: null
},
offer_time: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
offer_price: {
type: Number,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
description: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1024,
default: null
},
project_type: {
type: String,
enum: ['free', 'normal', 'force', 'highlight'],
default: 'normal'
},
payment_status: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
// فیلد برای وضعیت پروژه
status: {
type: String,
enum: ['pre_payment', 'paid', 'accepted', 'rejected', 'done', 'cancled', 'ongoing'],
default: 'pre_payment'
},
public_status: {
type: String,
enum: ['public', 'private'],
default: 'public'
},
created_for_user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
default: null
},
creator_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
requested_users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
selected_user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
default: null
},
final_price: {
type: Number,
required: false,
default: null
},
final_time: {
type: Number,
required: false,
default: null
},
installments: [{
installment_number: {
type: Number,
required: true
},
amount: {
type: Number,
required: true
},
due_date: {
type: Date,
required: true
}
}],
reject_reason: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 1024,
default: null
},
ratings: [{
creator_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
rating: {
type: Number,
required: true
},
comment: {
type: String,
trim: true
}
}],
acceptedAt: {
type: Date,
default: null
},
isExpired: {
type: Boolean,
default: false
}
})
projectSchema.plugin(timestamp)
// اضافه کردن پیجینیشن به مدل
projectSchema.plugin(mongoosePaginate)
const ProjectModel = mongoose.model('Project', projectSchema)
module.exports = ProjectModel

View File

@@ -1,33 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const requestSchema = new mongoose.Schema({
project: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Project',
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
time: {
type: Number,
required: true
},
price: {
type: Number,
required: true
},
status: {
type: String,
enum: ['pending', 'accepted', 'rejected'],
default: 'pending'
}
})
requestSchema.plugin(timestamp)
const RequestModel = mongoose.model('Request', requestSchema)
module.exports = RequestModel

View File

@@ -1,19 +0,0 @@
// models/Setting.js
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const settingSchema = new mongoose.Schema({
key: {
type: String,
required: true,
unique: true
},
value: {
type: String,
required: true
}
})
settingSchema.plugin(timestamp)
const SettingModel = mongoose.model('Setting', settingSchema)
module.exports = SettingModel

View File

@@ -1,19 +0,0 @@
const mongoose = require('mongoose')
const provincesSchema = new mongoose.Schema({
id: Number,
name: String,
slug: String
})
const citiesSchema = new mongoose.Schema({
id: Number,
name: String,
slug: String,
province_id: Number
})
const ProvinceModel = mongoose.model('Province', provincesSchema)
const CityModel = mongoose.model('City', citiesSchema)
module.exports = { ProvinceModel, CityModel }

View File

@@ -1,41 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const storySchema = new mongoose.Schema({
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
media_path: {
type: String,
required: true
},
media_type: {
type: String,
enum: ['image', 'video'],
required: true
},
expires_at: {
type: Date,
required: true,
index: true
},
viewers: [{
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
viewed_at: {
type: Date,
default: Date.now
}
}]
})
storySchema.index({ expires_at: 1 }, { expireAfterSeconds: 0 })
storySchema.plugin(timestamp)
module.exports = mongoose.model('Story', storySchema)

View File

@@ -1,15 +0,0 @@
const mongoose = require("mongoose");
const timestamp = require("mongoose-timestamp");
const mongoosePaginate = require("mongoose-paginate-v2");
const modelSchema = new mongoose.Schema({
type: { type: String, enum: ["course", "product", "service"], required: true },
tax: { type: Number, required: true, default: 9 }, // درصد مالیات
is_active: { type: Boolean, default: true },
});
modelSchema.plugin(timestamp);
modelSchema.plugin(mongoosePaginate);
const PostModel = mongoose.model("Tax", modelSchema);
module.exports = PostModel;

View File

@@ -1,20 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const ticketMessageSchema = new mongoose.Schema({
text: { type: String, required: false },
file: {
type: String, // مسیر فایل در ذخیره شود
required: false // فایل اختیاری است
},
senderType: { type: String, enum: ['User', 'Admin'], required: true },
senderId: { type: mongoose.Schema.Types.ObjectId, refPath: 'senderType', required: true },
ticket: { type: mongoose.Schema.Types.ObjectId, ref: 'Ticket', required: true }
})
ticketMessageSchema.plugin(timestamp)
// اضافه کردن پیجینیشن به مدل
ticketMessageSchema.plugin(mongoosePaginate)
const TicketMessageModel = mongoose.model('TicketMessage', ticketMessageSchema)
module.exports = TicketMessageModel

View File

@@ -1,24 +0,0 @@
const mongoose = require('mongoose')
const timestamp = require('mongoose-timestamp')
const mongoosePaginate = require('mongoose-paginate-v2')
const ticketSchema = new mongoose.Schema({
title: { type: String, required: true },
status: { type: String, enum: ['Pending', 'Answered', 'Customer Response', 'Closed'], default: 'Pending' },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
new_message: {
type: Boolean,
default: false // وضعیت خوانده شده یا نشده
}
})
ticketSchema.pre('save', function (next) {
this.updatedAt = Date.now()
next()
})
ticketSchema.plugin(timestamp)
// اضافه کردن پیجینیشن به مدل
ticketSchema.plugin(mongoosePaginate)
const TicketModel = mongoose.model('Ticket', ticketSchema)
module.exports = TicketModel

View File

@@ -1,21 +0,0 @@
const mongoose = require('mongoose')
const typeAndPriceSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
minLength: 1,
maxLength: 255
},
price: {
type: Number,
required: true,
trim: true,
minLength: 1,
maxLength: 255
}
})
const TypeAndPriceModel = mongoose.model('TypeAndPrice', typeAndPriceSchema)
module.exports = TypeAndPriceModel

View File

@@ -1,391 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const userSchema = new mongoose.Schema({
mobile: {
type: String,
required: false,
trim: true,
unique: true
},
otp: {
type: String,
required: false,
trim: true,
minLength: 6,
maxLength: 6,
default: null
},
otpSentAt: {
type: Date,
required: false,
default: null
},
user_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
password: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
first_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
last_name: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
user_type: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
is_verified: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: 'none'
},
profile_image: {
type: String,
required: false,
trim: true,
default: null
},
national_card_image: {
type: String,
required: false,
trim: true,
default: null
},
expertise: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
sub_expertise: {
type: [String], // تغییر نوع به آرایه از استرینگ
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
gender: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
height: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
weight: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
size: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
eye_color: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
hair_color: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
cooperation_type: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
cooperation_abroad: {
type: Boolean,
required: false,
default: null
},
conversation_projects: {
type: Boolean,
required: false,
default: null
},
bio: {
type: String,
required: false,
minLength: 1,
maxLength: 1024,
default: null
},
national_code: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
birthday: {
type: Date,
required: false,
default: null
},
is_Register: {
type: String,
default: false
},
shaba: {
type: String,
required: false,
trim: true,
minLength: 1,
maxLength: 255,
default: null
},
address: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
province: {
type: Object,
default: null
},
city: {
type: Object,
default: null
},
lat: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
lng: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
show_location: {
type: Boolean,
required: false,
default: null
},
rate: {
type: String,
trim: true,
maxLength: 512,
default: null
},
referral_code: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
shop_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Shop',
default: null
},
last_online: {
type: Date, // ذخیره تاریخ و زمان آخرین بازدید
default: Date.now // تنظیم تاریخ به تاریخ فعلی
},
business_license: {
type: String,
required: false,
trim: true,
default: null
},
user_level: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
user_score: {
type: String,
trim: true,
minLength: 1,
maxLength: 512,
default: null
},
last_post: {
type: Object,
default: null
},
// user_ratings: [{
// project_id: {
// type: mongoose.Schema.Types.ObjectId,
// ref: 'Project',
// required: true
// },
// creator_id: {
// type: mongoose.Schema.Types.ObjectId,
// ref: 'User',
// required: true
// },
// rating: {
// type: Number,
// required: true
// },
// comment: {
// type: String,
// trim: true
// }
// }],
daily_free_request: {
type: Number,
default: 1
},
last_free_request_date: {
type: Date,
default: null
},
blocked_by: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
blocked_users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
block_status: {
type: Boolean,
required: false,
default: null
},
blocked_until: {
type: Date,
required: false,
default: null
},
advertising_profiles: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'AdvertisingProfile'
}],
services: [{
title: {
type: String,
required: true,
trim: true
},
originalPrice: {
type: String,
required: true
},
discountPrice: {
type: String,
required: false
},
discountPercentage: {
type: Number,
required: false
},
status: {
type: String,
default: "UnderReview",
required: false
},
image: {
type: String,
required: false,
trim: true
}
}],
monthly_free_offer: {
type: Number,
default: 1
},
last_free_offer_date: {
type: Date,
default: null
}
// contactInfo: {
// phone: String,
// mobile: String,
// telegramLink: String,
// whatsappNumber: String,
// instagramLink: String,
// saveInfoForNextAds: { type: Boolean, default: false }
// },
// adTotalRatings: {
// type: Number,
// default: 0
// },
// adRatingsCount: {
// type: Number,
// default: 0
// },
// adAverageRating: {
// type: Number,
// default: 0
// }
})
userSchema.plugin(timestamp)
userSchema.plugin(mongoosePaginate)
const UserModel = mongoose.model('User', userSchema)
module.exports = UserModel

View File

@@ -1,12 +0,0 @@
// models/VersionModel.js
const mongoose = require('mongoose')
const versionSchema = new mongoose.Schema({
version: { type: String, required: true },
mandatory: { type: Boolean, required: true },
releaseNotes: { type: String },
updateUrls: { type: [String], required: true } // لینک‌های آپدیت
})
const VersionModel = mongoose.model('Version', versionSchema)
module.exports = VersionModel

View File

@@ -1,14 +0,0 @@
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const timestamp = require('mongoose-timestamp')
const license = new mongoose.Schema({
userId : String,
Confirmation : { type : Boolean, default : false},
licenseImg : { type : String },
})
license.plugin(timestamp)
license.plugin(mongoosePaginate)
const LicenseMoldel = mongoose.model('license', license)
module.exports = LicenseMoldel

View File

@@ -1,423 +0,0 @@
/* eslint-disable camelcase */
const jwt = require('jsonwebtoken')
const jMoment = require('moment-jalaali')
const OfferModel = require('../../../models/OfferModel')
const UserModel = require('../../../models/UserModel')
const OfferTypeModel = require('../../../models/OfferTypeModel')
const PaymentModel = require('../../../models/PaymentModel')
const NotificationModel = require('../../../models/NotificationModel')
const { default: axios } = require('axios')
const CommentModel = require('../../../models/CommentModel')
const {
resolveRatingForComment,
recalculateUserRating
} = require('../../../utils/commentRating')
const getOfferPriceFromDatabase = async (itemName) => {
const item = await OfferTypeModel.findOne({ name: itemName })
if (!item) {
throw new Error('Item not found in database')
}
return item.price
}
const getOfferTypes = 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 user = await UserModel.findById(userId)
if (!user) {
return res.status(422).json({
error: true,
message: 'کاربر یافت نشد'
})
}
let offerTypes = await OfferTypeModel.find({}, 'name price')
// بررسی برای مواردی مانند پیدا نشدن انواع پروژه
if (!offerTypes) {
return res.status(404).json({ message: 'انواع پروژه یافت نشد.' })
}
// اگر کاربر درخواست ماهانه نداشته باشد، نوع پروژه ماهانه را حذف کنید
if (user.monthly_free_offer <= 0) {
offerTypes = offerTypes.filter(projectType => projectType.name !== 'free')
}
// ارسال انواع پروژه به کاربر
res.status(200).json({ offerTypes })
} catch (error) {
// در صورت بروز خطا، ارسال پیام خطا به کاربر
console.error('Error in getOfferTypes:', error)
res.status(500).json({ message: 'خطا در دریافت انواع پروژه.' })
}
}
const getUserOffers = 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 { page = 1, limit = 10, status_filter, id } = req.query // افزودن پارامترهای صفحه‌بندی به درخواست
let filter = {}
let filterbyid = {receiver: id}
let filterbyidsender = {sender: id}
if (status_filter === 'درخواست') {
filter = { sender: userId }
} else {
filter = { receiver: userId }
}
// تنظیم گزینه‌های صفحه‌بندی
const options = {
page: parseInt(page), // تبدیل صفحه به عدد صحیح
limit: parseInt(limit), // تبدیل محدودیت به عدد صحیح
sort: { createdAt: -1 }, // بر اساس زمان ایجاد (createdAt) مرتب کنید (نزولی)
populate: [
{
path: 'sender',
select: 'profile_image user_level first_name last_name user_name is_verified user_score rate'
},
{
path: 'receiver',
select: 'profile_image user_level first_name last_name user_name is_verified user_score rate'
}
]
}
// دریافت آفرهای صفحه‌بندی شده
let offer = await OfferModel.paginate(filter, options)
let offerbyid = await OfferModel.paginate(filterbyid, options)
let offerbyidsender = await OfferModel.paginate(filterbyidsender, options)
const totalPages = offer.totalPages
const totalItems = offer.totalDocs
// فرمت کردن تاریخ و ارسال پاسخ
offer = offer?.docs.map(offer => {
const jDate = jMoment(offer.createdAt).format('jYYYY-jMM-jDD HH:mm')
return {
...offer._doc,
createdAt: jDate
}
})
res.json({
offerbyidsender,
offerbyid,
offer,
totalPages, // ارسال تعداد کل صفحات
totalItems // ارسال تعداد کل آیتم‌ها
})
} catch (error) {
next(error)
}
}
const updateOfferStatus = 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 // ID کاربری که درخواست را به‌روزرسانی می‌کند
const reciverUser = await UserModel.findById(userId)
const { offerId, action } = req.body // آیدی آفر و اکشن (accept یا reject)
// بررسی اعتبار ورودی‌ها
if (!offerId || !['accept', 'reject'].includes(action)) {
return res.status(400).json({ error: 'Invalid input' })
}
// یافتن آفر موردنظر
const offer = await OfferModel.findById(offerId)
const senderUser = await UserModel.findById(offer?.sender)
if (!offer) {
return res.status(404).json({ error: 'Offer not found' })
}
// بررسی اینکه آیا کاربر گیرنده این آفر است یا خیر
if (offer.receiver.toString() !== userId) {
return res.status(403).json({ error: 'You are not authorized to update this offer' })
}
// بررسی اینکه آیا آفر قبلاً به‌روزرسانی شده یا نه
if (offer.status !== 'pending') {
return res.status(400).json({ error: 'Offer has already been updated', message: 'وضعیت این درخواست قبلا تغییر کرده است.' })
}
// به‌روزرسانی وضعیت آفر بر اساس اکشن
offer.status = action === 'accept' ? 'accepted' : 'rejected'
await offer.save()
if (action === 'accept') {
const data = JSON.stringify({
mobile: senderUser?.mobile,
templateId: '633448',
parameters: [
{ name: 'USER', value: reciverUser?.first_name + ' ' + reciverUser?.last_name },
{ name: 'EMPLOYER', value: senderUser?.first_name + ' ' + senderUser?.last_name }
]
})
const config = {
method: 'post',
url: 'https://api.sms.ir/v1/send/verify',
headers: {
'Content-Type': 'application/json',
Accept: 'text/plain',
'x-api-key': process.env.SMS_IR_API_KEY
},
data
}
axios(config)
.then(function (response) {
})
.catch(function (error) {
console.log(error)
})
} else {
const data = JSON.stringify({
mobile: senderUser?.mobile,
templateId: '682678',
parameters: [
{ name: 'USER', value: reciverUser?.first_name + ' ' + reciverUser?.last_name },
{ name: 'EMPLOYER', value: senderUser?.first_name + ' ' + senderUser?.last_name }
]
})
const config = {
method: 'post',
url: 'https://api.sms.ir/v1/send/verify',
headers: {
'Content-Type': 'application/json',
Accept: 'text/plain',
'x-api-key': process.env.SMS_IR_API_KEY
},
data
}
axios(config)
.then(function (response) {
})
.catch(function (error) {
console.log(error)
})
}
res.status(200).json({
message: `Offer has been ${offer.status} successfully`,
offerId: offer._id,
status: offer.status
})
} catch (error) {
next(error)
}
}
// Controller function for handling successful advertising payment
const handleSuccessfulOfferPayment = async (req, res) => {
try {
const { reciverId, offerType } = req.body
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
// Check user's free offer eligibility
const user = await UserModel.findById(userId)
if (!user) return res.status(404).json({ error: 'User not found' })
// Check user's free offer eligibility
const reciverUser = await UserModel.findById(reciverId)
if (!user) return res.status(404).json({ error: 'User not found' })
let price
if (user.monthly_free_offer > 0 && offerType === 'free') {
// Use free offer and decrement counter
price = 0
user.monthly_free_offer -= 1
await user.save()
} else {
// Retrieve offer price based on type
price = await getOfferPriceFromDatabase(offerType)
// Create a new payment entry
const payment = new PaymentModel({
amount: Number(price),
status: 'successful',
authority: '', // Add authority if available
user_id: userId,
type: 'offer',
installment_step: null
})
await payment.save()
}
// Create a new payment entry
const payment = new PaymentModel({
amount: Number(price),
status: 'successful',
authority: '', // Add authority if available
user_id: userId,
type: 'offer',
installment_step: null
})
await payment.save()
// Create a new offer
const offer = new OfferModel({
sender: userId,
receiver: reciverId,
transaction_id: payment._id // Link the payment to the offer
})
await offer.save()
console.log(reciverUser?.first_name)
// Send notification to selected user
const notification = new NotificationModel({
user_id: reciverId,
project_post_id: offer?._id,
type: 'new_offer',
title: 'درخواست همکاری جدید',
description: `${reciverUser?.first_name} عزیز یک درخواست همکاری برای شما در مجموعه مدستاگرام ثبت شد`
})
await notification.save()
// Send Sms
const data = JSON.stringify({
mobile: reciverUser?.mobile,
templateId: '569006',
parameters: [
{ name: 'FIRSTNAME', value: reciverUser?.first_name },
{ name: 'LASTNAME', value: reciverUser?.last_name }
]
})
const config = {
method: 'post',
url: 'https://api.sms.ir/v1/send/verify',
headers: {
'Content-Type': 'application/json',
Accept: 'text/plain',
'x-api-key': process.env.SMS_IR_API_KEY
},
data
}
axios(config)
.then(function (response) {
})
.catch(function (error) {
console.log(error)
})
// Send event based on payment status
// Emit a success event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentSuccess', {
message: 'Payment and offer created successfully',
offerId: offer._id
})
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({
message: 'Payment and offer recorded successfully',
offerId: offer._id,
price
})
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
// Controller function for handling failed advertising payment
const handleFailedOfferPayment = async (req, res) => {
try {
const { offerType } = req.body
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 price = await getOfferPriceFromDatabase(offerType)
// Create a new payment entry
const payment = new PaymentModel({
amount: Number(price),
status: 'failed',
authority: '', // Add authority if available
user_id: userId,
type: 'offer',
installment_step: null
})
await payment.save()
// Emit a failure event
if ('eventEmitter' in req.app) {
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment failed' })
} else {
console.error('Error: eventEmitter is not defined or not properly configured.')
}
res.status(200).json({ message: 'Payment failed', price })
} catch (error) {
console.error('Error occurred:', error)
res.status(500).json({ error: 'Internal server error' })
}
}
const createOfferComment = 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 { offerId, comment, rate, user_id } = req.body
if (!offerId || !comment || !user_id) {
return res.status(400).send({ message: 'All fields are required' })
}
const { ratingValue, isNewRating, requiresRating } = await resolveRatingForComment({
creatorId: userId,
targetUserId: user_id,
rate
})
if (requiresRating) {
return res.status(400).send({ message: 'Rating must be between 1 and 5' })
}
const newComment = new CommentModel({
user: user_id,
offer: offerId,
creator: userId,
rating: ratingValue,
comment,
comment_for: 'user',
status: 'pending'
})
await newComment.save()
if (isNewRating) {
await recalculateUserRating(user_id)
}
res.status(200).json({ message: 'نظر شما با موفقیت ثبت شد' })
} catch (error) {
next(error)
}
}
module.exports = {
getUserOffers,
getOfferTypes,
handleSuccessfulOfferPayment,
handleFailedOfferPayment,
updateOfferStatus,
createOfferComment
}

View File

@@ -1,389 +0,0 @@
const express = require("express");
const router = express.Router();
const academyController = require("../../../controllers/application/academy/academyControllers");
const multer = require("multer");
const path = require("path");
const fs = require("fs");
// const academyCategoryController = require("../../../controllers/application/academy/academyCategoryController");
// ==========================================
// ⚙️ تنظیمات اولیه و دایرکتوری‌ها
// ==========================================
// اطمینان از وجود دایرکتوری‌ها
const ensureDir = (dir) => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
console.log("DEBUG: Created directory:", dir);
}
};
const BASE_DIR = process.cwd();
const courseVideoDir = path.join(BASE_DIR, "storage", "courses", "videos");
const courseImageDir = path.join(BASE_DIR, "storage", "courses", "images");
console.log("DEBUG: BASE_DIR:", BASE_DIR);
console.log("DEBUG: courseVideoDir:", courseVideoDir);
console.log("DEBUG: courseImageDir:", courseImageDir);
ensureDir(courseVideoDir);
ensureDir(courseImageDir);
// ==========================================
// 📁 تنظیمات آپلود فایل با Multer
// ==========================================
// تنظیمات ذخیره‌سازی فایل‌ها
const storage = multer.diskStorage({
destination: (req, file, cb) => {
console.log("DEBUG: Multer destination - fieldname:", file.fieldname);
// استفاده از دایرکتوری موقت برای آپلود اولیه
const tempDir = "/tmp/modstagram-uploads";
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
cb(null, tempDir);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
const filename = uniqueSuffix + path.extname(file.originalname);
console.log("DEBUG: Generated filename:", filename);
cb(null, filename);
},
});
// تنظیمات Multer با محدودیت حجم 500 مگابایت
const upload = multer({
storage: storage,
limits: {
fileSize: 500 * 1024 * 1024, // 500 MB
},
});
// ==========================================
// 🏢 مسیرهای مربوط به آکادمی
// ==========================================
// دریافت اطلاعات آکادمی کاربر جاری
router.get("/", academyController.findAcademy);
// یافتن آکادمی با شناسه
router.post("/findAcademyById", academyController.findAcademyById);
// تکمیل پروفایل آکادمی
router.post("/academy/profile", academyController.academyProfile);
// ==========================================
// 📚 مسیرهای مدیریت دوره‌ها (Courses)
// ==========================================
// ایجاد دوره جدید
router.post("/academy/creat/course", academyController.creatCourse);
// ویرایش دوره
router.patch("/academy/update/course", academyController.updateCourse);
// حذف دوره (تغییر وضعیت به reject)
router.delete("/academy/delete/course", academyController.deleteCourse);
// دریافت لیست دوره‌های آکادمی جاری
router.get("/academy/get/course", academyController.getCourse);
// دریافت لیست همه دوره‌ها با فیلتر و صفحه‌بندی
router.get("/academy/get/curses", academyController.getCourses);
// محتوای رایگان آموزشگاه برای اکسپلور
router.get(
"/academy/explore/free-content",
academyController.getFreeAcademyExploreContent
);
// دریافت دوره‌های یک آکادمی خاص
router.get("/academy/get/getAcademyCourse/:Id", academyController.getAcademyCourse);
// ==========================================
// 💳 مسیرهای پرداخت و مالی
// ==========================================
// پرداخت برای اشتراک پرو
router.post("/academy/course/payment", academyController.proPayment);
// کال‌بک درگاه پرداخت زرین‌پال (اشتراک پرو)
router.get("/academy/course/pro", academyController.handleCoursePaymentCallback);
// پرداخت برای خرید دوره
router.post("/academy/course/payment-web", academyController.coursePayment);
// کال‌بک درگاه پرداخت زرین‌پال (خرید دوره)
router.get("/academy/course/payment/verify", academyController.CoursePaymentCallback);
// ==========================================
// 🎬 مسیرهای محتوای دوره (ویدیوها و فایل‌ها)
// ==========================================
// دریافت محتوای دوره (ویدیوها)
router.get("/academy/course/content", academyController.getCourseContent);
// حذف ویدیو از دوره
router.delete("/academy/course/content/video", academyController.deleteCourseVideo);
// ==========================================
// 📹 مسیرهای آپلود ویدیو (۳ روش مختلف)
// ==========================================
// روش 1: آپلود معمولی با Multipart (با استفاده از Multer)
router.post(
"/academy/course/creat/video",
(req, res, next) => {
console.log("===== STEP 1: Route hit =====");
console.log("Content-Type:", req.headers["content-type"]);
next();
},
(req, res, next) => {
console.log("===== STEP 2: Before multer =====");
next();
},
(req, res, next) => {
// اجرای manual multer با try-catch
upload.single("video")(req, res, (err) => {
if (err) {
console.log("===== MULTER ERROR =====");
console.log("Error name:", err.name);
console.log("Error message:", err.message);
console.log("Full error:", err);
return res.status(400).json({
success: false,
message: `خطا در آپلود: ${err.message}`,
error: err.toString(),
});
}
console.log("===== STEP 3: Multer completed successfully =====");
next();
});
},
(req, res, next) => {
console.log("===== STEP 4: After multer =====");
console.log("req.file:", req.file);
console.log("req.body:", req.body);
if (!req.file) {
console.log("ERROR: No file in req.file!");
return res.status(400).json({
success: false,
message: "فایلی دریافت نشد",
});
}
next();
},
academyController.creatCourseVideo
);
// روش 2: آپلود با Base64 (برای فایل‌های کوچک)
router.post(
"/academy/course/creat/video-base64",
academyController.creatCourseVideoBase64
);
// روش 3: آپلود تکه‌تکه (Chunk Upload) - مناسب برای فایل‌های بزرگ
// دریافت هر تکه از ویدیو
router.post(
"/academy/course/creat/video-chunk",
academyController.creatCourseVideoChunk
);
// ادغام تکه‌ها بعد از اتمام آپلود
router.post(
"/academy/course/creat/video-chunk/merge",
academyController.mergeVideoChunks
);
// بررسی وضعیت آپلود (برای قابلیت Resume)
router.get("/academy/course/upload-status", academyController.getUploadStatus);
// ==========================================
// ❤️ مسیرهای لایک و تعامل
// ==========================================
// لایک کردن محتوای دوره
router.post("/academy/course/like", academyController.likeCourseContent);
// حذف لایک (دیسلایک)
router.post("/academy/course/dislike", academyController.dontLikeCourseContent);
// بررسی وضعیت لایک کاربر
router.post("/academy/course/is-like", academyController.isLikeCourseContent);
// دریافت تعداد لایک‌های یک دوره
router.get(
"/academy/course/likes-count/:courseId",
academyController.getCourseLikesCount
);
// دریافت لیست دوره‌های لایک شده توسط کاربر
router.get(
"/academy/user/liked-courses",
academyController.getUserLikedCourses
);
// ==========================================
// 💬 مسیرهای کامنت و نظرات
// ==========================================
// دریافت تعداد کل کامنت‌های یک دوره
router.get(
"/course/:courseId/comments/count",
academyController.getTotalCommentsCount
);
// دریافت لیست کامنت‌های یک دوره
router.get("/course/:courseId/comments", academyController.getCourseComments);
// ایجاد کامنت جدید
router.post("/comment/create", academyController.createComment);
// تغییر وضعیت کامنت (تایید/رد)
router.patch(
"/comment/:commentId/status",
academyController.updateCommentStatus
);
// حذف کامنت
router.delete("/comment/:commentId/delete", academyController.deleteComment);
// ==========================================
// 🛒 مسیرهای دوره‌های خریداری شده
// ==========================================
// دریافت دوره‌های خریداری شده توسط کاربر جاری
router.get(
"/course/getUserPurchasedCourses",
academyController.getUserPurchasedCourses
);
// دریافت دوره‌های خریداری شده (با populate - روش بهینه)
router.get(
"/course/getUserPurchasedCoursesWithPopulate",
academyController.getUserPurchasedCoursesWithPopulate
);
// دریافت همه پرداخت‌های آکادمی
router.get(
"/course/getAllAcademyPayments",
academyController.getAllAcademyPayments
);
// بررسی خرید دوره توسط کاربر
router.get(
"/course/checkCoursePurchase/:courseId",
academyController.checkCoursePurchase
);
// ==========================================
// 📊 مسیرهای آمار و گزارشات (STATIC)
// ==========================================
// آمار پرداخت‌ها
router.get("/payments/stats", academyController.getPaymentStats);
// آمار دوره‌ها
router.get("/courses/stats", academyController.getCourseStats);
// آمار دوره‌های خریداری شده
router.get("/purchased/stats", academyController.getPurchasedStats);
// آمار آکادمی‌ها
router.get("/academies/stats", academyController.getAcademyStats);
// ==========================================
// ⚙️ مسیرهای تنظیمات (Settings)
// ==========================================
// دریافت همه تنظیمات
router.get("/settings/all", academyController.getAllSettings);
router.get("/settings", academyController.getAllSettings);
// به‌روزرسانی همه تنظیمات
router.put("/settings/all", academyController.updateAllSettings);
router.put("/settings", academyController.updateAllSettings);
// دریافت و ویرایش قیمت دوره‌ها
router.get("/settings/course-prices", academyController.getCoursePrices);
router.put("/settings/course-price", academyController.updateCoursePrice);
// دریافت و ویرایش مالیات
router.get("/settings/tax", academyController.getTax);
router.put("/settings/tax", academyController.updateTax);
// ==========================================
// 🏷️ مسیرهای دسته‌بندی آکادمی (Categories)
// ==========================================
// دریافت لیست دسته‌بندی‌ها (با فیلتر و صفحه‌بندی)
router.get('/categories', academyController.getAll);
// دریافت درخت دسته‌بندی (ساختار سلسله‌مراتبی)
router.get('/categories/tree', academyController.getTree);
// دریافت دسته‌بندی‌های ویژه (برای نمایش در هوم پیج)
router.get('/categories/featured', academyController.getFeatured);
// دریافت دسته‌بندی با اسلاگ (برای سئو و لینک‌های زیبا)
router.get('/categories/slug/:slug', academyController.getBySlug);
// دریافت یک دسته‌بندی با شناسه
router.get('/categories/:id', academyController.getOne);
// ایجاد دسته‌بندی جدید
router.post('/categories', academyController.create);
// ویرایش دسته‌بندی (هم PUT و هم PATCH پشتیبانی می‌شود)
router.put('/categories/:id', academyController.update);
router.patch('/categories/:id', academyController.update);
// حذف دسته‌بندی (سافت دیلیت - فقط غیرفعال می‌شود)
router.delete('/categories/:id', academyController.remove);
// حذف فیزیکی دسته‌بندی (به همراه query string permanent=true)
router.delete('/categories/:id/permanent', (req, res) => {
req.query.permanent = 'true';
academyController.remove(req, res);
});
// تغییر وضعیت دسته‌بندی (فعال/غیرفعال)
router.patch('/categories/:id/status', academyController.toggleStatus);
// افزایش تعداد دوره‌های دسته‌بندی (هر بار یک دوره جدید اضافه می‌شود)
router.post('/categories/:id/increment-count', academyController.incrementCourseCount);
// ==========================================
// 👑 مسیرهای مدیریتی (پنل ادمین)
// ==========================================
// ------ مدیریت پرداخت‌ها ------
router.get("/payments", academyController.getAllPayments);
router.put("/payments/status", academyController.updatePaymentStatus);
// ------ مدیریت دوره‌ها ------
router.get("/courses", academyController.getAllCourses);
router.get("/courses/:id", academyController.getCourseById);
router.put("/courses/:id", academyController.updateCourse);
router.post("/courses", academyController.createCourse);
router.put("/academy/courses/status", academyController.updateCourseStatus);
router.delete("/courses/:courseId", academyController.admindeleteCourse);
// ------ مدیریت دوره‌های خریداری شده ------
router.get("/purchased", academyController.getAllPurchasedCourses);
// ------ مدیریت آکادمی‌ها ------
router.get("/academies", academyController.getAllAcademies);
router.get("/academies/:id", academyController.getAcademyById);
router.put("/academies/:id", academyController.updateAcademy);
router.post("/academies", academyController.createAcademy);
router.put("/academies/status", academyController.updateAcademyStatus);
router.delete("/academies/:id", academyController.deleteAcademy);
// ==========================================
// 📤 خروجی روت‌ها
// ==========================================
module.exports = router;

View File

@@ -1,45 +0,0 @@
const express = require('express')
const blockCheck = require('../../../middlewares/blockCheck')
const { createAdvertisingValidationRules, createAdvertising, getAdvertisingTypes, getAdvertisingCategory, getSingleAdvertising, getAdvertisingFeatures, getAdvertisings, toggleLike, addComment, getComments, rateAdvertising, getUserAdvertisings, editAdvertising, getUserSingleAdvertising, getUserAdvertisingProfile, updateUserAdvertisingProfile, getAdvertisingsWeb, getSingleAdvertisingWeb, getUserAdvertisingProfileWeb } = require('../../../controllers/application/advertising/advertisingController')
const auth = require('../../../middlewares/auth')
const { initiateAdvertisingPayment, handleAdvertisingPaymentCallback, handleAdvertisingRepublishPaymentCallback, republishAdvertisingPayment, handleSuccessfulAdvertisingPayment, handleFailedAdvertisingPayment, handleFailedAdvertisingRepublishPayment, handleSuccessfulAdvertisingRepublishPayment } = require('../../../controllers/application/payment/paymentController')
const { initiateAdvertisingPaymentWeb, handleAdvertisingPaymentCallbackWeb, republishAdvertisingPaymentWeb, handleAdvertisingRepublishPaymentCallbackWeb } = require('../../../controllers/application/payment/paymentControllerWeb')
const router = express.Router()
router.get('/', [auth], getAdvertisings)
router.get('/web', getAdvertisingsWeb)
router.get('/profile', [auth], getUserAdvertisingProfile)
router.get('/profile-web', getUserAdvertisingProfileWeb)
router.patch('/profile', [auth], [blockCheck], updateUserAdvertisingProfile)
router.get('/user-advertising', [auth], getUserAdvertisings)
router.post('/create', [auth], [blockCheck], createAdvertisingValidationRules(), createAdvertising)
router.get('/types', [auth], getAdvertisingTypes)
router.get('/categories', getAdvertisingCategory)
router.get('/features', [auth], getAdvertisingFeatures)
router.get('/get/:advertisingId', [auth], getSingleAdvertising)
router.get('/get/web/:advertisingId', getSingleAdvertisingWeb)
router.get('/get-detail/:advertisingId', [auth], getUserSingleAdvertising)
router.post('/initiate-payment', [auth], [blockCheck], initiateAdvertisingPayment)
router.post('/initiate-payment-web', [auth], [blockCheck], initiateAdvertisingPaymentWeb)
router.post('/republish-payment', [auth], [blockCheck], republishAdvertisingPayment)
router.post('/republish-payment-web', [auth], [blockCheck], republishAdvertisingPaymentWeb)
router.get('/payment', handleAdvertisingPaymentCallback)
router.get('/payment-web', handleAdvertisingPaymentCallbackWeb)
router.get('/republish-verify-payment', handleAdvertisingRepublishPaymentCallback)
router.get('/republish-verify-payment-web', handleAdvertisingRepublishPaymentCallbackWeb)
router.post('/like', [blockCheck], [auth], toggleLike)
router.post('/comment', [blockCheck], [auth], addComment)
router.get('/comments', getComments)
router.post('/rate', [blockCheck], [auth], rateAdvertising)
router.post('/edit/:id', [blockCheck], [auth], editAdvertising)
// Route for handling successful payment
router.post('/payment-success', handleSuccessfulAdvertisingPayment)
// Route for handling failed payment
router.post('/payment-failed', handleFailedAdvertisingPayment)
// Route for handling successful payment
router.post('/republish-payment-success', handleSuccessfulAdvertisingRepublishPayment)
// Route for handling failed payment
router.post('/republish-payment-failed', handleFailedAdvertisingRepublishPayment)
module.exports = router
module.exports = router

View File

@@ -1,7 +0,0 @@
const express = require('express')
const router = express.Router()
const citysController = require('../../../controllers/application/citysController')
router.get('/:id', citysController.getCities)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getExpertise } = require('../../../controllers/application/expertise/expertiseController')
const router = express.Router()
router.get('/', getExpertise)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getFinancial } = require('../../../controllers/application/financial/financialController')
const router = express.Router()
router.get('/', getFinancial)
module.exports = router

View File

@@ -1,14 +0,0 @@
const express = require('express')
const router = express.Router()
const { loginValidationRules, loginUser } = require('../../../controllers/application/login/loginController')
const { verifyUser } = require('../../../controllers/application/login/verifyController')
const { changePasswordUser, changePasswordValidationRules, editChangePasswordUser } = require('../../../controllers/application/login/changePasswordController')
const { loginWithUserName } = require('../../../controllers/application/login/loginWithUserName')
router.post('/', loginValidationRules(), loginUser)
router.post('/verify', verifyUser)
router.post('/change_password', changePasswordValidationRules(), changePasswordUser)
router.patch('/change_password', changePasswordValidationRules(), editChangePasswordUser)
router.post('/login_username', loginWithUserName)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getMessages, getUnreadMessages } = require('../../../controllers/application/messages/messageController')
const router = express.Router()
router.get('/', getMessages)
router.get('/unread', getUnreadMessages)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getNotifications, unreadNotifications } = require('../../../controllers/application/notification/notificationController')
const router = express.Router()
router.get('/', getNotifications)
router.get('/unread', unreadNotifications)
module.exports = router

View File

@@ -1,18 +0,0 @@
const express = require('express')
const { getUserOffers, getOfferTypes, handleSuccessfulOfferPayment, handleFailedOfferPayment, updateOfferStatus, createOfferComment } = require('../../../controllers/application/offer/offerController')
const { initiateOfferPaymentWeb, handleOfferPaymentCallback } = require('../../../controllers/application/payment/paymentControllerWeb')
const blockCheck = require('../../../middlewares/blockCheck')
const isRegister = require('../../../middlewares/isRegister')
const auth = require('./../../../middlewares/auth')
const router = express.Router()
router.get('/', getUserOffers)
router.get('/types', [auth], getOfferTypes)
router.post('/update-status', [auth], updateOfferStatus)
router.post('/payment-success', [auth], handleSuccessfulOfferPayment)
router.post('/payment-failed', [auth], handleFailedOfferPayment)
router.post('/comment', [auth], createOfferComment)
router.post('/initiate-payment-web', [auth], [blockCheck], initiateOfferPaymentWeb)
router.get('/payment-web', handleOfferPaymentCallback)
module.exports = router

View File

@@ -1,205 +0,0 @@
const express = require('express');
const { createPostValidationRules, createPost, getUserPosts, getUserPostsWeb, getPostByIdWeb } = require('../../../controllers/application/posts/postController');
const { toggleLike } = require('../../../controllers/application/posts/likeController');
const blockCheck = require('../../../middlewares/blockCheck');
const auth = require('../../../middlewares/auth');
const isHalfRegister = require('../../../middlewares/isHalfRegister');
const path = require('path');
const fs = require('fs');
const multer = require('multer');
console.log('DEBUG: Starting to load routes/posts.js');
const ensureDir = (dir) => {
console.log('DEBUG: Ensuring directory exists:', dir);
if (!fs.existsSync(dir)) {
console.log('DEBUG: Directory does not exist, creating:', dir);
fs.mkdirSync(dir, { recursive: true });
} else {
console.log('DEBUG: Directory already exists:', dir);
}
};
const imageDir = path.join(__dirname, '../../../storage/posts/images');
const videoDir = path.join(__dirname, '../../../storage/posts/videos');
console.log('DEBUG: imageDir path:', imageDir);
console.log('DEBUG: videoDir path:', videoDir);
ensureDir(imageDir);
ensureDir(videoDir);
console.log('DEBUG: Setting up multer storage');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = file.mimetype.startsWith('video/') ? videoDir : imageDir;
console.log('DEBUG: multer destination for file', file.originalname, ':', dir);
cb(null, dir);
},
filename: (req, file, cb) => {
const filename = `${Date.now()}-${file.originalname}`;
console.log('DEBUG: Generated filename for', file.originalname, ':', filename);
cb(null, filename);
},
});
console.log('DEBUG: Setting up multer instance with limits and filter');
const upload = multer({
storage,
limits: {
fileSize: 100 * 1024 * 1024, // حداکثر 100MB
files: 10, // حداکثر 10 فایل
fieldSize: 1024 * 1024,
fieldNameSize: 100,
},
fileFilter: (req, file, cb) => {
console.log('DEBUG: Checking file filter for', file.originalname, 'mimetype:', file.mimetype);
if (file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/')) {
console.log('DEBUG: File accepted:', file.originalname);
cb(null, true);
} else {
console.log('DEBUG: File rejected:', file.originalname);
cb(new Error('فقط تصاویر یا ویدئوها مجاز هستند'), false);
}
},
}).array('post_files', 10);
const multerErrorHandler = (err, req, res, next) => {
console.log('DEBUG: Request headers:', JSON.stringify(req.headers, null, 2));
console.log('DEBUG: Request body (raw):', req.body);
console.log('DEBUG: Request files:', req.files || 'No files received');
if (err instanceof multer.MulterError) {
console.error('DEBUG: Multer error:', err.message, err.code, err.field);
return res.status(400).json({
error: true,
message: `خطای آپلود: ${err.message} (${err.code})`,
field: err.field,
});
} else if (err) {
console.error('DEBUG: File filter error:', err.message);
return res.status(400).json({
error: true,
message: err.message,
});
}
console.log('DEBUG: Multer processing completed, files:', req.files || 'No files');
console.log('DEBUG: Request body after multer:', req.body);
next();
};
console.log('DEBUG: Setting up express router');
const router = express.Router();
// روت اصلی با multipart/form-data
router.post(
'/create',
(req, res, next) => {
console.log('DEBUG: reached auth');
console.log('DEBUG: Raw request headers:', JSON.stringify(req.headers, null, 2));
next();
},
auth,
(req, res, next) => {
console.log('DEBUG: reached blockCheck');
next();
},
blockCheck,
(req, res, next) => {
console.log('DEBUG: reached isHalfRegister');
next();
},
isHalfRegister,
(req, res, next) => {
console.log('DEBUG: reached multer');
console.log('DEBUG: Content-Length:', req.headers['content-length']);
console.log('DEBUG: Content-Type:', req.headers['content-type']);
next();
},
upload,
multerErrorHandler,
(req, res, next) => {
console.log('DEBUG: reached validation');
console.log('DEBUG: Parsed files:', req.files || 'No files');
console.log('DEBUG: Parsed body:', req.body);
next();
},
createPostValidationRules(),
(req, res, next) => {
console.log('DEBUG: reached controller');
next();
},
createPost
);
// روت جدید برای Base64
router.post(
'/create-base64',
(req, res, next) => {
console.log('DEBUG: reached auth for base64');
console.log('DEBUG: Raw request headers:', JSON.stringify(req.headers, null, 2));
next();
},
auth,
(req, res, next) => {
console.log('DEBUG: reached blockCheck for base64');
next();
},
blockCheck,
(req, res, next) => {
console.log('DEBUG: reached isHalfRegister for base64');
next();
},
isHalfRegister,
(req, res, next) => {
console.log('DEBUG: Request body (base64):', JSON.stringify(req.body, null, 2));
next();
},
createPostValidationRules(),
async (req, res) => {
try {
const { files, caption } = req.body;
console.log('DEBUG: Received files (base64):', files.length);
console.log('DEBUG: Received caption:', caption);
// تبدیل Base64 به فایل
const savedFiles = await Promise.all(
files.map(async (file, index) => {
const buffer = Buffer.from(file.data.split(',')[1], 'base64');
const dir = file.type.startsWith('video/') ? videoDir : imageDir;
const filename = `${Date.now()}-${index}-${file.name}`;
const filePath = path.join(dir, filename);
console.log('DEBUG: Saving file:', filePath);
await fs.promises.writeFile(filePath, buffer);
return {
path: filePath,
filename,
mimetype: file.type,
};
})
);
// فراخوانی createPost با فرمت مشابه
req.files = savedFiles;
req.body = { caption };
console.log('DEBUG: Prepared files for createPost:', savedFiles);
console.log('DEBUG: Prepared body for createPost:', req.body);
await createPost(req, res);
} catch (err) {
console.error('DEBUG: Base64 processing error:', err.message);
res.status(400).json({
error: true,
message: `خطای پردازش فایل‌ها: ${err.message}`,
});
}
}
);
router.post('/like', [auth, blockCheck], toggleLike);
router.get('/web/:postId', getPostByIdWeb);
router.get('/user-posts', [auth], getUserPosts);
router.get('/user-posts/web', getUserPostsWeb);
console.log('DEBUG: Finished loading routes/posts.js');
module.exports = router;

View File

@@ -1,8 +0,0 @@
const express = require('express')
const { getProfile , getUserById} = require('../../../controllers/application/profile/profileController')
const auth = require('../../../middlewares/auth')
const router = express.Router()
router.get('/',[auth], getProfile)
router.get('/:id', getUserById)
module.exports = router

View File

@@ -1,44 +0,0 @@
const express = require('express')
const { createProject, createProjectValidationRules } = require('../../../controllers/application/projects/createProjectController')
const { getProjects, getSingleProjects, getSingleProjectsWeb, getUserProjects, getUserProjectsWeb } = require('../../../controllers/application/projects/getProjectController')
const { getProjectTypes } = require('../../../controllers/application/projects/projectTypesController')
const { initiatePayment, handlePaymentCallback, paymentRequestStepOne, paymentRequestStepTwo, paymentRequestStepAll, paymentRequestStepOneCallback, paymentRequestStepTwoCallback, paymentRequestStepAllCallback, handleSuccessfulPayment, handleFailedPayment } = require('../../../controllers/application/payment/paymentController')
const auth = require('../../../middlewares/auth')
const { requestProjectValidationRules, requestProject, acceptProject, editRequestProject } = require('../../../controllers/application/projects/requestProjectController')
const { editProject, doneProject, cancleProject, editProjectValidationRules, setRateProject, doneProjectWeb } = require('../../../controllers/application/projects/updateProjectController')
const blockCheck = require('../../../middlewares/blockCheck')
const isRegister = require('../../../middlewares/isRegister')
const { initiatePaymentWeb, handlePaymentCallbackWeb, paymentRequestAcceptWeb } = require('../../../controllers/application/payment/paymentControllerWeb')
const router = express.Router()
router.get('/', getProjects)
router.get('/user-projects', [auth], getUserProjects)
router.get('/user-projects/web', getUserProjectsWeb)
router.get('/types', [auth], getProjectTypes)
router.get('/get/:projectId', [auth], getSingleProjects)
router.get('/get/web/:projectId', getSingleProjectsWeb)
router.get('/payment', handlePaymentCallback)
router.get('/payment-web', handlePaymentCallbackWeb)
router.post('/create', [blockCheck], [auth], createProjectValidationRules(), createProject)
router.post('/edit', [blockCheck], [auth], editProjectValidationRules(), editProject)
router.post('/rate', [blockCheck], [auth], [isRegister], setRateProject)
router.post('/initiate-payment', [blockCheck], [isRegister], [auth], initiatePayment)
router.post('/initiate-payment-web', [blockCheck], [isRegister], [auth], initiatePaymentWeb)
router.post('/request', [blockCheck], [isRegister], [auth], requestProjectValidationRules(), requestProject)
router.post('/edit-request', [isRegister], [blockCheck], [auth], requestProjectValidationRules(), editRequestProject)
router.post('/payment-request-step-one', [isRegister], [blockCheck], [auth], paymentRequestStepOne)
router.post('/payment-request-step-one/web', [isRegister], [blockCheck], [auth], paymentRequestAcceptWeb)
router.post('/payment-request-step-two', [isRegister], [blockCheck], [auth], paymentRequestStepTwo)
router.post('/payment-request-all', [isRegister], [blockCheck], [auth], paymentRequestStepAll)
router.get('/payment-request-step-one-callback', paymentRequestStepOneCallback)
router.get('/payment-request-step-two-callback', paymentRequestStepTwoCallback)
router.get('/payment-request-step-all-callback', paymentRequestStepAllCallback)
router.post('/accept', [isRegister], [blockCheck], [auth], acceptProject)
router.post('/done', [isRegister], [auth], doneProject)
router.post('/done/web', [isRegister], [auth], doneProjectWeb)
router.post('/cancle', [isRegister], [blockCheck], [auth], cancleProject)
// Route for handling successful payment
router.post('/payment-success', handleSuccessfulPayment)
// Route for handling failed payment
router.post('/payment-failed', handleFailedPayment)
module.exports = router

View File

@@ -1,7 +0,0 @@
const express = require('express')
const router = express.Router()
const provincesController = require('../../../controllers/application/provincesController')
router.get('/', provincesController.getProvinces)
module.exports = router

View File

@@ -1,16 +0,0 @@
const express = require('express')
const router = express.Router()
const { registerValidationRules, registerUser } = require('../../../controllers/application/register/registerController')
const { verifyUser } = require('../../../controllers/application/register/verifyController')
const { setUserName, updateUserName } = require('../../../controllers/application/register/usernameController')
const { setUserFullName, setUserFullNameValidationRules } = require('../../../controllers/application/register/userFullNameController')
const { setUserType } = require('../../../controllers/application/register/userTypeController')
router.post('/', registerValidationRules(), registerUser)
router.post('/verify', verifyUser)
router.post('/username', setUserName)
router.patch('/username', updateUserName)
router.post('/fullname', setUserFullNameValidationRules(), setUserFullName)
router.post('/user_type', setUserType)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getSearch } = require('../../../controllers/application/search-web/searchController')
const router = express.Router()
router.get('/', getSearch)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getSearch } = require('../../../controllers/application/search/searchController')
const router = express.Router()
router.get('/', getSearch)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getSetting } = require('../../../controllers/application/settings/settingsController')
const router = express.Router()
router.get('/:key', getSetting)
module.exports = router

View File

@@ -1,19 +0,0 @@
const express = require('express')
const auth = require('../../../middlewares/auth')
const blockCheck = require('../../../middlewares/blockCheck')
const isHalfRegister = require('../../../middlewares/isHalfRegister')
const {
createStoryBase64,
getStoriesFeed,
markStoryViewed,
deleteStory
} = require('../../../controllers/application/stories/storyController')
const router = express.Router()
router.get('/feed', getStoriesFeed)
router.post('/create-base64', auth, blockCheck, isHalfRegister, createStoryBase64)
router.post('/view', auth, blockCheck, markStoryViewed)
router.delete('/:storyId', auth, blockCheck, deleteStory)
module.exports = router

View File

@@ -1,9 +0,0 @@
const express = require('express')
const { getUserTickets, createTicket, addUserMessageToTicket, getTicketMessages } = require('../../../controllers/application/tickets/ticketsController')
const router = express.Router()
router.get('/', getUserTickets)
router.get('/messages', getTicketMessages)
router.post('/create', createTicket)
router.post('/message', addUserMessageToTicket)
module.exports = router

View File

@@ -1,23 +0,0 @@
const express = require('express')
const { getUsers, getSingleUser, blockUser, unblockUser, getComments,createLicense, getAllLicenses,updateLicenseConfirmation, getLicenseByUserId, inviteEmployer, getUserContactInfo, getPostsWeb, getSingleUserWeb, createUserComment, recordExploreInteraction } = require('../../../controllers/application/users/getUserController')
const blockCheck = require('../../../middlewares/blockCheck')
const isRegister = require('../../../middlewares/isRegister')
const auth = require('../../../middlewares/auth')
const router = express.Router()
router.get('/', [auth], getUsers)
router.get('/web', getPostsWeb)
router.get('/get', [auth], getSingleUser)
router.get('/get/web', getSingleUserWeb)
router.post('/block', [auth], [isRegister], [blockCheck], blockUser)
router.post('/unblock', [auth], [isRegister], [blockCheck], unblockUser)
router.get('/comments', [auth], getComments)
router.post('/comments', [auth], [isRegister], [blockCheck], createUserComment)
router.post('/explore/interaction', [auth], recordExploreInteraction)
router.get('/invite-employer', [auth], [isRegister], [blockCheck], inviteEmployer)
router.get('/contact-info', [auth], getUserContactInfo)
router.get('/License', getAllLicenses)
router.get('/License/:userId', getLicenseByUserId)
router.post('/License', createLicense)
router.put('/License/:userId', updateLicenseConfirmation)
module.exports = router

View File

@@ -1,40 +0,0 @@
const express = require('express')
const { setProfileImage, updateProfileImage } = require('../../../controllers/application/verify/setProfileImageController')
const { setExpertise, updateExpertise } = require('../../../controllers/application/verify/expertiseController')
const { setGender } = require('../../../controllers/application/verify/genderController')
const { setSizes, updateSizes } = require('../../../controllers/application/verify/sizesController')
const { setColors, updateColors } = require('../../../controllers/application/verify/colorsController')
const { setCooperationType, updateCooperationType } = require('../../../controllers/application/verify/cooperationTypeController')
const { setConversation, updateConversation } = require('../../../controllers/application/verify/conversationController')
const { saveAuth, saveAuthValidationRules, updateShaba } = require('../../../controllers/application/verify/authController')
const { setAddress, updateAddress } = require('../../../controllers/application/verify/addressController')
const { setNationalCardImage } = require('../../../controllers/application/verify/setNationalCardImageController')
const { completeRegistration } = require('../../../controllers/application/verify/completeRegistrationController')
const blockCheck = require('../../../middlewares/blockCheck')
const auth = require('../../../middlewares/auth')
const { setServices, updateServices } = require('../../../controllers/application/verify/servicesController')
const router = express.Router()
router.post('/profile_image', setProfileImage)
router.patch('/profile_image', [blockCheck], [auth], updateProfileImage)
router.post('/expertise', setExpertise)
router.patch('/expertise', [blockCheck], [auth], updateExpertise)
router.post('/gender', setGender)
router.post('/sizes', setSizes)
router.patch('/sizes', [blockCheck], [auth], updateSizes)
router.post('/services', setServices)
router.patch('/services', [blockCheck], [auth], updateServices)
router.post('/colors', setColors)
router.patch('/colors', [blockCheck], [auth], updateColors)
router.post('/cooperation_type', setCooperationType)
router.patch('/cooperation_type', [blockCheck], [auth], updateCooperationType)
router.post('/conversation', setConversation)
router.patch('/conversation', [blockCheck], [auth], updateConversation)
router.post('/auth', saveAuthValidationRules(), saveAuth)
router.patch('/shaba', [blockCheck], [auth], updateShaba)
router.post('/address', setAddress)
router.patch('/address', [blockCheck], [auth], updateAddress)
router.post('/national_card_image', setNationalCardImage)
router.post('/complete', completeRegistration)
module.exports = router

View File

@@ -1,6 +0,0 @@
const express = require('express')
const { getVersion } = require('../../../controllers/application/version/versionController')
const router = express.Router()
router.get('/', getVersion)
module.exports = router

View File

@@ -1,8 +0,0 @@
const express = require('express')
const { getProjects, getSingleProject, getProjectRequests } = require('../../../controllers/application/workroom/workroomController')
const router = express.Router()
router.get('/', getProjects)
router.get('/get', getSingleProject)
router.get('/get-requests/:projectId', getProjectRequests)
module.exports = router

View File

@@ -1,79 +0,0 @@
const registerRouter = require('./application/register')
const loginRouter = require('./application/login')
const verifyRouter = require('./application/verify')
const provincesRouter = require('./application/provinces')
const citiesRouter = require('./application/city')
const projectsRouter = require('./application/projects')
const usersRouter = require('./application/users')
const postsRouter = require('./application/posts')
const expertiseRouter = require('./application/expertise')
const profileRouter = require('./application/profile')
const workroomRouter = require('./application/workroom')
const messagesRouter = require('./application/messages')
const searchRouter = require('./application/search')
const searchRouterWeb = require('./application/search-web')
const financialRouter = require('./application/financial')
const notificationRouter = require('./application/notification')
const ticketsRouter = require('./application/tickets')
const advertisingRouter = require('./application/advertising')
const settingsRouter = require('./application/settings')
const versionRouter = require('./application/version')
const offerRouter = require('./application/offers')
const academyRoute = require('./application/academy')
const storiesRouter = require('./application/stories')
const projectsPanelRouter = require('./panel/projects')
const postsPanelRouter = require('./panel/posts')
const expertisePanelRouter = require('./panel/expertise')
const loginPanelRouter = require('./panel/login')
const usersPanelRouter = require('./panel/users')
const ticketsPanelRouter = require('./panel/tickets')
const financialPanelRouter = require('./panel/financial')
const commentsPanelRouter = require('./panel/comments')
const advertisingPanelRouter = require('./panel/advertising')
const shopsPanelRouter = require('./panel/shops')
const settingsPanelRouter = require('./panel/settings')
const versionPanelRouter = require('./panel/version')
const auth = require('../middlewares/auth')
const adminAuth = require('../middlewares/adminAuth')
module.exports = (app) => {
// App
app.use('/api/v1/register', registerRouter)
app.use('/api/v1/login', loginRouter)
app.use('/api/v1/verify', [auth], verifyRouter)
app.use('/api/v1/provinces', provincesRouter)
app.use('/api/v1/cities', citiesRouter)
app.use('/api/v1/projects', projectsRouter)
app.use('/api/v1/users', usersRouter)
app.use('/api/v1/posts', postsRouter)
app.use('/api/v1/expertise', expertiseRouter)
app.use('/api/v1/profile', profileRouter)
app.use('/api/v1/workroom', [auth], workroomRouter)
app.use('/api/v1/messages', [auth], messagesRouter)
app.use('/api/v1/notifications', [auth], notificationRouter)
app.use('/api/v1/search', [auth], searchRouter)
app.use('/api/v1/search-web', searchRouterWeb)
app.use('/api/v1/financial', [auth], financialRouter)
app.use('/api/v1/tickets', [auth], ticketsRouter)
app.use('/api/v1/offers', offerRouter)
app.use('/api/v1/advertising', advertisingRouter)
app.use('/api/v1/settings', settingsRouter)
app.use('/api/v1/version', versionRouter)
app.use('/api/v1/academy', academyRoute)
app.use('/api/v1/stories', storiesRouter)
// Panel
app.use('/api/v1/panel/login', loginPanelRouter)
app.use('/api/v1/panel/users', usersPanelRouter)
app.use('/api/v1/panel/projects', [adminAuth], projectsPanelRouter)
app.use('/api/v1/panel/posts', [adminAuth], postsPanelRouter)
app.use('/api/v1/panel/expertise', [adminAuth], expertisePanelRouter)
app.use('/api/v1/panel/tickets', [adminAuth], ticketsPanelRouter)
app.use('/api/v1/panel/financial', [adminAuth], financialPanelRouter)
app.use('/api/v1/panel/comments', [adminAuth], commentsPanelRouter)
app.use('/api/v1/panel/advertising', [adminAuth], advertisingPanelRouter)
app.use('/api/v1/panel/shops', [adminAuth], shopsPanelRouter)
app.use('/api/v1/panel/settings', [adminAuth], settingsPanelRouter)
app.use('/api/v1/panel/version', [adminAuth], versionPanelRouter)
}

View File

@@ -1,22 +0,0 @@
const express = require('express')
const { getAdvertisings, getSingleAdvertising, acceptAdvertising, rejectAdvertising, getAdvertisingCategories, getAdvertisingCategoryById,createAdvertisingCategory , deleteAdvertisingCategory } = require('../../../controllers/panel/advertising/advertisingController')
const { getAdvertisingComments, getAdvertisingCommentDetail, acceptAdvertisingComment } = require('../../../controllers/panel/comments/advertisingCommentController')
const router = express.Router()
router.get('/', getAdvertisings)
router.get('/detail', getSingleAdvertising)
router.post('/accept', acceptAdvertising)
router.post('/reject', rejectAdvertising)
router.get('/comments', getAdvertisingComments)
router.get('/comments/detail', getAdvertisingCommentDetail)
router.post('/comments/accept', acceptAdvertisingComment)
router.post('/advertising-categories', createAdvertisingCategory)
router.delete('/advertising-categories/:id', deleteAdvertisingCategory)
// لیست دسته‌بندی‌ها (پشتیبانی از query params)
router.get('/advertising-categories', getAdvertisingCategories);
// گرفتن یک دسته‌بندی با id
router.get('/advertising-categories/:id', getAdvertisingCategoryById);
module.exports = router

View File

@@ -1,8 +0,0 @@
const express = require('express')
const { acceptComment, getComments, getCommentDetail } = require('../../../controllers/panel/comments/commentController')
const router = express.Router()
router.get('/', getComments)
router.get('/detail', getCommentDetail)
router.post('/accept', acceptComment)
module.exports = router

View File

@@ -1,9 +0,0 @@
const express = require('express')
const { createExpertise, createSubExpertise, getExpertise, editExpertise } = require('../../../controllers/panel/expertise/expertiseController')
const router = express.Router()
router.get('/', getExpertise)
router.post('/create', createExpertise)
router.post('/create/sub', createSubExpertise)
router.put('/update/:id', editExpertise)
module.exports = router

View File

@@ -1,7 +0,0 @@
const express = require('express')
const { getFinancial, getFinancialDetail } = require('../../../controllers/panel/financial/financialController')
const router = express.Router()
router.get('/', getFinancial)
router.get('/detail', getFinancialDetail)
module.exports = router

View File

@@ -1,8 +0,0 @@
const express = require('express')
const router = express.Router()
const { adminLogin, changePassword } = require('../../../controllers/panel/login/loginController')
const adminAuth = require('../../../middlewares/adminAuth')
router.post('/', adminLogin)
router.post('/change-password', [adminAuth], changePassword)
module.exports = router

View File

@@ -1,8 +0,0 @@
const express = require('express')
const { acceptPost, getPosts, getPostDetail } = require('../../../controllers/panel/posts/postController')
const router = express.Router()
router.get('/', getPosts)
router.get('/detail', getPostDetail)
router.post('/accept', acceptPost)
module.exports = router

View File

@@ -1,11 +0,0 @@
const express = require('express')
const { createProjectTypes, getProjectTypes } = require('../../../controllers/panel/projects/projectTypesController')
const { getProjects, getProjectDetails, acceptProject, rejectProject } = require('../../../controllers/panel/projects/projectController')
const router = express.Router()
router.get('/', getProjects)
router.get('/detail', getProjectDetails)
router.post('/accept', acceptProject)
router.post('/reject', rejectProject)
router.get('/types', getProjectTypes)
router.post('/types/create', createProjectTypes)
module.exports = router

View File

@@ -1,7 +0,0 @@
const express = require('express')
const { getSetting, updateSetting } = require('../../../controllers/panel/settings/settingsController')
const router = express.Router()
router.get('/:key', getSetting)
router.put('/:key', updateSetting)
module.exports = router

View File

@@ -1,8 +0,0 @@
const express = require('express')
const { getShops, getShopDetail, updateShop } = require('../../../controllers/panel/shops/shopsController')
const router = express.Router()
router.get('/', getShops)
router.get('/detail', getShopDetail)
router.put('/update', updateShop)
module.exports = router

View File

@@ -1,9 +0,0 @@
const express = require('express')
const { addAdminMessageToTicket, getAllTickets, getTicketMessages, closeTicket } = require('../../../controllers/panel/tickets/ticketsController')
const router = express.Router()
router.get('/', getAllTickets)
router.get('/messages', getTicketMessages)
router.post('/message', addAdminMessageToTicket)
router.post('/close', closeTicket)
module.exports = router

View File

@@ -1,12 +0,0 @@
const express = require('express')
const { getUsers, getUserDetail,changePass, updateUser, verifyUser, rejectUser, updateUserStatus } = require('../../../controllers/panel/users/usersController')
const router = express.Router()
router.get('/', getUsers)
router.post('/change-password', changePass)
router.get('/detail', getUserDetail)
router.put('/update', updateUser)
router.post('/verify', verifyUser)
router.post('/reject', rejectUser)
router.post('/update-status', updateUserStatus);
module.exports = router

View File

@@ -1,7 +0,0 @@
const express = require('express')
const { getVersion, upsertVersion } = require('../../../controllers/panel/version/versionController')
const router = express.Router()
router.get('/', getVersion)
router.post('/', upsertVersion)
module.exports = router

View File

@@ -5,7 +5,7 @@ const GiftModel = require('../../../models/GiftModel')
const UserModel = require('../../../models/UserModel')
const PaymentModel = require('../../../models/PaymentModel')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const { getBucketBalance } = require('../wallet/walletController')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const MERCHANT_ID = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE
const GIFT_MIN_AMOUNT = 2000
@@ -39,10 +39,22 @@ async function validateGiftTarget (senderId, receiverId, amount) {
const GIFT_USER_SELECT = 'profile_image user_level first_name last_name user_name is_verified verify_badge'
async function creditGiftReceiver (receiverId, giftId, giftAmount) {
await WalletTransactionModel.create({
user: receiverId,
bucket: 'gift',
type: 'credit',
amount: Math.round(giftAmount * (1 - GIFT_COMMISSION_RATE)),
referenceType: 'payment',
referenceId: giftId,
description: 'هدیه دریافتی از کاربر دیگر (پس از کسر ۱۰٪ کارمزد)'
})
}
const initiateGiftPayment = async (req, res, next) => {
try {
const senderId = req.user._id
const { receiverId, amount, message } = req.body
const { receiverId, amount, message, platform, useWallet } = req.body
if (!receiverId) {
return res.status(400).json({ error: true, message: 'کاربر گیرنده مشخص نشده است' })
@@ -64,18 +76,43 @@ const initiateGiftPayment = async (req, res, next) => {
return res.status(403).json({ error: true, message: 'این کاربر دریافت هدیه را غیرفعال کرده است' })
}
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(senderId, giftAmount, useWallet)
if (fullyCovered) {
await debitWalletForPurchase(senderId, walletAmount, {
referenceType: 'payment',
description: 'پرداخت هدیه از کیف پول'
})
const gift = await GiftModel.create({
sender: senderId,
receiver: receiverId,
amount: giftAmount,
message: message || null,
paymentMethod: 'wallet'
})
await creditGiftReceiver(receiverId, gift._id, giftAmount)
return res.json({
paidFromWallet: true,
gift: { _id: gift._id, amount: gift.amount, message: gift.message }
})
}
const callbackBase = process.env.APP_SITE_CallBack || process.env.APP_URL
if (!callbackBase) {
return res.status(500).json({ error: true, message: 'آدرس بازگشت پرداخت تنظیم نشده است' })
}
const callbackUrl = `${callbackBase}/gifts/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&message=${encodeURIComponent(message || '')}`
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const callbackUrl = `${callbackBase}/gifts/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&walletAmount=${walletAmount}&message=${encodeURIComponent(message || '')}${platformSuffix}`
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: MERCHANT_ID,
amount: toRial(giftAmount),
amount: toRial(gatewayAmount),
description: `هدیه ویژه به ${receiver.first_name || receiver.user_name}`,
callback_url: callbackUrl
},
@@ -98,19 +135,25 @@ const initiateGiftPayment = async (req, res, next) => {
}
const handleGiftPaymentCallback = async (req, res) => {
const { Authority, Status, senderId, receiverId, amount, message } = req.query
const failRedirect = `${process.env.APP_SITE}/gift/payment/failed${receiverId ? `?receiverId=${receiverId}` : ''}`
const { Authority, Status, senderId, receiverId, amount, walletAmount, message, platform } = req.query
const isAndroid = platform === 'android'
const failRedirect = isAndroid
? `modstagram://FailedPay?type=gift${receiverId ? `&receiverId=${receiverId}` : ''}`
: `${process.env.APP_SITE}/gift/payment/failed${receiverId ? `?receiverId=${receiverId}` : ''}`
try {
if (Status !== 'OK') return res.redirect(failRedirect)
const giftAmount = Number(amount)
const usedWalletAmount = Number(walletAmount) || 0
const gatewayAmount = giftAmount - usedWalletAmount
const verify = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: MERCHANT_ID,
authority: Authority,
amount: toRial(giftAmount)
amount: toRial(gatewayAmount)
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -120,8 +163,15 @@ const handleGiftPaymentCallback = async (req, res) => {
return res.redirect(failRedirect)
}
// پرداخت درگاه قطعاً موفق شد — همین حالا سهمِ کیف پول هم کسر می‌شه (نگاه کن به
// utils/walletSplit.js)
await debitWalletForPurchase(senderId, usedWalletAmount, {
referenceType: 'payment',
description: 'پرداخت هدیه از کیف پول'
})
const payment = await PaymentModel.create({
amount: giftAmount,
amount: gatewayAmount,
status: 'successful',
authority: Authority,
user_id: senderId,
@@ -133,22 +183,20 @@ const handleGiftPaymentCallback = async (req, res) => {
receiver: receiverId,
amount: giftAmount,
message: message ? decodeURIComponent(message) : null,
transaction_id: payment._id
transaction_id: payment._id,
paymentMethod: usedWalletAmount > 0 ? 'mixed' : 'online'
})
await WalletTransactionModel.create({
user: receiverId,
bucket: 'gift',
type: 'credit',
amount: Math.round(giftAmount * (1 - GIFT_COMMISSION_RATE)),
referenceType: 'payment',
referenceId: gift._id,
description: 'هدیه دریافتی از کاربر دیگر (پس از کسر ۱۰٪ کارمزد)'
})
await creditGiftReceiver(receiverId, gift._id, giftAmount)
const receiverUser = await UserModel.findById(receiverId).select('user_name').lean()
const usernameParam = receiverUser?.user_name ? `&receiverUsername=${encodeURIComponent(receiverUser.user_name)}` : ''
if (isAndroid) {
const usernameParam = receiverUser?.user_name ? `&username=${encodeURIComponent(receiverUser.user_name)}` : ''
return res.redirect(`modstagram://SuccessPay?type=gift&giftId=${gift._id}&receiverId=${receiverId}${usernameParam}`)
}
const usernameParam = receiverUser?.user_name ? `&receiverUsername=${encodeURIComponent(receiverUser.user_name)}` : ''
return res.redirect(`${process.env.APP_SITE}/gift/payment/success?giftId=${gift._id}&receiverId=${receiverId}${usernameParam}`)
} catch (error) {
console.error('handleGiftPaymentCallback error:', error?.response?.data || error.message)
@@ -217,18 +265,15 @@ const replyToGift = async (req, res, next) => {
const initiateChatGift = async (req, res, next) => {
try {
const senderId = req.user._id
const { receiverId, amount, message, return_path: returnPath } = req.body
const { receiverId, amount, message, return_path: returnPath, platform, useWallet } = req.body
const { error, receiver, giftAmount } = await validateGiftTarget(senderId, receiverId, amount)
if (error) return res.status(error.status).json({ error: true, message: error.message })
const chargedBalance = await getBucketBalance(senderId, 'charged')
if (chargedBalance >= giftAmount) {
await WalletTransactionModel.create({
user: senderId,
bucket: 'charged',
type: 'debit',
amount: giftAmount,
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(senderId, giftAmount, useWallet)
if (fullyCovered) {
await debitWalletForPurchase(senderId, walletAmount, {
referenceType: 'payment',
description: 'پرداخت هدیه از کیف پول'
})
@@ -256,13 +301,14 @@ const initiateChatGift = async (req, res, next) => {
}
const safeReturnPath = typeof returnPath === 'string' && returnPath.startsWith('/') ? returnPath : '/'
const callbackUrl = `${callbackBase}/gifts/chat/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&message=${encodeURIComponent(message || '')}&returnPath=${encodeURIComponent(safeReturnPath)}`
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const callbackUrl = `${callbackBase}/gifts/chat/payment-callback?senderId=${senderId}&receiverId=${receiverId}&amount=${giftAmount}&walletAmount=${walletAmount}&message=${encodeURIComponent(message || '')}&returnPath=${encodeURIComponent(safeReturnPath)}${platformSuffix}`
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: MERCHANT_ID,
amount: toRial(giftAmount),
amount: toRial(gatewayAmount),
description: `هدیه در چت به ${receiver.first_name || receiver.user_name}`,
callback_url: callbackUrl
},
@@ -286,20 +332,26 @@ const initiateChatGift = async (req, res, next) => {
}
const handleChatGiftPaymentCallback = async (req, res) => {
const { Authority, Status, senderId, receiverId, amount, message, returnPath } = req.query
const { Authority, Status, senderId, receiverId, amount, walletAmount, message, returnPath, platform } = req.query
const isAndroid = platform === 'android'
const safeReturnPath = typeof returnPath === 'string' && returnPath.startsWith('/') ? decodeURIComponent(returnPath) : '/'
const failRedirect = `${process.env.APP_SITE}${safeReturnPath}?giftPaymentFailed=1`
const failRedirect = isAndroid
? `modstagram://FailedPay?type=gift${receiverId ? `&receiverId=${receiverId}` : ''}`
: `${process.env.APP_SITE}${safeReturnPath}?giftPaymentFailed=1`
try {
if (Status !== 'OK') return res.redirect(failRedirect)
const giftAmount = Number(amount)
const usedWalletAmount = Number(walletAmount) || 0
const gatewayAmount = giftAmount - usedWalletAmount
const verify = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
{
merchant_id: MERCHANT_ID,
authority: Authority,
amount: toRial(giftAmount)
amount: toRial(gatewayAmount)
},
{ headers: { 'Content-Type': 'application/json' } }
)
@@ -309,8 +361,13 @@ const handleChatGiftPaymentCallback = async (req, res) => {
return res.redirect(failRedirect)
}
await debitWalletForPurchase(senderId, usedWalletAmount, {
referenceType: 'payment',
description: 'پرداخت هدیه از کیف پول'
})
const payment = await PaymentModel.create({
amount: giftAmount,
amount: gatewayAmount,
status: 'successful',
authority: Authority,
user_id: senderId,
@@ -325,12 +382,20 @@ const handleChatGiftPaymentCallback = async (req, res) => {
amount: giftAmount,
message: decodedMessage,
transaction_id: payment._id,
paymentMethod: 'online',
paymentMethod: usedWalletAmount > 0 ? 'mixed' : 'online',
viaChatMessage: true,
opened: false,
openedAt: null
})
if (isAndroid) {
const receiverUser = await UserModel.findById(receiverId).select('user_name').lean()
const usernameParam = receiverUser?.user_name ? `&username=${encodeURIComponent(receiverUser.user_name)}` : ''
return res.redirect(
`modstagram://SuccessPay?type=gift&giftId=${gift._id}&receiverId=${receiverId}${usernameParam}`
)
}
return res.redirect(
`${process.env.APP_SITE}${safeReturnPath}?giftId=${gift._id}&giftAmount=${giftAmount}&giftMessage=${encodeURIComponent(decodedMessage || '')}`
)

View File

@@ -14,6 +14,7 @@ const {
consumeFreeCreation,
freeCreationLimitResponse
} = require('../../../utils/freeCreationLimit')
const { canUseFreeBillboard, consumeFreeBillboard } = require('../../../utils/billboardFreeLimit')
const {
PROJECT_ACCEPT_RATE,
PROJECT_COMPLETION_COMMISSION_RATE,
@@ -646,24 +647,20 @@ const initiateAdvertisingPayment = async (req, res) => {
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (user.daily_free_request > 0 && canUseFreeCreation(user)) {
const consumed = await consumeFreeCreation(user)
if (!consumed.ok) {
return res.status(403).json(freeCreationLimitResponse())
}
user.daily_free_request -= 1
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
const project = await ProjectModel.findById(advertisingId)
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Project not found')
res.status(404).send('Advertising not found')
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
}
} else {
const response = await axios.post(
@@ -802,24 +799,20 @@ const republishAdvertisingPayment = async (req, res) => {
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (user.daily_free_request > 0 && canUseFreeCreation(user)) {
const consumed = await consumeFreeCreation(user)
if (!consumed.ok) {
return res.status(403).json(freeCreationLimitResponse())
}
user.daily_free_request -= 1
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
const project = await ProjectModel.findById(advertisingId)
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Project not found')
res.status(404).send('Advertising not found')
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
}
} else {
const response = await axios.post(

View File

@@ -9,14 +9,16 @@ const NotificationModel = require('../../../models/NotificationModel')
const UserModel = require('../../../models/UserModel')
const AdvertisingTypeModel = require('../../../models/AdvertisingTypeModel')
const AdvertisingModel = require('../../../models/AdvertisingModel')
const { canUseFreeBillboard, consumeFreeBillboard } = require('../../../utils/billboardFreeLimit')
const OfferTypeModel = require('../../../models/OfferTypeModel')
const OfferModel = require('../../../models/OfferModel')
const { default: axios } = require('axios')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const { PROJECT_ACCEPT_RATE, PROJECT_COMPLETION_COMMISSION_RATE, creditProjectShare } = require('../../../utils/projectCommission')
const { isVerifiedBadge } = require('../../../utils/verifyBadge')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const OFFER_COMMISSION_RATE = 0.3
const OFFER_COMMISSION_RATE = 0.5
const apiKey =
process.env.ZARINPAL_MERCHANT_ID ||
@@ -285,6 +287,7 @@ const paymentRequestAcceptWeb = async (req, res) => {
}
const requestId = req.body.requestId
const platform = req.body.platform
const request = await RequestModel.findById(requestId)
if (!request) {
return res.status(404).json({ error: 'Request not found' })
@@ -300,13 +303,14 @@ const paymentRequestAcceptWeb = async (req, res) => {
return res.status(500).json({ error: true, message: 'آدرس بازگشت پرداخت تنظیم نشده است' })
}
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const response = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: apiKey,
amount: toRial(acceptAmount),
description: `پرداخت پذیرش همکاری برای پروژه ${project.title || projectId}`,
callback_url: `${callbackBase}/projects/payment-request-step-one-callback/web?projectId=${projectId}&requestId=${requestId}&userId=${userId}`
callback_url: `${callbackBase}/projects/payment-request-step-one-callback/web?projectId=${projectId}&requestId=${requestId}&userId=${userId}${platformSuffix}`
},
{ headers: { 'Content-Type': 'application/json' }, timeout: 20000 }
)
@@ -328,8 +332,14 @@ const paymentRequestAcceptWeb = async (req, res) => {
}
const paymentRequestAcceptWebCallback = async (req, res) => {
const { Authority, Status, projectId, requestId, userId } = req.query
const failRedirect = `${process.env.APP_SITE}/projects/payment/failed?projectId=${projectId || ''}`
const { Authority, Status, projectId, requestId, userId, platform } = req.query
const isAndroid = platform === 'android'
const failRedirect = isAndroid
? `modstagram://FailedPay?type=steps&projectId=${projectId || ''}`
: `${process.env.APP_SITE}/projects/payment/failed?projectId=${projectId || ''}`
const successRedirect = isAndroid
? `modstagram://SuccessPay?type=steps&projectId=${projectId || ''}&requestId=${requestId || ''}`
: `${process.env.APP_SITE}/projects/payment/success?projectId=${projectId}&type=accept`
try {
if (Status !== 'OK') return res.redirect(failRedirect)
@@ -339,7 +349,7 @@ const paymentRequestAcceptWebCallback = async (req, res) => {
if (!request || !project) return res.redirect(failRedirect)
if (project.status === 'ongoing') {
return res.redirect(`${process.env.APP_SITE}/projects/payment/success?projectId=${projectId}&type=accept`)
return res.redirect(successRedirect)
}
const acceptAmount = Math.round(Number(request.price) * PROJECT_ACCEPT_RATE)
@@ -402,7 +412,11 @@ const paymentRequestAcceptWebCallback = async (req, res) => {
})
await notification.save()
return res.redirect(`${process.env.APP_SITE}/projects/payment/success?projectId=${projectId}&type=accept`)
return res.redirect(
isAndroid
? `modstagram://SuccessPay?type=steps&projectId=${projectId}&requestId=${requestId}&price=${acceptAmount}`
: successRedirect
)
} catch (error) {
console.error('paymentRequestAcceptWebCallback error:', error?.response?.data || error?.message || error)
return res.redirect(failRedirect)
@@ -557,8 +571,8 @@ const initiateAdvertisingPaymentWeb = async (req, res) => {
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (user.daily_free_request > 0) {
user.daily_free_request -= 1
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
@@ -570,7 +584,7 @@ const initiateAdvertisingPaymentWeb = async (req, res) => {
await project.save()
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از بیلبورد رایگان این ماه استفاده کرده‌اید.' })
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
}
} else {
const response = await axios.post(
@@ -711,20 +725,20 @@ const republishAdvertisingPaymentWeb = async (req, res) => {
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (user.daily_free_request > 0) {
user.daily_free_request -= 1
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Project not found')
res.status(404).send('Advertising not found')
return
}
project.payment_status = 'done' // Set project status to 'done'
project.status = await resolveApprovalStatus(userId, 'temp_accept') // Set project status to 'done'
await project.save()
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کرده‌اید.' })
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
}
} else {
const response = await axios.post(
@@ -853,14 +867,14 @@ const initiateOfferPaymentWeb = async (req, res) => {
const decoded = jwt.verify(token, process.env.APP_SECRET)
const userId = decoded.id
const { receiverId, offerType, platform } = req.body
const { receiverId, offerType, platform, useWallet } = req.body
if (!receiverId || !offerType) { return res.status(400).json({ error: 'Invalid data' }) }
const user = await UserModel.findById(userId)
const receiver = await UserModel.findById(receiverId)
if (!user || !receiver) { return res.status(404).json({ error: 'User not found' }) }
const price = await getOfferPriceFromDatabase(offerType) // ✅ ریال
const price = await getOfferPriceFromDatabase(offerType) // تومان
// 🎁 free offer
if (offerType === 'free' && user.monthly_free_offer > 0) {
@@ -894,9 +908,39 @@ const initiateOfferPaymentWeb = async (req, res) => {
})
}
const amount = Number(price) * 10 // ✅ ریال – بدون ×10
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(userId, price, useWallet)
// کاملاً از کیف پول پوشش داده شد — اصلاً نیازی به درگاه نیست
if (fullyCovered) {
const payment = await PaymentModel.create({
amount: price,
status: 'successful',
authority: null,
user_id: userId,
type: 'offer'
})
const receiverShare = Math.round(price * OFFER_COMMISSION_RATE)
const offer = await OfferModel.create({
sender: userId,
receiver: receiverId,
transaction_id: payment._id,
pendingCommissionAmount: receiverShare,
walletAmountUsed: walletAmount
})
await debitWalletForPurchase(userId, walletAmount, {
referenceType: 'offer',
referenceId: offer._id,
description: 'پرداخت آفر از کیف پول'
})
return res.json({ paidFromWallet: true, offerId: offer._id })
}
const amount = toRial(gatewayAmount)
const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : ''
const callbackUrl = `https://api.modstagram.com/api/v1/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}${platformSuffix}`
const callbackUrl = `https://api.modstagram.com/api/v1/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}&walletAmount=${walletAmount}${platformSuffix}`
const zarinpalRes = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/request.json',
@@ -942,16 +986,21 @@ const handleOfferPaymentCallback = async (req, res) => {
const isAndroid = req.query.platform === 'android'
try {
const { Authority, Status, receiverId, offerType, userId } = req.query
const { Authority, Status, receiverId, offerType, userId, walletAmount } = req.query
const receiverUserForFail = receiverId ? await UserModel.findById(receiverId).select('user_name') : null
const failUsername = receiverUserForFail?.user_name || ''
const failParams = `receiverId=${receiverId || ''}&offerType=${offerType || ''}&username=${encodeURIComponent(failUsername)}`
if (Status !== 'OK') {
return isAndroid
? res.redirect('modstagram://FailedPay?type=offer')
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`)
? res.redirect(`modstagram://FailedPay?type=offer&${failParams}`)
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed?userId=${receiverId || ''}&offerType=${offerType || ''}`)
}
const price = await getOfferPriceFromDatabase(offerType) // ریال
const amount = Number(price) * 10
const offerPriceToman = await getOfferPriceFromDatabase(offerType) // تومان
const usedWalletAmount = Number(walletAmount) || 0
const gatewayAmountToman = offerPriceToman - usedWalletAmount
const amount = toRial(gatewayAmountToman)
const verify = await axios.post(
'https://api.zarinpal.com/pg/v4/payment/verify.json',
@@ -967,7 +1016,7 @@ const handleOfferPaymentCallback = async (req, res) => {
console.log('🔔payment:', data.code)
if (data.code === 100) {
const payment = await PaymentModel.create({
amount: (amount / 10),
amount: gatewayAmountToman,
status: 'successful',
authority: Authority,
ref_id: data.ref_id,
@@ -977,7 +1026,6 @@ const handleOfferPaymentCallback = async (req, res) => {
console.log('🔔payment:', payment)
const offerPriceToman = amount / 10
const receiverShare = Math.round(offerPriceToman * OFFER_COMMISSION_RATE)
// سهم گیرنده فقط با پاسخ‌گویی ظرف ۱۲ ساعت به اولین پیام دایرکت واریز می‌شود
@@ -986,7 +1034,16 @@ const handleOfferPaymentCallback = async (req, res) => {
sender: userId,
receiver: receiverId,
transaction_id: payment._id,
pendingCommissionAmount: receiverShare
pendingCommissionAmount: receiverShare,
walletAmountUsed: usedWalletAmount
})
// درگاه فقط بخشِ باقی‌مونده رو تایید کرد؛ حالا که پرداخت قطعاً موفق شد، سهمِ کیف
// پول هم واقعاً کسر می‌شه (نگاه کن به utils/walletSplit.js)
await debitWalletForPurchase(userId, usedWalletAmount, {
referenceType: 'offer',
referenceId: offer._id,
description: 'پرداخت آفر از کیف پول'
})
// صفحه/دیپ‌لینکِ موفقیت برای دکمه‌ی «ارسال پیام» به شناسه (و نام‌کاربریِ) گیرنده
@@ -1005,13 +1062,14 @@ const handleOfferPaymentCallback = async (req, res) => {
}
return isAndroid
? res.redirect('modstagram://FailedPay?type=offer')
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`)
? res.redirect(`modstagram://FailedPay?type=offer&${failParams}`)
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed?userId=${receiverId || ''}&offerType=${offerType || ''}`)
} catch (err) {
console.error(err)
const { receiverId, offerType } = req.query
return isAndroid
? res.redirect('modstagram://FailedPay?type=offer')
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed`)
? res.redirect(`modstagram://FailedPay?type=offer&receiverId=${receiverId || ''}&offerType=${offerType || ''}`)
: res.redirect(`${process.env.APP_SITE}/offer/payment/failed?userId=${receiverId || ''}&offerType=${offerType || ''}`)
}
}

View File

@@ -3,6 +3,7 @@ const OrderModel = require('../../../models/OrderModel')
const PaymentModel = require('../../../models/PaymentModel')
const ShopModel = require('../../../models/ShopModel')
const { notifyUser } = require('../../../utils/notifyUser')
const { resolveWalletSplit, debitWalletForPurchase } = require('../../../utils/walletSplit')
const MERCHANT_ID = process.env.ZARINPAL_MERCHANT_ID || process.env.MERCHENT_CODE
@@ -13,7 +14,7 @@ function toRial (tomanAmount) {
const initiateOrderPayment = async (req, res) => {
try {
const { orderId } = req.body
const { orderId, useWallet } = req.body
if (!orderId) {
return res.status(422).json({ error: true, message: 'سفارش مشخص نشده است' })
}
@@ -29,6 +30,42 @@ const initiateOrderPayment = async (req, res) => {
return res.status(400).json({ error: true, message: 'این سفارش قابل پرداخت نیست' })
}
const { walletAmount, gatewayAmount, fullyCovered } = await resolveWalletSplit(
req.user._id,
order.total_amount,
useWallet
)
// کاملاً از کیف پول پوشش داده شد — اصلاً نیازی به درگاه نیست
if (fullyCovered) {
await debitWalletForPurchase(req.user._id, walletAmount, {
referenceType: 'order',
referenceId: order._id,
description: `پرداخت سفارش شماره ${order._id} از کیف پول`
})
order.walletAmountUsed = walletAmount
order.status = 'processing'
await order.save()
await PaymentModel.create({
amount: order.total_amount,
status: 'successful',
authority: null,
user_id: order.buyer,
order_id: order._id,
type: 'shop_order'
})
const shop = await ShopModel.findById(order.shop)
if (shop) {
await notifyUser(req.app.get('io'), shop.owner, {
project_post_id: order._id,
type: 'shop_order',
title: 'سفارش جدید',
description: `یک سفارش جدید برای فروشگاه ${shop.name} ثبت و پرداخت شد.`
})
}
return res.json({ paidFromWallet: true })
}
const callbackBase = process.env.APP_SITE_CallBack || process.env.APP_URL
if (!callbackBase) {
return res.status(500).json({ error: true, message: 'آدرس بازگشت پرداخت تنظیم نشده است' })
@@ -38,7 +75,7 @@ const initiateOrderPayment = async (req, res) => {
'https://api.zarinpal.com/pg/v4/payment/request.json',
{
merchant_id: MERCHANT_ID,
amount: toRial(order.total_amount),
amount: toRial(gatewayAmount),
description: `پرداخت سفارش شماره ${order._id}`,
callback_url: `${callbackBase}/payment/shop-order/callback?orderId=${order._id}`
},
@@ -48,6 +85,9 @@ const initiateOrderPayment = async (req, res) => {
const result = response.data?.data
if (result?.code === 100 && result?.authority) {
order.paymentAuthority = result.authority
// فقط «قصدِ» استفاده از این مقدار کیف پول رو یادداشت می‌کنه — کسرِ واقعی توی
// کال‌بکِ موفقیت انجام می‌شه (نگاه کن به بالای فایل: utils/walletSplit.js)
order.walletAmountUsed = walletAmount
await order.save()
return res.json({ authority: result.authority })
}
@@ -77,8 +117,15 @@ const handleOrderPaymentCallback = async (req, res) => {
return res.redirect(failRedirect)
}
// این مقدار توی initiateOrderPayment فقط «یادداشت» شده بود (نگاه کن به utils/
// walletSplit.js) — کیف پول تا همین لحظه دست‌نخورده مونده، پس اگه پرداختِ درگاه
// ناموفق/رهاشده باشه، چیزی برای برگردوندن وجود نداره
const walletAmount = order.walletAmountUsed || 0
const gatewayAmount = order.total_amount - walletAmount
if (Status !== 'OK') {
order.status = 'failed'
order.walletAmountUsed = 0
await order.save()
await PaymentModel.create({
amount: order.total_amount,
@@ -102,15 +149,23 @@ const handleOrderPaymentCallback = async (req, res) => {
{
merchant_id: MERCHANT_ID,
authority: Authority,
amount: toRial(order.total_amount)
amount: toRial(gatewayAmount)
},
{ headers: { 'Content-Type': 'application/json' } }
)
const data = verify.data?.data
if (data?.code === 100 || data?.code === 101) {
// درگاه فقط بخشِ باقی‌مونده رو تایید کرد؛ حالا که پرداخت قطعاً موفق شد، سهمِ
// کیف پول هم واقعاً کسر می‌شه
await debitWalletForPurchase(order.buyer, walletAmount, {
referenceType: 'order',
referenceId: order._id,
description: `پرداخت سفارش شماره ${order._id} از کیف پول`
})
await PaymentModel.create({
amount: order.total_amount,
amount: gatewayAmount,
status: 'successful',
authority: Authority,
user_id: order.buyer,
@@ -135,6 +190,7 @@ const handleOrderPaymentCallback = async (req, res) => {
}
order.status = 'failed'
order.walletAmountUsed = 0
await order.save()
await PaymentModel.create({
amount: order.total_amount,

View File

@@ -72,9 +72,25 @@ const registerUser = async (req, res, next) => {
const otp = generateOTP()
const otpSentAt = new Date()
const setFields = { mobile, otp: String(otp), otpSentAt }
// سیستم معرفی به دوستان: کدِ معرفی فقط همین یک‌بار (اولین باری که این شماره اصلاً
// ثبت‌نام می‌شه) ثبت می‌شه — اگه existingUser از قبل referred_by داشته باشه (مثلاً
// کاربر OTP رو دوباره درخواست کرده)، دست‌نخورده می‌مونه؛ پاداشِ واقعی فقط با
// تکمیلِ ثبت‌نام (setUserType) پرداخت می‌شه، نه همین‌جا
const referralCode = typeof req.body.referral_code === 'string'
? req.body.referral_code.trim()
: null
if (referralCode && !existingUser?.referred_by) {
const referrer = await UserModel.findOne({ referral_code: referralCode }).select('_id')
if (referrer && String(referrer._id) !== String(existingUser?._id)) {
setFields.referred_by = referrer._id
}
}
await UserModel.findOneAndUpdate(
{ mobile },
{ $set: { mobile, otp: String(otp), otpSentAt } },
{ $set: setFields },
{ upsert: true, new: true, setDefaultsOnInsert: true }
)

View File

@@ -1,7 +1,10 @@
/* eslint-disable camelcase */
const UserModel = require('../../../models/UserModel')
const WalletTransactionModel = require('../../../models/WalletTransactionModel')
const jwt = require('jsonwebtoken')
const REFERRAL_REWARD_AMOUNT = 10000
const setUserType = async (req, res, next) => {
try {
const { user_type } = req.body
@@ -29,6 +32,25 @@ const setUserType = async (req, res, next) => {
// همه کاربران با نوع «user» ثبت می‌شوند
user.user_type = normalizedType
// سیستم معرفی به دوستان: اینجا آخرین قدمِ ثبت‌نامه (طبقِ authSteps.js —
// getRegistrationStep فقط وقتی user_type ست بشه «complete» می‌شه)، پس دقیقاً
// همین لحظه‌ست که «این کاربر واقعاً ثبت‌نام کرد» معنی پیدا می‌کنه، نه صرفِ وارد
// کردنِ شماره موبایل (که ممکنه هیچ‌وقت تکمیل نشه). referral_rewarded جلوی
// پرداختِ دوباره رو می‌گیره اگه این مسیر به هر دلیلی دوباره صدا زده بشه
if (user.referred_by && !user.referral_rewarded) {
await WalletTransactionModel.create({
user: user.referred_by,
bucket: 'gift',
type: 'credit',
amount: REFERRAL_REWARD_AMOUNT,
referenceType: 'payment',
referenceId: user._id,
description: 'پاداش معرفی کاربر جدید به مدستاگرام'
})
user.referral_rewarded = true
}
await user.save()
return res.json({

View File

@@ -278,7 +278,7 @@ async function getRankedExplorePosts ({
const userIds = [...new Set(posts.map((p) => String(p.user_id)))]
const users = await UserModel.find({ _id: { $in: userIds } })
.select(
'_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate blocked_users'
'_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate blocked_users cooperation_type cooperation_abroad lat lng'
)
.lean()
@@ -442,7 +442,7 @@ async function getPredictedTrendPosts ({
const userIds = [...new Set(scored.map((p) => String(p.user_id)))]
const users = await UserModel.find({ _id: { $in: userIds } })
.select(
'_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate blocked_users'
'_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate blocked_users cooperation_type cooperation_abroad lat lng'
)
.lean()
@@ -1313,7 +1313,7 @@ const getPostsWeb = async (req, res, next) => {
const userIds = [...new Set(posts.map((p) => String(p.user_id)))]
const users = await UserModel.find({ _id: { $in: userIds } })
.select(
'_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate lat lng blocked_users'
'_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate lat lng blocked_users cooperation_type cooperation_abroad'
)
.lean()
@@ -1338,7 +1338,7 @@ const getPostsWeb = async (req, res, next) => {
.lean()
const userIds = [...new Set(explorePosts.map((p) => String(p.user_id)))]
const usersMap = await UserModel.find({ _id: { $in: userIds } })
.select('_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate blocked_users')
.select('_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate blocked_users cooperation_type cooperation_abroad lat lng')
.lean()
const usersById = Object.fromEntries(usersMap.map((u) => [String(u._id), u]))
@@ -1422,7 +1422,7 @@ const getPostsWeb = async (req, res, next) => {
}
}
const userSelectFields = '_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate pinned_post_ids'
const userSelectFields = '_id user_name first_name last_name expertise sub_expertise province city user_level show_location profile_image is_verified verify_badge user_score rate pinned_post_ids cooperation_type cooperation_abroad lat lng'
let users = await UserModel.find(userFilter)
.select(userSelectFields)
.lean()

Some files were not shown because too many files have changed in this diff Show More