diff --git a/app/services/activitypub/fetch_remote_featured_collection_service.rb b/app/services/activitypub/fetch_remote_featured_collection_service.rb new file mode 100644 index 0000000000..e9858fe34d --- /dev/null +++ b/app/services/activitypub/fetch_remote_featured_collection_service.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService + include JsonLdHelper + + def call(uri, on_behalf_of = nil) + json = fetch_resource(uri, true, on_behalf_of) + + return unless supported_context?(json) + return unless json['type'] == 'FeaturedCollection' + + # Fetching an unknown account should eventually also fetch its + # collections, so it should be OK to only handle known accounts here + account = Account.find_by(uri: json['attributedTo']) + return unless account + + existing_collection = account.collections.find_by(uri:) + return existing_collection if existing_collection.present? + + ActivityPub::ProcessFeaturedCollectionService.new.call(account, json) + end +end diff --git a/app/services/activitypub/process_featured_collection_service.rb b/app/services/activitypub/process_featured_collection_service.rb index edbb50c533..73db0d6699 100644 --- a/app/services/activitypub/process_featured_collection_service.rb +++ b/app/services/activitypub/process_featured_collection_service.rb @@ -27,7 +27,7 @@ class ActivityPub::ProcessFeaturedCollectionService tag_name: @json.dig('topic', 'name') ) - process_items! + process_items! if @json['totalItems'].positive? @collection end diff --git a/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb new file mode 100644 index 0000000000..f5cb9194b2 --- /dev/null +++ b/spec/services/activitypub/fetch_remote_featured_collection_service_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::FetchRemoteFeaturedCollectionService do + subject { described_class.new } + + let(:account) { Fabricate(:remote_account) } + let(:uri) { 'https://example.com/featured_collections/1' } + let(:status) { 200 } + let(:response) do + { + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => uri, + 'type' => 'FeaturedCollection', + 'name' => 'Incredible people', + 'summary' => 'These are really amazing', + 'attributedTo' => account.uri, + 'sensitive' => false, + 'discoverable' => true, + 'totalItems' => 0, + } + end + + before do + stub_request(:get, uri) + .to_return_json( + status: status, + body: response, + headers: { 'Content-Type' => 'application/activity+json' } + ) + end + + context 'when collection does not exist' do + it 'creates a new collection' do + collection = nil + expect { collection = subject.call(uri) }.to change(Collection, :count).by(1) + + expect(collection.uri).to eq uri + expect(collection.name).to eq 'Incredible people' + end + end + + context 'when collection already exists' do + let!(:collection) do + Fabricate(:remote_collection, account:, uri:, name: 'temp') + end + + it 'returns the existing collection' do + expect do + expect(subject.call(uri)).to eq collection + end.to_not change(Collection, :count) + end + end + + context 'when the URI can not be fetched' do + let(:response) { nil } + let(:status) { 404 } + + it 'returns `nil`' do + expect(subject.call(uri)).to be_nil + end + end +end