Add `/api/v1/accounts/familiar_followers` to REST API (#17700)
* Add `/api/v1/accounts/familiar_followers` to REST API * Change hide network preference to be stored consistently for local and remote accounts * Add dummy classes to migration * Apply suggestions from code review Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
c439e13e12
commit
edf09ec747
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_accounts
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: familiar_followers.accounts, each_serializer: REST::FamiliarFollowersSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_accounts
|
||||||
|
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections').index_by(&:id).values_at(*account_ids).compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def familiar_followers
|
||||||
|
FamiliarFollowersPresenter.new(@accounts, current_user.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_ids
|
||||||
|
Array(params[:id]).map(&:to_i)
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,13 +15,13 @@ class FollowerAccountsController < ApplicationController
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in 0, public: true unless user_signed_in?
|
||||||
|
|
||||||
next if @account.user_hides_network?
|
next if @account.hide_collections?
|
||||||
|
|
||||||
follows
|
follows
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||||
|
|
||||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class FollowerAccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def restrict_fields_to
|
def restrict_fields_to
|
||||||
if page_requested? || !@account.user_hides_network?
|
if page_requested? || !@account.hide_collections?
|
||||||
# Return all fields
|
# Return all fields
|
||||||
else
|
else
|
||||||
%i(id type total_items)
|
%i(id type total_items)
|
||||||
|
|
|
@ -15,13 +15,13 @@ class FollowingAccountsController < ApplicationController
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in 0, public: true unless user_signed_in?
|
||||||
|
|
||||||
next if @account.user_hides_network?
|
next if @account.hide_collections?
|
||||||
|
|
||||||
follows
|
follows
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||||
|
|
||||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class FollowingAccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def restrict_fields_to
|
def restrict_fields_to
|
||||||
if page_requested? || !@account.user_hides_network?
|
if page_requested? || !@account.hide_collections?
|
||||||
# Return all fields
|
# Return all fields
|
||||||
else
|
else
|
||||||
%i(id type total_items)
|
%i(id type total_items)
|
||||||
|
|
|
@ -47,7 +47,6 @@ class Settings::PreferencesController < Settings::BaseController
|
||||||
:setting_system_font_ui,
|
:setting_system_font_ui,
|
||||||
:setting_noindex,
|
:setting_noindex,
|
||||||
:setting_theme,
|
:setting_theme,
|
||||||
:setting_hide_network,
|
|
||||||
:setting_aggregate_reblogs,
|
:setting_aggregate_reblogs,
|
||||||
:setting_show_application,
|
:setting_show_application,
|
||||||
:setting_advanced_layout,
|
:setting_advanced_layout,
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
|
|
@ -31,7 +31,6 @@ class UserSettingsDecorator
|
||||||
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
|
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
|
||||||
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
|
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
|
||||||
user.settings['theme'] = theme_preference if change?('setting_theme')
|
user.settings['theme'] = theme_preference if change?('setting_theme')
|
||||||
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
|
|
||||||
user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
|
user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
|
||||||
user.settings['show_application'] = show_application_preference if change?('setting_show_application')
|
user.settings['show_application'] = show_application_preference if change?('setting_show_application')
|
||||||
user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
|
user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
|
||||||
|
@ -97,10 +96,6 @@ class UserSettingsDecorator
|
||||||
boolean_cast_setting 'setting_noindex'
|
boolean_cast_setting 'setting_noindex'
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide_network_preference
|
|
||||||
boolean_cast_setting 'setting_hide_network'
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_application_preference
|
def show_application_preference
|
||||||
boolean_cast_setting 'setting_show_application'
|
boolean_cast_setting 'setting_show_application'
|
||||||
end
|
end
|
||||||
|
|
|
@ -349,11 +349,11 @@ class Account < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def hides_followers?
|
def hides_followers?
|
||||||
hide_collections? || user_hides_network?
|
hide_collections?
|
||||||
end
|
end
|
||||||
|
|
||||||
def hides_following?
|
def hides_following?
|
||||||
hide_collections? || user_hides_network?
|
hide_collections?
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_type
|
def object_type
|
||||||
|
|
|
@ -126,7 +126,7 @@ class User < ApplicationRecord
|
||||||
has_many :session_activations, dependent: :destroy
|
has_many :session_activations, dependent: :destroy
|
||||||
|
|
||||||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
|
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
|
||||||
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
|
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media,
|
||||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
||||||
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
|
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
|
||||||
:disable_swiping,
|
:disable_swiping,
|
||||||
|
@ -273,10 +273,6 @@ class User < ApplicationRecord
|
||||||
settings.notification_emails['trending_tag']
|
settings.notification_emails['trending_tag']
|
||||||
end
|
end
|
||||||
|
|
||||||
def hides_network?
|
|
||||||
@hides_network ||= settings.hide_network
|
|
||||||
end
|
|
||||||
|
|
||||||
def aggregates_reblogs?
|
def aggregates_reblogs?
|
||||||
@aggregates_reblogs ||= settings.aggregate_reblogs
|
@aggregates_reblogs ||= settings.aggregate_reblogs
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class FamiliarFollowersPresenter
|
||||||
|
class Result < ActiveModelSerializers::Model
|
||||||
|
attributes :id, :accounts
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(accounts, current_account_id)
|
||||||
|
@accounts = accounts
|
||||||
|
@current_account_id = current_account_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def accounts
|
||||||
|
map = Follow.includes(account: :account_stat).where(target_account_id: @accounts.map(&:id)).where(account_id: Follow.where(account_id: @current_account_id).joins(:target_account).merge(Account.where(hide_collections: [nil, false])).select(:target_account_id)).group_by(&:target_account_id)
|
||||||
|
@accounts.map { |account| Result.new(id: account.id, accounts: (account.hide_collections? ? [] : (map[account.id] || [])).map(&:account)) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::FamiliarFollowersSerializer < ActiveModel::Serializer
|
||||||
|
attribute :id
|
||||||
|
|
||||||
|
has_many :accounts, serializer: REST::AccountSerializer
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.id.to_s
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
= render 'accounts/header', account: @account
|
= render 'accounts/header', account: @account
|
||||||
|
|
||||||
- if @account.user_hides_network?
|
- if @account.hide_collections?
|
||||||
.nothing-here= t('accounts.network_hidden')
|
.nothing-here= t('accounts.network_hidden')
|
||||||
- elsif user_signed_in? && @account.blocking?(current_account)
|
- elsif user_signed_in? && @account.blocking?(current_account)
|
||||||
.nothing-here= t('accounts.unavailable')
|
.nothing-here= t('accounts.unavailable')
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
= render 'accounts/header', account: @account
|
= render 'accounts/header', account: @account
|
||||||
|
|
||||||
- if @account.user_hides_network?
|
- if @account.hide_collections?
|
||||||
.nothing-here= t('accounts.network_hidden')
|
.nothing-here= t('accounts.network_hidden')
|
||||||
- elsif user_signed_in? && @account.blocking?(current_account)
|
- elsif user_signed_in? && @account.blocking?(current_account)
|
||||||
.nothing-here= t('accounts.unavailable')
|
.nothing-here= t('accounts.unavailable')
|
||||||
|
|
|
@ -10,9 +10,6 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
|
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :setting_hide_network, as: :boolean, wrapper: :with_label
|
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label, recommended: true
|
= f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label, recommended: true
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,10 @@
|
||||||
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t(Setting.profile_directory ? 'simple_form.hints.defaults.discoverable' : 'simple_form.hints.defaults.discoverable_no_directory'), recommended: true
|
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,7 @@ en:
|
||||||
current_password: For security purposes please enter the password of the current account
|
current_password: For security purposes please enter the password of the current account
|
||||||
current_username: To confirm, please enter the username of the current account
|
current_username: To confirm, please enter the username of the current account
|
||||||
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
|
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
|
||||||
discoverable: Allow your account to be discovered by strangers through recommendations, profile directory and other features
|
discoverable: Allow your account to be discovered by strangers through recommendations, trends and other features
|
||||||
discoverable_no_directory: Allow your account to be discovered by strangers through recommendations and other features
|
|
||||||
email: You will be sent a confirmation e-mail
|
email: You will be sent a confirmation e-mail
|
||||||
fields: You can have up to 4 items displayed as a table on your profile
|
fields: You can have up to 4 items displayed as a table on your profile
|
||||||
header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
|
header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
|
||||||
|
|
|
@ -493,6 +493,7 @@ Rails.application.routes.draw do
|
||||||
resource :search, only: :show, controller: :search
|
resource :search, only: :show, controller: :search
|
||||||
resource :lookup, only: :show, controller: :lookup
|
resource :lookup, only: :show, controller: :lookup
|
||||||
resources :relationships, only: :index
|
resources :relationships, only: :index
|
||||||
|
resources :familiar_followers, only: :index
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :accounts, only: [:create, :show] do
|
resources :accounts, only: [:create, :show] do
|
||||||
|
|
|
@ -17,7 +17,6 @@ defaults: &defaults
|
||||||
timeline_preview: true
|
timeline_preview: true
|
||||||
show_staff_badge: true
|
show_staff_badge: true
|
||||||
default_sensitive: false
|
default_sensitive: false
|
||||||
hide_network: false
|
|
||||||
unfollow_modal: false
|
unfollow_modal: false
|
||||||
boost_modal: false
|
boost_modal: false
|
||||||
delete_modal: true
|
delete_modal: true
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
class MigrateHideNetworkPreference < ActiveRecord::Migration[6.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
# Dummy classes, to make migration possible across version changes
|
||||||
|
class Account < ApplicationRecord
|
||||||
|
has_one :user, inverse_of: :account
|
||||||
|
scope :local, -> { where(domain: nil) }
|
||||||
|
end
|
||||||
|
|
||||||
|
class User < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
Account.reset_column_information
|
||||||
|
|
||||||
|
Setting.unscoped.where(thing_type: 'User', var: 'hide_network').find_each do |setting|
|
||||||
|
account = User.find(setting.thing_id).account
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
account.update(hide_collections: setting.value)
|
||||||
|
setting.delete
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
Account.local.where(hide_collections: true).includes(:user).find_each do |account|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
Setting.create(thing_type: 'User', thing_id: account.user.id, var: 'hide_network', value: account.hide_collections?)
|
||||||
|
account.update(hide_collections: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_02_27_041951) do
|
ActiveRecord::Schema.define(version: 2022_03_04_195405) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
|
@ -103,7 +103,7 @@ describe FollowerAccountsController do
|
||||||
|
|
||||||
context 'when account hides their network' do
|
context 'when account hides their network' do
|
||||||
before do
|
before do
|
||||||
alice.user.settings.hide_network = true
|
alice.update(hide_collections: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns followers count' do
|
it 'returns followers count' do
|
||||||
|
|
|
@ -103,7 +103,7 @@ describe FollowingAccountsController do
|
||||||
|
|
||||||
context 'when account hides their network' do
|
context 'when account hides their network' do
|
||||||
before do
|
before do
|
||||||
alice.user.settings.hide_network = true
|
alice.update(hide_collections: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns followers count' do
|
it 'returns followers count' do
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe FamiliarFollowersPresenter do
|
||||||
|
describe '#accounts' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:familiar_follower) { Fabricate(:account) }
|
||||||
|
let(:requested_accounts) { Fabricate.times(2, :account) }
|
||||||
|
|
||||||
|
subject { described_class.new(requested_accounts, account.id) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
familiar_follower.follow!(requested_accounts.first)
|
||||||
|
account.follow!(familiar_follower)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a result for each requested account' do
|
||||||
|
expect(subject.accounts.map(&:id)).to eq requested_accounts.map(&:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns followers you follow' do
|
||||||
|
result = subject.accounts.first
|
||||||
|
|
||||||
|
expect(result).to_not be_nil
|
||||||
|
expect(result.id).to eq requested_accounts.first.id
|
||||||
|
expect(result.accounts).to match_array([familiar_follower])
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when requested account hides followers' do
|
||||||
|
before do
|
||||||
|
requested_accounts.first.update(hide_collections: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return followers you follow' do
|
||||||
|
result = subject.accounts.first
|
||||||
|
|
||||||
|
expect(result).to_not be_nil
|
||||||
|
expect(result.id).to eq requested_accounts.first.id
|
||||||
|
expect(result.accounts).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when familiar follower hides follows' do
|
||||||
|
before do
|
||||||
|
familiar_follower.update(hide_collections: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return followers you follow' do
|
||||||
|
result = subject.accounts.first
|
||||||
|
|
||||||
|
expect(result).to_not be_nil
|
||||||
|
expect(result.id).to eq requested_accounts.first.id
|
||||||
|
expect(result.accounts).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Reference in New Issue