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

165
utils/instagramUserAuth.js Normal file
View File

@@ -0,0 +1,165 @@
const UserModel = require('../models/UserModel')
const { downloadImageToProfiles } = require('./downloadRemoteImage')
async function findUserByInstagramId (instagramId) {
if (!instagramId) return null
return UserModel.findOne({ instagram_id: String(instagramId) })
}
/**
* Unlike Google, Instagram OAuth never returns an email — the only stable
* identity we have is instagram_id, so there is no email-based merge path.
*/
async function findExistingInstagramUser ({ instagramId }) {
return findUserByInstagramId(instagramId)
}
async function patchInstagramUser (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 createInstagramUser ({
instagramId,
username,
accountType,
tokenEnc,
tokenExpiresAt,
profilePictureUrl
}) {
const now = new Date()
const doc = {
instagram_id: String(instagramId),
instagram_username: username || null,
instagram_account_type: accountType || null,
instagram_access_token_enc: tokenEnc || null,
instagram_token_expires_at: tokenExpiresAt || null,
instagram_connected_at: now,
auth_provider: 'instagram',
createdAt: now,
updatedAt: now
}
if (profilePictureUrl) {
try {
doc.profile_image = await downloadImageToProfiles(profilePictureUrl, `instagram-${instagramId}`)
} catch (err) {
// آواتار اینستاگرام صرفاً یک مقدار پیش‌فرض دلپذیر است — شکست دانلود نباید ثبت‌نام را متوقف کند
console.error('[createInstagramUser] avatar download failed:', err.message)
}
}
try {
const result = await UserModel.collection.insertOne(doc)
const created = await UserModel.findById(result.insertedId)
if (created) return created
return findExistingInstagramUser({ instagramId })
} catch (err) {
if (err?.code !== 11000) throw err
const existing = await findExistingInstagramUser({ instagramId })
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 findExistingInstagramUser({ instagramId })
if (again) return again
}
throw retryErr
}
}
if (keyValue?.instagram_id) {
const byInstagram = await findUserByInstagramId(keyValue.instagram_id)
if (byInstagram) return byInstagram
}
throw err
}
}
/**
* Login/register via Instagram:
* - existing instagram_id → login (refresh token/profile fields)
* - otherwise create new user
*
* No email-based matching (Instagram doesn't provide one), so — unlike Google —
* there is no EMAIL_LINKED_TO_OTHER conflict case here.
*/
async function findOrCreateInstagramUser ({
instagramId,
username,
accountType,
tokenEnc,
tokenExpiresAt,
profilePictureUrl
}) {
const normalizedInstagramId = String(instagramId || '').trim()
if (!normalizedInstagramId) {
const err = new Error('MISSING_INSTAGRAM_IDENTITY')
err.code = 'MISSING_INSTAGRAM_IDENTITY'
throw err
}
const user = await findUserByInstagramId(normalizedInstagramId)
if (user) {
return (
(await patchInstagramUser(user._id, {
instagram_username: username,
instagram_account_type: accountType,
instagram_access_token_enc: tokenEnc,
instagram_token_expires_at: tokenExpiresAt
})) || user
)
}
return createInstagramUser({
instagramId: normalizedInstagramId,
username,
accountType,
tokenEnc,
tokenExpiresAt
})
}
module.exports = {
findUserByInstagramId,
findExistingInstagramUser,
patchInstagramUser,
createInstagramUser,
findOrCreateInstagramUser
}