Files
modstagram-back/utils/instagramUserAuth.js
payacom 3f416a7a60 full
2026-08-31 20:45:41 +03:30

166 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}