about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFire Demon <firedemon@creature.cafe>2020-08-16 23:18:38 -0500
committerFire Demon <firedemon@creature.cafe>2020-08-30 05:45:18 -0500
commitb1e6e6957e62f3da3857f42ec6c343cb9660434d (patch)
treee1305ef8cf98aabe10fa58c8bc27d0408c681066
parentaf0b6f445c597b41e861da9e77f39b4caed3e753 (diff)
[Federation] Include and dereference URI to the root post of threads; dynamically update thread permissions
-rw-r--r--app/lib/activitypub/activity/create.rb19
-rw-r--r--app/lib/activitypub/adapter.rb1
-rw-r--r--app/models/conversation.rb3
-rw-r--r--app/models/status.rb10
-rw-r--r--app/models/status_mute.rb2
-rw-r--r--app/serializers/activitypub/note_serializer.rb8
-rw-r--r--app/serializers/rest/status_serializer.rb6
-rw-r--r--db/migrate/20200816200108_add_root_to_conversations.rb7
-rw-r--r--db/migrate/20200816200239_backfill_root_to_conversations.rb19
-rw-r--r--db/migrate/20200817003033_add_defaults_to_conversations.rb8
-rw-r--r--db/schema.rb5
11 files changed, 78 insertions, 10 deletions
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 12c3b0d19..91f0ab146 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -373,6 +373,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def fetch_replies(status)
+    FetchReplyWorker.perform_async(@object['root']) unless @object['root'].blank? || [@object['id'], @object['url']].include?(@object['root']) || status_from_uri(@object['root'])
+
     collection = @object['replies']
     return if collection.nil?
 
@@ -385,11 +387,22 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
   def conversation_from_uri(uri)
     return nil if uri.nil?
-    return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri)
+    conversation = OStatus::TagManager.instance.local_id?(uri) ? Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) : nil
 
     begin
-      conversation = Conversation.find_by(uri: uri)
-      Conversation.create!(uri: uri, account: @account, public: %i(public unlisted).include?(visibility_from_audience)) if conversation.nil?
+      conversation = Conversation.find_by(uri: uri) if conversation.blank?
+
+      if @object['inReplyTo'].blank? && replied_to_status.blank?
+        if conversation.blank?
+          conversation = Conversation.create!(uri: uri, root: @object['id'], account: @account, public: %i(public unlisted).include?(visibility_from_audience))
+        elsif conversation.root.blank?
+          conversation.update!(root: @object['id'], account: @account, public: %i(public unlisted).include?(visibility_from_audience))
+        end
+      elsif conversation.blank?
+        conversation = Conversation.create!(uri: uri, account_id: nil, public: false)
+      end
+
+      conversation
     rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
       retry
     end
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index b02059430..107b93b44 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -16,6 +16,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
     require_auth: { 'mp' => 'http://the.monsterpit.net/ns#', 'requireAuth' => 'mp:requireAuth' },
     metadata: { 'mp' => 'https://the.monsterpit.net/ns#', 'metadata' => { '@id' => 'mp:metadata', '@type' => '@id' } },
     server_metadata: { 'mp' => 'https://the.monsterpit.net/ns#', 'serverMetadata' => { '@id' => 'mp:serverMetadata', '@type' => '@id' } },
+    root: { 'mp' => 'https://the.monsterpit.net/ns#', 'root' => { '@id' => 'mp:root', '@type' => '@id' } },
     manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' },
     sensitive: { 'sensitive' => 'as:sensitive' },
     hashtag: { 'Hashtag' => 'as:Hashtag' },
diff --git a/app/models/conversation.rb b/app/models/conversation.rb
index d1674fe4e..e065c34c8 100644
--- a/app/models/conversation.rb
+++ b/app/models/conversation.rb
@@ -9,6 +9,7 @@
 #  updated_at :datetime         not null
 #  account_id :bigint(8)
 #  public     :boolean          default(FALSE), not null
+#  root       :string
 #
 
 class Conversation < ApplicationRecord
@@ -16,7 +17,7 @@ class Conversation < ApplicationRecord
 
   has_many :statuses
   has_many :mutes, class_name: 'ConversationMute', inverse_of: :conversation, dependent: :destroy
-  belongs_to :account, inverse_of: :threads
+  belongs_to :account, inverse_of: :threads, optional: true
 
   def local?
     uri.nil?
diff --git a/app/models/status.rb b/app/models/status.rb
index 9067f44df..9c061bf85 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -376,6 +376,7 @@ class Status < ApplicationRecord
   after_destroy_commit :decrement_counter_caches
 
   after_create_commit :store_uri, if: :local?
+  after_create_commit :store_url, if: :local?
   after_create_commit :update_statistics, if: :local?
 
   around_create Mastodon::Snowflake::Callbacks
@@ -392,6 +393,7 @@ class Status < ApplicationRecord
 
   after_save :set_domain_permissions, if: :local?
   after_save :set_semiprivate, if: :local?
+  after_save :set_conversation_root
 
   class << self
     def selectable_visibilities
@@ -676,6 +678,10 @@ class Status < ApplicationRecord
     update_column(:uri, ActivityPub::TagManager.instance.uri_for(self)) if uri.nil?
   end
 
+  def store_url
+    update_column(:url, ActivityPub::TagManager.instance.url_for(self)) if url.nil?
+  end
+
   def prepare_contents
     text&.strip!
     spoiler_text&.strip!
@@ -717,6 +723,10 @@ class Status < ApplicationRecord
     end
   end
 
+  def set_conversation_root
+    conversation.update!(root: uri, account_id: account_id, public: distributable?) if !reply && conversation.root.blank?
+  end
+
   def carried_over_reply_to_account_id
     if thread.account_id == account_id && thread.reply?
       thread.in_reply_to_account_id
diff --git a/app/models/status_mute.rb b/app/models/status_mute.rb
index 223893604..1e01f0278 100644
--- a/app/models/status_mute.rb
+++ b/app/models/status_mute.rb
@@ -4,7 +4,7 @@
 # Table name: status_mutes
 #
 #  id         :bigint(8)        not null, primary key
-#  account_id :integer          not null
+#  account_id :bigint(8)        not null
 #  status_id  :bigint(8)        not null
 #
 
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 163f25560..b86ab932e 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -3,7 +3,7 @@
 class ActivityPub::NoteSerializer < ActivityPub::Serializer
   context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message
 
-  context_extensions :edited, :server_metadata
+  context_extensions :edited, :server_metadata, :root
 
   attributes :id, :type, :summary,
              :in_reply_to, :published, :url,
@@ -11,7 +11,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
              :atom_uri, :in_reply_to_atom_uri,
              :conversation
 
-  attribute :updated
+  attributes :updated, :root
   attribute :title, key: :name, if: :title_present?
 
   attribute :content
@@ -50,6 +50,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
     end
   end
 
+  def root
+    object.conversation&.root
+  end
+
   def summary
     return Formatter.instance.format(object) if title_present?
 
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index dec39ec24..1182d8e80 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -7,7 +7,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
              :favourites_count
 
   # Monsterfork additions
-  attributes :updated_at, :edited, :nest_level
+  attributes :updated_at, :edited, :nest_level, :root
 
   attribute :favourited, if: :current_user?
   attribute :reblogged, if: :current_user?
@@ -110,6 +110,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
     ActivityPub::TagManager.instance.url_for(object)
   end
 
+  def root
+    object.conversation&.root
+  end
+
   def favourited
     if instance_options && instance_options[:relationships]
       instance_options[:relationships].favourites_map[object.id] || false
diff --git a/db/migrate/20200816200108_add_root_to_conversations.rb b/db/migrate/20200816200108_add_root_to_conversations.rb
new file mode 100644
index 000000000..f45a3b476
--- /dev/null
+++ b/db/migrate/20200816200108_add_root_to_conversations.rb
@@ -0,0 +1,7 @@
+class AddRootToConversations < ActiveRecord::Migration[5.2]
+  def change
+    safety_assured do
+      add_column :conversations, :root, :string, index: true
+    end
+  end
+end
diff --git a/db/migrate/20200816200239_backfill_root_to_conversations.rb b/db/migrate/20200816200239_backfill_root_to_conversations.rb
new file mode 100644
index 000000000..2056e0765
--- /dev/null
+++ b/db/migrate/20200816200239_backfill_root_to_conversations.rb
@@ -0,0 +1,19 @@
+class BackfillRootToConversations < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    Rails.logger.info("Adding URI to statuses without one...")
+    Status.where(uri: nil).or(Status.where(uri: '')).find_each do |status|
+      status.update(uri: ActivityPub::TagManager.instance.uri_for(status))
+    end
+
+    Rails.logger.info('Setting root of all conversations...')
+    safety_assured do
+      execute('UPDATE conversations SET root = s.uri FROM (SELECT conversation_id, uri FROM statuses WHERE NOT reply) AS s WHERE conversations.id = s.conversation_id')
+    end
+  end
+
+  def down
+    true
+  end
+end
diff --git a/db/migrate/20200817003033_add_defaults_to_conversations.rb b/db/migrate/20200817003033_add_defaults_to_conversations.rb
new file mode 100644
index 000000000..fc3c0ceee
--- /dev/null
+++ b/db/migrate/20200817003033_add_defaults_to_conversations.rb
@@ -0,0 +1,8 @@
+class AddDefaultsToConversations < ActiveRecord::Migration[5.2]
+  def change
+    safety_assured do
+      change_column :conversations, :account_id, :bigint, default: nil
+      change_column :conversations, :root, :string, default: nil
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7d3ec26d7..5d7bd5262 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2020_08_11_024642) do
+ActiveRecord::Schema.define(version: 2020_08_17_003653) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -306,6 +306,7 @@ ActiveRecord::Schema.define(version: 2020_08_11_024642) do
     t.datetime "updated_at", null: false
     t.bigint "account_id"
     t.boolean "public", default: false, null: false
+    t.string "root"
     t.index ["account_id"], name: "index_conversations_on_account_id"
     t.index ["uri"], name: "index_conversations_on_uri", unique: true
   end
@@ -818,7 +819,7 @@ ActiveRecord::Schema.define(version: 2020_08_11_024642) do
   end
 
   create_table "status_mutes", force: :cascade do |t|
-    t.integer "account_id", null: false
+    t.bigint "account_id", null: false
     t.bigint "status_id", null: false
     t.index ["account_id", "status_id"], name: "index_status_mutes_on_account_id_and_status_id", unique: true
     t.index ["account_id"], name: "index_status_mutes_on_account_id"