diff --git a/_dead_code_quarantine/academy-duplicate-backend/academyCategoryController.js b/_dead_code_quarantine/academy-duplicate-backend/academyCategoryController.js deleted file mode 100644 index c33d87b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/academyCategoryController.js +++ /dev/null @@ -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 }); - } -}; diff --git a/_dead_code_quarantine/academy-duplicate-backend/helpers/chatHelpers.js b/_dead_code_quarantine/academy-duplicate-backend/helpers/chatHelpers.js deleted file mode 100644 index 827087e..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/helpers/chatHelpers.js +++ /dev/null @@ -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 -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/helpers/expiredMessages.js b/_dead_code_quarantine/academy-duplicate-backend/helpers/expiredMessages.js deleted file mode 100644 index e0dbd62..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/helpers/expiredMessages.js +++ /dev/null @@ -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 -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/helpers/viewOnceMessages.js b/_dead_code_quarantine/academy-duplicate-backend/helpers/viewOnceMessages.js deleted file mode 100644 index b94562b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/helpers/viewOnceMessages.js +++ /dev/null @@ -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 -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/index.js b/_dead_code_quarantine/academy-duplicate-backend/index.js deleted file mode 100644 index b85671f..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/index.js +++ /dev/null @@ -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}`) - }) -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/logs/sms-irs/13-03-2024.log b/_dead_code_quarantine/academy-duplicate-backend/logs/sms-irs/13-03-2024.log deleted file mode 100644 index 16b1f43..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/logs/sms-irs/13-03-2024.log +++ /dev/null @@ -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"} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/404.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/404.js deleted file mode 100644 index 8c2cb19..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/404.js +++ /dev/null @@ -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!' - }) - }) -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/adminAuth.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/adminAuth.js deleted file mode 100644 index fe58f18..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/adminAuth.js +++ /dev/null @@ -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 - }) - } -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/auth.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/auth.js deleted file mode 100644 index 0dcfce0..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/auth.js +++ /dev/null @@ -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() -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/blockCheck.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/blockCheck.js deleted file mode 100644 index 357a056..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/blockCheck.js +++ /dev/null @@ -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' - }) - } -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/exception.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/exception.js deleted file mode 100644 index 9f75b03..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/exception.js +++ /dev/null @@ -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: 'خطایی در عملیات مورد نظر رخ داده است.' - }) - }) -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/index.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/index.js deleted file mode 100644 index 8c61bdd..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/index.js +++ /dev/null @@ -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) -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/isHalfRegister.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/isHalfRegister.js deleted file mode 100644 index 72c3ec3..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/isHalfRegister.js +++ /dev/null @@ -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' - }) - } -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/isRegister.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/isRegister.js deleted file mode 100644 index 262872b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/isRegister.js +++ /dev/null @@ -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' - }) - } -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/lastOnline.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/lastOnline.js deleted file mode 100644 index 02e6532..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/lastOnline.js +++ /dev/null @@ -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() -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/middlewares/upload.js b/_dead_code_quarantine/academy-duplicate-backend/middlewares/upload.js deleted file mode 100644 index f95eb44..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/middlewares/upload.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyCategoryModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AcademyCategoryModel.js deleted file mode 100644 index 7f55200..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyCategoryModel.js +++ /dev/null @@ -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); \ No newline at end of file diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyContentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AcademyContentModel.js deleted file mode 100644 index f96c2af..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyContentModel.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyLikeModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AcademyLikeModel.js deleted file mode 100644 index bfc2048..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyLikeModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AcademyModel.js deleted file mode 100644 index 9a86d18..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyPaymentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AcademyPaymentModel.js deleted file mode 100644 index 28e1923..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AcademyPaymentModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdminModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdminModel.js deleted file mode 100644 index 83a358b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdminModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingCategoryModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingCategoryModel.js deleted file mode 100644 index 582a963..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingCategoryModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingCommentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingCommentModel.js deleted file mode 100644 index 6eb4640..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingCommentModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingFeaturesModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingFeaturesModel.js deleted file mode 100644 index 045b3b2..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingFeaturesModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingLikeModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingLikeModel.js deleted file mode 100644 index 752e97c..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingLikeModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingModel.js deleted file mode 100644 index bac1130..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingProfile.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingProfile.js deleted file mode 100644 index 435a2bf..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingProfile.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingRatingModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingRatingModel.js deleted file mode 100644 index 150059e..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingRatingModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingTypeModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingTypeModel.js deleted file mode 100644 index c860da5..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/AdvertisingTypeModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/CommentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/CommentModel.js deleted file mode 100644 index 30f2cc8..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/CommentModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/CourseComentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/CourseComentModel.js deleted file mode 100644 index 9f541ed..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/CourseComentModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/CourseModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/CourseModel.js deleted file mode 100644 index be3c723..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/CourseModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/CoursePaymentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/CoursePaymentModel.js deleted file mode 100644 index 7ddc068..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/CoursePaymentModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/ExpertiseModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/ExpertiseModel.js deleted file mode 100644 index fbf0076..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/ExpertiseModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/ExploreInteractionModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/ExploreInteractionModel.js deleted file mode 100644 index 09ebb88..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/ExploreInteractionModel.js +++ /dev/null @@ -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) diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/IsPaymentCourseModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/IsPaymentCourseModel.js deleted file mode 100644 index b467f87..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/IsPaymentCourseModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/LikeModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/LikeModel.js deleted file mode 100644 index 3751511..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/LikeModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/MessageModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/MessageModel.js deleted file mode 100644 index 5d344e1..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/MessageModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/NotificationModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/NotificationModel.js deleted file mode 100644 index 727b797..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/NotificationModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/OfferModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/OfferModel.js deleted file mode 100644 index f36d940..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/OfferModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/OfferTypeModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/OfferTypeModel.js deleted file mode 100644 index f765a8f..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/OfferTypeModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/PaymentModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/PaymentModel.js deleted file mode 100644 index 41e4e51..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/PaymentModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/PostModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/PostModel.js deleted file mode 100644 index 784547e..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/PostModel.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/ProjectModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/ProjectModel.js deleted file mode 100644 index fc5b38b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/ProjectModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/RequestModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/RequestModel.js deleted file mode 100644 index cb3193a..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/RequestModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/SettingsModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/SettingsModel.js deleted file mode 100644 index 3463eb0..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/SettingsModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/StateCity.js b/_dead_code_quarantine/academy-duplicate-backend/models/StateCity.js deleted file mode 100644 index cbf3ae2..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/StateCity.js +++ /dev/null @@ -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 } diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/StoryModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/StoryModel.js deleted file mode 100644 index e7b1294..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/StoryModel.js +++ /dev/null @@ -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) diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/TaxModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/TaxModel.js deleted file mode 100644 index 86c0bf4..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/TaxModel.js +++ /dev/null @@ -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; diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/TicketMessageModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/TicketMessageModel.js deleted file mode 100644 index 84331ee..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/TicketMessageModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/TicketModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/TicketModel.js deleted file mode 100644 index ba81f97..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/TicketModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/TypeAndPriceModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/TypeAndPriceModel.js deleted file mode 100644 index b922d1d..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/TypeAndPriceModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/UserModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/UserModel.js deleted file mode 100644 index baeffdc..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/UserModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/VersionModel.js b/_dead_code_quarantine/academy-duplicate-backend/models/VersionModel.js deleted file mode 100644 index 4fc532f..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/VersionModel.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/models/license.js b/_dead_code_quarantine/academy-duplicate-backend/models/license.js deleted file mode 100644 index 3d3733e..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/models/license.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/offerController.js b/_dead_code_quarantine/academy-duplicate-backend/offerController.js deleted file mode 100644 index 9fab920..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/offerController.js +++ /dev/null @@ -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 -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/academy/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/academy/index.js deleted file mode 100644 index 78a52d4..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/academy/index.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/advertising/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/advertising/index.js deleted file mode 100644 index 16cbd61..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/advertising/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/city/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/city/index.js deleted file mode 100644 index ddd6824..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/city/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/expertise/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/expertise/index.js deleted file mode 100644 index 7d83308..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/expertise/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/financial/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/financial/index.js deleted file mode 100644 index b94a642..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/financial/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/login/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/login/index.js deleted file mode 100644 index 34d932e..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/login/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/messages/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/messages/index.js deleted file mode 100644 index 4fdebd4..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/messages/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/notification/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/notification/index.js deleted file mode 100644 index 59dee4e..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/notification/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/offers/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/offers/index.js deleted file mode 100644 index 5928629..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/offers/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/posts/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/posts/index.js deleted file mode 100644 index 52f4e51..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/posts/index.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/profile/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/profile/index.js deleted file mode 100644 index f640227..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/profile/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/projects/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/projects/index.js deleted file mode 100644 index 6b2f418..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/projects/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/provinces/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/provinces/index.js deleted file mode 100644 index a9b674f..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/provinces/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/register/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/register/index.js deleted file mode 100644 index 7243e8b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/register/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/search-web/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/search-web/index.js deleted file mode 100644 index 52522eb..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/search-web/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/search/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/search/index.js deleted file mode 100644 index fd299ad..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/search/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/settings/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/settings/index.js deleted file mode 100644 index c00fa6d..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/settings/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/stories/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/stories/index.js deleted file mode 100644 index 701a745..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/stories/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/tickets/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/tickets/index.js deleted file mode 100644 index 564f8d3..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/tickets/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/users/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/users/index.js deleted file mode 100644 index a4c75f2..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/users/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/verify/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/verify/index.js deleted file mode 100644 index 1b9fc5d..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/verify/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/version/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/version/index.js deleted file mode 100644 index 3b5fc6d..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/version/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/application/workroom/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/application/workroom/index.js deleted file mode 100644 index 3e6c3ea..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/application/workroom/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/index.js deleted file mode 100644 index 825f95b..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/index.js +++ /dev/null @@ -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) -} diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/advertising/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/advertising/index.js deleted file mode 100644 index 478f393..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/advertising/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/comments/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/comments/index.js deleted file mode 100644 index 233f4d5..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/comments/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/expertise/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/expertise/index.js deleted file mode 100644 index 73324bd..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/expertise/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/financial/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/financial/index.js deleted file mode 100644 index b769f15..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/financial/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/login/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/login/index.js deleted file mode 100644 index 82701bc..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/login/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/posts/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/posts/index.js deleted file mode 100644 index befcd23..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/posts/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/projects/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/projects/index.js deleted file mode 100644 index d2af05f..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/projects/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/settings/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/settings/index.js deleted file mode 100644 index dc41e43..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/settings/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/shops/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/shops/index.js deleted file mode 100644 index b1c8f51..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/shops/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/tickets/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/tickets/index.js deleted file mode 100644 index 09c5fd3..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/tickets/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/users/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/users/index.js deleted file mode 100644 index 4403e54..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/users/index.js +++ /dev/null @@ -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 diff --git a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/version/index.js b/_dead_code_quarantine/academy-duplicate-backend/routes/panel/version/index.js deleted file mode 100644 index 9e42491..0000000 --- a/_dead_code_quarantine/academy-duplicate-backend/routes/panel/version/index.js +++ /dev/null @@ -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 diff --git a/controllers/application/gifts/giftController.js b/controllers/application/gifts/giftController.js index d8ae9ac..b48b3e8 100644 --- a/controllers/application/gifts/giftController.js +++ b/controllers/application/gifts/giftController.js @@ -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 || '')}` ) diff --git a/controllers/application/payment/paymentController.js b/controllers/application/payment/paymentController.js index ec5b435..c7bde36 100644 --- a/controllers/application/payment/paymentController.js +++ b/controllers/application/payment/paymentController.js @@ -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( diff --git a/controllers/application/payment/paymentControllerWeb.js b/controllers/application/payment/paymentControllerWeb.js index 0f061fb..4691fcd 100644 --- a/controllers/application/payment/paymentControllerWeb.js +++ b/controllers/application/payment/paymentControllerWeb.js @@ -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 || ''}`) } } diff --git a/controllers/application/payment/shopOrderPaymentController.js b/controllers/application/payment/shopOrderPaymentController.js index 308e1b6..a8eacd7 100644 --- a/controllers/application/payment/shopOrderPaymentController.js +++ b/controllers/application/payment/shopOrderPaymentController.js @@ -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, diff --git a/controllers/application/register/registerController.js b/controllers/application/register/registerController.js index 93b81f1..096266d 100644 --- a/controllers/application/register/registerController.js +++ b/controllers/application/register/registerController.js @@ -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 } ) diff --git a/controllers/application/register/userTypeController.js b/controllers/application/register/userTypeController.js index c3562a2..353e917 100644 --- a/controllers/application/register/userTypeController.js +++ b/controllers/application/register/userTypeController.js @@ -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({ diff --git a/controllers/application/users/getUserController.js b/controllers/application/users/getUserController.js index 3a378f1..45c60e0 100644 --- a/controllers/application/users/getUserController.js +++ b/controllers/application/users/getUserController.js @@ -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() diff --git a/controllers/application/users/referralController.js b/controllers/application/users/referralController.js new file mode 100644 index 0000000..4355e2e --- /dev/null +++ b/controllers/application/users/referralController.js @@ -0,0 +1,54 @@ +const crypto = require('crypto') +const UserModel = require('../../../models/UserModel') + +// بدون حروف/ارقامِ به‌هم‌شبیه (0/O، 1/I/L) — چون این کد قراره دستی هم کپی/تایپ بشه +const CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789' +const CODE_LENGTH = 8 +const MAX_ATTEMPTS = 5 + +function generateCode () { + const bytes = crypto.randomBytes(CODE_LENGTH) + let code = '' + for (let i = 0; i < CODE_LENGTH; i++) { + code += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length] + } + return code +} + +/** + * کدِ معرفیِ کاربرِ لاگین‌شده رو برمی‌گردونه؛ اگه هنوز نداشته باشه (referral_code + * روی همه‌ی کاربرهای قدیمی null هست چون این فیلد قبلاً مرده بود) با retry-on-collision + * یکی می‌سازه. لینکِ نهاییِ اشتراک‌گذاری رو فرانت (وب/اندروید) با همین کد می‌سازه: + * https://modstagram.com/users/?ref= + */ +const getReferralCode = async (req, res, next) => { + try { + const userId = req.user._id + const user = await UserModel.findById(userId).select('referral_code') + if (!user) return res.status(404).json({ error: true, message: 'کاربر یافت نشد' }) + + if (user.referral_code) { + return res.json({ referral_code: user.referral_code }) + } + + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + const code = generateCode() + try { + user.referral_code = code + await user.save() + return res.json({ referral_code: code }) + } catch (error) { + // E11000 duplicate key — کد به‌طور اتفاقی قبلاً برای یه کاربر دیگه ساخته شده؛ + // بی‌نهایت‌بعیده (32^8 حالت) ولی retry ساده و بی‌ضرره + if (error?.code === 11000) continue + throw error + } + } + + return res.status(500).json({ error: true, message: 'ساخت کد معرفی ناموفق بود، دوباره تلاش کنید' }) + } catch (error) { + next(error) + } +} + +module.exports = { getReferralCode } diff --git a/controllers/application/wallet/walletController.js b/controllers/application/wallet/walletController.js index 5aba30f..0726332 100644 --- a/controllers/application/wallet/walletController.js +++ b/controllers/application/wallet/walletController.js @@ -193,7 +193,7 @@ const requestWithdrawal = async (req, res, next) => { const initiateWalletCharge = async (req, res, next) => { try { - const { amount } = req.body + const { amount, platform } = req.body const chargeAmount = Number(amount) if (!Number.isFinite(chargeAmount) || chargeAmount <= 0) { return walletError(res, 422, 'مبلغ نامعتبر است') @@ -204,13 +204,14 @@ const initiateWalletCharge = async (req, res, next) => { return walletError(res, 500, 'آدرس بازگشت پرداخت تنظیم نشده است') } + const platformSuffix = platform ? `&platform=${encodeURIComponent(platform)}` : '' const response = await axios.post( 'https://api.zarinpal.com/pg/v4/payment/request.json', { merchant_id: MERCHANT_ID, amount: toRial(chargeAmount), description: 'شارژ کیف پول مدستاگرام', - callback_url: `${callbackBase}/wallet/charge/callback?userId=${req.user._id}&amount=${chargeAmount}` + callback_url: `${callbackBase}/wallet/charge/callback?userId=${req.user._id}&amount=${chargeAmount}${platformSuffix}` }, { headers: { 'Content-Type': 'application/json' } } ) @@ -228,9 +229,14 @@ const initiateWalletCharge = async (req, res, next) => { } const handleWalletChargeCallback = async (req, res) => { - const { Authority, Status, userId, amount } = req.query - const failRedirect = `${process.env.APP_SITE}/settings/wallet?charge=failed` - const successRedirect = `${process.env.APP_SITE}/settings/wallet?charge=success` + const { Authority, Status, userId, amount, platform } = req.query + const isAndroid = platform === 'android' + const failRedirect = isAndroid + ? 'modstagram://FailedPay?type=wallet' + : `${process.env.APP_SITE}/settings/wallet?charge=failed` + const successRedirect = isAndroid + ? 'modstagram://SuccessPay?type=wallet' + : `${process.env.APP_SITE}/settings/wallet?charge=success` try { if (Status !== 'OK') return res.redirect(failRedirect) diff --git a/index.js b/index.js index ad33434..c934870 100644 --- a/index.js +++ b/index.js @@ -129,6 +129,19 @@ app.use(express.urlencoded({ limit: '1000mb', extended: true })); // عوض می‌کنه؛ بدونِ immutable مرورگر با ETag/Last-Modified همچنان رواعتبارسنجی می‌کنه const STATIC_MEDIA_OPTIONS = { maxAge: '365d' }; +// چت‌ها (DM، اتاق گروهی، چتِ فروشگاه) می‌تونن فایل‌های حذف‌شدنی/زمان‌دار/یکبارمصرف +// داشته باشن — کشِ ۳۶۵روزه‌ی بالا باعث می‌شد بعد از حذفِ واقعیِ فایل روی سرور، همون +// بایت‌ها همچنان از کشِ مرورگر/شبکه/پراکسی قابل‌سرو بودن، یعنی «حذف/یکبارمصرف/ +// زمان‌دار» فقط توی دیتابیس واقعی بود نه توی چیزی که کاربر واقعاً می‌دید. برای این +// سه مسیر کش کاملاً غیرفعاله +const EPHEMERAL_MEDIA_OPTIONS = { + maxAge: 0, + cacheControl: false, + setHeaders: (res) => { + res.setHeader('Cache-Control', 'no-store, must-revalidate'); + }, +}; + // مسیرهای مشخص قبل از /storage عمومی — پروفایل از هر دو محل (قدیم و جدید) سرو می‌شود app.use('/storage/profiles', express.static(path.join(__dirname, '../storage/profiles'), STATIC_MEDIA_OPTIONS)); app.use('/storage/profiles', express.static(path.join(__dirname, 'storage/profiles'), STATIC_MEDIA_OPTIONS)); @@ -136,12 +149,12 @@ app.use('/storage/carts', express.static(path.join(__dirname, '../storage/carts' app.use('/storage/carts', express.static(path.join(__dirname, 'storage/carts'), STATIC_MEDIA_OPTIONS)); app.use('/storage/posts', express.static(path.join(__dirname, 'storage/posts'), STATIC_MEDIA_OPTIONS)); app.use('/storage/posts', express.static(path.join(__dirname, '../storage/posts'), STATIC_MEDIA_OPTIONS)); -app.use('/storage/messages', express.static(path.join(__dirname, '../storage/messages'), STATIC_MEDIA_OPTIONS)); -app.use('/storage/messages', express.static(path.join(__dirname, 'storage/messages'), STATIC_MEDIA_OPTIONS)); -app.use('/storage/shop-chat', express.static(path.join(__dirname, '../storage/shop-chat'), STATIC_MEDIA_OPTIONS)); -app.use('/storage/shop-chat', express.static(path.join(__dirname, 'storage/shop-chat'), STATIC_MEDIA_OPTIONS)); -app.use('/storage/chat-rooms', express.static(path.join(__dirname, '../storage/chat-rooms'), STATIC_MEDIA_OPTIONS)); -app.use('/storage/chat-rooms', express.static(path.join(__dirname, 'storage/chat-rooms'), STATIC_MEDIA_OPTIONS)); +app.use('/storage/messages', express.static(path.join(__dirname, '../storage/messages'), EPHEMERAL_MEDIA_OPTIONS)); +app.use('/storage/messages', express.static(path.join(__dirname, 'storage/messages'), EPHEMERAL_MEDIA_OPTIONS)); +app.use('/storage/shop-chat', express.static(path.join(__dirname, '../storage/shop-chat'), EPHEMERAL_MEDIA_OPTIONS)); +app.use('/storage/shop-chat', express.static(path.join(__dirname, 'storage/shop-chat'), EPHEMERAL_MEDIA_OPTIONS)); +app.use('/storage/chat-rooms', express.static(path.join(__dirname, '../storage/chat-rooms'), EPHEMERAL_MEDIA_OPTIONS)); +app.use('/storage/chat-rooms', express.static(path.join(__dirname, 'storage/chat-rooms'), EPHEMERAL_MEDIA_OPTIONS)); app.use('/storage/tickets', express.static(path.join(__dirname, '../storage/tickets'), STATIC_MEDIA_OPTIONS)); app.use('/storage/tickets', express.static(path.join(__dirname, 'storage/tickets'), STATIC_MEDIA_OPTIONS)); app.use('/storage/advertising', express.static(path.join(__dirname, '../storage/advertising'), STATIC_MEDIA_OPTIONS)); diff --git a/models/GiftModel.js b/models/GiftModel.js index cf79589..65670c8 100644 --- a/models/GiftModel.js +++ b/models/GiftModel.js @@ -40,7 +40,7 @@ const giftSchema = new mongoose.Schema({ }, paymentMethod: { type: String, - enum: ['online', 'wallet'], + enum: ['online', 'wallet', 'mixed'], default: 'online' }, viaChatMessage: { diff --git a/models/OfferModel.js b/models/OfferModel.js index 28e29c6..ab04dd6 100644 --- a/models/OfferModel.js +++ b/models/OfferModel.js @@ -23,6 +23,12 @@ const offerSchema = new mongoose.Schema({ ref: 'Payment', default: null }, + // مبلغی که فرستنده با تیکِ «استفاده از کیف پول» از سبدِ charged پرداخت کرده (نگاه کن + // به utils/walletSplit.js) — بقیه‌ی مبلغ (اگه مونده باشه) از درگاه گرفته شده + walletAmountUsed: { + type: Number, + default: 0 + }, // سهم ۳۰٪ گیرنده که فقط با پاسخ‌گویی ظرف ۱۲ ساعت به اولین پیام دایرکت واریز می‌شود — pendingCommissionAmount: { type: Number, diff --git a/models/OrderModel.js b/models/OrderModel.js index 903133e..4536556 100644 --- a/models/OrderModel.js +++ b/models/OrderModel.js @@ -107,6 +107,12 @@ const orderSchema = new mongoose.Schema({ type: String, default: null }, + // مبلغی که کاربر با تیکِ «استفاده از کیف پول» قرار بود از سبدِ charged کسر بشه — + // فقط در لحظه‌ی موفقیتِ قطعیِ پرداخت واقعاً کسر می‌شه (نگاه کن به utils/walletSplit.js) + walletAmountUsed: { + type: Number, + default: 0 + }, buyerAddressSnapshot: { type: buyerAddressSnapshotSchema, default: null diff --git a/models/UserModel.js b/models/UserModel.js index 6acab1c..956c423 100644 --- a/models/UserModel.js +++ b/models/UserModel.js @@ -572,13 +572,31 @@ const userSchema = new mongoose.Schema({ maxLength: 512, default: null }, + // کد یکتای معرفی‌کننده — با اولین درخواستِ GET /users/referral-code به‌صورت lazy + // ساخته می‌شه (referralController.js)، نه از قبل برای همه‌ی کاربرها. unique+sparse + // چون این کد توی لینکِ عمومیِ اشتراک‌گذاری میره و باید واقعاً غیرتکراری باشه referral_code: { type: String, trim: true, minLength: 1, maxLength: 512, + default: null, + unique: true, + sparse: true + }, + // کاربری که این کاربر رو با لینکِ معرفی‌اش وارد اپ کرده — فقط توی اولین مرحله‌ی + // ثبت‌نام (POST /register با mobile) ست می‌شه، دیگه هیچ‌جا عوض نمی‌شه + referred_by: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', default: null }, + // جلوگیری از پرداخت دوباره‌ی پاداشِ معرفی — با تکمیلِ ثبت‌نام (userTypeController) + // یک‌بار true می‌شه؛ اگه این مرحله به هر دلیلی دوباره صدا زده بشه، پرداختِ تکراری نمی‌شه + referral_rewarded: { + type: Boolean, + default: false + }, shop_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Shop', @@ -643,6 +661,13 @@ const userSchema = new mongoose.Schema({ type: Date, default: null }, + // تعداد بیلبورد رایگانی که این کاربر تا الان ثبت کرده — سقف کلی (نه روزانه) طبق + // utils/billboardFreeLimit.js، مستقل از daily_free_request که بین پروژه/بیلبورد/ + // درخواست مشترکه + free_billboard_count: { + type: Number, + default: 0 + }, blocked_by: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' diff --git a/routes/application/users/index.js b/routes/application/users/index.js index d4fb464..988f923 100644 --- a/routes/application/users/index.js +++ b/routes/application/users/index.js @@ -2,6 +2,7 @@ const express = require('express') const { getUsers, getSingleUser, blockUser, unblockUser, getComments,createLicense, getAllLicenses,updateLicenseConfirmation, getLicenseByUserId, inviteEmployer, getUserContactInfo, getPostsWeb, getSingleUserWeb, createUserComment, toggleCommentLike, recordExploreInteraction, getPublicProfilesForSeo, getTopHashtags } = require('../../../controllers/application/users/getUserController') const { getFollowStats, getFollowers, getFollowing, getBlockedUsers } = require('../../../controllers/application/users/followController') const { getNearbyUsers } = require('../../../controllers/application/users/nearbyUsersController') +const { getReferralCode } = require('../../../controllers/application/users/referralController') const { searchPartners, getMyPartnerAlert, @@ -35,6 +36,7 @@ router.post('/comments/like', [auth], [isRegister], [blockCheck], toggleCommentL router.post('/explore/interaction', [auth], recordExploreInteraction) router.get('/invite-employer', [auth], [isRegister], [blockCheck], inviteEmployer) router.get('/contact-info', [auth], getUserContactInfo) +router.get('/referral-code', [auth], getReferralCode) router.get('/License', [auth], getAllLicenses) router.get('/License/:userId', [auth, blockCheck], getLicenseByUserId) router.post('/License', [auth, blockCheck], createLicense) diff --git a/utils/billboardFreeLimit.js b/utils/billboardFreeLimit.js new file mode 100644 index 0000000..e9256de --- /dev/null +++ b/utils/billboardFreeLimit.js @@ -0,0 +1,24 @@ +// سقفِ کلیِ (نه روزانه/ماهانه) بیلبورد رایگان — طبق خواسته‌ی صریحِ کاربر: ۲۰۰ تا +// در کلِ عمرِ حساب، مستقل از سهمیه‌ی مشترکِ daily_free_request (که بین پروژه/بیلبورد/ +// درخواست تقسیم می‌شه) و مستقل از سقفِ عمومیِ FREE_LIFETIME_CREATION_LIMIT +// (utils/freeCreationLimit.js) +const BILLBOARD_FREE_LIMIT = 200 + +function canUseFreeBillboard (user) { + const used = user?.free_billboard_count || 0 + return used < BILLBOARD_FREE_LIMIT +} + +async function consumeFreeBillboard (user) { + if (!canUseFreeBillboard(user)) { + return { ok: false, message: `سقف ${BILLBOARD_FREE_LIMIT} بیلبورد رایگان به پایان رسیده است.` } + } + user.free_billboard_count = (user.free_billboard_count || 0) + 1 + return { ok: true } +} + +module.exports = { + BILLBOARD_FREE_LIMIT, + canUseFreeBillboard, + consumeFreeBillboard +} diff --git a/utils/exploreAlgorithm.js b/utils/exploreAlgorithm.js index 10ec5fb..b183086 100644 --- a/utils/exploreAlgorithm.js +++ b/utils/exploreAlgorithm.js @@ -10,6 +10,7 @@ const CourseModel = require('../models/CourseModel') const AcademyCategoryModel = require('../models/AcademyCategoryModel') const AdvertisingModel = require('../models/AdvertisingModel') const AdvertisingCategoryModel = require('../models/AdvertisingCategoryModel') +const { haversineMeters } = require('./nearbyPosts') const EXPERTISE_ALIASES = { مدل: ['مدل', 'مدلینگ'], @@ -252,12 +253,18 @@ async function buildUserExploreSignals(userId) { viewedAuthors: {}, likedPostIds: [], commentedPostIds: [], + viewerCooperationType: null, + viewerCooperationAbroad: null, + viewerLat: null, + viewerLng: null, + viewerProvinceId: null, + viewerCityId: null, isColdStart: true } } const user = await UserModel.findById(userId) - .select('expertise sub_expertise algorithm_reset_at') + .select('expertise sub_expertise algorithm_reset_at cooperation_type cooperation_abroad lat lng province city') .lean() // بعد از «ریست الگوریتم»، لایک/کامنت/آفرِ قبل از این تاریخ نباید توی ترجیحات @@ -362,6 +369,12 @@ async function buildUserExploreSignals(userId) { } }) + // «لوکیشنِ روشن» یعنی کاربر توی آدرس/محل‌سکونتش (verify/edit address — addressController.js) + // مختصاتِ معتبر ثبت کرده؛ توی این بک‌اند هیچ flagِ جداگانه‌ی «اجازه‌ی دسترسی به GPS» + // وجود نداره، پس همون معیارِ عملیِ nearbyPosts.js/reelsTabFeed.js رو تکرار می‌کنیم + const viewerLat = Number.isFinite(parseFloat(user?.lat)) ? parseFloat(user.lat) : null + const viewerLng = Number.isFinite(parseFloat(user?.lng)) ? parseFloat(user.lng) : null + return { userExpertise: user?.expertise || null, userSubExpertise: user?.sub_expertise || [], @@ -372,6 +385,14 @@ async function buildUserExploreSignals(userId) { viewedAuthors, likedPostIds, commentedPostIds: comments.map((c) => String(c.post)), + // طبق خواسته‌ی کاربر: نوع همکاری (وقتی توی «ویرایش نوع همکاری» مشخص می‌شه) و + // لوکیشن/محلِ سکونت باید روی الگوریتم اثر بذارن — پایین‌تر در scorePost استفاده می‌شن + viewerCooperationType: user?.cooperation_type || null, + viewerCooperationAbroad: user?.cooperation_abroad ?? null, + viewerLat, + viewerLng, + viewerProvinceId: user?.province?.id ?? null, + viewerCityId: user?.city?.id ?? null, isColdStart: !hasEnoughUserSignals({ likedPostIds, commentedPostIds: comments.map((c) => String(c.post)), @@ -551,6 +572,42 @@ function scorePost(post, author, signals, options = {}) { score += 16 } + // نوع همکاری: اگه کاربر فقط دنبالِ همکاری با افرادِ تاییدشده‌ست، نویسنده‌های + // تاییدشده کمی جلوتر بیفتن (نه فیلترِ کامل، فقط یه سوگیریِ خفیف توی رتبه‌بندی) + if (signals.viewerCooperationType === 'verified') { + const authorVerified = Boolean(author?.is_verified || author?.verify_badge) + score += authorVerified ? 10 : -10 + } + + // نوع همکاری: اگه کاربر گفته فقط توی همون شهرِ خودش دنبالِ همکاریه + // (cooperation_abroad=false)، محتوایِ هم‌شهری/هم‌استانی‌ها امتیازِ بیشتری بگیرن + if (signals.viewerCooperationAbroad === false && signals.viewerProvinceId) { + const authorProvinceId = author?.province?.id ?? null + const authorCityId = author?.city?.id ?? null + const sameProvince = + authorProvinceId != null && String(authorProvinceId) === String(signals.viewerProvinceId) + const sameCity = + signals.viewerCityId != null && + authorCityId != null && + String(authorCityId) === String(signals.viewerCityId) + if (sameCity) score += 20 + else if (sameProvince) score += 10 + else score -= 8 + } + + // محلِ سکونت: هرچی نویسنده به محلِ سکونتِ کاربر نزدیک‌تر باشه امتیازِ بیشتری + // می‌گیره (افولِ نمایی، شبیهِ interactionDecay) — فقط وقتی هر دو مختصاتِ معتبر + // دارن و نویسنده لوکیشنش رو مخفی نکرده (show_location) + if (signals.viewerLat != null && signals.viewerLng != null && author?.show_location !== false) { + const authorLat = Number.isFinite(parseFloat(author?.lat)) ? parseFloat(author.lat) : null + const authorLng = Number.isFinite(parseFloat(author?.lng)) ? parseFloat(author.lng) : null + if (authorLat != null && authorLng != null) { + const distanceKm = + haversineMeters(signals.viewerLat, signals.viewerLng, authorLat, authorLng) / 1000 + score += 18 * Math.exp(-distanceKm / 150) + } + } + if (post.type && signals.contentTypeWeight[post.type]) { score += signals.contentTypeWeight[post.type] * 2 } diff --git a/utils/offerReplyGate.js b/utils/offerReplyGate.js index 9e28bb7..91ab866 100644 --- a/utils/offerReplyGate.js +++ b/utils/offerReplyGate.js @@ -7,7 +7,7 @@ const OFFER_REPLY_WINDOW_MS = 12 * 60 * 60 * 1000 /** * Called whenever a chat message is sent. If there's a pending paid Offer * between these two users, tracks the requester's first direct message and - * the receiver's reply — the receiver's 30% commission is only credited if + * the receiver's reply — the receiver's 50% commission is only credited if * they reply within 12 hours of that first message. */ async function trackOfferMessageForCommission (io, senderId, receiverId) { @@ -49,7 +49,7 @@ async function trackOfferMessageForCommission (io, senderId, receiverId) { amount: offer.pendingCommissionAmount, referenceType: 'payment', referenceId: offer._id, - description: 'سهم دریافتی از درخواست همکاری (۳۰٪، پس از پاسخ به‌موقع)' + description: 'سهم دریافتی از درخواست همکاری (۵۰٪، پس از پاسخ به‌موقع)' }) if (io) { @@ -57,7 +57,7 @@ async function trackOfferMessageForCommission (io, senderId, receiverId) { io.to(`user:${String(offer.receiver)}`).emit('notification', { type: 'offer_commission', title: 'واریز سهم درخواست همکاری', - description: 'با پاسخ به‌موقع، ۳۰٪ مبلغ درخواست همکاری به کیف پول شما واریز شد.' + description: 'با پاسخ به‌موقع، ۵۰٪ مبلغ درخواست همکاری به کیف پول شما واریز شد.' }) } catch (_) {} } diff --git a/utils/postEngagement.js b/utils/postEngagement.js index 103ea25..7fac3bb 100644 --- a/utils/postEngagement.js +++ b/utils/postEngagement.js @@ -12,7 +12,7 @@ async function attachPostEngagementFields(posts, viewerId) { const postIds = posts.map((p) => p._id) const authorIds = [...new Set(posts.map((p) => String(p.user_id)))] - const [bookmarkAgg, sendAgg, shareAgg, viewAgg, authorDocs, userBookmarks, userFollows, activeStories] = await Promise.all([ + const [bookmarkAgg, sendAgg, shareAgg, viewAgg, offerAgg, authorDocs, userBookmarks, userFollows, activeStories] = await Promise.all([ PostBookmarkModel.aggregate([ { $match: { postId: { $in: postIds } } }, { $group: { _id: '$postId', count: { $sum: 1 } } }, @@ -35,6 +35,16 @@ async function attachPostEngagementFields(posts, viewerId) { }, { $group: { _id: '$targetId', count: { $sum: 1 } } }, ]), + ExploreInteractionModel.aggregate([ + { + $match: { + targetType: 'post', + targetId: { $in: postIds }, + action: 'offer', + }, + }, + { $group: { _id: '$targetId', count: { $sum: 1 } } }, + ]), UserModel.find({ _id: { $in: authorIds } }) .select('_id allow_save_posts') .lean(), @@ -68,6 +78,9 @@ async function attachPostEngagementFields(posts, viewerId) { const viewsMap = Object.fromEntries( viewAgg.map((v) => [String(v._id), v.count]) ) + const offersMap = Object.fromEntries( + offerAgg.map((o) => [String(o._id), o.count]) + ) const allowSaveMap = Object.fromEntries( authorDocs.map((u) => [String(u._id), u.allow_save_posts !== false]) ) @@ -95,6 +108,7 @@ async function attachPostEngagementFields(posts, viewerId) { sendsCount: sendsMap[String(post._id)] || 0, sharesCount: sharesMap[String(post._id)] || 0, viewsCount: viewsMap[String(post._id)] || 0, + offersCount: offersMap[String(post._id)] || 0, author_allow_save: allowSaveMap[String(post.user_id)] !== false, is_saved: savedSet.has(String(post._id)), is_following: followSet.has(String(post.user_id)), diff --git a/utils/walletSplit.js b/utils/walletSplit.js new file mode 100644 index 0000000..c5d61c3 --- /dev/null +++ b/utils/walletSplit.js @@ -0,0 +1,42 @@ +const WalletTransactionModel = require('../models/WalletTransactionModel') +const { getBucketBalance } = require('../controllers/application/wallet/walletController') + +/** + * وقتی کاربر موقع خرید تیکِ «استفاده از موجودی کیف پول» رو می‌زنه (useWallet=true)، + * تا هر مقدار که سبدِ «charged» (شارژِ دستیِ کیف پول، همون چیزی که کاربر خودش واریز + * کرده — نه سبدهای درآمدیِ shop/academy/project/booking) پوشش بده از همونجا کسر + * می‌شه و فقط باقی‌مونده (اگه چیزی مونده باشه) از درگاه زرین‌پال گرفته می‌شه. اگه + * useWallet نباشه یا موجودی صفر باشه، دقیقاً مثلِ قبل کاملش از درگاه پرداخت می‌شه. + * + * عمداً همینجا چیزی کسر نمی‌کنه (فقط محاسبه) — کسرِ واقعی رو خودِ فراخوان، فقط لحظه‌ای + * که پرداخت قطعاً موفق شده (یا کلاً از درگاه رد نشده، یعنی fullyCovered) صدا می‌زنه؛ + * این‌طوری اگه کاربر پرداختِ درگاه رو نصفه ول کنه، هیچ مبلغی از کیف پول کم نشده که + * لازم باشه برگردونده بشه + */ +async function resolveWalletSplit (userId, amount, useWallet) { + if (!useWallet) { + return { walletAmount: 0, gatewayAmount: amount, fullyCovered: false } + } + const balance = await getBucketBalance(userId, 'charged') + if (!balance || balance <= 0) { + return { walletAmount: 0, gatewayAmount: amount, fullyCovered: false } + } + const walletAmount = Math.min(balance, amount) + const gatewayAmount = amount - walletAmount + return { walletAmount, gatewayAmount, fullyCovered: gatewayAmount <= 0 } +} + +async function debitWalletForPurchase (userId, walletAmount, { referenceType, referenceId, description }) { + if (!walletAmount || walletAmount <= 0) return + await WalletTransactionModel.create({ + user: userId, + bucket: 'charged', + type: 'debit', + amount: walletAmount, + referenceType, + referenceId, + description + }) +} + +module.exports = { resolveWalletSplit, debitWalletForPurchase }