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 }