Merge commit '7840c6b75b61e64d89d7fd9f291277fb177e513f' into glitch-soc/merge-upstream

Conflicts:
- `app/controllers/api/v1/accounts/relationships_controller.rb`:
  We differed by listing suspended users when requesting relationships.
  Updated to upstream's code.
main
Claire 2023-12-18 18:40:58 +01:00
commit b474cbbd28
10 changed files with 185 additions and 170 deletions

View File

@ -5,10 +5,11 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user! before_action :require_user!
def index def index
accounts = Account.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 # .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor. # 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 render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end end

View File

@ -460,7 +460,7 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsRequest(newAccountIds)); 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 })); dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
}).catch(error => { }).catch(error => {
dispatch(fetchRelationshipsFail(error)); dispatch(fetchRelationshipsFail(error));

View File

@ -119,7 +119,7 @@ class Account extends ImmutablePureComponent {
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />; buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
} else if (defaultAction === 'block') { } else if (defaultAction === 'block') {
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />; 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} />; buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
} }
} }

View File

@ -289,7 +289,7 @@ class Header extends ImmutablePureComponent {
lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />; 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.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({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
menu.push(null); menu.push(null);
@ -299,7 +299,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') }); 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({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
menu.push(null); menu.push(null);
} }
@ -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.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) { if (signedIn && isRemote) {
@ -395,7 +397,7 @@ class Header extends ImmutablePureComponent {
<div className='account__header__image'> <div className='account__header__image'>
<div className='account__header__info'> <div className='account__header__info'>
{!suspended && info} {info}
</div> </div>
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />} {!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
@ -407,18 +409,16 @@ class Header extends ImmutablePureComponent {
<Avatar account={suspended || hidden ? undefined : account} size={90} /> <Avatar account={suspended || hidden ? undefined : account} size={90} />
</a> </a>
{!suspended && ( <div className='account__header__tabs__buttons'>
<div className='account__header__tabs__buttons'> {!hidden && (
{!hidden && ( <>
<> {actionBtn}
{actionBtn} {bellBtn}
{bellBtn} </>
</> )}
)}
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' /> <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
</div> </div>
)}
</div> </div>
<div className='account__header__tabs__name'> <div className='account__header__tabs__name'>

View File

@ -469,6 +469,10 @@ class Video extends PureComponent {
}; };
_syncVideoToVolumeState = (volume = null, muted = null) => { _syncVideoToVolumeState = (volume = null, muted = null) => {
if (!this.video) {
return;
}
this.video.volume = volume ?? this.state.volume; this.video.volume = volume ?? this.state.volume;
this.video.muted = muted ?? this.state.muted; this.video.muted = muted ?? this.state.muted;
}; };

View File

@ -43,6 +43,7 @@ end
Sidekiq.logger.level = ::Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s) Sidekiq.logger.level = ::Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s)
SidekiqUniqueJobs.configure do |config| SidekiqUniqueJobs.configure do |config|
config.enabled = !Rails.env.test?
config.reaper = :ruby config.reaper = :ruby
config.reaper_count = 1000 config.reaper_count = 1000
config.reaper_interval = 600 config.reaper_interval = 600

View File

@ -303,6 +303,10 @@ namespace :api, format: false do
resources :statuses, only: [:show, :destroy] resources :statuses, only: [:show, :destroy]
end end
namespace :accounts do
resources :relationships, only: :index
end
namespace :admin do namespace :admin do
resources :accounts, only: [:index] resources :accounts, only: [:index]
end end

View File

@ -7,66 +7,44 @@ RSpec.describe AccountsController do
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
shared_examples 'unapproved account check' do describe 'unapproved account check' do
before { account.user.update(approved: false) } before { account.user.update(approved: false) }
it 'returns http not found' do it 'returns http not found' do
get :show, params: { username: account.username, format: format } %w(html json rss).each do |format|
get :show, params: { username: account.username, format: format }
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end
end end
end end
shared_examples 'permanently suspended account check' do describe 'permanently suspended account check' do
before do before do
account.suspend! account.suspend!
account.deletion_request.destroy account.deletion_request.destroy
end end
it 'returns http gone' do it 'returns http gone' do
get :show, params: { username: account.username, format: format } %w(html json rss).each do |format|
get :show, params: { username: account.username, format: format }
expect(response).to have_http_status(410) expect(response).to have_http_status(410)
end
end end
end end
shared_examples 'temporarily suspended account check' do |code: 403| describe 'temporarily suspended account check' do
before { account.suspend! } before { account.suspend! }
it 'returns appropriate http response code' do it 'returns appropriate http response code' do
get :show, params: { username: account.username, format: format } { html: 403, json: 200, rss: 403 }.each do |format, code|
get :show, params: { username: account.username, format: format }
expect(response).to have_http_status(code) expect(response).to have_http_status(code)
end
end end
end end
describe 'GET #show' do describe 'GET #show' do
context 'with basic account status checks' do
context 'with HTML' do
let(:format) { 'html' }
it_behaves_like 'unapproved account check'
it_behaves_like 'permanently suspended account check'
it_behaves_like 'temporarily suspended account check'
end
context 'with JSON' do
let(:format) { 'json' }
it_behaves_like 'unapproved account check'
it_behaves_like 'permanently suspended account check'
it_behaves_like 'temporarily suspended account check', code: 200
end
context 'with RSS' do
let(:format) { 'rss' }
it_behaves_like 'unapproved account check'
it_behaves_like 'permanently suspended account check'
it_behaves_like 'temporarily suspended account check'
end
end
context 'with existing statuses' do context 'with existing statuses' do
let!(:status) { Fabricate(:status, account: account) } let!(:status) { Fabricate(:status, account: account) }
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) } let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
@ -227,22 +205,15 @@ RSpec.describe AccountsController do
context 'with RSS' do context 'with RSS' do
let(:format) { 'rss' } let(:format) { 'rss' }
shared_examples 'common RSS response' do
it 'returns http success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
end
context 'with a normal account in an RSS request' do context 'with a normal account in an RSS request' do
before do before do
get :show, params: { username: account.username, format: format } get :show, params: { username: account.username, format: format }
end end
it_behaves_like 'common RSS response' it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses', :aggregate_failures do it 'responds with correct statuses', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_media) expect(response.body).to include_status_tag(status_media)
expect(response.body).to include_status_tag(status_self_reply) expect(response.body).to include_status_tag(status_self_reply)
expect(response.body).to include_status_tag(status) expect(response.body).to include_status_tag(status)
@ -259,9 +230,10 @@ RSpec.describe AccountsController do
get :show, params: { username: account.username, format: format } get :show, params: { username: account.username, format: format }
end end
it_behaves_like 'common RSS response' it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses with replies', :aggregate_failures do it 'responds with correct statuses with replies', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_media) expect(response.body).to include_status_tag(status_media)
expect(response.body).to include_status_tag(status_reply) expect(response.body).to include_status_tag(status_reply)
expect(response.body).to include_status_tag(status_self_reply) expect(response.body).to include_status_tag(status_self_reply)
@ -278,9 +250,10 @@ RSpec.describe AccountsController do
get :show, params: { username: account.username, format: format } get :show, params: { username: account.username, format: format }
end end
it_behaves_like 'common RSS response' it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses with media', :aggregate_failures do it 'responds with correct statuses with media', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_media) expect(response.body).to include_status_tag(status_media)
expect(response.body).to_not include_status_tag(status_direct) expect(response.body).to_not include_status_tag(status_direct)
expect(response.body).to_not include_status_tag(status_private) expect(response.body).to_not include_status_tag(status_private)
@ -302,9 +275,10 @@ RSpec.describe AccountsController do
get :show, params: { username: account.username, format: format, tag: tag.to_param } get :show, params: { username: account.username, format: format, tag: tag.to_param }
end end
it_behaves_like 'common RSS response' it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
it 'responds with correct statuses with a tag', :aggregate_failures do it 'responds with correct statuses with a tag', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.body).to include_status_tag(status_tag) expect(response.body).to include_status_tag(status_tag)
expect(response.body).to_not include_status_tag(status_direct) expect(response.body).to_not include_status_tag(status_direct)
expect(response.body).to_not include_status_tag(status_media) expect(response.body).to_not include_status_tag(status_media)

View File

@ -1,102 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::RelationshipsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:follows') }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
let(:simon) { Fabricate(:account) }
let(:lewis) { Fabricate(:account) }
before do
user.account.follow!(simon)
lewis.follow!(user.account)
end
context 'when provided only one ID' do
before do
get :index, params: { id: simon.id }
end
it 'returns JSON with correct data', :aggregate_failures do
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
before do
get :index, params: { id: [simon.id, lewis.id] }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
context 'when there is returned JSON data' do
let(:json) { body_as_json }
it 'returns an enumerable json with correct elements', :aggregate_failures do
expect(json).to be_a Enumerable
expect_simon_item_one
expect_lewis_item_two
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
end
it 'returns JSON with correct data on cached requests too' do
get :index, params: { id: [simon.id] }
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
user.account.unfollow!(simon)
get :index, params: { id: [simon.id] }
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
end

View File

@ -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