Initial commit
This commit is contained in:
@@ -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 {
|
||||
@@ -20,32 +22,33 @@ 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 کاراکتر باشد'
|
||||
message: validationError
|
||||
})
|
||||
}
|
||||
// بررسی محدودیتهای مجاز در نام کاربری
|
||||
// const usernameRegex = /^[a-zA-Z0-9_]+$/
|
||||
// if (!usernameRegex.test(user_name)) {
|
||||
// return res.status(422).json({
|
||||
// error: true,
|
||||
// message: 'نام کاربری فقط میتواند شامل حروف الفبای انگلیسی، اعداد و کاراکتر _ باشد'
|
||||
// })
|
||||
// }
|
||||
|
||||
const normalizedUserName = user_name.trim().toLowerCase()
|
||||
|
||||
// بررسی تکراری بودن user_name
|
||||
const existingUser = await UserModel.findOne({ user_name: { $regex: new RegExp('^' + user_name + '$', 'i') } })
|
||||
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: 'نام کاربری تکراری است'
|
||||
message: 'نام کاربری تکراری است',
|
||||
suggestions
|
||||
})
|
||||
}
|
||||
|
||||
// آپدیت نام کاربری به صورت lowercase
|
||||
user.user_name = user_name.toLowerCase()
|
||||
user.user_name = normalizedUserName
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
@@ -73,7 +76,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({
|
||||
|
||||
69
utils/usernameSuggestions.js
Normal file
69
utils/usernameSuggestions.js
Normal file
@@ -0,0 +1,69 @@
|
||||
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 = [
|
||||
(b) => `${b}${random2()}`,
|
||||
(b) => `${b}_${random3()}`,
|
||||
(b) => `${b}.${random2()}`,
|
||||
(b) => `${b}-${random2()}`,
|
||||
(b) => `${b}${random4()}`,
|
||||
(b) => `${b}_m${random2()}`,
|
||||
(b) => `${b}${attempt}`,
|
||||
]
|
||||
|
||||
return builders.map((fn) => {
|
||||
let candidate = fn(base)
|
||||
if (candidate.length > USERNAME_MAX_LENGTH) {
|
||||
candidate = candidate.slice(0, USERNAME_MAX_LENGTH)
|
||||
}
|
||||
return candidate
|
||||
})
|
||||
}
|
||||
|
||||
const generateUsernameSuggestions = async (
|
||||
UserModel,
|
||||
baseUsername,
|
||||
excludeUserId,
|
||||
count = 3
|
||||
) => {
|
||||
const base = baseUsername.trim().toLowerCase()
|
||||
const suggestions = []
|
||||
const tried = new Set([base])
|
||||
|
||||
for (let attempt = 1; attempt <= 60 && suggestions.length < count; attempt++) {
|
||||
const candidates = buildCandidates(base, attempt)
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (suggestions.length >= count) break
|
||||
if (tried.has(candidate)) continue
|
||||
tried.add(candidate)
|
||||
if (validateUsername(candidate)) continue
|
||||
|
||||
const taken = await isUsernameTaken(UserModel, candidate, excludeUserId)
|
||||
if (!taken) suggestions.push(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
return suggestions
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateUsernameSuggestions,
|
||||
isUsernameTaken
|
||||
}
|
||||
33
utils/usernameValidation.js
Normal file
33
utils/usernameValidation.js
Normal file
@@ -0,0 +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
|
||||
}
|
||||
Reference in New Issue
Block a user