Merge commit '7d9b209fe84b00eff348ea9d54905cbfffa79788' into glitch-soc/merge-upstream

Conflicts:
- `app/models/form/admin_settings.rb`:
  Upstream changed code style change, including on a line modified by glitch-soc.
  Kept glitch-soc's line but with the code style change applied.
main
Claire 5 months ago
commit 30ee7339d3

@ -99,6 +99,16 @@
matchUpdateTypes: ['patch', 'minor'], matchUpdateTypes: ['patch', 'minor'],
groupName: 'eslint (non-major)', groupName: 'eslint (non-major)',
}, },
{
// Group actions/*-artifact in the same PR
matchManagers: ['github-actions'],
matchPackageNames: [
'actions/download-artifact',
'actions/upload-artifact',
],
matchUpdateTypes: ['major'],
groupName: 'artifact actions (major)',
},
{ {
// Update @types/* packages every week, with one grouped PR // Update @types/* packages every week, with one grouped PR
matchPackagePrefixes: '@types/', matchPackagePrefixes: '@types/',

@ -1,33 +1,21 @@
# This configuration was generated by # This configuration was generated by
# `haml-lint --auto-gen-config` # `haml-lint --auto-gen-config`
# on 2023-10-26 09:32:34 -0400 using Haml-Lint version 0.51.0. # on 2023-12-15 11:02:19 -0500 using Haml-Lint version 0.52.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base. # one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again. # versions of Haml-Lint, may require this file to be generated again.
linters: linters:
# Offense count: 16 # Offense count: 11
LineLength: LineLength:
exclude: exclude:
- 'app/views/admin/account_actions/new.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/ip_blocks/new.html.haml'
- 'app/views/admin/roles/_form.html.haml' - 'app/views/admin/roles/_form.html.haml'
- 'app/views/admin/settings/discovery/show.html.haml'
- 'app/views/auth/registrations/edit.html.haml' - 'app/views/auth/registrations/edit.html.haml'
- 'app/views/auth/registrations/new.html.haml' - 'app/views/auth/registrations/new.html.haml'
- 'app/views/filters/_filter_fields.html.haml'
- 'app/views/media/player.html.haml' - 'app/views/media/player.html.haml'
- 'app/views/settings/applications/_fields.html.haml' - 'app/views/settings/applications/_fields.html.haml'
- 'app/views/settings/imports/index.html.haml' - 'app/views/settings/imports/index.html.haml'
- 'app/views/settings/preferences/appearance/show.html.haml' - 'app/views/settings/preferences/appearance/show.html.haml'
- 'app/views/settings/preferences/notifications/show.html.haml' - 'app/views/settings/preferences/notifications/show.html.haml'
- 'app/views/settings/preferences/other/show.html.haml' - 'app/views/settings/preferences/other/show.html.haml'
# Offense count: 9
RuboCop:
exclude:
- 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/roles/_form.html.haml'

@ -109,6 +109,17 @@ Rails/SkipsModelValidations:
Exclude: Exclude:
- 'db/*migrate/**/*' - 'db/*migrate/**/*'
# Reason: We want to preserve the ability to migrate from arbitrary old versions,
# and cannot guarantee that every installation has run every migration as they upgrade.
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsunusedignoredcolumns
Rails/UnusedIgnoredColumns:
Enabled: false
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsnegateinclude
Rails/NegateInclude:
Enabled: false
# Reason: Some single letter camel case files shouldn't be split # Reason: Some single letter camel case files shouldn't be split
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath # https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
RSpec/FilePath: RSpec/FilePath:

@ -119,23 +119,6 @@ Rails/LexicallyScopedActionFilter:
- 'app/controllers/auth/passwords_controller.rb' - 'app/controllers/auth/passwords_controller.rb'
- 'app/controllers/auth/registrations_controller.rb' - 'app/controllers/auth/registrations_controller.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/NegateInclude:
Exclude:
- 'app/controllers/concerns/signature_verification.rb'
- 'app/helpers/jsonld_helper.rb'
- 'app/lib/activitypub/activity/create.rb'
- 'app/lib/activitypub/activity/move.rb'
- 'app/lib/feed_manager.rb'
- 'app/lib/link_details_extractor.rb'
- 'app/models/concerns/attachmentable.rb'
- 'app/models/concerns/remotable.rb'
- 'app/models/custom_filter.rb'
- 'app/services/activitypub/process_status_update_service.rb'
- 'app/services/fetch_link_card_service.rb'
- 'app/workers/web/push_notification_worker.rb'
- 'lib/paperclip/color_extractor.rb'
Rails/OutputSafety: Rails/OutputSafety:
Exclude: Exclude:
- 'config/initializers/simple_form.rb' - 'config/initializers/simple_form.rb'
@ -196,19 +179,6 @@ Rails/UniqueValidationWithoutIndex:
- 'app/models/identity.rb' - 'app/models/identity.rb'
- 'app/models/webauthn_credential.rb' - 'app/models/webauthn_credential.rb'
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/UnusedIgnoredColumns:
Exclude:
- 'app/models/account.rb'
- 'app/models/account_stat.rb'
- 'app/models/admin/action_log.rb'
- 'app/models/custom_filter.rb'
- 'app/models/email_domain_block.rb'
- 'app/models/report.rb'
- 'app/models/status_edit.rb'
- 'app/models/user.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
# SupportedStyles: exists, where # SupportedStyles: exists, where
@ -426,15 +396,6 @@ Style/RedundantFetchBlock:
- 'config/initializers/paperclip.rb' - 'config/initializers/paperclip.rb'
- 'config/puma.rb' - 'config/puma.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
Exclude:
- 'app/controllers/api/v1/directories_controller.rb'
- 'app/controllers/auth/confirmations_controller.rb'
- 'app/lib/ostatus/tag_manager.rb'
- 'app/models/form/import.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try! # AllowedMethods: present?, blank?, presence, try, try!
@ -455,11 +416,6 @@ Style/SingleArgumentDig:
Exclude: Exclude:
- 'lib/webpacker/manifest_extensions.rb' - 'lib/webpacker/manifest_extensions.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts:
Exclude:
- 'config/boot.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode. # Configuration parameters: Mode.
Style/StringConcatenation: Style/StringConcatenation:
@ -478,13 +434,6 @@ Style/StringLiterals:
- 'config/initializers/webauthn.rb' - 'config/initializers/webauthn.rb'
- 'config/routes.rb' - 'config/routes.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments.
# AllowedMethods: define_method, mail, respond_to
Style/SymbolProc:
Exclude:
- 'config/initializers/3_omniauth.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, AllowSafeAssignment. # Configuration parameters: EnforcedStyle, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex

@ -515,7 +515,7 @@ GEM
openssl (> 2.0) openssl (> 2.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.14.17) ox (2.14.17)
parallel (1.23.0) parallel (1.24.0)
parser (3.2.2.4) parser (3.2.2.4)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
@ -679,10 +679,10 @@ GEM
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-factory_bot (2.24.0) rubocop-factory_bot (2.24.0)
rubocop (~> 1.33) rubocop (~> 1.33)
rubocop-performance (1.19.1) rubocop-performance (1.20.0)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 1.30.0, < 2.0)
rubocop-rails (2.22.2) rubocop-rails (2.23.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)

@ -25,6 +25,6 @@ class Api::V1::Accounts::NotesController < Api::BaseController
end end
def relationships_presenter def relationships_presenter
AccountRelationshipsPresenter.new([@account.id], current_user.account_id) AccountRelationshipsPresenter.new([@account], current_user.account_id)
end end
end end

@ -25,6 +25,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController
end end
def relationships_presenter def relationships_presenter
AccountRelationshipsPresenter.new([@account.id], current_user.account_id) AccountRelationshipsPresenter.new([@account], current_user.account_id)
end end
end end

@ -5,7 +5,7 @@ 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') @accounts = Account.where(id: account_ids).select(:id, :domain)
@accounts.merge!(Account.without_suspended) unless truthy_param?(:with_suspended) @accounts.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end end

@ -88,7 +88,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def relationships(**options) def relationships(**options)
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options) AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
end end
def account_params def account_params

@ -12,7 +12,7 @@ class Api::V1::DirectoriesController < Api::BaseController
private private
def require_enabled! def require_enabled!
return not_found unless Setting.profile_directory not_found unless Setting.profile_directory
end end
def set_accounts def set_accounts

@ -25,11 +25,11 @@ class Api::V1::FollowRequestsController < Api::BaseController
private private
def account def account
Account.find(params[:id]) @account ||= Account.find(params[:id])
end end
def relationships(**options) def relationships(**options)
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, **options) AccountRelationshipsPresenter.new([account], current_user.account_id, **options)
end end
def load_accounts def load_accounts

@ -63,7 +63,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
end end
def captcha_user_bypass? def captcha_user_bypass?
return true if @confirmation_user.nil? || @confirmation_user.confirmed? @confirmation_user.nil? || @confirmation_user.confirmed?
end end
def set_pack def set_pack

@ -34,7 +34,7 @@ class RelationshipsController < ApplicationController
end end
def set_relationships def set_relationships
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id) @relationships = AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
end end
def form_account_batch_params def form_account_batch_params

@ -110,7 +110,7 @@ module ApplicationHelper
def can?(action, record) def can?(action, record)
return false if record.nil? return false if record.nil?
policy(record).public_send("#{action}?") policy(record).public_send(:"#{action}?")
end end
def fa_icon(icon, attributes = {}) def fa_icon(icon, attributes = {})

@ -52,7 +52,7 @@ class OStatus::TagManager
ActivityPub::TagManager.instance.uri_to_local_id(tag) ActivityPub::TagManager.instance.uri_to_local_id(tag)
else else
matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag)
return matches[1] unless matches.nil? matches[1] unless matches.nil?
end end
end end

@ -455,8 +455,8 @@ class Account < ApplicationRecord
end end
def inverse_alias(key, original_key) def inverse_alias(key, original_key)
define_method("#{key}=") do |value| define_method(:"#{key}=") do |value|
public_send("#{original_key}=", !ActiveModel::Type::Boolean.new.cast(value)) public_send(:"#{original_key}=", !ActiveModel::Type::Boolean.new.cast(value))
end end
define_method(key) do define_method(key) do

@ -18,16 +18,12 @@ class AccountDomainBlock < ApplicationRecord
belongs_to :account belongs_to :account
validates :domain, presence: true, uniqueness: { scope: :account_id }, domain: true validates :domain, presence: true, uniqueness: { scope: :account_id }, domain: true
after_commit :remove_blocking_cache after_commit :invalidate_domain_blocking_cache
after_commit :remove_relationship_cache
private private
def remove_blocking_cache def invalidate_domain_blocking_cache
Rails.cache.delete("exclude_domains_for:#{account_id}") Rails.cache.delete("exclude_domains_for:#{account_id}")
end Rails.cache.delete(['exclude_domains', account_id, domain])
def remove_relationship_cache
Rails.cache.delete_matched("relationship:#{account_id}:*")
end end
end end

@ -60,12 +60,6 @@ module Account::Interactions
end end
end end
def domain_blocking_map(target_account_ids, account_id)
accounts_map = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain }
blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
accounts_map.reduce({}) { |h, (id, domain)| h.merge(id => blocked_domains[domain]) }
end
def domain_blocking_map_by_domain(target_domains, account_id) def domain_blocking_map_by_domain(target_domains, account_id)
follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain) follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
end end

@ -10,7 +10,7 @@ module RelationshipCacheable
private private
def remove_relationship_cache def remove_relationship_cache
Rails.cache.delete("relationship:#{account_id}:#{target_account_id}") Rails.cache.delete(['relationship', account_id, target_account_id])
Rails.cache.delete("relationship:#{target_account_id}:#{account_id}") Rails.cache.delete(['relationship', target_account_id, account_id])
end end
end end

@ -7,7 +7,7 @@ module Remotable
def remotable_attachment(attachment_name, limit, suppress_errors: true, download_on_assign: true, attribute_name: nil) def remotable_attachment(attachment_name, limit, suppress_errors: true, download_on_assign: true, attribute_name: nil)
attribute_name ||= :"#{attachment_name}_remote_url" attribute_name ||= :"#{attachment_name}_remote_url"
define_method("download_#{attachment_name}!") do |url = nil| define_method(:"download_#{attachment_name}!") do |url = nil|
url ||= self[attribute_name] url ||= self[attribute_name]
return if url.blank? return if url.blank?
@ -24,29 +24,29 @@ module Remotable
Request.new(:get, url).perform do |response| Request.new(:get, url).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless (200...300).cover?(response.code) raise Mastodon::UnexpectedResponseError, response unless (200...300).cover?(response.code)
public_send("#{attachment_name}=", ResponseWithLimit.new(response, limit)) public_send(:"#{attachment_name}=", ResponseWithLimit.new(response, limit))
end end
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
Rails.logger.debug { "Error fetching remote #{attachment_name}: #{e}" } Rails.logger.debug { "Error fetching remote #{attachment_name}: #{e}" }
public_send("#{attachment_name}=", nil) if public_send("#{attachment_name}_file_name").present? public_send(:"#{attachment_name}=", nil) if public_send(:"#{attachment_name}_file_name").present?
raise e unless suppress_errors raise e unless suppress_errors
rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e
Rails.logger.debug { "Error fetching remote #{attachment_name}: #{e}" } Rails.logger.debug { "Error fetching remote #{attachment_name}: #{e}" }
public_send("#{attachment_name}=", nil) if public_send("#{attachment_name}_file_name").present? public_send(:"#{attachment_name}=", nil) if public_send(:"#{attachment_name}_file_name").present?
end end
nil nil
end end
define_method("#{attribute_name}=") do |url| define_method(:"#{attribute_name}=") do |url|
return if self[attribute_name] == url && public_send("#{attachment_name}_file_name").present? return if self[attribute_name] == url && public_send(:"#{attachment_name}_file_name").present?
self[attribute_name] = url if has_attribute?(attribute_name) self[attribute_name] = url if has_attribute?(attribute_name)
public_send("download_#{attachment_name}!", url) if download_on_assign public_send(:"download_#{attachment_name}!", url) if download_on_assign
end end
alias_method("reset_#{attachment_name}!", "download_#{attachment_name}!") alias_method(:"reset_#{attachment_name}!", "download_#{attachment_name}!")
end end
end end
end end

@ -100,7 +100,7 @@ class Form::AdminSettings
KEYS.each do |key| KEYS.each do |key|
define_method(key) do define_method(key) do
return instance_variable_get("@#{key}") if instance_variable_defined?("@#{key}") return instance_variable_get(:"@#{key}") if instance_variable_defined?(:"@#{key}")
stored_value = if UPLOAD_KEYS.include?(key) stored_value = if UPLOAD_KEYS.include?(key)
SiteUpload.where(var: key).first_or_initialize(var: key) SiteUpload.where(var: key).first_or_initialize(var: key)
@ -110,12 +110,12 @@ class Form::AdminSettings
Setting.public_send(key) Setting.public_send(key)
end end
instance_variable_set("@#{key}", stored_value) instance_variable_set(:"@#{key}", stored_value)
end end
end end
UPLOAD_KEYS.each do |key| UPLOAD_KEYS.each do |key|
define_method("#{key}=") do |file| define_method(:"#{key}=") do |file|
value = public_send(key) value = public_send(key)
value.file = file value.file = file
rescue Mastodon::DimensionsValidationError => e rescue Mastodon::DimensionsValidationError => e
@ -130,13 +130,13 @@ class Form::AdminSettings
return false unless errors.empty? && valid? return false unless errors.empty? && valid?
KEYS.each do |key| KEYS.each do |key|
next if PSEUDO_KEYS.include?(key) || !instance_variable_defined?("@#{key}") next if PSEUDO_KEYS.include?(key) || !instance_variable_defined?(:"@#{key}")
if UPLOAD_KEYS.include?(key) if UPLOAD_KEYS.include?(key)
public_send(key).save public_send(key).save
else else
setting = Setting.where(var: key).first_or_initialize(var: key) setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: typecast_value(key, instance_variable_get("@#{key}"))) setting.update(value: typecast_value(key, instance_variable_get(:"@#{key}")))
end end
end end
end end
@ -163,9 +163,9 @@ class Form::AdminSettings
def validate_site_uploads def validate_site_uploads
UPLOAD_KEYS.each do |key| UPLOAD_KEYS.each do |key|
next unless instance_variable_defined?("@#{key}") next unless instance_variable_defined?(:"@#{key}")
upload = instance_variable_get("@#{key}") upload = instance_variable_get(:"@#{key}")
next if upload.valid? next if upload.valid?
upload.errors.each do |error| upload.errors.each do |error|

@ -43,14 +43,19 @@ class Form::Import
validate :validate_data validate :validate_data
def guessed_type def guessed_type
return :muting if csv_headers_match?('Hide notifications') if csv_headers_match?('Hide notifications') || file_name_matches?('mutes') || file_name_matches?('muted_accounts')
return :following if csv_headers_match?('Show boosts') || csv_headers_match?('Notify on new posts') || csv_headers_match?('Languages') :muting
return :following if file_name_matches?('follows') || file_name_matches?('following_accounts') elsif csv_headers_match?('Show boosts') || csv_headers_match?('Notify on new posts') || csv_headers_match?('Languages') || file_name_matches?('follows') || file_name_matches?('following_accounts')
return :blocking if file_name_matches?('blocks') || file_name_matches?('blocked_accounts') :following
return :muting if file_name_matches?('mutes') || file_name_matches?('muted_accounts') elsif file_name_matches?('blocks') || file_name_matches?('blocked_accounts')
return :domain_blocking if file_name_matches?('domain_blocks') || file_name_matches?('blocked_domains') :blocking
return :bookmarks if file_name_matches?('bookmarks') elsif file_name_matches?('domain_blocks') || file_name_matches?('blocked_domains')
return :lists if file_name_matches?('lists') :domain_blocking
elsif file_name_matches?('bookmarks')
:bookmarks
elsif file_name_matches?('lists')
:lists
end
end end
# Whether the uploaded CSV file seems to correspond to a different import type than the one selected # Whether the uploaded CSV file seems to correspond to a different import type than the one selected

@ -5,8 +5,9 @@ class AccountRelationshipsPresenter
:muting, :requested, :requested_by, :domain_blocking, :muting, :requested, :requested_by, :domain_blocking,
:endorsed, :account_note :endorsed, :account_note
def initialize(account_ids, current_account_id, **options) def initialize(accounts, current_account_id, **options)
@account_ids = account_ids.map { |a| a.is_a?(Account) ? a.id : a.to_i } @accounts = accounts.to_a
@account_ids = @accounts.pluck(:id)
@current_account_id = current_account_id @current_account_id = current_account_id
@following = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id)) @following = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
@ -16,10 +17,11 @@ class AccountRelationshipsPresenter
@muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id)) @muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
@requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id)) @requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
@requested_by = cached[:requested_by].merge(Account.requested_by_map(@uncached_account_ids, @current_account_id)) @requested_by = cached[:requested_by].merge(Account.requested_by_map(@uncached_account_ids, @current_account_id))
@domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
@endorsed = cached[:endorsed].merge(Account.endorsed_map(@uncached_account_ids, @current_account_id)) @endorsed = cached[:endorsed].merge(Account.endorsed_map(@uncached_account_ids, @current_account_id))
@account_note = cached[:account_note].merge(Account.account_note_map(@uncached_account_ids, @current_account_id)) @account_note = cached[:account_note].merge(Account.account_note_map(@uncached_account_ids, @current_account_id))
@domain_blocking = domain_blocking_map
cache_uncached! cache_uncached!
@following.merge!(options[:following_map] || {}) @following.merge!(options[:following_map] || {})
@ -36,6 +38,31 @@ class AccountRelationshipsPresenter
private private
def domain_blocking_map
target_domains = @accounts.pluck(:domain).compact.uniq
blocks_by_domain = {}
# Fetch from cache
cache_keys = target_domains.map { |domain| domain_cache_key(domain) }
Rails.cache.read_multi(*cache_keys).each do |key, blocking|
blocks_by_domain[key.last] = blocking
end
uncached_domains = target_domains - blocks_by_domain.keys
# Read uncached values from database
AccountDomainBlock.where(account_id: @current_account_id, domain: uncached_domains).pluck(:domain).each do |domain|
blocks_by_domain[domain] = true
end
# Write database reads to cache
to_cache = uncached_domains.to_h { |domain| [domain_cache_key(domain), blocks_by_domain[domain]] }
Rails.cache.write_multi(to_cache, expires_in: 1.day)
# Return formatted value
@accounts.each_with_object({}) { |account, h| h[account.id] = blocks_by_domain[account.domain] }
end
def cached def cached
return @cached if defined?(@cached) return @cached if defined?(@cached)
@ -47,28 +74,23 @@ class AccountRelationshipsPresenter
muting: {}, muting: {},
requested: {}, requested: {},
requested_by: {}, requested_by: {},
domain_blocking: {},
endorsed: {}, endorsed: {},
account_note: {}, account_note: {},
} }
@uncached_account_ids = [] @uncached_account_ids = @account_ids.uniq
@account_ids.each do |account_id| cache_ids = @account_ids.map { |account_id| relationship_cache_key(account_id) }
maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}") Rails.cache.read_multi(*cache_ids).each do |key, maps_for_account|
@cached.deep_merge!(maps_for_account)
if maps_for_account.is_a?(Hash) @uncached_account_ids.delete(key.last)
@cached.deep_merge!(maps_for_account)
else
@uncached_account_ids << account_id
end
end end
@cached @cached
end end
def cache_uncached! def cache_uncached!
@uncached_account_ids.each do |account_id| to_cache = @uncached_account_ids.to_h do |account_id|
maps_for_account = { maps_for_account = {
following: { account_id => following[account_id] }, following: { account_id => following[account_id] },
followed_by: { account_id => followed_by[account_id] }, followed_by: { account_id => followed_by[account_id] },
@ -77,12 +99,21 @@ class AccountRelationshipsPresenter
muting: { account_id => muting[account_id] }, muting: { account_id => muting[account_id] },
requested: { account_id => requested[account_id] }, requested: { account_id => requested[account_id] },
requested_by: { account_id => requested_by[account_id] }, requested_by: { account_id => requested_by[account_id] },
domain_blocking: { account_id => domain_blocking[account_id] },
endorsed: { account_id => endorsed[account_id] }, endorsed: { account_id => endorsed[account_id] },
account_note: { account_id => account_note[account_id] }, account_note: { account_id => account_note[account_id] },
} }
Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day) [relationship_cache_key(account_id), maps_for_account]
end end
Rails.cache.write_multi(to_cache, expires_in: 1.day)
end
def domain_cache_key(domain)
['exclude_domains', @current_account_id, domain]
end
def relationship_cache_key(account_id)
['relationship', @current_account_id, account_id]
end end
end end

@ -6,8 +6,8 @@
%p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible') %p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account) = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account)
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote? = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote?
- if deletion_request.present? - if deletion_request.present? && can?(:destroy, account)
= link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, account) = link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') }
- else - else
.action-buttons .action-buttons
%div %div
@ -15,8 +15,8 @@
= link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account) = link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account)
- if account.user_disabled? - if account.user_disabled?
= link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user) = link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user)
- else - elsif can?(:disable, account.user)
= link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button' if can?(:disable, account.user) = link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button'
- if account.sensitized? - if account.sensitized?
= link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account) = link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account)
- elsif !account.local? || account.user_approved? - elsif !account.local? || account.user_approved?
@ -29,13 +29,13 @@
- if account.user_pending? - if account.user_pending?
= link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user) = link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user)
= link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user) = link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
- unless account.user_confirmed? - if !account.user_confirmed? && can?(:confirm, account.user)
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button' if can?(:confirm, account.user) = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button'
- if !account.local? || account.user_approved? - if (!account.local? || account.user_approved?) && can?(:suspend, account)
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button' if can?(:suspend, account) = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button'
%div %div
- if account.local? - if account.local?
- if !account.memorial? && account.user_approved? - if !account.memorial? && account.user_approved? && can?(:memorialize, account)
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, account) = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
- else - elsif can?(:redownload, account)
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button'

@ -47,8 +47,8 @@
- else - else
= t 'admin.accounts.security_measures.only_password' = t 'admin.accounts.security_measures.only_password'
%td %td
- if account.user&.two_factor_enabled? - if account.user&.two_factor_enabled? && can?(:disable_2fa, account.user)
= table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(account.user.id), method: :delete if can?(:disable_2fa, account.user) = table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(account.user.id), method: :delete
- if can?(:reset_password, account.user) - if can?(:reset_password, account.user)
%tr %tr
%td %td

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
unless ENV.key?('RAILS_ENV') unless ENV.key?('RAILS_ENV')
STDERR.puts 'ERROR: Missing RAILS_ENV environment variable, please set it to "production", "development", or "test".' warn 'ERROR: Missing RAILS_ENV environment variable, please set it to "production", "development", or "test".'
exit 1 exit 1
end end

@ -79,7 +79,7 @@ Devise.setup do |config|
oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] # OPTIONAL (default: basic) oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] # OPTIONAL (default: basic)
scope_string = ENV['OIDC_SCOPE'] if ENV['OIDC_SCOPE'] # NEED scope_string = ENV['OIDC_SCOPE'] if ENV['OIDC_SCOPE'] # NEED
scopes = scope_string.split(',') scopes = scope_string.split(',')
oidc_options[:scope] = scopes.map { |x| x.to_sym } oidc_options[:scope] = scopes.map(&:to_sym)
oidc_options[:response_type] = ENV['OIDC_RESPONSE_TYPE'] if ENV['OIDC_RESPONSE_TYPE'] # OPTIONAL (default: code) oidc_options[:response_type] = ENV['OIDC_RESPONSE_TYPE'] if ENV['OIDC_RESPONSE_TYPE'] # OPTIONAL (default: code)
oidc_options[:response_mode] = ENV['OIDC_RESPONSE_MODE'] if ENV['OIDC_RESPONSE_MODE'] # OPTIONAL (default: query) oidc_options[:response_mode] = ENV['OIDC_RESPONSE_MODE'] if ENV['OIDC_RESPONSE_MODE'] # OPTIONAL (default: query)
oidc_options[:display] = ENV['OIDC_DISPLAY'] if ENV['OIDC_DISPLAY'] # OPTIONAL (default: page) oidc_options[:display] = ENV['OIDC_DISPLAY'] if ENV['OIDC_DISPLAY'] # OPTIONAL (default: page)

@ -0,0 +1,5 @@
# frozen_string_literal: true
Fabricator(:custom_emoji_category) do
name { sequence(:name) { |i| "name_#{i}" } }
end

@ -178,11 +178,11 @@ RSpec.describe Remotable do
allow(foo).to receive(:public_send) allow(foo).to receive(:public_send)
foo.hoge_remote_url = url foo.hoge_remote_url = url
expect(foo).to have_received(:public_send).with("download_#{hoge}!", url) expect(foo).to have_received(:public_send).with(:"download_#{hoge}!", url)
allow(foo).to receive(:public_send) allow(foo).to receive(:public_send)
foo.download_hoge!(url) foo.download_hoge!(url)
expect(foo).to have_received(:public_send).with("#{hoge}=", response_with_limit) expect(foo).to have_received(:public_send).with(:"#{hoge}=", response_with_limit)
end end
end end
end end

@ -0,0 +1,118 @@
# frozen_string_literal: true
require 'rails_helper'
describe Form::CustomEmojiBatch do
describe '#save' do
subject { described_class.new({ current_account: account }.merge(options)) }
let(:options) { {} }
let(:account) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
context 'with empty custom_emoji_ids' do
let(:options) { { custom_emoji_ids: [] } }
it 'does nothing if custom_emoji_ids is empty' do
expect(subject.save).to be_nil
end
end
describe 'the update action' do
let(:custom_emoji) { Fabricate(:custom_emoji, category: Fabricate(:custom_emoji_category)) }
let(:custom_emoji_category) { Fabricate(:custom_emoji_category) }
context 'without anything to change' do
let(:options) { { action: 'update' } }
it 'silently exits without updating any custom emojis' do
expect { subject.save }.to_not change(Admin::ActionLog, :count)
end
end
context 'with a category_id' do
let(:options) { { action: 'update', custom_emoji_ids: [custom_emoji.id], category_id: custom_emoji_category.id } }
it 'updates the category of the emoji' do
subject.save
expect(custom_emoji.reload.category).to eq(custom_emoji_category)
end
end
context 'with a category_name' do
let(:options) { { action: 'update', custom_emoji_ids: [custom_emoji.id], category_name: custom_emoji_category.name } }
it 'updates the category of the emoji' do
subject.save
expect(custom_emoji.reload.category).to eq(custom_emoji_category)
end
end
end
describe 'the list action' do
let(:custom_emoji) { Fabricate(:custom_emoji, visible_in_picker: false) }
let(:options) { { action: 'list', custom_emoji_ids: [custom_emoji.id] } }
it 'updates the picker visibility of the emoji' do
subject.save
expect(custom_emoji.reload.visible_in_picker).to be(true)
end
end
describe 'the unlist action' do
let(:custom_emoji) { Fabricate(:custom_emoji, visible_in_picker: true) }
let(:options) { { action: 'unlist', custom_emoji_ids: [custom_emoji.id] } }
it 'updates the picker visibility of the emoji' do
subject.save
expect(custom_emoji.reload.visible_in_picker).to be(false)
end
end
describe 'the enable action' do
let(:custom_emoji) { Fabricate(:custom_emoji, disabled: true) }
let(:options) { { action: 'enable', custom_emoji_ids: [custom_emoji.id] } }
it 'updates the disabled value of the emoji' do
subject.save
expect(custom_emoji.reload).to_not be_disabled
end
end
describe 'the disable action' do
let(:custom_emoji) { Fabricate(:custom_emoji, visible_in_picker: false) }
let(:options) { { action: 'disable', custom_emoji_ids: [custom_emoji.id] } }
it 'updates the disabled value of the emoji' do
subject.save
expect(custom_emoji.reload).to be_disabled
end
end
describe 'the copy action' do
let(:custom_emoji) { Fabricate(:custom_emoji) }
let(:options) { { action: 'copy', custom_emoji_ids: [custom_emoji.id] } }
it 'makes a copy of the emoji' do
expect { subject.save }
.to change(CustomEmoji, :count).by(1)
end
end
describe 'the delete action' do
let(:custom_emoji) { Fabricate(:custom_emoji) }
let(:options) { { action: 'delete', custom_emoji_ids: [custom_emoji.id] } }
it 'destroys the emoji' do
subject.save
expect { custom_emoji.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end

@ -5,19 +5,18 @@ require 'rails_helper'
RSpec.describe AccountRelationshipsPresenter do RSpec.describe AccountRelationshipsPresenter do
describe '.initialize' do describe '.initialize' do
before do before do
allow(Account).to receive(:following_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:following_map).with(accounts.pluck(:id), current_account_id).and_return(default_map)
allow(Account).to receive(:followed_by_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:followed_by_map).with(accounts.pluck(:id), current_account_id).and_return(default_map)
allow(Account).to receive(:blocking_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:blocking_map).with(accounts.pluck(:id), current_account_id).and_return(default_map)
allow(Account).to receive(:muting_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:muting_map).with(accounts.pluck(:id), current_account_id).and_return(default_map)
allow(Account).to receive(:requested_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:requested_map).with(accounts.pluck(:id), current_account_id).and_return(default_map)
allow(Account).to receive(:requested_by_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:requested_by_map).with(accounts.pluck(:id), current_account_id).and_return(default_map)
allow(Account).to receive(:domain_blocking_map).with(account_ids, current_account_id).and_return(default_map)
end end
let(:presenter) { described_class.new(account_ids, current_account_id, **options) } let(:presenter) { described_class.new(accounts, current_account_id, **options) }
let(:current_account_id) { Fabricate(:account).id } let(:current_account_id) { Fabricate(:account).id }
let(:account_ids) { [Fabricate(:account).id] } let(:accounts) { [Fabricate(:account)] }
let(:default_map) { { 1 => true } } let(:default_map) { { accounts[0].id => true } }
context 'when options are not set' do context 'when options are not set' do
let(:options) { {} } let(:options) { {} }
@ -29,7 +28,33 @@ RSpec.describe AccountRelationshipsPresenter do
blocking: default_map, blocking: default_map,
muting: default_map, muting: default_map,
requested: default_map, requested: default_map,
domain_blocking: default_map domain_blocking: { accounts[0].id => nil }
)
end
end
context 'with a warm cache' do
let(:options) { {} }
before do
described_class.new(accounts, current_account_id, **options)
allow(Account).to receive(:following_map).with([], current_account_id).and_return({})
allow(Account).to receive(:followed_by_map).with([], current_account_id).and_return({})
allow(Account).to receive(:blocking_map).with([], current_account_id).and_return({})
allow(Account).to receive(:muting_map).with([], current_account_id).and_return({})
allow(Account).to receive(:requested_map).with([], current_account_id).and_return({})
allow(Account).to receive(:requested_by_map).with([], current_account_id).and_return({})
end
it 'sets returns expected values' do
expect(presenter).to have_attributes(
following: default_map,
followed_by: default_map,
blocking: default_map,
muting: default_map,
requested: default_map,
domain_blocking: { accounts[0].id => nil }
) )
end end
end end
@ -86,7 +111,7 @@ RSpec.describe AccountRelationshipsPresenter do
let(:options) { { domain_blocking_map: { 7 => true } } } let(:options) { { domain_blocking_map: { 7 => true } } }
it 'sets @domain_blocking merged with default_map and options[:domain_blocking_map]' do it 'sets @domain_blocking merged with default_map and options[:domain_blocking_map]' do
expect(presenter.domain_blocking).to eq default_map.merge(options[:domain_blocking_map]) expect(presenter.domain_blocking).to eq({ accounts[0].id => nil }.merge(options[:domain_blocking_map]))
end end
end end
end end

@ -4,7 +4,7 @@ require 'rails_helper'
describe 'OmniAuth callbacks' do describe 'OmniAuth callbacks' do
shared_examples 'omniauth provider callbacks' do |provider| shared_examples 'omniauth provider callbacks' do |provider|
subject { post send "user_#{provider}_omniauth_callback_path" } subject { post send :"user_#{provider}_omniauth_callback_path" }
context 'with full information in response' do context 'with full information in response' do
before do before do

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save