Initial commit

This commit is contained in:
payacom
2026-07-08 21:01:30 +03:30
parent 59c7c8e11c
commit 0b01b262fa
38 changed files with 1415 additions and 288 deletions

View File

@@ -15,6 +15,13 @@ const CourseComment = require("../../../models/CourseComentModel");
const IsPaymentCourse = require("../../../models/IsPaymentCourseModel");
const Tax = require("../../../models/TaxModel");
const AcademyCategoryModel = require('../../../models/AcademyCategoryModel');
const {
createLikeNotification,
resolveCourseOwnerId
} = require('../../../utils/likeNotification');
const {
createCommentNotification
} = require('../../../utils/commentNotification');
const onServerRestart = async () => {
const CoursePayment = await CoursePaymentModel.findOne({
@@ -1178,6 +1185,14 @@ const likeCourseContent = async (req, res, next) => {
createdAt: new Date(),
});
const courseOwnerId = await resolveCourseOwnerId(course);
await createLikeNotification({
ownerId: courseOwnerId,
likerId: userId,
entityId: course._id,
type: 'academy_like'
});
res.status(200).json({
success: true,
message: "دوره با موفقیت لایک شد",
@@ -1394,6 +1409,14 @@ const createComment = async (req, res) => {
newComment._id
).populate("user_id", "name username");
const courseOwnerId = await resolveCourseOwnerId(course);
await createCommentNotification({
ownerId: courseOwnerId,
commenterId: userId,
entityId: course._id,
type: 'academy_comment'
});
res.status(201).json({
success: true,
message: "کامنت با موفقیت ثبت شد",

View File

@@ -14,6 +14,11 @@ const AdvertisingLikeModel = require('../../../models/AdvertisingLikeModel')
const AdvertisingComment = require('../../../models/AdvertisingCommentModel')
const AdvertisingRatingModel = require('../../../models/AdvertisingRatingModel')
const AdvertisingProfileModel = require('../../../models/AdvertisingProfile')
const { createLikeNotification } = require('../../../utils/likeNotification')
const {
createCommentNotification,
createRatingNotification
} = require('../../../utils/commentNotification')
const createAdvertisingValidationRules = () => {
return [
@@ -698,6 +703,14 @@ const toggleLike = async (req, res, next) => {
// افزایش تعداد لایک‌ها در تبلیغات
advertising.likesCount += 1
await advertising.save()
await createLikeNotification({
ownerId: advertising.creator_id,
likerId: userId,
entityId: advertising._id,
type: 'billboard_like'
})
return res.status(201).json({ message: 'ویترین با موفقیت لایک شد' })
}
} catch (error) {
@@ -727,8 +740,16 @@ const addComment = async (req, res, next) => {
})
await newComment.save()
// افزایش تعداد کامنت‌های تبلیغ
// await AdvertisingModel.findByIdAndUpdate(advertisingId, { $inc: { commentsCount: 1 } })
const advertising = await AdvertisingModel.findById(advertisingId)
if (advertising) {
await createCommentNotification({
ownerId: advertising.creator_id,
commenterId: userId,
entityId: advertising._id,
type: 'billboard_comment'
})
}
res.status(201).json({ message: 'کامنت با موفقیت اضافه شد', comment: newComment })
} catch (error) {
@@ -783,6 +804,13 @@ const rateAdvertising = async (req, res, next) => {
rating
})
await newRating.save()
await createRatingNotification({
ownerId: advertising.creator_id,
raterId: userId,
entityId: advertising._id,
type: 'billboard_rating'
})
}
// به‌روزرسانی امتیازات کلی کاربر

View File

@@ -1,6 +1,7 @@
const UserModel = require('../../../models/UserModel')
const { check, validationResult } = require('express-validator')
const { default: axios } = require('axios')
const { OTP_VALID_MS } = require('../../../utils/otpExpiry')
const loginValidationRules = () => {
return [
check('mobile')
@@ -34,18 +35,22 @@ const loginUser = async (req, res, next) => {
}
const otp = generateOTP()
const otpSentAt = new Date()
// eslint-disable-next-line no-unused-vars
const user = await UserModel.findOneAndUpdate(
{ mobile },
{ $set: { mobile, otp } },
{ $set: { mobile, otp: String(otp), otpSentAt } },
{ upsert: true, new: true, lean: true }
)
// زمانبندی تنظیم مقدار otp به null پس از 10 دقیقه
setTimeout(() => {
UserModel.findOneAndUpdate({ mobile }, { $set: { otp: null } }, { new: true })
UserModel.findOneAndUpdate(
{ mobile },
{ $set: { otp: null, otpSentAt: null } },
{ new: true }
)
.then(() => {})
.catch(error => console.error('Error setting OTP to null:', error))
}, 5 * 60 * 1000)
}, OTP_VALID_MS)
const data = JSON.stringify({
mobile,
templateId: '930719',

View File

@@ -2,8 +2,14 @@
const UserModel = require('../../../models/UserModel')
const bcrypt = require('bcryptjs')
const TokenService = require('../../../services/TokenService')
const {
expireBlockSuspensionIfNeeded,
getBlockedAccountResponse
} = require('../../../utils/blockSuspension')
// const jwt = require('jsonwebtoken')
const INVALID_CREDENTIALS = 'نام کاربری یا کلمه عبور را اشتباه وارد کردید'
const loginWithUserName = async (req, res, next) => {
try {
// اعتبارسنجی ورودی ها
@@ -11,30 +17,36 @@ const loginWithUserName = async (req, res, next) => {
if (!user_name || !password) {
return res.status(422).json({
error: true,
message: 'اطلاعات ارسالی اشتباه است'
message: INVALID_CREDENTIALS
})
}
// یافتن کاربر با نام کاربری
const userLow = user_name.toLowerCase()
const user = await UserModel.findOne({ user_name: userLow })
let user = await UserModel.findOne({ user_name: userLow })
if (!user) {
return res.status(404).json({
return res.status(422).json({
error: true,
message: 'کاربری با این نام کاربری یافت نشد'
message: INVALID_CREDENTIALS
})
}
user = await expireBlockSuspensionIfNeeded(user)
const blockResponse = getBlockedAccountResponse(user)
if (blockResponse) {
return res.status(403).json(blockResponse)
}
if (!user.password) {
return res.status(422).json({
error: true,
message: 'رمز عبور برای این کاربر تنظیم نشده است'
message: INVALID_CREDENTIALS
})
}
const isPasswordValid = await bcrypt.compare(password, user.password)
if (!isPasswordValid) {
return res.status(422).json({
error: true,
message: 'نام کاربری یا رمز عبور اشتباه است'
message: INVALID_CREDENTIALS
})
}
// اگر هم نام کاربری و هم رمز عبور درست بود، ارسال پیام موفقیت آمیز

View File

@@ -1,5 +1,10 @@
const UserModel = require('../../../models/UserModel')
const TokenService = require('../../../services/TokenService')
const { isOtpExpired } = require('../../../utils/otpExpiry')
const {
expireBlockSuspensionIfNeeded,
getBlockedAccountResponse
} = require('../../../utils/blockSuspension')
// const jwt = require('jsonwebtoken')
const verifyUser = async (req, res, next) => {
@@ -12,15 +17,30 @@ const verifyUser = async (req, res, next) => {
})
}
const user = await UserModel.findOne({ mobile })
let user = await UserModel.findOne({ mobile })
if (!user) {
return res.status(404).json({
error: true,
message: 'کاربری با این شماره موبایل یافت نشد'
})
}
user = await expireBlockSuspensionIfNeeded(user)
const blockResponse = getBlockedAccountResponse(user)
if (blockResponse) {
return res.status(403).json(blockResponse)
}
const token = TokenService.sign({ id: user._id })
if (user.otp === otp) {
if (!user.otp || isOtpExpired(user)) {
return res.status(422).json({
error: true,
message: 'کد تایید منقضی شده است. لطفاً دوباره درخواست ارسال کد دهید'
})
}
if (String(user.otp) === String(otp)) {
// if (user.user_name === null) {
// return res.json({
// message: 'کد تایید صحیح بود',
@@ -77,7 +97,7 @@ const verifyUser = async (req, res, next) => {
return res.status(422).json({
error: true,
message: 'کد تایید اشتباه است'
message: 'کد را اشتباه وارد کردید'
})
} catch (error) {
next(error)

View File

@@ -2,6 +2,10 @@ const MessageModel = require('../../../models/MessageModel')
const UserModel = require('../../../models/UserModel')
const jwt = require('jsonwebtoken')
const moment = require('moment-jalaali')
const {
viewerBlockedUser,
userBlockedViewer,
} = require('../../../utils/blockVisibility')
const getMessages = async (req, res, next) => {
try {
const token = req.header('Authorization').split(' ')[1]
@@ -74,8 +78,8 @@ const getMessages = async (req, res, next) => {
}
// بررسی آیا کاربر فعلی در لیست بلاک‌شده‌های این کاربر است یا خیر
const blockedByCurrentUser = user.blocked_by.includes(userId)
const blockedYou = user.blocked_users.includes(userId)
const blockedByCurrentUser = viewerBlockedUser(user, userId)
const blockedYou = userBlockedViewer(user, userId)
return {
first_name: user.first_name,

View File

@@ -8,6 +8,10 @@ const PaymentModel = require('../../../models/PaymentModel')
const NotificationModel = require('../../../models/NotificationModel')
const { default: axios } = require('axios')
const CommentModel = require('../../../models/CommentModel')
const {
resolveRatingForComment,
recalculateUserRating
} = require('../../../utils/commentRating')
const getOfferPriceFromDatabase = async (itemName) => {
const item = await OfferTypeModel.findOne({ name: itemName })
@@ -374,55 +378,35 @@ const createOfferComment = async (req, res, next) => {
const userId = decodedToken.id
const { offerId, comment, rate, user_id } = req.body
if (!offerId || !comment || !rate || !user_id) {
if (!offerId || !comment || !user_id) {
return res.status(400).send({ message: 'All fields are required' })
}
// بررسی اینکه آیا کاربر قبلاً برای این پیشنهاد نظر داده است
const existingComment = await CommentModel.findOne({
offer: offerId,
creator: userId
const { ratingValue, isNewRating, requiresRating } = await resolveRatingForComment({
creatorId: userId,
targetUserId: user_id,
rate
})
if (existingComment) {
return res.status(400).send({ message: 'شما قبلاً نظر خود را برای این درخواست ثبت کرده‌اید' })
if (requiresRating) {
return res.status(400).send({ message: 'Rating must be between 1 and 5' })
}
const newComment = new CommentModel({
user: user_id,
offer: offerId,
creator: userId,
rating: rate,
rating: ratingValue,
comment,
comment_for: 'user',
status: 'pending'
})
await newComment.save()
const user = await UserModel.findById(user_id)
// پیدا کردن کامنت‌ها و محاسبه‌ی امتیاز کل
const comments = await CommentModel.find({ user: user_id, comment_for: 'user' })
const totalRating = Number(comments.reduce((acc, comment) => acc + comment.rating, 0))
let userLevel
if (user.expertise === 'مدل') {
if (totalRating <= 50) userLevel = 'تازه وارد'
else if (totalRating <= 100) userLevel = 'استاندارد'
else if (totalRating <= 300) userLevel = 'حرفه‌ای'
else userLevel = 'استاد'
} else if (['زیبایی', 'عکاس'].includes(user.expertise)) {
if (totalRating <= 50) userLevel = 'تازه وارد'
else if (totalRating <= 100) userLevel = 'استاندارد'
else if (totalRating <= 300) userLevel = 'حرفه‌ای'
else userLevel = 'استاد'
if (isNewRating) {
await recalculateUserRating(user_id)
}
user.user_score = totalRating
user.user_level = userLevel
const totalComments = await CommentModel.countDocuments({ user: user_id, comment_for: 'user' })
const averageRating = totalRating / totalComments
user.rate = averageRating.toFixed(1)
await user.save()
res.status(200).json({ message: 'نظر شما با موفقیت ثبت شد' })
} catch (error) {
next(error)

View File

@@ -3,6 +3,7 @@ const PostModel = require('../../../models/PostModel');
const jwt = require('jsonwebtoken');
const UserModel = require('../../../models/UserModel');
const LikeModel = require('../../../models/LikeModel');
const { createLikeNotification } = require('../../../utils/likeNotification');
const toggleLike = async (req, res, next) => {
try {
@@ -48,6 +49,13 @@ const toggleLike = async (req, res, next) => {
await post.save();
await LikeModel.create({ postId, userId });
await createLikeNotification({
ownerId: post.user_id,
likerId: userId,
entityId: post._id,
type: 'post_like'
});
return res.status(200).json({
message: 'پست با موفقیت لایک شد',
is_liked: true,

View File

@@ -5,6 +5,7 @@ const { check, validationResult } = require('express-validator');
const jwt = require('jsonwebtoken');
const path = require('path');
const { default: mongoose } = require('mongoose');
const { viewerIsBlockedBy } = require('../../../utils/blockVisibility');
const createPostValidationRules = () => {
console.log(4);
@@ -139,6 +140,15 @@ const getUserPostsWeb = async (req, res, next) => {
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
}
if (userReqId && viewerIsBlockedBy(user, userReqId)) {
return res.status(200).json({
posts: [],
postsCount: 0,
totalPages: 0,
totalItems: 0,
})
}
const data = {}
const options = {
page: req.query.page || 1,

View File

@@ -35,6 +35,7 @@ const getUserById = async (req, res, next) => {
user_name: user.user_name,
show_location: user.show_location,
is_verified: user.is_verified,
is_Register: user.is_Register,
user_score: user.user_score,
rate: user.rate,
bio: user.bio,

View File

@@ -6,6 +6,46 @@ const { check, validationResult } = require('express-validator')
const { ProvinceModel, CityModel } = require('../../../models/StateCity')
const NotificationModel = require('../../../models/NotificationModel')
const CommentModel = require('../../../models/CommentModel')
const {
resolveRatingForComment,
recalculateUserRating
} = require('../../../utils/commentRating')
const saveProjectComment = async ({
targetUserId,
projectId,
creatorId,
comment,
rate,
commentFor
}) => {
const { ratingValue, isNewRating, requiresRating } = await resolveRatingForComment({
creatorId,
targetUserId,
rate
})
if (requiresRating) {
return { error: { status: 400, message: 'Rating must be between 1 and 5' } }
}
const newComment = new CommentModel({
user: targetUserId,
project: projectId,
creator: creatorId,
rating: ratingValue,
comment,
comment_for: commentFor,
status: 'pending'
})
await newComment.save()
if (isNewRating && commentFor === 'user') {
await recalculateUserRating(targetUserId)
}
return { ratingValue, isNewRating }
}
const doneProject = async (req, res, next) => {
try {
@@ -16,7 +56,7 @@ const doneProject = async (req, res, next) => {
const userId = decodedToken.id
const { project_id, comment, rate, user_id } = req.body
if (!project_id || !comment || !rate || !user_id) {
if (!project_id || !comment || !user_id) {
return res.status(400).send({ message: 'All fields are required' })
}
@@ -42,43 +82,20 @@ const doneProject = async (req, res, next) => {
project.status = 'done'
await project.save()
const newComment = new CommentModel({
user: user_id,
project: project_id,
creator: userId,
rating: rate,
const commentResult = await saveProjectComment({
targetUserId: user_id,
projectId: project_id,
creatorId: userId,
comment,
comment_for: 'user',
status: 'pending'
rate,
commentFor: 'user'
})
await newComment.save()
const user = await UserModel.findById(user_id)
// پیدا کردن کامنت‌ها و محاسبه‌ی امتیاز کل
const comments = await CommentModel.find({ user: user_id, comment_for: 'user' })
const totalRating = Number(comments.reduce((acc, comment) => acc + comment.rating, 0))
let userLevel
if (user.expertise === 'مدل') {
if (totalRating <= 50) userLevel = 'تازه وارد'
else if (totalRating <= 100) userLevel = 'استاندارد'
else if (totalRating <= 300) userLevel = 'حرفه‌ای'
else userLevel = 'استاد'
} else if (['زیبایی', 'عکاس'].includes(user.expertise)) {
if (totalRating <= 50) userLevel = 'تازه وارد'
else if (totalRating <= 100) userLevel = 'استاندارد'
else if (totalRating <= 300) userLevel = 'حرفه‌ای'
else userLevel = 'استاد'
if (commentResult.error) {
return res.status(commentResult.error.status).send({ message: commentResult.error.message })
}
user.user_score = totalRating
user.user_level = userLevel
const totalComments = await CommentModel.countDocuments({ user: user_id, comment_for: 'user' })
const averageRating = totalRating / totalComments
user.rate = averageRating.toFixed(1)
await user.save()
const user = await UserModel.findById(user_id)
const notification = new NotificationModel({
user_id: user._id,
@@ -105,7 +122,7 @@ const doneProjectWeb = async (req, res, next) => {
const userId = decodedToken.id
const { project_id, comment, rate, user_id } = req.body
if (!project_id || !comment || !rate || !user_id) {
if (!project_id || !comment || !user_id) {
return res.status(400).send({ message: 'All fields are required' })
}
@@ -117,43 +134,20 @@ const doneProjectWeb = async (req, res, next) => {
project.status = 'done'
await project.save()
const newComment = new CommentModel({
user: user_id,
project: project_id,
creator: userId,
rating: rate,
const commentResult = await saveProjectComment({
targetUserId: user_id,
projectId: project_id,
creatorId: userId,
comment,
comment_for: 'user',
status: 'pending'
rate,
commentFor: 'user'
})
await newComment.save()
const user = await UserModel.findById(user_id)
// پیدا کردن کامنت‌ها و محاسبه‌ی امتیاز کل
const comments = await CommentModel.find({ user: user_id, comment_for: 'user' })
const totalRating = Number(comments.reduce((acc, comment) => acc + comment.rating, 0))
let userLevel
if (user.expertise === 'مدل') {
if (totalRating <= 50) userLevel = 'تازه وارد'
else if (totalRating <= 100) userLevel = 'استاندارد'
else if (totalRating <= 300) userLevel = 'حرفه‌ای'
else userLevel = 'استاد'
} else if (['زیبایی', 'عکاس'].includes(user.expertise)) {
if (totalRating <= 50) userLevel = 'تازه وارد'
else if (totalRating <= 100) userLevel = 'استاندارد'
else if (totalRating <= 300) userLevel = 'حرفه‌ای'
else userLevel = 'استاد'
if (commentResult.error) {
return res.status(commentResult.error.status).send({ message: commentResult.error.message })
}
user.user_score = totalRating
user.user_level = userLevel
const totalComments = await CommentModel.countDocuments({ user: user_id, comment_for: 'user' })
const averageRating = totalRating / totalComments
user.rate = averageRating.toFixed(1)
await user.save()
const user = await UserModel.findById(user_id)
const notification = new NotificationModel({
user_id: user._id,
@@ -182,7 +176,7 @@ const cancleProject = async (req, res, next) => {
const { project_id, comment, rate, user_id } = req.body
if (!project_id || !comment || !rate || !user_id) {
if (!project_id || !comment || !user_id) {
return res.status(400).send({ message: 'All fields are required' })
}
@@ -194,16 +188,18 @@ const cancleProject = async (req, res, next) => {
project.status = 'cancled'
await project.save()
const newComment = new CommentModel({
user: user_id,
project: project_id,
creator: userId,
rating: rate,
const commentResult = await saveProjectComment({
targetUserId: user_id,
projectId: project_id,
creatorId: userId,
comment,
comment_for: 'project',
status: 'pending'
rate,
commentFor: 'project'
})
await newComment.save()
if (commentResult.error) {
return res.status(commentResult.error.status).send({ message: commentResult.error.message })
}
res.status(200).json({ message: 'پروژه با موفقیت تغییر یافت شد' })
} catch (error) {
@@ -363,6 +359,14 @@ const setRateProject = async (req, res, next) => {
if (!project) {
return res.status(403).send({ message: 'Access denied: You are not the creator of this project.' })
}
const alreadyRated = project.ratings.some((item) =>
item.creator_id.toString() === userId.toString()
)
if (alreadyRated) {
return res.status(400).send({ message: 'شما قبلاً به این پروژه امتیاز داده‌اید' })
}
project.ratings.push({
project_id,
rating: rate,

View File

@@ -1,6 +1,7 @@
const { default: axios } = require('axios')
const UserModel = require('../../../models/UserModel')
const { check, validationResult } = require('express-validator')
const { OTP_VALID_MS } = require('../../../utils/otpExpiry')
// const { Token, VerificationCode } = require('sms-ir')
const registerValidationRules = () => {
@@ -36,17 +37,22 @@ const registerUser = async (req, res, next) => {
}
const otp = generateOTP()
const otpSentAt = new Date()
// eslint-disable-next-line no-unused-vars
const user = await UserModel.findOneAndUpdate(
{ mobile },
{ $set: { mobile, otp } },
{ $set: { mobile, otp: String(otp), otpSentAt } },
{ upsert: true, new: true, lean: true }
)
setTimeout(() => {
UserModel.findOneAndUpdate({ mobile }, { $set: { otp: null } }, { new: true })
UserModel.findOneAndUpdate(
{ mobile },
{ $set: { otp: null, otpSentAt: null } },
{ new: true }
)
.then(() => {})
.catch(error => console.error('Error setting OTP to null:', error))
}, 5 * 60 * 1000)
}, OTP_VALID_MS)
const data = JSON.stringify({
mobile,
templateId: '930719',

View File

@@ -34,8 +34,8 @@ const setUserFullName = async (req, res, next) => {
}
// آپدیت نام کاربری به صورت lowercase
user.first_name = first_name.toLowerCase()
user.last_name = last_name.toLowerCase()
user.first_name = first_name.trim().toLowerCase()
user.last_name = last_name.trim().toLowerCase()
await user.save()
return res.json({

View File

@@ -1,6 +1,8 @@
/* eslint-disable camelcase */
const UserModel = require('../../../models/UserModel')
const jwt = require('jsonwebtoken')
const { validateUsername } = require('../../../utils/usernameValidation')
const { generateUsernameSuggestions } = require('../../../utils/usernameSuggestions')
const setUserName = async (req, res, next) => {
try {
@@ -12,7 +14,6 @@ const setUserName = async (req, res, next) => {
})
}
// جستجوی کاربر با شماره موبایل ارسالی
const user = await UserModel.findOne({ mobile })
if (!user) {
return res.status(404).json({
@@ -20,32 +21,32 @@ const setUserName = async (req, res, next) => {
message: 'کاربری با این شماره موبایل یافت نشد'
})
}
// بررسی حداقل طول نام کاربری
if (user_name.length < 5) {
const validationError = validateUsername(user_name)
if (validationError) {
return res.status(422).json({
error: true,
message: 'نام کاربری باید حداقل 5 کاراکتر باشد'
})
}
// بررسی محدودیت‌های مجاز در نام کاربری
// const usernameRegex = /^[a-zA-Z0-9_]+$/
// if (!usernameRegex.test(user_name)) {
// return res.status(422).json({
// error: true,
// message: 'نام کاربری فقط می‌تواند شامل حروف الفبای انگلیسی، اعداد و کاراکتر _ باشد'
// })
// }
// بررسی تکراری بودن user_name
const existingUser = await UserModel.findOne({ user_name: { $regex: new RegExp('^' + user_name + '$', 'i') } })
if (existingUser && existingUser._id.toString() !== user._id.toString()) {
return res.status(422).json({
error: true,
message: 'نام کاربری تکراری است'
message: validationError
})
}
// آپدیت نام کاربری به صورت lowercase
user.user_name = user_name.toLowerCase()
const normalizedUserName = user_name.trim().toLowerCase()
const existingUser = await UserModel.findOne({ user_name: { $regex: new RegExp('^' + normalizedUserName + '$', 'i') } })
if (existingUser && existingUser._id.toString() !== user._id.toString()) {
const suggestions = await generateUsernameSuggestions(
UserModel,
normalizedUserName,
user._id.toString()
)
return res.status(422).json({
error: true,
message: 'نام کاربری تکراری است',
suggestions
})
}
user.user_name = normalizedUserName
await user.save()
return res.json({
@@ -73,7 +74,37 @@ const updateUserName = async (req, res, next) => {
})
}
const user = await UserModel.findByIdAndUpdate(userId, { user_name }, { new: true })
const validationError = validateUsername(user_name)
if (validationError) {
return res.status(422).json({
error: true,
message: validationError
})
}
const normalizedUserName = user_name.trim().toLowerCase()
const existingUser = await UserModel.findOne({
user_name: { $regex: new RegExp('^' + normalizedUserName + '$', 'i') }
})
if (existingUser && existingUser._id.toString() !== userId) {
const suggestions = await generateUsernameSuggestions(
UserModel,
normalizedUserName,
userId
)
return res.status(422).json({
error: true,
message: 'نام کاربری تکراری است',
suggestions
})
}
const user = await UserModel.findByIdAndUpdate(
userId,
{ user_name: normalizedUserName },
{ new: true }
)
if (!user) {
return res.status(404).json({

View File

@@ -1,5 +1,6 @@
const UserModel = require('../../../models/UserModel')
const TokenService = require('../../../services/TokenService')
const { isOtpExpired } = require('../../../utils/otpExpiry')
// const jwt = require('jsonwebtoken')
const verifyUser = async (req, res, next) => {
@@ -25,7 +26,14 @@ const verifyUser = async (req, res, next) => {
(!user.password) { step = 'password' } else if
(!user.first_name) { step = 'first_name' } else if
(!user.user_type) { step = 'user_type' } else { step = 'profile_image' }
if (user.otp === otp) {
if (!user.otp || isOtpExpired(user)) {
return res.status(422).json({
error: true,
message: 'کد تایید منقضی شده است. لطفاً دوباره درخواست ارسال کد دهید'
})
}
if (String(user.otp) === String(otp)) {
return res.json({
message: 'کد تایید صحیح بود',
token,
@@ -35,7 +43,7 @@ const verifyUser = async (req, res, next) => {
return res.status(422).json({
error: true,
message: 'کد تایید اشتباه است'
message: 'کد را اشتباه وارد کردید'
})
} catch (error) {
next(error)

View File

@@ -6,11 +6,25 @@ const jwt = require("jsonwebtoken");
const moment = require("moment-jalaali");
const NotificationModel = require("../../../models/NotificationModel");
const CommentModel = require("../../../models/CommentModel");
const {
hasCreatorRatedUser,
resolveRatingForComment,
recalculateUserRating,
} = require("../../../utils/commentRating");
const { createCommentNotification } = require("../../../utils/commentNotification");
const { ProvinceModel, CityModel } = require("../../../models/StateCity");
const OfferModel = require("../../../models/OfferModel");
const PostModel = require("../../../models/PostModel");
const ProjectModel = require("../../../models/ProjectModel");
const LicenseModel = require("../../../models/license")
const {
viewerIsBlockedBy,
viewerBlockedUser,
userBlockedViewer,
blockedProfilePayload,
getBlockerUserIds,
} = require("../../../utils/blockVisibility")
const { buildUserLevelMongoFilter, normalizeUserLevel } = require("../../../utils/userLevelFilter")
const getAllLicenses = async (req, res, next) => {
@@ -310,7 +324,8 @@ const getUsers = async (req, res, next) => {
}
}
if (userLevel) {
filter.user_level = userLevel;
const levelFilter = buildUserLevelMongoFilter(userLevel)
if (levelFilter) Object.assign(filter, levelFilter)
}
let users = await UserModel.aggregate([
@@ -775,9 +790,19 @@ const getPostsWeb = async (req, res, next) => {
.lean();
const userIds = [...new Set(explorePosts.map((p) => String(p.user_id)))];
const usersMap = await UserModel.find({ _id: { $in: userIds } })
.select('_id user_name first_name last_name expertise province city user_level')
.select('_id user_name first_name last_name expertise province city user_level show_location blocked_users')
.lean();
const usersById = Object.fromEntries(usersMap.map((u) => [String(u._id), u]));
if (decodedToken?.id) {
const blockerIds = new Set(
(await getBlockerUserIds(UserModel, decodedToken.id)).map(String)
);
explorePosts = explorePosts.filter(
(post) => !blockerIds.has(String(post.user_id))
);
}
explorePosts = explorePosts.map((post) => {
const user = usersById[String(post.user_id)] || {};
return {
@@ -786,8 +811,9 @@ const getPostsWeb = async (req, res, next) => {
first_name: user.first_name || '',
last_name: user.last_name || '',
expertise: user.expertise || '',
province: user.province || {},
city: user.city || {},
province: user.show_location ? user.province || {} : {},
city: user.show_location ? user.city || {} : {},
show_location: user.show_location,
user_level: user.user_level || '',
likesCount: post.likes ? post.likes.length : 0,
is_liked: false,
@@ -814,11 +840,21 @@ const getPostsWeb = async (req, res, next) => {
const cityFind = await CityModel.findOne({ id: city });
if (cityFind) userFilter["city.id"] = cityFind.id;
}
if (userLevel) userFilter.user_level = userLevel;
if (userLevel) {
const levelFilter = buildUserLevelMongoFilter(userLevel)
if (levelFilter) Object.assign(userFilter, levelFilter)
}
if (_id) userFilter._id = _id;
if (_id && decodedToken?.id) {
const profileOwner = await UserModel.findById(_id).select('blocked_users');
if (viewerIsBlockedBy(profileOwner, decodedToken.id)) {
return res.status(200).json({ posts: [], totalPages: 0, totalItems: 0 });
}
}
const users = await UserModel.find(userFilter)
.select("_id user_name first_name last_name expertise province city user_level")
.select("_id user_name first_name last_name expertise province city user_level show_location")
.lean();
if (!users || users.length === 0) {
@@ -836,6 +872,16 @@ const getPostsWeb = async (req, res, next) => {
return res.status(200).json({ posts: [], totalPages: 0, totalItems: 0 });
}
if (decodedToken?.id) {
const blockerIds = new Set(
(await getBlockerUserIds(UserModel, decodedToken.id)).map(String)
);
posts = posts.filter((post) => !blockerIds.has(String(post.user_id)));
if (!posts.length) {
return res.status(200).json({ posts: [], totalPages: 0, totalItems: 0 });
}
}
let likeFilter = {};
if (decodedToken) likeFilter.userId = decodedToken.id;
const likes = decodedToken
@@ -860,8 +906,9 @@ const getPostsWeb = async (req, res, next) => {
first_name: user?.first_name || "",
last_name: user?.last_name || "",
expertise: user?.expertise || "",
province: user?.province || {},
city: user?.city || {},
province: user?.show_location ? user?.province || {} : {},
city: user?.show_location ? user?.city || {} : {},
show_location: user?.show_location,
user_level: user?.user_level || "",
likesCount: post.likes ? post.likes.length : 0,
is_liked: !!likesMap[post._id.toString()],
@@ -915,8 +962,12 @@ const getSingleUser = async (req, res, next) => {
const lastOnline = moment(user.last_online).format("jYYYY-jMM-jDD");
// بررسی بلاک شدن
const blockedByCurrentUser = user.blocked_by.includes(userReqId);
const blockedYou = user.blocked_users.includes(userReqId);
const blockedByCurrentUser = viewerBlockedUser(user, userReqId);
const blockedYou = userBlockedViewer(user, userReqId);
if (blockedYou) {
return res.status(200).json({ user: blockedProfilePayload(user) });
}
// Allow chat
const offerExists = await OfferModel.exists({
@@ -937,6 +988,7 @@ const getSingleUser = async (req, res, next) => {
user_name: user.user_name,
show_location: user.show_location,
is_verified: user.is_verified,
is_Register: user.is_Register,
user_score: user.user_score,
rate: user.rate,
bio: user.bio,
@@ -948,8 +1000,8 @@ const getSingleUser = async (req, res, next) => {
profile_image: user.profile_image,
lat: user.show_location ? user.lat : null,
lng: user.show_location ? user.lng : null,
province: user.province,
city: user.city,
province: user.show_location ? user.province : null,
city: user.show_location ? user.city : null,
address: user.show_location ? user.address : null,
is_blocked: blockedByCurrentUser,
blocked_you: blockedYou,
@@ -1009,13 +1061,18 @@ const getSingleUserWeb = async (req, res, next) => {
let offerExists = false;
if (userReqId) {
blockedByCurrentUser = user.blocked_by.includes(userReqId);
blockedYou = user.blocked_users.includes(userReqId);
blockedByCurrentUser = viewerBlockedUser(user, userReqId);
blockedYou = userBlockedViewer(user, userReqId);
offerExists = await OfferModel.exists({
sender: userReqId,
receiver: user._id,
});
}
if (blockedYou && userReqId) {
return res.status(200).json({ user: blockedProfilePayload(user) });
}
// دریافت تعداد پست‌ها و پروژه‌ها
const postsCount = await PostModel.countDocuments({
user_id: user._id,
@@ -1043,6 +1100,7 @@ const getSingleUserWeb = async (req, res, next) => {
user_name: user.user_name,
show_location: user.show_location,
is_verified: user.is_verified,
is_Register: user.is_Register,
user_score: user.user_score,
rate: user.rate,
bio: user.bio,
@@ -1054,8 +1112,8 @@ const getSingleUserWeb = async (req, res, next) => {
profile_image: user.profile_image,
lat: user.show_location ? user.lat : null,
lng: user.show_location ? user.lng : null,
province: user.province,
city: user.city,
province: user.show_location ? user.province : null,
city: user.show_location ? user.city : null,
address: user.show_location ? user.address : null,
is_blocked: blockedByCurrentUser,
blocked_you: blockedYou,
@@ -1099,6 +1157,11 @@ const getUserContactInfo = async (req, res, next) => {
next(error);
}
};
const {
applyAutoBlockSuspension,
clearAutoBlockSuspension
} = require('../../../utils/blockSuspension')
const blockUser = async (req, res) => {
try {
const { user_to_block } = req.body;
@@ -1107,17 +1170,30 @@ const blockUser = async (req, res) => {
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
const userReqId = decodedToken.id;
const currentUser = await UserModel.findById(userReqId); // Req
const userToBlock = await UserModel.findById(user_to_block); // To Block
userToBlock.blocked_by.push(currentUser);
currentUser.blocked_users.push(userToBlock);
// بررسی تعداد بلاک ها و تنظیم block_status
if (userToBlock.blocked_by.length >= 3) {
userToBlock.block_status = true;
if (userReqId === user_to_block) {
return res.status(400).json({ message: "امکان بلاک خودتان وجود ندارد" });
}
const currentUser = await UserModel.findById(userReqId);
const userToBlock = await UserModel.findById(user_to_block);
if (!currentUser || !userToBlock) {
return res.status(404).json({ message: "کاربر یافت نشد" });
}
const alreadyBlocked = userToBlock.blocked_by.some((id) =>
id.equals(currentUser._id)
);
if (alreadyBlocked) {
return res.status(400).json({ message: "این کاربر قبلاً بلاک شده است" });
}
userToBlock.blocked_by.push(currentUser._id);
currentUser.blocked_users.push(userToBlock._id);
applyAutoBlockSuspension(userToBlock);
await currentUser.save();
await userToBlock.save();
@@ -1136,18 +1212,17 @@ const unblockUser = async (req, res) => {
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
const userReqId = decodedToken.id;
const currentUser = await UserModel.findById(userReqId); // Req
const userToUnblock = await UserModel.findById(user_to_unblock); // To Unblock
const currentUser = await UserModel.findById(userReqId);
const userToUnblock = await UserModel.findById(user_to_unblock);
if (!currentUser || !userToUnblock) {
return res.status(404).json({ message: "کاربر یافت نشد" });
}
// حذف کاربر از لیست blocked_by کاربر فعلی
currentUser.blocked_users.pull(userToUnblock._id);
// حذف کاربر از لیست blocked_users کاربر مورد نظر
userToUnblock.blocked_by.pull(currentUser._id);
// بررسی تعداد بلاک ها و تنظیم block_status
if (userToUnblock.blocked_by.length < 3) {
userToUnblock.block_status = null;
}
clearAutoBlockSuspension(userToUnblock);
await currentUser.save();
await userToUnblock.save();
@@ -1163,6 +1238,7 @@ const getComments = async (req, res, next) => {
try {
const token = req.header("Authorization").split(" ")[1];
if (!token) return res.status(401).send("Access Denied");
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
const user_id = req.query.user_id;
const post_id = req.query.post_id;
@@ -1192,27 +1268,53 @@ const getComments = async (req, res, next) => {
const comments = await CommentModel.paginate(filter, options);
const formattedComments = comments.docs.map((comment) => ({
_id: comment._id,
comment: comment.comment,
rating: comment.rating,
createdAt: moment(comment.createdAt).format("jYYYY-jMM-jDD HH:mm"),
status: comment.status,
user: {
profile_image: comment.creator.profile_image,
user_name: comment.creator.user_name,
is_verified: comment.creator.is_verified,
_id: comment.creator._id,
},
project: comment.project ? comment.project.project_name : null,
post_id: comment.post ? comment.post.toString() : null,
}));
const earliestRatedComments = await CommentModel.find({
user: user_id,
rating: { $ne: null, $gt: 0 },
})
.select("creator _id createdAt")
.sort({ createdAt: 1 });
const ratedCreatorCommentIds = new Map();
for (const ratedComment of earliestRatedComments) {
const creatorId = ratedComment.creator.toString();
if (!ratedCreatorCommentIds.has(creatorId)) {
ratedCreatorCommentIds.set(creatorId, ratedComment._id.toString());
}
}
const formattedComments = comments.docs.map((comment) => {
const creatorId = comment.creator._id.toString();
const isPrimaryRatingComment =
comment.rating != null &&
comment.rating > 0 &&
ratedCreatorCommentIds.get(creatorId) === comment._id.toString();
return {
_id: comment._id,
comment: comment.comment,
rating: isPrimaryRatingComment ? comment.rating : null,
createdAt: moment(comment.createdAt).format("jYYYY-jMM-jDD HH:mm"),
status: comment.status,
user: {
profile_image: comment.creator.profile_image,
user_name: comment.creator.user_name,
is_verified: comment.creator.is_verified,
_id: comment.creator._id,
},
project: comment.project ? comment.project.project_name : null,
post_id: comment.post ? comment.post.toString() : null,
};
});
const hasRated = await hasCreatorRatedUser(decodedToken.id, user_id);
res.status(200).json({
comments: formattedComments,
totalPages: comments.totalPages,
totalItems: comments.totalDocs,
currentPage: comments.page,
has_rated: hasRated,
});
} catch (error) {
next(error);
@@ -1231,20 +1333,17 @@ const createUserComment = async (req, res, next) => {
return res.status(400).send({ message: "All fields are required" });
}
const existingRating = await CommentModel.findOne({
user: user_id,
creator: creatorId,
comment_for: post_id ? "post" : "user",
rating: { $exists: true, $ne: null }
});
const { ratingValue, isNewRating, requiresRating } =
await resolveRatingForComment({
creatorId,
targetUserId: user_id,
rate,
});
let ratingValue = rate;
if (existingRating) {
ratingValue = existingRating.rating;
} else {
if (!rate || rate < 1 || rate > 5) {
return res.status(400).send({ message: "Rating must be between 1 and 5" });
}
if (requiresRating) {
return res
.status(400)
.send({ message: "Rating must be between 1 and 5" });
}
const newComment = new CommentModel({
@@ -1258,50 +1357,16 @@ const createUserComment = async (req, res, next) => {
});
await newComment.save();
const user = await UserModel.findById(user_id);
// محاسبه امتیاز جدید کاربر
const comments = await CommentModel.find({
user: user_id,
comment_for: "user",
});
const totalRating = comments.reduce(
(acc, comment) => acc + comment.rating,
0
);
const totalComments = comments.length;
const averageRating = totalRating / totalComments;
// تعیین سطح کاربر
let userLevel;
if (user.expertise === "مدل") {
if (totalRating <= 50) userLevel = "تازه وارد";
else if (totalRating <= 100) userLevel = "استاندارد";
else if (totalRating <= 300) userLevel = "حرفه‌ای";
else userLevel = "استاد";
} else if (["زیبایی", "عکاس"].includes(user.expertise)) {
if (totalRating <= 50) userLevel = "تازه وارد";
else if (totalRating <= 100) userLevel = "استاندارد";
else if (totalRating <= 300) userLevel = "حرفه‌ای";
else userLevel = "استاد";
if (isNewRating) {
await recalculateUserRating(user_id);
}
// به‌روزرسانی اطلاعات کاربر
user.user_score = totalRating;
user.user_level = userLevel;
user.rate = averageRating.toFixed(1);
await user.save();
// ارسال نوتیفیکیشن
const notification = new NotificationModel({
user_id: user._id,
type: "user_comment",
title: "نظر جدید",
description: `یک کاربر جدید برای شما نظر ثبت کرد:
${comment}
امتیاز: ${rate} از 5`,
await createCommentNotification({
ownerId: user_id,
commenterId: creatorId,
entityId: post_id || user_id,
type: post_id ? "post_comment" : "profile_comment",
});
await notification.save();
res.status(201).json({
message: "نظر با موفقیت ثبت شد و بعد از تایید، منتشر میشود.",

View File

@@ -9,7 +9,18 @@ const setAddress = async (req, res, next) => {
if (!token) return res.status(401).send('Access Denied')
const { province_id, city_id, address, lat, lng, show_location } = req.body
// اعتبارسنجی داده‌ها
if (!province_id || !city_id || !address || !lat || !lng || show_location === 'undefined') {
if (
!province_id ||
!city_id ||
!address ||
lat === undefined ||
lat === null ||
lat === '' ||
lng === undefined ||
lng === null ||
lng === '' ||
show_location === undefined
) {
return res.status(422).json({
error: true,
message: 'اطلاعات ارسالی ناقص است'
@@ -41,7 +52,8 @@ const setAddress = async (req, res, next) => {
user.address = address
user.lat = lat
user.lng = lng
user.show_location = show_location
user.show_location =
show_location === true || show_location === 'true'
await user.save()
@@ -60,7 +72,18 @@ const updateAddress = async (req, res, next) => {
const { province_id, city_id, address, lat, lng, show_location } = req.body
// اعتبارسنجی داده‌ها
if (!province_id || !city_id || !address || !lat || !lng || show_location === undefined) {
if (
!province_id ||
!city_id ||
!address ||
lat === undefined ||
lat === null ||
lat === '' ||
lng === undefined ||
lng === null ||
lng === '' ||
show_location === undefined
) {
return res.status(422).json({
error: true,
message: 'اطلاعات ارسالی ناقص است'
@@ -95,7 +118,8 @@ const updateAddress = async (req, res, next) => {
user.address = address
user.lat = lat
user.lng = lng
user.show_location = show_location
user.show_location =
show_location === true || show_location === 'true'
await user.save()

View File

@@ -0,0 +1,39 @@
const UserModel = require('../../../models/UserModel')
const jwt = require('jsonwebtoken')
const completeRegistration = async (req, res, next) => {
try {
const token = req.header('Authorization')?.split(' ')[1]
if (!token) return res.status(401).send('Access Denied')
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
const user = await UserModel.findById(decodedToken.id)
if (!user) {
return res.status(404).json({
error: true,
message: 'کاربر یافت نشد'
})
}
user.is_Register = true
if (!user.is_verified || user.is_verified === 'none') {
user.is_verified = 'pending'
}
await user.save()
return res.json({
message: 'ثبت نام تکمیل شد',
is_verified: user.is_verified,
is_Register: user.is_Register
})
} catch (error) {
next(error)
}
}
module.exports = {
completeRegistration
}

View File

@@ -38,6 +38,7 @@ const setNationalCardImage = async (req, res, next) => {
// ذخیره مسیر فایل در دیتابیس
user.national_card_image = `/carts/${uniqueFileName}`
user.is_Register = true
user.is_verified = 'pending'
await user.save()

View File

@@ -33,7 +33,7 @@ const setProfileImage = async (req, res, next) => {
}
// ذخیره فایل عکس پروفایل
const uploadDir = path.join(__dirname, '../../../../storage/profiles')
const uploadDir = path.join(__dirname, '../../../storage/profiles')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true })
}
@@ -78,7 +78,7 @@ const updateProfileImage = async (req, res, next) => {
})
}
const uploadDir = path.join(__dirname, '../../../../storage/profiles')
const uploadDir = path.join(__dirname, '../../../storage/profiles')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true })