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.rb30
-rw-r--r--app/models/concerns/account_interactions.rb13
-rw-r--r--app/models/concerns/attachmentable.rb2
-rw-r--r--app/models/concerns/expireable.rb24
-rw-r--r--app/models/custom_filter.rb56
-rw-r--r--app/models/form/admin_settings.rb2
-rw-r--r--app/models/invite.rb18
7 files changed, 100 insertions, 45 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 5099e4953..4abcd438a 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -102,6 +102,7 @@ class Account < ApplicationRecord
   has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id
 
   has_many :report_notes, dependent: :destroy
+  has_many :custom_filters, inverse_of: :account, dependent: :destroy
 
   # Moderation notes
   has_many :account_moderation_notes, dependent: :destroy
@@ -129,6 +130,7 @@ class Account < ApplicationRecord
   scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
   scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
+  scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) }
 
   delegate :email,
            :unconfirmed_email,
@@ -311,34 +313,6 @@ class Account < ApplicationRecord
       DeliveryFailureTracker.filter(urls)
     end
 
-    def triadic_closures(account, limit: 5, offset: 0)
-      sql = <<-SQL.squish
-        WITH first_degree AS (
-          SELECT target_account_id
-          FROM follows
-          WHERE account_id = :account_id
-        )
-        SELECT accounts.*
-        FROM follows
-        INNER JOIN accounts ON follows.target_account_id = accounts.id
-        WHERE
-          account_id IN (SELECT * FROM first_degree)
-          AND target_account_id NOT IN (SELECT * FROM first_degree)
-          AND target_account_id NOT IN (:excluded_account_ids)
-          AND accounts.suspended = false
-        GROUP BY target_account_id, accounts.id
-        ORDER BY count(account_id) DESC
-        OFFSET :offset
-        LIMIT :limit
-      SQL
-
-      excluded_account_ids = account.excluded_from_timeline_account_ids + [account.id]
-
-      find_by_sql(
-        [sql, { account_id: account.id, excluded_account_ids: excluded_account_ids, limit: limit, offset: offset }]
-      )
-    end
-
     def search_for(terms, limit = 10)
       textsearch, query = generate_query_for_search(terms)
 
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index d067415fd..cacee54e0 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -89,10 +89,13 @@ module AccountInteractions
                               .find_or_create_by!(target_account: other_account)
 
     rel.update!(show_reblogs: reblogs)
+    remove_potential_friendship(other_account)
+
     rel
   end
 
   def block!(other_account, uri: nil)
+    remove_potential_friendship(other_account)
     block_relationships.create_with(uri: uri)
                        .find_or_create_by!(target_account: other_account)
   end
@@ -100,10 +103,13 @@ module AccountInteractions
   def mute!(other_account, notifications: nil)
     notifications = true if notifications.nil?
     mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account)
+    remove_potential_friendship(other_account)
+
     # When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
     if mute.hide_notifications? != notifications
       mute.update!(hide_notifications: notifications)
     end
+
     mute
   end
 
@@ -198,4 +204,11 @@ module AccountInteractions
     lists.joins(account: :user)
          .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
   end
+
+  private
+
+  def remove_potential_friendship(other_account, mutual = false)
+    PotentialFriendshipTracker.remove(id, other_account.id)
+    PotentialFriendshipTracker.remove(other_account.id, id) if mutual
+  end
 end
diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb
index 44bdfa39a..de4cf8775 100644
--- a/app/models/concerns/attachmentable.rb
+++ b/app/models/concerns/attachmentable.rb
@@ -28,7 +28,7 @@ module Attachmentable
     self.class.attachment_definitions.each_key do |attachment_name|
       attachment = send(attachment_name)
 
-      next if attachment.blank? || !attachment.content_type.match?(/image.*/) || attachment.queued_for_write[:original].blank?
+      next if attachment.blank? || !/image.*/.match?(attachment.content_type) || attachment.queued_for_write[:original].blank?
 
       width, height = FastImage.size(attachment.queued_for_write[:original].path)
 
diff --git a/app/models/concerns/expireable.rb b/app/models/concerns/expireable.rb
new file mode 100644
index 000000000..444ccdfdb
--- /dev/null
+++ b/app/models/concerns/expireable.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Expireable
+  extend ActiveSupport::Concern
+
+  included do
+    scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
+
+    attr_reader :expires_in
+
+    def expires_in=(interval)
+      self.expires_at = interval.to_i.seconds.from_now unless interval.blank?
+      @expires_in     = interval
+    end
+
+    def expire!
+      touch(:expires_at)
+    end
+
+    def expired?
+      !expires_at.nil? && expires_at < Time.now.utc
+    end
+  end
+end
diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb
new file mode 100644
index 000000000..342207a55
--- /dev/null
+++ b/app/models/custom_filter.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: custom_filters
+#
+#  id           :bigint(8)        not null, primary key
+#  account_id   :bigint(8)
+#  expires_at   :datetime
+#  phrase       :text             default(""), not null
+#  context      :string           default([]), not null, is an Array
+#  whole_word   :boolean          default(TRUE), not null
+#  irreversible :boolean          default(FALSE), not null
+#  created_at   :datetime         not null
+#  updated_at   :datetime         not null
+#
+
+class CustomFilter < ApplicationRecord
+  VALID_CONTEXTS = %w(
+    home
+    notifications
+    public
+    thread
+  ).freeze
+
+  include Expireable
+
+  belongs_to :account
+
+  validates :phrase, :context, presence: true
+  validate :context_must_be_valid
+  validate :irreversible_must_be_within_context
+
+  scope :active_irreversible, -> { where(irreversible: true).where(Arel.sql('expires_at IS NULL OR expires_at > NOW()')) }
+
+  before_validation :clean_up_contexts
+  after_commit :remove_cache
+
+  private
+
+  def clean_up_contexts
+    self.context = Array(context).map(&:strip).map(&:presence).compact
+  end
+
+  def remove_cache
+    Rails.cache.delete("filters:#{account_id}")
+    Redis.current.publish("timeline:#{account_id}", Oj.dump(event: :filters_changed))
+  end
+
+  def context_must_be_valid
+    errors.add(:context, I18n.t('filters.errors.invalid_context')) if context.empty? || context.any? { |c| !VALID_CONTEXTS.include?(c) }
+  end
+
+  def irreversible_must_be_within_context
+    errors.add(:irreversible, I18n.t('filters.errors.invalid_irreversible')) if irreversible? && !context.include?('home') && !context.include?('notifications')
+  end
+end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 32922e7f1..723480bdd 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -36,6 +36,8 @@ class Form::AdminSettings
     :peers_api_enabled=,
     :show_known_fediverse_at_about_page,
     :show_known_fediverse_at_about_page=,
+    :preview_sensitive_media,
+    :preview_sensitive_media=,
     to: Setting
   )
 end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index d0cc427c4..fe2322462 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -15,33 +15,19 @@
 #
 
 class Invite < ApplicationRecord
+  include Expireable
+
   belongs_to :user
   has_many :users, inverse_of: :invite
 
   scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }
-  scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
 
   before_validation :set_code
 
-  attr_reader :expires_in
-
-  def expires_in=(interval)
-    self.expires_at = interval.to_i.seconds.from_now unless interval.blank?
-    @expires_in     = interval
-  end
-
   def valid_for_use?
     (max_uses.nil? || uses < max_uses) && !expired?
   end
 
-  def expire!
-    touch(:expires_at)
-  end
-
-  def expired?
-    !expires_at.nil? && expires_at < Time.now.utc
-  end
-
   private
 
   def set_code