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:
root
2026-07-11 14:06:46 +03:30
parent 51eb7d0224
commit c62e4c0577
175 changed files with 23463 additions and 21573 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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