diff --git a/app/jobs/activity/creator_added_model_job.rb b/app/jobs/activity/creator_added_model_job.rb index 42c47949d..6c0246855 100644 --- a/app/jobs/activity/creator_added_model_job.rb +++ b/app/jobs/activity/creator_added_model_job.rb @@ -3,6 +3,20 @@ class Activity::CreatorAddedModelJob < ApplicationJob def perform(model_id) model = Model.find(model_id) + if model.public? + Federails::Activity.create!( + actor: model.creator&.actor || model.actor, + action: "Create", + entity: model + ) + end + # Post a comment as well, for notes + post_comment(model) + end + + private + + def post_comment(model) Comment.create!( system: true, commentable: model, diff --git a/app/models/comment.rb b/app/models/comment.rb index 4f4f7f8e9..a32ebea4c 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -37,7 +37,10 @@ def to_activitypub_object attributedTo: (commenter&.actor&.respond_to?(:federated_url) ? commenter.actor.federated_url : nil), sensitive: sensitive, to: ["https://www.w3.org/ns/activitystreams#Public"], - cc: [commenter.actor.followers_url], + cc: [ + commenter.actor&.followers_url, + commentable.actor&.followers_url + ], tag: tags }.compact end diff --git a/app/models/model.rb b/app/models/model.rb index 6c0055c7b..989bb088a 100644 --- a/app/models/model.rb +++ b/app/models/model.rb @@ -152,6 +152,33 @@ def split!(files: []) new_model end + def federated_url + return nil unless public? + Rails.application.routes.url_helpers.url_for(self, {only_path: false}) + end + + def to_activitypub_object + # We define a 3DModel object type, following the naming of https://schema.org/3DModel + # This is described in our ActivityPub docs at https://manyfold.app/technology/activitypub.html + { + id: federated_url, + type: "3DModel", + published: created_at&.iso8601, + attributedTo: (creator&.actor&.respond_to?(:federated_url) ? creator.actor.federated_url : nil), + sensitive: sensitive, + to: ["https://www.w3.org/ns/activitystreams#Public"], + cc: [ + creator&.actor&.followers_url, + collection&.actor&.followers_url, + actor&.followers_url + ].compact + }.compact + end + + def public? + Pundit::PolicyFinder.new(Model).policy.new(nil, self).show? + end + private def normalize_license diff --git a/app/views/models/show.activitypub.jbuilder b/app/views/models/show.activitypub.jbuilder new file mode 100644 index 000000000..fe60ee3e5 --- /dev/null +++ b/app/views/models/show.activitypub.jbuilder @@ -0,0 +1,2 @@ +json.set! "@context", "https://www.w3.org/ns/activitystreams" +json.merge! @model.to_activitypub_object diff --git a/spec/models/model_spec.rb b/spec/models/model_spec.rb index 9892bc3b0..03b94c1f9 100644 --- a/spec/models/model_spec.rb +++ b/spec/models/model_spec.rb @@ -427,4 +427,47 @@ expect(Activity::CreatorAddedModelJob).to have_been_enqueued.with(model.id).at_least(:once) end end + + context "when serializing to an ActivityPub Note" do + let(:creator) { create(:creator) } + let(:collection) { create(:collection) } + let(:model) { create(:model, sensitive: true, creator: creator, collection: collection) } + let(:ap_object) { model.to_activitypub_object } + + it "creates a 3DModel" do + expect(ap_object[:type]).to eq "3DModel" + end + + it "includes id" do + expect(ap_object[:id]).to eq model.federated_url + end + + it "includes publication time" do + expect(ap_object[:published]).to be_present + end + + it "includes sensitive flag" do + expect(ap_object[:sensitive]).to be true + end + + it "includes attribution" do + expect(ap_object[:attributedTo]).to eq creator.actor.federated_url + end + + it "includes to field" do + expect(ap_object[:to]).to include "https://www.w3.org/ns/activitystreams#Public" + end + + it "includes model followers in cc field" do + expect(ap_object[:cc]).to include model.actor.followers_url + end + + it "includes creator followers in cc field" do + expect(ap_object[:cc]).to include model.creator.actor.followers_url + end + + it "includes collection followers in cc field" do + expect(ap_object[:cc]).to include model.collection.actor.followers_url + end + end end