full
This commit is contained in:
136
controllers/panel/academy/academyContentController.js
Normal file
136
controllers/panel/academy/academyContentController.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const AcademyContentModel = require('../../../models/AcademyContentModel')
|
||||
const CourseModel = require('../../../models/CourseModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const { default: mongoose } = require('mongoose')
|
||||
const { TEMP_ACCEPT } = require('../../../utils/moderationStatus')
|
||||
|
||||
const getAcademyContents = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { page = 1, limit = 10, status = TEMP_ACCEPT, courseId } = req.query
|
||||
const filter = {}
|
||||
|
||||
if (status && status !== 'all') {
|
||||
filter.status = status
|
||||
}
|
||||
if (courseId && mongoose.Types.ObjectId.isValid(courseId)) {
|
||||
filter.courseId = courseId
|
||||
}
|
||||
|
||||
const options = {
|
||||
page: parseInt(page, 10),
|
||||
limit: parseInt(limit, 10),
|
||||
sort: { createdAt: -1 },
|
||||
}
|
||||
|
||||
const result = await AcademyContentModel.paginate(filter, options)
|
||||
const courseIds = [
|
||||
...new Set(result.docs.map((item) => item.courseId).filter(Boolean)),
|
||||
]
|
||||
const courses = await CourseModel.find({ _id: { $in: courseIds } })
|
||||
.select('_id cuorse_name user_id')
|
||||
.lean()
|
||||
const courseMap = Object.fromEntries(
|
||||
courses.map((course) => [String(course._id), course])
|
||||
)
|
||||
|
||||
const items = result.docs.map((item) => ({
|
||||
_id: item._id,
|
||||
courseId: item.courseId,
|
||||
courseName: courseMap[String(item.courseId)]?.cuorse_name || '-',
|
||||
file_name: item.file_name || '-',
|
||||
type: item.type,
|
||||
status: item.status,
|
||||
is_free: item.is_free,
|
||||
course_video: item.course_video,
|
||||
course_images: item.course_images,
|
||||
createdAt: item.createdAt
|
||||
? jMoment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
: '-',
|
||||
}))
|
||||
|
||||
res.status(200).json({
|
||||
items,
|
||||
totalPages: result.totalPages,
|
||||
totalItems: result.totalDocs,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getAcademyContentDetail = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { contentId } = req.query
|
||||
if (!contentId || !mongoose.Types.ObjectId.isValid(contentId)) {
|
||||
return res.status(400).json({ message: 'شناسه محتوا معتبر نیست' })
|
||||
}
|
||||
|
||||
const item = await AcademyContentModel.findById(contentId).lean()
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'محتوا یافت نشد' })
|
||||
}
|
||||
|
||||
const course = await CourseModel.findById(item.courseId)
|
||||
.select('_id cuorse_name caption user_id')
|
||||
.lean()
|
||||
|
||||
res.status(200).json({
|
||||
item: {
|
||||
...item,
|
||||
createdAt: item.createdAt
|
||||
? jMoment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
: '-',
|
||||
course,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateAcademyContentStatus = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { id, status } = req.body
|
||||
if (!id || !status) {
|
||||
return res.status(422).json({ message: 'اطلاعات ناقص است' })
|
||||
}
|
||||
|
||||
if (!['accept', 'reject', 'temp_accept', 'pending'].includes(status)) {
|
||||
return res.status(422).json({ message: 'وضعیت نامعتبر است' })
|
||||
}
|
||||
|
||||
const item = await AcademyContentModel.findById(id)
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'محتوا یافت نشد' })
|
||||
}
|
||||
|
||||
item.status = status
|
||||
await item.save()
|
||||
|
||||
res.status(200).json({
|
||||
message:
|
||||
status === 'accept'
|
||||
? 'ویدیو با موفقیت تایید کامل شد'
|
||||
: status === 'reject'
|
||||
? 'ویدیو رد شد'
|
||||
: 'وضعیت ویدیو بهروزرسانی شد',
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAcademyContents,
|
||||
getAcademyContentDetail,
|
||||
updateAcademyContentStatus,
|
||||
}
|
||||
@@ -337,6 +337,38 @@ const getAdvertisingCategoryById = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PATCH /advertising/advertising-categories/:id
|
||||
*/
|
||||
const updateAdvertisingCategory = async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
if (!id || !id.match(/^[0-9a-fA-F]{24}$/)) {
|
||||
return res.status(400).json({ success: false, message: 'شناسه معتبر نیست' });
|
||||
}
|
||||
|
||||
const { title, status, relatedExpertise } = req.body;
|
||||
const update = {};
|
||||
if (typeof title !== 'undefined') update.title = title.trim();
|
||||
if (typeof status !== 'undefined') update.status = status;
|
||||
if (typeof relatedExpertise !== 'undefined') {
|
||||
if (!Array.isArray(relatedExpertise)) {
|
||||
return res.status(422).json({ success: false, message: 'اطلاعات ارسالی اشتباه است' });
|
||||
}
|
||||
update.relatedExpertise = relatedExpertise;
|
||||
}
|
||||
|
||||
const category = await AdvertisingCategoryModel.findByIdAndUpdate(id, update, { new: true });
|
||||
if (!category) {
|
||||
return res.status(404).json({ success: false, message: 'دستهبندی یافت نشد' });
|
||||
}
|
||||
|
||||
return res.status(200).json({ success: true, message: 'دستهبندی با موفقیت ویرایش شد', data: category });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
getSingleAdvertising,
|
||||
@@ -345,6 +377,7 @@ module.exports = {
|
||||
createAdvertisingCategory,
|
||||
getAdvertisingCategories,
|
||||
getAdvertisingCategoryById,
|
||||
updateAdvertisingCategory,
|
||||
acceptAdvertising,
|
||||
rejectAdvertising
|
||||
}
|
||||
|
||||
145
controllers/panel/comments/academyCommentController.js
Normal file
145
controllers/panel/comments/academyCommentController.js
Normal file
@@ -0,0 +1,145 @@
|
||||
const CourseComment = require('../../../models/CourseComentModel')
|
||||
const CourseModel = require('../../../models/CourseModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
|
||||
const getAcademyComments = async (req, res, next) => {
|
||||
try {
|
||||
const { page = 1, limit = 20, startDate, endDate, status } = req.query
|
||||
const filter = {}
|
||||
|
||||
if (startDate || endDate) {
|
||||
filter.createdAt = {}
|
||||
if (startDate) filter.createdAt.$gte = new Date(startDate)
|
||||
if (endDate) filter.createdAt.$lte = new Date(endDate)
|
||||
}
|
||||
if (status) filter.status = status
|
||||
|
||||
const comments = await CourseComment.paginate(filter, {
|
||||
page: parseInt(page, 10),
|
||||
limit: parseInt(limit, 10),
|
||||
sort: { createdAt: -1 },
|
||||
})
|
||||
|
||||
const courseIds = [...new Set(comments.docs.map((c) => String(c.course_id)).filter(Boolean))]
|
||||
const userIds = [...new Set(comments.docs.map((c) => String(c.user_id)).filter(Boolean))]
|
||||
|
||||
const [courses, users] = await Promise.all([
|
||||
CourseModel.find({ _id: { $in: courseIds } }).select('cuorse_name user_id').lean(),
|
||||
UserModel.find({ _id: { $in: userIds } }).select('user_name first_name last_name profile_image').lean(),
|
||||
])
|
||||
|
||||
const courseMap = new Map(courses.map((c) => [String(c._id), c]))
|
||||
const userMap = new Map(users.map((u) => [String(u._id), u]))
|
||||
|
||||
const ownerIds = [...new Set(courses.map((c) => String(c.user_id)).filter(Boolean))]
|
||||
const owners = await UserModel.find({ _id: { $in: ownerIds } })
|
||||
.select('user_name first_name last_name')
|
||||
.lean()
|
||||
const ownerMap = new Map(owners.map((u) => [String(u._id), u]))
|
||||
|
||||
const commentList = comments.docs.map((comment) => {
|
||||
const course = courseMap.get(String(comment.course_id))
|
||||
const user = userMap.get(String(comment.user_id))
|
||||
const creator = course ? ownerMap.get(String(course.user_id)) : null
|
||||
|
||||
return {
|
||||
_id: comment._id,
|
||||
createdAt: jMoment(comment.createdAt).format('jYYYY-jMM-jDD HH:mm'),
|
||||
status: comment.status,
|
||||
comment: comment.comment,
|
||||
rating: comment.rate,
|
||||
course: course?.cuorse_name || '-',
|
||||
user: user
|
||||
? {
|
||||
_id: user._id,
|
||||
user_name: user.user_name,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
}
|
||||
: null,
|
||||
creator: creator
|
||||
? {
|
||||
_id: creator._id,
|
||||
user_name: creator.user_name,
|
||||
first_name: creator.first_name,
|
||||
last_name: creator.last_name,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
comments: commentList,
|
||||
totalPages: comments.totalPages,
|
||||
totalItems: comments.totalDocs,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getAcademyCommentDetail = async (req, res, next) => {
|
||||
try {
|
||||
const { comment_id: commentId } = req.query
|
||||
if (!commentId) {
|
||||
return res.status(422).json({ error: true, message: 'شناسه کامنت الزامی است' })
|
||||
}
|
||||
|
||||
const comment = await CourseComment.findById(commentId).lean()
|
||||
if (!comment) {
|
||||
return res.status(404).json({ error: true, message: 'کامنت یافت نشد' })
|
||||
}
|
||||
|
||||
const [course, user] = await Promise.all([
|
||||
CourseModel.findById(comment.course_id).select('cuorse_name user_id').lean(),
|
||||
UserModel.findById(comment.user_id).select('user_name first_name last_name profile_image').lean(),
|
||||
])
|
||||
const creator = course?.user_id
|
||||
? await UserModel.findById(course.user_id).select('user_name first_name last_name').lean()
|
||||
: null
|
||||
|
||||
res.status(200).json({
|
||||
comment: {
|
||||
_id: comment._id,
|
||||
comment: comment.comment,
|
||||
rating: comment.rate,
|
||||
status: comment.status,
|
||||
createdAt: jMoment(comment.createdAt).format('jYYYY-jMM-jDD HH:mm'),
|
||||
course: course?.cuorse_name || '-',
|
||||
user,
|
||||
creator,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const acceptAcademyComment = async (req, res, next) => {
|
||||
try {
|
||||
const { id, status, text } = req.body
|
||||
if (!id || !status) {
|
||||
return res.status(422).json({ error: true, message: 'اطلاعات ارسالی اشتباه است' })
|
||||
}
|
||||
|
||||
const comment = await CourseComment.findById(id)
|
||||
if (!comment) {
|
||||
return res.status(404).json({ error: true, message: 'کامنت یافت نشد' })
|
||||
}
|
||||
|
||||
if (text) comment.comment = text
|
||||
comment.status = status
|
||||
await comment.save()
|
||||
|
||||
res.status(201).json({ message: 'وضعیت کامنت با موفقیت بهروزرسانی شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAcademyComments,
|
||||
getAcademyCommentDetail,
|
||||
acceptAcademyComment,
|
||||
}
|
||||
76
controllers/panel/licenses/licensesController.js
Normal file
76
controllers/panel/licenses/licensesController.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const mongoose = require('mongoose')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const LicenseModel = require('../../../models/license')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const { syncVerifyBadge } = require('../../../utils/verifyBadge')
|
||||
|
||||
const getAllLicensesPanel = async (req, res, next) => {
|
||||
try {
|
||||
const { page = 1, limit = 20 } = req.query
|
||||
|
||||
const options = {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
const result = await LicenseModel.paginate({}, options)
|
||||
const userIds = result.docs.map((doc) => doc.userId).filter(Boolean)
|
||||
const users = await UserModel.find({ _id: { $in: userIds } })
|
||||
.select('user_name first_name last_name is_verified profile_image')
|
||||
.lean()
|
||||
const userMap = new Map(users.map((u) => [String(u._id), u]))
|
||||
|
||||
const licenses = result.docs.map((doc) => ({
|
||||
_id: doc._id,
|
||||
userId: doc.userId,
|
||||
Confirmation: doc.Confirmation,
|
||||
licenseImg: doc.licenseImg,
|
||||
user: userMap.get(String(doc.userId)) || null,
|
||||
createdAt: jMoment(doc.createdAt).format('jYYYY-jMM-jDD HH:mm'),
|
||||
updatedAt: jMoment(doc.updatedAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
}))
|
||||
|
||||
res.status(200).json({
|
||||
licenses,
|
||||
totalPages: result.totalPages,
|
||||
totalItems: result.totalDocs
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateLicenseConfirmationPanel = async (req, res, next) => {
|
||||
try {
|
||||
const { userId } = req.params
|
||||
const { Confirmation } = req.body
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(userId)) {
|
||||
return res.status(400).json({ message: 'شناسه کاربر معتبر نیست' })
|
||||
}
|
||||
|
||||
const license = await LicenseModel.findOneAndUpdate(
|
||||
{ userId },
|
||||
{ Confirmation: Boolean(Confirmation) },
|
||||
{ new: true }
|
||||
)
|
||||
if (!license) return res.status(404).json({ message: 'مجوز یافت نشد' })
|
||||
|
||||
const user = await UserModel.findById(userId)
|
||||
if (user) {
|
||||
user.license_verified = Boolean(Confirmation)
|
||||
syncVerifyBadge(user)
|
||||
await user.save()
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'وضعیت مجوز با موفقیت تغییر کرد', license })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAllLicensesPanel,
|
||||
updateLicenseConfirmationPanel
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* eslint-disable camelcase */
|
||||
const mongoose = require('mongoose')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const ShopModel = require('../../../models/ShopModel')
|
||||
const ShopProductListingModel = require('../../../models/ShopProductListingModel')
|
||||
const { notifyUser } = require('../../../utils/notifyUser')
|
||||
|
||||
const SHOP_STATUS_NOTIFICATION = {
|
||||
active: { title: 'تایید فروشگاه', description: 'فروشگاه شما توسط پشتیبانی تایید و فعال شد.' },
|
||||
inactive: { title: 'غیرفعال شدن فروشگاه', description: 'فروشگاه شما توسط پشتیبانی غیرفعال شد.' },
|
||||
rejected: { title: 'رد ثبت فروشگاه', description: 'درخواست ثبت فروشگاه شما رد شد.' }
|
||||
}
|
||||
|
||||
const PRODUCT_STATUS_NOTIFICATION = {
|
||||
active: { title: 'تایید کالا', description: 'کالای ثبتشده شما توسط پشتیبانی تایید و فعال شد.' },
|
||||
inactive: { title: 'غیرفعال شدن کالا', description: 'کالای شما توسط پشتیبانی غیرفعال شد.' },
|
||||
rejected: { title: 'رد ثبت کالا', description: 'کالای ثبتشده شما رد شد.' }
|
||||
}
|
||||
|
||||
const getMarketplaceShops = async (req, res, next) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, status } = req.query
|
||||
const filter = {}
|
||||
if (status) filter.status = status
|
||||
|
||||
const options = {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
sort: { createdAt: -1 },
|
||||
populate: [{ path: 'owner', select: '_id user_name first_name last_name' }]
|
||||
}
|
||||
|
||||
const shops = await ShopModel.paginate(filter, options)
|
||||
const list = shops.docs.map((shop) => ({
|
||||
_id: shop._id,
|
||||
name: shop.name,
|
||||
logo: shop.logo,
|
||||
province: shop.province,
|
||||
city: shop.city,
|
||||
status: shop.status,
|
||||
owner_id: shop.owner?._id,
|
||||
owner_username: shop.owner?.user_name,
|
||||
owner_name: [shop.owner?.first_name, shop.owner?.last_name].filter(Boolean).join(' '),
|
||||
createdAt: jMoment(shop.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
}))
|
||||
|
||||
res.status(200).json({
|
||||
shops: list,
|
||||
totalPages: shops.totalPages,
|
||||
totalItems: shops.totalDocs
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getPendingMarketplaceProducts = async (req, res, next) => {
|
||||
try {
|
||||
const { page = 1, limit = 20 } = req.query
|
||||
|
||||
const options = {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
sort: { createdAt: -1 },
|
||||
populate: [{ path: 'shop', select: '_id name logo status' }]
|
||||
}
|
||||
|
||||
const result = await ShopProductListingModel.paginate({ status: 'pending_shop_approval' }, options)
|
||||
const list = result.docs.map((listing) => ({
|
||||
_id: listing._id,
|
||||
title: listing.title,
|
||||
image: listing.images?.[listing.primaryImageIndex] || listing.images?.[0] || null,
|
||||
shop: listing.shop
|
||||
? { _id: listing.shop._id, name: listing.shop.name, logo: listing.shop.logo }
|
||||
: null,
|
||||
createdAt: jMoment(listing.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
}))
|
||||
|
||||
res.status(200).json({
|
||||
products: list,
|
||||
totalPages: result.totalPages,
|
||||
totalItems: result.totalDocs
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getMarketplaceShopDetail = async (req, res, next) => {
|
||||
try {
|
||||
const shop = await ShopModel.findById(req.params.id).populate(
|
||||
'owner',
|
||||
'_id user_name first_name last_name mobile email profile_image is_verified verify_badge'
|
||||
)
|
||||
if (!shop) return res.status(404).json({ message: 'فروشگاه مورد نظر یافت نشد' })
|
||||
|
||||
const listings = await ShopProductListingModel.find({ shop: shop._id }).sort({ createdAt: -1 })
|
||||
|
||||
return res.status(200).json({ shop, listings })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateMarketplaceShopStatus = async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { status } = req.body
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).json({ message: 'شناسه فروشگاه معتبر نیست' })
|
||||
}
|
||||
if (!['active', 'inactive', 'rejected'].includes(status)) {
|
||||
return res.status(422).json({ message: 'وضعیت انتخابشده معتبر نیست' })
|
||||
}
|
||||
|
||||
const shop = await ShopModel.findByIdAndUpdate(id, { status }, { new: true })
|
||||
if (!shop) return res.status(404).json({ message: 'فروشگاه یافت نشد' })
|
||||
|
||||
const notif = SHOP_STATUS_NOTIFICATION[status]
|
||||
if (notif) {
|
||||
await notifyUser(req.app.get('io'), shop.owner, {
|
||||
project_post_id: shop._id,
|
||||
type: 'shop_approval',
|
||||
title: notif.title,
|
||||
description: notif.description
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'وضعیت فروشگاه با موفقیت تغییر کرد', shop })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateMarketplaceProductStatus = async (req, res, next) => {
|
||||
try {
|
||||
const { shopId, productId } = req.params
|
||||
const { status } = req.body
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(shopId) || !mongoose.Types.ObjectId.isValid(productId)) {
|
||||
return res.status(400).json({ message: 'شناسه معتبر نیست' })
|
||||
}
|
||||
if (!['active', 'inactive', 'rejected'].includes(status)) {
|
||||
return res.status(422).json({ message: 'وضعیت انتخابشده معتبر نیست' })
|
||||
}
|
||||
|
||||
const listing = await ShopProductListingModel.findOneAndUpdate(
|
||||
{ _id: productId, shop: shopId },
|
||||
{ status },
|
||||
{ new: true }
|
||||
).populate('shop', 'owner')
|
||||
if (!listing) return res.status(404).json({ message: 'کالا یافت نشد' })
|
||||
|
||||
const notif = PRODUCT_STATUS_NOTIFICATION[status]
|
||||
if (notif && listing.shop?.owner) {
|
||||
await notifyUser(req.app.get('io'), listing.shop.owner, {
|
||||
project_post_id: listing._id,
|
||||
type: 'shop_product_approval',
|
||||
title: notif.title,
|
||||
description: notif.description
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'وضعیت کالا با موفقیت تغییر کرد', listing })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMarketplaceShops,
|
||||
getPendingMarketplaceProducts,
|
||||
getMarketplaceShopDetail,
|
||||
updateMarketplaceShopStatus,
|
||||
updateMarketplaceProductStatus
|
||||
}
|
||||
82
controllers/panel/moderation/moderationStatsController.js
Normal file
82
controllers/panel/moderation/moderationStatsController.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const PostModel = require('../../../models/PostModel')
|
||||
const CommentModel = require('../../../models/CommentModel')
|
||||
const AdvertisingCommentModel = require('../../../models/AdvertisingCommentModel')
|
||||
const CourseCommentModel = require('../../../models/CourseComentModel')
|
||||
const AdvertisingModel = require('../../../models/AdvertisingModel')
|
||||
const ProjectModel = require('../../../models/ProjectModel')
|
||||
const CourseModel = require('../../../models/CourseModel')
|
||||
const AcademyContentModel = require('../../../models/AcademyContentModel')
|
||||
const ShopModel = require('../../../models/ShopModel')
|
||||
const ShopProductListingModel = require('../../../models/ShopProductListingModel')
|
||||
const {
|
||||
REVIEW_POST_STATUSES,
|
||||
REVIEW_COMMENT_STATUSES,
|
||||
REVIEW_PROJECT_STATUSES,
|
||||
REVIEW_BILLBOARD_STATUSES,
|
||||
TEMP_ACCEPT,
|
||||
} = require('../../../utils/moderationStatus')
|
||||
|
||||
const getModerationStats = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const [
|
||||
posts,
|
||||
comments,
|
||||
vitrineComments,
|
||||
academyComments,
|
||||
billboards,
|
||||
projects,
|
||||
courses,
|
||||
academyVideos,
|
||||
pendingShops,
|
||||
pendingShopProducts,
|
||||
] = await Promise.all([
|
||||
PostModel.countDocuments({ status: { $in: REVIEW_POST_STATUSES } }),
|
||||
CommentModel.countDocuments({ status: { $in: REVIEW_COMMENT_STATUSES } }),
|
||||
AdvertisingCommentModel.countDocuments({
|
||||
status: { $in: REVIEW_COMMENT_STATUSES },
|
||||
}),
|
||||
CourseCommentModel.countDocuments({
|
||||
status: { $in: REVIEW_COMMENT_STATUSES },
|
||||
}),
|
||||
AdvertisingModel.countDocuments({
|
||||
status: { $in: REVIEW_BILLBOARD_STATUSES },
|
||||
}),
|
||||
ProjectModel.countDocuments({ status: { $in: REVIEW_PROJECT_STATUSES } }),
|
||||
CourseModel.countDocuments({ status: TEMP_ACCEPT }),
|
||||
AcademyContentModel.countDocuments({ status: TEMP_ACCEPT }),
|
||||
ShopModel.countDocuments({ status: 'pending_review' }),
|
||||
ShopProductListingModel.countDocuments({ status: 'pending_shop_approval' }),
|
||||
])
|
||||
|
||||
res.status(200).json({
|
||||
posts,
|
||||
comments,
|
||||
vitrineComments,
|
||||
academyComments,
|
||||
billboards,
|
||||
projects,
|
||||
courses,
|
||||
academyVideos,
|
||||
pendingShops,
|
||||
pendingShopProducts,
|
||||
total:
|
||||
posts +
|
||||
comments +
|
||||
vitrineComments +
|
||||
academyComments +
|
||||
billboards +
|
||||
projects +
|
||||
courses +
|
||||
academyVideos +
|
||||
pendingShops +
|
||||
pendingShopProducts,
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getModerationStats }
|
||||
100
controllers/panel/notifications/notificationsController.js
Normal file
100
controllers/panel/notifications/notificationsController.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const AdminBroadcastModel = require('../../../models/AdminBroadcastModel')
|
||||
const NotificationModel = require('../../../models/NotificationModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const { sendPushToTokens } = require('../../../utils/pushNotification')
|
||||
|
||||
/** ارسال اعلان همگانی — مثل ارسال پیامک، به همهی کاربران */
|
||||
const createBroadcast = async (req, res, next) => {
|
||||
try {
|
||||
const { title, body } = req.body
|
||||
if (!title || !body) {
|
||||
return res.status(400).json({ message: 'عنوان و متن اعلان الزامی است' })
|
||||
}
|
||||
|
||||
const users = await UserModel.find({}).select('_id fcm_tokens fcm_web_tokens').lean()
|
||||
|
||||
const broadcast = await AdminBroadcastModel.create({
|
||||
title,
|
||||
body,
|
||||
recipientCount: users.length,
|
||||
sentBy: req.admin?._id
|
||||
})
|
||||
|
||||
if (users.length > 0) {
|
||||
await NotificationModel.insertMany(
|
||||
users.map((u) => ({
|
||||
user_id: u._id,
|
||||
type: 'admin_broadcast',
|
||||
title,
|
||||
description: body,
|
||||
broadcast_id: broadcast._id
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
const tokens = users.flatMap((u) => [...(u.fcm_tokens || []), ...(u.fcm_web_tokens || [])])
|
||||
if (tokens.length > 0) {
|
||||
sendPushToTokens(tokens, {
|
||||
title,
|
||||
description: body,
|
||||
type: 'admin_broadcast',
|
||||
data: { broadcastId: String(broadcast._id) }
|
||||
}).catch((err) => console.error('[notificationsController] ارسال پوش همگانی خطا:', err.message))
|
||||
}
|
||||
|
||||
res.status(201).json({ broadcast })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getBroadcasts = async (req, res, next) => {
|
||||
try {
|
||||
const { page = 1, limit = 20 } = req.query
|
||||
const options = {
|
||||
page: parseInt(page, 10) || 1,
|
||||
limit: parseInt(limit, 10) || 20,
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
const result = await AdminBroadcastModel.paginate({}, options)
|
||||
const broadcasts = result.docs.map((b) => ({
|
||||
...b._doc,
|
||||
createdAtJalali: jMoment(b.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
}))
|
||||
|
||||
res.json({
|
||||
broadcasts,
|
||||
totalPages: result.totalPages,
|
||||
totalItems: result.totalDocs
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getBroadcastDetail = async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const broadcast = await AdminBroadcastModel.findById(id).lean()
|
||||
if (!broadcast) return res.status(404).json({ message: 'اعلان یافت نشد' })
|
||||
|
||||
const [seenCount, clickedCount] = await Promise.all([
|
||||
NotificationModel.countDocuments({ broadcast_id: id, push_delivered_seen: true }),
|
||||
NotificationModel.countDocuments({ broadcast_id: id, push_clicked: true })
|
||||
])
|
||||
|
||||
res.json({
|
||||
broadcast: {
|
||||
...broadcast,
|
||||
createdAtJalali: jMoment(broadcast.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
},
|
||||
seenCount,
|
||||
clickedCount
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createBroadcast, getBroadcasts, getBroadcastDetail }
|
||||
@@ -150,8 +150,8 @@ const acceptPost = async (req, res, next) => {
|
||||
user_id: user?._id,
|
||||
project_post_id: post?._id,
|
||||
type: 'accept-post',
|
||||
title: 'انتشار پست',
|
||||
description: 'پست شما مورد تایید قرار گرفت.'
|
||||
title: 'تایید کامل پست',
|
||||
description: 'پست شما تایید کامل شد.'
|
||||
})
|
||||
await notification.save()
|
||||
} else {
|
||||
|
||||
207
controllers/panel/reports/reportController.js
Normal file
207
controllers/panel/reports/reportController.js
Normal file
@@ -0,0 +1,207 @@
|
||||
/* eslint-disable camelcase */
|
||||
const PostReportModel = require('../../../models/PostReportModel')
|
||||
const PostModel = require('../../../models/PostModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const { default: mongoose } = require('mongoose')
|
||||
|
||||
function postMediaPreview (post) {
|
||||
if (post?.files?.length) {
|
||||
const file = post.files[0]
|
||||
return { path: file.path, type: file.type }
|
||||
}
|
||||
if (post?.post_images?.length) {
|
||||
return { path: post.post_images[0], type: 'image' }
|
||||
}
|
||||
if (post?.post_video) {
|
||||
return { path: post.post_video, type: 'video' }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const listReportedPosts = async (req, res) => {
|
||||
try {
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1)
|
||||
const limit = Math.min(50, Math.max(1, parseInt(req.query.limit, 10) || 10))
|
||||
const skip = (page - 1) * limit
|
||||
const { status, startDate, endDate } = req.query
|
||||
|
||||
const match = {}
|
||||
if (startDate || endDate) {
|
||||
match.createdAt = {}
|
||||
if (startDate) match.createdAt.$gte = new Date(startDate)
|
||||
if (endDate) match.createdAt.$lte = new Date(endDate)
|
||||
}
|
||||
|
||||
const grouped = await PostReportModel.aggregate([
|
||||
...(Object.keys(match).length ? [{ $match: match }] : []),
|
||||
{
|
||||
$group: {
|
||||
_id: '$post_id',
|
||||
reports_count: { $sum: 1 },
|
||||
latest_at: { $max: '$createdAt' },
|
||||
},
|
||||
},
|
||||
{ $sort: { latest_at: -1 } },
|
||||
])
|
||||
|
||||
const postIds = grouped.map((g) => g._id)
|
||||
const posts = await PostModel.find({ _id: { $in: postIds } })
|
||||
.select('_id user_id caption status report_hidden files post_images post_video type')
|
||||
.lean()
|
||||
const postsById = Object.fromEntries(posts.map((p) => [String(p._id), p]))
|
||||
|
||||
const ownerIds = [...new Set(posts.map((p) => String(p.user_id)))]
|
||||
const owners = await UserModel.find({ _id: { $in: ownerIds } })
|
||||
.select('_id first_name last_name user_name')
|
||||
.lean()
|
||||
const ownersById = Object.fromEntries(owners.map((u) => [String(u._id), u]))
|
||||
|
||||
let rows = grouped.map((g) => {
|
||||
const post = postsById[String(g._id)]
|
||||
const owner = post ? ownersById[String(post.user_id)] || {} : {}
|
||||
const isActive = post ? post.report_hidden !== true : true
|
||||
return {
|
||||
post_id: g._id,
|
||||
reports_count: g.reports_count,
|
||||
latest_at: g.latest_at,
|
||||
first_name: owner.first_name || '-',
|
||||
last_name: owner.last_name || '-',
|
||||
user_name: owner.user_name || '-',
|
||||
caption: post?.caption || '',
|
||||
media: post ? postMediaPreview(post) : null,
|
||||
post_status: post?.status || '-',
|
||||
is_active: isActive,
|
||||
admin_status: isActive ? 'active' : 'inactive',
|
||||
}
|
||||
})
|
||||
|
||||
if (status === 'active') {
|
||||
rows = rows.filter((r) => r.is_active)
|
||||
} else if (status === 'inactive') {
|
||||
rows = rows.filter((r) => !r.is_active)
|
||||
}
|
||||
|
||||
const totalItems = rows.length
|
||||
const paginated = rows.slice(skip, skip + limit)
|
||||
|
||||
return res.status(200).json({
|
||||
reports: paginated,
|
||||
totalPages: Math.ceil(totalItems / limit) || 1,
|
||||
totalItems,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('listReportedPosts:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت گزارشها' })
|
||||
}
|
||||
}
|
||||
|
||||
const getReportedPostDetail = async (req, res) => {
|
||||
try {
|
||||
const { postId } = req.params
|
||||
if (!mongoose.Types.ObjectId.isValid(postId)) {
|
||||
return res.status(400).json({ message: 'شناسه پست نامعتبر است' })
|
||||
}
|
||||
|
||||
const post = await PostModel.findById(postId).lean()
|
||||
if (!post) {
|
||||
return res.status(404).json({ message: 'پست یافت نشد' })
|
||||
}
|
||||
|
||||
const owner = await UserModel.findById(post.user_id)
|
||||
.select('_id first_name last_name user_name profile_image')
|
||||
.lean()
|
||||
|
||||
const reports = await PostReportModel.find({ post_id: postId })
|
||||
.sort({ createdAt: -1 })
|
||||
.lean()
|
||||
|
||||
const reporterIds = [...new Set(reports.map((r) => String(r.reporter_id)))]
|
||||
const reporters = await UserModel.find({ _id: { $in: reporterIds } })
|
||||
.select('_id first_name last_name user_name mobile')
|
||||
.lean()
|
||||
const reportersById = Object.fromEntries(reporters.map((u) => [String(u._id), u]))
|
||||
|
||||
return res.status(200).json({
|
||||
post: {
|
||||
_id: post._id,
|
||||
caption: post.caption || '',
|
||||
type: post.type,
|
||||
files: post.files || [],
|
||||
media: postMediaPreview(post),
|
||||
status: post.status,
|
||||
is_active: post.report_hidden !== true,
|
||||
admin_status: post.report_hidden !== true ? 'active' : 'inactive',
|
||||
owner: {
|
||||
_id: owner?._id,
|
||||
first_name: owner?.first_name || '-',
|
||||
last_name: owner?.last_name || '-',
|
||||
user_name: owner?.user_name || '-',
|
||||
profile_image: owner?.profile_image || '',
|
||||
},
|
||||
},
|
||||
reports_count: reports.length,
|
||||
reports: reports.map((r) => {
|
||||
const reporter = reportersById[String(r.reporter_id)] || {}
|
||||
return {
|
||||
_id: r._id,
|
||||
reason: r.reason || '',
|
||||
text: r.text || '',
|
||||
createdAt: r.createdAt
|
||||
? jMoment(r.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
: '-',
|
||||
reporter: {
|
||||
_id: reporter._id,
|
||||
first_name: reporter.first_name || '-',
|
||||
last_name: reporter.last_name || '-',
|
||||
user_name: reporter.user_name || '-',
|
||||
mobile: reporter.mobile || '-',
|
||||
},
|
||||
}
|
||||
}),
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('getReportedPostDetail:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت جزئیات گزارش' })
|
||||
}
|
||||
}
|
||||
|
||||
const toggleReportedPostStatus = async (req, res) => {
|
||||
try {
|
||||
const { postId } = req.params
|
||||
const { admin_status } = req.body
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(postId)) {
|
||||
return res.status(400).json({ message: 'شناسه پست نامعتبر است' })
|
||||
}
|
||||
if (!['active', 'inactive'].includes(admin_status)) {
|
||||
return res.status(400).json({ message: 'وضعیت نامعتبر است' })
|
||||
}
|
||||
|
||||
const post = await PostModel.findById(postId)
|
||||
if (!post) {
|
||||
return res.status(404).json({ message: 'پست یافت نشد' })
|
||||
}
|
||||
|
||||
post.report_hidden = admin_status === 'inactive'
|
||||
await post.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message:
|
||||
admin_status === 'active'
|
||||
? 'پست فعال شد و نمایش داده میشود'
|
||||
: 'پست غیرفعال شد و دیگر نمایش داده نمیشود',
|
||||
admin_status,
|
||||
is_active: admin_status === 'active',
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('toggleReportedPostStatus:', err)
|
||||
return res.status(500).json({ message: 'خطا در تغییر وضعیت پست' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listReportedPosts,
|
||||
getReportedPostDetail,
|
||||
toggleReportedPostStatus,
|
||||
}
|
||||
163
controllers/panel/shop-categories/shopCategoryController.js
Normal file
163
controllers/panel/shop-categories/shopCategoryController.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/* eslint-disable camelcase */
|
||||
const ShopCategoryModel = require('../../../models/ShopCategoryModel')
|
||||
|
||||
const createShopCategory = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { category } = req.body
|
||||
if (!category) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const existingCategory = await ShopCategoryModel.findOne({ category })
|
||||
if (existingCategory) {
|
||||
return res.status(400).json({ message: 'دستهبندی با این نام قبلاً ثبت شده است' })
|
||||
}
|
||||
|
||||
const newCategory = new ShopCategoryModel({ category })
|
||||
await newCategory.save()
|
||||
|
||||
res.json({ message: 'دستهبندی با موفقیت اضافه شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const createSubShopCategory = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const categoryId = req.query.id
|
||||
const { name } = req.body
|
||||
if (!categoryId || !name) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const category = await ShopCategoryModel.findById(categoryId)
|
||||
if (!category) {
|
||||
return res.status(404).json({ message: 'دستهبندی موردنظر یافت نشد' })
|
||||
}
|
||||
|
||||
category.sub_categories.push({ name })
|
||||
await category.save()
|
||||
|
||||
res.json({ message: 'زیردسته با موفقیت به دستهبندی اضافه شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const createSubSubShopCategory = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { categoryId, subCategoryId, name } = req.body
|
||||
if (!categoryId || !subCategoryId || !name) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'اطلاعات ارسالی اشتباه است'
|
||||
})
|
||||
}
|
||||
|
||||
const category = await ShopCategoryModel.findById(categoryId)
|
||||
if (!category) {
|
||||
return res.status(404).json({ message: 'دستهبندی موردنظر یافت نشد' })
|
||||
}
|
||||
|
||||
const subCategory = category.sub_categories.id(subCategoryId)
|
||||
if (!subCategory) {
|
||||
return res.status(404).json({ message: 'زیردسته موردنظر یافت نشد' })
|
||||
}
|
||||
|
||||
subCategory.sub_sub_categories.push({ name })
|
||||
await category.save()
|
||||
|
||||
res.json({ message: 'دستهبندی فرعی با موفقیت اضافه شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const getShopCategories = async (req, res, next) => {
|
||||
try {
|
||||
const categories = await ShopCategoryModel.find().select('category sub_categories relatedExpertise')
|
||||
res.json({ categories })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const editShopCategory = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const { category } = req.body
|
||||
const categoryId = req.params.id
|
||||
|
||||
if (!category || !categoryId) {
|
||||
return res.status(422).json({ message: 'اطلاعات ارسالی اشتباه است' })
|
||||
}
|
||||
|
||||
const updatedCategory = await ShopCategoryModel.findByIdAndUpdate(
|
||||
categoryId,
|
||||
{ category },
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
if (!updatedCategory) {
|
||||
return res.status(404).json({ message: 'دستهبندی یافت نشد' })
|
||||
}
|
||||
|
||||
res.json({ message: 'دستهبندی با موفقیت ویرایش شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateShopCategoryExpertise = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
|
||||
const categoryId = req.params.id
|
||||
const { relatedExpertise } = req.body
|
||||
|
||||
if (!categoryId || !Array.isArray(relatedExpertise)) {
|
||||
return res.status(422).json({ message: 'اطلاعات ارسالی اشتباه است' })
|
||||
}
|
||||
|
||||
const updatedCategory = await ShopCategoryModel.findByIdAndUpdate(
|
||||
categoryId,
|
||||
{ relatedExpertise },
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
if (!updatedCategory) {
|
||||
return res.status(404).json({ message: 'دستهبندی یافت نشد' })
|
||||
}
|
||||
|
||||
res.json({ message: 'حوزههای مرتبط با موفقیت ذخیره شد', category: updatedCategory })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createShopCategory,
|
||||
createSubShopCategory,
|
||||
createSubSubShopCategory,
|
||||
getShopCategories,
|
||||
editShopCategory,
|
||||
updateShopCategoryExpertise
|
||||
}
|
||||
154
controllers/panel/stories/storyController.js
Normal file
154
controllers/panel/stories/storyController.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/* eslint-disable camelcase */
|
||||
const StoryModel = require('../../../models/StoryModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
|
||||
function storyMediaUrl (storagePath) {
|
||||
if (!storagePath) return ''
|
||||
const normalized = storagePath.replace(/\\/g, '/')
|
||||
const idx = normalized.indexOf('/storage/')
|
||||
if (idx >= 0) return normalized.slice(idx)
|
||||
return normalized.replace('/root/modstagram-back/storage', '/storage')
|
||||
}
|
||||
|
||||
function formatRemaining (expiresAt) {
|
||||
const diff = new Date(expiresAt).getTime() - Date.now()
|
||||
if (diff <= 0) return 'منقضی شده'
|
||||
const hours = Math.floor(diff / (60 * 60 * 1000))
|
||||
const minutes = Math.floor((diff % (60 * 60 * 1000)) / (60 * 1000))
|
||||
return `${hours} ساعت و ${minutes} دقیقه`
|
||||
}
|
||||
|
||||
const listStories = async (req, res) => {
|
||||
try {
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1)
|
||||
const limit = Math.min(50, Math.max(1, parseInt(req.query.limit, 10) || 10))
|
||||
const skip = (page - 1) * limit
|
||||
const { status, startDate, endDate } = req.query
|
||||
|
||||
const filter = {}
|
||||
if (status === 'active' || status === 'inactive') {
|
||||
filter.admin_status = status
|
||||
}
|
||||
if (startDate || endDate) {
|
||||
filter.createdAt = {}
|
||||
if (startDate) filter.createdAt.$gte = new Date(startDate)
|
||||
if (endDate) filter.createdAt.$lte = new Date(endDate)
|
||||
}
|
||||
|
||||
const [stories, totalItems] = await Promise.all([
|
||||
StoryModel.find(filter)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.lean(),
|
||||
StoryModel.countDocuments(filter),
|
||||
])
|
||||
|
||||
const userIds = [...new Set(stories.map((s) => String(s.user_id)))]
|
||||
const users = await UserModel.find({ _id: { $in: userIds } })
|
||||
.select('_id first_name last_name user_name profile_image')
|
||||
.lean()
|
||||
const usersById = Object.fromEntries(users.map((u) => [String(u._id), u]))
|
||||
|
||||
const list = stories.map((story) => {
|
||||
const user = usersById[String(story.user_id)] || {}
|
||||
return {
|
||||
_id: story._id,
|
||||
user_id: story.user_id,
|
||||
first_name: user.first_name || '-',
|
||||
last_name: user.last_name || '-',
|
||||
user_name: user.user_name || '-',
|
||||
profile_image: user.profile_image || '',
|
||||
createdAt: story.createdAt
|
||||
? jMoment(story.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
: '-',
|
||||
expires_at: story.expires_at,
|
||||
remaining: formatRemaining(story.expires_at),
|
||||
views_count: story.viewers?.length || 0,
|
||||
media_type: story.media_type,
|
||||
media_url: storyMediaUrl(story.media_path),
|
||||
overlays: story.overlays || [],
|
||||
admin_status: story.admin_status || 'active',
|
||||
is_expired: new Date(story.expires_at) <= new Date(),
|
||||
}
|
||||
})
|
||||
|
||||
return res.status(200).json({
|
||||
stories: list,
|
||||
totalPages: Math.ceil(totalItems / limit) || 1,
|
||||
totalItems,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('listStories:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت استوریها' })
|
||||
}
|
||||
}
|
||||
|
||||
const getStoryDetail = async (req, res) => {
|
||||
try {
|
||||
const story = await StoryModel.findById(req.params.id).lean()
|
||||
if (!story) {
|
||||
return res.status(404).json({ message: 'استوری یافت نشد' })
|
||||
}
|
||||
|
||||
const user = await UserModel.findById(story.user_id)
|
||||
.select('_id first_name last_name user_name profile_image')
|
||||
.lean()
|
||||
|
||||
return res.status(200).json({
|
||||
story: {
|
||||
_id: story._id,
|
||||
user_id: story.user_id,
|
||||
first_name: user?.first_name || '-',
|
||||
last_name: user?.last_name || '-',
|
||||
user_name: user?.user_name || '-',
|
||||
profile_image: user?.profile_image || '',
|
||||
createdAt: story.createdAt
|
||||
? jMoment(story.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
: '-',
|
||||
expires_at: story.expires_at,
|
||||
remaining: formatRemaining(story.expires_at),
|
||||
views_count: story.viewers?.length || 0,
|
||||
media_type: story.media_type,
|
||||
media_url: storyMediaUrl(story.media_path),
|
||||
overlays: story.overlays || [],
|
||||
admin_status: story.admin_status || 'active',
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('getStoryDetail:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت استوری' })
|
||||
}
|
||||
}
|
||||
|
||||
const toggleStoryStatus = async (req, res) => {
|
||||
try {
|
||||
const { admin_status } = req.body
|
||||
if (!['active', 'inactive'].includes(admin_status)) {
|
||||
return res.status(400).json({ message: 'وضعیت نامعتبر است' })
|
||||
}
|
||||
|
||||
const story = await StoryModel.findById(req.params.id)
|
||||
if (!story) {
|
||||
return res.status(404).json({ message: 'استوری یافت نشد' })
|
||||
}
|
||||
|
||||
story.admin_status = admin_status
|
||||
await story.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message: admin_status === 'active' ? 'استوری فعال شد' : 'استوری غیرفعال شد',
|
||||
admin_status: story.admin_status,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('toggleStoryStatus:', err)
|
||||
return res.status(500).json({ message: 'خطا در تغییر وضعیت استوری' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listStories,
|
||||
getStoryDetail,
|
||||
toggleStoryStatus,
|
||||
}
|
||||
87
controllers/panel/suggestions/suggestionsController.js
Normal file
87
controllers/panel/suggestions/suggestionsController.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const UserSuggestionModel = require('../../../models/UserSuggestionModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
|
||||
const STATUS_LABELS = {
|
||||
new: 'جدید',
|
||||
in_progress: 'در حال انجام',
|
||||
done: 'انجام شده',
|
||||
rejected: 'رد شده'
|
||||
}
|
||||
|
||||
const formatSuggestion = (item) => ({
|
||||
...item._doc,
|
||||
statusLabel: STATUS_LABELS[item.status] || STATUS_LABELS.new,
|
||||
createdAt: jMoment(item.createdAt).format('jYYYY/jMM/jDD HH:mm')
|
||||
})
|
||||
|
||||
const getAllSuggestions = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 20, search, status } = req.query
|
||||
const filter = {}
|
||||
|
||||
if (search) {
|
||||
filter.text = { $regex: search, $options: 'i' }
|
||||
}
|
||||
|
||||
if (status) {
|
||||
filter.status = status
|
||||
}
|
||||
|
||||
const options = {
|
||||
page: parseInt(page, 10) || 1,
|
||||
limit: parseInt(limit, 10) || 20,
|
||||
populate: [
|
||||
{
|
||||
path: 'user',
|
||||
select: '_id user_name first_name last_name mobile expertise'
|
||||
}
|
||||
],
|
||||
sort: { createdAt: -1 }
|
||||
}
|
||||
|
||||
const suggestions = await UserSuggestionModel.paginate(filter, options)
|
||||
const list = suggestions.docs.map(formatSuggestion)
|
||||
|
||||
res.json({
|
||||
suggestions: list,
|
||||
totalPages: suggestions.totalPages,
|
||||
totalItems: suggestions.totalDocs
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
const updateSuggestionStatus = async (req, res) => {
|
||||
try {
|
||||
const { suggestionId } = req.params
|
||||
const { status } = req.body
|
||||
const allowed = ['new', 'in_progress', 'done', 'rejected']
|
||||
|
||||
if (!allowed.includes(status)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'وضعیت نامعتبر است'
|
||||
})
|
||||
}
|
||||
|
||||
const suggestion = await UserSuggestionModel.findByIdAndUpdate(
|
||||
suggestionId,
|
||||
{ status },
|
||||
{ new: true }
|
||||
).populate('user', '_id user_name first_name last_name mobile expertise')
|
||||
|
||||
if (!suggestion) {
|
||||
return res.status(404).json({ message: 'پیشنهاد یافت نشد' })
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: 'وضعیت بهروزرسانی شد',
|
||||
suggestion: formatSuggestion(suggestion)
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getAllSuggestions, updateSuggestionStatus, STATUS_LABELS }
|
||||
@@ -3,8 +3,8 @@
|
||||
const { default: mongoose } = require('mongoose')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const NotificationModel = require('../../../models/NotificationModel')
|
||||
const { default: axios } = require('axios')
|
||||
const { notifyUser } = require('../../../utils/notifyUser')
|
||||
const { syncVerifyBadge } = require('../../../utils/verifyBadge')
|
||||
|
||||
const getUsers = async (req, res, next) => {
|
||||
try {
|
||||
@@ -76,6 +76,9 @@ const getUsers = async (req, res, next) => {
|
||||
last_name: user.last_name,
|
||||
user_type: user.user_type,
|
||||
is_verified: user.is_verified,
|
||||
license_verified: user.license_verified,
|
||||
gold_badge: user.gold_badge,
|
||||
verify_badge: user.verify_badge,
|
||||
profile_image: user.profile_image,
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
@@ -230,6 +233,9 @@ const getUserDetail = async (req, res, next) => {
|
||||
user_score: user?.user_score,
|
||||
services :user?.services,
|
||||
is_verified: user?.is_verified,
|
||||
license_verified: user?.license_verified,
|
||||
gold_badge: user?.gold_badge,
|
||||
verify_badge: user?.verify_badge,
|
||||
rate: user?.rate,
|
||||
bio: user?.bio,
|
||||
eye_color: user?.eye_color,
|
||||
@@ -244,98 +250,119 @@ const getUserDetail = async (req, res, next) => {
|
||||
}
|
||||
const updateUser = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.query.userId // دریافت شناسه کاربر از درخواست
|
||||
const userId = req.query.userId
|
||||
|
||||
// چک کردن اعتبار شناسه کاربر
|
||||
if (!mongoose.Types.ObjectId.isValid(userId)) {
|
||||
return res.status(400).json({ message: 'شناسه کاربر معتبر نیست' })
|
||||
}
|
||||
|
||||
// فیلدهایی که میخواهید آپدیت شوند را از درخواست دریافت کنید
|
||||
const updateFields = req.body
|
||||
const updateFields = { ...(req.body || {}) }
|
||||
|
||||
// نرمالسازی و انتقال شماره موبایل (رفع duplicate key)
|
||||
if (Object.prototype.hasOwnProperty.call(updateFields, 'mobile')) {
|
||||
let mobile = updateFields.mobile
|
||||
if (mobile === '' || mobile === null) {
|
||||
updateFields.$unset = { ...(updateFields.$unset || {}), mobile: '' }
|
||||
delete updateFields.mobile
|
||||
} else {
|
||||
mobile = String(mobile).replace(/\D/g, '')
|
||||
if (!/^09[0-9]{9}$/.test(mobile)) {
|
||||
return res.status(422).json({
|
||||
errors: [{ message: 'فرمت شماره موبایل صحیح نیست (مثال: 09123456789)' }]
|
||||
})
|
||||
}
|
||||
updateFields.mobile = mobile
|
||||
|
||||
// اگر شماره متعلق به کاربر دیگری است، از او برداشته شود تا ادمین بتواند منتقل کند
|
||||
const holder = await UserModel.findOne({
|
||||
mobile,
|
||||
_id: { $ne: userId }
|
||||
}).select('_id user_name mobile')
|
||||
|
||||
if (holder) {
|
||||
await UserModel.updateOne(
|
||||
{ _id: holder._id },
|
||||
{ $unset: { mobile: '' } }
|
||||
)
|
||||
console.log(
|
||||
`[updateUser] freed mobile ${mobile} from user ${holder._id} (${holder.user_name})`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// اجرای آپدیت و بازگشت مقدار آپدیت شده
|
||||
const updatedUser = await UserModel.findByIdAndUpdate(
|
||||
userId,
|
||||
updateFields,
|
||||
{ new: true }
|
||||
updateFields.$unset
|
||||
? {
|
||||
$set: Object.fromEntries(
|
||||
Object.entries(updateFields).filter(([k]) => k !== '$unset')
|
||||
),
|
||||
$unset: updateFields.$unset
|
||||
}
|
||||
: updateFields,
|
||||
{ new: true, runValidators: true }
|
||||
)
|
||||
|
||||
// بررسی آیا کاربر وجود دارد یا نه
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
|
||||
// ارسال پاسخ با مقادیر آپدیت شده
|
||||
res.status(200).json({ message: 'کاربر با موفقیت ویرایش شد', id: updatedUser._id })
|
||||
res.status(200).json({
|
||||
message: 'کاربر با موفقیت ویرایش شد',
|
||||
id: updatedUser._id,
|
||||
mobile: updatedUser.mobile || null
|
||||
})
|
||||
} catch (error) {
|
||||
const errors = []
|
||||
|
||||
if (error.code === 11000) {
|
||||
// Duplicate key error
|
||||
for (const key in error.keyValue) {
|
||||
errors.push({ message: `ارور در فیلد ${key}` })
|
||||
const field = Object.keys(error.keyValue || {})[0] || 'فیلد'
|
||||
if (field === 'mobile') {
|
||||
errors.push({
|
||||
message:
|
||||
'این شماره موبایل قبلاً برای کاربر دیگری ثبت شده است. ابتدا شماره را از آن کاربر حذف کنید یا دوباره ذخیره کنید تا بهصورت خودکار منتقل شود.'
|
||||
})
|
||||
} else {
|
||||
errors.push({ message: `مقدار تکراری در فیلد ${field}` })
|
||||
}
|
||||
} else {
|
||||
errors.push({ message: 'خطا در آپدیت کاربر' })
|
||||
console.error('[updateUser]', error)
|
||||
errors.push({ message: error.message || 'خطا در آپدیت کاربر' })
|
||||
}
|
||||
|
||||
res.status(500).json({ errors })
|
||||
res.status(409).json({ errors })
|
||||
}
|
||||
}
|
||||
const verifyUser = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.query.userId // دریافت شناسه کاربر از درخواست
|
||||
const userId = req.query.userId
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
user.is_verified = 'verified';
|
||||
user.is_Register = true;
|
||||
await user.save();
|
||||
|
||||
|
||||
const notification = new NotificationModel({
|
||||
user_id: userId,
|
||||
if (!user.national_card_image) {
|
||||
return res.status(422).json({
|
||||
message: 'کاربر هنوز تصویر کارت ملی ارسال نکرده است'
|
||||
})
|
||||
}
|
||||
|
||||
user.is_verified = 'verified'
|
||||
user.is_Register = true
|
||||
syncVerifyBadge(user)
|
||||
await user.save()
|
||||
|
||||
await notifyUser(req.app?.get?.('io'), userId, {
|
||||
type: 'verify',
|
||||
title: 'تایید مدارک',
|
||||
description: 'مدارک ارسالی شما با موفقیت تایید شد'
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
// Sms
|
||||
|
||||
const userReciver = await UserModel.findById(userId)
|
||||
|
||||
const data = JSON.stringify({
|
||||
mobile: userReciver?.mobile,
|
||||
templateId: '632057',
|
||||
parameters: [
|
||||
{ name: 'USERS', value: userReciver?.first_name + ' ' + userReciver?.last_name }
|
||||
]
|
||||
description: 'مدارک و تصویر احراز هویت شما تایید شد. تیک تایید کنار نام کاربری شما فعال شد.'
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
// End Sms
|
||||
|
||||
// ارسال پاسخ با مقادیر آپدیت شده
|
||||
res.status(200).json({ message: 'مدارک کاربر با موفقیت تایید شد' })
|
||||
return res.status(200).json({
|
||||
message: 'مدارک کاربر با موفقیت تایید شد',
|
||||
is_verified: user.is_verified
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
@@ -358,6 +385,18 @@ const updateUserStatus = async (req, res, next) => {
|
||||
|
||||
if (!user) return res.status(404).json({ message: 'کاربر یافت نشد' });
|
||||
|
||||
await notifyUser(req.app?.get?.('io'), userId, block_status
|
||||
? {
|
||||
type: 'block',
|
||||
title: 'مسدود شدن حساب',
|
||||
description: 'حساب کاربری شما توسط پشتیبانی مسدود شد.'
|
||||
}
|
||||
: {
|
||||
type: 'unblock',
|
||||
title: 'رفع مسدودیت حساب',
|
||||
description: 'حساب کاربری شما توسط پشتیبانی دوباره فعال شد.'
|
||||
})
|
||||
|
||||
res.status(200).json({ message: 'وضعیت کاربر با موفقیت تغییر کرد', block_status: user.block_status });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -367,44 +406,101 @@ const updateUserStatus = async (req, res, next) => {
|
||||
|
||||
const rejectUser = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.query.userId // دریافت شناسه کاربر از درخواست
|
||||
const userId = req.query.userId
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
|
||||
user.is_verified = 'rejected'
|
||||
user.is_Register = false
|
||||
syncVerifyBadge(user)
|
||||
await user.save()
|
||||
const notification = new NotificationModel({
|
||||
user_id: userId,
|
||||
|
||||
await notifyUser(req.app?.get?.('io'), userId, {
|
||||
type: 'reject-user',
|
||||
title: 'رد مدارک',
|
||||
description: 'مدارک ارسالی شما رد شد، برای اطلاعات بیشتر از بخش تیکت به پشتیبانی پیام دهید.'
|
||||
description:
|
||||
'مدارک احراز هویت شما رد شد. لطفاً تصویر کارت ملی را دوباره از بخش احراز هویت ارسال کنید یا از طریق تیکت با پشتیبانی در ارتباط باشید.'
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
// ارسال پاسخ با مقادیر آپدیت شده
|
||||
res.status(200).json({ message: 'مدارک کاربر با موفقیت رد شد' })
|
||||
return res.status(200).json({
|
||||
message: 'مدارک کاربر با موفقیت رد شد',
|
||||
is_verified: user.is_verified
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
const changePass = async (req, res, next) => {
|
||||
|
||||
const grantGoldBadge = async (req, res, next) => {
|
||||
try {
|
||||
const { userId, password } = req.body;
|
||||
const userId = req.query.userId
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
user.password = password
|
||||
|
||||
user.gold_badge = true
|
||||
syncVerifyBadge(user)
|
||||
await user.save()
|
||||
|
||||
// ارسال پاسخ با مقادیر آپدیت شده
|
||||
res.status(200).json({ message: 'رمز عبور با موفقیت تغییر کرد' });
|
||||
await notifyUser(req.app?.get?.('io'), userId, {
|
||||
type: 'verify',
|
||||
title: 'تیک طلایی',
|
||||
description: 'تیک طلایی توسط مدیریت برای شما فعال شد.'
|
||||
})
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'تیک طلایی با موفقیت فعال شد',
|
||||
gold_badge: user.gold_badge,
|
||||
verify_badge: user.verify_badge
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const revokeGoldBadge = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.query.userId
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
|
||||
user.gold_badge = false
|
||||
syncVerifyBadge(user)
|
||||
await user.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'تیک طلایی با موفقیت غیرفعال شد',
|
||||
gold_badge: user.gold_badge,
|
||||
verify_badge: user.verify_badge
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const changePass = async (req, res, next) => {
|
||||
try {
|
||||
const bcrypt = require('bcryptjs')
|
||||
const { userId, password } = req.body
|
||||
if (!password || String(password).length < 8) {
|
||||
return res.status(422).json({ message: 'رمز عبور باید حداقل ۸ کاراکتر باشد' })
|
||||
}
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
user.password = await bcrypt.hash(String(password), 10)
|
||||
await user.save()
|
||||
|
||||
res.status(200).json({ message: 'رمز عبور با موفقیت تغییر کرد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getUsers, getUserDetail, updateUser, verifyUser, rejectUser, updateUserStatus, changePass
|
||||
getUsers, getUserDetail, updateUser, verifyUser, rejectUser, updateUserStatus, changePass, grantGoldBadge, revokeGoldBadge
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable camelcase */
|
||||
const mongoose = require('mongoose')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const WalletWithdrawalRequestModel = require('../../../models/WalletWithdrawalRequestModel')
|
||||
const { notifyUser } = require('../../../utils/notifyUser')
|
||||
|
||||
const getWalletRequests = async (req, res, next) => {
|
||||
try {
|
||||
const { page = 1, limit = 20, status } = req.query
|
||||
const filter = {}
|
||||
if (status) filter.status = status
|
||||
|
||||
const options = {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
sort: { requestedAt: -1 },
|
||||
populate: [
|
||||
{ path: 'user', select: '_id user_name first_name last_name profile_image' },
|
||||
{ path: 'shop', select: '_id name' }
|
||||
]
|
||||
}
|
||||
|
||||
const requests = await WalletWithdrawalRequestModel.paginate(filter, options)
|
||||
const list = requests.docs.map((req) => ({
|
||||
_id: req._id,
|
||||
profile_image: req.user?.profile_image,
|
||||
first_name: req.user?.first_name,
|
||||
last_name: req.user?.last_name,
|
||||
user_name: req.user?.user_name,
|
||||
bucket: req.bucket,
|
||||
shop_name: req.shop?.name || null,
|
||||
amount: req.amount,
|
||||
shaba: req.shabaSnapshot,
|
||||
status: req.status,
|
||||
requestedAt: jMoment(req.requestedAt).format('jYYYY-jMM-jDD HH:mm'),
|
||||
paidAt: req.paidAt ? jMoment(req.paidAt).format('jYYYY-jMM-jDD HH:mm') : null
|
||||
}))
|
||||
|
||||
res.status(200).json({
|
||||
requests: list,
|
||||
totalPages: requests.totalPages,
|
||||
totalItems: requests.totalDocs
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateWalletRequestStatus = async (req, res, next) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { status } = req.body
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).json({ message: 'شناسه درخواست معتبر نیست' })
|
||||
}
|
||||
if (!['در حال انجام', 'منتظر تایید', 'انجام شده'].includes(status)) {
|
||||
return res.status(422).json({ message: 'وضعیت انتخابشده معتبر نیست' })
|
||||
}
|
||||
|
||||
const update = { status }
|
||||
if (status === 'انجام شده') {
|
||||
update.paidAt = new Date()
|
||||
}
|
||||
|
||||
const request = await WalletWithdrawalRequestModel.findByIdAndUpdate(id, update, { new: true })
|
||||
if (!request) return res.status(404).json({ message: 'درخواست یافت نشد' })
|
||||
|
||||
if (status === 'انجام شده') {
|
||||
await notifyUser(req.app.get('io'), request.user, {
|
||||
project_post_id: request._id,
|
||||
type: 'wallet_withdrawal',
|
||||
title: 'واریز تسویه کیف پول',
|
||||
description: `درخواست تسویه شما به مبلغ ${request.amount.toLocaleString()} تومان انجام شد.`
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'وضعیت درخواست با موفقیت تغییر کرد', request })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getWalletRequests,
|
||||
updateWalletRequestStatus
|
||||
}
|
||||
Reference in New Issue
Block a user