commit: 7d7844a47fdfb8862847f025dc4c1b1bc5fdafe5
parent: f2cbfb2eb375cde302461a91e5fb0a3af8f6eeb5
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 10 Sep 2017 09:58:38 +0200
Default follows for new users (#4871)
When a new user confirms their e-mail, bootstrap their home timeline
by automatically following a set of accounts. By default, all local
admin accounts (that are unlocked). Can be customized by new admin
setting (comma-separated usernames, local and unlocked only)
Diffstat:
11 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
@@ -13,6 +13,7 @@ module Admin
closed_registrations_message
open_deletion
timeline_preview
+ bootstrap_timeline_accounts
).freeze
BOOLEAN_SETTINGS = %w(
diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb
@@ -2,4 +2,10 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
+
+ def show
+ super do |user|
+ BootstrapTimelineWorker.perform_async(user.account_id) if user.errors.empty?
+ end
+ end
end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
@@ -24,6 +24,8 @@ class Form::AdminSettings
:open_deletion=,
:timeline_preview,
:timeline_preview=,
+ :bootstrap_timeline_accounts,
+ :bootstrap_timeline_accounts=,
to: Setting
)
end
diff --git a/app/services/bootstrap_timeline_service.rb b/app/services/bootstrap_timeline_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class BootstrapTimelineService < BaseService
+ def call(source_account)
+ bootstrap_timeline_accounts.each do |target_account|
+ FollowService.new.call(source_account, target_account)
+ end
+ end
+
+ private
+
+ 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
+ .where(username: usernames)
+ .where(locked: false)
+ end
+end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
@@ -5,9 +5,9 @@ class FollowService < BaseService
# Follow a remote user, notify remote user about the follow
# @param [Account] source_account From which to follow
- # @param [String] uri User URI to follow in the form of username@domain
+ # @param [String, Account] uri User URI to follow in the form of username@domain (or account record)
def call(source_account, uri)
- target_account = ResolveRemoteAccountService.new.call(uri)
+ target_account = uri.is_a?(Account) ? uri : ResolveRemoteAccountService.new.call(uri)
raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account)
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
@@ -28,5 +28,10 @@
= f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 }
= f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
+ %hr/
+
+ .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')
+
.actions
= f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/workers/bootstrap_timeline_worker.rb b/app/workers/bootstrap_timeline_worker.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class BootstrapTimelineWorker
+ include Sidekiq::Worker
+
+ def perform(account_id)
+ BootstrapTimelineService.new.call(Account.find(account_id))
+ end
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
@@ -167,6 +167,9 @@ en:
unresolved: Unresolved
view: View
settings:
+ bootstrap_timeline_accounts:
+ desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins.
+ title: Default follows for new users
contact_information:
email: Business e-mail
username: Contact username
diff --git a/config/settings.yml b/config/settings.yml
@@ -41,6 +41,7 @@ defaults: &defaults
- root
- webmaster
- administrator
+ bootstrap_timeline_accounts: ''
development:
<<: *defaults
diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb
@@ -10,4 +10,22 @@ describe Auth::ConfirmationsController, type: :controller do
expect(response).to have_http_status(:success)
end
end
+
+ describe 'GET #show' do
+ let!(:user) { Fabricate(:user, confirmation_token: 'foobar', confirmed_at: nil) }
+
+ before do
+ allow(BootstrapTimelineWorker).to receive(:perform_async)
+ @request.env['devise.mapping'] = Devise.mappings[:user]
+ get :show, params: { confirmation_token: 'foobar' }
+ end
+
+ it 'redirects to login' do
+ expect(response).to redirect_to(new_user_session_path)
+ end
+
+ it 'queues up bootstrapping of home timeline' do
+ expect(BootstrapTimelineWorker).to have_received(:perform_async).with(user.account_id)
+ end
+ end
end
diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+RSpec.describe BootstrapTimelineService 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') }
+
+ before do
+ Setting.bootstrap_timeline_accounts = 'alice, bob'
+ 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
+ end
+ end
+end