gitlab-org--gitlab-foss/app/services/users/migrate_records_to_ghost_us...

112 lines
3.1 KiB
Ruby

# frozen_string_literal: true
# When a user is destroyed, some of their associated records are
# moved to a "Ghost User", to prevent these associated records from
# being destroyed.
#
# For example, all the issues/MRs a user has created are _not_ destroyed
# when the user is destroyed.
module Users
class MigrateRecordsToGhostUserService
extend ActiveSupport::Concern
DestroyError = Class.new(StandardError)
attr_reader :ghost_user, :user, :initiator_user, :hard_delete
def initialize(user, initiator_user, execution_tracker)
@user = user
@initiator_user = initiator_user
@execution_tracker = execution_tracker
@ghost_user = User.ghost
end
def execute(hard_delete: false)
@hard_delete = hard_delete
migrate_records
post_migrate_records
end
private
attr_reader :execution_tracker
def migrate_records
return if hard_delete
migrate_issues
migrate_merge_requests
migrate_notes
migrate_abuse_reports
migrate_award_emoji
migrate_snippets
migrate_reviews
end
def post_migrate_records
delete_snippets
# Rails attempts to load all related records into memory before
# destroying: https://github.com/rails/rails/issues/22510
# This ensures we delete records in batches.
user.destroy_dependent_associations_in_batches(exclude: [:snippets])
user.nullify_dependent_associations_in_batches
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
user_data = user.destroy
user.namespace.destroy
user_data
end
def delete_snippets
response = Snippets::BulkDestroyService.new(initiator_user, user.snippets).execute(skip_authorization: true)
raise DestroyError, response.message if response.error?
end
def migrate_issues
batched_migrate(Issue, :author_id)
batched_migrate(Issue, :last_edited_by_id)
end
def migrate_merge_requests
batched_migrate(MergeRequest, :author_id)
batched_migrate(MergeRequest, :merge_user_id)
end
def migrate_notes
batched_migrate(Note, :author_id)
end
def migrate_abuse_reports
user.reported_abuse_reports.update_all(reporter_id: ghost_user.id)
end
def migrate_award_emoji
user.award_emoji.update_all(user_id: ghost_user.id)
end
def migrate_snippets
snippets = user.snippets.only_project_snippets
snippets.update_all(author_id: ghost_user.id)
end
def migrate_reviews
batched_migrate(Review, :author_id)
end
# rubocop:disable CodeReuse/ActiveRecord
def batched_migrate(base_scope, column, batch_size: 50)
loop do
update_count = base_scope.where(column => user.id).limit(batch_size).update_all(column => ghost_user.id)
break if update_count == 0
raise Gitlab::Utils::ExecutionTracker::ExecutionTimeOutError if execution_tracker.over_limit?
end
end
# rubocop:enable CodeReuse/ActiveRecord
end
end
Users::MigrateRecordsToGhostUserService.prepend_mod_with('Users::MigrateRecordsToGhostUserService')