about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2018-07-10 10:52:18 +0200
committerGitHub <noreply@github.com>2018-07-10 10:52:18 +0200
commite6647ccf6d13030a91c7e635526e1e40a059ce31 (patch)
treeeda07c40c934ab9a55d7847afe4af3e54bce86b8 /app/models
parentc699b2d141d7aa910bd81ae5fe881ecec7039395 (diff)
parent402da8065c2b378cca6361f2c7252bd766f25dde (diff)
Merge pull request #557 from ThibG/glitch-soc/tentative-merge
Merge upstream changes
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/glitch.rb7
-rw-r--r--app/models/glitch/keyword_mute.rb123
-rw-r--r--app/models/glitch/keyword_mute_helper.rb27
-rw-r--r--app/models/invite.rb18
10 files changed, 100 insertions, 202 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/glitch.rb b/app/models/glitch.rb
deleted file mode 100644
index 0e497babc..000000000
--- a/app/models/glitch.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module Glitch
-  def self.table_name_prefix
-    'glitch_'
-  end
-end
diff --git a/app/models/glitch/keyword_mute.rb b/app/models/glitch/keyword_mute.rb
deleted file mode 100644
index 49769cb73..000000000
--- a/app/models/glitch/keyword_mute.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-# frozen_string_literal: true
-# == Schema Information
-#
-# Table name: glitch_keyword_mutes
-#
-#  id                :bigint(8)        not null, primary key
-#  account_id        :bigint(8)        not null
-#  keyword           :string           not null
-#  whole_word        :boolean          default(TRUE), not null
-#  created_at        :datetime         not null
-#  updated_at        :datetime         not null
-#  apply_to_mentions :boolean          default(TRUE), not null
-#
-
-class Glitch::KeywordMute < ApplicationRecord
-  belongs_to :account, required: true
-
-  validates_presence_of :keyword
-
-  after_commit :invalidate_cached_matchers
-
-  module Scopes
-    Unscoped = 0b00
-    HomeFeed = 0b01
-    Mentions = 0b10
-  end
-
-  def self.text_matcher_for(account_id)
-    TextMatcher.new(account_id)
-  end
-
-  def self.tag_matcher_for(account_id)
-    TagMatcher.new(account_id)
-  end
-
-  def scope
-    s = Scopes::Unscoped
-    s |= Scopes::HomeFeed
-    s |= Scopes::Mentions if apply_to_mentions?
-    s
-  end
-
-  private
-
-  def invalidate_cached_matchers
-    Rails.cache.delete(TextMatcher.cache_key(account_id))
-    Rails.cache.delete(TagMatcher.cache_key(account_id))
-  end
-
-  class CachedKeywordMute
-    attr_reader :keyword
-    attr_reader :whole_word
-    attr_reader :scope
-
-    def initialize(keyword, whole_word, scope)
-      @keyword = keyword
-      @whole_word = whole_word
-      @scope = scope
-    end
-
-    def boundary_regex_for_keyword
-      sb = keyword =~ /\A[[:word:]]/ ? '\b' : ''
-      eb = keyword =~ /[[:word:]]\Z/ ? '\b' : ''
-
-      /(?mix:#{sb}#{Regexp.escape(keyword)}#{eb})/
-    end
-
-    def matches?(str, required_scope)
-      ((required_scope & scope) == required_scope) && \
-        str =~ (whole_word ? boundary_regex_for_keyword : /#{Regexp.escape(keyword)}/i)
-    end
-  end
-
-  class Matcher
-    attr_reader :account_id
-    attr_reader :keywords
-
-    def initialize(account_id)
-      @account_id = account_id
-      @keywords = Rails.cache.fetch(self.class.cache_key(account_id)) { fetch_keywords }
-    end
-
-    protected
-
-    def fetch_keywords
-      Glitch::KeywordMute.select(:whole_word, :keyword, :apply_to_mentions)
-        .where(account_id: account_id)
-        .map { |kw| CachedKeywordMute.new(transform_keyword(kw.keyword), kw.whole_word, kw.scope) }
-    end
-
-    def transform_keyword(keyword)
-      keyword
-    end
-  end
-
-  class TextMatcher < Matcher
-    def self.cache_key(account_id)
-      format('keyword_mutes:regex:text:%s', account_id)
-    end
-
-    def matches?(str, scope)
-      keywords.any? { |kw| kw.matches?(str, scope) }
-    end
-  end
-
-  class TagMatcher < Matcher
-    def self.cache_key(account_id)
-      format('keyword_mutes:regex:tag:%s', account_id)
-    end
-
-    def matches?(tags, scope)
-      tags.pluck(:name).any? do |n|
-        keywords.any? { |kw| kw.matches?(n, scope) }
-      end
-    end
-
-    protected
-
-    def transform_keyword(kw)
-      Tag::HASHTAG_RE =~ kw ? $1 : kw
-    end
-  end
-end
diff --git a/app/models/glitch/keyword_mute_helper.rb b/app/models/glitch/keyword_mute_helper.rb
deleted file mode 100644
index 955c3b1f3..000000000
--- a/app/models/glitch/keyword_mute_helper.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'html2text'
-
-class Glitch::KeywordMuteHelper
-  attr_reader :text_matcher
-  attr_reader :tag_matcher
-
-  def initialize(receiver_id)
-    @text_matcher   = Glitch::KeywordMute.text_matcher_for(receiver_id)
-    @tag_matcher    = Glitch::KeywordMute.tag_matcher_for(receiver_id)
-  end
-
-  def matches?(status, scope)
-    matchers_match?(status, scope) || (status.reblog? && matchers_match?(status.reblog, scope))
-  end
-
-  private
-
-  def matchers_match?(status, scope)
-    text_matcher.matches?(prepare_text(status.text), scope) ||
-      text_matcher.matches?(prepare_text(status.spoiler_text), scope) ||
-      tag_matcher.matches?(status.tags, scope)
-  end
-
-  def prepare_text(text)
-    Html2Text.convert(text)
-  end
-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