Merge branch 'main' into glitch-soc/merge-upstream
This commit is contained in:
		
						commit
						536892b8ae
					
				| 
						 | 
					@ -12,7 +12,7 @@ export function fetchSuggestions(withRelationships = false) {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
    dispatch(fetchSuggestionsRequest());
 | 
					    dispatch(fetchSuggestionsRequest());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    api(getState).get('/api/v2/suggestions').then(response => {
 | 
					    api(getState).get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
 | 
				
			||||||
      dispatch(importFetchedAccounts(response.data.map(x => x.account)));
 | 
					      dispatch(importFetchedAccounts(response.data.map(x => x.account)));
 | 
				
			||||||
      dispatch(fetchSuggestionsSuccess(response.data));
 | 
					      dispatch(fetchSuggestionsSuccess(response.data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@ const messages = defineMessages({
 | 
				
			||||||
  publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
 | 
					  publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
 | 
					const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started|^\/start/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default @(component => injectIntl(component, { withRef: true }))
 | 
					export default @(component => injectIntl(component, { withRef: true }))
 | 
				
			||||||
class ColumnsArea extends ImmutablePureComponent {
 | 
					class ColumnsArea extends ImmutablePureComponent {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -144,7 +144,7 @@ class ActivityPub::Activity
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def delete_later!(uri)
 | 
					  def delete_later!(uri)
 | 
				
			||||||
    redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
 | 
					    redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, true)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def status_from_object
 | 
					  def status_from_object
 | 
				
			||||||
| 
						 | 
					@ -210,12 +210,22 @@ class ActivityPub::Activity
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def lock_or_return(key, expire_after = 7.days.seconds)
 | 
					  def lock_or_return(key, expire_after = 2.hours.seconds)
 | 
				
			||||||
    yield if redis.set(key, true, nx: true, ex: expire_after)
 | 
					    yield if redis.set(key, true, nx: true, ex: expire_after)
 | 
				
			||||||
  ensure
 | 
					  ensure
 | 
				
			||||||
    redis.del(key)
 | 
					    redis.del(key)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def lock_or_fail(key)
 | 
				
			||||||
 | 
					    RedisLock.acquire({ redis: Redis.current, key: key }) do |lock|
 | 
				
			||||||
 | 
					      if lock.acquired?
 | 
				
			||||||
 | 
					        yield
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        raise Mastodon::RaceConditionError
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def fetch?
 | 
					  def fetch?
 | 
				
			||||||
    !@options[:delivery]
 | 
					    !@options[:delivery]
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
 | 
				
			||||||
  def perform
 | 
					  def perform
 | 
				
			||||||
    return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
 | 
					    return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RedisLock.acquire(lock_options) do |lock|
 | 
					    lock_or_fail("announce:#{@object['id']}") do
 | 
				
			||||||
      if lock.acquired?
 | 
					 | 
				
			||||||
      original_status = status_from_object
 | 
					      original_status = status_from_object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return reject_payload! if original_status.nil? || !announceable?(original_status)
 | 
					      return reject_payload! if original_status.nil? || !announceable?(original_status)
 | 
				
			||||||
| 
						 | 
					@ -24,9 +23,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      distribute(@status)
 | 
					      distribute(@status)
 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        raise Mastodon::RaceConditionError
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @status
 | 
					    @status
 | 
				
			||||||
| 
						 | 
					@ -69,8 +65,4 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
 | 
				
			||||||
  def reblog_of_local_status?
 | 
					  def reblog_of_local_status?
 | 
				
			||||||
    status_from_uri(object_uri)&.account&.local?
 | 
					    status_from_uri(object_uri)&.account&.local?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def lock_options
 | 
					 | 
				
			||||||
    { redis: Redis.current, key: "announce:#{@object['id']}" }
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,8 +45,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
  def create_status
 | 
					  def create_status
 | 
				
			||||||
    return reject_payload! if unsupported_object_type? || invalid_origin?(object_uri) || tombstone_exists? || !related_to_local_activity?
 | 
					    return reject_payload! if unsupported_object_type? || invalid_origin?(object_uri) || tombstone_exists? || !related_to_local_activity?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RedisLock.acquire(lock_options) do |lock|
 | 
					    lock_or_fail("create:#{object_uri}") do
 | 
				
			||||||
      if lock.acquired?
 | 
					 | 
				
			||||||
      return if delete_arrived_first?(object_uri) || poll_vote? # rubocop:disable Lint/NonLocalExitFromIterator
 | 
					      return if delete_arrived_first?(object_uri) || poll_vote? # rubocop:disable Lint/NonLocalExitFromIterator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      @status = find_existing_status
 | 
					      @status = find_existing_status
 | 
				
			||||||
| 
						 | 
					@ -56,9 +55,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
      elsif @options[:delivered_to_account_id].present?
 | 
					      elsif @options[:delivered_to_account_id].present?
 | 
				
			||||||
        postprocess_audience_and_deliver
 | 
					        postprocess_audience_and_deliver
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        raise Mastodon::RaceConditionError
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @status
 | 
					    @status
 | 
				
			||||||
| 
						 | 
					@ -313,13 +309,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
    poll = replied_to_status.preloadable_poll
 | 
					    poll = replied_to_status.preloadable_poll
 | 
				
			||||||
    already_voted = true
 | 
					    already_voted = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RedisLock.acquire(poll_lock_options) do |lock|
 | 
					    lock_or_fail("vote:#{replied_to_status.poll_id}:#{@account.id}") do
 | 
				
			||||||
      if lock.acquired?
 | 
					 | 
				
			||||||
      already_voted = poll.votes.where(account: @account).exists?
 | 
					      already_voted = poll.votes.where(account: @account).exists?
 | 
				
			||||||
      poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri)
 | 
					      poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri)
 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        raise Mastodon::RaceConditionError
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    increment_voters_count! unless already_voted
 | 
					    increment_voters_count! unless already_voted
 | 
				
			||||||
| 
						 | 
					@ -514,12 +506,4 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
    poll.reload
 | 
					    poll.reload
 | 
				
			||||||
    retry
 | 
					    retry
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def lock_options
 | 
					 | 
				
			||||||
    { redis: Redis.current, key: "create:#{object_uri}" }
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def poll_lock_options
 | 
					 | 
				
			||||||
    { redis: Redis.current, key: "vote:#{replied_to_status.poll_id}:#{@account.id}" }
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,8 +20,15 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 | 
				
			||||||
  def delete_note
 | 
					  def delete_note
 | 
				
			||||||
    return if object_uri.nil?
 | 
					    return if object_uri.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lock_or_return("delete_status_in_progress:#{object_uri}", 5.minutes.seconds) do
 | 
				
			||||||
      unless invalid_origin?(object_uri)
 | 
					      unless invalid_origin?(object_uri)
 | 
				
			||||||
      RedisLock.acquire(lock_options) { |_lock| delete_later!(object_uri) }
 | 
					        # This lock ensures a concurrent `ActivityPub::Activity::Create` either
 | 
				
			||||||
 | 
					        # does not create a status at all, or has finished saving it to the
 | 
				
			||||||
 | 
					        # database before we try to load it.
 | 
				
			||||||
 | 
					        # Without the lock, `delete_later!` could be called after `delete_arrived_first?`
 | 
				
			||||||
 | 
					        # and `Status.find` before `Status.create!`
 | 
				
			||||||
 | 
					        lock_or_fail("create:#{object_uri}") { delete_later!(object_uri) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Tombstone.find_or_create_by(uri: object_uri, account: @account)
 | 
					        Tombstone.find_or_create_by(uri: object_uri, account: @account)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,23 +37,18 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return if @status.nil?
 | 
					      return if @status.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if @status.distributable?
 | 
					      forward! if @json['signature'].present? && @status.distributable?
 | 
				
			||||||
      forward_for_reply
 | 
					 | 
				
			||||||
      forward_for_reblogs
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      delete_now!
 | 
					      delete_now!
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def forward_for_reblogs
 | 
					 | 
				
			||||||
    return if @json['signature'].blank?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    rebloggers_ids = @status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)
 | 
					 | 
				
			||||||
    inboxes        = Account.where(id: ::Follow.where(target_account_id: rebloggers_ids).select(:account_id)).inboxes - [@account.preferred_inbox_url]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ActivityPub::LowPriorityDeliveryWorker.push_bulk(inboxes) do |inbox_url|
 | 
					 | 
				
			||||||
      [payload, rebloggers_ids.first, inbox_url]
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rebloggers_ids
 | 
				
			||||||
 | 
					    return @rebloggers_ids if defined?(@rebloggers_ids)
 | 
				
			||||||
 | 
					    @rebloggers_ids = @status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def inboxes_for_reblogs
 | 
				
			||||||
 | 
					    Account.where(id: ::Follow.where(target_account_id: rebloggers_ids).select(:account_id)).inboxes
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def replied_to_status
 | 
					  def replied_to_status
 | 
				
			||||||
| 
						 | 
					@ -58,13 +60,19 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 | 
				
			||||||
    !replied_to_status.nil? && replied_to_status.account.local?
 | 
					    !replied_to_status.nil? && replied_to_status.account.local?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def forward_for_reply
 | 
					  def inboxes_for_reply
 | 
				
			||||||
    return unless @json['signature'].present? && reply_to_local?
 | 
					    replied_to_status.account.followers.inboxes
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inboxes = replied_to_status.account.followers.inboxes - [@account.preferred_inbox_url]
 | 
					  def forward!
 | 
				
			||||||
 | 
					    inboxes = inboxes_for_reblogs
 | 
				
			||||||
 | 
					    inboxes += inboxes_for_reply if reply_to_local?
 | 
				
			||||||
 | 
					    inboxes -= [@account.preferred_inbox_url]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActivityPub::LowPriorityDeliveryWorker.push_bulk(inboxes) do |inbox_url|
 | 
					    sender_id = reply_to_local? ? replied_to_status.account_id : rebloggers_ids.first
 | 
				
			||||||
      [payload, replied_to_status.account_id, inbox_url]
 | 
					
 | 
				
			||||||
 | 
					    ActivityPub::LowPriorityDeliveryWorker.push_bulk(inboxes.uniq) do |inbox_url|
 | 
				
			||||||
 | 
					      [payload, sender_id, inbox_url]
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,8 +83,4 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 | 
				
			||||||
  def payload
 | 
					  def payload
 | 
				
			||||||
    @payload ||= Oj.dump(@json)
 | 
					    @payload ||= Oj.dump(@json)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def lock_options
 | 
					 | 
				
			||||||
    { redis: Redis.current, key: "create:#{object_uri}" }
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,8 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
    @account  = status.account
 | 
					    @account  = status.account
 | 
				
			||||||
    @options  = options
 | 
					    @options  = options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @status.discard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RedisLock.acquire(lock_options) do |lock|
 | 
					    RedisLock.acquire(lock_options) do |lock|
 | 
				
			||||||
      if lock.acquired?
 | 
					      if lock.acquired?
 | 
				
			||||||
        remove_from_self if @account.local?
 | 
					        remove_from_self if @account.local?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,10 +69,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - unless whitelist_mode?
 | 
					  - unless whitelist_mode?
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
 | 
					      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html'), recommended: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
 | 
					      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html'), recommended: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
 | 
					      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -562,7 +562,7 @@ en:
 | 
				
			||||||
    settings:
 | 
					    settings:
 | 
				
			||||||
      activity_api_enabled:
 | 
					      activity_api_enabled:
 | 
				
			||||||
        desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets
 | 
					        desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets
 | 
				
			||||||
        title: Publish aggregate statistics about user activity
 | 
					        title: Publish aggregate statistics about user activity in the API
 | 
				
			||||||
      bootstrap_timeline_accounts:
 | 
					      bootstrap_timeline_accounts:
 | 
				
			||||||
        desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins.
 | 
					        desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins.
 | 
				
			||||||
        title: Default follows for new users
 | 
					        title: Default follows for new users
 | 
				
			||||||
| 
						 | 
					@ -593,7 +593,7 @@ en:
 | 
				
			||||||
        title: Mascot image
 | 
					        title: Mascot image
 | 
				
			||||||
      peers_api_enabled:
 | 
					      peers_api_enabled:
 | 
				
			||||||
        desc_html: Domain names this server has encountered in the fediverse
 | 
					        desc_html: Domain names this server has encountered in the fediverse
 | 
				
			||||||
        title: Publish list of discovered servers
 | 
					        title: Publish list of discovered servers in the API
 | 
				
			||||||
      preview_sensitive_media:
 | 
					      preview_sensitive_media:
 | 
				
			||||||
        desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
 | 
					        desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
 | 
				
			||||||
        title: Show sensitive media in OpenGraph previews
 | 
					        title: Show sensitive media in OpenGraph previews
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,4 +49,24 @@ RSpec.describe ActivityPub::Activity::Delete do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  context 'when the status has been reported' do
 | 
				
			||||||
 | 
					    describe '#perform' do
 | 
				
			||||||
 | 
					      subject { described_class.new(json, sender) }
 | 
				
			||||||
 | 
					      let!(:reporter) { Fabricate(:account) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        reporter.reports.create!(target_account: status.account, status_ids: [status.id], forwarded: false)
 | 
				
			||||||
 | 
					        subject.perform
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'marks the status as deleted' do
 | 
				
			||||||
 | 
					        expect(Status.find_by(id: status.id)).to be_nil
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'actually keeps a copy for inspection' do
 | 
				
			||||||
 | 
					        expect(Status.with_discarded.find_by(id: status.id)).to_not be_nil
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue