M app/models/concerns/account_interactions.rb => app/models/concerns/account_interactions.rb +1 -0
@@ 272,6 272,7 @@ module AccountInteractions
def lists_for_local_distribution
lists.joins(account: :user)
+ .where.not(list_accounts: { follow_id: nil })
.where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
end
M app/models/follow_request.rb => app/models/follow_request.rb +2 -1
@@ 32,7 32,8 @@ class FollowRequest < ApplicationRecord
validates :languages, language: true
def authorize!
- account.follow!(target_account, reblogs: show_reblogs, notify: notify, languages: languages, uri: uri, bypass_limit: true)
+ follow = account.follow!(target_account, reblogs: show_reblogs, notify: notify, languages: languages, uri: uri, bypass_limit: true)
+ ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id) # rubocop:disable Rails/SkipsModelValidations
MergeWorker.perform_async(target_account.id, account.id) if account.local?
destroy!
end
M app/models/list_account.rb => app/models/list_account.rb +11 -5
@@ 4,16 4,18 @@
#
# Table name: list_accounts
#
-# id :bigint(8) not null, primary key
-# list_id :bigint(8) not null
-# account_id :bigint(8) not null
-# follow_id :bigint(8)
+# id :bigint(8) not null, primary key
+# list_id :bigint(8) not null
+# account_id :bigint(8) not null
+# follow_id :bigint(8)
+# follow_request_id :bigint(8)
#
class ListAccount < ApplicationRecord
belongs_to :list
belongs_to :account
belongs_to :follow, optional: true
+ belongs_to :follow_request, optional: true
validates :account_id, uniqueness: { scope: :list_id }
@@ 22,6 24,10 @@ class ListAccount < ApplicationRecord
private
def set_follow
- self.follow = Follow.find_by!(account_id: list.account_id, target_account_id: account.id) unless list.account_id == account.id
+ return if list.account_id == account.id
+
+ self.follow = Follow.find_by!(account_id: list.account_id, target_account_id: account.id)
+ rescue ActiveRecord::RecordNotFound
+ self.follow_request = FollowRequest.find_by!(account_id: list.account_id, target_account_id: account.id)
end
end
A db/migrate/20230330155710_add_follow_request_id_to_list_accounts.rb => db/migrate/20230330155710_add_follow_request_id_to_list_accounts.rb +10 -0
@@ 0,0 1,10 @@
+# frozen_string_literal: true
+
+class AddFollowRequestIdToListAccounts < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ def change
+ safety_assured { add_reference :list_accounts, :follow_request, foreign_key: { on_delete: :cascade }, index: false }
+ add_index :list_accounts, :follow_request_id, algorithm: :concurrently, where: 'follow_request_id IS NOT NULL'
+ end
+end
M db/schema.rb => db/schema.rb +4 -1
@@ 10,7 10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2023_03_30_140036) do
+ActiveRecord::Schema.define(version: 2023_03_30_155710) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ 554,8 554,10 @@ ActiveRecord::Schema.define(version: 2023_03_30_140036) do
t.bigint "list_id", null: false
t.bigint "account_id", null: false
t.bigint "follow_id"
+ t.bigint "follow_request_id"
t.index ["account_id", "list_id"], name: "index_list_accounts_on_account_id_and_list_id", unique: true
t.index ["follow_id"], name: "index_list_accounts_on_follow_id", where: "(follow_id IS NOT NULL)"
+ t.index ["follow_request_id"], name: "index_list_accounts_on_follow_request_id", where: "(follow_request_id IS NOT NULL)"
t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id"
end
@@ 1198,6 1200,7 @@ ActiveRecord::Schema.define(version: 2023_03_30_140036) do
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
add_foreign_key "invites", "users", on_delete: :cascade
add_foreign_key "list_accounts", "accounts", on_delete: :cascade
+ add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade
add_foreign_key "list_accounts", "follows", on_delete: :cascade
add_foreign_key "list_accounts", "lists", on_delete: :cascade
add_foreign_key "lists", "accounts", on_delete: :cascade
M spec/controllers/api/v1/lists/accounts_controller_spec.rb => spec/controllers/api/v1/lists/accounts_controller_spec.rb +38 -7
@@ 29,17 29,48 @@ describe Api::V1::Lists::AccountsController do
let(:scopes) { 'write:lists' }
let(:bob) { Fabricate(:account, username: 'bob') }
- before do
- user.account.follow!(bob)
- post :create, params: { list_id: list.id, account_ids: [bob.id] }
+ context 'when the added account is followed' do
+ before do
+ user.account.follow!(bob)
+ post :create, params: { list_id: list.id, account_ids: [bob.id] }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'adds account to the list' do
+ expect(list.accounts.include?(bob)).to be true
+ end
end
- it 'returns http success' do
- expect(response).to have_http_status(200)
+ context 'when the added account has been sent a follow request' do
+ before do
+ user.account.follow_requests.create!(target_account: bob)
+ post :create, params: { list_id: list.id, account_ids: [bob.id] }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'adds account to the list' do
+ expect(list.accounts.include?(bob)).to be true
+ end
end
- it 'adds account to the list' do
- expect(list.accounts.include?(bob)).to be true
+ context 'when the added account is not followed' do
+ before do
+ post :create, params: { list_id: list.id, account_ids: [bob.id] }
+ end
+
+ it 'returns http not found' do
+ expect(response).to have_http_status(404)
+ end
+
+ it 'does not add the account to the list' do
+ expect(list.accounts.include?(bob)).to be false
+ end
end
end
M spec/models/concerns/account_interactions_spec.rb => spec/models/concerns/account_interactions_spec.rb +24 -0
@@ 683,4 683,28 @@ describe AccountInteractions do
end
end
end
+
+ describe '#lists_for_local_distribution' do
+ let!(:inactive_follower_user) { Fabricate(:user, current_sign_in_at: 5.years.ago) }
+ let!(:follower_user) { Fabricate(:user, current_sign_in_at: Time.now.utc) }
+ let!(:follow_request_user) { Fabricate(:user, current_sign_in_at: Time.now.utc) }
+
+ let!(:inactive_follower_list) { Fabricate(:list, account: inactive_follower_user.account) }
+ let!(:follower_list) { Fabricate(:list, account: follower_user.account) }
+ let!(:follow_request_list) { Fabricate(:list, account: follow_request_user.account) }
+
+ before do
+ inactive_follower_user.account.follow!(account)
+ follower_user.account.follow!(account)
+ follow_request_user.account.follow_requests.create!(target_account: account)
+
+ inactive_follower_list.accounts << account
+ follower_list.accounts << account
+ follow_request_list.accounts << account
+ end
+
+ it 'includes only the list from the active follower' do
+ expect(account.lists_for_local_distribution.to_a).to eq [follower_list]
+ end
+ end
end
M spec/models/follow_request_spec.rb => spec/models/follow_request_spec.rb +37 -5
@@ 4,13 4,27 @@ require 'rails_helper'
RSpec.describe FollowRequest, type: :model do
describe '#authorize!' do
- let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) }
- let(:account) { Fabricate(:account) }
- let(:target_account) { Fabricate(:account) }
+ let!(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) }
+ let(:account) { Fabricate(:account) }
+ let(:target_account) { Fabricate(:account) }
+
+ context 'when the to-be-followed person has been added to a list' do
+ let!(:list) { Fabricate(:list, account: account) }
+
+ before do
+ list.accounts << target_account
+ end
+
+ it 'updates the ListAccount' do
+ expect { follow_request.authorize! }.to change { [list.list_accounts.first.follow_request_id, list.list_accounts.first.follow_id] }.from([follow_request.id, nil]).to([nil, anything])
+ end
+ end
it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
- expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, languages: nil, bypass_limit: true)
- expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id)
+ expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, languages: nil, bypass_limit: true) do
+ account.active_relationships.create!(target_account: target_account)
+ end
+ expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id)
expect(follow_request).to receive(:destroy!)
follow_request.authorize!
end
@@ 29,4 43,22 @@ RSpec.describe FollowRequest, type: :model do
expect(follow_request.account.muting_reblogs?(target)).to be true
end
end
+
+ describe '#reject!' do
+ let!(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) }
+ let(:account) { Fabricate(:account) }
+ let(:target_account) { Fabricate(:account) }
+
+ context 'when the to-be-followed person has been added to a list' do
+ let!(:list) { Fabricate(:list, account: account) }
+
+ before do
+ list.accounts << target_account
+ end
+
+ it 'deletes the ListAccount record' do
+ expect { follow_request.reject! }.to change { list.accounts.count }.from(1).to(0)
+ end
+ end
+ end
end