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.rb1
-rw-r--r--app/models/account_filter.rb27
-rw-r--r--app/models/announcement.rb4
-rw-r--r--app/models/concerns/account_interactions.rb16
-rw-r--r--app/models/concerns/rate_limitable.rb36
-rw-r--r--app/models/follow.rb3
-rw-r--r--app/models/follow_request.rb3
-rw-r--r--app/models/media_attachment.rb1
-rw-r--r--app/models/status.rb18
9 files changed, 102 insertions, 7 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 0fcf897c9..73421ee5a 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -106,6 +106,7 @@ class Account < ApplicationRecord
   scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
   scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
   scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
+  scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
   scope :popular, -> { order('account_stats.followers_count desc') }
   scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
   scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index c7bf07787..7b6012e0f 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -14,6 +14,7 @@ class AccountFilter
     email
     ip
     staff
+    order
   ).freeze
 
   attr_reader :params
@@ -24,7 +25,7 @@ class AccountFilter
   end
 
   def results
-    scope = Account.recent.includes(:user)
+    scope = Account.includes(:user).reorder(nil)
 
     params.each do |key, value|
       scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
@@ -38,6 +39,7 @@ class AccountFilter
   def set_defaults!
     params['local']  = '1' if params['remote'].blank?
     params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank? && params['pending'].blank?
+    params['order']  = 'recent' if params['order'].blank?
   end
 
   def scope_for(key, value)
@@ -51,9 +53,9 @@ class AccountFilter
     when 'active'
       Account.without_suspended
     when 'pending'
-      accounts_with_users.merge User.pending
+      accounts_with_users.merge(User.pending)
     when 'disabled'
-      accounts_with_users.merge User.disabled
+      accounts_with_users.merge(User.disabled)
     when 'silenced'
       Account.silenced
     when 'suspended'
@@ -63,16 +65,31 @@ class AccountFilter
     when 'display_name'
       Account.matches_display_name(value)
     when 'email'
-      accounts_with_users.merge User.matches_email(value)
+      accounts_with_users.merge(User.matches_email(value))
     when 'ip'
       valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value)) : Account.none
     when 'staff'
-      accounts_with_users.merge User.staff
+      accounts_with_users.merge(User.staff)
+    when 'order'
+      order_scope(value)
     else
       raise "Unknown filter: #{key}"
     end
   end
 
+  def order_scope(value)
+    case value
+    when 'active'
+      params['remote'] ? Account.joins(:account_stat).by_recent_status : Account.joins(:user).by_recent_sign_in
+    when 'recent'
+      Account.recent
+    when 'alphabetic'
+      Account.alphabetic
+    else
+      raise "Unknown order: #{value}"
+    end
+  end
+
   def accounts_with_users
     Account.joins(:user)
   end
diff --git a/app/models/announcement.rb b/app/models/announcement.rb
index d99502f44..f8ac4e09d 100644
--- a/app/models/announcement.rb
+++ b/app/models/announcement.rb
@@ -48,6 +48,10 @@ class Announcement < ApplicationRecord
     @mentions ||= Account.from_text(text)
   end
 
+  def statuses
+    @statuses ||= Status.from_text(text)
+  end
+
   def tags
     @tags ||= Tag.find_or_create_by_names(Extractor.extract_hashtags(text))
   end
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index 14bcf7bb1..32fcb5397 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -87,10 +87,10 @@ module AccountInteractions
     has_many :announcement_mutes, dependent: :destroy
   end
 
-  def follow!(other_account, reblogs: nil, uri: nil)
+  def follow!(other_account, reblogs: nil, uri: nil, rate_limit: false)
     reblogs = true if reblogs.nil?
 
-    rel = active_relationships.create_with(show_reblogs: reblogs, uri: uri)
+    rel = active_relationships.create_with(show_reblogs: reblogs, uri: uri, rate_limit: rate_limit)
                               .find_or_create_by!(target_account: other_account)
 
     rel.update!(show_reblogs: reblogs)
@@ -99,6 +99,18 @@ module AccountInteractions
     rel
   end
 
+  def request_follow!(other_account, reblogs: nil, uri: nil, rate_limit: false)
+    reblogs = true if reblogs.nil?
+
+    rel = follow_requests.create_with(show_reblogs: reblogs, uri: uri, rate_limit: rate_limit)
+                         .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)
diff --git a/app/models/concerns/rate_limitable.rb b/app/models/concerns/rate_limitable.rb
new file mode 100644
index 000000000..ad1b5e44e
--- /dev/null
+++ b/app/models/concerns/rate_limitable.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module RateLimitable
+  extend ActiveSupport::Concern
+
+  def rate_limit=(value)
+    @rate_limit = value
+  end
+
+  def rate_limit?
+    @rate_limit
+  end
+
+  def rate_limiter(by, options = {})
+    return @rate_limiter if defined?(@rate_limiter)
+
+    @rate_limiter = RateLimiter.new(by, options)
+  end
+
+  class_methods do
+    def rate_limit(options = {})
+      after_create do
+        by = public_send(options[:by])
+
+        if rate_limit? && by&.local?
+          rate_limiter(by, options).record!
+          @rate_limit_recorded = true
+        end
+      end
+
+      after_rollback do
+        rate_limiter(public_send(options[:by]), options).rollback! if @rate_limit_recorded
+      end
+    end
+  end
+end
diff --git a/app/models/follow.rb b/app/models/follow.rb
index 87fa11425..f3e48a2ed 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -15,6 +15,9 @@
 class Follow < ApplicationRecord
   include Paginable
   include RelationshipCacheable
+  include RateLimitable
+
+  rate_limit by: :account, family: :follows
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index 96ac7eaa5..3325e264c 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -15,6 +15,9 @@
 class FollowRequest < ApplicationRecord
   include Paginable
   include RelationshipCacheable
+  include RateLimitable
+
+  rate_limit by: :account, family: :follows
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 6a0b892f6..2813d9200 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -170,6 +170,7 @@ class MediaAttachment < ApplicationRecord
 
   def variant?(other_file_name)
     return true if file_file_name == other_file_name
+    return false if file_file_name.nil?
 
     formats = file.styles.values.map(&:format).compact
 
diff --git a/app/models/status.rb b/app/models/status.rb
index f4284f771..b2d3c3f3b 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -35,6 +35,9 @@ class Status < ApplicationRecord
   include Paginable
   include Cacheable
   include StatusThreadingConcern
+  include RateLimitable
+
+  rate_limit by: :account, family: :statuses
 
   self.discard_column = :deleted_at
 
@@ -416,6 +419,21 @@ class Status < ApplicationRecord
       end
     end
 
+    def from_text(text)
+      return [] if text.blank?
+
+      text.scan(FetchLinkCardService::URL_PATTERN).map(&:first).uniq.map do |url|
+        status = begin
+          if TagManager.instance.local_url?(url)
+            ActivityPub::TagManager.instance.uri_to_resource(url, Status)
+          else
+            Status.find_by(uri: url) || Status.find_by(url: url)
+          end
+        end
+        status&.distributable? ? status : nil
+      end.compact
+    end
+
     private
 
     def timeline_scope(local_only = false)