Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Update activity support to first post only #14

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/models/discourse_activity_pub_activity.rb
Expand Up @@ -15,6 +15,10 @@ def delete?
ap_type === DiscourseActivityPub::AP::Activity::Delete.type
end

def update?
ap_type === DiscourseActivityPub::AP::Activity::Update.type
end

def ready?
case object_type
when "DiscourseActivityPubActivity"
Expand Down Expand Up @@ -51,6 +55,8 @@ def deliver(to_actor_id: nil, delay: nil)
to_actor_id: to_actor_id
}

Jobs.cancel_scheduled_job(:discourse_activity_pub_deliver, args)

if delay
Jobs.enqueue_in(delay.minutes, :discourse_activity_pub_deliver, args)
scheduled_at = (Time.now.utc + delay.minutes).iso8601
Expand Down Expand Up @@ -80,6 +86,7 @@ def after_deliver
args = {}
args[:published_at] = published_at if create?
args[:deleted_at] = published_at if delete?
args[:updated_at] = published_at if update?
self.object.model.activity_pub_after_publish(args)
end
end
Expand Down
19 changes: 10 additions & 9 deletions app/models/discourse_activity_pub_object.rb
Expand Up @@ -14,7 +14,7 @@ def ready?(ap_type)
return true unless local?

case ap_type
when DiscourseActivityPub::AP::Activity::Create.type
when DiscourseActivityPub::AP::Activity::Create.type, DiscourseActivityPub::AP::Activity::Update.type
!!model && !model.trashed?
when DiscourseActivityPub::AP::Activity::Delete.type
!model || model.trashed?
Expand All @@ -33,12 +33,8 @@ def self.handle_model_callback(model, ap_type_sym)
ap = DiscourseActivityPub::AP::Object.from_type(ap_type_sym)
return unless model.activity_pub_enabled && ap&.composition?

if ap_type_sym == :update
# We don't currently permit updates after publication
return if model.activity_pub_published?
# We don't permit updates if object has been deleted
return if model.activity_pub_deleted?
end
# We don't permit updates if object has been deleted
return if model.activity_pub_deleted? && ap_type_sym == :update

# If we're pre-publication clear all objects and data.
if !model.activity_pub_published? && ap_type_sym == :delete
Expand All @@ -61,14 +57,19 @@ def self.handle_model_callback(model, ap_type_sym)

object.save!

if ap_type_sym != :update
DiscourseActivityPubActivity.create!(
unless !model.activity_pub_published? && ap_type_sym == :update
activity_attrs = {
local: true,
actor_id: model.activity_pub_actor.id,
object_id: object.id,
object_type: 'DiscourseActivityPubObject',
ap_type: ap.type
}
unless DiscourseActivityPubActivity.exists?(
pmusaraj marked this conversation as resolved.
Show resolved Hide resolved
activity_attrs.merge(published_at: nil)
)
DiscourseActivityPubActivity.create!(activity_attrs)
end
end
end
end
Expand Down
Expand Up @@ -2,7 +2,8 @@

class DiscourseActivityPub::AP::Object::NoteSerializer < DiscourseActivityPub::AP::ObjectSerializer
attributes :content,
:url
:url,
:updated

def content
content = object.content
Expand All @@ -24,6 +25,10 @@ def include_url?
object.stored.local? && !deleted?
end

def include_updated?
object.updated.present?
end

def deleted?
!object.stored.model || object.stored.model.trashed?
end
Expand Down
Expand Up @@ -23,6 +23,10 @@ export default {
"activity_pub_deleted_at",
"activity_pub_deleted_at"
);
api.includePostAttributes(
"activity_pub_updated_at",
"activity_pub_updated_at"
);

// TODO (future): PR discourse/discourse to add post infos via api
api.reopenWidget("post-meta-data", {
Expand All @@ -43,6 +47,9 @@ export default {
if (attrs.activity_pub_deleted_at) {
time = moment(attrs.activity_pub_deleted_at);
status = "deleted";
} else if (attrs.activity_pub_updated_at) {
time = moment(attrs.activity_pub_updated_at);
status = "updated";
} else if (attrs.activity_pub_published_at) {
time = moment(attrs.activity_pub_published_at);
status = "published";
Expand Down Expand Up @@ -94,6 +101,7 @@ export default {
activity_pub_scheduled_at: data.model.scheduled_at,
activity_pub_published_at: data.model.published_at,
activity_pub_deleted_at: data.model.deleted_at,
activity_pub_updated_at: data.model.updated_at,
};
this.get("model.postStream")
.triggerActivityPubStateChange(data.model.id, stateProps)
Expand Down
3 changes: 2 additions & 1 deletion assets/stylesheets/common/common.scss
Expand Up @@ -107,7 +107,8 @@
}

.post-info.activity-pub {
&.published .d-icon {
&.published .d-icon,
&.updated .d-icon {
color: var(--success);
}
&.scheduled .d-icon,
Expand Down
3 changes: 2 additions & 1 deletion config/locales/client.en.yml
Expand Up @@ -37,4 +37,5 @@ en:
published: "Post was published via ActivityPub at %{time}"
scheduled: "Post is scheduled to be published via ActivityPub at %{time}"
scheduled_past: "Post was scheduled to be published via ActivityPub at %{time}"
deleted: "ActivityPub note was deleted at %{time}"
deleted: "ActivityPub note was deleted at %{time}"
updated: "Post was updated via ActivityPub at %{time}"
3 changes: 2 additions & 1 deletion lib/discourse_activity_pub/ap/actor/group.rb
Expand Up @@ -17,7 +17,8 @@ def can_perform_activity
accept: [:follow],
reject: [:follow],
create: [:note],
delete: [:note]
delete: [:note],
update: [:note]
}
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/discourse_activity_pub/ap/object/note.rb
Expand Up @@ -12,6 +12,10 @@ def content
stored&.content
end

def updated
stored&.updated_at.iso8601
end

def can_belong_to
%i(post)
end
Expand Down
25 changes: 13 additions & 12 deletions plugin.rb
Expand Up @@ -226,6 +226,7 @@
register_post_custom_field_type("activity_pub_scheduled_at", :string)
register_post_custom_field_type("activity_pub_published_at", :string)
register_post_custom_field_type("activity_pub_deleted_at", :string)
register_post_custom_field_type("activity_pub_updated_at", :string)

add_to_class(:post, :activity_pub_url) do
"#{DiscourseActivityPub.base_url}#{self.url}"
Expand All @@ -251,7 +252,7 @@
topic.category&.activity_pub_actor
end
add_to_class(:post, :activity_pub_after_publish) do |args = {}|
if !activity_pub_enabled || (!args[:published_at] && !args[:deleted_at])
if !activity_pub_enabled || (args.keys & %i[published_at deleted_at updated_at]).empty?
return nil
end

Expand All @@ -261,6 +262,9 @@
custom_fields["activity_pub_deleted_at"] = args[:deleted_at] if args[
:deleted_at
]
custom_fields["activity_pub_updated_at"] = args[:updated_at] if args[
:updated_at
]
save_custom_fields(true)

activity_pub_publish_state
Expand All @@ -278,6 +282,9 @@
add_to_class(:post, :activity_pub_deleted_at) do
custom_fields["activity_pub_deleted_at"]
end
add_to_class(:post, :activity_pub_updated_at) do
custom_fields["activity_pub_updated_at"]
end
add_to_class(:post, :activity_pub_published?) { !!activity_pub_published_at }
add_to_class(:post, :activity_pub_deleted?) { !!activity_pub_deleted_at }
add_to_class(:post, :activity_pub_scheduled_at) do
Expand All @@ -295,7 +302,8 @@
type: "post",
scheduled_at: self.activity_pub_scheduled_at,
published_at: self.activity_pub_published_at,
deleted_at: self.activity_pub_deleted_at
deleted_at: self.activity_pub_deleted_at,
updated_at: self.activity_pub_updated_at
}
}
opts = {
Expand All @@ -319,6 +327,9 @@
add_to_serializer(:post, :activity_pub_deleted_at) do
object.activity_pub_deleted_at
end
add_to_serializer(:post, :activity_pub_updated_at) do
object.activity_pub_updated_at
end

# TODO (future): discourse/discourse needs to cook earlier for validators.
# See also discourse/discourse/plugins/poll/lib/poll.rb.
Expand All @@ -332,16 +343,6 @@
"activity_pub_content"
] = DiscourseActivityPub::ExcerptParser.get_content(post)
end
on(:before_edit_post) do |post, fields|
if fields.has_key?(:raw) && post.activity_pub_published? &&
post.activity_pub_content != post.activity_pub_object.content
post.errors.add(
:base,
I18n.t("post.discourse_activity_pub.error.edit_after_publication")
)
raise ActiveRecord::Rollback
end
end
on(:post_edited) do |post, topic_changed, post_revisor|
DiscourseActivityPubObject.handle_model_callback(post, :update)
end
Expand Down
12 changes: 12 additions & 0 deletions spec/fabricators/discourse_activity_pub_activity_fabricator.rb
Expand Up @@ -51,6 +51,18 @@
end
end

Fabricator(:discourse_activity_pub_activity_update, from: :discourse_activity_pub_activity) do
ap_type { DiscourseActivityPub::AP::Activity::Update.type }
actor { Fabricate(:discourse_activity_pub_actor_group) }
object { Fabricate(:discourse_activity_pub_object_note) }
local { true }

after_create do |activity|
object.model.custom_fields['activity_pub_published_at'] = Time.now
object.model.save_custom_fields(true)
end
end

Fabricator(:discourse_activity_pub_activity_delete, from: :discourse_activity_pub_activity) do
ap_type { DiscourseActivityPub::AP::Activity::Delete.type }
actor { Fabricate(:discourse_activity_pub_actor_group) }
Expand Down
21 changes: 21 additions & 0 deletions spec/jobs/discourse_activity_pub_deliver_spec.rb
Expand Up @@ -238,5 +238,26 @@ def execute_job(args = {})
end
end
end

context "when delivering an Update" do
let(:activity) { Fabricate(:discourse_activity_pub_activity_update) }
let(:person) { Fabricate(:discourse_activity_pub_actor_person) }

it "performs the right request" do
body = activity.ap.json
body[:to] = person.ap.id

expect_request(
actor_id: activity.actor.id,
uri: person.inbox,
body: body
)
execute_job(
activity_id: activity.id,
from_actor_id: activity.actor.id,
to_actor_id: person.id
)
end
end
end
end
Expand Up @@ -3,9 +3,10 @@
require 'rails_helper'

describe DiscourseActivityPub::DeliveryFailureTracker do
let!(:actor) { Fabricate(:discourse_activity_pub_actor_person) }
subject { described_class.new(actor.inbox) }

let!(:actor) { Fabricate(:discourse_activity_pub_actor_person) }

after do
Discourse.redis.flushdb
end
Expand Down
16 changes: 7 additions & 9 deletions spec/lib/post_revisor_spec.rb
Expand Up @@ -28,18 +28,16 @@
end

describe "with different note content" do
it "adds the right error" do
it "does not add an error" do
subject.revise!(user, raw: "#{post.raw} revision inside note")
expect(post.errors.present?).to eq(true)
expect(post.errors.messages[:base].first).to eq(
I18n.t("post.discourse_activity_pub.error.edit_after_publication")
)
expect(post.errors.present?).to eq(false)
end

it "does not perform the edit" do
original_raw = post.raw
subject.revise!(user, raw: "#{post.raw} revision inside note")
expect(post.reload.raw).to eq(original_raw)
it "performs the edit" do
updated_raw = "#{post.raw} revision inside note"
subject.revise!(user, raw: updated_raw)
expect(post.reload.raw).to eq(updated_raw)
expect(post.activity_pub_content).to eq(updated_raw)
end
end

Expand Down
21 changes: 21 additions & 0 deletions spec/models/discourse_activity_pub_activity_spec.rb
Expand Up @@ -150,6 +150,16 @@
end
end
end

it "cancels existing scheduled deliveries" do
job_args = {
activity_id: accept_activity.id,
from_actor_id: accept_activity.actor.id,
to_actor_id: accept_activity.object.actor.id
}
Jobs.expects(:cancel_scheduled_job).with(:discourse_activity_pub_deliver, job_args).once
accept_activity.deliver(to_actor_id: accept_activity.object.actor.id)
end
end

describe "#after_deliver" do
Expand Down Expand Up @@ -189,6 +199,17 @@
end
end

context "with update activity" do
let(:update_activity) { Fabricate(:discourse_activity_pub_activity_update, actor: actor) }

it "calls activity_pub_after_publish on associated object models" do
freeze_time
original_time = Time.now.utc.iso8601
Post.any_instance.expects(:activity_pub_after_publish).with({ updated_at: original_time }).once
update_activity.after_deliver
end
end

context "with accept activity" do
let(:accept_activity) { Fabricate(:discourse_activity_pub_activity_accept, actor: actor) }

Expand Down