Compare commits
3 Commits
d54f5441a3
...
be3a32f26c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3a32f26c | ||
|
|
33e499d43b | ||
|
|
b245e7b71a |
14
.eslintrc.json
Normal file
14
.eslintrc.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"env": {
|
||||
"commonjs": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "standard",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 2]
|
||||
}
|
||||
}
|
||||
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
.vscode/storage/*
|
||||
logs/*
|
||||
node_modules/
|
||||
*.mp4
|
||||
*.mov
|
||||
storage/courses/videos/*.mp4
|
||||
storage/posts/videos/*.mp4
|
||||
*.mp4
|
||||
*.mov
|
||||
*.avi
|
||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install --production
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3002
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
2
boot/index.js
Normal file
2
boot/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const startMongoDB = require('./mongo')
|
||||
startMongoDB()
|
||||
24
boot/mongo.js
Normal file
24
boot/mongo.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// const mongoose = require('mongoose')
|
||||
// const { MONGO_DBNAME, MONGO_HOST, MONGO_PORT } = process.env
|
||||
// mongoose.connection.on('error', (error) => {
|
||||
// console.log('mongodb connection failed! ', error.message)
|
||||
// })
|
||||
// const startMongoDB = () => {
|
||||
// mongoose.connect(`mongodb://${MONGO_HOST}:${MONGO_PORT}/${MONGO_DBNAME}`, {
|
||||
// })
|
||||
// }
|
||||
// module.exports = startMongoDB
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
mongoose.connection.on('error', (error) => {
|
||||
console.log('mongodb connection failed! ', error.message)
|
||||
})
|
||||
mongoose.connection.on('open', function () {
|
||||
console.log('Database Connected! ')
|
||||
})
|
||||
const startMongoDB = () => {
|
||||
mongoose.connect(process.env.DATABASE_URL, {
|
||||
authSource: 'admin'
|
||||
})
|
||||
}
|
||||
module.exports = startMongoDB
|
||||
74
controllers/application/academy/academyCategoryController.js
Normal file
74
controllers/application/academy/academyCategoryController.js
Normal file
@@ -0,0 +1,74 @@
|
||||
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 });
|
||||
}
|
||||
};
|
||||
4430
controllers/application/academy/academyControllers.js
Normal file
4430
controllers/application/academy/academyControllers.js
Normal file
File diff suppressed because it is too large
Load Diff
72
controllers/application/academy/helpers/chatHelpers.js
Normal file
72
controllers/application/academy/helpers/chatHelpers.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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),
|
||||
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
|
||||
}
|
||||
357
controllers/application/academy/index.js
Normal file
357
controllers/application/academy/index.js
Normal file
@@ -0,0 +1,357 @@
|
||||
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': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
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}`)
|
||||
})
|
||||
}
|
||||
18
controllers/application/academy/logs/sms-irs/13-03-2024.log
Normal file
18
controllers/application/academy/logs/sms-irs/13-03-2024.log
Normal file
@@ -0,0 +1,18 @@
|
||||
[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"}
|
||||
9
controllers/application/academy/middlewares/404.js
Normal file
9
controllers/application/academy/middlewares/404.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = (app) => {
|
||||
app.use((req, res, next) => {
|
||||
res.status(404).send({
|
||||
code: 'Not Found',
|
||||
status: 404,
|
||||
message: 'requested resource could not be found!'
|
||||
})
|
||||
})
|
||||
}
|
||||
55
controllers/application/academy/middlewares/adminAuth.js
Normal file
55
controllers/application/academy/middlewares/adminAuth.js
Normal file
@@ -0,0 +1,55 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
21
controllers/application/academy/middlewares/auth.js
Normal file
21
controllers/application/academy/middlewares/auth.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
}
|
||||
47
controllers/application/academy/middlewares/blockCheck.js
Normal file
47
controllers/application/academy/middlewares/blockCheck.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const UserModel = require('../models/UserModel') // مسیر درست به مدل کاربر
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
console.log(2);
|
||||
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.block_status === true) {
|
||||
return res.status(403).send({
|
||||
status: 'error',
|
||||
code: 403,
|
||||
message: 'حساب کاربری شما مسدود شده است، برای اطلاعات بیشتر با پشتیبانی تماس بگیرید!',
|
||||
type: 'block'
|
||||
})
|
||||
}
|
||||
|
||||
req.user = user // کاربر را به درخواست اضافه کنید تا در سایر middleware ها و کنترلرها استفاده شود.
|
||||
next()
|
||||
} catch (error) {
|
||||
console.error('Error in blockCheck middleware:', error)
|
||||
res.status(500).send({
|
||||
status: 'error',
|
||||
code: 500,
|
||||
message: 'Server error'
|
||||
})
|
||||
}
|
||||
}
|
||||
11
controllers/application/academy/middlewares/exception.js
Normal file
11
controllers/application/academy/middlewares/exception.js
Normal file
@@ -0,0 +1,11 @@
|
||||
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: 'خطایی در عملیات مورد نظر رخ داده است.'
|
||||
})
|
||||
})
|
||||
}
|
||||
17
controllers/application/academy/middlewares/index.js
Normal file
17
controllers/application/academy/middlewares/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const bodyParser = require('body-parser')
|
||||
const cors = require('cors')
|
||||
const formData = require('express-form-data')
|
||||
const session = require('express-session')
|
||||
const lastOnline = require('./lastOnline')
|
||||
module.exports = (app) => {
|
||||
app.use(cors()) // develop
|
||||
app.use(formData.parse())
|
||||
app.use(bodyParser.json())
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
app.use(session({
|
||||
secret: 'your_secret_key', // کلید مخفی برای امنیت جلسات
|
||||
resave: false,
|
||||
saveUninitialized: false
|
||||
}))
|
||||
app.use(lastOnline)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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'
|
||||
})
|
||||
}
|
||||
}
|
||||
50
controllers/application/academy/middlewares/isRegister.js
Normal file
50
controllers/application/academy/middlewares/isRegister.js
Normal file
@@ -0,0 +1,50 @@
|
||||
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'
|
||||
})
|
||||
}
|
||||
}
|
||||
21
controllers/application/academy/middlewares/lastOnline.js
Normal file
21
controllers/application/academy/middlewares/lastOnline.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
}
|
||||
24
controllers/application/academy/middlewares/upload.js
Normal file
24
controllers/application/academy/middlewares/upload.js
Normal file
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
190
controllers/application/academy/models/AcademyCategoryModel.js
Normal file
190
controllers/application/academy/models/AcademyCategoryModel.js
Normal file
@@ -0,0 +1,190 @@
|
||||
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);
|
||||
@@ -0,0 +1,71 @@
|
||||
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;
|
||||
25
controllers/application/academy/models/AcademyLikeModel.js
Normal file
25
controllers/application/academy/models/AcademyLikeModel.js
Normal file
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
55
controllers/application/academy/models/AcademyModel.js
Normal file
55
controllers/application/academy/models/AcademyModel.js
Normal file
@@ -0,0 +1,55 @@
|
||||
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;
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
80
controllers/application/academy/models/AdminModel.js
Normal file
80
controllers/application/academy/models/AdminModel.js
Normal file
@@ -0,0 +1,80 @@
|
||||
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
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
@@ -0,0 +1,27 @@
|
||||
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
|
||||
@@ -0,0 +1,20 @@
|
||||
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
|
||||
230
controllers/application/academy/models/AdvertisingModel.js
Normal file
230
controllers/application/academy/models/AdvertisingModel.js
Normal file
@@ -0,0 +1,230 @@
|
||||
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
|
||||
113
controllers/application/academy/models/AdvertisingProfile.js
Normal file
113
controllers/application/academy/models/AdvertisingProfile.js
Normal file
@@ -0,0 +1,113 @@
|
||||
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
|
||||
@@ -0,0 +1,26 @@
|
||||
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
|
||||
@@ -0,0 +1,25 @@
|
||||
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
|
||||
54
controllers/application/academy/models/CommentModel.js
Normal file
54
controllers/application/academy/models/CommentModel.js
Normal file
@@ -0,0 +1,54 @@
|
||||
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: true
|
||||
},
|
||||
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
|
||||
40
controllers/application/academy/models/CourseComentModel.js
Normal file
40
controllers/application/academy/models/CourseComentModel.js
Normal file
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
79
controllers/application/academy/models/CourseModel.js
Normal file
79
controllers/application/academy/models/CourseModel.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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
|
||||
},
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("Course", modelSchema);
|
||||
module.exports = PostModel;
|
||||
22
controllers/application/academy/models/CoursePaymentModel.js
Normal file
22
controllers/application/academy/models/CoursePaymentModel.js
Normal file
@@ -0,0 +1,22 @@
|
||||
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;
|
||||
47
controllers/application/academy/models/ExpertiseModel.js
Normal file
47
controllers/application/academy/models/ExpertiseModel.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
25
controllers/application/academy/models/LikeModel.js
Normal file
25
controllers/application/academy/models/LikeModel.js
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
51
controllers/application/academy/models/MessageModel.js
Normal file
51
controllers/application/academy/models/MessageModel.js
Normal file
@@ -0,0 +1,51 @@
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
const MessageModel = mongoose.model('Message', messageSchema)
|
||||
|
||||
module.exports = MessageModel
|
||||
37
controllers/application/academy/models/NotificationModel.js
Normal file
37
controllers/application/academy/models/NotificationModel.js
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
32
controllers/application/academy/models/OfferModel.js
Normal file
32
controllers/application/academy/models/OfferModel.js
Normal file
@@ -0,0 +1,32 @@
|
||||
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
|
||||
21
controllers/application/academy/models/OfferTypeModel.js
Normal file
21
controllers/application/academy/models/OfferTypeModel.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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
|
||||
46
controllers/application/academy/models/PaymentModel.js
Normal file
46
controllers/application/academy/models/PaymentModel.js
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
60
controllers/application/academy/models/PostModel.js
Normal file
60
controllers/application/academy/models/PostModel.js
Normal file
@@ -0,0 +1,60 @@
|
||||
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;
|
||||
186
controllers/application/academy/models/ProjectModel.js
Normal file
186
controllers/application/academy/models/ProjectModel.js
Normal file
@@ -0,0 +1,186 @@
|
||||
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
|
||||
33
controllers/application/academy/models/RequestModel.js
Normal file
33
controllers/application/academy/models/RequestModel.js
Normal file
@@ -0,0 +1,33 @@
|
||||
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
|
||||
19
controllers/application/academy/models/SettingsModel.js
Normal file
19
controllers/application/academy/models/SettingsModel.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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
|
||||
19
controllers/application/academy/models/StateCity.js
Normal file
19
controllers/application/academy/models/StateCity.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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 }
|
||||
15
controllers/application/academy/models/TaxModel.js
Normal file
15
controllers/application/academy/models/TaxModel.js
Normal file
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
20
controllers/application/academy/models/TicketMessageModel.js
Normal file
20
controllers/application/academy/models/TicketMessageModel.js
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
24
controllers/application/academy/models/TicketModel.js
Normal file
24
controllers/application/academy/models/TicketModel.js
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
21
controllers/application/academy/models/TypeAndPriceModel.js
Normal file
21
controllers/application/academy/models/TypeAndPriceModel.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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
|
||||
381
controllers/application/academy/models/UserModel.js
Normal file
381
controllers/application/academy/models/UserModel.js
Normal file
@@ -0,0 +1,381 @@
|
||||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
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
|
||||
12
controllers/application/academy/models/VersionModel.js
Normal file
12
controllers/application/academy/models/VersionModel.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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
|
||||
14
controllers/application/academy/models/license.js
Normal file
14
controllers/application/academy/models/license.js
Normal file
@@ -0,0 +1,14 @@
|
||||
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
|
||||
1414
controllers/application/advertising/advertisingController.js
Normal file
1414
controllers/application/advertising/advertisingController.js
Normal file
File diff suppressed because it is too large
Load Diff
16
controllers/application/citysController.js
Normal file
16
controllers/application/citysController.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { CityModel } = require('../../models/StateCity')
|
||||
const getCities = async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const cities = await CityModel.find({ province_id: id })
|
||||
|
||||
res.status(200).json({
|
||||
cities
|
||||
})
|
||||
} catch (err) {
|
||||
res.status(500).send('Internal Server Error')
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getCities
|
||||
}
|
||||
13
controllers/application/expertise/expertiseController.js
Normal file
13
controllers/application/expertise/expertiseController.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable camelcase */
|
||||
const ExpertiseModel = require('../../../models/ExpertiseModel')
|
||||
const getExpertise = async (req, res, next) => {
|
||||
try {
|
||||
const expertises = await ExpertiseModel.find().select('expertise sub_expertise')
|
||||
res.json({ expertises })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getExpertise
|
||||
}
|
||||
65
controllers/application/financial/financialController.js
Normal file
65
controllers/application/financial/financialController.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const PaymentModel = require('../../../models/PaymentModel')
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const moment = require('moment-jalaali')
|
||||
|
||||
const getFinancial = async (req, res) => {
|
||||
try {
|
||||
// Extract user ID from JWT token
|
||||
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 options = {
|
||||
page: req.query.page || 1, // صفحه پیشفرض ۱
|
||||
limit: req.query.limit || 10, // حداکثر تعداد موارد برای هر صفحه
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
// Find all payments associated with the user
|
||||
const financial = await PaymentModel.paginate(
|
||||
{ user_id: userId }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
// Prepare the data to be sent back
|
||||
const userPayments = await Promise.all(financial.docs.map(async (payment) => {
|
||||
// Find the project associated with the payment
|
||||
const project = await ProjectModel.findById(payment.project_id)
|
||||
|
||||
if (!project) {
|
||||
return null
|
||||
}
|
||||
// استفاده از کتابخانه moment-jalaali برای تبدیل تاریخ و زمان به شمسی
|
||||
const shamsiDate = moment(payment.createdAt).format('jYYYY-jMM-jDD')
|
||||
const shamsiTime = moment(payment.createdAt).format('HH:mm')
|
||||
|
||||
// Extract the required information
|
||||
const userPayment = {
|
||||
_id: payment._id,
|
||||
project_title: project.title,
|
||||
project_id: project._id,
|
||||
payment_date: shamsiDate, // Format date as YYYY-MM-DD
|
||||
payment_time: shamsiTime,
|
||||
iban: project.creator_id ? project.creator_id.iban : null, // Check if creator_id exists before accessing iban
|
||||
amount: payment.amount,
|
||||
payment_status: payment.status
|
||||
}
|
||||
|
||||
return userPayment
|
||||
}))
|
||||
|
||||
// Filter out null values
|
||||
const validUserPayments = userPayments.filter(payment => payment !== null)
|
||||
|
||||
// Send the payments back to the client
|
||||
res.status(200).json({
|
||||
financial: financial,
|
||||
totalPages: financial.totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems: financial.totalDocs // ارسال تعداد کل آیتمها
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getFinancial }
|
||||
77
controllers/application/login/changePasswordController.js
Normal file
77
controllers/application/login/changePasswordController.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable camelcase */
|
||||
const bcrypt = require('bcryptjs')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
|
||||
const jwt = require('jsonwebtoken')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
|
||||
const changePasswordValidationRules = () => {
|
||||
return [
|
||||
check('new_password')
|
||||
.notEmpty().withMessage('رمز عبور نمیتواند خالی باشد')
|
||||
.isLength({ min: 8, max: 25 }).withMessage('رمز عبور نباید کوتاه تر از 8 کاراکتر باشد')
|
||||
.matches(/^(?=.*[a-zA-Zآ-ی])(?=.*\d).{8,}$/u).withMessage('رمز عبور باید شامل حروف و اعداد باشد')
|
||||
]
|
||||
}
|
||||
const changePasswordUser = async (req, res, next) => {
|
||||
try {
|
||||
const { new_password } = 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 errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
|
||||
// یافتن کاربر با شماره موبایل
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
// هش کردن پسورد جدید و ذخیره در دیتابیس
|
||||
const hashedPassword = await bcrypt.hash(new_password, 10)
|
||||
user.password = hashedPassword
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'کلمه عبور با موفقیت ذخیره شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const editChangePasswordUser = async (req, res, next) => {
|
||||
try {
|
||||
const { mobile, new_password } = req.body
|
||||
const user = await UserModel.findOne({ mobile })
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// هش کردن پسورد جدید و ذخیره در دیتابیس
|
||||
const hashedPassword = await bcrypt.hash(new_password, 10)
|
||||
user.password = hashedPassword
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'کلمه عبور با موفقیت تغییر یافت'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
changePasswordUser, changePasswordValidationRules, editChangePasswordUser
|
||||
}
|
||||
87
controllers/application/login/loginController.js
Normal file
87
controllers/application/login/loginController.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const { default: axios } = require('axios')
|
||||
const loginValidationRules = () => {
|
||||
return [
|
||||
check('mobile')
|
||||
.notEmpty().withMessage('شماره موبایل نمیتواند خالی باشد')
|
||||
.isLength({ min: 11, max: 11 }).withMessage('شماره موبایل باید دقیقاً 11 رقم باشد')
|
||||
.matches(/^09[0-9]{9}$/).withMessage('فرمت شماره موبایل صحیح نیست')
|
||||
]
|
||||
}
|
||||
|
||||
const loginUser = async (req, res, next) => {
|
||||
try {
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
|
||||
const { mobile } = req.body
|
||||
if (!mobile) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شماره موبایل نمیتواند خالی باشد'
|
||||
})
|
||||
}
|
||||
|
||||
// const existingUser = await UserModel.findOne({ mobile })
|
||||
// if (!existingUser) {
|
||||
// return res.status(422).json({ error: true, message: 'کاربری با این شماره موبایل یافت نشد' })
|
||||
// }
|
||||
function generateOTP () {
|
||||
return Math.floor(100000 + Math.random() * 900000)
|
||||
}
|
||||
|
||||
const otp = generateOTP()
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const user = await UserModel.findOneAndUpdate(
|
||||
{ mobile },
|
||||
{ $set: { mobile, otp } },
|
||||
{ upsert: true, new: true, lean: true }
|
||||
)
|
||||
// زمانبندی تنظیم مقدار otp به null پس از 10 دقیقه
|
||||
setTimeout(() => {
|
||||
UserModel.findOneAndUpdate({ mobile }, { $set: { otp: null } }, { new: true })
|
||||
.then(() => {})
|
||||
.catch(error => console.error('Error setting OTP to null:', error))
|
||||
}, 5 * 60 * 1000)
|
||||
const data = JSON.stringify({
|
||||
mobile,
|
||||
templateId: '930719',
|
||||
parameters: [
|
||||
{ name: 'CODE', value: otp.toString() }
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'کد تایید ارسال شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loginValidationRules,
|
||||
loginUser
|
||||
}
|
||||
92
controllers/application/login/loginWithUserName.js
Normal file
92
controllers/application/login/loginWithUserName.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const TokenService = require('../../../services/TokenService')
|
||||
// const jwt = require('jsonwebtoken')
|
||||
|
||||
const loginWithUserName = async (req, res, next) => {
|
||||
try {
|
||||
// اعتبارسنجی ورودی ها
|
||||
const { user_name, password } = req.body
|
||||
if (!user_name || !password) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
// یافتن کاربر با نام کاربری
|
||||
const userLow = user_name.toLowerCase()
|
||||
const user = await UserModel.findOne({ user_name: userLow })
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این نام کاربری یافت نشد'
|
||||
})
|
||||
}
|
||||
if (!user.password) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'رمز عبور برای این کاربر تنظیم نشده است'
|
||||
})
|
||||
}
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password)
|
||||
if (!isPasswordValid) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'نام کاربری یا رمز عبور اشتباه است'
|
||||
})
|
||||
}
|
||||
// اگر هم نام کاربری و هم رمز عبور درست بود، ارسال پیام موفقیت آمیز
|
||||
const token = TokenService.sign({ id: user._id })
|
||||
// if (user.user_name === null) {
|
||||
// return res.json({
|
||||
// message: 'ورود با موفقیت',
|
||||
// page: 'username'
|
||||
// })
|
||||
// }
|
||||
|
||||
// if (!user.first_name && !user.last_name) {
|
||||
// return res.json({
|
||||
// message: 'ورود با موفقیت',
|
||||
// page: 'name'
|
||||
// })
|
||||
// }
|
||||
|
||||
// if (!user.user_type) {
|
||||
// return res.json({
|
||||
// message: 'کد تایید صحیح بود',
|
||||
// page: 'usertype'
|
||||
// })
|
||||
// }
|
||||
let step = ''
|
||||
if (user.user_name === null) { step = 'user_name' } else if
|
||||
(!user.password) { step = 'password' } else if
|
||||
(!user.first_name) { step = 'first_name' } else if
|
||||
(!user.user_type) { step = 'user_type' }
|
||||
if (!user.user_type || user.user_name === null || !user.first_name || !user.last_name) {
|
||||
return res.json({
|
||||
message: 'کد تایید صحیح بود',
|
||||
token,
|
||||
step,
|
||||
user_type: null,
|
||||
page: 'auth-page',
|
||||
id: user._id
|
||||
})
|
||||
}
|
||||
return res.json({
|
||||
message: 'کد تایید صحیح بود',
|
||||
token,
|
||||
page: 'home',
|
||||
step,
|
||||
user_type: user.user_type,
|
||||
id: user._id
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loginWithUserName
|
||||
}
|
||||
89
controllers/application/login/verifyController.js
Normal file
89
controllers/application/login/verifyController.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const TokenService = require('../../../services/TokenService')
|
||||
// const jwt = require('jsonwebtoken')
|
||||
|
||||
const verifyUser = async (req, res, next) => {
|
||||
try {
|
||||
const { mobile, otp } = req.body
|
||||
if (!mobile || !otp) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const user = await UserModel.findOne({ mobile })
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
const token = TokenService.sign({ id: user._id })
|
||||
if (user.otp === otp) {
|
||||
// if (user.user_name === null) {
|
||||
// return res.json({
|
||||
// message: 'کد تایید صحیح بود',
|
||||
// page: 'username'
|
||||
// })
|
||||
// }
|
||||
|
||||
// if (!user.first_name && !user.last_name) {
|
||||
// return res.json({
|
||||
// message: 'کد تایید صحیح بود',
|
||||
// page: 'name'
|
||||
// })
|
||||
// }
|
||||
|
||||
// if (!user.user_type) {
|
||||
// return res.json({
|
||||
// message: 'کد تایید صحیح بود',
|
||||
// page: 'usertype'
|
||||
// })
|
||||
// }
|
||||
|
||||
// اگر همه فیلدها پر بودند
|
||||
// return res.json({
|
||||
// message: 'کد تایید صحیح بود',
|
||||
// token,
|
||||
// user_type: user.user_type,
|
||||
// page: 'home',
|
||||
// id: user._id
|
||||
// })
|
||||
let step = ''
|
||||
if (user.user_name === null) { step = 'user_name' } else if
|
||||
(!user.password) { step = 'password' } else if
|
||||
(!user.first_name) { step = 'first_name' } else if
|
||||
(!user.user_type) { step = 'user_type' } else { step = 'profile_image' }
|
||||
if (!user.user_type || user.user_name === null || !user.first_name || !user.last_name) {
|
||||
return res.json({
|
||||
message: 'کد تایید صحیح بود',
|
||||
token,
|
||||
step,
|
||||
user_type: null,
|
||||
page: 'auth-page',
|
||||
id: user._id
|
||||
})
|
||||
}
|
||||
return res.json({
|
||||
message: 'کد تایید صحیح بود',
|
||||
token,
|
||||
user_type: user.user_type,
|
||||
step,
|
||||
page: 'home',
|
||||
id: user._id
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'کد تایید اشتباه است'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verifyUser
|
||||
}
|
||||
127
controllers/application/messages/messageController.js
Normal file
127
controllers/application/messages/messageController.js
Normal file
@@ -0,0 +1,127 @@
|
||||
const MessageModel = require('../../../models/MessageModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment-jalaali')
|
||||
const getMessages = 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 { search } = req.query
|
||||
|
||||
// پیدا کردن تمام پیامهایی که کاربر مورد نظر آنها فرستنده یا گیرنده بوده است
|
||||
const messageFilter = {
|
||||
$or: [
|
||||
{ senderId: userId },
|
||||
{ receiverId: userId }
|
||||
]
|
||||
}
|
||||
|
||||
// پیدا کردن تمام پیامها با فیلتر و سورت براساس جدیدترین پیام
|
||||
const messages = await MessageModel.find(messageFilter).sort({ createdAt: -1 })
|
||||
|
||||
// استخراج شناسههای تمام کاربران مرتبط با این پیامها (بدون تکرار)
|
||||
const userIdToLastMessageDate = {}
|
||||
messages.forEach(message => {
|
||||
if (!userIdToLastMessageDate[message.senderId]) {
|
||||
userIdToLastMessageDate[message.senderId] = message.createdAt
|
||||
}
|
||||
if (!userIdToLastMessageDate[message.receiverId]) {
|
||||
userIdToLastMessageDate[message.receiverId] = message.createdAt
|
||||
}
|
||||
})
|
||||
|
||||
// تبدیل مجموعه شناسههای کاربر به آرایه
|
||||
const userIdsArray = Object.keys(userIdToLastMessageDate)
|
||||
|
||||
// ساخت فیلتر جستجو برای کاربران مرتبط
|
||||
const userFilter = { _id: { $in: userIdsArray } }
|
||||
|
||||
if (search) {
|
||||
userFilter.$and = [
|
||||
{ _id: { $in: userIdsArray } }, // فقط کاربران مرتبط
|
||||
{
|
||||
$or: [
|
||||
{ first_name: { $regex: search, $options: 'i' } },
|
||||
{ last_name: { $regex: search, $options: 'i' } },
|
||||
{ user_name: { $regex: search, $options: 'i' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// پیدا کردن اطلاعات کاربران مرتبط بر اساس فیلتر جستجو و شمارش تعداد پیامهای خوانده نشده برای هر کاربر
|
||||
const usersData = await UserModel.find(userFilter)
|
||||
const usersWithUnreadCount = await Promise.all(usersData.map(async (user) => {
|
||||
const unreadMessagesCount = await MessageModel.countDocuments({ receiverId: userId, senderId: user._id, readStatus: 0 })
|
||||
|
||||
let lastOnlineStatus
|
||||
const now = moment()
|
||||
const lastOnline = moment(user.last_online)
|
||||
const diffMinutes = now.diff(lastOnline, 'minutes')
|
||||
|
||||
if (diffMinutes < 1) {
|
||||
lastOnlineStatus = 'آنلاین'
|
||||
} else if (diffMinutes < 60) {
|
||||
lastOnlineStatus = `${diffMinutes} دقیقه پیش`
|
||||
} else if (diffMinutes < 1440) {
|
||||
lastOnlineStatus = `${Math.floor(diffMinutes / 60)} ساعت پیش`
|
||||
} else {
|
||||
lastOnlineStatus = `${Math.floor(diffMinutes / 1440)} روز پیش`
|
||||
}
|
||||
|
||||
// بررسی آیا کاربر فعلی در لیست بلاکشدههای این کاربر است یا خیر
|
||||
const blockedByCurrentUser = user.blocked_by.includes(userId)
|
||||
const blockedYou = user.blocked_users.includes(userId)
|
||||
|
||||
return {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
user_name: user.user_name,
|
||||
is_verified: user.is_verified,
|
||||
profile_image: user.profile_image,
|
||||
last_online: lastOnlineStatus,
|
||||
_id: user._id,
|
||||
unread_messages_count: unreadMessagesCount,
|
||||
is_blocked: blockedByCurrentUser,
|
||||
blocked_you: blockedYou,
|
||||
last_message_date: userIdToLastMessageDate[user._id]
|
||||
}
|
||||
}))
|
||||
|
||||
// حذف کاربر فعلی از آرایه و سورت کردن کاربران براساس آخرین پیام
|
||||
const filteredUsersData = usersWithUnreadCount
|
||||
.filter(user => user._id.toString() !== userId)
|
||||
.sort((a, b) => new Date(b.last_message_date) - new Date(a.last_message_date))
|
||||
|
||||
res.status(200).json({
|
||||
filteredUsersData,
|
||||
totalMessages: messages.length
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getUnreadMessages = 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 unreadMessagesCount = await MessageModel.countDocuments({ receiverId: userId, readStatus: 0 })
|
||||
|
||||
res.status(200).json({ unread_messages_count: unreadMessagesCount })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getMessages, getUnreadMessages
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const NotificationModel = require('../../../models/NotificationModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
|
||||
const getNotifications = 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, search } = req.query // افزودن پارامترهای صفحهبندی به درخواست
|
||||
const filter = { user_id: userId }
|
||||
if (search) {
|
||||
filter.$or = [
|
||||
{ title: { $regex: search, $options: 'i' } },
|
||||
{ description: { $regex: search, $options: 'i' } }
|
||||
]
|
||||
}
|
||||
// استفاده از مدل پیجینیت شده برای دریافت نتایج صفحهبندی شده
|
||||
const options = {
|
||||
page: parseInt(page), // تبدیل صفحه به عدد صحیح
|
||||
limit: parseInt(limit), // تبدیل محدودیت به عدد صحیح
|
||||
sort: { createdAt: -1 } // بر اساس زمان ایجاد (createdAt) مرتب کنید (نزولی)
|
||||
}
|
||||
|
||||
// دریافت اعلانهای مربوط به کاربر با استفاده از userId
|
||||
let notifications = await NotificationModel.paginate(filter, options)
|
||||
const totalPages = notifications.totalPages
|
||||
const totalItems = notifications.totalDocs
|
||||
notifications = notifications?.docs.map(notification => {
|
||||
const jDate = jMoment(notification.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
return {
|
||||
...notification._doc,
|
||||
createdAt: jDate
|
||||
}
|
||||
})
|
||||
// تغییر وضعیت "read" اعلانها به "true"
|
||||
await NotificationModel.updateMany({ user_id: userId }, { read: true })
|
||||
res.json({
|
||||
notifications,
|
||||
totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems // ارسال تعداد کل آیتمها
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const unreadNotifications = 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 newNotificationsCount = await NotificationModel.countDocuments({ user_id: userId, read: false }).exec()
|
||||
|
||||
res.json({ newNotificationsCount })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNotifications, unreadNotifications
|
||||
}
|
||||
439
controllers/application/offer/offerController.js
Normal file
439
controllers/application/offer/offerController.js
Normal file
@@ -0,0 +1,439 @@
|
||||
/* 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 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': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
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': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
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': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
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 || !rate || !user_id) {
|
||||
return res.status(400).send({ message: 'All fields are required' })
|
||||
}
|
||||
// بررسی اینکه آیا کاربر قبلاً برای این پیشنهاد نظر داده است
|
||||
const existingComment = await CommentModel.findOne({
|
||||
offer: offerId,
|
||||
creator: userId
|
||||
})
|
||||
if (existingComment) {
|
||||
return res.status(400).send({ message: 'شما قبلاً نظر خود را برای این درخواست ثبت کردهاید' })
|
||||
}
|
||||
const newComment = new CommentModel({
|
||||
user: user_id,
|
||||
offer: offerId,
|
||||
creator: userId,
|
||||
rating: rate,
|
||||
comment,
|
||||
comment_for: 'user',
|
||||
status: 'pending'
|
||||
})
|
||||
await newComment.save()
|
||||
|
||||
const user = await UserModel.findById(user_id)
|
||||
|
||||
// پیدا کردن کامنتها و محاسبهی امتیاز کل
|
||||
const comments = await CommentModel.find({ user: user_id, comment_for: 'user' })
|
||||
const totalRating = Number(comments.reduce((acc, comment) => acc + comment.rating, 0))
|
||||
|
||||
let userLevel
|
||||
if (user.expertise === 'مدل') {
|
||||
if (totalRating <= 50) userLevel = 'تازه وارد'
|
||||
else if (totalRating <= 100) userLevel = 'استاندارد'
|
||||
else if (totalRating <= 300) userLevel = 'حرفهای'
|
||||
else userLevel = 'استاد'
|
||||
} else if (['زیبایی', 'عکاس'].includes(user.expertise)) {
|
||||
if (totalRating <= 50) userLevel = 'تازه وارد'
|
||||
else if (totalRating <= 100) userLevel = 'استاندارد'
|
||||
else if (totalRating <= 300) userLevel = 'حرفهای'
|
||||
else userLevel = 'استاد'
|
||||
}
|
||||
|
||||
user.user_score = totalRating
|
||||
user.user_level = userLevel
|
||||
|
||||
const totalComments = await CommentModel.countDocuments({ user: user_id, comment_for: 'user' })
|
||||
const averageRating = totalRating / totalComments
|
||||
user.rate = averageRating.toFixed(1)
|
||||
await user.save()
|
||||
|
||||
res.status(200).json({ message: 'نظر شما با موفقیت ثبت شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getUserOffers,
|
||||
getOfferTypes,
|
||||
handleSuccessfulOfferPayment,
|
||||
handleFailedOfferPayment,
|
||||
updateOfferStatus,
|
||||
createOfferComment
|
||||
}
|
||||
1185
controllers/application/payment/paymentController.js
Normal file
1185
controllers/application/payment/paymentController.js
Normal file
File diff suppressed because it is too large
Load Diff
935
controllers/application/payment/paymentControllerWeb.js
Normal file
935
controllers/application/payment/paymentControllerWeb.js
Normal file
@@ -0,0 +1,935 @@
|
||||
/* eslint-disable eqeqeq */
|
||||
|
||||
const TypeAndPriceModel = require('../../../models/TypeAndPriceModel');
|
||||
const ProjectModel = require('../../../models/ProjectModel');
|
||||
const PaymentModel = require('../../../models/PaymentModel');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const RequestModel = require('../../../models/RequestModel');
|
||||
const NotificationModel = require('../../../models/NotificationModel');
|
||||
const UserModel = require('../../../models/UserModel');
|
||||
const AdvertisingTypeModel = require('../../../models/AdvertisingTypeModel');
|
||||
const AdvertisingModel = require('../../../models/AdvertisingModel');
|
||||
const OfferTypeModel = require('../../../models/OfferTypeModel');
|
||||
const OfferModel = require('../../../models/OfferModel');
|
||||
const { default: axios } = require('axios');
|
||||
|
||||
const apiKey = "c7c41e8a-918f-4741-bcd5-58f3bc51db73"; // اصلاح نام متغیر محیطی
|
||||
|
||||
// Function to get price based on item name from database
|
||||
const getPriceFromDatabase = async (itemName) => {
|
||||
const item = await TypeAndPriceModel.findOne({ name: itemName });
|
||||
if (!item) {
|
||||
throw new Error(`Item "${itemName}" not found in TypeAndPriceModel`);
|
||||
}
|
||||
return item.price;
|
||||
};
|
||||
|
||||
const getAdvertisingPriceFromDatabase = async (itemName) => {
|
||||
const item = await AdvertisingTypeModel.findOne({ name: itemName });
|
||||
if (!item) {
|
||||
throw new Error(`Item "${itemName}" not found in AdvertisingTypeModel`);
|
||||
}
|
||||
return item.price;
|
||||
};
|
||||
|
||||
const getOfferPriceFromDatabase = async (offerType) => {
|
||||
const item = await OfferTypeModel.findOne({ name: offerType });
|
||||
if (!item) {
|
||||
throw new Error(`Offer type "${offerType}" not found in OfferTypeModel`);
|
||||
}
|
||||
return item.price;
|
||||
};
|
||||
// Controller function for initiating payment
|
||||
const initiatePaymentWeb = async (req, res) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
const itemName = req.body.item_name // Get item name from request body
|
||||
const price = await getPriceFromDatabase(itemName) // Get price from database
|
||||
const projectId = req.body.projectId // Get project ID from request body
|
||||
// Save project ID for later use in callback URL
|
||||
req.session.projectId = projectId
|
||||
const item = await TypeAndPriceModel.findOne({ name: itemName })
|
||||
const user = await UserModel.findById(userId)
|
||||
|
||||
if (item.name === 'free' && item.price === 0) {
|
||||
if (user.daily_free_request > 0) {
|
||||
user.daily_free_request -= 1
|
||||
await user.save()
|
||||
const project = await ProjectModel.findById(projectId)
|
||||
if (!project) {
|
||||
res.status(404).send('Project not found')
|
||||
return
|
||||
}
|
||||
project.payment_status = 'done' // Set project status to 'done'
|
||||
project.status = 'paid' // Set project status to 'done'
|
||||
await project.save()
|
||||
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', projectId, type: 'free' })
|
||||
} else {
|
||||
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کردهاید.' })
|
||||
}
|
||||
} else {
|
||||
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
|
||||
MerchantID: apiKey,
|
||||
Amount: Number(price),
|
||||
Description: `پرداخت برای سفارش شماره ${projectId}`,
|
||||
CallbackURL: `${process.env.APP_URL}/projects/payment-web?projectId=${projectId}&itemName=${itemName}&userId=${userId}` // Assuming your backend URL for handling payment callback
|
||||
})
|
||||
|
||||
const data = response.data
|
||||
if (data.Status === 100) {
|
||||
// Payment request was successful
|
||||
const authority = data.Authority // The payment authority (شناسه پرداخت)
|
||||
res.status(200).json({ authority })
|
||||
} else {
|
||||
// Payment request failed
|
||||
console.error('Error occurred while initiating payment:', data)
|
||||
res.status(500).json({ error: 'Error occurred while initiating payment' })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Error occurred while getting price from database or sending the request
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
// Controller function for handling payment callback from ZarinPal
|
||||
const handlePaymentCallbackWeb = async (req, res) => {
|
||||
try {
|
||||
// console.log(req.query)
|
||||
// Extract payment status and authority from ZarinPal callback
|
||||
const status = req.query.Status
|
||||
const authority = req.query.Authority
|
||||
const projectId = req.query.projectId // Get project ID from session
|
||||
const itemName = req.query.itemName // Get project ID from session
|
||||
const userId = req.query.userId // Get project ID from session
|
||||
// Clear project ID from session
|
||||
// Query ZarinPal API to verify payment status
|
||||
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
|
||||
const price = await getPriceFromDatabase(itemName) // Get price from database
|
||||
const verificationResponse = await axios.post(verificationUrl, {
|
||||
MerchantID: apiKey,
|
||||
Authority: authority,
|
||||
Amount: Number(price)
|
||||
})
|
||||
|
||||
const verificationData = verificationResponse.data
|
||||
if (verificationData.Status === 100) {
|
||||
const payment = new PaymentModel({
|
||||
amount: Number(price),
|
||||
status: 'successful',
|
||||
authority,
|
||||
user_id: userId, // فرضاً شما از نوع میانافزارهای مدیریت کاربر استفاده میکنید و اطلاعات کاربر را از طریق `req.user` در دسترس دارید
|
||||
project_id: projectId,
|
||||
type: 'create',
|
||||
installment_step: null
|
||||
// دیگر اطلاعات مورد نیاز برای ثبت پرداخت
|
||||
})
|
||||
await payment.save()
|
||||
|
||||
// Payment verification is successful
|
||||
// Now, update project status to 'done' or desired status
|
||||
const project = await ProjectModel.findById(projectId)
|
||||
if (!project) {
|
||||
res.status(404).send('Project not found')
|
||||
return
|
||||
}
|
||||
project.payment_status = 'done' // Set project status to 'done'
|
||||
project.status = 'paid' // Set project status to 'done'
|
||||
await project.save()
|
||||
|
||||
// Send event based on payment status
|
||||
if ('eventEmitter' in req.app) {
|
||||
if (status === 'OK') {
|
||||
// Payment is successful
|
||||
// Emit an event to be listened to in the frontend
|
||||
// Example event name: 'paymentSuccess'
|
||||
// Example data: { message: 'Payment successful' }
|
||||
// You can customize the event name and data as needed
|
||||
req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' })
|
||||
} else {
|
||||
// Payment is canceled or failed
|
||||
// Emit an event to be listened to in the frontend
|
||||
// Example event name: 'paymentFailed'
|
||||
// Example data: { message: 'Payment canceled or failed' }
|
||||
// You can customize the event name and data as needed
|
||||
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment canceled or failed' })
|
||||
}
|
||||
} else {
|
||||
console.error('Error: eventEmitter is not defined or not properly configured.')
|
||||
}
|
||||
|
||||
// Redirect to the callback URL with the project ID
|
||||
res.redirect(`https://modstagram.com/projects/payment/success?projectId=${projectId}&price=${price}&type=create`)
|
||||
} else {
|
||||
// Payment verification failed
|
||||
const payment = new PaymentModel({
|
||||
amount: Number(price),
|
||||
status: 'failed',
|
||||
authority,
|
||||
user_id: userId, // فرضاً شما از نوع میانافزارهای مدیریت کاربر استفاده میکنید و اطلاعات کاربر را از طریق `req.user` در دسترس دارید
|
||||
project_id: projectId,
|
||||
type: 'create'
|
||||
// دیگر اطلاعات مورد نیاز برای ثبت پرداخت
|
||||
})
|
||||
await payment.save()
|
||||
res.redirect(`https://modstagram.com/projects/payment/failed?projectId=${projectId}&price=${price}&type=create`)
|
||||
}
|
||||
} catch (error) {
|
||||
// Error occurred while verifying payment
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).send('Internal server error')
|
||||
}
|
||||
}
|
||||
|
||||
const paymentRequestAcceptWeb = async (req, res) => {
|
||||
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 projectId = req.body.projectId
|
||||
// Find the project
|
||||
const project = await ProjectModel.findById(projectId)
|
||||
if (!project) {
|
||||
return res.status(404).json({ error: 'Project not found' })
|
||||
}
|
||||
if (project.status == 'ongoing') {
|
||||
return res.status(400).json({ message: 'برای این پروژه از قبل کاربر انتخاب شده' })
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (userId != project.creator_id.toString()) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما نمیتوانید این پروژه را ویرایش کنید'
|
||||
})
|
||||
}
|
||||
|
||||
// Get the final price from the request model
|
||||
const requestId = req.body.requestId
|
||||
const request = await RequestModel.findById(requestId)
|
||||
if (!request) {
|
||||
return res.status(404).json({ error: 'Request not found' })
|
||||
}
|
||||
request.status = 'accepted'
|
||||
await request.save()
|
||||
|
||||
project.selected_user = request.user
|
||||
project.status = 'ongoing'
|
||||
project.final_price = request.price
|
||||
project.final_time = request.time
|
||||
await project.save()
|
||||
// Send notification to selected user
|
||||
const notification = new NotificationModel({
|
||||
user_id: request.user,
|
||||
project_post_id: projectId,
|
||||
type: 'request_accepted',
|
||||
title: 'انتخاب پیشنهاد',
|
||||
description: `کارفرما پیشنهاد کاری شما برای پروژه ${project.title} را پذیرفت.`
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
// Sms
|
||||
const userReciver = await UserModel.findById(request.user)
|
||||
const data = JSON.stringify({
|
||||
mobile: userReciver?.mobile,
|
||||
templateId: '302876',
|
||||
parameters: [
|
||||
{ name: 'USER', value: userReciver?.first_name + ' ' + userReciver?.last_name },
|
||||
{ name: 'PROJECT', value: project?.title }
|
||||
]
|
||||
})
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
res.status(200).json({
|
||||
message: 'درخواست با موفقیت تایید شد'
|
||||
})
|
||||
} catch (error) {
|
||||
// Error occurred while getting price from database or sending the request
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
const initiateAdvertisingPaymentWeb = async (req, res) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
const itemName = req.body.item_name // Get item name from request body
|
||||
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
|
||||
const advertisingId = req.body.advertisingId // Get project ID from request body
|
||||
const showDiscount = req.body.showDiscount
|
||||
|
||||
// Save project ID for later use in callback URL
|
||||
req.session.advertisingId = advertisingId
|
||||
const item = await AdvertisingTypeModel.findOne({ name: itemName })
|
||||
const user = await UserModel.findById(userId)
|
||||
|
||||
if (item.name === 'free' && item.price === 0) {
|
||||
if (user.daily_free_request > 0) {
|
||||
user.daily_free_request -= 1
|
||||
await user.save()
|
||||
const project = await AdvertisingModel.findById(advertisingId)
|
||||
if (!project) {
|
||||
res.status(404).send('Advertising not found')
|
||||
return
|
||||
}
|
||||
project.payment_status = 'done' // Set project status to 'done'
|
||||
project.status = 'paid' // Set project status to 'done'
|
||||
await project.save()
|
||||
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
|
||||
} else {
|
||||
res.status(403).json({ message: 'شما قبلاً از بیلبورد رایگان این ماه استفاده کردهاید.' })
|
||||
}
|
||||
} else {
|
||||
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
|
||||
MerchantID: process.env.ZARINPAL_MERCHANT_ID,
|
||||
Amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
|
||||
Description: `پرداخت برای سفارش شماره ${advertisingId}`,
|
||||
CallbackURL: `${process.env.APP_SITE_CallBack}/advertising/payment-web?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
|
||||
})
|
||||
const data = response.data
|
||||
if (data.Status === 100) {
|
||||
// Payment request was successful
|
||||
const authority = data.Authority // The payment authority (شناسه پرداخت)
|
||||
res.status(200).json({ authority })
|
||||
} else {
|
||||
// Payment request failed
|
||||
console.error('Error occurred while initiating payment:', data)
|
||||
res.status(500).json({ error: 'Error occurred while initiating payment' })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Error occurred while getting price from database or sending the request
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
const handleAdvertisingPaymentCallbackWeb = async (req, res) => {
|
||||
try {
|
||||
// console.log(req.query)
|
||||
// Extract payment status and authority from ZarinPal callback
|
||||
const status = req.query.Status
|
||||
const authority = req.query.Authority
|
||||
const advertisingId = req.query.advertisingId // Get project ID from session
|
||||
const itemName = req.query.itemName // Get project ID from session
|
||||
const userId = req.query.userId // Get project ID from session
|
||||
const showDiscount = req.query.showDiscount // Get project ID from session
|
||||
// Clear project ID from session
|
||||
// Query ZarinPal API to verify payment status
|
||||
|
||||
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
|
||||
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
|
||||
const verificationResponse = await axios.post(verificationUrl, {
|
||||
MerchantID: apiKey,
|
||||
Authority: authority,
|
||||
Amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)
|
||||
})
|
||||
|
||||
const verificationData = verificationResponse.data
|
||||
if (verificationData.Status === 100) {
|
||||
const payment = new PaymentModel({
|
||||
amount: showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price),
|
||||
status: 'successful',
|
||||
authority,
|
||||
user_id: userId, // فرضاً شما از نوع میانافزارهای مدیریت کاربر استفاده میکنید و اطلاعات کاربر را از طریق `req.user` در دسترس دارید
|
||||
advertising_id: advertisingId,
|
||||
type: 'advertising',
|
||||
installment_step: null
|
||||
// دیگر اطلاعات مورد نیاز برای ثبت پرداخت
|
||||
})
|
||||
await payment.save()
|
||||
|
||||
// Payment verification is successful
|
||||
// Now, update project status to 'done' or desired status
|
||||
const advertising = await AdvertisingModel.findById(advertisingId)
|
||||
if (!advertising) {
|
||||
res.status(404).send('Advertising not found')
|
||||
return
|
||||
}
|
||||
advertising.payment_status = 'done' // Set advertising status to 'done'
|
||||
advertising.status = 'paid' // Set advertising status to 'done'
|
||||
await advertising.save()
|
||||
|
||||
// Send event based on payment status
|
||||
if ('eventEmitter' in req.app) {
|
||||
if (status === 'OK') {
|
||||
// Payment is successful
|
||||
// Emit an event to be listened to in the frontend
|
||||
// Example event name: 'paymentSuccess'
|
||||
// Example data: { message: 'Payment successful' }
|
||||
// You can customize the event name and data as needed
|
||||
req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' })
|
||||
} else {
|
||||
// Payment is canceled or failed
|
||||
// Emit an event to be listened to in the frontend
|
||||
// Example event name: 'paymentFailed'
|
||||
// Example data: { message: 'Payment canceled or failed' }
|
||||
// You can customize the event name and data as needed
|
||||
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment canceled or failed' })
|
||||
}
|
||||
} else {
|
||||
console.error('Error: eventEmitter is not defined or not properly configured.')
|
||||
}
|
||||
|
||||
// Redirect to the callback URL with the advertising ID
|
||||
res.redirect(`https://modstagram.com/billboards/payment/success?projectId=${advertisingId}&price=${showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)}&type=advertising&showDiscount=${showDiscount}`)
|
||||
} else {
|
||||
// Payment verification failed
|
||||
const payment = new PaymentModel({
|
||||
amount: showDiscount ? Number(price + 20000) : Number(price),
|
||||
status: 'failed',
|
||||
authority,
|
||||
user_id: userId, // فرضاً شما از نوع میانافزارهای مدیریت کاربر استفاده میکنید و اطلاعات کاربر را از طریق `req.user` در دسترس دارید
|
||||
advertising_id: advertisingId,
|
||||
type: 'advertising'
|
||||
// دیگر اطلاعات مورد نیاز برای ثبت پرداخت
|
||||
})
|
||||
console.log('ss')
|
||||
|
||||
await payment.save()
|
||||
res.redirect(`https://modstagram.com/billboards/payment/failed?projectId=${advertisingId}&price=${showDiscount === true || showDiscount === 'true' ? Number(price + 20000) : Number(price)}&type=advertising&showDiscount=${showDiscount}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// Error occurred while verifying payment
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).send('Internal server error')
|
||||
}
|
||||
}
|
||||
|
||||
const republishAdvertisingPaymentWeb = async (req, res) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
const itemName = req.body.item_name // Get item name from request body
|
||||
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
|
||||
const advertisingId = req.body.advertisingId // Get project ID from request body
|
||||
const showDiscount = req.body.showDiscount
|
||||
// Save project ID for later use in callback URL
|
||||
req.session.advertisingId = advertisingId
|
||||
const item = await AdvertisingTypeModel.findOne({ name: itemName })
|
||||
const user = await UserModel.findById(userId)
|
||||
|
||||
if (item.name === 'free' && item.price === 0) {
|
||||
if (user.daily_free_request > 0) {
|
||||
user.daily_free_request -= 1
|
||||
await user.save()
|
||||
const project = await AdvertisingModel.findById(advertisingId)
|
||||
if (!project) {
|
||||
res.status(404).send('Project not found')
|
||||
return
|
||||
}
|
||||
project.payment_status = 'done' // Set project status to 'done'
|
||||
project.status = 'paid' // Set project status to 'done'
|
||||
await project.save()
|
||||
res.status(200).json({ message: 'پروژه رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
|
||||
} else {
|
||||
res.status(403).json({ message: 'شما قبلاً از درخواست رایگان امروز استفاده کردهاید.' })
|
||||
}
|
||||
} else {
|
||||
const response = await axios.post('https://www.zarinpal.com/pg/rest/WebGate/PaymentRequest.json', {
|
||||
MerchantID: apiKey,
|
||||
Amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
|
||||
Description: `پرداخت برای سفارش شماره ${advertisingId}`,
|
||||
CallbackURL: `${process.env.APP_URL}/advertising/republish-verify-payment-web?advertisingId=${advertisingId}&itemName=${itemName}&userId=${userId}&showDiscount=${showDiscount}` // Assuming your backend URL for handling payment callback
|
||||
})
|
||||
const data = response.data
|
||||
if (data.Status === 100) {
|
||||
// Payment request was successful
|
||||
const authority = data.Authority // The payment authority (شناسه پرداخت)
|
||||
res.status(200).json({ authority })
|
||||
} else {
|
||||
// Payment request failed
|
||||
console.error('Error occurred while initiating payment:', data)
|
||||
res.status(500).json({ error: 'Error occurred while initiating payment' })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Error occurred while getting price from database or sending the request
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
const handleAdvertisingRepublishPaymentCallbackWeb = async (req, res) => {
|
||||
try {
|
||||
// console.log(req.query)
|
||||
// Extract payment status and authority from ZarinPal callback
|
||||
const status = req.query.Status
|
||||
const authority = req.query.Authority
|
||||
const advertisingId = req.query.advertisingId // Get project ID from session
|
||||
const itemName = req.query.itemName // Get project ID from session
|
||||
const userId = req.query.userId // Get project ID from session
|
||||
const showDiscount = req.query.showDiscount // Get project ID from session
|
||||
// Clear project ID from session
|
||||
// Query ZarinPal API to verify payment status
|
||||
const verificationUrl = 'https://www.zarinpal.com/pg/rest/WebGate/PaymentVerification.json'
|
||||
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
|
||||
const verificationResponse = await axios.post(verificationUrl, {
|
||||
MerchantID: apiKey,
|
||||
Authority: authority,
|
||||
Amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100)
|
||||
})
|
||||
|
||||
const verificationData = verificationResponse.data
|
||||
if (verificationData.Status === 100) {
|
||||
const payment = new PaymentModel({
|
||||
amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
|
||||
status: 'successful',
|
||||
authority,
|
||||
user_id: userId, // فرضاً شما از نوع میانافزارهای مدیریت کاربر استفاده میکنید و اطلاعات کاربر را از طریق `req.user` در دسترس دارید
|
||||
advertising_id: advertisingId,
|
||||
type: 'advertising',
|
||||
installment_step: null
|
||||
// دیگر اطلاعات مورد نیاز برای ثبت پرداخت
|
||||
})
|
||||
await payment.save()
|
||||
|
||||
// Payment verification is successful
|
||||
// Now, update project status to 'done' or desired status
|
||||
const advertising = await AdvertisingModel.findById(advertisingId)
|
||||
if (!advertising) {
|
||||
res.status(404).send('Advertising not found')
|
||||
return
|
||||
}
|
||||
advertising.payment_status = 'done' // Set advertising status to 'done'
|
||||
advertising.status = 'accepted' // Set advertising status to 'done'
|
||||
advertising.acceptedAt = new Date()
|
||||
await advertising.save()
|
||||
|
||||
// Send event based on payment status
|
||||
if ('eventEmitter' in req.app) {
|
||||
if (status === 'OK') {
|
||||
// Payment is successful
|
||||
// Emit an event to be listened to in the frontend
|
||||
// Example event name: 'paymentSuccess'
|
||||
// Example data: { message: 'Payment successful' }
|
||||
// You can customize the event name and data as needed
|
||||
req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' })
|
||||
} else {
|
||||
// Payment is canceled or failed
|
||||
// Emit an event to be listened to in the frontend
|
||||
// Example event name: 'paymentFailed'
|
||||
// Example data: { message: 'Payment canceled or failed' }
|
||||
// You can customize the event name and data as needed
|
||||
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment canceled or failed' })
|
||||
}
|
||||
} else {
|
||||
console.error('Error: eventEmitter is not defined or not properly configured.')
|
||||
}
|
||||
|
||||
// Redirect to the callback URL with the advertising ID
|
||||
res.redirect(`https://modstagram.com/billboards/payment/success?projectId=${advertisingId}&price=${showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100)}&type=advertising-republish&showDiscount=${showDiscount}`)
|
||||
} else {
|
||||
// Payment verification failed
|
||||
const payment = new PaymentModel({
|
||||
amount: showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100),
|
||||
status: 'failed',
|
||||
authority,
|
||||
user_id: userId, // فرضاً شما از نوع میانافزارهای مدیریت کاربر استفاده میکنید و اطلاعات کاربر را از طریق `req.user` در دسترس دارید
|
||||
advertising_id: advertisingId,
|
||||
type: 'advertising'
|
||||
// دیگر اطلاعات مورد نیاز برای ثبت پرداخت
|
||||
})
|
||||
await payment.save()
|
||||
res.redirect(`https://modstagram.com/billboards/payment/failed?projectId=${advertisingId}&price=${showDiscount === true || showDiscount === 'true' ? Number(((price + 20000) * 70) / 100) : Number(((price) * 70) / 100)}&type=advertising-republish&showDiscount=${showDiscount}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// Error occurred while verifying payment
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).send('Internal server error')
|
||||
}
|
||||
}
|
||||
// const initiateOfferPaymentWeb = async (req, res) => {
|
||||
// try {
|
||||
// const token = req.header('Authorization')?.split(' ')[1];
|
||||
// if (!token) {
|
||||
// return res.status(401).json({ error: 'Access Denied: No token provided' });
|
||||
// }
|
||||
|
||||
// let decodedToken;
|
||||
// try {
|
||||
// decodedToken = jwt.verify(token, process.env.APP_SECRET);
|
||||
// } catch (err) {
|
||||
// return res.status(401).json({ error: 'Invalid token' });
|
||||
// }
|
||||
|
||||
// const userId = decodedToken.id;
|
||||
// const { receiverId, offerType } = req.body;
|
||||
|
||||
// // اعتبارسنجی ورودیها
|
||||
// if (!receiverId || !offerType) {
|
||||
// return res.status(400).json({ error: 'receiverId and offerType are required' });
|
||||
// }
|
||||
|
||||
// const user = await UserModel.findById(userId);
|
||||
// if (!user) {
|
||||
// return res.status(404).json({ error: 'User not found' });
|
||||
// }
|
||||
|
||||
// const receiverUser = await UserModel.findById(receiverId);
|
||||
// if (!receiverUser) {
|
||||
// return res.status(404).json({ error: 'Receiver not found' });
|
||||
// }
|
||||
|
||||
// let price;
|
||||
// if (user.monthly_free_offer > 0 && offerType === 'free') {
|
||||
// price = 0;
|
||||
// user.monthly_free_offer -= 1;
|
||||
// await user.save();
|
||||
// } else {
|
||||
// price = await getOfferPriceFromDatabase(offerType);
|
||||
// }
|
||||
|
||||
// if (price === 0) {
|
||||
// const offer = new OfferModel({
|
||||
// sender: userId,
|
||||
// receiver: receiverId,
|
||||
// transaction_id: null,
|
||||
// });
|
||||
// await offer.save();
|
||||
// return res.status(200).json({ message: 'آفر رایگان با موفقیت ثبت شد', offerId: offer._id, type: 'free' });
|
||||
// }
|
||||
// console.log('🔹 Initiating ZarinPal Payment');
|
||||
// console.log('MerchantID:', apiKey);
|
||||
// console.log('Amount:', Number(price * 10));
|
||||
// console.log('Description:', `پرداخت برای ارسال آفر به ${receiverUser.first_name}`);
|
||||
// console.log('CallbackURL:', `${process.env.APP_URL}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`);
|
||||
|
||||
// const ZARINPAL_URL = 'https://api.zarinpal.com/pg/v4/payment/request.json';
|
||||
// //
|
||||
// const response = await axios.post(ZARINPAL_URL, {
|
||||
// merchant_id: process.env.ZARINPAL_MERCHANT_ID, // ❌ MerchantID نیست!
|
||||
// amount: Number(price), // ✅ به ریال
|
||||
// description: `پرداخت برای ارسال آفر به ${receiverUser.first_name}`,
|
||||
// callback_url: `${process.env.APP_SITE_CallBack}/offer/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`,
|
||||
// }, { headers: { 'Content-Type': 'application/json' } });
|
||||
|
||||
// const result = response.data.data; // ✅ data.data
|
||||
|
||||
// if (result.code === 100) { // ✅ code === 100
|
||||
// return res.status(200).json({
|
||||
// authority: result.authority,
|
||||
// paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`
|
||||
// });
|
||||
// } else {
|
||||
// console.error('ZarinPal error:', data);
|
||||
// return res.status(500).json({ error: `Failed to initiate payment: ZarinPal Status ${data.Status}` });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('❌ Error in initiateOfferPaymentWeb:', error.message, error.stack);
|
||||
// return res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
// }
|
||||
// }
|
||||
const initiateOfferPaymentWeb = async (req, res) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.split(' ')[1];
|
||||
if (!token) return res.status(401).json({ error: 'No token' });
|
||||
|
||||
const decoded = jwt.verify(token, process.env.APP_SECRET);
|
||||
const userId = decoded.id;
|
||||
|
||||
const { receiverId, offerType } = 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' });
|
||||
|
||||
let price = await getOfferPriceFromDatabase(offerType); // ✅ ریال
|
||||
|
||||
// 🎁 free offer
|
||||
if (offerType === 'free' && user.monthly_free_offer > 0) {
|
||||
const offer = await OfferModel.create({
|
||||
sender: userId,
|
||||
receiver: receiverId,
|
||||
transaction_id: null,
|
||||
});
|
||||
|
||||
user.monthly_free_offer -= 1;
|
||||
await user.save();
|
||||
|
||||
return res.json({
|
||||
type: 'free',
|
||||
offerId: offer._id,
|
||||
});
|
||||
}
|
||||
|
||||
const amount = Number(price) * 10; // ✅ ریال – بدون ×10
|
||||
const callbackUrl = `https://api.modstagram.com/api/v1/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`;
|
||||
|
||||
const zarinpalRes = await axios.post(
|
||||
'https://api.zarinpal.com/pg/v4/payment/request.json',
|
||||
{
|
||||
merchant_id:"c7c41e8a-918f-4741-bcd5-58f3bc51db73",
|
||||
amount,
|
||||
description: `ارسال آفر به ${receiver.first_name}`,
|
||||
callback_url: callbackUrl
|
||||
|
||||
// callback_url: `${process.env.APP_SITE_CallBack}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`,
|
||||
},
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
|
||||
// console.log(`${process.env.APP_SITE_CallBack}/offers/payment-web?receiverId=${receiverId}&offerType=${offerType}&userId=${userId}`);
|
||||
|
||||
|
||||
const result = zarinpalRes.data.data;
|
||||
console.log(result);
|
||||
console.log("ZarinPal Callback URL:", callbackUrl);
|
||||
console.log("Initiate payment request:", { userId, receiverId, offerType, price });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (result.code === 100) {
|
||||
return res.json({
|
||||
authority: result.authority,
|
||||
paymentUrl: `https://www.zarinpal.com/pg/StartPay/${result.authority}`,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).json({ error: 'ZarinPal error' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// try {
|
||||
// const { receiverId, offerType, userId, Authority, Status } = req.query;
|
||||
|
||||
// // ❗ چک اولیه
|
||||
// if (Status !== 'OK' || !Authority) {
|
||||
// return res.status(400).json({
|
||||
// success: false,
|
||||
// message: 'پرداخت ناموفق یا لغو شد'
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (!receiverId || !offerType || !userId) {
|
||||
// return res.status(400).json({
|
||||
// success: false,
|
||||
// message: 'پارامترهای ناقص'
|
||||
// });
|
||||
// }
|
||||
|
||||
// // پیدا کردن کاربرها
|
||||
// const user = await UserModel.findById(userId);
|
||||
// const receiverUser = await UserModel.findById(receiverId);
|
||||
|
||||
// if (!user || !receiverUser) {
|
||||
// return res.status(404).json({
|
||||
// success: false,
|
||||
// message: 'کاربر یافت نشد'
|
||||
// });
|
||||
// }
|
||||
|
||||
// const price = await getOfferPriceFromDatabase(offerType);
|
||||
|
||||
// const verifyResponse = await axios.post(
|
||||
// 'https://api.zarinpal.com/pg/v4/payment/verify.json',
|
||||
// {
|
||||
// merchant_id: process.env.ZARINPAL_MERCHANT_ID, // یا MERCHENT_CODE
|
||||
// authority: Authority,
|
||||
// amount: price, // به ریال
|
||||
// },
|
||||
// { headers: { 'Content-Type': 'application/json' } }
|
||||
// );
|
||||
|
||||
// const verifyData = verifyResponse.data.data;
|
||||
|
||||
// if (verifyData.code === 100) {
|
||||
// // ✅ پرداخت موفق
|
||||
// const payment = new PaymentModel({
|
||||
// amount: Number(price),
|
||||
// status: 'successful',
|
||||
// authority: Authority,
|
||||
// ref_id: verifyData.ref_id,
|
||||
// user_id: userId,
|
||||
// type: 'offer',
|
||||
// });
|
||||
// await payment.save();
|
||||
|
||||
// const offer = new OfferModel({
|
||||
// sender: userId,
|
||||
// receiver: receiverId,
|
||||
// transaction_id: payment._id,
|
||||
// });
|
||||
// await offer.save();
|
||||
|
||||
// // نوتیفیکیشن و SMS (همون کد قبلی)
|
||||
// const notification = new NotificationModel({
|
||||
// user_id: receiverId,
|
||||
// project_post_id: offer._id,
|
||||
// type: 'new_offer',
|
||||
// title: 'درخواست همکاری جدید',
|
||||
// description: `${user.first_name} یک درخواست همکاری برای شما ارسال کرده است`,
|
||||
// });
|
||||
// await notification.save();
|
||||
|
||||
// const data = JSON.stringify({
|
||||
// mobile: receiverUser?.mobile,
|
||||
// templateId: '569006',
|
||||
// parameters: [
|
||||
// { name: 'FIRSTNAME', value: receiverUser?.first_name },
|
||||
// { name: 'LASTNAME', value: receiverUser?.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_VERIFY_KEY,
|
||||
// },
|
||||
// data,
|
||||
// };
|
||||
|
||||
// await axios(config).catch((error) => {
|
||||
// console.error('SMS error:', error.message);
|
||||
// });
|
||||
|
||||
// if ('eventEmitter' in req.app && status === 'OK') {
|
||||
// req.app.get('eventEmitter').emit('paymentSuccess', { message: 'Payment successful' });
|
||||
// } else {
|
||||
// req.app.get('eventEmitter')?.emit('paymentFailed', { message: 'Payment canceled or failed' });
|
||||
// }
|
||||
|
||||
// res.redirect(`https://modstagram.com/offer/payment/success?userId=${userId}`);
|
||||
|
||||
// return res.status(200).json({
|
||||
// success: true,
|
||||
// message: 'پرداخت و آفر با موفقیت ثبت شد',
|
||||
// ref_id: verifyData.ref_id,
|
||||
// offerId: offer._id
|
||||
// });
|
||||
// } else {
|
||||
// // پرداخت ناموفق
|
||||
// const payment = new PaymentModel({
|
||||
// amount: Number(price),
|
||||
// status: 'failed',
|
||||
// authority: Authority,
|
||||
// user_id: userId,
|
||||
// type: 'offer',
|
||||
// });
|
||||
// await payment.save();
|
||||
// res.redirect(`https://modstagram.com/offer/payment/failed?userId=${userId}`);
|
||||
// return res.status(400).json({
|
||||
// success: false,
|
||||
// message: `خطا در تأیید پرداخت: ${verifyData.code}`
|
||||
// });
|
||||
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('❌ Callback Error:', error.message);
|
||||
// return res.status(500).json({
|
||||
// success: false,
|
||||
// message: 'خطای سرور'
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleOfferPaymentCallback = async (req, res) => {
|
||||
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
|
||||
console.log('🔔 CALLBACK QUERY:', req.query);
|
||||
|
||||
try {
|
||||
const { Authority, Status, receiverId, offerType, userId } = req.query;
|
||||
|
||||
if (Status !== 'OK')
|
||||
return res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
|
||||
|
||||
const price = await getOfferPriceFromDatabase(offerType); // ریال
|
||||
const amount = Number(price) * 10;
|
||||
|
||||
const verify = await axios.post(
|
||||
'https://api.zarinpal.com/pg/v4/payment/verify.json',
|
||||
{
|
||||
merchant_id: "c7c41e8a-918f-4741-bcd5-58f3bc51db73",
|
||||
authority: Authority,
|
||||
amount,
|
||||
},
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
|
||||
const data = verify.data.data;
|
||||
console.log('🔔payment:', data.code);
|
||||
if (data.code === 100) {
|
||||
const payment = await PaymentModel.create({
|
||||
amount : (amount / 10),
|
||||
status: 'successful',
|
||||
authority: Authority,
|
||||
ref_id: data.ref_id,
|
||||
user_id: userId,
|
||||
type: 'offer',
|
||||
});
|
||||
|
||||
console.log('🔔payment:', payment);
|
||||
|
||||
const offer = await OfferModel.create({
|
||||
sender: userId,
|
||||
receiver: receiverId,
|
||||
transaction_id: payment._id,
|
||||
});
|
||||
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/success?offerId=${offer._id}`
|
||||
);
|
||||
}
|
||||
|
||||
return res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
initiatePaymentWeb,
|
||||
handlePaymentCallbackWeb,
|
||||
paymentRequestAcceptWeb,
|
||||
initiateAdvertisingPaymentWeb,
|
||||
handleAdvertisingPaymentCallbackWeb,
|
||||
republishAdvertisingPaymentWeb,
|
||||
handleAdvertisingRepublishPaymentCallbackWeb,
|
||||
initiateOfferPaymentWeb,
|
||||
handleOfferPaymentCallback,
|
||||
};
|
||||
64
controllers/application/posts/likeController.js
Normal file
64
controllers/application/posts/likeController.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/* eslint-disable camelcase */
|
||||
const PostModel = require('../../../models/PostModel');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const UserModel = require('../../../models/UserModel');
|
||||
const LikeModel = require('../../../models/LikeModel');
|
||||
|
||||
const toggleLike = async (req, res, next) => {
|
||||
try {
|
||||
const { postId } = req.body;
|
||||
const token = req.header('Authorization')?.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'توکن ارائه نشده است' });
|
||||
}
|
||||
|
||||
let userId;
|
||||
try {
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
|
||||
userId = decodedToken.id;
|
||||
} catch (err) {
|
||||
return res.status(401).json({ message: 'توکن نامعتبر است' });
|
||||
}
|
||||
|
||||
if (!postId) {
|
||||
return res.status(400).json({ message: 'شناسه پست الزامی است' });
|
||||
}
|
||||
|
||||
const post = await PostModel.findById(postId);
|
||||
if (!post) {
|
||||
return res.status(404).json({ message: 'پست مورد نظر یافت نشد' });
|
||||
}
|
||||
|
||||
const isLiked = post.likes.includes(userId);
|
||||
if (isLiked) {
|
||||
// unlike
|
||||
post.likes.pull(userId);
|
||||
await post.save();
|
||||
await LikeModel.findOneAndDelete({ postId, userId });
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'لایک با موفقیت حذف شد',
|
||||
is_liked: false,
|
||||
likesCount: post.likes.length,
|
||||
});
|
||||
} else {
|
||||
// like
|
||||
post.likes.push(userId);
|
||||
await post.save();
|
||||
await LikeModel.create({ postId, userId });
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'پست با موفقیت لایک شد',
|
||||
is_liked: true,
|
||||
likesCount: post.likes.length,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('خطا در toggleLike:', error);
|
||||
res.status(500).json({ message: 'خطا در سرور' });
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { toggleLike };
|
||||
182
controllers/application/posts/postController.js
Normal file
182
controllers/application/posts/postController.js
Normal file
@@ -0,0 +1,182 @@
|
||||
/* eslint-disable camelcase */
|
||||
const PostModel = require('../../../models/PostModel');
|
||||
const UserModel = require('../../../models/UserModel');
|
||||
const { check, validationResult } = require('express-validator');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const path = require('path');
|
||||
const { default: mongoose } = require('mongoose');
|
||||
|
||||
const createPostValidationRules = () => {
|
||||
console.log(4);
|
||||
return [
|
||||
check('caption')
|
||||
.notEmpty().withMessage('توضیحات نمیتواند خالی باشد'),
|
||||
];
|
||||
};
|
||||
|
||||
const createPost = async (req, res) => {
|
||||
try {
|
||||
console.log('DEBUG: createPost called');
|
||||
console.log('DEBUG: req.files:', req.files);
|
||||
console.log('DEBUG: req.body:', req.body);
|
||||
|
||||
const { caption } = req.body;
|
||||
const files = req.files || [];
|
||||
const userId = req.user.id; // از میدلویر auth
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
console.log('DEBUG: No files provided');
|
||||
return res.status(400).json({ error: true, message: 'هیچ فایلی آپلود نشده است' });
|
||||
}
|
||||
|
||||
if (!caption) {
|
||||
console.log('DEBUG: No caption provided');
|
||||
return res.status(400).json({ error: true, message: 'کپشن الزامی است' });
|
||||
}
|
||||
|
||||
const filePaths = files.map((file) => ({
|
||||
path: file.path,
|
||||
type: file.mimetype.startsWith('video/') ? 'video' : 'image',
|
||||
}));
|
||||
|
||||
console.log('DEBUG: Saving post to MongoDB, filePaths:', filePaths);
|
||||
const post = new PostModel({
|
||||
user_id: userId,
|
||||
caption,
|
||||
files: filePaths,
|
||||
type: files[0].mimetype.startsWith('video/') ? 'video' : 'image', // اضافه کردن فیلد type
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
await post.save();
|
||||
console.log('DEBUG: Post saved:', post._id);
|
||||
res.status(201).json({ message: 'پست با موفقیت ایجاد شد', postId: post._id });
|
||||
} catch (err) {
|
||||
console.error('DEBUG: createPost error:', err.message);
|
||||
res.status(500).json({ error: true, message: `خطا در سرور: ${err.message}` });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const getUserPosts = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const user_id = req.query.user_id
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userReqId = decodedToken.id
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(user_id)) {
|
||||
return res.status(400).json({ message: 'شناسه کاربر معتبر نیست' })
|
||||
}
|
||||
const user = await UserModel.findById(user_id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
const response = {}
|
||||
const options = {
|
||||
page: req.query.page || 1, // صفحه پیشفرض ۱
|
||||
limit: req.query.limit || 10, // حداکثر تعداد موارد برای هر صفحه
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
if (user_id.toString() === userReqId.toString()) {
|
||||
// کاربر خودش است، همه پستهایش را بررسی میکند
|
||||
const posts = await PostModel.paginate(
|
||||
{ user_id: user._id, status: { $in: ['pending', 'accept'] } }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
response.posts = posts.docs
|
||||
response.postsCount = posts.totalDocs
|
||||
response.totalPages = posts.totalPages // ارسال تعداد کل صفحات
|
||||
response.totalItems = posts.totalDocs
|
||||
} else {
|
||||
// دیگران، فقط پستهایی که وضعیتشان "accept" است را ببینند
|
||||
const posts = await PostModel.paginate(
|
||||
{ user_id: user._id, status: 'accept' }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
response.posts = posts.docs
|
||||
response.postsCount = posts.totalDocs
|
||||
response.totalPages = posts.totalPages // ارسال تعداد کل صفحات
|
||||
response.totalItems = posts.totalDocs
|
||||
}
|
||||
return res.status(200).json({ data: response })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const getUserPostsWeb = async (req, res, next) => {
|
||||
try {
|
||||
let userReqId = null
|
||||
|
||||
// دریافت توکن و بررسی معتبر بودنش (اگر وجود داشت)
|
||||
const authHeader = req.header('Authorization')
|
||||
if (authHeader) {
|
||||
const token = authHeader.split(' ')[1]
|
||||
try {
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
userReqId = decodedToken.id
|
||||
} catch (err) {
|
||||
console.warn('توکن نامعتبر است، ادامه بدون احراز هویت') // فقط هشدار، نه ارور
|
||||
}
|
||||
}
|
||||
|
||||
const user_id = req.query.user_id
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(user_id)) {
|
||||
return res.status(400).json({ message: 'شناسه کاربر معتبر نیست' })
|
||||
}
|
||||
|
||||
const user = await UserModel.findById(user_id)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
|
||||
const data = {}
|
||||
const options = {
|
||||
page: req.query.page || 1,
|
||||
limit: req.query.limit || 10,
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
if (userReqId && user_id.toString() === userReqId.toString()) {
|
||||
// اگر کاربر لاگین کرده و درخواست برای خودش است، همه پستهایش را ببیند
|
||||
const posts = await PostModel.paginate(
|
||||
{ user_id: user._id, status: { $in: ['pending', 'accept'] } },
|
||||
options
|
||||
)
|
||||
data.posts = posts.docs
|
||||
data.postsCount = posts.totalDocs
|
||||
data.totalPages = posts.totalPages
|
||||
data.totalItems = posts.totalDocs
|
||||
} else {
|
||||
// برای بقیه، فقط پستهایی که وضعیتشان "accept" است نمایش داده شود
|
||||
const posts = await PostModel.paginate(
|
||||
{ user_id: user._id, status: 'accept' },
|
||||
options
|
||||
)
|
||||
data.posts = posts.docs
|
||||
data.postsCount = posts.totalDocs
|
||||
data.totalPages = posts.totalPages
|
||||
data.totalItems = posts.totalDocs
|
||||
}
|
||||
|
||||
return res.status(200).json(data)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPostValidationRules,
|
||||
createPost,
|
||||
getUserPosts,
|
||||
getUserPostsWeb
|
||||
}
|
||||
139
controllers/application/profile/profileController.js
Normal file
139
controllers/application/profile/profileController.js
Normal file
@@ -0,0 +1,139 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
|
||||
// GET user by ID from URL
|
||||
const getUserById = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.params.id; // گرفتن آیدی از پارامتر URL
|
||||
const service = req.query.services;
|
||||
|
||||
const user = await UserModel.findById(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "کاربر مورد نظر یافت نشد" });
|
||||
}
|
||||
|
||||
let isRegister = true;
|
||||
// if (
|
||||
// !user.user_name ||
|
||||
// !user.first_name ||
|
||||
// !user.last_name ||
|
||||
// !user.user_type ||
|
||||
// !user.national_card_image ||
|
||||
// !user.password ||
|
||||
// !user.national_code
|
||||
// ) {
|
||||
// isRegister = false;
|
||||
// }
|
||||
|
||||
const response = {
|
||||
_id: user._id,
|
||||
user_type: user.user_type,
|
||||
user_level: user.user_level,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
user_name: user.user_name,
|
||||
show_location: user.show_location,
|
||||
is_verified: user.is_verified,
|
||||
user_score: user.user_score,
|
||||
rate: user.rate,
|
||||
bio: user.bio,
|
||||
height: user.height,
|
||||
weight: user.weight,
|
||||
size: user.size,
|
||||
eye_color: user.eye_color,
|
||||
hair_color: user.hair_color,
|
||||
profile_image: user.profile_image,
|
||||
lat: user.lat,
|
||||
lng: user.lng,
|
||||
address: user.address,
|
||||
province: user.province,
|
||||
city: user.city,
|
||||
shaba: user.shaba,
|
||||
national_code: user.national_code,
|
||||
mobile: user.mobile,
|
||||
cooperation_abroad: user.cooperation_abroad,
|
||||
cooperation_type: user.cooperation_type,
|
||||
conversation_projects: user.conversation_projects,
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
isRegister,
|
||||
user,
|
||||
services: service === "1" ? user.services : null
|
||||
};
|
||||
|
||||
return res.status(200).json({ user: response });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getProfile = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const service = req.query.services
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
const user = await UserModel.findById(userId)
|
||||
let isRegister = true
|
||||
if (!user.user_name ||
|
||||
!user.first_name ||
|
||||
!user.last_name ||
|
||||
!user.user_type ||
|
||||
!user.national_card_image ||
|
||||
!user.password ||
|
||||
!user.national_code
|
||||
) {
|
||||
isRegister = false
|
||||
}
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
const response = {
|
||||
_id: user._id,
|
||||
user_type: user.user_type,
|
||||
user_level: user.user_level,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
user_name: user.user_name,
|
||||
show_location: user.show_location,
|
||||
is_verified: user.is_verified,
|
||||
is_Register: user.is_Register,
|
||||
user_score: user.user_score,
|
||||
rate: user.rate,
|
||||
bio: user.bio,
|
||||
height: user.height,
|
||||
weight: user.weight,
|
||||
size: user.size,
|
||||
eye_color: user.eye_color,
|
||||
hair_color: user.hair_color,
|
||||
profile_image: user.profile_image,
|
||||
lat: user.lat,
|
||||
lng: user.lng,
|
||||
address: user.address,
|
||||
province: user.province,
|
||||
city: user.city,
|
||||
shaba: user.shaba,
|
||||
national_code: user.national_code,
|
||||
mobile: user.mobile,
|
||||
cooperation_abroad: user.cooperation_abroad,
|
||||
cooperation_type: user.cooperation_type,
|
||||
conversation_projects: user.conversation_projects,
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
isRegister,
|
||||
services: service === '1' ? user.services : null
|
||||
}
|
||||
return res.status(200).json({ user: response })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getProfile,
|
||||
getUserById
|
||||
};
|
||||
220
controllers/application/projects/createProjectController.js
Normal file
220
controllers/application/projects/createProjectController.js
Normal file
@@ -0,0 +1,220 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const createProjectValidationRules = () => {
|
||||
return [
|
||||
check('title').notEmpty().withMessage('عنوان نمیتواند خالی باشد'),
|
||||
|
||||
check('expertise').notEmpty().withMessage('تخصص نمیتواند خالی باشد'),
|
||||
|
||||
check('gender')
|
||||
.isIn(['male', 'female'])
|
||||
.withMessage('جنسیت باید male یا female باشد'),
|
||||
|
||||
check('age').notEmpty().withMessage('سن نمیتواند خالی باشد'),
|
||||
|
||||
check('conversation_projects')
|
||||
.notEmpty()
|
||||
.withMessage('پروژههای مکالمه نمیتواند خالی باشد')
|
||||
.isBoolean()
|
||||
.withMessage('مقدار پروژههای مکالمه باید یک مقدار boolean باشد'),
|
||||
|
||||
check('province').notEmpty().withMessage('استان نمیتواند خالی باشد'),
|
||||
|
||||
check('city').notEmpty().withMessage('شهر نمیتواند خالی باشد'),
|
||||
|
||||
check('offer_time')
|
||||
.notEmpty()
|
||||
.withMessage('زمان پروژه نمیتواند خالی باشد'),
|
||||
|
||||
check('offer_price')
|
||||
.notEmpty()
|
||||
.withMessage('قیمت پیشنهادی نمیتواند خالی باشد'),
|
||||
|
||||
check('description')
|
||||
.notEmpty()
|
||||
.withMessage('توضیحات نمیتواند خالی باشد'),
|
||||
|
||||
check('project_type')
|
||||
.notEmpty()
|
||||
.withMessage('نوع پروژه نمیتواند خالی باشد')
|
||||
.isIn(['free', 'normal', 'force', 'highlight'])
|
||||
.withMessage('نوع پروژه باید normal یا force یا highlight باشد')
|
||||
]
|
||||
}
|
||||
|
||||
const createProject = 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 user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما دسترسی به این بخش ندارید'
|
||||
})
|
||||
}
|
||||
if (user.is_verified !== 'verified') {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'مدارک شما تایید نشده است'
|
||||
})
|
||||
}
|
||||
// اعتبارسنجی درخواست
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
const {
|
||||
title,
|
||||
expertise,
|
||||
sub_expertise,
|
||||
gender,
|
||||
age,
|
||||
conversation_projects,
|
||||
province,
|
||||
city,
|
||||
offer_time,
|
||||
offer_price,
|
||||
description,
|
||||
project_type,
|
||||
public_status,
|
||||
created_for_user,
|
||||
number_of_person
|
||||
} = req.body
|
||||
if (
|
||||
!title ||
|
||||
!expertise ||
|
||||
!age ||
|
||||
conversation_projects === null ||
|
||||
!province ||
|
||||
!city ||
|
||||
!offer_time ||
|
||||
!offer_price ||
|
||||
!description ||
|
||||
!project_type
|
||||
) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const provinceFind = await ProvinceModel.findOne(
|
||||
{ id: province },
|
||||
{ _id: 0 }
|
||||
)
|
||||
const cityFind = await CityModel.findOne({ id: city })
|
||||
// اگر پروژه برای یک کاربر خاص باید ساخته شود
|
||||
if (public_status && public_status === 'private' && created_for_user) {
|
||||
const createdUser = await UserModel.findById(created_for_user)
|
||||
if (!createdUser) {
|
||||
return res.status(422).json({
|
||||
error: true, message: 'کاربر مورد نظر برای ساخت پروژه یافت نشد'
|
||||
})
|
||||
}
|
||||
// ایجاد شیء پروژه
|
||||
const projectData = {
|
||||
title,
|
||||
expertise,
|
||||
sub_expertise,
|
||||
gender,
|
||||
age,
|
||||
conversation_projects,
|
||||
province,
|
||||
city,
|
||||
offer_time,
|
||||
offer_price,
|
||||
description,
|
||||
project_type,
|
||||
creator_id: user._id, // ارتباط با کاربر
|
||||
public_status,
|
||||
created_for_user
|
||||
}
|
||||
|
||||
// ساخت شیء پروژه
|
||||
const project = new ProjectModel(projectData)
|
||||
|
||||
// ذخیره کردن رکورد در پایگاه داده
|
||||
await project.save()
|
||||
|
||||
res.status(201).json({ message: 'پروژه با موفقیت ساخته شد', id: project._id }
|
||||
)
|
||||
} else {
|
||||
if (number_of_person > 1) {
|
||||
for (let i = 1; i <= number_of_person; i++) {
|
||||
let currentProjectType
|
||||
|
||||
if (project_type === 'free' && i === 1) {
|
||||
currentProjectType = 'free'
|
||||
} else if (project_type === 'highlight' || project_type === 'force') {
|
||||
currentProjectType = project_type
|
||||
} else {
|
||||
currentProjectType = 'normal'
|
||||
}
|
||||
|
||||
const newProject = new ProjectModel({
|
||||
title: `${title}_${i}`,
|
||||
expertise,
|
||||
sub_expertise,
|
||||
gender,
|
||||
age,
|
||||
conversation_projects,
|
||||
province: provinceFind,
|
||||
city: cityFind,
|
||||
offer_time,
|
||||
offer_price,
|
||||
description,
|
||||
creator_id: user._id, // ارتباط با کاربر
|
||||
project_type: currentProjectType,
|
||||
public_status,
|
||||
created_for_user
|
||||
})
|
||||
|
||||
// ذخیره کردن رکورد در پایگاه داده
|
||||
await newProject.save()
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
message: 'پروژهها با موفقیت ساخته شدند'
|
||||
})
|
||||
} else {
|
||||
const project = new ProjectModel({
|
||||
title,
|
||||
expertise,
|
||||
sub_expertise,
|
||||
gender,
|
||||
age,
|
||||
conversation_projects,
|
||||
province: provinceFind,
|
||||
city: cityFind,
|
||||
offer_time,
|
||||
offer_price,
|
||||
description,
|
||||
creator_id: user._id, // ارتباط با کاربر
|
||||
project_type
|
||||
})
|
||||
|
||||
// ذخیره کردن رکورد در پایگاه داده
|
||||
await project.save()
|
||||
|
||||
res.status(201).json({
|
||||
message: 'پروژه با موفقیت ساخته شد',
|
||||
id: project._id
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createProjectValidationRules,
|
||||
createProject
|
||||
}
|
||||
406
controllers/application/projects/getProjectController.js
Normal file
406
controllers/application/projects/getProjectController.js
Normal file
@@ -0,0 +1,406 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { default: mongoose } = require('mongoose')
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const RequestModel = require('../../../models/RequestModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const calculateRemainingTime = (milliseconds) => {
|
||||
const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24))
|
||||
const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
|
||||
|
||||
return `${days} روز و ${hours} ساعت`
|
||||
}
|
||||
const getProjects = async (req, res, next) => {
|
||||
try {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { expertise, page = 1, limit = 10, most_price, most_requests, age, gender } = req.query
|
||||
// ساخت فیلتر برای استفاده در جستجوی MongoDB
|
||||
const filter = {
|
||||
payment_status: 'done',
|
||||
status: 'accepted',
|
||||
public_status: 'public',
|
||||
$or: [
|
||||
{ isExpired: false }, // پروژههایی که isExpired برابر با false است
|
||||
{ isExpired: { $exists: false } } // پروژههایی که فیلد isExpired وجود ندارد
|
||||
]
|
||||
}
|
||||
if (expertise) {
|
||||
filter.expertise = expertise
|
||||
}
|
||||
if (age) {
|
||||
filter.age = age
|
||||
}
|
||||
if (gender) {
|
||||
filter.gender = gender
|
||||
}
|
||||
// استفاده از مدل پیجینیت شده برای دریافت نتایج صفحهبندی شده
|
||||
|
||||
if (most_price || most_requests) {
|
||||
// دریافت لیست پروژهها با استفاده از فیلتر و گزینههای صفحهبندی
|
||||
const options = {
|
||||
page: parseInt(page), // تبدیل صفحه به عدد صحیح
|
||||
limit: parseInt(limit), // تبدیل محدودیت به عدد صحیح
|
||||
sort: most_price === 'true' ? { offer_price: -1 } : { requested_users: 'desc' }
|
||||
}
|
||||
const projects = await ProjectModel.paginate(filter, options)
|
||||
|
||||
const newProj = projects.docs
|
||||
res.status(200).json({
|
||||
projects: newProj,
|
||||
totalPages: projects.totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems: projects.totalDocs // ارسال تعداد کل آیتمها
|
||||
})
|
||||
} else {
|
||||
// دریافت لیست پروژهها با استفاده از فیلتر و گزینههای صفحهبندی
|
||||
const options = {
|
||||
page: parseInt(page), // تبدیل صفحه به عدد صحیح
|
||||
limit: parseInt(limit), // تبدیل محدودیت به عدد صحیح
|
||||
sort: { acceptedAt: -1, createdAt: -1 } // مرتبسازی بر اساس acceptedAt و سپس createdAt
|
||||
}
|
||||
const projects = await ProjectModel.paginate(filter, options)
|
||||
const newProj = projects.docs
|
||||
res.status(200).json({
|
||||
projects: newProj,
|
||||
totalPages: projects.totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems: projects.totalDocs // ارسال تعداد کل آیتمها
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
// try {
|
||||
// const token = req.header('Authorization').split(' ')[1]
|
||||
// if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
// // eslint-disable-next-line no-unused-vars
|
||||
// const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
|
||||
// const { expertise, page = 1, limit = 10 } = req.query // افزودن پارامترهای صفحهبندی به درخواست
|
||||
|
||||
// // ساخت فیلتر برای استفاده در جستجوی MongoDB
|
||||
// const filter = {
|
||||
// payment_status: 'done',
|
||||
// status: 'accepted',
|
||||
// public_status: 'public'
|
||||
// } // افزودن شرطهای دیگر برای فیلتر
|
||||
|
||||
// if (expertise) {
|
||||
// filter.expertise = expertise
|
||||
// }
|
||||
|
||||
// // استفاده از مدل پیجینیت شده برای دریافت نتایج صفحهبندی شده
|
||||
// const options = {
|
||||
// page: parseInt(page), // تبدیل صفحه به عدد صحیح
|
||||
// limit: parseInt(limit) // تبدیل محدودیت به عدد صحیح
|
||||
// }
|
||||
|
||||
// // دریافت لیست پروژهها با استفاده از فیلتر و گزینههای صفحهبندی
|
||||
// const projects = await ProjectModel.paginate(filter, options)
|
||||
|
||||
// // تبدیل نتایج به فرمت مورد نیاز
|
||||
// const projectsWithUserInfo = await Promise.all(projects.docs.map(async project => {
|
||||
// const creator = await UserModel.findById(project.creator_id).select('first_name last_name user_name user_type is_verified profile_image gender province city rate')
|
||||
// const projectWithUserInfo = {
|
||||
// ...project.toJSON(), // تبدیل آبجکت پروژه به JSON
|
||||
// creator // اضافه کردن اطلاعات کاربر به آبجکت پروژه
|
||||
// }
|
||||
|
||||
// // محاسبه مدت زمان باقیمانده برای هر پروژه
|
||||
// const currentTime = new Date()
|
||||
// const projectCreatedAt = new Date(project.createdAt)
|
||||
// const timeDiff = project.offer_time * 24 * 60 * 60 * 1000 - (currentTime - projectCreatedAt)
|
||||
// const remainingTime = calculateRemainingTime(timeDiff)
|
||||
// projectWithUserInfo.remainingTime = remainingTime
|
||||
|
||||
// return projectWithUserInfo
|
||||
// }))
|
||||
|
||||
// res.status(200).json({
|
||||
// projects: projectsWithUserInfo,
|
||||
// totalPages: projects.totalPages, // ارسال تعداد کل صفحات
|
||||
// totalItems: projects.totalDocs // ارسال تعداد کل آیتمها
|
||||
// })
|
||||
// } catch (error) {
|
||||
// next(error)
|
||||
// }
|
||||
}
|
||||
const getUserProjects = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const user_id = req.query.user_id
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
// const userReqId = decodedToken.id
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(user_id)) {
|
||||
return res.status(400).json({ message: 'شناسه کاربر معتبر نیست' })
|
||||
}
|
||||
const user = await UserModel.findById(user_id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
const response = {}
|
||||
if (user.user_type === 'user') {
|
||||
const options = {
|
||||
page: req.query.page || 1, // صفحه پیشفرض ۱
|
||||
limit: req.query.limit || 10, // حداکثر تعداد موارد برای هر صفحه
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
// پیجینیشن بر روی لیست پروژههای کاربر اعمال میشود
|
||||
const projects = await ProjectModel.paginate(
|
||||
{ selected_user: user._id, status: 'done' }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
response.projects = projects.docs
|
||||
response.totalPages = projects.totalPages // ارسال تعداد کل صفحات
|
||||
response.totalItems = projects.totalDocs // ارسال تعداد کل آیتمها
|
||||
response.allProjectsCount = await ProjectModel.countDocuments({ selected_user: user._id })
|
||||
const completedProjects = await Promise.all([
|
||||
ProjectModel.countDocuments({ selected_user: user._id, status: 'done' })
|
||||
])
|
||||
response.successfulProjects = completedProjects
|
||||
} else {
|
||||
const options = {
|
||||
page: req.query.page || 1, // صفحه پیشفرض ۱
|
||||
limit: req.query.limit || 10, // حداکثر تعداد موارد برای هر صفحه
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
// پیجینیشن بر روی لیست پروژههای کاربر اعمال میشود
|
||||
const projects = await ProjectModel.paginate(
|
||||
{ creator_id: user._id, status: { $in: ['done', 'accepted'] } }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
response.projects = projects.docs
|
||||
response.totalPages = projects.totalPages // ارسال تعداد کل صفحات
|
||||
response.totalItems = projects.totalDocs
|
||||
response.allProjectsCount = await ProjectModel.countDocuments({ creator_id: user._id, payment_status: 'done' })
|
||||
// تعداد پروژههای موفق را به دست آورید
|
||||
const successfulProjects = await ProjectModel.countDocuments({ creator_id: user._id, status: 'done' })
|
||||
response.successfulProjects = successfulProjects
|
||||
}
|
||||
return res.status(200).json({ data: response })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const getUserProjectsWeb = async (req, res, next) => {
|
||||
try {
|
||||
const user_id = req.query.user_id
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(user_id)) {
|
||||
return res.status(400).json({ message: 'شناسه کاربر معتبر نیست' })
|
||||
}
|
||||
const user = await UserModel.findById(user_id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
const data = {}
|
||||
if (user.user_type === 'user') {
|
||||
const options = {
|
||||
page: req.query.page || 1, // صفحه پیشفرض ۱
|
||||
limit: req.query.limit || 10, // حداکثر تعداد موارد برای هر صفحه
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
// پیجینیشن بر روی لیست پروژههای کاربر اعمال میشود
|
||||
const projects = await ProjectModel.paginate(
|
||||
{ selected_user: user._id, status: 'done' }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
data.projects = projects.docs
|
||||
data.totalPages = projects.totalPages // ارسال تعداد کل صفحات
|
||||
data.totalItems = projects.totalDocs // ارسال تعداد کل آیتمها
|
||||
data.allProjectsCount = await ProjectModel.countDocuments({ selected_user: user._id })
|
||||
const completedProjects = await Promise.all([
|
||||
ProjectModel.countDocuments({ selected_user: user._id, status: 'done' })
|
||||
])
|
||||
data.successfulProjects = completedProjects
|
||||
} else {
|
||||
const options = {
|
||||
page: req.query.page || 1, // صفحه پیشفرض ۱
|
||||
limit: req.query.limit || 10, // حداکثر تعداد موارد برای هر صفحه
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
// پیجینیشن بر روی لیست پروژههای کاربر اعمال میشود
|
||||
const projects = await ProjectModel.paginate(
|
||||
{ creator_id: user._id, status: { $in: ['done', 'accepted'] } }, // فیلتر
|
||||
options // گزینههای پیجینیشن
|
||||
)
|
||||
data.projects = projects.docs
|
||||
data.totalPages = projects.totalPages // ارسال تعداد کل صفحات
|
||||
data.totalItems = projects.totalDocs
|
||||
data.allProjectsCount = await ProjectModel.countDocuments({ creator_id: user._id, payment_status: 'done' })
|
||||
// تعداد پروژههای موفق را به دست آورید
|
||||
const successfulProjects = await ProjectModel.countDocuments({ creator_id: user._id, status: 'done' })
|
||||
data.successfulProjects = successfulProjects
|
||||
}
|
||||
return res.status(200).json(data)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const getSingleProjects = 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 projectId = req.params.projectId
|
||||
const projectRequests = await RequestModel.find({ project: projectId })
|
||||
.populate('user', 'price time user_level first_name last_name user_name is_verified user_score rate profile_image')
|
||||
.sort({ status: 1 })
|
||||
.lean()
|
||||
const modifiedRequests = projectRequests.map(request => ({
|
||||
...request,
|
||||
time: request.user._id.toString() === userId ? request.time : null,
|
||||
price: request.user._id.toString() === userId ? request.price : null
|
||||
}))
|
||||
const project = await ProjectModel.findById(projectId)
|
||||
if (!project) {
|
||||
return res.status(404).json({ message: 'پروژه مورد نظر یافت نشد' })
|
||||
}
|
||||
// محاسبه مبلغ باقیمانده و مجموع مبلغ پرداخت شده
|
||||
// تابع محاسبه مبلغ پرداخت شده
|
||||
const getTotalPaidAmount = (installments) => {
|
||||
let totalPaidAmount = 0
|
||||
installments.forEach(installment => {
|
||||
totalPaidAmount += installment.amount
|
||||
})
|
||||
return totalPaidAmount
|
||||
}
|
||||
|
||||
// تابع محاسبه مبلغ باقیمانده
|
||||
const getRemainingAmount = (project) => {
|
||||
const totalPaidAmount = getTotalPaidAmount(project.installments)
|
||||
const remainingAmount = project.final_price - totalPaidAmount
|
||||
return remainingAmount
|
||||
}
|
||||
|
||||
const totalPaidAmount = getTotalPaidAmount(project.installments)
|
||||
const remainingAmount = getRemainingAmount(project)
|
||||
// اطلاعات کاربر سازنده را نیز دریافت کنید و به آبجکت پروژه اضافه کنید
|
||||
const creator = await UserModel.findById(project.creator_id).select('first_name last_name user_name user_type is_verified profile_image gender province city rate')
|
||||
|
||||
const projectWithUserInfo = {
|
||||
...project._doc,
|
||||
creator // اضافه کردن اطلاعات کاربر به آبجکت پروژه
|
||||
}
|
||||
const selectedUser = await UserModel.findById(project.selected_user).select('_id user_name first_name last_name user_type is_verified profile_image expertise sub_expertise province city rate user_level user_score')
|
||||
projectWithUserInfo.selected_user = selectedUser
|
||||
// محاسبه مدت زمان باقیمانده برای پروژه
|
||||
const currentTime = new Date()
|
||||
const projectCreatedAt = new Date(project.createdAt)
|
||||
const timeDiff = project.offer_time * 24 * 60 * 60 * 1000 - (currentTime - projectCreatedAt)
|
||||
const remainingTime = calculateRemainingTime(timeDiff)
|
||||
projectWithUserInfo.remainingTime = remainingTime
|
||||
let projectDetails = projectWithUserInfo
|
||||
if ((project.selected_user && project.selected_user.toString()) === userId || project.creator_id.toString() === userId) {
|
||||
projectDetails = {
|
||||
...projectWithUserInfo,
|
||||
total_paid_amount: totalPaidAmount,
|
||||
remaining_amount: remainingAmount
|
||||
}
|
||||
} else {
|
||||
projectDetails =
|
||||
projectWithUserInfo
|
||||
}
|
||||
// }
|
||||
res.status(200).json({ project: projectDetails, projectRequests: modifiedRequests })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const getSingleProjectsWeb = async (req, res, next) => {
|
||||
try {
|
||||
let userId = null
|
||||
let token = req.header('Authorization')
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
token = token.split(' ')[1]
|
||||
if (token) {
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
userId = decodedToken.id
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Invalid token:', err.message)
|
||||
}
|
||||
}
|
||||
|
||||
const projectId = req.params.projectId
|
||||
const projectRequests = await RequestModel.find({ project: projectId })
|
||||
.populate('user', 'price time user_level first_name last_name user_name is_verified user_score rate profile_image')
|
||||
.sort({ status: 1 })
|
||||
.lean()
|
||||
|
||||
// اگر کاربر لاگین کرده باشد، زمان و قیمت را نشان بده، در غیر این صورت حذف کن
|
||||
const modifiedRequests = projectRequests.map(request => ({
|
||||
...request,
|
||||
time: userId && request.user._id.toString() === userId ? request.time : null,
|
||||
price: userId && request.user._id.toString() === userId ? request.price : null
|
||||
}))
|
||||
|
||||
const project = await ProjectModel.findById(projectId)
|
||||
if (!project) {
|
||||
return res.status(404).json({ message: 'پروژه مورد نظر یافت نشد' })
|
||||
}
|
||||
|
||||
// محاسبه مبلغ باقیمانده و مجموع مبلغ پرداخت شده
|
||||
const getTotalPaidAmount = (installments) => {
|
||||
return installments.reduce((total, installment) => total + installment.amount, 0)
|
||||
}
|
||||
|
||||
const getRemainingAmount = (project) => {
|
||||
return project.final_price - getTotalPaidAmount(project.installments)
|
||||
}
|
||||
|
||||
const totalPaidAmount = getTotalPaidAmount(project.installments)
|
||||
const remainingAmount = getRemainingAmount(project)
|
||||
|
||||
// دریافت اطلاعات سازنده پروژه
|
||||
const creator = await UserModel.findById(project.creator_id).select('first_name last_name user_name user_type is_verified profile_image gender province city rate')
|
||||
|
||||
const projectWithUserInfo = {
|
||||
...project._doc,
|
||||
creator
|
||||
}
|
||||
|
||||
// دریافت اطلاعات کاربر منتخب در صورت وجود
|
||||
const selectedUser = await UserModel.findById(project.selected_user).select('_id user_name first_name last_name user_type is_verified profile_image expertise sub_expertise province city rate user_level user_score')
|
||||
projectWithUserInfo.selected_user = selectedUser
|
||||
|
||||
// محاسبه زمان باقیمانده برای پیشنهادات پروژه
|
||||
const currentTime = new Date()
|
||||
const projectCreatedAt = new Date(project.createdAt)
|
||||
const timeDiff = project.offer_time * 24 * 60 * 60 * 1000 - (currentTime - projectCreatedAt)
|
||||
projectWithUserInfo.remainingTime = calculateRemainingTime(timeDiff)
|
||||
|
||||
let projectDetails = projectWithUserInfo
|
||||
|
||||
// فقط اگر کاربر احراز هویت شده باشد و سازنده یا برنده پروژه باشد، اطلاعات مالی را برگردان
|
||||
if (userId && (project.selected_user?.toString() === userId || project.creator_id.toString() === userId)) {
|
||||
projectDetails = {
|
||||
...projectWithUserInfo,
|
||||
total_paid_amount: totalPaidAmount,
|
||||
remaining_amount: remainingAmount
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json({ project: projectDetails, projectRequests: modifiedRequests })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
// const projectDetails = await ProjectModel.findById(projectId).populate('requested_users', 'price time user_level first_name last_name user_name is_verified user_score rate profile_image') // جزییات درخواستهای کاربران
|
||||
|
||||
module.exports = {
|
||||
getProjects, getSingleProjects, getUserProjects, getUserProjectsWeb, getSingleProjectsWeb
|
||||
}
|
||||
41
controllers/application/projects/projectTypesController.js
Normal file
41
controllers/application/projects/projectTypesController.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const TypeAndPriceModel = require('../../../models/TypeAndPriceModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
|
||||
const getProjectTypes = 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 projectTypes = await TypeAndPriceModel.find({}, 'name price')
|
||||
|
||||
// بررسی برای مواردی مانند پیدا نشدن انواع پروژه
|
||||
if (!projectTypes) {
|
||||
return res.status(404).json({ message: 'انواع پروژه یافت نشد.' })
|
||||
}
|
||||
// اگر کاربر درخواست رایگان روزانه نداشته باشد، نوع پروژه رایگان را حذف کنید
|
||||
if (user.daily_free_request <= 0) {
|
||||
projectTypes = projectTypes.filter(projectType => projectType.name !== 'free')
|
||||
}
|
||||
// ارسال انواع پروژه به کاربر
|
||||
res.status(200).json({ projectTypes })
|
||||
} catch (error) {
|
||||
// در صورت بروز خطا، ارسال پیام خطا به کاربر
|
||||
console.error('Error in getProjectTypes:', error)
|
||||
res.status(500).json({ message: 'خطا در دریافت انواع پروژه.' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getProjectTypes
|
||||
}
|
||||
195
controllers/application/projects/requestProjectController.js
Normal file
195
controllers/application/projects/requestProjectController.js
Normal file
@@ -0,0 +1,195 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { default: axios } = require('axios')
|
||||
const NotificationModel = require('../../../models/NotificationModel')
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const RequestModel = require('../../../models/RequestModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const requestProjectValidationRules = () => {
|
||||
return [
|
||||
check('project_id')
|
||||
.notEmpty().withMessage('آیدی پروژه نمیتواند خالی باشد'),
|
||||
check('request_time')
|
||||
.notEmpty().withMessage('زمان پروژه نمیتواند خالی باشد'),
|
||||
|
||||
check('offer_price')
|
||||
.notEmpty().withMessage('قیمت پیشنهادی نمیتواند خالی باشد')
|
||||
]
|
||||
}
|
||||
|
||||
const requestProject = 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 user = await UserModel.findById(decodedToken.id)
|
||||
if (!user || user.user_type !== 'user') {
|
||||
return res.status(422).json({
|
||||
error: true, message: 'شما دسترسی به این بخش ندارید'
|
||||
})
|
||||
}
|
||||
if (user.is_verified !== 'verified') {
|
||||
return res.status(422).json({
|
||||
error: true, message: 'مدارک شما تایید نشده است'
|
||||
})
|
||||
}
|
||||
// اعتبارسنجی درخواست
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
const { request_time, offer_price, project_id } = req.body
|
||||
if (!project_id || !offer_price || !request_time
|
||||
) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
// چک کردن برای وجود درخواست قبلی
|
||||
const existingRequest = await RequestModel.findOne({ project: project_id, user: user._id })
|
||||
if (existingRequest) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما قبلاً برای این پروژه درخواست دادهاید'
|
||||
})
|
||||
}
|
||||
// ایجاد یک درخواست جدید
|
||||
const request = new RequestModel({
|
||||
project: project_id,
|
||||
user: user._id,
|
||||
time: request_time,
|
||||
price: offer_price
|
||||
})
|
||||
await request.save()
|
||||
|
||||
// ثبت اعلان برای سازنده پروژه
|
||||
const project = await ProjectModel.findById(project_id)
|
||||
const creatorId = project.creator_id
|
||||
|
||||
const notification = new NotificationModel({
|
||||
user_id: creatorId,
|
||||
project_post_id: project_id,
|
||||
type: 'request',
|
||||
title: 'پیشنهاد جدید',
|
||||
description: `کاربر ${user.user_name} یک پیشنهاد جدید برای پروژه شما ارسال کرده است.`
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
const userReciver = await UserModel.findById(project.creator_id)
|
||||
|
||||
const data = JSON.stringify({
|
||||
mobile: userReciver?.mobile,
|
||||
templateId: '876533',
|
||||
parameters: [
|
||||
{ name: 'EMPLOYER', value: userReciver?.first_name + ' ' + userReciver?.last_name },
|
||||
{ name: 'PROJECT', value: project?.title }
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
// به روز رسانی پروژه با افزودن کاربر به لیست کاربران درخواست دهنده
|
||||
const updatedProject = await ProjectModel.findOneAndUpdate(
|
||||
{ _id: project_id },
|
||||
{ $push: { requested_users: user._id } },
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
if (!updatedProject) {
|
||||
console.error('Error adding requested user: Project not found')
|
||||
return res.status(404).json({ error: true, message: 'پروژه پیدا نشد' })
|
||||
}
|
||||
|
||||
res.status(201).json({ message: 'درخواست با موفقیت شد', id: project_id }
|
||||
)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const editRequestProject = 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 user = await UserModel.findById(decodedToken.id)
|
||||
if (!user || user.user_type !== 'user') {
|
||||
return res.status(422).json({
|
||||
error: true, message: 'شما دسترسی به این بخش ندارید'
|
||||
})
|
||||
}
|
||||
if (user.is_verified !== 'verified') {
|
||||
return res.status(422).json({
|
||||
error: true, message: 'مدارک شما تایید نشده است'
|
||||
})
|
||||
}
|
||||
// اعتبارسنجی درخواست
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
const { request_time, offer_price, project_id } = req.body
|
||||
if (!project_id || !offer_price || !request_time
|
||||
) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
// چک کردن برای وجود درخواست قبلی
|
||||
const existingRequest = await RequestModel.findOne({ project: project_id, user: user._id })
|
||||
if (!existingRequest) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما قبلاً برای این پروژه درخواست ندادهاید'
|
||||
})
|
||||
}
|
||||
// اکنون میتوانید اطلاعات درخواست را ویرایش کنید
|
||||
existingRequest.time = request_time
|
||||
existingRequest.price = offer_price
|
||||
await existingRequest.save()
|
||||
|
||||
// ثبت اعلان برای سازنده پروژه
|
||||
const project = await ProjectModel.findById(project_id)
|
||||
const creatorId = project.creator_id
|
||||
|
||||
const notification = new NotificationModel({
|
||||
user_id: creatorId,
|
||||
project_post_id: project_id,
|
||||
type: 'request',
|
||||
title: 'ویرایش پیشنهاد',
|
||||
description: `کاربر ${user.user_name} پیشنهاد خود را ویرایش کرد.`
|
||||
})
|
||||
await notification.save()
|
||||
res.status(200).json({
|
||||
message: 'درخواست با موفقیت ویرایش شد',
|
||||
id: project_id
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const acceptProject = async (req, res, next) => { }
|
||||
module.exports = {
|
||||
requestProjectValidationRules,
|
||||
requestProject,
|
||||
acceptProject,
|
||||
editRequestProject
|
||||
}
|
||||
381
controllers/application/projects/updateProjectController.js
Normal file
381
controllers/application/projects/updateProjectController.js
Normal file
@@ -0,0 +1,381 @@
|
||||
/* eslint-disable camelcase */
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
|
||||
const NotificationModel = require('../../../models/NotificationModel')
|
||||
const CommentModel = require('../../../models/CommentModel')
|
||||
|
||||
const doneProject = 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 { project_id, comment, rate, user_id } = req.body
|
||||
if (!project_id || !comment || !rate || !user_id) {
|
||||
return res.status(400).send({ message: 'All fields are required' })
|
||||
}
|
||||
|
||||
const project = await ProjectModel.findOne({ _id: project_id, creator_id: userId })
|
||||
if (!project) {
|
||||
return res.status(403).send({ message: 'Access denied: You are not the creator of this project.' })
|
||||
}
|
||||
|
||||
const getTotalPaidAmount = (installments) => {
|
||||
return installments.reduce((total, installment) => total + installment.amount, 0)
|
||||
}
|
||||
|
||||
const getRemainingAmount = (project) => {
|
||||
const totalPaidAmount = getTotalPaidAmount(project.installments)
|
||||
return project.final_price - totalPaidAmount
|
||||
}
|
||||
|
||||
const remainingAmount = getRemainingAmount(project)
|
||||
if (remainingAmount !== 0) {
|
||||
return res.status(422).send({ message: 'مبلغ کامل پرداخت نشده است' })
|
||||
}
|
||||
|
||||
project.status = 'done'
|
||||
await project.save()
|
||||
|
||||
const newComment = new CommentModel({
|
||||
user: user_id,
|
||||
project: project_id,
|
||||
creator: userId,
|
||||
rating: rate,
|
||||
comment,
|
||||
comment_for: 'user',
|
||||
status: 'pending'
|
||||
})
|
||||
await newComment.save()
|
||||
|
||||
const user = await UserModel.findById(user_id)
|
||||
|
||||
// پیدا کردن کامنتها و محاسبهی امتیاز کل
|
||||
const comments = await CommentModel.find({ user: user_id, comment_for: 'user' })
|
||||
const totalRating = Number(comments.reduce((acc, comment) => acc + comment.rating, 0))
|
||||
|
||||
let userLevel
|
||||
if (user.expertise === 'مدل') {
|
||||
if (totalRating <= 50) userLevel = 'تازه وارد'
|
||||
else if (totalRating <= 100) userLevel = 'استاندارد'
|
||||
else if (totalRating <= 300) userLevel = 'حرفهای'
|
||||
else userLevel = 'استاد'
|
||||
} else if (['زیبایی', 'عکاس'].includes(user.expertise)) {
|
||||
if (totalRating <= 50) userLevel = 'تازه وارد'
|
||||
else if (totalRating <= 100) userLevel = 'استاندارد'
|
||||
else if (totalRating <= 300) userLevel = 'حرفهای'
|
||||
else userLevel = 'استاد'
|
||||
}
|
||||
|
||||
user.user_score = totalRating
|
||||
user.user_level = userLevel
|
||||
|
||||
const totalComments = await CommentModel.countDocuments({ user: user_id, comment_for: 'user' })
|
||||
const averageRating = totalRating / totalComments
|
||||
user.rate = averageRating.toFixed(1)
|
||||
await user.save()
|
||||
|
||||
const notification = new NotificationModel({
|
||||
user_id: user._id,
|
||||
project_post_id: project_id,
|
||||
type: 'end_project',
|
||||
title: 'اتمام پروژه',
|
||||
description: `کارفرما پروژه ${project?.title} را به وضعیت انجام شده تغییر داد و برای شما نظر ثبت کرد(برای ثبت نظر لمس کنید)
|
||||
نظر کارفرما : ${comment}
|
||||
`
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
res.status(200).json({ message: 'پروژه با موفقیت تغییر یافت شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const doneProjectWeb = 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 { project_id, comment, rate, user_id } = req.body
|
||||
if (!project_id || !comment || !rate || !user_id) {
|
||||
return res.status(400).send({ message: 'All fields are required' })
|
||||
}
|
||||
|
||||
const project = await ProjectModel.findOne({ _id: project_id, creator_id: userId })
|
||||
if (!project) {
|
||||
return res.status(403).send({ message: 'Access denied: You are not the creator of this project.' })
|
||||
}
|
||||
|
||||
project.status = 'done'
|
||||
await project.save()
|
||||
|
||||
const newComment = new CommentModel({
|
||||
user: user_id,
|
||||
project: project_id,
|
||||
creator: userId,
|
||||
rating: rate,
|
||||
comment,
|
||||
comment_for: 'user',
|
||||
status: 'pending'
|
||||
})
|
||||
await newComment.save()
|
||||
|
||||
const user = await UserModel.findById(user_id)
|
||||
|
||||
// پیدا کردن کامنتها و محاسبهی امتیاز کل
|
||||
const comments = await CommentModel.find({ user: user_id, comment_for: 'user' })
|
||||
const totalRating = Number(comments.reduce((acc, comment) => acc + comment.rating, 0))
|
||||
|
||||
let userLevel
|
||||
if (user.expertise === 'مدل') {
|
||||
if (totalRating <= 50) userLevel = 'تازه وارد'
|
||||
else if (totalRating <= 100) userLevel = 'استاندارد'
|
||||
else if (totalRating <= 300) userLevel = 'حرفهای'
|
||||
else userLevel = 'استاد'
|
||||
} else if (['زیبایی', 'عکاس'].includes(user.expertise)) {
|
||||
if (totalRating <= 50) userLevel = 'تازه وارد'
|
||||
else if (totalRating <= 100) userLevel = 'استاندارد'
|
||||
else if (totalRating <= 300) userLevel = 'حرفهای'
|
||||
else userLevel = 'استاد'
|
||||
}
|
||||
|
||||
user.user_score = totalRating
|
||||
user.user_level = userLevel
|
||||
|
||||
const totalComments = await CommentModel.countDocuments({ user: user_id, comment_for: 'user' })
|
||||
const averageRating = totalRating / totalComments
|
||||
user.rate = averageRating.toFixed(1)
|
||||
await user.save()
|
||||
|
||||
const notification = new NotificationModel({
|
||||
user_id: user._id,
|
||||
project_post_id: project_id,
|
||||
type: 'end_project',
|
||||
title: 'اتمام پروژه',
|
||||
description: `کارفرما پروژه ${project?.title} را به وضعیت انجام شده تغییر داد و برای شما نظر ثبت کرد(برای ثبت نظر لمس کنید)
|
||||
نظر کارفرما : ${comment}
|
||||
`
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
res.status(200).json({ message: 'پروژه با موفقیت تغییر یافت شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const cancleProject = 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 { project_id, comment, rate, user_id } = req.body
|
||||
|
||||
if (!project_id || !comment || !rate || !user_id) {
|
||||
return res.status(400).send({ message: 'All fields are required' })
|
||||
}
|
||||
|
||||
const project = await ProjectModel.findOne({ _id: project_id, creator_id: userId })
|
||||
if (!project) {
|
||||
return res.status(403).send({ message: 'Access denied: You are not the creator of this project.' })
|
||||
}
|
||||
|
||||
project.status = 'cancled'
|
||||
await project.save()
|
||||
|
||||
const newComment = new CommentModel({
|
||||
user: user_id,
|
||||
project: project_id,
|
||||
creator: userId,
|
||||
rating: rate,
|
||||
comment,
|
||||
comment_for: 'project',
|
||||
status: 'pending'
|
||||
})
|
||||
await newComment.save()
|
||||
|
||||
res.status(200).json({ message: 'پروژه با موفقیت تغییر یافت شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const editProjectValidationRules = () => {
|
||||
return [
|
||||
check('title').notEmpty().withMessage('عنوان نمیتواند خالی باشد'),
|
||||
|
||||
check('expertise').notEmpty().withMessage('تخصص نمیتواند خالی باشد'),
|
||||
|
||||
check('gender')
|
||||
.isIn(['male', 'female'])
|
||||
.withMessage('جنسیت باید male یا female باشد'),
|
||||
|
||||
check('age').notEmpty().withMessage('سن نمیتواند خالی باشد'),
|
||||
|
||||
check('conversation_projects')
|
||||
.notEmpty()
|
||||
.withMessage('پروژههای مکالمه نمیتواند خالی باشد')
|
||||
.isBoolean()
|
||||
.withMessage('مقدار پروژههای مکالمه باید یک مقدار boolean باشد'),
|
||||
|
||||
check('province').notEmpty().withMessage('استان نمیتواند خالی باشد'),
|
||||
|
||||
check('city').notEmpty().withMessage('شهر نمیتواند خالی باشد'),
|
||||
|
||||
check('offer_time')
|
||||
.notEmpty()
|
||||
.withMessage('زمان پروژه نمیتواند خالی باشد'),
|
||||
|
||||
check('offer_price')
|
||||
.notEmpty()
|
||||
.withMessage('قیمت پیشنهادی نمیتواند خالی باشد'),
|
||||
|
||||
check('description')
|
||||
.notEmpty()
|
||||
.withMessage('توضیحات نمیتواند خالی باشد')
|
||||
]
|
||||
}
|
||||
|
||||
const editProject = 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 user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما دسترسی به این بخش ندارید'
|
||||
})
|
||||
}
|
||||
if (user.is_verified !== 'verified') {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'مدارک شما تایید نشده است'
|
||||
})
|
||||
}
|
||||
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
expertise,
|
||||
sub_expertise,
|
||||
gender,
|
||||
age,
|
||||
conversation_projects,
|
||||
province,
|
||||
city,
|
||||
offer_time,
|
||||
offer_price,
|
||||
description,
|
||||
projectId
|
||||
} = req.body
|
||||
|
||||
const project = await ProjectModel.findById(projectId)
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'پروژه مورد نظر یافت نشد'
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (user._id.toString() != project.creator_id.toString()) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما نمیتوانید این پروژه را ویرایش کنید'
|
||||
})
|
||||
}
|
||||
if (project.status !== 'paid' && project.status !== 'pre_payment') {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما نمیتوانید این پروژه را ویرایش کنید'
|
||||
})
|
||||
}
|
||||
// اعتبارسنجی اطلاعات ویرایش شده
|
||||
if (
|
||||
!title ||
|
||||
!expertise ||
|
||||
!age ||
|
||||
conversation_projects === null ||
|
||||
!province ||
|
||||
!city ||
|
||||
!offer_time ||
|
||||
!offer_price ||
|
||||
!description
|
||||
) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
const provinceFind = await ProvinceModel.findOne(
|
||||
{ id: province },
|
||||
{ _id: 0 }
|
||||
)
|
||||
const cityFind = await CityModel.findOne({ id: city })
|
||||
// ویرایش یک نسخه از پروژه
|
||||
project.title = title
|
||||
project.expertise = expertise
|
||||
project.sub_expertise = sub_expertise
|
||||
project.gender = gender
|
||||
project.age = age
|
||||
project.conversation_projects = conversation_projects
|
||||
project.province = provinceFind
|
||||
project.city = cityFind
|
||||
project.offer_time = offer_time
|
||||
project.offer_price = offer_price
|
||||
project.description = description
|
||||
// ذخیره کردن تغییرات در پایگاه داده
|
||||
await project.save()
|
||||
res.status(200).json({
|
||||
message: 'پروژه با موفقیت ویرایش شد',
|
||||
id: project._id
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const setRateProject = 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 { project_id, comment, rate } = req.body
|
||||
const project = await ProjectModel.findOne({ _id: project_id, selected_user: userId })
|
||||
if (!project) {
|
||||
return res.status(403).send({ message: 'Access denied: You are not the creator of this project.' })
|
||||
}
|
||||
project.ratings.push({
|
||||
project_id,
|
||||
rating: rate,
|
||||
comment,
|
||||
creator_id: userId
|
||||
})
|
||||
await project.save()
|
||||
|
||||
res.status(200).json({ message: 'نظر شما با موفقیت ثبت شد.' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
doneProject, cancleProject, editProject, editProjectValidationRules, setRateProject, doneProjectWeb
|
||||
}
|
||||
15
controllers/application/provincesController.js
Normal file
15
controllers/application/provincesController.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { ProvinceModel } = require('../../models/StateCity')
|
||||
|
||||
const getProvinces = async (req, res, next) => {
|
||||
try {
|
||||
const provinces = await ProvinceModel.find({})
|
||||
res.status(200).json({
|
||||
provinces
|
||||
})
|
||||
} catch (err) {
|
||||
res.status(500).send('Internal Server Error')
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getProvinces
|
||||
}
|
||||
89
controllers/application/register/registerController.js
Normal file
89
controllers/application/register/registerController.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const { default: axios } = require('axios')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
// const { Token, VerificationCode } = require('sms-ir')
|
||||
|
||||
const registerValidationRules = () => {
|
||||
return [
|
||||
check('mobile')
|
||||
.notEmpty().withMessage('شماره موبایل نمیتواند خالی باشد')
|
||||
.isLength({ min: 11, max: 11 }).withMessage('شماره موبایل باید دقیقاً 11 رقم باشد')
|
||||
.matches(/^09[0-9]{9}$/).withMessage('فرمت شماره موبایل صحیح نیست')
|
||||
]
|
||||
}
|
||||
|
||||
const registerUser = async (req, res, next) => {
|
||||
try {
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
|
||||
const { mobile } = req.body
|
||||
if (!mobile) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شماره موبایل نمیتواند خالی باشد'
|
||||
})
|
||||
}
|
||||
|
||||
const existingUser = await UserModel.findOne({ mobile })
|
||||
if (existingUser && existingUser.national_code !== null) {
|
||||
return res.status(422).json({ error: true, message: 'شماره موبایل تکراری است' })
|
||||
}
|
||||
function generateOTP () {
|
||||
return Math.floor(100000 + Math.random() * 900000)
|
||||
}
|
||||
|
||||
const otp = generateOTP()
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const user = await UserModel.findOneAndUpdate(
|
||||
{ mobile },
|
||||
{ $set: { mobile, otp } },
|
||||
{ upsert: true, new: true, lean: true }
|
||||
)
|
||||
setTimeout(() => {
|
||||
UserModel.findOneAndUpdate({ mobile }, { $set: { otp: null } }, { new: true })
|
||||
.then(() => {})
|
||||
.catch(error => console.error('Error setting OTP to null:', error))
|
||||
}, 5 * 60 * 1000)
|
||||
const data = JSON.stringify({
|
||||
mobile,
|
||||
templateId: '930719',
|
||||
parameters: [
|
||||
{ name: 'CODE', value: otp.toString() }
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
// console.log(JSON.stringify(response.data))
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'کد تایید ارسال شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerValidationRules,
|
||||
registerUser
|
||||
}
|
||||
51
controllers/application/register/userFullNameController.js
Normal file
51
controllers/application/register/userFullNameController.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setUserFullNameValidationRules = () => {
|
||||
return [
|
||||
check('first_name')
|
||||
.notEmpty().withMessage('نام نمیتواند خالی باشد'),
|
||||
check('last_name')
|
||||
.notEmpty().withMessage('نام خانوادگی نمیتواند خالی باشد')
|
||||
]
|
||||
}
|
||||
|
||||
const setUserFullName = async (req, res, next) => {
|
||||
try {
|
||||
const { first_name, last_name } = req.body
|
||||
|
||||
// اعتبارسنجی دادههای ورودی
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
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 user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// آپدیت نام کاربری به صورت lowercase
|
||||
user.first_name = first_name.toLowerCase()
|
||||
user.last_name = last_name.toLowerCase()
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'نام و نام خانوادگی با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setUserFullName, setUserFullNameValidationRules
|
||||
}
|
||||
44
controllers/application/register/userTypeController.js
Normal file
44
controllers/application/register/userTypeController.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setUserType = async (req, res, next) => {
|
||||
try {
|
||||
const { user_type } = req.body
|
||||
if (!user_type || !['user', 'employer'].includes(user_type)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'نوع یوزر معتبر نیست'
|
||||
})
|
||||
}
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
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 user = await UserModel.findById(decodedToken.id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// آپدیت نوع یوزر
|
||||
user.user_type = user_type.toLowerCase()
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'نوع کاربر با موفقیت بهروزرسانی شد',
|
||||
id: user._id
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setUserType
|
||||
}
|
||||
95
controllers/application/register/usernameController.js
Normal file
95
controllers/application/register/usernameController.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setUserName = async (req, res, next) => {
|
||||
try {
|
||||
const { mobile, user_name } = req.body
|
||||
if (!mobile || !user_name) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const user = await UserModel.findOne({ mobile })
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
// بررسی حداقل طول نام کاربری
|
||||
if (user_name.length < 5) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'نام کاربری باید حداقل 5 کاراکتر باشد'
|
||||
})
|
||||
}
|
||||
// بررسی محدودیتهای مجاز در نام کاربری
|
||||
// const usernameRegex = /^[a-zA-Z0-9_]+$/
|
||||
// if (!usernameRegex.test(user_name)) {
|
||||
// return res.status(422).json({
|
||||
// error: true,
|
||||
// message: 'نام کاربری فقط میتواند شامل حروف الفبای انگلیسی، اعداد و کاراکتر _ باشد'
|
||||
// })
|
||||
// }
|
||||
// بررسی تکراری بودن user_name
|
||||
const existingUser = await UserModel.findOne({ user_name: { $regex: new RegExp('^' + user_name + '$', 'i') } })
|
||||
if (existingUser && existingUser._id.toString() !== user._id.toString()) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'نام کاربری تکراری است'
|
||||
})
|
||||
}
|
||||
|
||||
// آپدیت نام کاربری به صورت lowercase
|
||||
user.user_name = user_name.toLowerCase()
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'نام کاربری با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateUserName = 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_name } = req.body
|
||||
|
||||
if (!user_name) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const user = await UserModel.findByIdAndUpdate(userId, { user_name }, { new: true })
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'نام کاربری با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setUserName, updateUserName
|
||||
}
|
||||
47
controllers/application/register/verifyController.js
Normal file
47
controllers/application/register/verifyController.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const TokenService = require('../../../services/TokenService')
|
||||
// const jwt = require('jsonwebtoken')
|
||||
|
||||
const verifyUser = async (req, res, next) => {
|
||||
try {
|
||||
const { mobile, otp } = req.body
|
||||
if (!mobile || !otp) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const user = await UserModel.findOne({ mobile })
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
const token = TokenService.sign({ id: user._id })
|
||||
let step = ''
|
||||
if (user.user_name === null) { step = 'user_name' } else if
|
||||
(!user.password) { step = 'password' } else if
|
||||
(!user.first_name) { step = 'first_name' } else if
|
||||
(!user.user_type) { step = 'user_type' } else { step = 'profile_image' }
|
||||
if (user.otp === otp) {
|
||||
return res.json({
|
||||
message: 'کد تایید صحیح بود',
|
||||
token,
|
||||
step
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'کد تایید اشتباه است'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verifyUser
|
||||
}
|
||||
52
controllers/application/search-web/searchController.js
Normal file
52
controllers/application/search-web/searchController.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const getSearch = async (req, res, next) => {
|
||||
try {
|
||||
const { search, type, page = 1, limit = 10 } = req.query
|
||||
let result
|
||||
const selectedFields = '_id user_name first_name last_name user_type is_verified profile_image expertise rate user_level user_score'
|
||||
let filter
|
||||
let totalPages
|
||||
let totalItems
|
||||
if (search) {
|
||||
if (!type) {
|
||||
res.status(422).json({ success: false, message: 'نوع جستجو را مشخص کنید' })
|
||||
} else if (type === 'project') {
|
||||
// جستجو در مدل Project با فیلتر کردن برای user_type === "employer"
|
||||
|
||||
filter = { $or: [{ title: new RegExp(search, 'i'), status: 'accepted' }, { description: new RegExp(search, 'i'), status: 'accepted' }] }
|
||||
result = await ProjectModel.paginate(filter, { page: parseInt(page), limit: parseInt(limit), sort: { createdAt: -1 } })
|
||||
totalPages = result?.totalPages
|
||||
totalItems = result?.totalDocs
|
||||
} else if (type === 'user') {
|
||||
// جستجو در مدل User با فیلتر کردن برای user_type === "user"
|
||||
filter = { user_type: 'user', $or: [{ user_name: new RegExp(search, 'i') }, { first_name: new RegExp(search, 'i') }, { last_name: new RegExp(search, 'i') }] }
|
||||
result = await UserModel.paginate(filter, { page: parseInt(page), limit: parseInt(limit), select: selectedFields })
|
||||
totalPages = result?.totalPages
|
||||
totalItems = result?.totalDocs
|
||||
} else if (type === 'employer') {
|
||||
// جستجو در مدل User با فیلتر کردن برای user_type === "employer"
|
||||
filter = { user_type: 'employer', $or: [{ user_name: new RegExp(search, 'i') }, { first_name: new RegExp(search, 'i') }, { last_name: new RegExp(search, 'i') }] }
|
||||
result = await UserModel.paginate(filter, { page: parseInt(page), limit: parseInt(limit), select: selectedFields })
|
||||
totalPages = result?.totalPages
|
||||
totalItems = result?.totalDocs
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: result?.docs,
|
||||
type,
|
||||
totalPages,
|
||||
totalItems
|
||||
})
|
||||
} else {
|
||||
res.status(422).json({ success: false, message: 'چیزی تایپ کنید' })
|
||||
}
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSearch
|
||||
}
|
||||
51
controllers/application/search/searchController.js
Normal file
51
controllers/application/search/searchController.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const getSearch = async (req, res, next) => {
|
||||
try {
|
||||
const { search, type, page = 1, limit = 10 } = req.query
|
||||
let result
|
||||
const selectedFields = '_id user_name first_name last_name user_type is_verified profile_image expertise rate user_level user_score'
|
||||
let filter
|
||||
let totalPages
|
||||
let totalItems
|
||||
if (search) {
|
||||
if (!type) {
|
||||
res.status(422).json({ success: false, message: 'نوع جستجو را مشخص کنید' })
|
||||
} else if (type === 'project') {
|
||||
// جستجو در مدل Project با فیلتر کردن برای user_type === "employer"
|
||||
|
||||
filter = { $or: [{ title: new RegExp(search, 'i'), status: 'accepted' }, { description: new RegExp(search, 'i'), status: 'accepted' }] }
|
||||
result = await ProjectModel.paginate(filter, { page: parseInt(page), limit: parseInt(limit), sort: { createdAt: -1 } })
|
||||
totalPages = result?.totalPages
|
||||
totalItems = result?.totalDocs
|
||||
} else if (type === 'user') {
|
||||
// جستجو در مدل User با فیلتر کردن برای user_type === "user"
|
||||
filter = { user_type: 'user', $or: [{ user_name: new RegExp(search, 'i') }, { first_name: new RegExp(search, 'i') }, { last_name: new RegExp(search, 'i') }] }
|
||||
result = await UserModel.paginate(filter, { page: parseInt(page), limit: parseInt(limit), select: selectedFields })
|
||||
totalPages = result?.totalPages
|
||||
totalItems = result?.totalDocs
|
||||
} else if (type === 'employer') {
|
||||
// جستجو در مدل User با فیلتر کردن برای user_type === "employer"
|
||||
filter = { user_type: 'employer', $or: [{ user_name: new RegExp(search, 'i') }, { first_name: new RegExp(search, 'i') }, { last_name: new RegExp(search, 'i') }] }
|
||||
result = await UserModel.paginate(filter, { page: parseInt(page), limit: parseInt(limit), select: selectedFields })
|
||||
totalPages = result?.totalPages
|
||||
totalItems = result?.totalDocs
|
||||
}
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
result: result?.docs,
|
||||
type,
|
||||
totalPages,
|
||||
totalItems
|
||||
})
|
||||
} else {
|
||||
res.status(422).json({ success: false, message: 'چیزی تایپ کنید' })
|
||||
}
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSearch
|
||||
}
|
||||
19
controllers/application/settings/settingsController.js
Normal file
19
controllers/application/settings/settingsController.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// controllers/settingController.js
|
||||
|
||||
const SettingModel = require('../../../models/SettingsModel')
|
||||
|
||||
// متد برای دریافت تنظیمات بر اساس key
|
||||
const getSetting = async (req, res, next) => {
|
||||
try {
|
||||
const { key } = req.params
|
||||
const setting = await SettingModel.findOne({ key })
|
||||
if (!setting) {
|
||||
return res.status(404).json({ message: 'تنظیمات مورد نظر یافت نشد' })
|
||||
}
|
||||
res.status(200).json(setting)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getSetting }
|
||||
147
controllers/application/tickets/ticketsController.js
Normal file
147
controllers/application/tickets/ticketsController.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const TicketModel = require('../../../models/TicketModel')
|
||||
const TicketMessageModel = require('../../../models/TicketMessageModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
|
||||
const getUserTickets = async (req, res) => {
|
||||
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, search } = req.query // افزودن پارامترهای صفحهبندی به درخواست
|
||||
const filter = { user: userId }
|
||||
if (search) {
|
||||
filter.$or = [
|
||||
{ title: { $regex: search, $options: 'i' } }
|
||||
]
|
||||
}
|
||||
const options = {
|
||||
page: parseInt(page) || 1,
|
||||
limit: parseInt(limit) || 10,
|
||||
sort: { updatedAt: -1 }
|
||||
}
|
||||
const tickets = await TicketModel.paginate(filter, options)
|
||||
const ticketList = tickets?.docs.map(ticket => {
|
||||
const jDate = jMoment(ticket.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
return {
|
||||
...ticket._doc,
|
||||
createdAt: jDate
|
||||
}
|
||||
})
|
||||
res.json({
|
||||
tickets: ticketList,
|
||||
totalPages: tickets.totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems: tickets.totalDocs // ارسال تعداد کل آیتمها
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
const createTicket = async (req, res) => {
|
||||
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 { title } = req.body
|
||||
|
||||
const newTicket = new TicketModel({
|
||||
title,
|
||||
user: userId
|
||||
})
|
||||
await newTicket.save()
|
||||
|
||||
res.status(201).json(newTicket)
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
const addUserMessageToTicket = async (req, res) => {
|
||||
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 { ticketId } = req.query
|
||||
const { text } = req.body
|
||||
const { file } = req.files
|
||||
let fileUrl = null
|
||||
if (file) {
|
||||
// اگر فایل ارسال شده است، آن را ذخیره کنید
|
||||
const uploadDir = path.join(__dirname, '../../../../storage/tickets')
|
||||
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 = `/tickets/${uniqueFileName}`
|
||||
}
|
||||
const newMessage = new TicketMessageModel({
|
||||
text,
|
||||
senderType: 'User',
|
||||
senderId: userId,
|
||||
ticket: ticketId,
|
||||
file: fileUrl
|
||||
})
|
||||
await newMessage.save()
|
||||
|
||||
const ticket = await TicketModel.findById(ticketId)
|
||||
if (!ticket) {
|
||||
return res.status(404).json({ error: 'Ticket not found' })
|
||||
}
|
||||
|
||||
ticket.status = 'Customer Response'
|
||||
await ticket.save()
|
||||
const newMessageWithJalaliDate = {
|
||||
...newMessage._doc,
|
||||
createdAt: jMoment(newMessage.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
}
|
||||
res.status(201).json(newMessageWithJalaliDate)
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
const getTicketMessages = async (req, res) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { ticketId } = req.query
|
||||
const { page = 1, limit = 10 } = req.query
|
||||
const options = {
|
||||
page: parseInt(page) || 1,
|
||||
limit: parseInt(limit) || 10,
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
const messages = await TicketMessageModel.paginate({ ticket: ticketId }, options)
|
||||
const ticket = await TicketModel.findById(ticketId)
|
||||
ticket.new_message = false
|
||||
await ticket.save()
|
||||
const messageList = messages?.docs.map(message => {
|
||||
const jDate = jMoment(message.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
return {
|
||||
...message._doc,
|
||||
createdAt: jDate
|
||||
}
|
||||
})
|
||||
res.json({
|
||||
messages: messageList.reverse(),
|
||||
totalPages: messages.totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems: messages.totalDocs // ارسال تعداد کل آیتمها
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
module.exports = { getUserTickets, createTicket, addUserMessageToTicket, getTicketMessages }
|
||||
1378
controllers/application/users/getUserController.js
Normal file
1378
controllers/application/users/getUserController.js
Normal file
File diff suppressed because it is too large
Load Diff
112
controllers/application/verify/addressController.js
Normal file
112
controllers/application/verify/addressController.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setAddress = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { province_id, city_id, address, lat, lng, show_location } = req.body
|
||||
// اعتبارسنجی دادهها
|
||||
if (!province_id || !city_id || !address || !lat || !lng || show_location === 'undefined') {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی ناقص است'
|
||||
})
|
||||
}
|
||||
|
||||
// یافتن کاربر با شماره موبایل
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// یافتن استان و شهر
|
||||
const province = await ProvinceModel.findOne({ id: province_id })
|
||||
const city = await CityModel.findOne({ id: city_id })
|
||||
if (!province || !city) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'استان یا شهر موردنظر یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
user.province = province
|
||||
user.city = city
|
||||
user.address = address
|
||||
user.lat = lat
|
||||
user.lng = lng
|
||||
user.show_location = show_location
|
||||
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'آدرس با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const updateAddress = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { province_id, city_id, address, lat, lng, show_location } = req.body
|
||||
|
||||
// اعتبارسنجی دادهها
|
||||
if (!province_id || !city_id || !address || !lat || !lng || show_location === undefined) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی ناقص است'
|
||||
})
|
||||
}
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
|
||||
// یافتن کاربر با استفاده از شناسه کاربر
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// یافتن استان و شهر با استفاده از شناسه استان و شهر
|
||||
const province = await ProvinceModel.findOne({ id: province_id })
|
||||
const city = await CityModel.findOne({ id: city_id })
|
||||
if (!province || !city) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'استان یا شهر مورد نظر یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// بهروزرسانی اطلاعات آدرس کاربر
|
||||
user.province = province
|
||||
user.city = city
|
||||
user.address = address
|
||||
user.lat = lat
|
||||
user.lng = lng
|
||||
user.show_location = show_location
|
||||
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'آدرس با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setAddress, updateAddress
|
||||
}
|
||||
108
controllers/application/verify/authController.js
Normal file
108
controllers/application/verify/authController.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const saveAuthValidationRules = () => {
|
||||
return [
|
||||
check('shaba').notEmpty().withMessage('شماره شبا نمیتواند خالی باشد')
|
||||
.matches(/^(?=.{24}$)[0-9]*$/).withMessage('فرمت شماره شبا صحیح نیست'),
|
||||
check('birthday').notEmpty().withMessage('تاریخ تولد نمیتواند خالی باشد'),
|
||||
check('national_code').notEmpty().withMessage('کد ملی نمیتواند خالی باشد')
|
||||
.isLength({ min: 10, max: 10 }).withMessage('کد ملی باید 10 رقم باشد')
|
||||
]
|
||||
}
|
||||
|
||||
const saveAuth = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
const { shaba, birthday, national_code } = req.body
|
||||
if (!shaba || !birthday || !national_code) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// بررسی تکراری بودن کد ملی
|
||||
const existingUserWithNationalCode = await UserModel.findOne({ national_code })
|
||||
if (existingUserWithNationalCode && existingUserWithNationalCode._id.toString() !== user._id.toString()) {
|
||||
return res.status(409).json({
|
||||
error: true,
|
||||
message: 'کد ملی تکراری است'
|
||||
})
|
||||
}
|
||||
user.shaba = shaba
|
||||
user.birthday = birthday
|
||||
user.national_code = national_code
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'اطلاعات با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const updateShaba = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const errors = validationResult(req)
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(422).json({ errors: errors.array() })
|
||||
}
|
||||
|
||||
const { shaba } = req.body
|
||||
|
||||
if (!shaba) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شبا ارسال نشده است'
|
||||
})
|
||||
}
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
|
||||
// جستجوی کاربر با استفاده از شناسه کاربر
|
||||
const user = await UserModel.findById(userId)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// فقط شبا را بهروزرسانی کنید
|
||||
user.shaba = shaba
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'شبا با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
saveAuth, saveAuthValidationRules, updateShaba
|
||||
}
|
||||
80
controllers/application/verify/colorsController.js
Normal file
80
controllers/application/verify/colorsController.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setColors = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { eye_color, hair_color } = req.body
|
||||
if (!eye_color || !hair_color) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
user.eye_color = eye_color
|
||||
user.hair_color = hair_color
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'رنگ ها با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const updateColors = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { eye_color, hair_color } = req.body
|
||||
|
||||
// اعتبارسنجی دادهها
|
||||
if (!eye_color || !hair_color) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی ناقص است'
|
||||
})
|
||||
}
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
|
||||
// یافتن کاربر با استفاده از شناسه کاربر
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// بهروزرسانی اطلاعات رنگها
|
||||
user.eye_color = eye_color
|
||||
user.hair_color = hair_color
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'رنگها با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setColors, updateColors
|
||||
}
|
||||
74
controllers/application/verify/conversationController.js
Normal file
74
controllers/application/verify/conversationController.js
Normal file
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setConversation = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { bio, conversation_projects } = req.body
|
||||
if (!bio || conversation_projects === undefined) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
user.bio = bio
|
||||
user.conversation_projects = conversation_projects
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'روابط عمومی با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const updateConversation = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { bio, conversation_projects } = req.body
|
||||
|
||||
if (!bio || conversation_projects === undefined) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
// جستجوی کاربر با استفاده از شناسه کاربر
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
user.bio = bio
|
||||
user.conversation_projects = conversation_projects
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'روابط عمومی با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
setConversation, updateConversation
|
||||
}
|
||||
93
controllers/application/verify/cooperationTypeController.js
Normal file
93
controllers/application/verify/cooperationTypeController.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setCooperationType = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { cooperation_type, cooperation_abroad } = req.body
|
||||
if (!cooperation_type || cooperation_abroad === undefined) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
if (!['all', 'verified'].includes(cooperation_type)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'نوع همکاری نیست'
|
||||
})
|
||||
}
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
user.cooperation_type = cooperation_type
|
||||
user.cooperation_abroad = cooperation_abroad
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'نوع همکاری با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const updateCooperationType = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { cooperation_type, cooperation_abroad } = req.body
|
||||
|
||||
if (!cooperation_type || cooperation_abroad === undefined) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
if (!['all', 'verified'].includes(cooperation_type)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'نوع همکاری نامعتبر است'
|
||||
})
|
||||
}
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
|
||||
// جستجوی کاربر با استفاده از شناسه کاربر
|
||||
const user = await UserModel.findById(userId)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
user.cooperation_type = cooperation_type
|
||||
user.cooperation_abroad = cooperation_abroad
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'نوع همکاری با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setCooperationType,
|
||||
updateCooperationType
|
||||
}
|
||||
81
controllers/application/verify/expertiseController.js
Normal file
81
controllers/application/verify/expertiseController.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setExpertise = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { expertise, sub_expertise } = req.body
|
||||
if (!expertise || !sub_expertise) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// آپدیت نام کاربری به صورت lowercase
|
||||
user.expertise = expertise
|
||||
user.sub_expertise = sub_expertise
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'تخصص با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateExpertise = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { expertise, sub_expertise } = req.body
|
||||
|
||||
// اعتبارسنجی دادهها
|
||||
if (!expertise || !sub_expertise) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی ناقص است'
|
||||
})
|
||||
}
|
||||
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const userId = decodedToken.id
|
||||
|
||||
// یافتن کاربر با استفاده از شناسه کاربر
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شناسه یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// بهروزرسانی اطلاعات تخصص و زیرتخصص
|
||||
user.expertise = expertise
|
||||
user.sub_expertise = sub_expertise
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'تخصص با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
setExpertise, updateExpertise
|
||||
}
|
||||
46
controllers/application/verify/genderController.js
Normal file
46
controllers/application/verify/genderController.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setGender = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { gender } = req.body
|
||||
if (!gender) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ورودی اشتباه است'
|
||||
})
|
||||
}
|
||||
if (!['male', 'female'].includes(gender)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'جنسیت معتبر نیست'
|
||||
})
|
||||
}
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
// آپدیت نوع یوزر
|
||||
user.gender = gender.toLowerCase()
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'جنسیت با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setGender
|
||||
}
|
||||
107
controllers/application/verify/servicesController.js
Normal file
107
controllers/application/verify/servicesController.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/* eslint-disable camelcase */
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
const setServices = 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 user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'شما دسترسی به این بخش ندارید'
|
||||
})
|
||||
}
|
||||
const { services } = req.body
|
||||
if (!services) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
// مدیریت تصاویر برای خدمات
|
||||
const servicesWithImages = []
|
||||
const parsedServices = JSON.parse(services) // پارس کردن رشته JSON به آرایه جاوا اسکریپت
|
||||
if (parsedServices && parsedServices.length > 0) {
|
||||
for (const service of parsedServices) {
|
||||
const serviceImages = []
|
||||
const serviceId = service.id
|
||||
if (req.files && req.files.serviceImages && req.files.serviceImages[serviceId]) {
|
||||
const imageFiles = Array.isArray(req.files.serviceImages[serviceId]) ? req.files.serviceImages[serviceId] : [req.files.serviceImages[serviceId]]
|
||||
const uploadDir = path.join(__dirname, '../../../../storage/services')
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true })
|
||||
}
|
||||
for (const imageFile of imageFiles) {
|
||||
const uniqueFileName = `${user.user_name}-${Date.now()}${Math.random().toString().slice(2, 11)}${path.extname(imageFile.name)}`
|
||||
const filePath = path.join(uploadDir, uniqueFileName)
|
||||
// await fs.promises.rename(imageFile.path, filePath)
|
||||
await fs.move(imageFile.path, filePath)
|
||||
const imageUrl = `/services/${uniqueFileName}`
|
||||
serviceImages.push(imageUrl)
|
||||
// بهروزرسانی مسیر تصویر در آبجکت service
|
||||
service.image = imageUrl
|
||||
}
|
||||
} else {
|
||||
console.log('sss')
|
||||
}
|
||||
servicesWithImages.push({ ...service, images: serviceImages })
|
||||
}
|
||||
}
|
||||
|
||||
user.services = servicesWithImages
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'خدمات با موفقیت ثبت شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateServices = async (req, res, next) => {
|
||||
try {
|
||||
const { height, weight, size } = req.body
|
||||
|
||||
if (!height || !weight || !size) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
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(404).json({
|
||||
error: true,
|
||||
message: 'کاربر یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
user.height = height
|
||||
user.weight = weight
|
||||
user.size = size
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'سایز با موفقیت بهروزرسانی شد'
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
setServices, updateServices
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* eslint-disable camelcase */
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setNationalCardImage = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { national_card_image } = req.files
|
||||
// جستجوی کاربر با شماره موبایل ارسالی
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
// بررسی آیا فایل آپلود شده است
|
||||
if (!national_card_image) {
|
||||
return res.status(400).json({
|
||||
error: true,
|
||||
message: 'لطفاً عکس کارت ملی را انتخاب کنید'
|
||||
})
|
||||
}
|
||||
|
||||
// ذخیره فایل عکس پروفایل
|
||||
const uploadDir = path.join(__dirname, '../../../../storage/carts')
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true })
|
||||
}
|
||||
|
||||
const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(national_card_image.name)}`
|
||||
const filePath = path.join(uploadDir, uniqueFileName)
|
||||
await fs.move(national_card_image.path, filePath)
|
||||
|
||||
// ذخیره مسیر فایل در دیتابیس
|
||||
user.national_card_image = `/carts/${uniqueFileName}`
|
||||
user.is_verified = 'pending'
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'عکس کارت ملی با موفقیت ذخیره شد',
|
||||
national_card_image: user.national_card_image
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setNationalCardImage
|
||||
}
|
||||
105
controllers/application/verify/setProfileImageController.js
Normal file
105
controllers/application/verify/setProfileImageController.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/* eslint-disable camelcase */
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const setProfileImage = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const { mobile } = req.body
|
||||
const { profile_image } = req.files
|
||||
if (!mobile) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
// بررسی آیا فایل آپلود شده است
|
||||
if (!profile_image) {
|
||||
return res.status(400).json({
|
||||
error: true,
|
||||
message: 'لطفاً عکس پروفایل را انتخاب کنید'
|
||||
})
|
||||
}
|
||||
|
||||
// ذخیره فایل عکس پروفایل
|
||||
const uploadDir = path.join(__dirname, '../../../../storage/profiles')
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true })
|
||||
}
|
||||
const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(profile_image.name)}`
|
||||
const filePath = path.join(uploadDir, uniqueFileName)
|
||||
|
||||
await fs.move(profile_image.path, filePath)
|
||||
// ذخیره مسیر فایل در دیتابیس
|
||||
user.profile_image = `/profiles/${uniqueFileName}`
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'عکس پروفایل با موفقیت ذخیره شد',
|
||||
profile_image: user.profile_image
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateProfileImage = 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 { profile_image } = req.files
|
||||
|
||||
if (!profile_image) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const user = await UserModel.findById(userId)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: true,
|
||||
message: 'کاربری با این شماره موبایل یافت نشد'
|
||||
})
|
||||
}
|
||||
|
||||
const uploadDir = path.join(__dirname, '../../../../storage/profiles')
|
||||
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true })
|
||||
}
|
||||
|
||||
const uniqueFileName = `${user.user_name}-${Date.now()}${path.extname(profile_image.name)}`
|
||||
const filePath = path.join(uploadDir, uniqueFileName)
|
||||
|
||||
await fs.move(profile_image.path, filePath)
|
||||
|
||||
user.profile_image = `/profiles/${uniqueFileName}`
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
message: 'عکس پروفایل با موفقیت بهروزرسانی شد',
|
||||
profile_image: user.profile_image
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
setProfileImage, updateProfileImage
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user