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

182
utils/googleUserAuth.js Normal file
View File

@@ -0,0 +1,182 @@
const UserModel = require('../models/UserModel')
function escapeRegex (value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function normalizeEmail (email) {
return String(email || '').toLowerCase().trim()
}
async function findUserByGoogleId (googleId) {
if (!googleId) return null
return UserModel.findOne({ google_id: String(googleId) })
}
async function findUserByEmail (email) {
if (!email) return null
const normalized = normalizeEmail(email)
return UserModel.findOne({
$or: [
{ email: normalized },
{ email: new RegExp(`^${escapeRegex(normalized)}$`, 'i') },
],
})
}
async function findExistingGoogleUser ({ googleId, email }) {
const byGoogle = await findUserByGoogleId(googleId)
if (byGoogle) return byGoogle
return findUserByEmail(email)
}
async function patchGoogleUser (userId, fields) {
// فقط فیلدهای واقعی؛ هرگز uniqueهای sparse را null نکن
const $set = {}
for (const [key, value] of Object.entries(fields || {})) {
if (value === undefined || value === null || value === '') continue
$set[key] = value
}
if (Object.keys($set).length === 0) {
return UserModel.findById(userId)
}
await UserModel.collection.updateOne(
{ _id: userId },
{ $set }
)
return UserModel.findById(userId)
}
function getDuplicateKeyValue (err) {
return err?.keyValue && typeof err.keyValue === 'object' ? err.keyValue : null
}
/**
* ساخت کاربر گوگل بدون اعمال defaultهای null روی فیلدهای unique/sparse
*/
async function createGoogleUser ({ googleId, email }) {
const now = new Date()
const doc = {
google_id: String(googleId),
email: normalizeEmail(email),
email_verified: true,
auth_provider: 'google',
createdAt: now,
updatedAt: now,
}
try {
const result = await UserModel.collection.insertOne(doc)
const created = await UserModel.findById(result.insertedId)
if (created) return created
return findExistingGoogleUser({ googleId, email })
} catch (err) {
if (err?.code !== 11000) throw err
const existing = await findExistingGoogleUser({ googleId, email })
if (existing) return existing
const keyValue = getDuplicateKeyValue(err)
// ایندکس غیر-sparse روی mobile:null — nullها را پاک کن و دوباره بساز
if (keyValue && Object.prototype.hasOwnProperty.call(keyValue, 'mobile')) {
try {
await UserModel.collection.updateMany(
{ mobile: null },
{ $unset: { mobile: '' } }
)
const retry = await UserModel.collection.insertOne(doc)
return UserModel.findById(retry.insertedId)
} catch (retryErr) {
if (retryErr?.code === 11000) {
const again = await findExistingGoogleUser({ googleId, email })
if (again) return again
}
throw retryErr
}
}
if (keyValue?.email) {
const byEmail = await findUserByEmail(keyValue.email)
if (byEmail) {
return (
(await patchGoogleUser(byEmail._id, {
google_id: String(googleId),
email: normalizeEmail(byEmail.email || email),
email_verified: true,
})) || byEmail
)
}
}
if (keyValue?.google_id) {
const byGoogle = await findUserByGoogleId(keyValue.google_id)
if (byGoogle) return byGoogle
}
throw err
}
}
/**
* Login/register via Google:
* - existing google_id → login
* - existing email without google / same google → link + login
* - existing email with different google_id → conflict
* - otherwise create new user
*/
async function findOrCreateGoogleUser ({ googleId, email }) {
const normalizedEmail = normalizeEmail(email)
const normalizedGoogleId = String(googleId || '').trim()
if (!normalizedGoogleId || !normalizedEmail) {
const err = new Error('MISSING_GOOGLE_IDENTITY')
err.code = 'MISSING_GOOGLE_IDENTITY'
throw err
}
let user = await findUserByGoogleId(normalizedGoogleId)
if (user) {
const patch = {}
if (!user.email) patch.email = normalizedEmail
if (!user.email_verified) patch.email_verified = true
if (Object.keys(patch).length > 0) {
return (await patchGoogleUser(user._id, patch)) || user
}
return user
}
user = await findUserByEmail(normalizedEmail)
if (user) {
const existingGoogleId = user.google_id ? String(user.google_id).trim() : ''
if (existingGoogleId && existingGoogleId !== normalizedGoogleId) {
const conflict = new Error('EMAIL_LINKED_TO_OTHER_GOOGLE')
conflict.code = 'EMAIL_LINKED_TO_OTHER_GOOGLE'
throw conflict
}
return (
(await patchGoogleUser(user._id, {
google_id: normalizedGoogleId,
email: normalizeEmail(user.email || normalizedEmail),
email_verified: true,
})) || user
)
}
return createGoogleUser({
googleId: normalizedGoogleId,
email: normalizedEmail,
})
}
module.exports = {
findOrCreateGoogleUser,
findExistingGoogleUser,
}