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/concerns/account_interactions.rb10
-rw-r--r--app/models/custom_filter.rb28
-rw-r--r--app/models/custom_filter_status.rb37
-rw-r--r--app/models/form/status_filter_batch_action.rb34
4 files changed, 98 insertions, 11 deletions
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index a7401362f..9b358d338 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -249,15 +249,7 @@ module AccountInteractions
 
   def status_matches_filters(status)
     active_filters = CustomFilter.cached_filters_for(id)
-
-    filter_matches = active_filters.filter_map do |filter, rules|
-      next if rules[:keywords].blank?
-
-      match = rules[:keywords].match(status.proper.searchable_text)
-      FilterResultPresenter.new(filter: filter, keyword_matches: [match.to_s]) unless match.nil?
-    end
-
-    filter_matches
+    CustomFilter.apply_cached_filters(active_filters, status)
   end
 
   def followers_for_local_distribution
diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb
index 985eab125..da2a91493 100644
--- a/app/models/custom_filter.rb
+++ b/app/models/custom_filter.rb
@@ -34,6 +34,7 @@ class CustomFilter < ApplicationRecord
 
   belongs_to :account
   has_many :keywords, class_name: 'CustomFilterKeyword', foreign_key: :custom_filter_id, inverse_of: :custom_filter, dependent: :destroy
+  has_many :statuses, class_name: 'CustomFilterStatus', foreign_key: :custom_filter_id, inverse_of: :custom_filter, dependent: :destroy
   accepts_nested_attributes_for :keywords, reject_if: :all_blank, allow_destroy: true
 
   validates :title, :context, presence: true
@@ -62,8 +63,10 @@ class CustomFilter < ApplicationRecord
 
   def self.cached_filters_for(account_id)
     active_filters = Rails.cache.fetch("filters:v3:#{account_id}") do
+      filters_hash = {}
+
       scope = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account_id: account_id }).where(Arel.sql('expires_at IS NULL OR expires_at > NOW()'))
-      scope.to_a.group_by(&:custom_filter).map do |filter, keywords|
+      scope.to_a.group_by(&:custom_filter).each do |filter, keywords|
         keywords.map! do |keyword|
           if keyword.whole_word
             sb = /\A[[:word:]]/.match?(keyword.keyword) ? '\b' : ''
@@ -74,13 +77,34 @@ class CustomFilter < ApplicationRecord
             /#{Regexp.escape(keyword.keyword)}/i
           end
         end
-        [filter, { keywords: Regexp.union(keywords) }]
+
+        filters_hash[filter.id] = { keywords: Regexp.union(keywords), filter: filter }
+      end.to_h
+
+      scope = CustomFilterStatus.includes(:custom_filter).where(custom_filter: { account_id: account_id }).where(Arel.sql('expires_at IS NULL OR expires_at > NOW()'))
+      scope.to_a.group_by(&:custom_filter).each do |filter, statuses|
+        filters_hash[filter.id] ||= { filter: filter }
+        filters_hash[filter.id].merge!(status_ids: statuses.map(&:status_id))
       end
+
+      filters_hash.values.map { |cache| [cache.delete(:filter), cache] }
     end.to_a
 
     active_filters.select { |custom_filter, _| !custom_filter.expired? }
   end
 
+  def self.apply_cached_filters(cached_filters, status)
+    cached_filters.filter_map do |filter, rules|
+      match = rules[:keywords].match(status.proper.searchable_text) if rules[:keywords].present?
+      keyword_matches = [match.to_s] unless match.nil?
+
+      status_matches = [status.id, status.reblog_of_id].compact & rules[:status_ids] if rules[:status_ids].present?
+
+      next if keyword_matches.blank? && status_matches.blank?
+      FilterResultPresenter.new(filter: filter, keyword_matches: keyword_matches, status_matches: status_matches)
+    end
+  end
+
   def prepare_cache_invalidation!
     @should_invalidate_cache = true
   end
diff --git a/app/models/custom_filter_status.rb b/app/models/custom_filter_status.rb
new file mode 100644
index 000000000..b6bea1394
--- /dev/null
+++ b/app/models/custom_filter_status.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: custom_filter_statuses
+#
+#  id               :bigint(8)        not null, primary key
+#  custom_filter_id :bigint(8)        not null
+#  status_id        :bigint(8)        default(""), not null
+#  created_at       :datetime         not null
+#  updated_at       :datetime         not null
+#
+
+class CustomFilterStatus < ApplicationRecord
+  belongs_to :custom_filter
+  belongs_to :status
+
+  validates :status, uniqueness: { scope: :custom_filter }
+  validate :validate_status_access
+
+  before_save :prepare_cache_invalidation!
+  before_destroy :prepare_cache_invalidation!
+  after_commit :invalidate_cache!
+
+  private
+
+  def validate_status_access
+    errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show?
+  end
+
+  def prepare_cache_invalidation!
+    custom_filter.prepare_cache_invalidation!
+  end
+
+  def invalidate_cache!
+    custom_filter.invalidate_cache!
+  end
+end
diff --git a/app/models/form/status_filter_batch_action.rb b/app/models/form/status_filter_batch_action.rb
new file mode 100644
index 000000000..d87bd5cc4
--- /dev/null
+++ b/app/models/form/status_filter_batch_action.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class Form::StatusFilterBatchAction
+  include ActiveModel::Model
+  include AccountableConcern
+  include Authorization
+
+  attr_accessor :current_account, :type,
+                :status_filter_ids, :filter_id
+
+  def save!
+    process_action!
+  end
+
+  private
+
+  def status_filters
+    filter = current_account.custom_filters.find(filter_id)
+    filter.statuses.where(id: status_filter_ids)
+  end
+
+  def process_action!
+    return if status_filter_ids.empty?
+
+    case type
+    when 'remove'
+      handle_remove!
+    end
+  end
+
+  def handle_remove!
+    status_filters.destroy_all
+  end
+end