Welcome to mirror list, hosted at ThFree Co, Russian Federation.

migrate_records_to_ghost_user_service.rb « users « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 2d92aaed7dabdc410a0d3e8e4e1e6131bf9c7a73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 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')