add script to update social identities

This commit is contained in:
Tanmay Sharma
2025-07-29 07:45:34 +05:30
parent edab2b65bf
commit 2117a941af
2 changed files with 105 additions and 96 deletions
@@ -1,4 +1,4 @@
class Migration::MigrateAccountContactsJob < ApplicationJob
class Migration::PopulateAccountContactStatusAndSocialsJob < ApplicationJob
queue_as :async_database_migration
def perform(account_id)
@@ -7,111 +7,47 @@ class Migration::MigrateAccountContactsJob < ApplicationJob
Rails.logger.info "Processing contact migration for account #{account_id} (#{account.name})"
contacts = account.contacts.where(contact_type: 'visitor')
# Find the first lead contact to determine cutoff
first_lead = account.contacts.lead.order(:created_at).first
# Build the contacts scope in a more readable way
contacts_scope = if first_lead.present?
account.contacts.visitor.where('created_at < ?', first_lead.created_at)
else
account.contacts.visitor
end
total_count = contacts_scope.count
processed_count = 0
contacts.find_each do |contact|
processed_count += 1 if migrate_contact(contact)
contacts_scope.find_each(batch_size: 500) do |contact|
processed_count += 1 if sync_contact_attributes(contact)
end
Rails.logger.info "Processed #{processed_count} contacts for account #{account_id}"
Rails.logger.info "Processed #{processed_count}/#{total_count} contacts for account #{account_id}"
end
private
def migrate_contact(contact)
return false unless contact.contact_type == 'visitor'
def sync_contact_attributes(contact)
# Track only the fields we care about
original_contact_type = contact.contact_type
original_additional = contact.additional_attributes.dup
# Migrate additional_attributes for social cases
migrate_social_attributes(contact)
# Run the sync service with migration
::Contacts::SyncAttributes.new(contact).perform_with_migration
# Determine and update contact type
new_type = determine_contact_type(contact)
return false if new_type == 'visitor'
# Build update hash with only changed fields we care about
update_hash = {}
update_hash[:contact_type] = contact.contact_type if contact.contact_type != original_contact_type
update_hash[:additional_attributes] = contact.additional_attributes if contact.additional_attributes != original_additional
contact.update!(contact_type: new_type)
return false if update_hash.empty?
# Use update_columns to bypass callbacks and avoid triggering events
# rubocop:disable Rails/SkipsModelValidations
contact.update_columns(update_hash)
# rubocop:enable Rails/SkipsModelValidations
true
rescue StandardError => e
Rails.logger.error "Failed to migrate contact #{contact.id}: #{e.message}"
false
end
def migrate_social_attributes(contact)
additional_attrs = contact.additional_attributes || {}
social_profiles = additional_attrs['social_profiles'] || {}
return if social_profiles.blank? || social_profiles == {}
modified = process_social_platforms(contact, additional_attrs, social_profiles)
return unless modified
contact.update!(additional_attributes: additional_attrs)
end
def process_social_platforms(contact, attrs, profiles)
modified = false
# Telegram - migrate username only (ID comes from service)
modified ||= migrate_telegram_data(attrs, profiles)
# Facebook - migrate username only
modified ||= migrate_facebook_data(attrs, profiles)
# Instagram - migrate username only
modified ||= migrate_instagram_data(attrs, profiles)
# LINE - special case: migrate ID and derive username from contact name
modified ||= migrate_line_data(contact, attrs, profiles)
modified
end
def migrate_telegram_data(attrs, profiles)
# Check both social_profiles.telegram and legacy username field
telegram_username = profiles['telegram'].presence || attrs['username'].presence
return false if telegram_username.blank?
attrs['social_telegram_user_name'] ||= telegram_username
true
end
def migrate_facebook_data(attrs, profiles)
return false if profiles['facebook'].blank?
attrs['social_facebook_user_name'] ||= profiles['facebook']
true
end
def migrate_instagram_data(attrs, profiles)
return false if profiles['instagram'].blank?
attrs['social_instagram_user_name'] ||= profiles['instagram']
true
end
def migrate_line_data(contact, attrs, profiles)
return false if profiles['line'].blank?
attrs['social_line_user_id'] ||= profiles['line']
attrs['social_line_user_name'] ||= contact.name.presence || profiles['line']
true
end
def identification?(contact)
contact.email.present? || contact.phone_number.present? || contact.identifier.present? || social_details_present?(contact)
end
def social_details_present?(contact)
contact.additional_attributes.keys.any? do |key|
key.start_with?('social_') && contact.additional_attributes[key].present?
end
end
def determine_contact_type(contact)
# Visitor: No identification info
return 'visitor' unless identification?(contact)
# Lead: Has identification (email, phone, or social details)
'lead'
end
end
+74 -1
View File
@@ -10,6 +10,12 @@ class Contacts::SyncAttributes
set_contact_type
end
def perform_with_migration
update_contact_location_and_country_code
migrate_social_attributes
set_contact_type
end
private
def update_contact_location_and_country_code
@@ -24,7 +30,7 @@ class Contacts::SyncAttributes
return unless @contact.contact_type == 'visitor'
# If the contact has an email or phone number or social details( facebook_user_id, instagram_user_id, etc) then it is a lead
# If contact is from external channel like facebook, instagram, whatsapp, etc then it is a lead
return unless @contact.email.present? || @contact.phone_number.present? || social_details_present?
return unless @contact.email.present? || @contact.phone_number.present? || @contact.identifier.present? || social_details_present?
@contact.contact_type = 'lead'
end
@@ -34,4 +40,71 @@ class Contacts::SyncAttributes
key.start_with?('social_') && @contact.additional_attributes[key].present?
end
end
def migrate_social_attributes
return if @contact.additional_attributes.blank?
attrs = @contact.additional_attributes
social_profiles = attrs['social_profiles'] || {}
return if social_profiles.blank?
# Migrate Telegram - check both social_profiles and legacy username field
migrate_telegram_attributes(attrs, social_profiles)
# Migrate Facebook
migrate_facebook_attributes(attrs, social_profiles)
# Migrate LINE
migrate_line_attributes(attrs, social_profiles)
end
def migrate_telegram_attributes(attrs, profiles)
# Previous format
# {
# "social_profiles": {
# "telegram": "gilpadraocruz"
# }
# }
# New format
# {
# "social_telegram_user_name": "gilpadraocruz"
# }
telegram_username = profiles['telegram'].presence || attrs['username'].presence
return if telegram_username.blank?
attrs['social_telegram_user_name'] ||= telegram_username
end
def migrate_facebook_attributes(attrs, profiles)
# Previous format
# {
# "social_profiles": {
# "facebook": "gilpadraocruz"
# }
# }
# New format
# {
# "social_facebook_user_name": "gilpadraocruz"
# }
return if profiles['facebook'].blank?
attrs['social_facebook_user_name'] ||= profiles['facebook']
end
def migrate_line_attributes(attrs, profiles)
# Previous format
# {
# "social_profiles": {
# "line": "gilpadraocruz"
# }
# }
# New format
# {
# "social_line_user_id": "gilpadraocruz"
# }
return if profiles['line'].blank?
attrs['social_line_user_id'] ||= profiles['line']
end
end