about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2021-04-24 17:01:43 +0200
committerGitHub <noreply@github.com>2021-04-24 17:01:43 +0200
commitdaccc07dc170627b17564402296f6c8631d0cd97 (patch)
treebb1fea8fde8f44b622b9b39cff46026689dc30ca /app/models
parent863ae47b5145e53c6cc820bd7eff0efd41339e03 (diff)
Change auto-following admin-selected accounts, show in recommendations (#16078)
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account_suggestions.rb25
-rw-r--r--app/models/account_suggestions/global_source.rb37
-rw-r--r--app/models/account_suggestions/past_interactions_source.rb36
-rw-r--r--app/models/account_suggestions/setting_source.rb68
-rw-r--r--app/models/account_suggestions/source.rb34
-rw-r--r--app/models/account_suggestions/suggestion.rb7
-rw-r--r--app/models/follow_recommendation.rb15
-rw-r--r--app/models/form/admin_settings.rb2
8 files changed, 200 insertions, 24 deletions
diff --git a/app/models/account_suggestions.rb b/app/models/account_suggestions.rb
index 7fe9d618e..d1774e62f 100644
--- a/app/models/account_suggestions.rb
+++ b/app/models/account_suggestions.rb
@@ -1,17 +1,28 @@
 # frozen_string_literal: true
 
 class AccountSuggestions
-  class Suggestion < ActiveModelSerializers::Model
-    attributes :account, :source
-  end
+  SOURCES = [
+    AccountSuggestions::SettingSource,
+    AccountSuggestions::PastInteractionsSource,
+    AccountSuggestions::GlobalSource,
+  ].freeze
 
   def self.get(account, limit)
-    suggestions = PotentialFriendshipTracker.get(account, limit).map { |target_account| Suggestion.new(account: target_account, source: :past_interaction) }
-    suggestions.concat(FollowRecommendation.get(account, limit - suggestions.size, suggestions.map { |suggestion| suggestion.account.id }).map { |target_account| Suggestion.new(account: target_account, source: :global) }) if suggestions.size < limit
-    suggestions
+    SOURCES.each_with_object([]) do |source_class, suggestions|
+      source_suggestions = source_class.new.get(
+        account,
+        skip_account_ids: suggestions.map(&:account_id),
+        limit: limit - suggestions.size
+      )
+
+      suggestions.concat(source_suggestions)
+    end
   end
 
   def self.remove(account, target_account_id)
-    PotentialFriendshipTracker.remove(account.id, target_account_id)
+    SOURCES.each do |source_class|
+      source = source_class.new
+      source.remove(account, target_account_id)
+    end
   end
 end
diff --git a/app/models/account_suggestions/global_source.rb b/app/models/account_suggestions/global_source.rb
new file mode 100644
index 000000000..ac764de50
--- /dev/null
+++ b/app/models/account_suggestions/global_source.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::GlobalSource < AccountSuggestions::Source
+  def key
+    :global
+  end
+
+  def get(account, skip_account_ids: [], limit: 40)
+    account_ids = account_ids_for_locale(account.user_locale) - [account.id] - skip_account_ids
+
+    as_ordered_suggestions(
+      scope(account).where(id: account_ids),
+      account_ids
+    ).take(limit)
+  end
+
+  def remove(_account, _target_account_id)
+    nil
+  end
+
+  private
+
+  def scope(account)
+    Account.searchable
+           .followable_by(account)
+           .not_excluded_by_account(account)
+           .not_domain_blocked_by_account(account)
+  end
+
+  def account_ids_for_locale(locale)
+    Redis.current.zrevrange("follow_recommendations:#{locale}", 0, -1).map(&:to_i)
+  end
+
+  def to_ordered_list_key(account)
+    account.id
+  end
+end
diff --git a/app/models/account_suggestions/past_interactions_source.rb b/app/models/account_suggestions/past_interactions_source.rb
new file mode 100644
index 000000000..d169394f1
--- /dev/null
+++ b/app/models/account_suggestions/past_interactions_source.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::PastInteractionsSource < AccountSuggestions::Source
+  include Redisable
+
+  def key
+    :past_interactions
+  end
+
+  def get(account, skip_account_ids: [], limit: 40)
+    account_ids = account_ids_for_account(account.id, limit + skip_account_ids.size) - skip_account_ids
+
+    as_ordered_suggestions(
+      scope.where(id: account_ids),
+      account_ids
+    ).take(limit)
+  end
+
+  def remove(account, target_account_id)
+    redis.zrem("interactions:#{account.id}", target_account_id)
+  end
+
+  private
+
+  def scope
+    Account.searchable
+  end
+
+  def account_ids_for_account(account_id, limit)
+    redis.zrevrange("interactions:#{account_id}", 0, limit).map(&:to_i)
+  end
+
+  def to_ordered_list_key(account)
+    account.id
+  end
+end
diff --git a/app/models/account_suggestions/setting_source.rb b/app/models/account_suggestions/setting_source.rb
new file mode 100644
index 000000000..be9eff233
--- /dev/null
+++ b/app/models/account_suggestions/setting_source.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::SettingSource < AccountSuggestions::Source
+  def key
+    :staff
+  end
+
+  def get(account, skip_account_ids: [], limit: 40)
+    return [] unless setting_enabled?
+
+    as_ordered_suggestions(
+      scope(account).where(setting_to_where_condition).where.not(id: skip_account_ids),
+      usernames_and_domains
+    ).take(limit)
+  end
+
+  def remove(_account, _target_account_id)
+    nil
+  end
+
+  private
+
+  def scope(account)
+    Account.searchable
+           .followable_by(account)
+           .not_excluded_by_account(account)
+           .not_domain_blocked_by_account(account)
+           .where(locked: false)
+           .where.not(id: account.id)
+  end
+
+  def usernames_and_domains
+    @usernames_and_domains ||= setting_to_usernames_and_domains
+  end
+
+  def setting_enabled?
+    setting.present?
+  end
+
+  def setting_to_where_condition
+    usernames_and_domains.map do |(username, domain)|
+      Arel::Nodes::Grouping.new(
+        Account.arel_table[:username].lower.eq(username.downcase).and(
+          Account.arel_table[:domain].lower.eq(domain&.downcase)
+        )
+      )
+    end.reduce(:or)
+  end
+
+  def setting_to_usernames_and_domains
+    setting.split(',').map do |str|
+      username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
+      domain           = nil if TagManager.instance.local_domain?(domain)
+
+      next if username.blank?
+
+      [username, domain]
+    end.compact
+  end
+
+  def setting
+    Setting.bootstrap_timeline_accounts
+  end
+
+  def to_ordered_list_key(account)
+    [account.username, account.domain]
+  end
+end
diff --git a/app/models/account_suggestions/source.rb b/app/models/account_suggestions/source.rb
new file mode 100644
index 000000000..bd1068d20
--- /dev/null
+++ b/app/models/account_suggestions/source.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::Source
+  def key
+    raise NotImplementedError
+  end
+
+  def get(_account, **kwargs)
+    raise NotImplementedError
+  end
+
+  def remove(_account, target_account_id)
+    raise NotImplementedError
+  end
+
+  protected
+
+  def as_ordered_suggestions(scope, ordered_list)
+    return [] if ordered_list.empty?
+
+    map = scope.index_by(&method(:to_ordered_list_key))
+
+    ordered_list.map { |ordered_list_key| map[ordered_list_key] }.compact.map do |account|
+      AccountSuggestions::Suggestion.new(
+        account: account,
+        source: key
+      )
+    end
+  end
+
+  def to_ordered_list_key(_account)
+    raise NotImplementedError
+  end
+end
diff --git a/app/models/account_suggestions/suggestion.rb b/app/models/account_suggestions/suggestion.rb
new file mode 100644
index 000000000..2c6f4d27f
--- /dev/null
+++ b/app/models/account_suggestions/suggestion.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AccountSuggestions::Suggestion < ActiveModelSerializers::Model
+  attributes :account, :source
+
+  delegate :id, to: :account, prefix: true
+end
diff --git a/app/models/follow_recommendation.rb b/app/models/follow_recommendation.rb
index c4355224d..6670b6560 100644
--- a/app/models/follow_recommendation.rb
+++ b/app/models/follow_recommendation.rb
@@ -21,19 +21,4 @@ class FollowRecommendation < ApplicationRecord
   def readonly?
     true
   end
-
-  def self.get(account, limit, exclude_account_ids = [])
-    account_ids = Redis.current.zrevrange("follow_recommendations:#{account.user_locale}", 0, -1).map(&:to_i) - exclude_account_ids - [account.id]
-
-    return [] if account_ids.empty? || limit < 1
-
-    accounts = Account.followable_by(account)
-                      .not_excluded_by_account(account)
-                      .not_domain_blocked_by_account(account)
-                      .where(id: account_ids)
-                      .limit(limit)
-                      .index_by(&:id)
-
-    account_ids.map { |id| accounts[id] }.compact
-  end
 end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index b5c3dcdbe..6fc7c56fd 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -16,7 +16,6 @@ class Form::AdminSettings
     open_deletion
     timeline_preview
     show_staff_badge
-    enable_bootstrap_timeline_accounts
     bootstrap_timeline_accounts
     theme
     min_invite_role
@@ -41,7 +40,6 @@ class Form::AdminSettings
     open_deletion
     timeline_preview
     show_staff_badge
-    enable_bootstrap_timeline_accounts
     activity_api_enabled
     peers_api_enabled
     show_known_fediverse_at_about_page