fix: resolve MongoDB connection and port configuration issues
- Added dotenv configuration for environment variables - Fixed MongoDB connection string loading - Configured server port to 3005 - Updated cron jobs for expired messages
This commit is contained in:
@@ -1,77 +1,77 @@
|
||||
const moment = require('moment-jalaali')
|
||||
|
||||
const BLOCK_THRESHOLD = 5
|
||||
const BLOCK_DURATION_MS = 7 * 24 * 60 * 60 * 1000
|
||||
|
||||
const applyAutoBlockSuspension = (user) => {
|
||||
if (user.blocked_by.length >= BLOCK_THRESHOLD) {
|
||||
user.block_status = true
|
||||
user.blocked_until = new Date(Date.now() + BLOCK_DURATION_MS)
|
||||
}
|
||||
}
|
||||
|
||||
const clearAutoBlockSuspension = (user) => {
|
||||
if (user.blocked_by.length < BLOCK_THRESHOLD && user.blocked_until) {
|
||||
user.block_status = null
|
||||
user.blocked_until = null
|
||||
}
|
||||
}
|
||||
|
||||
const isUserCurrentlyBlocked = (user) => {
|
||||
if (!user.block_status) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (user.blocked_until) {
|
||||
if (new Date() >= new Date(user.blocked_until)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return user.block_status === true
|
||||
}
|
||||
|
||||
const expireBlockSuspensionIfNeeded = async (user) => {
|
||||
if (!user.blocked_until) {
|
||||
return user
|
||||
}
|
||||
|
||||
if (new Date() >= new Date(user.blocked_until)) {
|
||||
user.block_status = null
|
||||
user.blocked_until = null
|
||||
await user.save()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
const getBlockedAccountResponse = (user) => {
|
||||
if (!isUserCurrentlyBlocked(user)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const untilText = user.blocked_until
|
||||
? moment(user.blocked_until).format('jYYYY/jMM/jDD')
|
||||
: null
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
code: 403,
|
||||
message: untilText
|
||||
? `حساب کاربری شما به دلیل بلاک شدن توسط ${BLOCK_THRESHOLD} کاربر تا تاریخ ${untilText} غیرفعال است.`
|
||||
: 'حساب کاربری شما مسدود شده است، برای اطلاعات بیشتر با پشتیبانی تماس بگیرید!',
|
||||
type: 'block',
|
||||
blocked_until: user.blocked_until || null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BLOCK_THRESHOLD,
|
||||
BLOCK_DURATION_MS,
|
||||
applyAutoBlockSuspension,
|
||||
clearAutoBlockSuspension,
|
||||
isUserCurrentlyBlocked,
|
||||
expireBlockSuspensionIfNeeded,
|
||||
getBlockedAccountResponse
|
||||
}
|
||||
const moment = require('moment-jalaali')
|
||||
|
||||
const BLOCK_THRESHOLD = 5
|
||||
const BLOCK_DURATION_MS = 7 * 24 * 60 * 60 * 1000
|
||||
|
||||
const applyAutoBlockSuspension = (user) => {
|
||||
if (user.blocked_by.length >= BLOCK_THRESHOLD) {
|
||||
user.block_status = true
|
||||
user.blocked_until = new Date(Date.now() + BLOCK_DURATION_MS)
|
||||
}
|
||||
}
|
||||
|
||||
const clearAutoBlockSuspension = (user) => {
|
||||
if (user.blocked_by.length < BLOCK_THRESHOLD && user.blocked_until) {
|
||||
user.block_status = null
|
||||
user.blocked_until = null
|
||||
}
|
||||
}
|
||||
|
||||
const isUserCurrentlyBlocked = (user) => {
|
||||
if (!user.block_status) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (user.blocked_until) {
|
||||
if (new Date() >= new Date(user.blocked_until)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return user.block_status === true
|
||||
}
|
||||
|
||||
const expireBlockSuspensionIfNeeded = async (user) => {
|
||||
if (!user.blocked_until) {
|
||||
return user
|
||||
}
|
||||
|
||||
if (new Date() >= new Date(user.blocked_until)) {
|
||||
user.block_status = null
|
||||
user.blocked_until = null
|
||||
await user.save()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
const getBlockedAccountResponse = (user) => {
|
||||
if (!isUserCurrentlyBlocked(user)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const untilText = user.blocked_until
|
||||
? moment(user.blocked_until).format('jYYYY/jMM/jDD')
|
||||
: null
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
code: 403,
|
||||
message: untilText
|
||||
? `حساب کاربری شما به دلیل بلاک شدن توسط ${BLOCK_THRESHOLD} کاربر تا تاریخ ${untilText} غیرفعال است.`
|
||||
: 'حساب کاربری شما مسدود شده است، برای اطلاعات بیشتر با پشتیبانی تماس بگیرید!',
|
||||
type: 'block',
|
||||
blocked_until: user.blocked_until || null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BLOCK_THRESHOLD,
|
||||
BLOCK_DURATION_MS,
|
||||
applyAutoBlockSuspension,
|
||||
clearAutoBlockSuspension,
|
||||
isUserCurrentlyBlocked,
|
||||
expireBlockSuspensionIfNeeded,
|
||||
getBlockedAccountResponse
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
function listIncludesUserId(list, userId) {
|
||||
if (!userId || !list?.length) return false
|
||||
return list.some((id) => String(id) === String(userId))
|
||||
}
|
||||
|
||||
/** Viewer has blocked the profile owner */
|
||||
function viewerBlockedUser(user, viewerId) {
|
||||
if (!user || !viewerId) return false
|
||||
return listIncludesUserId(user.blocked_by, viewerId)
|
||||
}
|
||||
|
||||
/** Profile owner has blocked the viewer */
|
||||
function userBlockedViewer(user, viewerId) {
|
||||
if (!user || !viewerId) return false
|
||||
return listIncludesUserId(user.blocked_users, viewerId)
|
||||
}
|
||||
|
||||
/** Viewer (blocked person) cannot see profile/posts of user who blocked them */
|
||||
function viewerIsBlockedBy(user, viewerId) {
|
||||
return userBlockedViewer(user, viewerId)
|
||||
}
|
||||
|
||||
function blockedProfilePayload(user) {
|
||||
return {
|
||||
_id: user._id,
|
||||
user_name: user.user_name,
|
||||
blocked_you: true,
|
||||
is_self: false,
|
||||
user_type: user.user_type,
|
||||
postsCount: 0,
|
||||
allProjectsCount: 0,
|
||||
successfulProjectsCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/** User IDs who have blocked the viewer */
|
||||
async function getBlockerUserIds(UserModel, viewerId) {
|
||||
if (!viewerId) return []
|
||||
return UserModel.find({ blocked_users: viewerId }).distinct('_id')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listIncludesUserId,
|
||||
viewerBlockedUser,
|
||||
userBlockedViewer,
|
||||
viewerIsBlockedBy,
|
||||
blockedProfilePayload,
|
||||
getBlockerUserIds,
|
||||
}
|
||||
function listIncludesUserId(list, userId) {
|
||||
if (!userId || !list?.length) return false
|
||||
return list.some((id) => String(id) === String(userId))
|
||||
}
|
||||
|
||||
/** Viewer has blocked the profile owner */
|
||||
function viewerBlockedUser(user, viewerId) {
|
||||
if (!user || !viewerId) return false
|
||||
return listIncludesUserId(user.blocked_by, viewerId)
|
||||
}
|
||||
|
||||
/** Profile owner has blocked the viewer */
|
||||
function userBlockedViewer(user, viewerId) {
|
||||
if (!user || !viewerId) return false
|
||||
return listIncludesUserId(user.blocked_users, viewerId)
|
||||
}
|
||||
|
||||
/** Viewer (blocked person) cannot see profile/posts of user who blocked them */
|
||||
function viewerIsBlockedBy(user, viewerId) {
|
||||
return userBlockedViewer(user, viewerId)
|
||||
}
|
||||
|
||||
function blockedProfilePayload(user) {
|
||||
return {
|
||||
_id: user._id,
|
||||
user_name: user.user_name,
|
||||
blocked_you: true,
|
||||
is_self: false,
|
||||
user_type: user.user_type,
|
||||
postsCount: 0,
|
||||
allProjectsCount: 0,
|
||||
successfulProjectsCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/** User IDs who have blocked the viewer */
|
||||
async function getBlockerUserIds(UserModel, viewerId) {
|
||||
if (!viewerId) return []
|
||||
return UserModel.find({ blocked_users: viewerId }).distinct('_id')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listIncludesUserId,
|
||||
viewerBlockedUser,
|
||||
userBlockedViewer,
|
||||
viewerIsBlockedBy,
|
||||
blockedProfilePayload,
|
||||
getBlockerUserIds,
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
const NotificationModel = require('../models/NotificationModel')
|
||||
const { resolveCourseOwnerId } = require('./likeNotification')
|
||||
|
||||
const COMMENT_TITLE = 'یک کامنت برای شما ثبت شد'
|
||||
const COMMENT_DESCRIPTION = 'یک کامنت برای شما ثبت شد'
|
||||
const RATING_TITLE = 'یک امتیاز برای شما ثبت شد'
|
||||
const RATING_DESCRIPTION = 'یک امتیاز برای شما ثبت شد'
|
||||
|
||||
const createCommentNotification = async ({
|
||||
ownerId,
|
||||
commenterId,
|
||||
entityId,
|
||||
type = 'post_comment'
|
||||
}) => {
|
||||
try {
|
||||
if (!ownerId || !commenterId || !entityId) return
|
||||
if (String(ownerId) === String(commenterId)) return
|
||||
|
||||
await NotificationModel.create({
|
||||
user_id: ownerId,
|
||||
project_post_id: entityId,
|
||||
type,
|
||||
title: COMMENT_TITLE,
|
||||
description: COMMENT_DESCRIPTION
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error creating comment notification:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const createRatingNotification = async ({
|
||||
ownerId,
|
||||
raterId,
|
||||
entityId,
|
||||
type = 'billboard_rating'
|
||||
}) => {
|
||||
try {
|
||||
if (!ownerId || !raterId || !entityId) return
|
||||
if (String(ownerId) === String(raterId)) return
|
||||
|
||||
await NotificationModel.create({
|
||||
user_id: ownerId,
|
||||
project_post_id: entityId,
|
||||
type,
|
||||
title: RATING_TITLE,
|
||||
description: RATING_DESCRIPTION
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error creating rating notification:', error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createCommentNotification,
|
||||
createRatingNotification,
|
||||
resolveCourseOwnerId
|
||||
}
|
||||
const NotificationModel = require('../models/NotificationModel')
|
||||
const { resolveCourseOwnerId } = require('./likeNotification')
|
||||
|
||||
const COMMENT_TITLE = 'یک کامنت برای شما ثبت شد'
|
||||
const COMMENT_DESCRIPTION = 'یک کامنت برای شما ثبت شد'
|
||||
const RATING_TITLE = 'یک امتیاز برای شما ثبت شد'
|
||||
const RATING_DESCRIPTION = 'یک امتیاز برای شما ثبت شد'
|
||||
|
||||
const createCommentNotification = async ({
|
||||
ownerId,
|
||||
commenterId,
|
||||
entityId,
|
||||
type = 'post_comment'
|
||||
}) => {
|
||||
try {
|
||||
if (!ownerId || !commenterId || !entityId) return
|
||||
if (String(ownerId) === String(commenterId)) return
|
||||
|
||||
await NotificationModel.create({
|
||||
user_id: ownerId,
|
||||
project_post_id: entityId,
|
||||
type,
|
||||
title: COMMENT_TITLE,
|
||||
description: COMMENT_DESCRIPTION
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error creating comment notification:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const createRatingNotification = async ({
|
||||
ownerId,
|
||||
raterId,
|
||||
entityId,
|
||||
type = 'billboard_rating'
|
||||
}) => {
|
||||
try {
|
||||
if (!ownerId || !raterId || !entityId) return
|
||||
if (String(ownerId) === String(raterId)) return
|
||||
|
||||
await NotificationModel.create({
|
||||
user_id: ownerId,
|
||||
project_post_id: entityId,
|
||||
type,
|
||||
title: RATING_TITLE,
|
||||
description: RATING_DESCRIPTION
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error creating rating notification:', error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createCommentNotification,
|
||||
createRatingNotification,
|
||||
resolveCourseOwnerId
|
||||
}
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
const CommentModel = require('../models/CommentModel')
|
||||
const UserModel = require('../models/UserModel')
|
||||
|
||||
const RATED_FILTER = { $ne: null, $gt: 0 }
|
||||
|
||||
const findExistingUserRating = (creatorId, targetUserId) => {
|
||||
return CommentModel.findOne({
|
||||
creator: creatorId,
|
||||
user: targetUserId,
|
||||
rating: RATED_FILTER
|
||||
})
|
||||
}
|
||||
|
||||
const hasCreatorRatedUser = async (creatorId, targetUserId) => {
|
||||
const existing = await findExistingUserRating(creatorId, targetUserId)
|
||||
return !!existing
|
||||
}
|
||||
|
||||
const resolveRatingForComment = async ({ creatorId, targetUserId, rate }) => {
|
||||
const existingRating = await findExistingUserRating(creatorId, targetUserId)
|
||||
|
||||
if (existingRating) {
|
||||
if (rate && rate >= 1 && rate <= 5) {
|
||||
return {
|
||||
ratingValue: null,
|
||||
isNewRating: false,
|
||||
alreadyRated: true,
|
||||
}
|
||||
}
|
||||
return { ratingValue: null, isNewRating: false }
|
||||
}
|
||||
|
||||
if (!rate || rate < 1 || rate > 5) {
|
||||
return { ratingValue: null, isNewRating: false, requiresRating: true }
|
||||
}
|
||||
|
||||
return { ratingValue: rate, isNewRating: true }
|
||||
}
|
||||
|
||||
const getUserLevel = (user, totalRating) => {
|
||||
if (user.expertise === 'مدل') {
|
||||
if (totalRating <= 50) return 'تازه وارد'
|
||||
if (totalRating <= 100) return 'استاندارد'
|
||||
if (totalRating <= 300) return 'حرفهای'
|
||||
return 'استاد'
|
||||
}
|
||||
|
||||
if (['زیبایی', 'عکاس'].includes(user.expertise)) {
|
||||
if (totalRating <= 50) return 'تازه وارد'
|
||||
if (totalRating <= 100) return 'استاندارد'
|
||||
if (totalRating <= 300) return 'حرفهای'
|
||||
return 'استاد'
|
||||
}
|
||||
|
||||
return user.user_level
|
||||
}
|
||||
|
||||
const recalculateUserRating = async (targetUserId) => {
|
||||
const user = await UserModel.findById(targetUserId)
|
||||
if (!user) return
|
||||
|
||||
const ratedComments = await CommentModel.find({
|
||||
user: targetUserId,
|
||||
rating: RATED_FILTER
|
||||
}).sort({ createdAt: 1 })
|
||||
|
||||
const uniqueRatingsByCreator = new Map()
|
||||
for (const comment of ratedComments) {
|
||||
const creatorKey = comment.creator.toString()
|
||||
if (!uniqueRatingsByCreator.has(creatorKey)) {
|
||||
uniqueRatingsByCreator.set(creatorKey, comment.rating)
|
||||
}
|
||||
}
|
||||
|
||||
const ratings = Array.from(uniqueRatingsByCreator.values())
|
||||
const totalRating = ratings.reduce((acc, rating) => acc + rating, 0)
|
||||
const ratedCount = ratings.length
|
||||
|
||||
user.user_score = totalRating
|
||||
user.user_level = getUserLevel(user, totalRating)
|
||||
user.rate = ratedCount > 0 ? (totalRating / ratedCount).toFixed(1) : '0'
|
||||
await user.save()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RATED_FILTER,
|
||||
findExistingUserRating,
|
||||
hasCreatorRatedUser,
|
||||
resolveRatingForComment,
|
||||
recalculateUserRating
|
||||
}
|
||||
const CommentModel = require('../models/CommentModel')
|
||||
const UserModel = require('../models/UserModel')
|
||||
|
||||
const RATED_FILTER = { $ne: null, $gt: 0 }
|
||||
|
||||
const findExistingUserRating = (creatorId, targetUserId) => {
|
||||
return CommentModel.findOne({
|
||||
creator: creatorId,
|
||||
user: targetUserId,
|
||||
rating: RATED_FILTER
|
||||
})
|
||||
}
|
||||
|
||||
const hasCreatorRatedUser = async (creatorId, targetUserId) => {
|
||||
const existing = await findExistingUserRating(creatorId, targetUserId)
|
||||
return !!existing
|
||||
}
|
||||
|
||||
const resolveRatingForComment = async ({ creatorId, targetUserId, rate }) => {
|
||||
const existingRating = await findExistingUserRating(creatorId, targetUserId)
|
||||
|
||||
if (existingRating) {
|
||||
if (rate && rate >= 1 && rate <= 5) {
|
||||
return {
|
||||
ratingValue: null,
|
||||
isNewRating: false,
|
||||
alreadyRated: true,
|
||||
}
|
||||
}
|
||||
return { ratingValue: null, isNewRating: false }
|
||||
}
|
||||
|
||||
if (!rate || rate < 1 || rate > 5) {
|
||||
return { ratingValue: null, isNewRating: false, requiresRating: true }
|
||||
}
|
||||
|
||||
return { ratingValue: rate, isNewRating: true }
|
||||
}
|
||||
|
||||
const getUserLevel = (user, totalRating) => {
|
||||
if (user.expertise === 'مدل') {
|
||||
if (totalRating <= 50) return 'تازه وارد'
|
||||
if (totalRating <= 100) return 'استاندارد'
|
||||
if (totalRating <= 300) return 'حرفهای'
|
||||
return 'استاد'
|
||||
}
|
||||
|
||||
if (['زیبایی', 'عکاس'].includes(user.expertise)) {
|
||||
if (totalRating <= 50) return 'تازه وارد'
|
||||
if (totalRating <= 100) return 'استاندارد'
|
||||
if (totalRating <= 300) return 'حرفهای'
|
||||
return 'استاد'
|
||||
}
|
||||
|
||||
return user.user_level
|
||||
}
|
||||
|
||||
const recalculateUserRating = async (targetUserId) => {
|
||||
const user = await UserModel.findById(targetUserId)
|
||||
if (!user) return
|
||||
|
||||
const ratedComments = await CommentModel.find({
|
||||
user: targetUserId,
|
||||
rating: RATED_FILTER
|
||||
}).sort({ createdAt: 1 })
|
||||
|
||||
const uniqueRatingsByCreator = new Map()
|
||||
for (const comment of ratedComments) {
|
||||
const creatorKey = comment.creator.toString()
|
||||
if (!uniqueRatingsByCreator.has(creatorKey)) {
|
||||
uniqueRatingsByCreator.set(creatorKey, comment.rating)
|
||||
}
|
||||
}
|
||||
|
||||
const ratings = Array.from(uniqueRatingsByCreator.values())
|
||||
const totalRating = ratings.reduce((acc, rating) => acc + rating, 0)
|
||||
const ratedCount = ratings.length
|
||||
|
||||
user.user_score = totalRating
|
||||
user.user_level = getUserLevel(user, totalRating)
|
||||
user.rate = ratedCount > 0 ? (totalRating / ratedCount).toFixed(1) : '0'
|
||||
await user.save()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RATED_FILTER,
|
||||
findExistingUserRating,
|
||||
hasCreatorRatedUser,
|
||||
resolveRatingForComment,
|
||||
recalculateUserRating
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,47 @@
|
||||
const NotificationModel = require('../models/NotificationModel')
|
||||
const AcademyModel = require('../models/AcademyModel')
|
||||
|
||||
const LIKE_TITLE = 'پست شما لایک شد'
|
||||
const LIKE_DESCRIPTION = 'پست شما لایک شد'
|
||||
|
||||
const createLikeNotification = async ({
|
||||
ownerId,
|
||||
likerId,
|
||||
entityId,
|
||||
type = 'post_like'
|
||||
}) => {
|
||||
try {
|
||||
if (!ownerId || !likerId || !entityId) return
|
||||
if (String(ownerId) === String(likerId)) return
|
||||
|
||||
await NotificationModel.create({
|
||||
user_id: ownerId,
|
||||
project_post_id: entityId,
|
||||
type,
|
||||
title: LIKE_TITLE,
|
||||
description: LIKE_DESCRIPTION
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error creating like notification:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const resolveCourseOwnerId = async (course) => {
|
||||
if (course?.user_id) {
|
||||
return course.user_id
|
||||
}
|
||||
|
||||
if (course?.academyId) {
|
||||
const academy = await AcademyModel.findById(course.academyId)
|
||||
if (academy?.userId) {
|
||||
return academy.userId
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createLikeNotification,
|
||||
resolveCourseOwnerId
|
||||
}
|
||||
const NotificationModel = require('../models/NotificationModel')
|
||||
const AcademyModel = require('../models/AcademyModel')
|
||||
|
||||
const LIKE_TITLE = 'پست شما لایک شد'
|
||||
const LIKE_DESCRIPTION = 'پست شما لایک شد'
|
||||
|
||||
const createLikeNotification = async ({
|
||||
ownerId,
|
||||
likerId,
|
||||
entityId,
|
||||
type = 'post_like'
|
||||
}) => {
|
||||
try {
|
||||
if (!ownerId || !likerId || !entityId) return
|
||||
if (String(ownerId) === String(likerId)) return
|
||||
|
||||
await NotificationModel.create({
|
||||
user_id: ownerId,
|
||||
project_post_id: entityId,
|
||||
type,
|
||||
title: LIKE_TITLE,
|
||||
description: LIKE_DESCRIPTION
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error creating like notification:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const resolveCourseOwnerId = async (course) => {
|
||||
if (course?.user_id) {
|
||||
return course.user_id
|
||||
}
|
||||
|
||||
if (course?.academyId) {
|
||||
const academy = await AcademyModel.findById(course.academyId)
|
||||
if (academy?.userId) {
|
||||
return academy.userId
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createLikeNotification,
|
||||
resolveCourseOwnerId
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
const OTP_VALID_MS = 3 * 60 * 1000
|
||||
|
||||
const isOtpExpired = (user) => {
|
||||
if (!user?.otp) return true
|
||||
if (!user?.otpSentAt) return false
|
||||
return Date.now() - new Date(user.otpSentAt).getTime() > OTP_VALID_MS
|
||||
}
|
||||
|
||||
const clearOtp = (UserModel, mobile) =>
|
||||
UserModel.updateOne({ mobile }, { $set: { otp: null, otpSentAt: null } })
|
||||
|
||||
module.exports = {
|
||||
OTP_VALID_MS,
|
||||
isOtpExpired,
|
||||
clearOtp
|
||||
}
|
||||
const OTP_VALID_MS = 3 * 60 * 1000
|
||||
|
||||
const isOtpExpired = (user) => {
|
||||
if (!user?.otp) return true
|
||||
if (!user?.otpSentAt) return false
|
||||
return Date.now() - new Date(user.otpSentAt).getTime() > OTP_VALID_MS
|
||||
}
|
||||
|
||||
const clearOtp = (UserModel, mobile) =>
|
||||
UserModel.updateOne({ mobile }, { $set: { otp: null, otpSentAt: null } })
|
||||
|
||||
module.exports = {
|
||||
OTP_VALID_MS,
|
||||
isOtpExpired,
|
||||
clearOtp
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
function canCreateProject(user) {
|
||||
return Boolean(
|
||||
user?.user_name?.trim() &&
|
||||
user?.first_name?.trim() &&
|
||||
user?.last_name?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
function respondProjectProfileIncomplete(res) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message:
|
||||
"برای ثبت پروژه، تکمیل نام کاربری، نام و نام خانوادگی در تنظیمات الزامی است",
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
canCreateProject,
|
||||
respondProjectProfileIncomplete,
|
||||
};
|
||||
function canCreateProject(user) {
|
||||
return Boolean(
|
||||
user?.user_name?.trim() &&
|
||||
user?.first_name?.trim() &&
|
||||
user?.last_name?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
function respondProjectProfileIncomplete(res) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message:
|
||||
"برای ثبت پروژه، تکمیل نام کاربری، نام و نام خانوادگی در تنظیمات الزامی است",
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
canCreateProject,
|
||||
respondProjectProfileIncomplete,
|
||||
};
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
const USER_LEVELS = {
|
||||
NEW: 'تازه وارد',
|
||||
STANDARD: 'استاندارد',
|
||||
PRO: 'حرفهای',
|
||||
MASTER: 'استاد',
|
||||
}
|
||||
|
||||
/** Normalize level strings from URL/query (handles ی / ZWNJ variants). */
|
||||
function normalizeUserLevel(raw) {
|
||||
if (raw == null || raw === '') return null
|
||||
|
||||
const text = String(raw)
|
||||
.trim()
|
||||
.replace(/\u200c/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
|
||||
if (text === USER_LEVELS.NEW) return USER_LEVELS.NEW
|
||||
if (text === USER_LEVELS.STANDARD) return USER_LEVELS.STANDARD
|
||||
if (text === USER_LEVELS.MASTER) return USER_LEVELS.MASTER
|
||||
if (text === USER_LEVELS.PRO || text === 'حرفه ای') return USER_LEVELS.PRO
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
/** Build Mongo filter for user_level (includes unset levels for تازه وارد). */
|
||||
function buildUserLevelMongoFilter(rawLevel) {
|
||||
const level = normalizeUserLevel(rawLevel)
|
||||
if (!level) return null
|
||||
|
||||
if (level === USER_LEVELS.NEW) {
|
||||
return {
|
||||
$or: [
|
||||
{ user_level: USER_LEVELS.NEW },
|
||||
{ user_level: null },
|
||||
{ user_level: '' },
|
||||
{ user_level: { $exists: false } },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return { user_level: level }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
USER_LEVELS,
|
||||
normalizeUserLevel,
|
||||
buildUserLevelMongoFilter,
|
||||
}
|
||||
const USER_LEVELS = {
|
||||
NEW: 'تازه وارد',
|
||||
STANDARD: 'استاندارد',
|
||||
PRO: 'حرفهای',
|
||||
MASTER: 'استاد',
|
||||
}
|
||||
|
||||
/** Normalize level strings from URL/query (handles ی / ZWNJ variants). */
|
||||
function normalizeUserLevel(raw) {
|
||||
if (raw == null || raw === '') return null
|
||||
|
||||
const text = String(raw)
|
||||
.trim()
|
||||
.replace(/\u200c/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
|
||||
if (text === USER_LEVELS.NEW) return USER_LEVELS.NEW
|
||||
if (text === USER_LEVELS.STANDARD) return USER_LEVELS.STANDARD
|
||||
if (text === USER_LEVELS.MASTER) return USER_LEVELS.MASTER
|
||||
if (text === USER_LEVELS.PRO || text === 'حرفه ای') return USER_LEVELS.PRO
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
/** Build Mongo filter for user_level (includes unset levels for تازه وارد). */
|
||||
function buildUserLevelMongoFilter(rawLevel) {
|
||||
const level = normalizeUserLevel(rawLevel)
|
||||
if (!level) return null
|
||||
|
||||
if (level === USER_LEVELS.NEW) {
|
||||
return {
|
||||
$or: [
|
||||
{ user_level: USER_LEVELS.NEW },
|
||||
{ user_level: null },
|
||||
{ user_level: '' },
|
||||
{ user_level: { $exists: false } },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return { user_level: level }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
USER_LEVELS,
|
||||
normalizeUserLevel,
|
||||
buildUserLevelMongoFilter,
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
const { validateUsername, USERNAME_MAX_LENGTH } = require('./usernameValidation')
|
||||
|
||||
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
const isUsernameTaken = async (UserModel, userName, excludeUserId) => {
|
||||
const existing = await UserModel.findOne({
|
||||
user_name: { $regex: new RegExp(`^${escapeRegex(userName)}$`, 'i') }
|
||||
})
|
||||
|
||||
if (!existing) return false
|
||||
if (excludeUserId && existing._id.toString() === excludeUserId) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const buildCandidates = (base, attempt) => {
|
||||
const random2 = () => String(Math.floor(Math.random() * 90 + 10))
|
||||
const random3 = () => String(Math.floor(Math.random() * 900 + 100))
|
||||
const random4 = () => String(Math.floor(Math.random() * 9000 + 1000))
|
||||
|
||||
const builders = [
|
||||
() => `${base}_${random2()}`,
|
||||
() => `${base}.${random2()}`,
|
||||
() => `${base}${random3()}`,
|
||||
() => `${base}-${random2()}`,
|
||||
() => `${base}_${random3()}`,
|
||||
() => `${base}${random4()}`,
|
||||
]
|
||||
|
||||
return builders[attempt % builders.length]()
|
||||
}
|
||||
|
||||
const trimToMax = (value) =>
|
||||
value.length > USERNAME_MAX_LENGTH ? value.slice(0, USERNAME_MAX_LENGTH) : value
|
||||
|
||||
const generateUsernameSuggestions = async (UserModel, baseUserName, excludeUserId) => {
|
||||
const base = trimToMax(baseUserName.trim().toLowerCase())
|
||||
const suggestions = []
|
||||
let attempt = 0
|
||||
|
||||
while (suggestions.length < 3 && attempt < 30) {
|
||||
const candidate = trimToMax(buildCandidates(base, attempt))
|
||||
attempt += 1
|
||||
|
||||
if (validateUsername(candidate)) continue
|
||||
if (suggestions.includes(candidate)) continue
|
||||
if (await isUsernameTaken(UserModel, candidate, excludeUserId)) continue
|
||||
|
||||
suggestions.push(candidate)
|
||||
}
|
||||
|
||||
return suggestions
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateUsernameSuggestions,
|
||||
}
|
||||
const { validateUsername, USERNAME_MAX_LENGTH } = require('./usernameValidation')
|
||||
|
||||
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
const isUsernameTaken = async (UserModel, userName, excludeUserId) => {
|
||||
const existing = await UserModel.findOne({
|
||||
user_name: { $regex: new RegExp(`^${escapeRegex(userName)}$`, 'i') }
|
||||
})
|
||||
|
||||
if (!existing) return false
|
||||
if (excludeUserId && existing._id.toString() === excludeUserId) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const buildCandidates = (base, attempt) => {
|
||||
const random2 = () => String(Math.floor(Math.random() * 90 + 10))
|
||||
const random3 = () => String(Math.floor(Math.random() * 900 + 100))
|
||||
const random4 = () => String(Math.floor(Math.random() * 9000 + 1000))
|
||||
|
||||
const builders = [
|
||||
() => `${base}_${random2()}`,
|
||||
() => `${base}.${random2()}`,
|
||||
() => `${base}${random3()}`,
|
||||
() => `${base}-${random2()}`,
|
||||
() => `${base}_${random3()}`,
|
||||
() => `${base}${random4()}`,
|
||||
]
|
||||
|
||||
return builders[attempt % builders.length]()
|
||||
}
|
||||
|
||||
const trimToMax = (value) =>
|
||||
value.length > USERNAME_MAX_LENGTH ? value.slice(0, USERNAME_MAX_LENGTH) : value
|
||||
|
||||
const generateUsernameSuggestions = async (UserModel, baseUserName, excludeUserId) => {
|
||||
const base = trimToMax(baseUserName.trim().toLowerCase())
|
||||
const suggestions = []
|
||||
let attempt = 0
|
||||
|
||||
while (suggestions.length < 3 && attempt < 30) {
|
||||
const candidate = trimToMax(buildCandidates(base, attempt))
|
||||
attempt += 1
|
||||
|
||||
if (validateUsername(candidate)) continue
|
||||
if (suggestions.includes(candidate)) continue
|
||||
if (await isUsernameTaken(UserModel, candidate, excludeUserId)) continue
|
||||
|
||||
suggestions.push(candidate)
|
||||
}
|
||||
|
||||
return suggestions
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateUsernameSuggestions,
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
/** حروف انگلیسی، اعداد و . _ - — بدون فارسی و کاراکترهای غیرمعمول */
|
||||
const USERNAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]{5,99}$/
|
||||
|
||||
const USERNAME_MIN_LENGTH = 6
|
||||
const USERNAME_MAX_LENGTH = 100
|
||||
|
||||
const validateUsername = (user_name) => {
|
||||
if (!user_name || typeof user_name !== 'string') {
|
||||
return 'نام کاربری الزامی است'
|
||||
}
|
||||
|
||||
const trimmed = user_name.trim()
|
||||
if (trimmed.length < USERNAME_MIN_LENGTH) {
|
||||
return `نام کاربری باید حداقل ${USERNAME_MIN_LENGTH} کاراکتر باشد`
|
||||
}
|
||||
|
||||
if (trimmed.length > USERNAME_MAX_LENGTH) {
|
||||
return `نام کاربری نمیتواند بیشتر از ${USERNAME_MAX_LENGTH} کاراکتر باشد`
|
||||
}
|
||||
|
||||
if (!USERNAME_REGEX.test(trimmed)) {
|
||||
return 'نام کاربری فقط میتواند شامل حروف انگلیسی، اعداد و کاراکترهای . _ - باشد'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
USERNAME_REGEX,
|
||||
USERNAME_MIN_LENGTH,
|
||||
USERNAME_MAX_LENGTH,
|
||||
validateUsername
|
||||
}
|
||||
/** حروف انگلیسی، اعداد و . _ - — بدون فارسی و کاراکترهای غیرمعمول */
|
||||
const USERNAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]{5,99}$/
|
||||
|
||||
const USERNAME_MIN_LENGTH = 6
|
||||
const USERNAME_MAX_LENGTH = 100
|
||||
|
||||
const validateUsername = (user_name) => {
|
||||
if (!user_name || typeof user_name !== 'string') {
|
||||
return 'نام کاربری الزامی است'
|
||||
}
|
||||
|
||||
const trimmed = user_name.trim()
|
||||
if (trimmed.length < USERNAME_MIN_LENGTH) {
|
||||
return `نام کاربری باید حداقل ${USERNAME_MIN_LENGTH} کاراکتر باشد`
|
||||
}
|
||||
|
||||
if (trimmed.length > USERNAME_MAX_LENGTH) {
|
||||
return `نام کاربری نمیتواند بیشتر از ${USERNAME_MAX_LENGTH} کاراکتر باشد`
|
||||
}
|
||||
|
||||
if (!USERNAME_REGEX.test(trimmed)) {
|
||||
return 'نام کاربری فقط میتواند شامل حروف انگلیسی، اعداد و کاراکترهای . _ - باشد'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
USERNAME_REGEX,
|
||||
USERNAME_MIN_LENGTH,
|
||||
USERNAME_MAX_LENGTH,
|
||||
validateUsername
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user