--- /dev/null
+# 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
format.html do
expires_in 0, public: true unless user_signed_in?
- next if @account.user_hides_network?
+ next if @account.hide_collections?
follows
end
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?)
end
def restrict_fields_to
- if page_requested? || !@account.user_hides_network?
+ if page_requested? || !@account.hide_collections?
# Return all fields
else
%i(id type total_items)
format.html do
expires_in 0, public: true unless user_signed_in?
- next if @account.user_hides_network?
+ next if @account.hide_collections?
follows
end
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?)
end
def restrict_fields_to
- if page_requested? || !@account.user_hides_network?
+ if page_requested? || !@account.hide_collections?
# Return all fields
else
%i(id type total_items)
:setting_system_font_ui,
:setting_noindex,
:setting_theme,
- :setting_hide_network,
:setting_aggregate_reblogs,
:setting_show_application,
:setting_advanced_layout,
private
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
def set_account
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['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['show_application'] = show_application_preference if change?('setting_show_application')
user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
boolean_cast_setting 'setting_noindex'
end
- def hide_network_preference
- boolean_cast_setting 'setting_hide_network'
- end
-
def show_application_preference
boolean_cast_setting 'setting_show_application'
end
end
def hides_followers?
- hide_collections? || user_hides_network?
+ hide_collections?
end
def hides_following?
- hide_collections? || user_hides_network?
+ hide_collections?
end
def object_type
has_many :session_activations, dependent: :destroy
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,
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
:disable_swiping,
settings.notification_emails['trending_tag']
end
- def hides_network?
- @hides_network ||= settings.hide_network
- end
-
def aggregates_reblogs?
@aggregates_reblogs ||= settings.aggregate_reblogs
end
--- /dev/null
+# 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
--- /dev/null
+# 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
= render 'accounts/header', account: @account
-- if @account.user_hides_network?
+- if @account.hide_collections?
.nothing-here= t('accounts.network_hidden')
- elsif user_signed_in? && @account.blocking?(current_account)
.nothing-here= t('accounts.unavailable')
= render 'accounts/header', account: @account
-- if @account.user_hides_network?
+- if @account.hide_collections?
.nothing-here= t('accounts.network_hidden')
- elsif user_signed_in? && @account.blocking?(current_account)
.nothing-here= t('accounts.unavailable')
.fields-group
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
- .fields-group
- = f.input :setting_hide_network, as: :boolean, wrapper: :with_label
-
.fields-group
= f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label, recommended: true
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
.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/
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
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_no_directory: Allow your account to be discovered by strangers through recommendations and other features
+ discoverable: Allow your account to be discovered by strangers through recommendations, trends and other features
email: You will be sent a confirmation e-mail
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
resource :search, only: :show, controller: :search
resource :lookup, only: :show, controller: :lookup
resources :relationships, only: :index
+ resources :familiar_followers, only: :index
end
resources :accounts, only: [:create, :show] do
timeline_preview: true
show_staff_badge: true
default_sensitive: false
- hide_network: false
unfollow_modal: false
boost_modal: false
delete_modal: true
--- /dev/null
+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
#
# 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
enable_extension "plpgsql"
context 'when account hides their network' do
before do
- alice.user.settings.hide_network = true
+ alice.update(hide_collections: true)
end
it 'returns followers count' do
context 'when account hides their network' do
before do
- alice.user.settings.hide_network = true
+ alice.update(hide_collections: true)
end
it 'returns followers count' do
--- /dev/null
+# 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