Change auto-following admin-selected accounts, show in recommendations (#16078)
This commit is contained in:
		
					parent
					
						
							
								863ae47b51
							
						
					
				
			
			
				commit
				
					
						daccc07dc1
					
				
			
		
					 16 changed files with 228 additions and 128 deletions
				
			
		|  | @ -5,20 +5,20 @@ class Api::V1::SuggestionsController < Api::BaseController | ||||||
| 
 | 
 | ||||||
|   before_action -> { doorkeeper_authorize! :read } |   before_action -> { doorkeeper_authorize! :read } | ||||||
|   before_action :require_user! |   before_action :require_user! | ||||||
|   before_action :set_accounts |  | ||||||
| 
 | 
 | ||||||
|   def index |   def index | ||||||
|     render json: @accounts, each_serializer: REST::AccountSerializer |     suggestions = suggestions_source.get(current_account, limit: limit_param(DEFAULT_ACCOUNTS_LIMIT)) | ||||||
|  |     render json: suggestions.map(&:account), each_serializer: REST::AccountSerializer | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def destroy |   def destroy | ||||||
|     PotentialFriendshipTracker.remove(current_account.id, params[:id]) |     suggestions_source.remove(current_account, params[:id]) | ||||||
|     render_empty |     render_empty | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def set_accounts |   def suggestions_source | ||||||
|     @accounts = PotentialFriendshipTracker.get(current_account, limit_param(DEFAULT_ACCOUNTS_LIMIT)) |     AccountSuggestions::PastInteractionsSource.new | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -27,15 +27,5 @@ class PotentialFriendshipTracker | ||||||
|     def remove(account_id, target_account_id) |     def remove(account_id, target_account_id) | ||||||
|       redis.zrem("interactions:#{account_id}", target_account_id) |       redis.zrem("interactions:#{account_id}", target_account_id) | ||||||
|     end |     end | ||||||
| 
 |  | ||||||
|     def get(account, limit) |  | ||||||
|       account_ids = redis.zrevrange("interactions:#{account.id}", 0, limit) |  | ||||||
| 
 |  | ||||||
|       return [] if account_ids.empty? || limit < 1 |  | ||||||
| 
 |  | ||||||
|       accounts = Account.searchable.where(id: account_ids).index_by(&:id) |  | ||||||
| 
 |  | ||||||
|       account_ids.map { |id| accounts[id.to_i] }.compact |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -1,17 +1,28 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class AccountSuggestions | class AccountSuggestions | ||||||
|   class Suggestion < ActiveModelSerializers::Model |   SOURCES = [ | ||||||
|     attributes :account, :source |     AccountSuggestions::SettingSource, | ||||||
|   end |     AccountSuggestions::PastInteractionsSource, | ||||||
|  |     AccountSuggestions::GlobalSource, | ||||||
|  |   ].freeze | ||||||
| 
 | 
 | ||||||
|   def self.get(account, limit) |   def self.get(account, limit) | ||||||
|     suggestions = PotentialFriendshipTracker.get(account, limit).map { |target_account| Suggestion.new(account: target_account, source: :past_interaction) } |     SOURCES.each_with_object([]) do |source_class, suggestions| | ||||||
|     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 |       source_suggestions = source_class.new.get( | ||||||
|     suggestions |         account, | ||||||
|  |         skip_account_ids: suggestions.map(&:account_id), | ||||||
|  |         limit: limit - suggestions.size | ||||||
|  |       ) | ||||||
|  | 
 | ||||||
|  |       suggestions.concat(source_suggestions) | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def self.remove(account, target_account_id) |   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 | ||||||
| end | end | ||||||
|  |  | ||||||
							
								
								
									
										37
									
								
								app/models/account_suggestions/global_source.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/models/account_suggestions/global_source.rb
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										36
									
								
								app/models/account_suggestions/past_interactions_source.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/models/account_suggestions/past_interactions_source.rb
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										68
									
								
								app/models/account_suggestions/setting_source.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								app/models/account_suggestions/setting_source.rb
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										34
									
								
								app/models/account_suggestions/source.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/models/account_suggestions/source.rb
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										7
									
								
								app/models/account_suggestions/suggestion.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/models/account_suggestions/suggestion.rb
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class AccountSuggestions::Suggestion < ActiveModelSerializers::Model | ||||||
|  |   attributes :account, :source | ||||||
|  | 
 | ||||||
|  |   delegate :id, to: :account, prefix: true | ||||||
|  | end | ||||||
|  | @ -21,19 +21,4 @@ class FollowRecommendation < ApplicationRecord | ||||||
|   def readonly? |   def readonly? | ||||||
|     true |     true | ||||||
|   end |   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 | end | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ class Form::AdminSettings | ||||||
|     open_deletion |     open_deletion | ||||||
|     timeline_preview |     timeline_preview | ||||||
|     show_staff_badge |     show_staff_badge | ||||||
|     enable_bootstrap_timeline_accounts |  | ||||||
|     bootstrap_timeline_accounts |     bootstrap_timeline_accounts | ||||||
|     theme |     theme | ||||||
|     min_invite_role |     min_invite_role | ||||||
|  | @ -41,7 +40,6 @@ class Form::AdminSettings | ||||||
|     open_deletion |     open_deletion | ||||||
|     timeline_preview |     timeline_preview | ||||||
|     show_staff_badge |     show_staff_badge | ||||||
|     enable_bootstrap_timeline_accounts |  | ||||||
|     activity_api_enabled |     activity_api_enabled | ||||||
|     peers_api_enabled |     peers_api_enabled | ||||||
|     show_known_fediverse_at_about_page |     show_known_fediverse_at_about_page | ||||||
|  |  | ||||||
|  | @ -5,48 +5,13 @@ class BootstrapTimelineService < BaseService | ||||||
|     @source_account = source_account |     @source_account = source_account | ||||||
| 
 | 
 | ||||||
|     autofollow_inviter! |     autofollow_inviter! | ||||||
|     autofollow_bootstrap_timeline_accounts! if Setting.enable_bootstrap_timeline_accounts |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def autofollow_inviter! |   def autofollow_inviter! | ||||||
|     return unless @source_account&.user&.invite&.autofollow? |     return unless @source_account&.user&.invite&.autofollow? | ||||||
|  | 
 | ||||||
|     FollowService.new.call(@source_account, @source_account.user.invite.user.account) |     FollowService.new.call(@source_account, @source_account.user.invite.user.account) | ||||||
|   end |   end | ||||||
| 
 |  | ||||||
|   def autofollow_bootstrap_timeline_accounts! |  | ||||||
|     bootstrap_timeline_accounts.each do |target_account| |  | ||||||
|       begin |  | ||||||
|         FollowService.new.call(@source_account, target_account) |  | ||||||
|       rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError |  | ||||||
|         nil |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def bootstrap_timeline_accounts |  | ||||||
|     return @bootstrap_timeline_accounts if defined?(@bootstrap_timeline_accounts) |  | ||||||
| 
 |  | ||||||
|     @bootstrap_timeline_accounts = bootstrap_timeline_accounts_usernames.empty? ? admin_accounts : local_unlocked_accounts(bootstrap_timeline_accounts_usernames) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def bootstrap_timeline_accounts_usernames |  | ||||||
|     @bootstrap_timeline_accounts_usernames ||= (Setting.bootstrap_timeline_accounts || '').split(',').map { |str| str.strip.gsub(/\A@/, '') }.reject(&:blank?) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def admin_accounts |  | ||||||
|     User.admins |  | ||||||
|         .includes(:account) |  | ||||||
|         .where(accounts: { locked: false }) |  | ||||||
|         .map(&:account) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def local_unlocked_accounts(usernames) |  | ||||||
|     Account.local |  | ||||||
|            .without_suspended |  | ||||||
|            .where(username: usernames) |  | ||||||
|            .where(locked: false) |  | ||||||
|            .where(moved_to_account_id: nil) |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -4,11 +4,25 @@ class ExistingUsernameValidator < ActiveModel::EachValidator | ||||||
|   def validate_each(record, attribute, value) |   def validate_each(record, attribute, value) | ||||||
|     return if value.blank? |     return if value.blank? | ||||||
| 
 | 
 | ||||||
|     if options[:multiple] |     usernames_and_domains = begin | ||||||
|       missing_usernames = value.split(',').map { |username| username.strip.gsub(/\A@/, '') }.filter_map { |username| username unless Account.find_local(username) } |       value.split(',').map do |str| | ||||||
|       record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any? |         username, domain = str.strip.gsub(/\A@/, '').split('@') | ||||||
|     else |         domain = nil if TagManager.instance.local_domain?(domain) | ||||||
|       record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value.strip.gsub(/\A@/, '')) | 
 | ||||||
|  |         next if username.blank? | ||||||
|  | 
 | ||||||
|  |         [str, username, domain] | ||||||
|  |       end.compact | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)| | ||||||
|  |       str unless Account.find_remote(username, domain) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     if usernames_with_no_accounts.any? && options[:multiple] | ||||||
|  |       record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: usernames_with_no_accounts.join(', '))) | ||||||
|  |     elsif usernames_with_no_accounts.any? || usernames_and_domains.size > 1 | ||||||
|  |       record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -50,10 +50,7 @@ | ||||||
|   %hr.spacer/ |   %hr.spacer/ | ||||||
| 
 | 
 | ||||||
|   .fields-group |   .fields-group | ||||||
|     = f.input :enable_bootstrap_timeline_accounts, as: :boolean, wrapper: :with_label, label: t('admin.settings.enable_bootstrap_timeline_accounts.title'), hint: t('admin.settings.enable_bootstrap_timeline_accounts.desc_html') |     = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html') | ||||||
| 
 |  | ||||||
|   .fields-group |  | ||||||
|     = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html'), disabled: !Setting.enable_bootstrap_timeline_accounts |  | ||||||
| 
 | 
 | ||||||
|   %hr.spacer/ |   %hr.spacer/ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -564,8 +564,8 @@ en: | ||||||
|         desc_html: Counts of locally published posts, active users, and new registrations in weekly buckets |         desc_html: Counts of locally published posts, active users, and new registrations in weekly buckets | ||||||
|         title: Publish aggregate statistics about user activity in the API |         title: Publish aggregate statistics about user activity in the API | ||||||
|       bootstrap_timeline_accounts: |       bootstrap_timeline_accounts: | ||||||
|         desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins. |         desc_html: Separate multiple usernames by comma. These accounts will be guaranteed to be shown in follow recommendations | ||||||
|         title: Default follows for new users |         title: Recommend these accounts to new users | ||||||
|       contact_information: |       contact_information: | ||||||
|         email: Business e-mail |         email: Business e-mail | ||||||
|         username: Contact username |         username: Contact username | ||||||
|  | @ -582,9 +582,6 @@ en: | ||||||
|         users: To logged-in local users |         users: To logged-in local users | ||||||
|       domain_blocks_rationale: |       domain_blocks_rationale: | ||||||
|         title: Show rationale |         title: Show rationale | ||||||
|       enable_bootstrap_timeline_accounts: |  | ||||||
|         desc_html: Make new users automatically follow configured accounts so their home feed doesn't start out empty |  | ||||||
|         title: Enable default follows for new users |  | ||||||
|       hero: |       hero: | ||||||
|         desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail |         desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail | ||||||
|         title: Hero image |         title: Hero image | ||||||
|  |  | ||||||
|  | @ -62,7 +62,6 @@ defaults: &defaults | ||||||
|     - mod |     - mod | ||||||
|     - moderator |     - moderator | ||||||
|   disallowed_hashtags: # space separated string or list of hashtags without the hash |   disallowed_hashtags: # space separated string or list of hashtags without the hash | ||||||
|   enable_bootstrap_timeline_accounts: true |  | ||||||
|   bootstrap_timeline_accounts: '' |   bootstrap_timeline_accounts: '' | ||||||
|   activity_api_enabled: true |   activity_api_enabled: true | ||||||
|   peers_api_enabled: true |   peers_api_enabled: true | ||||||
|  |  | ||||||
|  | @ -1,42 +1,4 @@ | ||||||
| require 'rails_helper' | require 'rails_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe BootstrapTimelineService, type: :service do | RSpec.describe BootstrapTimelineService, type: :service do | ||||||
|   subject { described_class.new } |  | ||||||
| 
 |  | ||||||
|   describe '#call' do |  | ||||||
|     let(:source_account) { Fabricate(:account) } |  | ||||||
| 
 |  | ||||||
|     context 'when setting is empty' do |  | ||||||
|       let!(:admin) { Fabricate(:user, admin: true) } |  | ||||||
| 
 |  | ||||||
|       before do |  | ||||||
|         Setting.bootstrap_timeline_accounts = nil |  | ||||||
|         subject.call(source_account) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'follows admin accounts from account' do |  | ||||||
|         expect(source_account.following?(admin.account)).to be true |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when setting is set' do |  | ||||||
|       let!(:alice) { Fabricate(:account, username: 'alice') } |  | ||||||
|       let!(:bob)   { Fabricate(:account, username: 'bob') } |  | ||||||
|       let!(:eve)   { Fabricate(:account, username: 'eve', suspended: true) } |  | ||||||
| 
 |  | ||||||
|       before do |  | ||||||
|         Setting.bootstrap_timeline_accounts = 'alice, @bob, eve, unknown' |  | ||||||
|         subject.call(source_account) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'follows found accounts from account' do |  | ||||||
|         expect(source_account.following?(alice)).to be true |  | ||||||
|         expect(source_account.following?(bob)).to be true |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'does not follow suspended account' do |  | ||||||
|         expect(source_account.following?(eve)).to be false |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue