forked from cybrespace/mastodon
		
	Serialize ActivityPub alternate link into OStatus deletes, handle it (#4730)
Requires moving Atom rendering from DistributionWorker (where `stream_entry.status` is already nil) to inline (where `stream_entry.status.destroyed?` is true) and distributing that. Unfortunately, such XML renderings can no longer be easily chained together into one payload of n items.
This commit is contained in:
		
							parent
							
								
									9958eba356
								
							
						
					
					
						commit
						4c76402ba1
					
				
					 7 changed files with 57 additions and 20 deletions
				
			
		| 
						 | 
					@ -3,7 +3,9 @@
 | 
				
			||||||
class OStatus::Activity::Deletion < OStatus::Activity::Base
 | 
					class OStatus::Activity::Deletion < OStatus::Activity::Base
 | 
				
			||||||
  def perform
 | 
					  def perform
 | 
				
			||||||
    Rails.logger.debug "Deleting remote status #{id}"
 | 
					    Rails.logger.debug "Deleting remote status #{id}"
 | 
				
			||||||
    status = Status.find_by(uri: id, account: @account)
 | 
					
 | 
				
			||||||
 | 
					    status   = Status.find_by(uri: id, account: @account)
 | 
				
			||||||
 | 
					    status ||= Status.find_by(uri: activitypub_uri, account: @account) if activitypub_uri?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if status.nil?
 | 
					    if status.nil?
 | 
				
			||||||
      redis.setex("delete_upon_arrival:#{@account.id}:#{id}", 6 * 3_600, id)
 | 
					      redis.setex("delete_upon_arrival:#{@account.id}:#{id}", 6 * 3_600, id)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,6 +79,9 @@ class OStatus::AtomSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if stream_entry.status.nil?
 | 
					    if stream_entry.status.nil?
 | 
				
			||||||
      append_element(entry, 'content', 'Deleted status')
 | 
					      append_element(entry, 'content', 'Deleted status')
 | 
				
			||||||
 | 
					    elsif stream_entry.status.destroyed?
 | 
				
			||||||
 | 
					      append_element(entry, 'content', 'Deleted status')
 | 
				
			||||||
 | 
					      append_element(entry, 'link', nil, rel: :alternate, type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(stream_entry.status)) if stream_entry.account.local?
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      serialize_status_attributes(entry, stream_entry.status)
 | 
					      serialize_status_attributes(entry, stream_entry.status)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,7 @@ class Status < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_one :notification, as: :activity, dependent: :destroy
 | 
					  has_one :notification, as: :activity, dependent: :destroy
 | 
				
			||||||
  has_one :preview_card, dependent: :destroy
 | 
					  has_one :preview_card, dependent: :destroy
 | 
				
			||||||
 | 
					  has_one :stream_entry, as: :activity, inverse_of: :status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :uri, uniqueness: true, unless: :local?
 | 
					  validates :uri, uniqueness: true, unless: :local?
 | 
				
			||||||
  validates :text, presence: true, unless: :reblog?
 | 
					  validates :text, presence: true, unless: :reblog?
 | 
				
			||||||
| 
						 | 
					@ -90,7 +91,11 @@ class Status < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def verb
 | 
					  def verb
 | 
				
			||||||
    reblog? ? :share : :post
 | 
					    if destroyed?
 | 
				
			||||||
 | 
					      :delete
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      reblog? ? :share : :post
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def object_type
 | 
					  def object_type
 | 
				
			||||||
| 
						 | 
					@ -110,7 +115,11 @@ class Status < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def title
 | 
					  def title
 | 
				
			||||||
    reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
 | 
					    if destroyed?
 | 
				
			||||||
 | 
					      "#{account.acct} deleted status"
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def hidden?
 | 
					  def hidden?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,9 +20,10 @@ class BatchedRemoveStatusService < BaseService
 | 
				
			||||||
    @activity_json_batches = []
 | 
					    @activity_json_batches = []
 | 
				
			||||||
    @json_payloads         = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h
 | 
					    @json_payloads         = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h
 | 
				
			||||||
    @activity_json         = {}
 | 
					    @activity_json         = {}
 | 
				
			||||||
 | 
					    @activity_xml          = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Ensure that rendered XML reflects destroyed state
 | 
					    # Ensure that rendered XML reflects destroyed state
 | 
				
			||||||
    Status.where(id: statuses.map(&:id)).in_batches.destroy_all
 | 
					    statuses.each(&:destroy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Batch by source account
 | 
					    # Batch by source account
 | 
				
			||||||
    statuses.group_by(&:account_id).each do |_, account_statuses|
 | 
					    statuses.group_by(&:account_id).each do |_, account_statuses|
 | 
				
			||||||
| 
						 | 
					@ -31,7 +32,7 @@ class BatchedRemoveStatusService < BaseService
 | 
				
			||||||
      unpush_from_home_timelines(account_statuses)
 | 
					      unpush_from_home_timelines(account_statuses)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if account.local?
 | 
					      if account.local?
 | 
				
			||||||
        batch_stream_entries(account_statuses)
 | 
					        batch_stream_entries(account, account_statuses)
 | 
				
			||||||
        batch_activity_json(account, account_statuses)
 | 
					        batch_activity_json(account, account_statuses)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -42,18 +43,16 @@ class BatchedRemoveStatusService < BaseService
 | 
				
			||||||
      batch_salmon_slaps(status) if status.local?
 | 
					      batch_salmon_slaps(status) if status.local?
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Pubsubhubbub::DistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
 | 
					    Pubsubhubbub::RawDistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
 | 
				
			||||||
    NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
 | 
					    NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
 | 
				
			||||||
    ActivityPub::DeliveryWorker.push_bulk(@activity_json_batches) { |batch| batch }
 | 
					    ActivityPub::DeliveryWorker.push_bulk(@activity_json_batches) { |batch| batch }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def batch_stream_entries(statuses)
 | 
					  def batch_stream_entries(account, statuses)
 | 
				
			||||||
    stream_entry_ids = statuses.map { |s| s.stream_entry.id }
 | 
					    statuses.each do |status|
 | 
				
			||||||
 | 
					      @stream_entry_batches << [build_xml(status.stream_entry), account.id]
 | 
				
			||||||
    stream_entry_ids.each_slice(100) do |batch_of_stream_entry_ids|
 | 
					 | 
				
			||||||
      @stream_entry_batches << [batch_of_stream_entry_ids]
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,11 +100,10 @@ class BatchedRemoveStatusService < BaseService
 | 
				
			||||||
  def batch_salmon_slaps(status)
 | 
					  def batch_salmon_slaps(status)
 | 
				
			||||||
    return if @mentions[status.id].empty?
 | 
					    return if @mentions[status.id].empty?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    payload    = stream_entry_to_xml(status.stream_entry.reload)
 | 
					 | 
				
			||||||
    recipients = @mentions[status.id].map(&:account).reject(&:local?).select(&:ostatus?).uniq(&:domain).map(&:id)
 | 
					    recipients = @mentions[status.id].map(&:account).reject(&:local?).select(&:ostatus?).uniq(&:domain).map(&:id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    recipients.each do |recipient_id|
 | 
					    recipients.each do |recipient_id|
 | 
				
			||||||
      @salmon_batches << [payload, status.account_id, recipient_id]
 | 
					      @salmon_batches << [build_xml(status.stream_entry), status.account_id, recipient_id]
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -145,6 +143,12 @@ class BatchedRemoveStatusService < BaseService
 | 
				
			||||||
    ).as_json)
 | 
					    ).as_json)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_xml(stream_entry)
 | 
				
			||||||
 | 
					    return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @activity_xml[stream_entry.id] = stream_entry_to_xml(stream_entry)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def sign_json(status, json)
 | 
					  def sign_json(status, json)
 | 
				
			||||||
    Oj.dump(ActivityPub::LinkedDataSignature.new(json).sign!(status.account))
 | 
					    Oj.dump(ActivityPub::LinkedDataSignature.new(json).sign!(status.account))
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,8 +22,6 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return unless @account.local?
 | 
					    return unless @account.local?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @stream_entry = @stream_entry.reload
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    remove_from_remote_followers
 | 
					    remove_from_remote_followers
 | 
				
			||||||
    remove_from_remote_affected
 | 
					    remove_from_remote_affected
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -62,7 +60,7 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def remove_from_remote_followers
 | 
					  def remove_from_remote_followers
 | 
				
			||||||
    # OStatus
 | 
					    # OStatus
 | 
				
			||||||
    Pubsubhubbub::DistributionWorker.perform_async(@stream_entry.id)
 | 
					    Pubsubhubbub::RawDistributionWorker.perform_async(salmon_xml, @account.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # ActivityPub
 | 
					    # ActivityPub
 | 
				
			||||||
    ActivityPub::DeliveryWorker.push_bulk(@account.followers.inboxes) do |inbox_url|
 | 
					    ActivityPub::DeliveryWorker.push_bulk(@account.followers.inboxes) do |inbox_url|
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								app/workers/pubsubhubbub/raw_distribution_worker.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/workers/pubsubhubbub/raw_distribution_worker.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Pubsubhubbub::RawDistributionWorker
 | 
				
			||||||
 | 
					  include Sidekiq::Worker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sidekiq_options queue: 'push'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def perform(xml, source_account_id)
 | 
				
			||||||
 | 
					    @account       = Account.find(source_account_id)
 | 
				
			||||||
 | 
					    @subscriptions = active_subscriptions.to_a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions) do |subscription|
 | 
				
			||||||
 | 
					      [subscription.id, xml]
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def active_subscriptions
 | 
				
			||||||
 | 
					    Subscription.where(account: @account).active.select('id, callback_url, domain')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -48,11 +48,10 @@ RSpec.describe BatchedRemoveStatusService do
 | 
				
			||||||
    expect(Redis.current).to have_received(:publish).with('timeline:public', any_args).at_least(:once)
 | 
					    expect(Redis.current).to have_received(:publish).with('timeline:public', any_args).at_least(:once)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it 'sends PuSH update to PuSH subscribers with two payloads united' do
 | 
					  it 'sends PuSH update to PuSH subscribers' do
 | 
				
			||||||
    expect(a_request(:post, 'http://example.com/push').with { |req|
 | 
					    expect(a_request(:post, 'http://example.com/push').with { |req|
 | 
				
			||||||
      matches = req.body.scan(TagManager::VERBS[:delete])
 | 
					      matches = req.body.match(TagManager::VERBS[:delete])
 | 
				
			||||||
      matches.size == 2
 | 
					    }).to have_been_made.at_least_once
 | 
				
			||||||
    }).to have_been_made
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it 'sends Salmon slap to previously mentioned users' do
 | 
					  it 'sends Salmon slap to previously mentioned users' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue