diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb index 7107890af1..32a5f71293 100644 --- a/app/controllers/api/v1/statuses/pins_controller.rb +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -26,7 +26,7 @@ class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController def distribute_add_activity! json = ActiveModelSerializers::SerializableResource.new( @status, - serializer: ActivityPub::AddSerializer, + serializer: ActivityPub::AddNoteSerializer, adapter: ActivityPub::Adapter ).as_json @@ -36,7 +36,7 @@ class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController def distribute_remove_activity! json = ActiveModelSerializers::SerializableResource.new( @status, - serializer: ActivityPub::RemoveSerializer, + serializer: ActivityPub::RemoveNoteSerializer, adapter: ActivityPub::Adapter ).as_json diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 65db807d18..be3641589f 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -37,7 +37,7 @@ class StatusesController < ApplicationController def activity expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? - render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status), content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter + render_with_cache json: @status, content_type: 'application/activity+json', serializer: activity_serializer, adapter: ActivityPub::Adapter end def embed @@ -69,4 +69,8 @@ class StatusesController < ApplicationController def redirect_to_original redirect_to(ActivityPub::TagManager.instance.url_for(@status.reblog), allow_other_host: true) if @status.reblog? end + + def activity_serializer + @status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer + end end diff --git a/app/presenters/activitypub/activity_presenter.rb b/app/presenters/activitypub/activity_presenter.rb deleted file mode 100644 index 994cbeaf48..0000000000 --- a/app/presenters/activitypub/activity_presenter.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model - attributes :id, :type, :actor, :published, :to, :cc, :virtual_object - - class << self - def from_status(status, allow_inlining: true) - new.tap do |presenter| - presenter.id = ActivityPub::TagManager.instance.activity_uri_for(status) - presenter.type = status.reblog? ? 'Announce' : 'Create' - presenter.actor = ActivityPub::TagManager.instance.uri_for(status.account) - presenter.published = status.created_at - presenter.to = ActivityPub::TagManager.instance.to(status) - presenter.cc = ActivityPub::TagManager.instance.cc(status) - - presenter.virtual_object = begin - if status.reblog? - if allow_inlining && status.account == status.proper.account && status.proper.private_visibility? && status.local? - status.proper - else - ActivityPub::TagManager.instance.uri_for(status.proper) - end - else - status.proper - end - end - end - end - end -end diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb deleted file mode 100644 index 23a5b42bc7..0000000000 --- a/app/serializers/activitypub/activity_serializer.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::ActivitySerializer < ActivityPub::Serializer - def self.serializer_for(model, options) - case model.class.name - when 'Status' - ActivityPub::NoteSerializer - else - super - end - end - - attributes :id, :type, :actor, :published, :to, :cc - - has_one :virtual_object, key: :object - - def published - object.published.iso8601 - end -end diff --git a/app/serializers/activitypub/add_featured_collection_serializer.rb b/app/serializers/activitypub/add_featured_collection_serializer.rb new file mode 100644 index 0000000000..022c44a1bc --- /dev/null +++ b/app/serializers/activitypub/add_featured_collection_serializer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class ActivityPub::AddFeaturedCollectionSerializer < ActivityPub::Serializer + include RoutingHelper + + attributes :type, :actor, :target + has_one :object, serializer: ActivityPub::FeaturedCollectionSerializer + + def type + 'Add' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def target + ap_account_featured_collections_url(object.account_id) + end +end diff --git a/app/serializers/activitypub/add_hashtag_serializer.rb b/app/serializers/activitypub/add_hashtag_serializer.rb new file mode 100644 index 0000000000..3522cbcc2e --- /dev/null +++ b/app/serializers/activitypub/add_hashtag_serializer.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class ActivityPub::AddHashtagSerializer < ActivityPub::Serializer + attributes :type, :actor, :target + + has_one :object, serializer: ActivityPub::HashtagSerializer + + def type + 'Add' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def target + # Technically this is not correct, as tags have their own collection. + # But sadly we do not store the collection URI for tags anywhere so cannot + # handle `Add` activities to that properly (yet). The receiving code for + # this currently looks at the type of the contained objects to do the + # right thing. + ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) + end +end diff --git a/app/serializers/activitypub/add_note_serializer.rb b/app/serializers/activitypub/add_note_serializer.rb new file mode 100644 index 0000000000..45e9a8708a --- /dev/null +++ b/app/serializers/activitypub/add_note_serializer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class ActivityPub::AddNoteSerializer < ActivityPub::Serializer + attributes :type, :actor, :target + + has_one :proper_object, key: :object + + def type + 'Add' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def proper_object + ActivityPub::TagManager.instance.uri_for(object) + end + + def target + ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) + end +end diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb deleted file mode 100644 index a2a1256ec5..0000000000 --- a/app/serializers/activitypub/add_serializer.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::AddSerializer < ActivityPub::Serializer - class UriSerializer < ActiveModel::Serializer - include RoutingHelper - - def serializable_hash(*_args) - ActivityPub::TagManager.instance.uri_for(object) - end - end - - def self.serializer_for(model, options) - case model - when Status - UriSerializer - when FeaturedTag - ActivityPub::HashtagSerializer - when Collection - ActivityPub::FeaturedCollectionSerializer - else - super - end - end - - include RoutingHelper - - attributes :type, :actor, :target - has_one :proper_object, key: :object - - def type - 'Add' - end - - def actor - ActivityPub::TagManager.instance.uri_for(object.account) - end - - def proper_object - object - end - - def target - case object - when Status, FeaturedTag - # Technically this is not correct, as tags have their own collection. - # But sadly we do not store the collection URI for tags anywhere so cannot - # handle `Add` activities to that properly (yet). The receiving code for - # this currently looks at the type of the contained objects to do the - # right thing. - ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) - when Collection - ap_account_featured_collections_url(object.account_id) - end - end -end diff --git a/app/serializers/activitypub/announce_note_serializer.rb b/app/serializers/activitypub/announce_note_serializer.rb new file mode 100644 index 0000000000..45dc0b4739 --- /dev/null +++ b/app/serializers/activitypub/announce_note_serializer.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class ActivityPub::AnnounceNoteSerializer < ActivityPub::Serializer + def self.serializer_for(model, options) + return ActivityPub::NoteSerializer if model.is_a?(Status) + + super + end + + attributes :id, :type, :actor, :published, :to, :cc + + has_one :virtual_object, key: :object + + def id + ActivityPub::TagManager.instance.activity_uri_for(object) + end + + def type + 'Announce' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def to + ActivityPub::TagManager.instance.to(object) + end + + def cc + ActivityPub::TagManager.instance.cc(object) + end + + def published + object.created_at.iso8601 + end + + def virtual_object + if allow_inlining? && object.account == object.proper.account && object.proper.private_visibility? && object.local? + object.proper + else + ActivityPub::TagManager.instance.uri_for(object.proper) + end + end + + private + + def allow_inlining? + return instance_options[:allow_inlining] if instance_options.key?(:allow_inlining) + + true + end +end diff --git a/app/serializers/activitypub/create_note_serializer.rb b/app/serializers/activitypub/create_note_serializer.rb new file mode 100644 index 0000000000..ce753657cb --- /dev/null +++ b/app/serializers/activitypub/create_note_serializer.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class ActivityPub::CreateNoteSerializer < ActivityPub::Serializer + attributes :id, :type, :actor, :published, :to, :cc + + has_one :object, serializer: ActivityPub::NoteSerializer + + def id + ActivityPub::TagManager.instance.activity_uri_for(object) + end + + def type + 'Create' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def to + ActivityPub::TagManager.instance.to(object) + end + + def cc + ActivityPub::TagManager.instance.cc(object) + end + + def published + object.created_at.iso8601 + end +end diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_note_serializer.rb similarity index 91% rename from app/serializers/activitypub/delete_serializer.rb rename to app/serializers/activitypub/delete_note_serializer.rb index a7d5bd4698..41a218dc3f 100644 --- a/app/serializers/activitypub/delete_serializer.rb +++ b/app/serializers/activitypub/delete_note_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::DeleteSerializer < ActivityPub::Serializer +class ActivityPub::DeleteNoteSerializer < ActivityPub::Serializer class TombstoneSerializer < ActivityPub::Serializer context_extensions :atom_uri diff --git a/app/serializers/activitypub/outbox_serializer.rb b/app/serializers/activitypub/outbox_serializer.rb index 4d3d9706de..942c1bbc4e 100644 --- a/app/serializers/activitypub/outbox_serializer.rb +++ b/app/serializers/activitypub/outbox_serializer.rb @@ -2,14 +2,15 @@ class ActivityPub::OutboxSerializer < ActivityPub::CollectionSerializer def self.serializer_for(model, options) - if model.instance_of?(::ActivityPub::ActivityPresenter) - ActivityPub::ActivitySerializer + case model + when Status + model.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer else super end end def items - object.items.map { |status| ActivityPub::ActivityPresenter.from_status(status) } + object.items end end diff --git a/app/serializers/activitypub/remove_hashtag_serializer.rb b/app/serializers/activitypub/remove_hashtag_serializer.rb new file mode 100644 index 0000000000..ba7ec00383 --- /dev/null +++ b/app/serializers/activitypub/remove_hashtag_serializer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ActivityPub::RemoveHashtagSerializer < ActivityPub::Serializer + attributes :type, :actor, :target + has_one :object, serializer: ActivityPub::HashtagSerializer + + def type + 'Remove' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def target + ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) + end +end diff --git a/app/serializers/activitypub/remove_note_serializer.rb b/app/serializers/activitypub/remove_note_serializer.rb new file mode 100644 index 0000000000..92a73f05c7 --- /dev/null +++ b/app/serializers/activitypub/remove_note_serializer.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub::RemoveNoteSerializer < ActivityPub::Serializer + attributes :type, :actor, :target + has_one :proper_object, key: :object + + def type + 'Remove' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def proper_object + ActivityPub::TagManager.instance.uri_for(object) + end + + def target + ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) + end +end diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb deleted file mode 100644 index 4f78804031..0000000000 --- a/app/serializers/activitypub/remove_serializer.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class ActivityPub::RemoveSerializer < ActivityPub::Serializer - class UriSerializer < ActiveModel::Serializer - include RoutingHelper - - def serializable_hash(*_args) - ActivityPub::TagManager.instance.uri_for(object) - end - end - - def self.serializer_for(model, options) - case model.class.name - when 'Status' - UriSerializer - when 'FeaturedTag' - ActivityPub::HashtagSerializer - else - super - end - end - - include RoutingHelper - - attributes :type, :actor, :target - has_one :proper_object, key: :object - - def type - 'Remove' - end - - def actor - ActivityPub::TagManager.instance.uri_for(object.account) - end - - def proper_object - object - end - - def target - ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) - end -end diff --git a/app/serializers/activitypub/undo_announce_serializer.rb b/app/serializers/activitypub/undo_announce_serializer.rb index a0f09e8ab9..5e6366073d 100644 --- a/app/serializers/activitypub/undo_announce_serializer.rb +++ b/app/serializers/activitypub/undo_announce_serializer.rb @@ -3,7 +3,11 @@ class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to - has_one :virtual_object, key: :object, serializer: ActivityPub::ActivitySerializer + has_one :virtual_object, key: :object, serializer: ActivityPub::AnnounceNoteSerializer do |serializer| + serializer.send(:instance_options)[:allow_inlining] = false + + object + end def id [ActivityPub::TagManager.instance.uri_for(object.account), '#announces/', object.id, '/undo'].join @@ -22,6 +26,6 @@ class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer end def virtual_object - ActivityPub::ActivityPresenter.from_status(object, allow_inlining: false) + object end end diff --git a/app/serializers/activitypub/update_serializer.rb b/app/serializers/activitypub/update_actor_serializer.rb similarity index 86% rename from app/serializers/activitypub/update_serializer.rb rename to app/serializers/activitypub/update_actor_serializer.rb index a5eb857d3f..a10a632eee 100644 --- a/app/serializers/activitypub/update_serializer.rb +++ b/app/serializers/activitypub/update_actor_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UpdateSerializer < ActivityPub::Serializer +class ActivityPub::UpdateActorSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to has_one :object, serializer: ActivityPub::ActorSerializer diff --git a/app/serializers/activitypub/update_note_serializer.rb b/app/serializers/activitypub/update_note_serializer.rb new file mode 100644 index 0000000000..8dac72250b --- /dev/null +++ b/app/serializers/activitypub/update_note_serializer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class ActivityPub::UpdateNoteSerializer < ActivityPub::Serializer + attributes :id, :type, :actor, :published, :to, :cc + + has_one :object, serializer: ActivityPub::NoteSerializer + + def id + [ActivityPub::TagManager.instance.uri_for(object), '#updates/', edited_at.to_i].join + end + + def type + 'Update' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def to + ActivityPub::TagManager.instance.to(object) + end + + def cc + ActivityPub::TagManager.instance.cc(object) + end + + def published + edited_at.iso8601 + end + + private + + def edited_at + instance_options[:updated_at]&.to_datetime || object.edited_at + end +end diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 42aa4c93cf..299b5df234 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -34,7 +34,8 @@ class BackupService < BaseService add_comma = true file.write(statuses.map do |status| - item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status), ActivityPub::ActivitySerializer) + serializer = status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer + item = serialize_payload(status, serializer) item.delete(:@context) unless item[:type] == 'Announce' || item[:object][:attachment].blank? diff --git a/app/services/create_collection_service.rb b/app/services/create_collection_service.rb index 008d4b5c09..7e8935e1a4 100644 --- a/app/services/create_collection_service.rb +++ b/app/services/create_collection_service.rb @@ -32,6 +32,6 @@ class CreateCollectionService end def activity_json - ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::AddSerializer, adapter: ActivityPub::Adapter).to_json + ActiveModelSerializers::SerializableResource.new(@collection, serializer: ActivityPub::AddFeaturedCollectionSerializer, adapter: ActivityPub::Adapter).to_json end end diff --git a/app/services/create_featured_tag_service.rb b/app/services/create_featured_tag_service.rb index 13d6ac0201..298bdb5928 100644 --- a/app/services/create_featured_tag_service.rb +++ b/app/services/create_featured_tag_service.rb @@ -26,6 +26,6 @@ class CreateFeaturedTagService < BaseService private def build_json(featured_tag) - Oj.dump(serialize_payload(featured_tag, ActivityPub::AddSerializer, signer: @account)) + Oj.dump(serialize_payload(featured_tag, ActivityPub::AddHashtagSerializer, signer: @account)) end end diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index cca79eced6..4c292d4b16 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -49,8 +49,4 @@ class ReblogService < BaseService def increment_statistics ActivityTracker.increment('activity:interactions') end - - def build_json(reblog) - Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_status(reblog), ActivityPub::ActivitySerializer, signer: reblog.account)) - end end diff --git a/app/services/remove_featured_tag_service.rb b/app/services/remove_featured_tag_service.rb index af8c5a64ee..4fdd43eb6a 100644 --- a/app/services/remove_featured_tag_service.rb +++ b/app/services/remove_featured_tag_service.rb @@ -26,6 +26,6 @@ class RemoveFeaturedTagService < BaseService private def build_json(featured_tag) - Oj.dump(serialize_payload(featured_tag, ActivityPub::RemoveSerializer, signer: @account)) + Oj.dump(serialize_payload(featured_tag, ActivityPub::RemoveHashtagSerializer, signer: @account)) end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index f61cb632b2..caa22b4729 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -103,7 +103,7 @@ class RemoveStatusService < BaseService end def signed_activity_json - @signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account, always_sign: true)) + @signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteNoteSerializer, signer: @account, always_sign: true)) end def remove_reblogs diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 7ddf1a553d..666b64cacf 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -72,6 +72,6 @@ class SuspendAccountService < BaseService end def signed_activity_json - @signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account)) + @signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateActorSerializer, signer: @account)) end end diff --git a/app/services/unsuspend_account_service.rb b/app/services/unsuspend_account_service.rb index 180b2cd3f6..1a52e80d24 100644 --- a/app/services/unsuspend_account_service.rb +++ b/app/services/unsuspend_account_service.rb @@ -63,6 +63,6 @@ class UnsuspendAccountService < BaseService end def signed_activity_json - @signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account)) + @signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateActorSerializer, signer: @account)) end end diff --git a/app/workers/activitypub/distribution_worker.rb b/app/workers/activitypub/distribution_worker.rb index 125278fa51..63013bdc69 100644 --- a/app/workers/activitypub/distribution_worker.rb +++ b/app/workers/activitypub/distribution_worker.rb @@ -24,11 +24,15 @@ class ActivityPub::DistributionWorker < ActivityPub::RawDistributionWorker end def payload - @payload ||= Oj.dump(serialize_payload(activity, ActivityPub::ActivitySerializer, signer: @account)) + @payload ||= Oj.dump(serialize_payload(@status, activity_serializer, serializer_options.merge(signer: @account))) end - def activity - ActivityPub::ActivityPresenter.from_status(@status) + def activity_serializer + @status.reblog? ? ActivityPub::AnnounceNoteSerializer : ActivityPub::CreateNoteSerializer + end + + def serializer_options + {} end def options diff --git a/app/workers/activitypub/status_update_distribution_worker.rb b/app/workers/activitypub/status_update_distribution_worker.rb index 7f70fcaecc..4ef06dee5a 100644 --- a/app/workers/activitypub/status_update_distribution_worker.rb +++ b/app/workers/activitypub/status_update_distribution_worker.rb @@ -15,15 +15,11 @@ class ActivityPub::StatusUpdateDistributionWorker < ActivityPub::DistributionWor protected - def activity - ActivityPub::ActivityPresenter.new( - id: [ActivityPub::TagManager.instance.uri_for(@status), '#updates/', @options[:updated_at]&.to_datetime&.to_i || @status.edited_at.to_i].join, - type: 'Update', - actor: ActivityPub::TagManager.instance.uri_for(@status.account), - published: @options[:updated_at]&.to_datetime || @status.edited_at, - to: ActivityPub::TagManager.instance.to(@status), - cc: ActivityPub::TagManager.instance.cc(@status), - virtual_object: @status - ) + def activity_serializer + ActivityPub::UpdateNoteSerializer + end + + def serializer_options + super.merge({ updated_at: @options[:updated_at] }) end end diff --git a/app/workers/activitypub/update_distribution_worker.rb b/app/workers/activitypub/update_distribution_worker.rb index 9a418f0f3d..976f516498 100644 --- a/app/workers/activitypub/update_distribution_worker.rb +++ b/app/workers/activitypub/update_distribution_worker.rb @@ -23,6 +23,6 @@ class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker end def payload - @payload ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account, sign_with: @options[:sign_with])) + @payload ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateActorSerializer, signer: @account, sign_with: @options[:sign_with])) end end diff --git a/spec/serializers/activitypub/activity_serializer_spec.rb b/spec/serializers/activitypub/activity_serializer_spec.rb deleted file mode 100644 index 15e67111e9..0000000000 --- a/spec/serializers/activitypub/activity_serializer_spec.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::ActivitySerializer do - subject { serialized_record_json(presenter, described_class, adapter: ActivityPub::Adapter) } - - let(:tag_manager) { ActivityPub::TagManager.instance } - let(:status) { Fabricate(:status, created_at: Time.utc(2026, 0o1, 27, 15, 29, 31)) } - - context 'with a new status' do - let(:presenter) { ActivityPub::ActivityPresenter.from_status(status) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'id' => tag_manager.activity_uri_for(status), - 'type' => 'Create', - 'actor' => tag_manager.uri_for(status.account), - 'published' => '2026-01-27T15:29:31Z', - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], - 'cc' => [a_string_matching(/followers$/)], - 'object' => a_hash_including( - 'id' => tag_manager.uri_for(status) - ), - }) - - expect(subject).to_not have_key('target') - end - end - - context 'with a new reblog' do - let(:reblog) { Fabricate(:status, reblog: status, created_at: Time.utc(2026, 0o1, 27, 16, 21, 44)) } - let(:presenter) { ActivityPub::ActivityPresenter.from_status(reblog) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'id' => tag_manager.activity_uri_for(reblog), - 'type' => 'Announce', - 'actor' => tag_manager.uri_for(reblog.account), - 'published' => '2026-01-27T16:21:44Z', - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], - 'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)], - 'object' => tag_manager.uri_for(status), - }) - - expect(subject).to_not have_key('target') - end - - context 'when inlining of private local status is allowed' do - let(:status) { Fabricate(:status, visibility: :private, created_at: Time.utc(2026, 0o1, 27, 15, 29, 31)) } - let(:reblog) { Fabricate(:status, reblog: status, account: status.account, created_at: Time.utc(2026, 0o1, 27, 16, 21, 44)) } - let(:presenter) { ActivityPub::ActivityPresenter.from_status(reblog, allow_inlining: true) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'id' => tag_manager.activity_uri_for(reblog), - 'type' => 'Announce', - 'actor' => tag_manager.uri_for(reblog.account), - 'published' => '2026-01-27T16:21:44Z', - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], - 'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)], - 'object' => a_hash_including( - 'id' => tag_manager.uri_for(status) - ), - }) - - expect(subject).to_not have_key('target') - end - end - end - - context 'with a custom presenter for a status `Update`' do - let(:status) { Fabricate(:status, edited_at: Time.utc(2026, 0o1, 27, 15, 29, 31)) } - let(:presenter) do - ActivityPub::ActivityPresenter.new( - id: 'https://localhost/status/1#updates/1769527771', - type: 'Update', - actor: 'https://localhost/actor/1', - published: status.edited_at, - to: ['https://www.w3.org/ns/activitystreams#Public'], - cc: ['https://localhost/actor/1/followers'], - virtual_object: status - ) - end - - it 'serializes to the expected json' do - expect(subject).to include({ - 'id' => 'https://localhost/status/1#updates/1769527771', - 'type' => 'Update', - 'actor' => 'https://localhost/actor/1', - 'published' => '2026-01-27T15:29:31Z', - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], - 'cc' => ['https://localhost/actor/1/followers'], - 'object' => a_hash_including( - 'id' => tag_manager.uri_for(status) - ), - }) - - expect(subject).to_not have_key('target') - end - end -end diff --git a/spec/serializers/activitypub/add_featured_collection_serializer_spec.rb b/spec/serializers/activitypub/add_featured_collection_serializer_spec.rb new file mode 100644 index 0000000000..109aab49fd --- /dev/null +++ b/spec/serializers/activitypub/add_featured_collection_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::AddFeaturedCollectionSerializer do + subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:object) { Fabricate(:collection) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'type' => 'Add', + 'actor' => tag_manager.uri_for(object.account), + 'target' => a_string_matching(%r{/featured_collections$}), + 'object' => a_hash_including({ + 'type' => 'FeaturedCollection', + }), + }) + + expect(subject).to_not have_key('id') + expect(subject).to_not have_key('published') + expect(subject).to_not have_key('to') + expect(subject).to_not have_key('cc') + end +end diff --git a/spec/serializers/activitypub/add_hashtag_serializer_spec.rb b/spec/serializers/activitypub/add_hashtag_serializer_spec.rb new file mode 100644 index 0000000000..6c3c419e47 --- /dev/null +++ b/spec/serializers/activitypub/add_hashtag_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::AddHashtagSerializer do + subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:object) { Fabricate(:featured_tag) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'type' => 'Add', + 'actor' => tag_manager.uri_for(object.account), + 'target' => a_string_matching(%r{/featured$}), + 'object' => a_hash_including({ + 'type' => 'Hashtag', + }), + }) + + expect(subject).to_not have_key('id') + expect(subject).to_not have_key('published') + expect(subject).to_not have_key('to') + expect(subject).to_not have_key('cc') + end +end diff --git a/spec/serializers/activitypub/add_note_serializer_spec.rb b/spec/serializers/activitypub/add_note_serializer_spec.rb new file mode 100644 index 0000000000..85ef189b95 --- /dev/null +++ b/spec/serializers/activitypub/add_note_serializer_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::AddNoteSerializer do + subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:object) { Fabricate(:status) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'type' => 'Add', + 'actor' => tag_manager.uri_for(object.account), + 'target' => a_string_matching(%r{/featured$}), + 'object' => tag_manager.uri_for(object), + }) + + expect(subject).to_not have_key('id') + expect(subject).to_not have_key('published') + expect(subject).to_not have_key('to') + expect(subject).to_not have_key('cc') + end +end diff --git a/spec/serializers/activitypub/add_serializer_spec.rb b/spec/serializers/activitypub/add_serializer_spec.rb deleted file mode 100644 index 6ab8a865ad..0000000000 --- a/spec/serializers/activitypub/add_serializer_spec.rb +++ /dev/null @@ -1,119 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::AddSerializer do - describe '.serializer_for' do - subject { described_class.serializer_for(model, {}) } - - context 'with a Status model' do - let(:model) { Status.new } - - it { is_expected.to eq(described_class::UriSerializer) } - end - - context 'with a FeaturedTag model' do - let(:model) { FeaturedTag.new } - - it { is_expected.to eq(ActivityPub::HashtagSerializer) } - end - - context 'with a Collection model' do - let(:model) { Collection.new } - - it { is_expected.to eq(ActivityPub::FeaturedCollectionSerializer) } - end - - context 'with an Array' do - let(:model) { [] } - - it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) } - end - end - - describe '#target' do - subject { described_class.new(object).target } - - context 'when object is a Status' do - let(:object) { Fabricate(:status) } - - it { is_expected.to match(%r{/#{object.account_id}/collections/featured$}) } - end - - context 'when object is a FeaturedTag' do - let(:object) { Fabricate(:featured_tag) } - - it { is_expected.to match(%r{/#{object.account_id}/collections/featured$}) } - end - - context 'when object is a Collection' do - let(:object) { Fabricate(:collection) } - - it { is_expected.to match(%r{/#{object.account_id}/featured_collections$}) } - end - end - - describe 'Serialization' do - subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } - - let(:tag_manager) { ActivityPub::TagManager.instance } - - context 'with a status' do - let(:object) { Fabricate(:status) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'type' => 'Add', - 'actor' => tag_manager.uri_for(object.account), - 'target' => a_string_matching(%r{/featured$}), - 'object' => tag_manager.uri_for(object), - }) - - expect(subject).to_not have_key('id') - expect(subject).to_not have_key('published') - expect(subject).to_not have_key('to') - expect(subject).to_not have_key('cc') - end - end - - context 'with a featured tag' do - let(:object) { Fabricate(:featured_tag) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'type' => 'Add', - 'actor' => tag_manager.uri_for(object.account), - 'target' => a_string_matching(%r{/featured$}), - 'object' => a_hash_including({ - 'type' => 'Hashtag', - }), - }) - - expect(subject).to_not have_key('id') - expect(subject).to_not have_key('published') - expect(subject).to_not have_key('to') - expect(subject).to_not have_key('cc') - end - end - - context 'with a collection' do - let(:object) { Fabricate(:collection) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'type' => 'Add', - 'actor' => tag_manager.uri_for(object.account), - 'target' => a_string_matching(%r{/featured_collections$}), - 'object' => a_hash_including({ - 'type' => 'FeaturedCollection', - }), - }) - - expect(subject).to_not have_key('id') - expect(subject).to_not have_key('published') - expect(subject).to_not have_key('to') - expect(subject).to_not have_key('cc') - end - end - end -end diff --git a/spec/serializers/activitypub/announce_note_serializer_spec.rb b/spec/serializers/activitypub/announce_note_serializer_spec.rb new file mode 100644 index 0000000000..d62f93e5ec --- /dev/null +++ b/spec/serializers/activitypub/announce_note_serializer_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::AnnounceNoteSerializer do + subject { serialized_record_json(reblog, described_class, adapter: ActivityPub::Adapter, options:) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:status) { Fabricate(:status, created_at: Time.utc(2026, 1, 27, 15, 29, 31)) } + let(:reblog) { Fabricate(:status, reblog: status, created_at: Time.utc(2026, 1, 27, 16, 21, 44)) } + let(:options) { {} } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => tag_manager.activity_uri_for(reblog), + 'type' => 'Announce', + 'actor' => tag_manager.uri_for(reblog.account), + 'published' => '2026-01-27T16:21:44Z', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)], + 'object' => tag_manager.uri_for(status), + }) + + expect(subject).to_not have_key('target') + end + + context 'when status is local and private' do + let(:status) { Fabricate(:status, visibility: :private, created_at: Time.utc(2026, 1, 27, 15, 29, 31)) } + let(:reblog) { Fabricate(:status, reblog: status, account: status.account, created_at: Time.utc(2026, 1, 27, 16, 21, 44)) } + + context 'when inlining of private local status is allowed' do + shared_examples 'serialization with inlining' do + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => tag_manager.activity_uri_for(reblog), + 'type' => 'Announce', + 'actor' => tag_manager.uri_for(reblog.account), + 'published' => '2026-01-27T16:21:44Z', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)], + 'object' => a_hash_including( + 'id' => tag_manager.uri_for(status) + ), + }) + + expect(subject).to_not have_key('target') + end + end + + context 'with `allow_inlining` explicitly set to `true`' do + let(:options) { { allow_inlining: true } } + + it_behaves_like 'serialization with inlining' + end + + context 'with `allow_inlining` unset' do + let(:options) { {} } + + it_behaves_like 'serialization with inlining' + end + end + + context 'when inlining is not allowed' do + let(:options) { { allow_inlining: false } } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => tag_manager.activity_uri_for(reblog), + 'type' => 'Announce', + 'actor' => tag_manager.uri_for(reblog.account), + 'published' => '2026-01-27T16:21:44Z', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'cc' => [tag_manager.uri_for(status.account), a_string_matching(/followers$/)], + 'object' => tag_manager.uri_for(status), + }) + end + end + end +end diff --git a/spec/serializers/activitypub/create_note_serializer_spec.rb b/spec/serializers/activitypub/create_note_serializer_spec.rb new file mode 100644 index 0000000000..7a2b8ba9f7 --- /dev/null +++ b/spec/serializers/activitypub/create_note_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::CreateNoteSerializer do + subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:status) { Fabricate(:status, created_at: Time.utc(2026, 1, 27, 15, 29, 31)) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => tag_manager.activity_uri_for(status), + 'type' => 'Create', + 'actor' => tag_manager.uri_for(status.account), + 'published' => '2026-01-27T15:29:31Z', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'cc' => [a_string_matching(/followers$/)], + 'object' => a_hash_including( + 'id' => tag_manager.uri_for(status) + ), + }) + + expect(subject).to_not have_key('target') + end +end diff --git a/spec/serializers/activitypub/delete_serializer_spec.rb b/spec/serializers/activitypub/delete_note_serializer_spec.rb similarity index 93% rename from spec/serializers/activitypub/delete_serializer_spec.rb rename to spec/serializers/activitypub/delete_note_serializer_spec.rb index ba5a4f9cb4..3165c05ce5 100644 --- a/spec/serializers/activitypub/delete_serializer_spec.rb +++ b/spec/serializers/activitypub/delete_note_serializer_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe ActivityPub::DeleteSerializer do +RSpec.describe ActivityPub::DeleteNoteSerializer do subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) } let(:tag_manager) { ActivityPub::TagManager.instance } diff --git a/spec/serializers/activitypub/remove_hashtag_serializer_spec.rb b/spec/serializers/activitypub/remove_hashtag_serializer_spec.rb new file mode 100644 index 0000000000..cf9ab2c26e --- /dev/null +++ b/spec/serializers/activitypub/remove_hashtag_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::RemoveHashtagSerializer do + subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:object) { Fabricate(:featured_tag) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'type' => 'Remove', + 'actor' => tag_manager.uri_for(object.account), + 'target' => a_string_matching(%r{/featured$}), + 'object' => a_hash_including({ + 'type' => 'Hashtag', + }), + }) + + expect(subject).to_not have_key('id') + expect(subject).to_not have_key('published') + expect(subject).to_not have_key('to') + expect(subject).to_not have_key('cc') + end +end diff --git a/spec/serializers/activitypub/remove_note_serializer_spec.rb b/spec/serializers/activitypub/remove_note_serializer_spec.rb new file mode 100644 index 0000000000..83531de693 --- /dev/null +++ b/spec/serializers/activitypub/remove_note_serializer_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::RemoveNoteSerializer do + subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:object) { Fabricate(:status) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'type' => 'Remove', + 'actor' => tag_manager.uri_for(object.account), + 'target' => a_string_matching(%r{/featured$}), + 'object' => tag_manager.uri_for(object), + }) + + expect(subject).to_not have_key('id') + expect(subject).to_not have_key('published') + expect(subject).to_not have_key('to') + expect(subject).to_not have_key('cc') + end +end diff --git a/spec/serializers/activitypub/remove_serializer_spec.rb b/spec/serializers/activitypub/remove_serializer_spec.rb deleted file mode 100644 index a79977774d..0000000000 --- a/spec/serializers/activitypub/remove_serializer_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActivityPub::RemoveSerializer do - describe '.serializer_for' do - subject { described_class.serializer_for(model, {}) } - - context 'with a Status model' do - let(:model) { Status.new } - - it { is_expected.to eq(described_class::UriSerializer) } - end - - context 'with a FeaturedTag model' do - let(:model) { FeaturedTag.new } - - it { is_expected.to eq(ActivityPub::HashtagSerializer) } - end - - context 'with an Array' do - let(:model) { [] } - - it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) } - end - end - - describe 'Serialization' do - subject { serialized_record_json(object, described_class, adapter: ActivityPub::Adapter) } - - let(:tag_manager) { ActivityPub::TagManager.instance } - - context 'with a status' do - let(:object) { Fabricate(:status) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'type' => 'Remove', - 'actor' => tag_manager.uri_for(object.account), - 'target' => a_string_matching(%r{/featured$}), - 'object' => tag_manager.uri_for(object), - }) - - expect(subject).to_not have_key('id') - expect(subject).to_not have_key('published') - expect(subject).to_not have_key('to') - expect(subject).to_not have_key('cc') - end - end - - context 'with a featured tag' do - let(:object) { Fabricate(:featured_tag) } - - it 'serializes to the expected json' do - expect(subject).to include({ - 'type' => 'Remove', - 'actor' => tag_manager.uri_for(object.account), - 'target' => a_string_matching(%r{/featured$}), - 'object' => a_hash_including({ - 'type' => 'Hashtag', - }), - }) - - expect(subject).to_not have_key('id') - expect(subject).to_not have_key('published') - expect(subject).to_not have_key('to') - expect(subject).to_not have_key('cc') - end - end - end -end diff --git a/spec/serializers/activitypub/undo_announce_serializer_spec.rb b/spec/serializers/activitypub/undo_announce_serializer_spec.rb index d1ecd3cfc5..19b8106c6f 100644 --- a/spec/serializers/activitypub/undo_announce_serializer_spec.rb +++ b/spec/serializers/activitypub/undo_announce_serializer_spec.rb @@ -26,4 +26,17 @@ RSpec.describe ActivityPub::UndoAnnounceSerializer do expect(subject).to_not have_key('cc') expect(subject).to_not have_key('target') end + + context 'when status is local and private' do + let(:status) { Fabricate(:status, visibility: :private) } + let(:reblog) { Fabricate(:status, reblog: status, account: status.account) } + + it 'does not inline the status' do + expect(subject).to include({ + 'object' => a_hash_including({ + 'object' => tag_manager.uri_for(status), + }), + }) + end + end end diff --git a/spec/serializers/activitypub/update_serializer_spec.rb b/spec/serializers/activitypub/update_actor_serializer_spec.rb similarity index 93% rename from spec/serializers/activitypub/update_serializer_spec.rb rename to spec/serializers/activitypub/update_actor_serializer_spec.rb index 7fd034b604..541b4e656d 100644 --- a/spec/serializers/activitypub/update_serializer_spec.rb +++ b/spec/serializers/activitypub/update_actor_serializer_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe ActivityPub::UpdateSerializer do +RSpec.describe ActivityPub::UpdateActorSerializer do subject { serialized_record_json(account, described_class, adapter: ActivityPub::Adapter) } let(:tag_manager) { ActivityPub::TagManager.instance } diff --git a/spec/serializers/activitypub/update_note_serializer_spec.rb b/spec/serializers/activitypub/update_note_serializer_spec.rb new file mode 100644 index 0000000000..4307b29f06 --- /dev/null +++ b/spec/serializers/activitypub/update_note_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::UpdateNoteSerializer do + subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:status) { Fabricate(:status, edited_at: Time.utc(2026, 1, 27, 15, 29, 31)) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => "#{tag_manager.uri_for(status)}#updates/1769527771", + 'type' => 'Update', + 'actor' => tag_manager.uri_for(status.account), + 'published' => '2026-01-27T15:29:31Z', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'cc' => [a_string_matching(%r{/followers$})], + 'object' => a_hash_including( + 'id' => tag_manager.uri_for(status) + ), + }) + + expect(subject).to_not have_key('target') + end +end diff --git a/spec/workers/activitypub/distribution_worker_spec.rb b/spec/workers/activitypub/distribution_worker_spec.rb index 9e5db53185..1551ba63ba 100644 --- a/spec/workers/activitypub/distribution_worker_spec.rb +++ b/spec/workers/activitypub/distribution_worker_spec.rb @@ -51,5 +51,46 @@ RSpec.describe ActivityPub::DistributionWorker do end end end + + context 'with a reblog' do + before do + follower.follow!(reblog.account) + end + + context 'when the reblogged status is not private' do + let(:status) { Fabricate(:status) } + let(:reblog) { Fabricate(:status, reblog: status) } + + it 'delivers an activity without inlining the status' do + expected_json = { + type: 'Announce', + object: ActivityPub::TagManager.instance.uri_for(status), + } + + expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [[match_json_values(expected_json), reblog.account.id, 'http://example.com', anything]]) do + subject.perform(reblog.id) + end + end + end + + context 'when the reblogged status is private' do + let(:status) { Fabricate(:status, visibility: :private) } + let(:reblog) { Fabricate(:status, reblog: status, account: status.account) } + + it 'delivers an activity that inlines the status' do + expected_json = { + type: 'Announce', + object: a_hash_including({ + id: ActivityPub::TagManager.instance.uri_for(status), + type: 'Note', + }), + } + + expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [[match_json_values(expected_json), reblog.account.id, 'http://example.com', anything]]) do + subject.perform(reblog.id) + end + end + end + end end end