Skip to content

Commit

Permalink
Allow viewing and severing relationships with suspended accounts (mas…
Browse files Browse the repository at this point in the history
  • Loading branch information
ClearlyClaire authored and vmstan committed Dec 14, 2023
1 parent f91155a commit 9b7da0b
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 121 deletions.
5 changes: 3 additions & 2 deletions app/controllers/api/v1/accounts/relationships_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!

def index
accounts = Account.without_suspended.where(id: account_ids).select('id')
scope = Account.where(id: account_ids).select('id')
scope.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
@accounts = scope.index_by(&:id).values_at(*account_ids).compact
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end

Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/actions/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ export function fetchRelationships(accountIds) {

dispatch(fetchRelationshipsRequest(newAccountIds));

api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
}).catch(error => {
dispatch(fetchRelationshipsFail(error));
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/account.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class Account extends ImmutablePureComponent {
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
} else if (defaultAction === 'block') {
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
} else if (!account.get('moved') || following) {
} else if (!account.get('suspended') && !account.get('moved') || following) {
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
}
}
Expand Down
30 changes: 15 additions & 15 deletions app/javascript/mastodon/features/account/components/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ class Header extends ImmutablePureComponent {
lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
}

if (signedIn && account.get('id') !== me) {
if (signedIn && account.get('id') !== me && !account.get('suspended')) {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
menu.push(null);
Expand All @@ -299,7 +299,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
}

if ('share' in navigator) {
if ('share' in navigator && !account.get('suspended')) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
menu.push(null);
}
Expand Down Expand Up @@ -347,7 +347,9 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
}

menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
if (!account.get('suspended')) {
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
}
}

if (signedIn && isRemote) {
Expand Down Expand Up @@ -395,7 +397,7 @@ class Header extends ImmutablePureComponent {

<div className='account__header__image'>
<div className='account__header__info'>
{!suspended && info}
{info}
</div>

{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
Expand All @@ -407,18 +409,16 @@ class Header extends ImmutablePureComponent {
<Avatar account={suspended || hidden ? undefined : account} size={90} />
</a>

{!suspended && (
<div className='account__header__tabs__buttons'>
{!hidden && (
<>
{actionBtn}
{bellBtn}
</>
)}
<div className='account__header__tabs__buttons'>
{!hidden && (
<>
{actionBtn}
{bellBtn}
</>
)}

<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
</div>
)}
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
</div>
</div>

<div className='account__header__tabs__name'>
Expand Down
4 changes: 4 additions & 0 deletions config/routes/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@
resources :statuses, only: [:show, :destroy]
end

namespace :accounts do
resources :relationships, only: :index
end

namespace :admin do
resources :accounts, only: [:index]
end
Expand Down
102 changes: 0 additions & 102 deletions spec/controllers/api/v1/accounts/relationships_controller_spec.rb

This file was deleted.

133 changes: 133 additions & 0 deletions spec/requests/api/v1/accounts/relationships_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# frozen_string_literal: true

require 'rails_helper'

describe 'GET /api/v1/accounts/relationships' do
subject do
get '/api/v1/accounts/relationships', headers: headers, params: params
end

let(:user) { Fabricate(:user) }
let(:scopes) { 'read:follows' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }

let(:simon) { Fabricate(:account) }
let(:lewis) { Fabricate(:account) }
let(:bob) { Fabricate(:account, suspended: true) }

before do
user.account.follow!(simon)
lewis.follow!(user.account)
end

context 'when provided only one ID' do
let(:params) { { id: simon.id } }

it 'returns JSON with correct data', :aggregate_failures do
subject

json = body_as_json

expect(response).to have_http_status(200)
expect(json).to be_a Enumerable
expect(json.first[:following]).to be true
expect(json.first[:followed_by]).to be false
end
end

context 'when provided multiple IDs' do
let(:params) { { id: [simon.id, lewis.id, bob.id] } }

context 'when there is returned JSON data' do
let(:json) { body_as_json }

context 'with default parameters' do
it 'returns an enumerable json with correct elements, excluding suspended accounts', :aggregate_failures do
subject

expect(response).to have_http_status(200)
expect(json).to be_a Enumerable
expect(json.size).to eq 2

expect_simon_item_one
expect_lewis_item_two
end
end

context 'with `with_suspended` parameter' do
let(:params) { { id: [simon.id, lewis.id, bob.id], with_suspended: true } }

it 'returns an enumerable json with correct elements, including suspended accounts', :aggregate_failures do
subject

expect(response).to have_http_status(200)
expect(json).to be_a Enumerable
expect(json.size).to eq 3

expect_simon_item_one
expect_lewis_item_two
expect_bob_item_three
end
end

def expect_simon_item_one
expect(json.first[:id]).to eq simon.id.to_s
expect(json.first[:following]).to be true
expect(json.first[:showing_reblogs]).to be true
expect(json.first[:followed_by]).to be false
expect(json.first[:muting]).to be false
expect(json.first[:requested]).to be false
expect(json.first[:domain_blocking]).to be false
end

def expect_lewis_item_two
expect(json.second[:id]).to eq lewis.id.to_s
expect(json.second[:following]).to be false
expect(json.second[:showing_reblogs]).to be false
expect(json.second[:followed_by]).to be true
expect(json.second[:muting]).to be false
expect(json.second[:requested]).to be false
expect(json.second[:domain_blocking]).to be false
end

def expect_bob_item_three
expect(json.third[:id]).to eq bob.id.to_s
expect(json.third[:following]).to be false
expect(json.third[:showing_reblogs]).to be false
expect(json.third[:followed_by]).to be false
expect(json.third[:muting]).to be false
expect(json.third[:requested]).to be false
expect(json.third[:domain_blocking]).to be false
end
end

it 'returns JSON with correct data on cached requests too' do
subject
subject

expect(response).to have_http_status(200)

json = body_as_json

expect(json).to be_a Enumerable
expect(json.first[:following]).to be true
expect(json.first[:showing_reblogs]).to be true
end

it 'returns JSON with correct data after change too' do
subject
user.account.unfollow!(simon)

get '/api/v1/accounts/relationships', headers: headers, params: { id: [simon.id] }

expect(response).to have_http_status(200)

json = body_as_json

expect(json).to be_a Enumerable
expect(json.first[:following]).to be false
expect(json.first[:showing_reblogs]).to be false
end
end
end

0 comments on commit 9b7da0b

Please sign in to comment.