ResolveAccountWorker.perform_async(signed_request_account.acct)
end
- DeliveryFailureTracker.track_inverse_success!(signed_request_account)
+ DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
end
def process_payload
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
- @available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
+ @available = DeliveryFailureTracker.available?(params[:id])
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@private_comment = @domain_block&.private_comment
@public_comment = @domain_block&.public_comment
class DeliveryFailureTracker
FAILURE_DAYS_THRESHOLD = 7
- def initialize(inbox_url)
- @inbox_url = inbox_url
+ def initialize(url_or_host)
+ @host = url_or_host.start_with?('https://') || url_or_host.start_with?('http://') ? Addressable::URI.parse(url_or_host).normalized_host : url_or_host
end
def track_failure!
Redis.current.sadd(exhausted_deliveries_key, today)
- Redis.current.sadd('unavailable_inboxes', @inbox_url) if reached_failure_threshold?
+ UnavailableDomain.create(domain: @host) if reached_failure_threshold?
end
def track_success!
Redis.current.del(exhausted_deliveries_key)
- Redis.current.srem('unavailable_inboxes', @inbox_url)
+ UnavailableDomain.find_by(domain: @host)&.destroy
end
def days
Redis.current.scard(exhausted_deliveries_key) || 0
end
+ def available?
+ !UnavailableDomain.where(domain: @host).exists?
+ end
+
+ alias reset! track_success!
+
class << self
- def filter(arr)
- arr.reject(&method(:unavailable?))
- end
+ def without_unavailable(urls)
+ unavailable_domains_map = Rails.cache.fetch('unavailable_domains') { UnavailableDomain.pluck(:domain).each_with_object({}) { |domain, hash| hash[domain] = true } }
- def unavailable?(url)
- Redis.current.sismember('unavailable_inboxes', url)
+ urls.reject do |url|
+ host = Addressable::URI.parse(url).normalized_host
+ unavailable_domains_map[host]
+ end
end
def available?(url)
- !unavailable?(url)
+ new(url).available?
end
- def track_inverse_success!(from_account)
- new(from_account.inbox_url).track_success! if from_account.inbox_url.present?
- new(from_account.shared_inbox_url).track_success! if from_account.shared_inbox_url.present?
+ def reset!(url)
+ new(url).reset!
end
end
private
def exhausted_deliveries_key
- "exhausted_deliveries:#{@inbox_url}"
+ "exhausted_deliveries:#{@host}"
end
def today
def inboxes
urls = reorder(nil).where(protocol: :activitypub).pluck(Arel.sql("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)"))
- DeliveryFailureTracker.filter(urls)
+ DeliveryFailureTracker.without_unavailable(urls)
end
def search_for(terms, limit = 10, offset = 0)
# created_at :datetime not null
# updated_at :datetime not null
# published_at :datetime
-# status_ids :bigint is an Array
+# status_ids :bigint(8) is an Array
#
class Announcement < ApplicationRecord
payload = Oj.dump(follow_activity(activity_id))
update!(state: :pending, follow_activity_id: activity_id)
- DeliveryFailureTracker.new(inbox_url).track_success!
+ DeliveryFailureTracker.track_success!(inbox_url)
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
end
payload = Oj.dump(unfollow_activity(activity_id))
update!(state: :idle, follow_activity_id: nil)
- DeliveryFailureTracker.new(inbox_url).track_success!
+ DeliveryFailureTracker.track_success!(inbox_url)
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
end
--- /dev/null
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: unavailable_domains
+#
+# id :bigint(8) not null, primary key
+# domain :string default(""), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class UnavailableDomain < ApplicationRecord
+ include DomainNormalizable
+
+ after_commit :reset_cache!
+
+ private
+
+ def reset_cache!
+ Rails.cache.delete('unavailable_domains')
+ end
+end
%th= t('admin.accounts.inbox_url')
%td
= @account.inbox_url
- = fa_icon DeliveryFailureTracker.unavailable?(@account.inbox_url) ? 'times' : 'check'
+ = fa_icon DeliveryFailureTracker.available?(@account.inbox_url) ? 'check' : 'times'
%tr
%th= t('admin.accounts.shared_inbox_url')
%td
= @account.shared_inbox_url
- = fa_icon DeliveryFailureTracker.unavailable?(@account.shared_inbox_url) ? 'times' : 'check'
+ = fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times'
%div{ style: 'overflow: hidden' }
%div{ style: 'float: right' }
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
def perform(json, source_account_id, inbox_url, options = {})
- return if DeliveryFailureTracker.unavailable?(inbox_url)
+ return unless DeliveryFailureTracker.available?(inbox_url)
@options = options.with_indifferent_access
@json = json
--- /dev/null
+class CreateUnavailableDomains < ActiveRecord::Migration[5.2]
+ def change
+ create_table :unavailable_domains do |t|
+ t.string :domain, default: '', null: false, index: { unique: true }
+
+ t.timestamps
+ end
+ end
+end
--- /dev/null
+class MigrateUnavailableInboxes < ActiveRecord::Migration[5.2]
+ disable_ddl_transaction!
+
+ def up
+ urls = Redis.current.smembers('unavailable_inboxes')
+
+ urls.each do |url|
+ host = Addressable::URI.parse(url).normalized_host
+ UnavailableDomain.create(domain: host)
+ end
+
+ Redis.current.del(*(['unavailable_inboxes'] + Redis.current.keys('exhausted_deliveries:*')))
+ end
+
+ def down; end
+end
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_03_12_185443) do
+ActiveRecord::Schema.define(version: 2020_04_07_202420) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
t.index ["uri"], name: "index_tombstones_on_uri"
end
+ create_table "unavailable_domains", force: :cascade do |t|
+ t.string "domain", default: "", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["domain"], name: "index_unavailable_domains_on_domain", unique: true
+ end
+
create_table "user_invite_requests", force: :cascade do |t|
t.bigint "user_id"
t.text "text"
--- /dev/null
+Fabricator(:unavailable_domain) do
+ domain { Faker::Internet.domain }
+end
describe '#track_failure!' do
it 'marks URL as unavailable after 7 days of being called' do
- 6.times { |i| Redis.current.sadd('exhausted_deliveries:http://example.com/inbox', i) }
+ 6.times { |i| Redis.current.sadd('exhausted_deliveries:example.com', i) }
subject.track_failure!
expect(subject.days).to eq 7
- expect(described_class.unavailable?('http://example.com/inbox')).to be true
+ expect(described_class.available?('http://example.com/inbox')).to be false
end
it 'repeated calls on the same day do not count' do
end
end
- describe '.filter' do
+ describe '.without_unavailable' do
before do
- Redis.current.sadd('unavailable_inboxes', 'http://example.com/unavailable/inbox')
+ Fabricate(:unavailable_domain, domain: 'foo.bar')
end
it 'removes URLs that are unavailable' do
- result = described_class.filter(['http://example.com/good/inbox', 'http://example.com/unavailable/inbox'])
+ results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox'])
- expect(result).to include('http://example.com/good/inbox')
- expect(result).to_not include('http://example.com/unavailable/inbox')
+ expect(results).to include('http://example.com/good/inbox')
+ expect(results).to_not include('http://foo.bar/unavailable/inbox')
end
end
- describe '.track_inverse_success!' do
- let(:from_account) { Fabricate(:account, inbox_url: 'http://example.com/inbox', shared_inbox_url: 'http://example.com/shared/inbox') }
-
+ describe '.reset!' do
before do
- Redis.current.sadd('unavailable_inboxes', 'http://example.com/inbox')
- Redis.current.sadd('unavailable_inboxes', 'http://example.com/shared/inbox')
-
- described_class.track_inverse_success!(from_account)
+ Fabricate(:unavailable_domain, domain: 'foo.bar')
+ described_class.reset!('https://foo.bar/inbox')
end
it 'marks inbox URL as available again' do
- expect(described_class.available?('http://example.com/inbox')).to be true
- end
-
- it 'marks shared inbox URL as available again' do
- expect(described_class.available?('http://example.com/shared/inbox')).to be true
+ expect(described_class.available?('http://foo.bar/inbox')).to be true
end
end
end
--- /dev/null
+require 'rails_helper'
+
+RSpec.describe UnavailableDomain, type: :model do
+end