about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb109
-rw-r--r--app/models/account_domain_block.rb26
-rw-r--r--app/models/concerns/account_avatar.rb2
-rw-r--r--app/models/concerns/account_header.rb2
-rw-r--r--app/models/concerns/account_interactions.rb127
-rw-r--r--app/models/status.rb13
6 files changed, 172 insertions, 107 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 03e7db398..f418a0f8b 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -43,6 +43,7 @@ class Account < ApplicationRecord
 
   include AccountAvatar
   include AccountHeader
+  include AccountInteractions
   include Attachmentable
   include Remotable
   include Targetable
@@ -67,26 +68,6 @@ class Account < ApplicationRecord
   has_many :mentions, inverse_of: :account, dependent: :destroy
   has_many :notifications, inverse_of: :account, dependent: :destroy
 
-  # Follow relations
-  has_many :follow_requests, dependent: :destroy
-
-  has_many :active_relationships,  class_name: 'Follow', foreign_key: 'account_id',        dependent: :destroy
-  has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_account_id', dependent: :destroy
-
-  has_many :following, -> { order('follows.id desc') }, through: :active_relationships,  source: :target_account
-  has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
-
-  # Block relationships
-  has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
-  has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
-  has_many :blocked_by_relationships, class_name: 'Block', foreign_key: :target_account_id, dependent: :destroy
-  has_many :blocked_by, -> { order('blocks.id desc') }, through: :blocked_by_relationships, source: :account
-
-  # Mute relationships
-  has_many :mute_relationships, class_name: 'Mute', foreign_key: 'account_id', dependent: :destroy
-  has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
-  has_many :conversation_mutes
-
   # Media
   has_many :media_attachments, dependent: :destroy
 
@@ -120,62 +101,6 @@ class Account < ApplicationRecord
 
   delegate :allowed_languages, to: :user, prefix: false, allow_nil: true
 
-  def follow!(other_account)
-    active_relationships.find_or_create_by!(target_account: other_account)
-  end
-
-  def block!(other_account)
-    block_relationships.find_or_create_by!(target_account: other_account)
-  end
-
-  def mute!(other_account)
-    mute_relationships.find_or_create_by!(target_account: other_account)
-  end
-
-  def mute_conversation!(conversation)
-    conversation_mutes.find_or_create_by!(conversation: conversation)
-  end
-
-  def unfollow!(other_account)
-    follow = active_relationships.find_by(target_account: other_account)
-    follow&.destroy
-  end
-
-  def unblock!(other_account)
-    block = block_relationships.find_by(target_account: other_account)
-    block&.destroy
-  end
-
-  def unmute!(other_account)
-    mute = mute_relationships.find_by(target_account: other_account)
-    mute&.destroy
-  end
-
-  def unmute_conversation!(conversation)
-    mute = conversation_mutes.find_by(conversation: conversation)
-    mute&.destroy!
-  end
-
-  def following?(other_account)
-    following.include?(other_account)
-  end
-
-  def blocking?(other_account)
-    blocking.include?(other_account)
-  end
-
-  def muting?(other_account)
-    muting.include?(other_account)
-  end
-
-  def muting_conversation?(conversation)
-    conversation_mutes.where(conversation: conversation).exists?
-  end
-
-  def requested?(other_account)
-    follow_requests.where(target_account: other_account).exists?
-  end
-
   def local?
     domain.nil?
   end
@@ -200,14 +125,6 @@ class Account < ApplicationRecord
     followers.reorder(nil).pluck('distinct accounts.domain')
   end
 
-  def favourited?(status)
-    status.proper.favourites.where(account: self).exists?
-  end
-
-  def reblogged?(status)
-    status.proper.reblogs.where(account: self).exists?
-  end
-
   def keypair
     OpenSSL::PKey::RSA.new(private_key || public_key)
   end
@@ -238,6 +155,10 @@ class Account < ApplicationRecord
     Rails.cache.fetch("exclude_account_ids_for:#{id}") { blocking.pluck(:target_account_id) + blocked_by.pluck(:account_id) + muting.pluck(:target_account_id) }
   end
 
+  def excluded_from_timeline_domains
+    Rails.cache.fetch("exclude_domains_for:#{id}") { domain_blocks.pluck(:domain) }
+  end
+
   class << self
     def find_local!(username)
       find_remote!(username, nil)
@@ -321,26 +242,6 @@ class Account < ApplicationRecord
       find_by_sql([sql, account.id, account.id, limit])
     end
 
-    def following_map(target_account_ids, account_id)
-      follow_mapping(Follow.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
-    end
-
-    def followed_by_map(target_account_ids, account_id)
-      follow_mapping(Follow.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
-    end
-
-    def blocking_map(target_account_ids, account_id)
-      follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
-    end
-
-    def muting_map(target_account_ids, account_id)
-      follow_mapping(Mute.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
-    end
-
-    def requested_map(target_account_ids, account_id)
-      follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
-    end
-
     private
 
     def generate_query_for_search(terms)
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
new file mode 100644
index 000000000..9241d9720
--- /dev/null
+++ b/app/models/account_domain_block.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: account_domain_blocks
+#
+#  id         :integer          not null, primary key
+#  account_id :integer
+#  domain     :string
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class AccountDomainBlock < ApplicationRecord
+  include Paginable
+
+  belongs_to :account, required: true
+
+  after_create  :remove_blocking_cache
+  after_destroy :remove_blocking_cache
+
+  private
+
+  def remove_blocking_cache
+    Rails.cache.delete("exclude_domains_for:#{account_id}")
+  end
+end
diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb
index c664366ef..73507a328 100644
--- a/app/models/concerns/account_avatar.rb
+++ b/app/models/concerns/account_avatar.rb
@@ -2,6 +2,7 @@
 
 module AccountAvatar
   extend ActiveSupport::Concern
+
   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
 
   class_methods do
@@ -10,6 +11,7 @@ module AccountAvatar
       styles[:static] = { format: 'png' } if file.content_type == 'image/gif'
       styles
     end
+
     private :avatar_styles
   end
 
diff --git a/app/models/concerns/account_header.rb b/app/models/concerns/account_header.rb
index f1b0883ee..4d96e990a 100644
--- a/app/models/concerns/account_header.rb
+++ b/app/models/concerns/account_header.rb
@@ -2,6 +2,7 @@
 
 module AccountHeader
   extend ActiveSupport::Concern
+
   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
 
   class_methods do
@@ -10,6 +11,7 @@ module AccountHeader
       styles[:static] = { format: 'png' } if file.content_type == 'image/gif'
       styles
     end
+
     private :header_styles
   end
 
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
new file mode 100644
index 000000000..d51e6643e
--- /dev/null
+++ b/app/models/concerns/account_interactions.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+module AccountInteractions
+  extend ActiveSupport::Concern
+
+  class_methods do
+    def following_map(target_account_ids, account_id)
+      follow_mapping(Follow.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
+    end
+
+    def followed_by_map(target_account_ids, account_id)
+      follow_mapping(Follow.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
+    end
+
+    def blocking_map(target_account_ids, account_id)
+      follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
+    end
+
+    def muting_map(target_account_ids, account_id)
+      follow_mapping(Mute.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
+    end
+
+    def requested_map(target_account_ids, account_id)
+      follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
+    end
+  end
+
+  included do
+    # Follow relations
+    has_many :follow_requests, dependent: :destroy
+
+    has_many :active_relationships,  class_name: 'Follow', foreign_key: 'account_id',        dependent: :destroy
+    has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_account_id', dependent: :destroy
+
+    has_many :following, -> { order('follows.id desc') }, through: :active_relationships,  source: :target_account
+    has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
+
+    # Block relationships
+    has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
+    has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
+    has_many :blocked_by_relationships, class_name: 'Block', foreign_key: :target_account_id, dependent: :destroy
+    has_many :blocked_by, -> { order('blocks.id desc') }, through: :blocked_by_relationships, source: :account
+
+    # Mute relationships
+    has_many :mute_relationships, class_name: 'Mute', foreign_key: 'account_id', dependent: :destroy
+    has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
+    has_many :conversation_mutes, dependent: :destroy
+    has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
+
+    def follow!(other_account)
+      active_relationships.find_or_create_by!(target_account: other_account)
+    end
+
+    def block!(other_account)
+      block_relationships.find_or_create_by!(target_account: other_account)
+    end
+
+    def mute!(other_account)
+      mute_relationships.find_or_create_by!(target_account: other_account)
+    end
+
+    def mute_conversation!(conversation)
+      conversation_mutes.find_or_create_by!(conversation: conversation)
+    end
+
+    def block_domain!(other_domain)
+      domain_blocks.find_or_create_by!(domain: other_domain)
+    end
+
+    def unfollow!(other_account)
+      follow = active_relationships.find_by(target_account: other_account)
+      follow&.destroy
+    end
+
+    def unblock!(other_account)
+      block = block_relationships.find_by(target_account: other_account)
+      block&.destroy
+    end
+
+    def unmute!(other_account)
+      mute = mute_relationships.find_by(target_account: other_account)
+      mute&.destroy
+    end
+
+    def unmute_conversation!(conversation)
+      mute = conversation_mutes.find_by(conversation: conversation)
+      mute&.destroy!
+    end
+
+    def unblock_domain!(other_domain)
+      block = domain_blocks.find_by(domain: other_domain)
+      block&.destroy
+    end
+
+    def following?(other_account)
+      active_relationships.where(target_account: other_account).exists?
+    end
+
+    def blocking?(other_account)
+      block_relationships.where(target_account: other_account).exists?
+    end
+
+    def domain_blocking?(other_domain)
+      domain_blocks.where(domain: other_domain).exists?
+    end
+
+    def muting?(other_account)
+      mute_relationships.where(target_account: other_account).exists?
+    end
+
+    def muting_conversation?(conversation)
+      conversation_mutes.where(conversation: conversation).exists?
+    end
+
+    def requested?(other_account)
+      follow_requests.where(target_account: other_account).exists?
+    end
+
+    def favourited?(status)
+      status.proper.favourites.where(account: self).exists?
+    end
+
+    def reblogged?(status)
+      status.proper.reblogs.where(account: self).exists?
+    end
+  end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index fd1049116..760ecc928 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -67,7 +67,7 @@ class Status < ApplicationRecord
   scope :local_only, -> { left_outer_joins(:account).where(accounts: { domain: nil }) }
   scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) }
   scope :including_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: true }) }
-  scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
+  scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids, accounts: { domain: account.excluded_from_timeline_domains }) }
 
   cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
 
@@ -284,7 +284,9 @@ class Status < ApplicationRecord
   end
 
   def find_statuses_from_tree_path(ids, account)
-    statuses = Status.where(id: ids).to_a
+    statuses = Status.where(id: ids).includes(:account).to_a
+
+    # FIXME: n+1 bonanza
     statuses.reject! { |status| filter_from_context?(status, account) }
 
     # Order ancestors/descendants by tree path
@@ -292,6 +294,11 @@ class Status < ApplicationRecord
   end
 
   def filter_from_context?(status, account)
-    account&.blocking?(status.account_id) || account&.muting?(status.account_id) || (status.account.silenced? && !account&.following?(status.account_id)) || !status.permitted?(account)
+    should_filter   = account&.blocking?(status.account_id)
+    should_filter ||= account&.domain_blocking?(status.account.domain)
+    should_filter ||= account&.muting?(status.account_id)
+    should_filter ||= (status.account.silenced? && !account&.following?(status.account_id))
+    should_filter ||= !status.permitted?(account)
+    should_filter
   end
 end