about summary refs log tree commit diff
path: root/app/services/suspend_account_service.rb
blob: 6c2ecad30f619b69198ae27739e5dbc743c0fb56 (plain) (blame)
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# frozen_string_literal: true

class SuspendAccountService < BaseService
  ASSOCIATIONS_ON_SUSPEND = %w(
    account_pins
    active_relationships
    block_relationships
    blocked_by_relationships
    conversation_mutes
    conversations
    custom_filters
    domain_blocks
    favourites
    follow_requests
    list_accounts
    media_attachments
    mute_relationships
    muted_by_relationships
    notifications
    owned_lists
    passive_relationships
    report_notes
    scheduled_statuses
    status_pins
    stream_entries
    subscriptions
  ).freeze

  ASSOCIATIONS_ON_DESTROY = %w(
    reports
    targeted_moderation_notes
    targeted_reports
  ).freeze

  # Suspend an account and remove as much of its data as possible
  # @param [Account]
  # @param [Hash] options
  # @option [Boolean] :including_user Remove the user record as well
  # @option [Boolean] :destroy Remove the account record instead of suspending
  def call(account, **options)
    @account = account
    @options = options

    reject_follows!
    purge_user!
    purge_profile!
    purge_content!
  end

  private

  def reject_follows!
    return if @account.local? || !@account.activitypub?

    ActivityPub::DeliveryWorker.push_bulk(Follow.where(account: @account)) do |follow|
      [build_reject_json(follow), follow.target_account_id, follow.account.inbox_url]
    end
  end

  def purge_user!
    return if !@account.local? || @account.user.nil?

    if @options[:including_user]
      @account.user.destroy
    else
      @account.user.disable!
    end
  end

  def purge_content!
    distribute_delete_actor! if @account.local? && !@options[:skip_distribution]

    @account.statuses.reorder(nil).find_in_batches do |statuses|
      BatchedRemoveStatusService.new.call(statuses, skip_side_effects: @options[:destroy])
    end

    associations_for_destruction.each do |association_name|
      destroy_all(@account.public_send(association_name))
    end

    @account.destroy if @options[:destroy]
  end

  def purge_profile!
    # If the account is going to be destroyed
    # there is no point wasting time updating
    # its values first

    return if @options[:destroy]

    @account.silenced         = false
    @account.suspended        = true
    @account.locked           = false
    @account.display_name     = ''
    @account.note             = ''
    @account.fields           = []
    @account.statuses_count   = 0
    @account.followers_count  = 0
    @account.following_count  = 0
    @account.moved_to_account = nil
    @account.avatar.destroy
    @account.header.destroy
    @account.save!
  end

  def destroy_all(association)
    association.in_batches.destroy_all
  end

  def distribute_delete_actor!
    ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url|
      [delete_actor_json, @account.id, inbox_url]
    end

    ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes) do |inbox_url|
      [delete_actor_json, @account.id, inbox_url]
    end
  end

  def delete_actor_json
    return @delete_actor_json if defined?(@delete_actor_json)

    payload = ActiveModelSerializers::SerializableResource.new(
      @account,
      serializer: ActivityPub::DeleteActorSerializer,
      adapter: ActivityPub::Adapter
    ).as_json

    @delete_actor_json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
  end

  def build_reject_json(follow)
    ActiveModelSerializers::SerializableResource.new(
      follow,
      serializer: ActivityPub::RejectFollowSerializer,
      adapter: ActivityPub::Adapter
    ).to_json
  end

  def delivery_inboxes
    @delivery_inboxes ||= @account.followers.inboxes + Relay.enabled.pluck(:inbox_url)
  end

  def low_priority_delivery_inboxes
    Account.inboxes - delivery_inboxes
  end

  def associations_for_destruction
    if @options[:destroy]
      ASSOCIATIONS_ON_SUSPEND + ASSOCIATIONS_ON_DESTROY
    else
      ASSOCIATIONS_ON_SUSPEND
    end
  end
end