2018-11-18 11:00:15 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
# 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 MigrateToGhostUserService
|
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
attr_reader :ghost_user, :user, :hard_delete
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
def initialize(user)
|
|
|
|
@user = user
|
2021-11-11 11:23:49 +05:30
|
|
|
@ghost_user = User.ghost
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
# If an admin attempts to hard delete a user, in some cases associated
|
|
|
|
# records may have a NOT NULL constraint on the user ID that prevent that record
|
|
|
|
# from being destroyed. In such situations we must assign the record to the ghost user.
|
|
|
|
# Passing in `hard_delete: true` will ensure these records get assigned to
|
|
|
|
# the ghost user before the user is destroyed. Other associated records will be destroyed.
|
|
|
|
# letting the other associated records be destroyed.
|
|
|
|
def execute(hard_delete: false)
|
|
|
|
@hard_delete = hard_delete
|
2017-08-17 22:00:37 +05:30
|
|
|
transition = user.block_transition
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
# Block the user before moving records to prevent a data race.
|
|
|
|
# For example, if the user creates an issue after `migrate_issues`
|
|
|
|
# runs and before the user is destroyed, the destroy will fail with
|
|
|
|
# an exception.
|
|
|
|
user.block
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
begin
|
|
|
|
user.transaction do
|
|
|
|
migrate_records
|
|
|
|
end
|
|
|
|
rescue Exception # rubocop:disable Lint/RescueException
|
2017-08-17 22:00:37 +05:30
|
|
|
# Reverse the user block if record migration fails
|
2021-11-11 11:23:49 +05:30
|
|
|
if transition
|
2017-08-17 22:00:37 +05:30
|
|
|
transition.rollback
|
|
|
|
user.save!
|
|
|
|
end
|
2021-11-11 11:23:49 +05:30
|
|
|
|
|
|
|
raise
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
user.reset
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
def migrate_records
|
2022-04-04 11:22:00 +05:30
|
|
|
return if hard_delete
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
migrate_issues
|
|
|
|
migrate_merge_requests
|
|
|
|
migrate_notes
|
|
|
|
migrate_abuse_reports
|
2018-10-15 14:42:47 +05:30
|
|
|
migrate_award_emoji
|
2020-05-24 23:13:21 +05:30
|
|
|
migrate_snippets
|
2020-06-23 00:09:42 +05:30
|
|
|
migrate_reviews
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
def migrate_issues
|
2022-05-07 20:08:51 +05:30
|
|
|
batched_migrate(Issue, :author_id)
|
|
|
|
batched_migrate(Issue, :last_edited_by_id)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
def migrate_merge_requests
|
2022-05-07 20:08:51 +05:30
|
|
|
batched_migrate(MergeRequest, :author_id)
|
|
|
|
batched_migrate(MergeRequest, :merge_user_id)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2018-12-05 23:21:45 +05:30
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
def migrate_notes
|
2022-05-07 20:08:51 +05:30
|
|
|
batched_migrate(Note, :author_id)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def migrate_abuse_reports
|
|
|
|
user.reported_abuse_reports.update_all(reporter_id: ghost_user.id)
|
|
|
|
end
|
|
|
|
|
2018-10-15 14:42:47 +05:30
|
|
|
def migrate_award_emoji
|
2017-08-17 22:00:37 +05:30
|
|
|
user.award_emoji.update_all(user_id: ghost_user.id)
|
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
def migrate_snippets
|
|
|
|
snippets = user.snippets.only_project_snippets
|
|
|
|
snippets.update_all(author_id: ghost_user.id)
|
|
|
|
end
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
def migrate_reviews
|
2022-05-07 20:08:51 +05:30
|
|
|
batched_migrate(Review, :author_id)
|
2020-06-23 00:09:42 +05:30
|
|
|
end
|
2022-05-07 20:08:51 +05:30
|
|
|
|
|
|
|
# rubocop:disable CodeReuse/ActiveRecord
|
|
|
|
def batched_migrate(base_scope, column)
|
|
|
|
loop do
|
|
|
|
update_count = base_scope.where(column => user.id).limit(100).update_all(column => ghost_user.id)
|
|
|
|
break if update_count == 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# rubocop:enable CodeReuse/ActiveRecord
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
2019-12-04 20:38:33 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
Users::MigrateToGhostUserService.prepend_mod_with('Users::MigrateToGhostUserService')
|