This commit is contained in:
payacom
2026-08-31 20:45:41 +03:30
parent fa9f23c61d
commit 3f416a7a60
367 changed files with 621895 additions and 6443 deletions

View 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,
}

View File

@@ -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
}

View 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,
}

View 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
}

View File

@@ -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
}

View 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 }

View 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 }

View File

@@ -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 {

View 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,
}

View 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
}

View 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,
}

View 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 }

View File

@@ -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
}

View File

@@ -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
}