From a8713ee8b73ef63dab45c3c4db949dbfc49a6381 Mon Sep 17 00:00:00 2001 From: multiple creatures Date: Thu, 12 Dec 2019 04:38:56 -0600 Subject: add ability for post authors to kick jerks out of their threads --- app/lib/activitypub/activity/create.rb | 7 +++- app/models/concerns/account_associations.rb | 3 ++ app/models/concerns/status_threading_concern.rb | 3 +- app/models/conversation.rb | 1 + app/models/conversation_kick.rb | 13 ++++++++ app/services/remove_status_for_account_service.rb | 38 ++++++++++++++++++++++ config/locales/en.yml | 1 + .../20191212043419_create_conversation_kicks.rb | 10 ++++++ db/schema.rb | 12 ++++++- spec/fabricators/conversation_kick_fabricator.rb | 4 +++ spec/models/conversation_kick_spec.rb | 5 +++ 11 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 app/models/conversation_kick.rb create mode 100644 app/services/remove_status_for_account_service.rb create mode 100644 db/migrate/20191212043419_create_conversation_kicks.rb create mode 100644 spec/fabricators/conversation_kick_fabricator.rb create mode 100644 spec/models/conversation_kick_spec.rb diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 1839a649e..b82bfec21 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -66,7 +66,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity @mentions_local_account = false process_status_params - return reject_payload! if twitter_retweet? || recipient_rejects_replies? + return reject_payload! if twitter_retweet? || recipient_rejects_replies? || kicked? process_tags process_audience @@ -109,6 +109,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity @params[:thread]&.account_id != @account.id end + def kicked? + @params[:conversation].present? && + @params[:conversation].kicks.where(account_id: @account.id).exists? + end + def process_status_params @params = begin { diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index a90104943..19190f0c5 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -61,5 +61,8 @@ module AccountAssociations # queued boosts has_many :queued_boosts, dependent: :destroy, inverse_of: :account + + # kicked-out-of conversations + has_many :conversation_kicks, dependent: :destroy, inverse_of: :account end end diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index 1e5c52c46..adf8659d3 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -127,7 +127,8 @@ module StatusThreadingConcern end def statuses_with_accounts(ids) - Status.where(id: ids).includes(:account) + kicked_accounts = ConversationKick.select(:account_id).where(conversation_id: self.conversation_id) + Status.where(id: ids).where.not(account_id: kicked_accounts).includes(:account) end def filter_from_context?(status, account, relations) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 4dfaea889..7d277fa85 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -13,6 +13,7 @@ class Conversation < ApplicationRecord validates :uri, uniqueness: true, if: :uri? has_many :statuses + has_many :kicks, class_name: 'ConversationKick', inverse_of: :conversation, dependent: :destroy def local? uri.nil? diff --git a/app/models/conversation_kick.rb b/app/models/conversation_kick.rb new file mode 100644 index 000000000..c2dbdf50b --- /dev/null +++ b/app/models/conversation_kick.rb @@ -0,0 +1,13 @@ +# == Schema Information +# +# Table name: conversation_kicks +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# conversation_id :bigint(8) not null +# + +class ConversationKick < ApplicationRecord + belongs_to :account + belongs_to :conversation +end diff --git a/app/services/remove_status_for_account_service.rb b/app/services/remove_status_for_account_service.rb new file mode 100644 index 000000000..5fa682d01 --- /dev/null +++ b/app/services/remove_status_for_account_service.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class RemoveStatusForAccountService < BaseService + include Redisable + + def call(account, status) + @account = account + @status = status + @payload = Oj.dump(event: :delete, payload: status.id.to_s) + + RedisLock.acquire(lock_options) do |lock| + if lock.acquired? + remove_from_feeds + remove_from_lists + else + raise Mastodon::RaceConditionError + end + end + end + + private + + def remove_from_feeds + FeedManager.instance.unpush_from_home(@account, @status) + Redis.current.publish("timeline:direct:#{@account.id}", @payload) + redis.publish("timeline:#{@account.id}", @payload) + end + + def remove_from_lists + @account.lists_for_local_distribution.select(:id, :account_id).reorder(nil).find_each do |list| + FeedManager.instance.unpush_from_list(list, @status) + end + end + + def lock_options + { redis: Redis.current, key: "distribute:#{@status.id}" } + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 8417f952a..7f026135f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -975,6 +975,7 @@ en: other: "%{count} votes" vote: Vote replies_rejected: 'The author is not accepting replies to this roar.' + kicked: "You do not have the author's permission to reply to this thread." show_more: Show more sign_in_to_participate: Sign in to participate in the conversation title: '%{name}: "%{quote}"' diff --git a/db/migrate/20191212043419_create_conversation_kicks.rb b/db/migrate/20191212043419_create_conversation_kicks.rb new file mode 100644 index 000000000..533114845 --- /dev/null +++ b/db/migrate/20191212043419_create_conversation_kicks.rb @@ -0,0 +1,10 @@ +class CreateConversationKicks < ActiveRecord::Migration[5.2] + def change + create_table :conversation_kicks do |t| + t.references :account, null: false, foreign_key: {on_delete: :cascade} + t.references :conversation, null: false, foreign_key: {on_delete: :cascade} + end + + add_index :conversation_kicks, [:account_id, :conversation_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 47b792a8b..603203c4f 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: 2019_12_12_022653) do +ActiveRecord::Schema.define(version: 2019_12_12_043419) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -218,6 +218,14 @@ ActiveRecord::Schema.define(version: 2019_12_12_022653) do t.index ["status_id"], name: "index_bookmarks_on_status_id" end + create_table "conversation_kicks", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "conversation_id", null: false + t.index ["account_id", "conversation_id"], name: "index_conversation_kicks_on_account_id_and_conversation_id", unique: true + t.index ["account_id"], name: "index_conversation_kicks_on_account_id" + t.index ["conversation_id"], name: "index_conversation_kicks_on_conversation_id" + end + create_table "conversation_mutes", force: :cascade do |t| t.bigint "conversation_id", null: false t.bigint "account_id", null: false @@ -846,6 +854,8 @@ ActiveRecord::Schema.define(version: 2019_12_12_022653) do add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade add_foreign_key "bookmarks", "accounts", on_delete: :cascade add_foreign_key "bookmarks", "statuses", on_delete: :cascade + add_foreign_key "conversation_kicks", "accounts", on_delete: :cascade + add_foreign_key "conversation_kicks", "conversations", on_delete: :cascade add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade add_foreign_key "conversation_mutes", "conversations", on_delete: :cascade add_foreign_key "custom_filters", "accounts", on_delete: :cascade diff --git a/spec/fabricators/conversation_kick_fabricator.rb b/spec/fabricators/conversation_kick_fabricator.rb new file mode 100644 index 000000000..ba321212d --- /dev/null +++ b/spec/fabricators/conversation_kick_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:conversation_kick) do + account nil + conversation nil +end diff --git a/spec/models/conversation_kick_spec.rb b/spec/models/conversation_kick_spec.rb new file mode 100644 index 000000000..942d14afd --- /dev/null +++ b/spec/models/conversation_kick_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ConversationKick, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end -- cgit