Skip to content

Commit ac3fd84

Browse files
Gargronhiyuki2578
authored andcommitted
Add account migration UI (mastodon#11846)
Fix mastodon#10736 - Change data export to be available for non-functional accounts - Change non-functional accounts to include redirecting accounts
1 parent aa1ac2d commit ac3fd84

31 files changed

Lines changed: 542 additions & 73 deletions

app/controllers/concerns/export_controller_concern.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ module ExportControllerConcern
55

66
included do
77
before_action :authenticate_user!
8+
before_action :require_not_suspended!
89
before_action :load_export
10+
11+
skip_before_action :require_functional!
912
end
1013

1114
private
@@ -27,4 +30,8 @@ def export_data
2730
def export_filename
2831
"#{controller_name}.csv"
2932
end
33+
34+
def require_not_suspended!
35+
forbidden if current_account.suspended?
36+
end
3037
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
class Settings::AliasesController < Settings::BaseController
4+
layout 'admin'
5+
6+
before_action :authenticate_user!
7+
before_action :set_aliases, except: :destroy
8+
before_action :set_alias, only: :destroy
9+
10+
def index
11+
@alias = current_account.aliases.build
12+
end
13+
14+
def create
15+
@alias = current_account.aliases.build(resource_params)
16+
17+
if @alias.save
18+
redirect_to settings_aliases_path, notice: I18n.t('aliases.created_msg')
19+
else
20+
render :show
21+
end
22+
end
23+
24+
def destroy
25+
@alias.destroy!
26+
redirect_to settings_aliases_path, notice: I18n.t('aliases.deleted_msg')
27+
end
28+
29+
private
30+
31+
def resource_params
32+
params.require(:account_alias).permit(:acct)
33+
end
34+
35+
def set_alias
36+
@alias = current_account.aliases.find(params[:id])
37+
end
38+
39+
def set_aliases
40+
@aliases = current_account.aliases.order(id: :desc).reject(&:new_record?)
41+
end
42+
end

app/controllers/settings/exports_controller.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ class Settings::ExportsController < Settings::BaseController
66
layout 'admin'
77

88
before_action :authenticate_user!
9+
before_action :require_not_suspended!
10+
11+
skip_before_action :require_functional!
912

1013
def show
1114
@export = Export.new(current_account)
@@ -34,4 +37,8 @@ def create
3437
def lock_options
3538
{ redis: Redis.current, key: "backup:#{current_user.id}" }
3639
end
40+
41+
def require_not_suspended!
42+
forbidden if current_account.suspended?
43+
end
3744
end

app/controllers/settings/migrations_controller.rb

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,59 @@ class Settings::MigrationsController < Settings::BaseController
44
layout 'admin'
55

66
before_action :authenticate_user!
7+
before_action :require_not_suspended!
8+
before_action :set_migrations
9+
before_action :set_cooldown
10+
11+
skip_before_action :require_functional!
712

813
def show
9-
@migration = Form::Migration.new(account: current_account.moved_to_account)
14+
@migration = current_account.migrations.build
1015
end
1116

12-
def update
13-
@migration = Form::Migration.new(resource_params)
17+
def create
18+
@migration = current_account.migrations.build(resource_params)
1419

15-
if @migration.valid? && migration_account_changed?
16-
current_account.update!(moved_to_account: @migration.account)
20+
if @migration.save_with_challenge(current_user)
21+
current_account.update!(moved_to_account: @migration.target_account)
1722
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
18-
redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg')
23+
ActivityPub::MoveDistributionWorker.perform_async(@migration.id)
24+
redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
1925
else
2026
render :show
2127
end
2228
end
2329

30+
def cancel
31+
if current_account.moved_to_account_id.present?
32+
current_account.update!(moved_to_account: nil)
33+
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
34+
end
35+
36+
redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg')
37+
end
38+
39+
helper_method :on_cooldown?
40+
2441
private
2542

2643
def resource_params
27-
params.require(:migration).permit(:acct)
44+
params.require(:account_migration).permit(:acct, :current_password, :current_username)
45+
end
46+
47+
def set_migrations
48+
@migrations = current_account.migrations.includes(:target_account).order(id: :desc).reject(&:new_record?)
49+
end
50+
51+
def set_cooldown
52+
@cooldown = current_account.migrations.within_cooldown.first
53+
end
54+
55+
def on_cooldown?
56+
@cooldown.present?
2857
end
2958

30-
def migration_account_changed?
31-
current_account.moved_to_account_id != @migration.account&.id &&
32-
current_account.id != @migration.account&.id
59+
def require_not_suspended!
60+
forbidden if current_account.suspended?
3361
end
3462
end

app/helpers/settings_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,12 @@ def session_device_icon(session)
8787
'desktop'
8888
end
8989
end
90+
91+
def compact_account_link_to(account)
92+
return if account.nil?
93+
94+
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
95+
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
96+
end
97+
end
9098
end

app/models/account_alias.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
# == Schema Information
4+
#
5+
# Table name: account_aliases
6+
#
7+
# id :bigint(8) not null, primary key
8+
# account_id :bigint(8)
9+
# acct :string default(""), not null
10+
# uri :string default(""), not null
11+
# created_at :datetime not null
12+
# updated_at :datetime not null
13+
#
14+
15+
class AccountAlias < ApplicationRecord
16+
belongs_to :account
17+
18+
validates :acct, presence: true, domain: { acct: true }
19+
validates :uri, presence: true
20+
21+
before_validation :set_uri
22+
after_create :add_to_account
23+
after_destroy :remove_from_account
24+
25+
private
26+
27+
def set_uri
28+
target_account = ResolveAccountService.new.call(acct)
29+
self.uri = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
30+
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
31+
# Validation will take care of it
32+
end
33+
34+
def add_to_account
35+
account.update(also_known_as: account.also_known_as + [uri])
36+
end
37+
38+
def remove_from_account
39+
account.update(also_known_as: account.also_known_as.reject { |x| x == uri })
40+
end
41+
end

app/models/account_migration.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
# == Schema Information
4+
#
5+
# Table name: account_migrations
6+
#
7+
# id :bigint(8) not null, primary key
8+
# account_id :bigint(8)
9+
# acct :string default(""), not null
10+
# followers_count :bigint(8) default(0), not null
11+
# target_account_id :bigint(8)
12+
# created_at :datetime not null
13+
# updated_at :datetime not null
14+
#
15+
16+
class AccountMigration < ApplicationRecord
17+
COOLDOWN_PERIOD = 30.days.freeze
18+
19+
belongs_to :account
20+
belongs_to :target_account, class_name: 'Account'
21+
22+
before_validation :set_target_account
23+
before_validation :set_followers_count
24+
25+
validates :acct, presence: true, domain: { acct: true }
26+
validate :validate_migration_cooldown
27+
validate :validate_target_account
28+
29+
scope :within_cooldown, ->(now = Time.now.utc) { where(arel_table[:created_at].gteq(now - COOLDOWN_PERIOD)) }
30+
31+
attr_accessor :current_password, :current_username
32+
33+
def save_with_challenge(current_user)
34+
if current_user.encrypted_password.present?
35+
errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
36+
else
37+
errors.add(:current_username, :invalid) unless account.username == current_username
38+
end
39+
40+
return false unless errors.empty?
41+
42+
save
43+
end
44+
45+
def cooldown_at
46+
created_at + COOLDOWN_PERIOD
47+
end
48+
49+
private
50+
51+
def set_target_account
52+
self.target_account = ResolveAccountService.new.call(acct)
53+
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
54+
# Validation will take care of it
55+
end
56+
57+
def set_followers_count
58+
self.followers_count = account.followers_count
59+
end
60+
61+
def validate_target_account
62+
if target_account.nil?
63+
errors.add(:acct, I18n.t('migrations.errors.not_found'))
64+
else
65+
errors.add(:acct, I18n.t('migrations.errors.missing_also_known_as')) unless target_account.also_known_as.include?(ActivityPub::TagManager.instance.uri_for(account))
66+
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
67+
errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
68+
end
69+
end
70+
71+
def validate_migration_cooldown
72+
errors.add(:base, I18n.t('migrations.errors.on_cooldown')) if account.migrations.within_cooldown.exists?
73+
end
74+
end

app/models/concerns/account_associations.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ module AccountAssociations
5252

5353
# Account migrations
5454
belongs_to :moved_to_account, class_name: 'Account', optional: true
55+
has_many :migrations, class_name: 'AccountMigration', dependent: :destroy, inverse_of: :account
56+
has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account
5557

5658
# Hashtags
5759
has_and_belongs_to_many :tags

app/models/form/migration.rb

Lines changed: 0 additions & 25 deletions
This file was deleted.

app/models/remote_follow.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def normalize_acct(value)
4949
end
5050

5151
def fetch_template!
52-
return missing_resource if acct.blank?
52+
return missing_resource_error if acct.blank?
5353

5454
_, domain = acct.split('@')
5555

0 commit comments

Comments
 (0)