Merge pull request #2562 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to 0a7cff53c2
master
Claire 2024-01-11 18:41:33 +01:00 committed by GitHub
commit cbc951627c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 763 additions and 686 deletions

View File

@ -1,20 +1,13 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2024-01-08 14:02:57 -0500 using Haml-Lint version 0.53.0.
# on 2024-01-09 11:30:07 -0500 using Haml-Lint version 0.53.0.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.
linters:
# Offense count: 10
# Offense count: 1
LineLength:
exclude:
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/auth/registrations/edit.html.haml'
- 'app/views/auth/registrations/new.html.haml'
- 'app/views/media/player.html.haml'
- 'app/views/settings/applications/_fields.html.haml'
- 'app/views/settings/imports/index.html.haml'
- 'app/views/settings/preferences/appearance/show.html.haml'
- 'app/views/settings/preferences/other/show.html.haml'

View File

@ -48,6 +48,7 @@ gem 'omniauth', '~> 2.0'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
gem 'color_diff', '~> 0.1'
gem 'csv', '~> 3.2'
gem 'discard', '~> 1.2'
gem 'doorkeeper', '~> 5.6'
gem 'ed25519', '~> 1.3'
@ -75,7 +76,6 @@ gem 'premailer-rails'
gem 'rack-attack', '~> 6.6'
gem 'rack-cors', '~> 2.0', require: 'rack/cors'
gem 'rails-i18n', '~> 7.0'
gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-settings-cached.git', branch: 'v0.6.6-aliases-true'
gem 'redcarpet', '~> 3.6'
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'

View File

@ -18,14 +18,6 @@ GIT
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
GIT
remote: https://github.com/mastodon/rails-settings-cached.git
revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab
branch: v0.6.6-aliases-true
specs:
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
GIT
remote: https://github.com/stanhu/omniauth-cas.git
revision: 4211e6d05941b4a981f9a36b49ec166cecd0e271
@ -216,6 +208,7 @@ GEM
crass (1.0.6)
css_parser (1.14.0)
addressable
csv (3.2.8)
database_cleaner-active_record (2.1.0)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
@ -272,7 +265,7 @@ GEM
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.104.0)
excon (0.109.0)
fabrication (2.31.0)
faker (3.2.2)
i18n (>= 1.8.11, < 2)
@ -307,7 +300,7 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
fog-core (2.3.0)
fog-core (2.4.0)
builder
excon (~> 0.71)
formatador (>= 0.2, < 2.0)
@ -378,9 +371,9 @@ GEM
terminal-table (>= 1.5.1)
idn-ruby (0.1.5)
io-console (0.7.1)
irb (1.11.0)
irb (1.11.1)
rdoc
reline (>= 0.3.8)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.7.1)
json-canonicalization (1.0.0)
@ -624,7 +617,7 @@ GEM
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.8.3)
reline (0.4.1)
reline (0.4.2)
io-console (~> 0.5)
request_store (1.5.1)
rack (>= 1.4)
@ -692,7 +685,7 @@ GEM
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-prof (1.6.3)
ruby-prof (1.7.0)
ruby-progressbar (1.13.0)
ruby-saml (1.15.0)
nokogiri (>= 1.13.10)
@ -724,7 +717,7 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.1.30)
sidekiq-unique-jobs (7.1.31)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (< 5.0)
@ -853,6 +846,7 @@ DEPENDENCIES
color_diff (~> 0.1)
concurrent-ruby
connection_pool
csv (~> 3.2)
database_cleaner-active_record
debug (~> 1.8)
devise (~> 4.9)
@ -922,7 +916,6 @@ DEPENDENCIES
rails (~> 7.1.1)
rails-controller-testing (~> 1.0)
rails-i18n (~> 7.0)
rails-settings-cached (~> 0.6)!
rdf-normalize (~> 0.5)
redcarpet (~> 3.6)
redis (~> 4.5)

View File

@ -2,7 +2,7 @@
class Api::V1::StreamingController < Api::BaseController
def index
if Rails.configuration.x.streaming_api_base_url == request.host
if same_host?
not_found
else
redirect_to streaming_api_url, status: 301, allow_other_host: true
@ -11,6 +11,11 @@ class Api::V1::StreamingController < Api::BaseController
private
def same_host?
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
request.host == base_url.host && request.port == (base_url.port || 80)
end
def streaming_api_url
Addressable::URI.parse(request.url).tap do |uri|
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)

View File

@ -224,7 +224,7 @@ module LanguagesHelper
'en-GB': 'English (British)',
'es-AR': 'Español (Argentina)',
'es-MX': 'Español (México)',
'fr-QC': 'Français (Canadien)',
'fr-CA': 'Français (Canadien)',
'pt-BR': 'Português (Brasil)',
'pt-PT': 'Português (Portugal)',
'sr-Latn': 'Srpski (latinica)',

View File

@ -1,80 +0,0 @@
# frozen_string_literal: true
module Settings
class ScopedSettings
DEFAULTING_TO_UNSCOPED = %w(
flavour
skin
noindex
).freeze
def initialize(object)
@object = object
end
def method_missing(method, *args)
method_name = method.to_s
# set a value for a variable
if method_name[-1] == '='
var_name = method_name.sub('=', '')
value = args.first
self[var_name] = value
else
# retrieve a value
self[method_name]
end
end
def respond_to_missing?(*)
true
end
def all_as_records
vars = thing_scoped
records = vars.index_by(&:var)
Setting.default_settings.each do |key, default_value|
next if records.key?(key) || default_value.is_a?(Hash)
records[key] = Setting.new(var: key, value: default_value)
end
records
end
def []=(key, value)
key = key.to_s
record = thing_scoped.find_or_initialize_by(var: key)
record.update!(value: value)
Rails.cache.write(Setting.cache_key(key, @object), value)
end
def [](key)
Rails.cache.fetch(Setting.cache_key(key, @object)) do
db_val = thing_scoped.find_by(var: key.to_s)
if db_val
default_value = ScopedSettings.default_settings[key]
return default_value.with_indifferent_access.merge!(db_val.value) if default_value.is_a?(Hash)
db_val.value
else
ScopedSettings.default_settings[key]
end
end
end
class << self
def default_settings
defaulting = DEFAULTING_TO_UNSCOPED.index_with { |k| Setting[k] }
Setting.default_settings.merge!(defaulting)
end
end
protected
def thing_scoped
Setting.unscoped.where(thing_type: @object.class.base_class.to_s, thing_id: @object.id)
end
end
end

View File

@ -13,49 +13,114 @@
# thing_id :bigint(8)
#
class Setting < RailsSettings::Base
source Rails.root.join('config', 'settings.yml')
# This file is derived from a fork of the `rails-settings-cached` gem available at
# https://github.com/mastodon/rails-settings-cached/tree/v0.6.6-aliases-true, with
# the original available at:
# https://github.com/huacnlee/rails-settings-cached/tree/0.x
# It is licensed as follows:
# Copyright (c) 2006 Alex Wayne
# Some additional features added 2009 by Georg Ledermann
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
# NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
class Setting < ApplicationRecord
after_commit :rewrite_cache, on: %i(create update)
after_commit :expire_cache, on: %i(destroy)
# Settings are server-wide settings only, but they were previously
# used for users too. This can be dropped later with a database
# migration dropping any scoped setting.
default_scope { where(thing_type: nil, thing_id: nil) }
class << self
# get or set a variable with the variable as the called method
# rubocop:disable Style/MissingRespondToMissing
def method_missing(method, *args)
# set a value for a variable
if method.end_with?('=')
var_name = method.to_s.chomp('=')
value = args.first
self[var_name] = value
else
# retrieve a value
self[method.to_s]
end
end
# rubocop:enable Style/MissingRespondToMissing
def cache_prefix_by_startup
@cache_prefix_by_startup ||= Digest::MD5.hexdigest(default_settings.to_s)
end
def cache_key(var_name)
"rails_settings_cached/#{cache_prefix_by_startup}/#{var_name}"
end
def [](key)
Rails.cache.fetch(cache_key(key)) do
db_val = find_by(var: key)
db_val ? db_val.value : default_settings[key]
end
end
# set a setting value by [] notation
def []=(var_name, value)
record = find_or_initialize_by(var: var_name.to_s)
record.value = value
record.save!
end
def default_settings
return @default_settings if defined?(@default_settings)
content = Rails.root.join('config', 'settings.yml').read
hash = content.empty? ? {} : YAML.safe_load(ERB.new(content).result, aliases: true).to_hash
@default_settings = (hash[Rails.env] || {}).freeze
end
end
# get the value field, YAML decoded
def value
YAML.safe_load(self[:value], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol]) if self[:value].present?
end
# set the value field, YAML encoded
def value=(new_value)
self[:value] = new_value.to_yaml
end
def rewrite_cache
Rails.cache.write(cache_key, value)
end
def expire_cache
Rails.cache.delete(cache_key)
end
def cache_key
self.class.cache_key(var)
end
def to_param
var
end
class << self
def [](key)
return super(key) unless rails_initialized?
Rails.cache.fetch(cache_key(key, nil)) do
db_val = object(key)
if db_val
default_value = default_settings[key]
return default_value.with_indifferent_access.merge!(db_val.value) if default_value.is_a?(Hash)
db_val.value
else
default_settings[key]
end
end
end
def all_as_records
vars = thing_scoped
records = vars.index_by(&:var)
default_settings.each do |key, default_value|
next if records.key?(key) || default_value.is_a?(Hash)
records[key] = Setting.new(var: key, value: default_value)
end
records
end
def default_settings
return {} unless RailsSettings::Default.enabled?
RailsSettings::Default.instance
end
end
end

View File

@ -118,14 +118,15 @@ class User < ApplicationRecord
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') }
before_validation :sanitize_languages
before_validation :sanitize_role
before_validation :sanitize_time_zone
before_validation :sanitize_locale
before_create :set_approved
after_commit :send_pending_devise_notifications
after_create_commit :trigger_webhooks
normalizes :locale, with: ->(locale) { I18n.available_locales.exclude?(locale.to_sym) ? nil : locale }
normalizes :time_zone, with: ->(time_zone) { ActiveSupport::TimeZone[time_zone].nil? ? nil : time_zone }
normalizes :chosen_languages, with: ->(chosen_languages) { chosen_languages.compact_blank.presence }
# This avoids a deprecation warning from Rails 5.1
# It seems possible that a future release of devise-two-factor will
# handle this itself, and this can be removed from our User class.
@ -447,25 +448,10 @@ class User < ApplicationRecord
@bypass_invite_request_check
end
def sanitize_languages
return if chosen_languages.nil?
chosen_languages.compact_blank!
self.chosen_languages = nil if chosen_languages.empty?
end
def sanitize_role
self.role = nil if role.present? && role.everyone?
end
def sanitize_time_zone
self.time_zone = nil if time_zone.present? && ActiveSupport::TimeZone[time_zone].nil?
end
def sanitize_locale
self.locale = nil if locale.present? && I18n.available_locales.exclude?(locale.to_sym)
end
def prepare_new_user!
BootstrapTimelineWorker.perform_async(account_id)
ActivityTracker.increment('activity:accounts:local')

View File

@ -5,7 +5,11 @@
= f.input :return_to, as: :hidden
.field-group
= f.input :current_password, wrapper: :with_block_label, input_html: { autocomplete: 'current-password', autofocus: true }, label: t('challenge.prompt'), required: true
= f.input :current_password,
input_html: { autocomplete: 'current-password', autofocus: true },
label: t('challenge.prompt'),
required: true,
wrapper: :with_block_label
.actions
= f.button :button, t('challenge.confirm'), type: :submit

View File

@ -21,7 +21,13 @@
= render 'shared/error_messages', object: resource
.fields-group
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, readonly: current_user.present?, hint: current_user.present? && t('auth.confirmations.wrong_email_hint')
= f.input :email,
autofocus: true,
hint: current_user.present? && t('auth.confirmations.wrong_email_hint'),
input_html: { 'aria-label': t('simple_form.labels.defaults.email') },
label: t('simple_form.labels.defaults.email'),
readonly: current_user.present?,
wrapper: :with_label
.actions
= f.button :button, t('auth.resend_confirmation'), type: :submit

View File

@ -8,9 +8,18 @@
= f.input :reset_password_token, as: :hidden
.fields-group
= f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.new_password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, required: true
= f.input :password,
autofocus: true,
input_html: { 'aria-label': t('simple_form.labels.defaults.new_password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last },
label: t('simple_form.labels.defaults.new_password'),
required: true,
wrapper: :with_label
.fields-group
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_new_password'), autocomplete: 'new-password' }, required: true
= f.input :password_confirmation,
input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_new_password'), autocomplete: 'new-password' },
label: t('simple_form.labels.defaults.confirm_new_password'),
required: true,
wrapper: :with_label
.actions
= f.button :button, t('auth.set_new_password'), type: :submit

View File

@ -5,7 +5,12 @@
= render 'shared/error_messages', object: resource
.fields-group
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, hint: false
= f.input :email,
autofocus: true,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.email') },
label: t('simple_form.labels.defaults.email'),
wrapper: :with_label
.actions
= f.button :button, t('auth.reset_password'), type: :submit

View File

@ -5,7 +5,9 @@
= fa_icon 'warning'
.log-entry__content
.log-entry__title
= t('disputes.strikes.title', action: t(account_warning.action, scope: 'disputes.strikes.title_actions'), date: l(account_warning.created_at.to_date))
= t 'disputes.strikes.title',
action: t(account_warning.action, scope: 'disputes.strikes.title_actions'),
date: l(account_warning.created_at.to_date)
.log-entry__timestamp
%time.formatted{ datetime: account_warning.created_at.iso8601 }= l(account_warning.created_at)

View File

@ -3,7 +3,9 @@
%span{ title: session.user_agent }<
= fa_icon "#{session_device_icon(session)} fw", 'aria-label': session_device_icon(session)
&nbsp;
= t 'sessions.description', browser: t("sessions.browsers.#{session.browser}", default: session.browser.to_s), platform: t("sessions.platforms.#{session.platform}", default: session.platform.to_s)
= t 'sessions.description',
browser: t("sessions.browsers.#{session.browser}", default: session.browser.to_s),
platform: t("sessions.platforms.#{session.platform}", default: session.platform.to_s)
%td
%samp= session.ip
%td

View File

@ -15,15 +15,33 @@
- if (!use_seamless_external_login? || resource.encrypted_password.present?) && !omniauth_only?
.fields-row
.fields-row__column.fields-group.fields-row__column-6
= f.input :email, wrapper: :with_label, input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
= f.input :email,
disabled: current_account.suspended?,
input_html: { 'aria-label': t('simple_form.labels.defaults.email') },
required: true,
wrapper: :with_label
.fields-row__column.fields-group.fields-row__column-6
= f.input :current_password, wrapper: :with_label, input_html: { 'aria-label': t('simple_form.labels.defaults.current_password'), autocomplete: 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
= f.input :current_password,
disabled: current_account.suspended?,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.current_password'), autocomplete: 'current-password' },
required: true,
wrapper: :with_label
.fields-row
.fields-row__column.fields-group.fields-row__column-6
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.new_password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
= f.input :password,
disabled: current_account.suspended?,
hint: t('simple_form.hints.defaults.password'),
input_html: { 'aria-label': t('simple_form.labels.defaults.new_password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last },
label: t('simple_form.labels.defaults.new_password'),
wrapper: :with_label
.fields-row__column.fields-group.fields-row__column-6
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_new_password'), autocomplete: 'new-password' }, disabled: current_account.suspended?
= f.input :password_confirmation,
disabled: current_account.suspended?,
input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_new_password'), autocomplete: 'new-password' },
label: t('simple_form.labels.defaults.confirm_new_password'),
wrapper: :with_label
.actions
= f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?

View File

@ -19,25 +19,61 @@
.fields-group
= f.simple_fields_for :account do |ff|
= ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}"
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'username' }, hint: false
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, hint: false
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password' }, hint: false
= f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), autocomplete: 'off' }, hint: false
= f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: 'Website'), autocomplete: 'off' }
= ff.input :username,
append: "@#{site_hostname}",
input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 },
label: false,
required: true,
wrapper: :with_label
= f.input :email,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'username' },
placeholder: t('simple_form.labels.defaults.email'),
required: true
= f.input :password,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last },
placeholder: t('simple_form.labels.defaults.password'),
required: true
= f.input :password_confirmation,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password' },
placeholder: t('simple_form.labels.defaults.confirm_password'),
required: true
= f.input :confirm_password,
as: :string,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), autocomplete: 'off' },
placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')),
required: false
= f.input :website,
as: :url,
input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: 'Website'), autocomplete: 'off' },
label: t('simple_form.labels.defaults.honeypot', label: 'Website'),
required: false,
wrapper: :with_label
- if approved_registrations? && @invite.blank?
%p.lead= t('auth.sign_up.manual_review', domain: site_hostname)
.fields-group
= f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields|
= invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text, label: false, hint: false
= invite_request_fields.input :text,
as: :text,
hint: false,
label: false,
required: Setting.require_invite_text,
wrapper: :with_block_label
= hidden_field_tag :accept, params[:accept]
= f.input :invite_code, as: :hidden
.fields-group
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.privacy_policy_agreement_html', rules_path: about_more_path, privacy_policy_path: privacy_policy_path), required: true
= f.input :agreement,
as: :boolean,
label: t('auth.privacy_policy_agreement_html', rules_path: about_more_path, privacy_policy_path: privacy_policy_path),
required: true,
wrapper: :with_label
.actions
= f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit

View File

@ -10,11 +10,25 @@
%p.lead= t('auth.sign_in.preamble_html', domain: site_hostname)
.fields-group
- if use_seamless_external_login?
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.username_or_email'), input_html: { 'aria-label': t('simple_form.labels.defaults.username_or_email') }, hint: false
= f.input :email,
autofocus: true,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.username_or_email') },
label: t('simple_form.labels.defaults.username_or_email'),
wrapper: :with_label
- else
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, hint: false
= f.input :email,
autofocus: true,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.email') },
label: t('simple_form.labels.defaults.email'),
wrapper: :with_label
.fields-group
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'current-password' }, hint: false
= f.input :password,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'current-password' },
label: t('simple_form.labels.defaults.password'),
wrapper: :with_label
.actions
= f.button :button, t('auth.login'), type: :submit

View File

@ -5,7 +5,12 @@
%p.hint.authentication-hint= t('simple_form.hints.sessions.otp')
.fields-group
= f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label': t('simple_form.labels.defaults.otp_attempt'), autocomplete: 'one-time-code' }, autofocus: true
= f.input :otp_attempt,
autofocus: true,
input_html: { 'aria-label': t('simple_form.labels.defaults.otp_attempt'), autocomplete: 'one-time-code' },
label: t('simple_form.labels.defaults.otp_attempt'),
type: :number,
wrapper: :with_label
.actions
= f.button :button, t('auth.login'), type: :submit

View File

@ -14,7 +14,10 @@
%p.lead= t('auth.setup.email_below_hint_html')
.fields-group
= f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' }
= f.input :email,
hint: false,
input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' },
required: true
.actions
= f.button :button, t('auth.resend_confirmation'), type: :submit, class: 'button timer-button', disabled: true

View File

@ -13,14 +13,39 @@
meta = @media_attachment.file.meta || {}
- if @media_attachment.video?
= react_component :video, src: @media_attachment.file.url(:original), preview: @media_attachment.thumbnail.present? ? @media_attachment.thumbnail.url : @media_attachment.file.url(:small), frameRate: meta.dig('original', 'frame_rate'), blurhash: @media_attachment.blurhash, width: 670, height: 380, editable: true, detailed: true, inline: true, alt: @media_attachment.description, media: [ActiveModelSerializers::SerializableResource.new(@media_attachment, serializer: REST::MediaAttachmentSerializer)].as_json do
= react_component :video,
alt: @media_attachment.description,
blurhash: @media_attachment.blurhash,
detailed: true,
editable: true,
frameRate: meta.dig('original', 'frame_rate'),
height: 380,
inline: true,
media: [ActiveModelSerializers::SerializableResource.new(@media_attachment, serializer: REST::MediaAttachmentSerializer)].as_json,
preview: @media_attachment.thumbnail.present? ? @media_attachment.thumbnail.url : @media_attachment.file.url(:small),
src: @media_attachment.file.url(:original),
width: 670 do
%video{ controls: 'controls' }
%source{ src: @media_attachment.file.url(:original) }
- elsif @media_attachment.gifv?
= react_component :media_gallery, height: 380, standalone: true, autoplay: true, media: [ActiveModelSerializers::SerializableResource.new(@media_attachment, serializer: REST::MediaAttachmentSerializer).as_json] do
= react_component :media_gallery,
autoplay: true,
height: 380,
media: [ActiveModelSerializers::SerializableResource.new(@media_attachment, serializer: REST::MediaAttachmentSerializer).as_json],
standalone: true do
%video{ autoplay: 'autoplay', muted: 'muted', loop: 'loop' }
%source{ src: @media_attachment.file.url(:original) }
- elsif @media_attachment.audio?
= react_component :audio, src: @media_attachment.file.url(:original), poster: @media_attachment.thumbnail.present? ? @media_attachment.thumbnail.url : @media_attachment.account.avatar_static_url, backgroundColor: meta.dig('colors', 'background'), foregroundColor: meta.dig('colors', 'foreground'), accentColor: meta.dig('colors', 'accent'), width: 670, height: 380, fullscreen: true, alt: @media_attachment.description, duration: meta.dig(:original, :duration) do
= react_component :audio,
accentColor: meta.dig('colors', 'accent'),
alt: @media_attachment.description,
backgroundColor: meta.dig('colors', 'background'),
duration: meta.dig(:original, :duration),
foregroundColor: meta.dig('colors', 'foreground'),
fullscreen: true,
height: 380,
poster: @media_attachment.thumbnail.present? ? @media_attachment.thumbnail.url : @media_attachment.account.avatar_static_url,
src: @media_attachment.file.url(:original),
width: 670 do
%audio{ controls: 'controls' }
%source{ src: @media_attachment.file.url(:original) }

View File

@ -1,11 +1,17 @@
.fields-group
= f.input :name, wrapper: :with_label, label: t('activerecord.attributes.doorkeeper/application.name')
= f.input :name,
label: t('activerecord.attributes.doorkeeper/application.name'),
wrapper: :with_label
.fields-group
= f.input :website, wrapper: :with_label, label: t('activerecord.attributes.doorkeeper/application.website')
= f.input :website,
label: t('activerecord.attributes.doorkeeper/application.website'),
wrapper: :with_label
.fields-group
= f.input :redirect_uri, wrapper: :with_block_label, label: t('activerecord.attributes.doorkeeper/application.redirect_uri'), hint: t('doorkeeper.applications.help.redirect_uri')
= f.input :redirect_uri,
label: t('activerecord.attributes.doorkeeper/application.redirect_uri'), hint: t('doorkeeper.applications.help.redirect_uri'),
wrapper: :with_block_label
%p.hint= t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: content_tag(:code, Doorkeeper.configuration.native_redirect_uri)).html_safe
@ -15,4 +21,15 @@
%span.hint= t('simple_form.hints.defaults.scopes')
- Doorkeeper.configuration.scopes.group_by { |s| s.split(':').first }.each_value do |value|
= f.input :scopes, label: false, hint: false, collection: value.sort, wrapper: :with_block_label, include_blank: false, label_method: ->(scope) { safe_join([content_tag(:samp, scope, class: class_for_scope(scope)), content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) }, selected: f.object.scopes.all, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
= f.input :scopes,
as: :check_boxes,
collection_wrapper_tag: 'ul',
collection: value.sort,
hint: false,
include_blank: false,
item_wrapper_tag: 'li',
label_method: ->(scope) { safe_join([content_tag(:samp, scope, class: class_for_scope(scope)), content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) },
label: false,
required: false,
selected: f.object.scopes.all,
wrapper: :with_block_label

View File

@ -11,7 +11,9 @@
%p.lead= t('featured_tags.hint_html')
.fields-group
= f.input :name, wrapper: :with_block_label, hint: safe_join([t('simple_form.hints.featured_tag.name'), safe_join(@recently_used_tags.map { |tag| link_to("##{tag.display_name}", settings_featured_tags_path(featured_tag: { name: tag.name }), method: :post) }, ', ')], ' ')
= f.input :name,
hint: safe_join([t('simple_form.hints.featured_tag.name'), safe_join(@recently_used_tags.map { |tag| link_to("##{tag.display_name}", settings_featured_tags_path(featured_tag: { name: tag.name }), method: :post) }, ', ')], ' '),
wrapper: :with_block_label
.actions
= f.button :button, t('featured_tags.add_new'), type: :submit

View File

@ -3,13 +3,29 @@
= simple_form_for @import, url: settings_imports_path do |f|
.field-group
= f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface')
= f.input :type,
as: :grouped_select,
collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) },
group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") },
group_method: :last,
hint: t('imports.preface'),
include_blank: false,
label_method: ->(type) { I18n.t("imports.types.#{type}") },
wrapper: :with_block_label
.fields-row
.fields-group.fields-row__column.fields-row__column-6
= f.input :data, as: :file, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data')
= f.input :data,
as: :file,
hint: t('simple_form.hints.imports.data'),
wrapper: :with_block_label
.fields-group.fields-row__column.fields-row__column-6
= f.input :mode, as: :radio_buttons, collection: Import::MODES, label_method: ->(mode) { safe_join([I18n.t("imports.modes.#{mode}"), content_tag(:span, I18n.t("imports.modes.#{mode}_long"), class: 'hint')]) }, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
= f.input :mode,
as: :radio_buttons,
collection_wrapper_tag: 'ul',
collection: Import::MODES,
item_wrapper_tag: 'li',
label_method: ->(mode) { safe_join([I18n.t("imports.modes.#{mode}"), content_tag(:span, I18n.t("imports.modes.#{mode}_long"), class: 'hint')]) }
.actions
= f.button :button, t('imports.upload'), type: :submit

View File

@ -15,7 +15,11 @@
.fields-row
.fields-row__column.fields-group.fields-row__column-6
= f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, label: t('simple_form.labels.account_migration.acct'), hint: t('simple_form.hints.account_migration.acct')
= f.input :acct,
hint: t('simple_form.hints.account_migration.acct'),
input_html: { autocapitalize: 'none', autocorrect: 'off' },
label: t('simple_form.labels.account_migration.acct'),
wrapper: :with_block_label
.fields-row__column.fields-group.fields-row__column-6
- if current_user.encrypted_password.present?

View File

@ -7,9 +7,18 @@
= simple_form_for current_user, url: settings_preferences_appearance_path, html: { method: :put, id: 'edit_user' } do |f|
.fields-row
.fields-group.fields-row__column.fields-row__column-6
= f.input :locale, collection: ui_languages, wrapper: :with_label, include_blank: false, label_method: ->(locale) { native_locale_name(locale) }, selected: I18n.locale, hint: false
= f.input :locale,
collection: ui_languages,
hint: false,
include_blank: false,
label_method: ->(locale) { native_locale_name(locale) },
selected: I18n.locale,
wrapper: :with_label
.fields-group.fields-row__column.fields-row__column-6
= f.input :time_zone, wrapper: :with_label, collection: ActiveSupport::TimeZone.all.map { |tz| ["(GMT#{tz.formatted_offset}) #{tz.name}", tz.tzinfo.name] }, hint: false
= f.input :time_zone,
collection: ActiveSupport::TimeZone.all.map { |tz| ["(GMT#{tz.formatted_offset}) #{tz.name}", tz.tzinfo.name] },
hint: false,
wrapper: :with_label
- unless I18n.locale == :en
.flash-message.translation-prompt
@ -23,11 +32,17 @@
%p.hint= t 'appearance.advanced_web_interface_hint'
.fields-group
= ff.input :'web.advanced_layout', wrapper: :with_label, hint: false, label: I18n.t('simple_form.labels.defaults.setting_advanced_layout')
= ff.input :'web.advanced_layout',
hint: false,
label: I18n.t('simple_form.labels.defaults.setting_advanced_layout'),
wrapper: :with_label
%h4= t 'appearance.animations_and_accessibility'
.fields-group
= ff.input :'web.use_pending_items', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_use_pending_items'), hint: I18n.t('simple_form.hints.defaults.setting_use_pending_items')
= ff.input :'web.use_pending_items',
hint: I18n.t('simple_form.hints.defaults.setting_use_pending_items'),
label: I18n.t('simple_form.labels.defaults.setting_use_pending_items'),
wrapper: :with_label
.fields-group
= ff.input :'web.auto_play', wrapper: :with_label, recommended: true, label: I18n.t('simple_form.labels.defaults.setting_auto_play_gif')
@ -52,10 +67,21 @@
%h4= t 'appearance.sensitive_content'
.fields-group
= ff.input :'web.display_media', collection: %w(default show_all hide_all), label_method: ->(item) { t("simple_form.hints.defaults.setting_display_media_#{item}") }, hint: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: I18n.t('simple_form.labels.defaults.setting_display_media')
= ff.input :'web.display_media',
as: :radio_buttons,
collection_wrapper_tag: 'ul',
collection: %w(default show_all hide_all),
hint: false,
item_wrapper_tag: 'li',
label_method: ->(item) { t("simple_form.hints.defaults.setting_display_media_#{item}") },
label: I18n.t('simple_form.labels.defaults.setting_display_media'),
wrapper: :with_floating_label
.fields-group
= ff.input :'web.use_blurhash', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_use_blurhash'), hint: I18n.t('simple_form.hints.defaults.setting_use_blurhash')
= ff.input :'web.use_blurhash',
hint: I18n.t('simple_form.hints.defaults.setting_use_blurhash'),
label: I18n.t('simple_form.labels.defaults.setting_use_blurhash'),
wrapper: :with_label
.fields-group
= ff.input :'web.expand_content_warnings', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_expand_spoilers')

View File

@ -9,27 +9,66 @@
= f.simple_fields_for :settings, current_user.settings do |ff|
.fields-group
= ff.input :aggregate_reblogs, wrapper: :with_label, recommended: true, label: I18n.t('simple_form.labels.defaults.setting_aggregate_reblogs'), hint: I18n.t('simple_form.hints.defaults.setting_aggregate_reblogs')
= ff.input :aggregate_reblogs,
hint: I18n.t('simple_form.hints.defaults.setting_aggregate_reblogs'),
label: I18n.t('simple_form.labels.defaults.setting_aggregate_reblogs'),
recommended: true,
wrapper: :with_label
%h4= t 'preferences.posting_defaults'
.fields-row
.fields-group.fields-row__column.fields-row__column-6
= ff.input :default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: ->(visibility) { safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_privacy')
= ff.input :default_privacy,
collection: Status.selectable_visibilities,
hint: false,
include_blank: false,
label_method: ->(visibility) { safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') },
label: I18n.t('simple_form.labels.defaults.setting_default_privacy'),
required: false,
wrapper: :with_label
.fields-group.fields-row__column.fields-row__column-6
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: ->(locale) { locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
= ff.input :default_language,
collection: [nil] + filterable_languages,
hint: false,
include_blank: false,
label_method: ->(locale) { locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) },
label: I18n.t('simple_form.labels.defaults.setting_default_language'),
required: false,
wrapper: :with_label
.fields-group
= ff.input :default_sensitive, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_default_sensitive'), hint: I18n.t('simple_form.hints.defaults.setting_default_sensitive')
= ff.input :default_sensitive,
hint: I18n.t('simple_form.hints.defaults.setting_default_sensitive'),
label: I18n.t('simple_form.labels.defaults.setting_default_sensitive'),
wrapper: :with_label
.fields-group
= ff.input :default_content_type, collection: ['text/plain', 'text/markdown', 'text/html'], wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_default_content_type'), include_blank: false, label_method: ->(item) { safe_join([t("simple_form.labels.defaults.setting_default_content_type_#{item.split('/')[1]}"), content_tag(:span, t("simple_form.hints.defaults.setting_default_content_type_#{item.split('/')[1]}"), class: 'hint')]) }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', glitch_only: true
= ff.input :default_content_type,
collection: %w(text/plain text/markdown text/html),
wrapper: :with_label,
label: I18n.t('simple_form.labels.defaults.setting_default_content_type'),
include_blank: false,
label_method: ->(item) { safe_join([t("simple_form.labels.defaults.setting_default_content_type_#{item.split('/')[1]}"), content_tag(:span, t("simple_form.hints.defaults.setting_default_content_type_#{item.split('/')[1]}"), class: 'hint')]) },
required: false,
as: :radio_buttons,
collection_wrapper_tag: 'ul',
item_wrapper_tag: 'li',
glitch_only: true
%h4= t 'preferences.public_timelines'
.fields-group
= f.input :chosen_languages, collection: filterable_languages, wrapper: :with_block_label, include_blank: false, label_method: ->(locale) { native_locale_name(locale) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
= f.input :chosen_languages,
as: :check_boxes,
collection_wrapper_tag: 'ul',
collection: filterable_languages,
include_blank: false,
item_wrapper_tag: 'li',
label_method: ->(locale) { native_locale_name(locale) },
required: false,
wrapper: :with_block_label
.actions
= f.button :button, t('generic.save_changes'), type: :submit

View File

@ -33,7 +33,10 @@
.fields-row
.fields-row__column.fields-row__column-6
.fields-group
= f.input :avatar, wrapper: :with_block_label, input_html: { accept: Account::Avatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(Account::Avatar::LIMIT))
= f.input :avatar,
hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(Account::Avatar::LIMIT)),
input_html: { accept: Account::Avatar::IMAGE_MIME_TYPES.join(',') },
wrapper: :with_block_label
.fields-row__column.fields-row__column-6
.fields-group
@ -46,7 +49,10 @@
.fields-row
.fields-row__column.fields-row__column-6
.fields-group
= f.input :header, wrapper: :with_block_label, input_html: { accept: Account::Header::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(Account::Header::LIMIT))
= f.input :header,
hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(Account::Header::LIMIT)),
input_html: { accept: Account::Header::IMAGE_MIME_TYPES.join(',') },
wrapper: :with_block_label
.fields-row__column.fields-row__column-6
.fields-group

View File

@ -12,7 +12,12 @@
%samp.qr-alternative__code= @new_otp_secret.scan(/.{4}/).join(' ')
.fields-group
= f.input :otp_attempt, wrapper: :with_label, hint: t('otp_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { autocomplete: 'off' }, required: true
= f.input :otp_attempt,
hint: t('otp_authentication.code_hint'),
input_html: { autocomplete: 'off' },
label: t('simple_form.labels.defaults.otp_attempt'),
required: true,
wrapper: :with_label
.actions
= f.button :button, t('otp_authentication.enable'), type: :submit

View File

@ -31,7 +31,7 @@ Rails.application.configure do
:fi,
:fo,
:fr,
:'fr-QC',
:'fr-CA',
:fy,
:ga,
:gd,

View File

@ -29,7 +29,7 @@ Rails.application.config.add_autoload_paths_to_load_path = false
# Do not treat an `ActionController::Parameters` instance
# as equal to an equivalent `Hash` by default.
# Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality = false
Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality = false
# Active Record Encryption now uses SHA-256 as its hash digest algorithm. Important: If you have
# data encrypted with previous Rails versions, there are two scenarios to consider:
@ -50,7 +50,7 @@ Rails.application.config.add_autoload_paths_to_load_path = false
# Instead, run these callbacks on the instance most likely to have internal
# state which matches what was committed to the database, typically the last
# instance to save.
# Rails.application.config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = false
Rails.application.config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = false
# Configures SQLite with a strict strings mode, which disables double-quoted string literals.
#
@ -59,10 +59,10 @@ Rails.application.config.add_autoload_paths_to_load_path = false
# it then considers them as string literals. Because of this, typos can silently go unnoticed.
# For example, it is possible to create an index for a non existing column.
# See https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted for more details.
# Rails.application.config.active_record.sqlite3_adapter_strict_strings_by_default = true
Rails.application.config.active_record.sqlite3_adapter_strict_strings_by_default = true
# Disable deprecated singular associations names
# Rails.application.config.active_record.allow_deprecated_singular_associations_name = false
Rails.application.config.active_record.allow_deprecated_singular_associations_name = false
# Enable the Active Job `BigDecimal` argument serializer, which guarantees
# roundtripping. Without this serializer, some queue adapters may serialize
@ -78,12 +78,12 @@ Rails.application.config.add_autoload_paths_to_load_path = false
# `write` are given an invalid `expires_at` or `expires_in` time.
# Options are `true`, and `false`. If `false`, the exception will be reported
# as `handled` and logged instead.
# Rails.application.config.active_support.raise_on_invalid_cache_expiration_time = true
Rails.application.config.active_support.raise_on_invalid_cache_expiration_time = true
# Specify whether Query Logs will format tags using the SQLCommenter format
# (https://open-telemetry.github.io/opentelemetry-sqlcommenter/), or using the legacy format.
# Options are `:legacy` and `:sqlcommenter`.
# Rails.application.config.active_record.query_log_tags_format = :sqlcommenter
Rails.application.config.active_record.query_log_tags_format = :sqlcommenter
# Specify the default serializer used by `MessageEncryptor` and `MessageVerifier`
# instances.
@ -129,39 +129,37 @@ Rails.application.config.add_autoload_paths_to_load_path = false
# `config.load_defaults 7.1` does not set this value for environments other than
# development and test.
#
# if Rails.env.local?
# Rails.application.config.log_file_size = 100 * 1024 * 1024
# end
Rails.application.config.log_file_size = 100 * 1024 * 1024 if Rails.env.local?
# Enable raising on assignment to attr_readonly attributes. The previous
# behavior would allow assignment but silently not persist changes to the
# database.
# Rails.application.config.active_record.raise_on_assign_to_attr_readonly = true
Rails.application.config.active_record.raise_on_assign_to_attr_readonly = true
# Enable validating only parent-related columns for presence when the parent is mandatory.
# The previous behavior was to validate the presence of the parent record, which performed an extra query
# to get the parent every time the child record was updated, even when parent has not changed.
# Rails.application.config.active_record.belongs_to_required_validates_foreign_key = false
Rails.application.config.active_record.belongs_to_required_validates_foreign_key = false
# Enable precompilation of `config.filter_parameters`. Precompilation can
# improve filtering performance, depending on the quantity and types of filters.
# Rails.application.config.precompile_filter_parameters = true
Rails.application.config.precompile_filter_parameters = true
# Enable before_committed! callbacks on all enrolled records in a transaction.
# The previous behavior was to only run the callbacks on the first copy of a record
# if there were multiple copies of the same record enrolled in the transaction.
# Rails.application.config.active_record.before_committed_on_all_records = true
Rails.application.config.active_record.before_committed_on_all_records = true
# Disable automatic column serialization into YAML.
# To keep the historic behavior, you can set it to `YAML`, however it is
# recommended to explicitly define the serialization method for each column
# rather than to rely on a global default.
# Rails.application.config.active_record.default_column_serializer = nil
Rails.application.config.active_record.default_column_serializer = nil
# Run `after_commit` and `after_*_commit` callbacks in the order they are defined in a model.
# This matches the behaviour of all other callbacks.
# In previous versions of Rails, they ran in the inverse order.
# Rails.application.config.active_record.run_after_transaction_callbacks_in_order_defined = true
Rails.application.config.active_record.run_after_transaction_callbacks_in_order_defined = true
# Whether a `transaction` block is committed or rolled back when exited via `return`, `break` or `throw`.
#
@ -169,7 +167,7 @@ Rails.application.config.add_autoload_paths_to_load_path = false
# Controls when to generate a value for <tt>has_secure_token</tt> declarations.
#
# Rails.application.config.active_record.generate_secure_token_on = :initialize
Rails.application.config.active_record.generate_secure_token_on = :initialize
# ** Please read carefully, this must be configured in config/application.rb **
# Change the format of the cache entry.
@ -190,7 +188,7 @@ Rails.application.config.add_autoload_paths_to_load_path = false
#
# In previous versions of Rails, Action View always used `Rails::HTML4::Sanitizer` as its vendor.
#
# Rails.application.config.action_view.sanitizer_vendor = Rails::HTML::Sanitizer.best_supported_vendor
Rails.application.config.action_view.sanitizer_vendor = Rails::HTML::Sanitizer.best_supported_vendor
# Configure Action Text to use an HTML5 standards-compliant sanitizer when it is supported on your
# platform.
@ -213,4 +211,4 @@ Rails.application.config.add_autoload_paths_to_load_path = false
#
# In previous versions of Rails, these test helpers always used an HTML4 parser.
#
# Rails.application.config.dom_testing_default_html_version = :html5
Rails.application.config.dom_testing_default_html_version = :html5

View File

@ -1,5 +1,5 @@
---
fr-QC:
fr-CA:
activerecord:
attributes:
poll:

View File

@ -1,5 +1,5 @@
---
fr-QC:
fr-CA:
devise:
confirmations:
confirmed: Votre adresse de courriel a été validée avec succès.
@ -20,8 +20,8 @@ fr-QC:
confirmation_instructions:
action: Vérifier ladresse courriel
action_with_app: Confirmer et retourner à %{app}
explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de lactiver. Si ce n'était pas vous, veuiller ignorer ce courriel.
explanation_when_pending: Vous avez demandé à vous inscrire à %{host} avec cette adresse courriel. Une fois que vous aurez confirmé cette adresse, nous étudierons votre demande. Vous pouvez vous connecter pour changer vos détails ou supprimer votre compte, mais vous ne pouvez pas accéder à la plupart des fonctionalités du compte avant que votre compte ne soit approuvé. Si votre demande est rejetée, vos données seront supprimées, donc aucune action supplémentaire ne sera requise de votre part. Si ce n'était pas vous, veuiller ignorer ce courriel.
explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de lactiver. Si ce n'était pas vous, veuillez ignorer ce courriel.
explanation_when_pending: Vous avez demandé à vous inscrire à %{host} avec cette adresse courriel. Une fois que vous aurez confirmé cette adresse, nous étudierons votre demande. Vous pouvez vous connecter pour changer vos détails ou supprimer votre compte, mais vous ne pouvez pas accéder à la plupart des fonctionnalités du compte avant que votre compte ne soit approuvé. Si votre demande est rejetée, vos données seront supprimées, donc aucune action supplémentaire ne sera requise de votre part. Si ce n'était pas vous, veuillez ignorer ce courriel.
extra_html: Veuillez également consulter <a href="%{terms_path}">les règles du serveur</a> et <a href="%{policy_path}">nos conditions dutilisation</a>.
subject: 'Mastodon: Instructions de confirmation pour %{instance}'
title: Vérifier ladresse courriel

View File

@ -1,5 +1,5 @@
---
fr-QC:
fr-CA:
activerecord:
attributes:
doorkeeper/application:

View File

@ -1,5 +1,5 @@
---
fr-QC:
fr-CA:
about:
about_mastodon_html: 'Le réseau social de l''avenir : pas de publicité, pas de surveillance institutionnelle, conception éthique et décentralisation ! Gardez le contrôle de vos données avec Mastodon !'
contact_missing: Non défini

View File

@ -1,5 +1,5 @@
---
fr-QC:
fr-CA:
simple_form:
hints:
account:

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddHideNotificationsToMute < ActiveRecord::Migration[5.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
add_column_with_default :mutes, :hide_notifications, :boolean, default: true, allow_null: false
add_column :mutes, :hide_notifications, :boolean, default: true, null: false
end
def down

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddDisabledToCustomEmojis < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :custom_emojis, :disabled, :bool, default: false }
safety_assured { add_column :custom_emojis, :disabled, :bool, default: false, null: false }
end
def down

View File

@ -1,16 +1,12 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddReblogsToFollows < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :follows, :show_reblogs, :boolean, default: true, allow_null: false
add_column_with_default :follow_requests, :show_reblogs, :boolean, default: true, allow_null: false
add_column :follows, :show_reblogs, :boolean, default: true, null: false
add_column :follow_requests, :show_reblogs, :boolean, default: true, null: false
end
end

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddMemorialToAccounts < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :accounts, :memorial, :bool, default: false }
safety_assured { add_column :accounts, :memorial, :bool, default: false, null: false }
end
def down

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddDisabledToUsers < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :users, :disabled, :bool, default: false }
safety_assured { add_column :users, :disabled, :bool, default: false, null: false }
end
def down

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddModeratorToAccounts < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :users, :moderator, :bool, default: false }
safety_assured { add_column :users, :moderator, :bool, default: false, null: false }
end
def down

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddEmbedURLToPreviewCards < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :preview_cards, :embed_url, :string, default: '', allow_null: false
add_column :preview_cards, :embed_url, :string, default: '', null: false
end
end

View File

@ -9,7 +9,7 @@ class AddApplyToMentionsFlagToKeywordMutes < ActiveRecord::Migration[5.2]
def up
safety_assured do
add_column_with_default :glitch_keyword_mutes, :apply_to_mentions, :boolean, allow_null: false, default: true
add_column :glitch_keyword_mutes, :apply_to_mentions, :boolean, null: false, default: true
end
end

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddAutofollowToInvites < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :invites, :autofollow, :bool, default: false, allow_null: false
add_column :invites, :autofollow, :bool, default: false, null: false
end
end

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddWholeWordToCustomFilter < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def change
safety_assured do
add_column_with_default :custom_filters, :whole_word, :boolean, default: true, allow_null: false
add_column :custom_filters, :whole_word, :boolean, default: true, null: false
end
end

View File

@ -1,19 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddConfidentialToDoorkeeperApplication < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default(
add_column(
:oauth_applications,
:confidential,
:boolean,
allow_null: false,
null: false,
default: true # maintaining backwards compatibility: require secrets
)
end

View File

@ -1,19 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddSilentToMentions < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default(
add_column(
:mentions,
:silent,
:boolean,
allow_null: false,
null: false,
default: false
)
end

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddRejectReportsToDomainBlocks < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :domain_blocks, :reject_reports, :boolean, default: false, allow_null: false
add_column :domain_blocks, :reject_reports, :boolean, default: false, null: false
end
end

View File

@ -1,19 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddUnreadToAccountConversations < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default(
add_column(
:account_conversations,
:unread,
:boolean,
allow_null: false,
null: false,
default: false
)
end

View File

@ -1,19 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddShowRepliesToLists < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default(
add_column(
:lists,
:replies_policy,
:integer,
allow_null: false,
null: false,
default: 0
)
end

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddOverwriteToImports < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :imports, :overwrite, :boolean, default: false, allow_null: false
add_column :imports, :overwrite, :boolean, default: false, null: false
end
end

View File

@ -1,19 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddLockVersionToPolls < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default(
add_column(
:polls,
:lock_version,
:integer,
allow_null: false,
null: false,
default: 0
)
end

View File

@ -1,19 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddApprovedToUsers < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default(
add_column(
:users,
:approved,
:bool,
allow_null: false,
null: false,
default: true
)
end

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddLockVersionToAccountStats < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :account_stats, :lock_version, :integer, allow_null: false, default: 0 }
safety_assured { add_column :account_stats, :lock_version, :integer, null: false, default: 0 }
end
def down

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddTitleToAccountWarningPresets < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :account_warning_presets, :title, :string, default: '', allow_null: false }
safety_assured { add_column :account_warning_presets, :title, :string, default: '', null: false }
end
def down

View File

@ -1,16 +1,12 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddNotifyToFollows < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :follows, :notify, :boolean, default: false, allow_null: false
add_column_with_default :follow_requests, :notify, :boolean, default: false, allow_null: false
add_column :follows, :notify, :boolean, default: false, null: false
add_column :follow_requests, :notify, :boolean, default: false, null: false
end
end

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddObfuscateToDomainBlocks < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :domain_blocks, :obfuscate, :boolean, default: false, allow_null: false }
safety_assured { add_column :domain_blocks, :obfuscate, :boolean, default: false, null: false }
end
def down

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddCategoryToReports < ActiveRecord::Migration[6.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :reports, :category, :int, default: 0, allow_null: false
add_column :reports, :category, :int, default: 0, null: false
change_table(:reports, bulk: true) do |t|
t.column :action_taken_at, :datetime
t.column :rule_ids, :bigint, array: true

View File

@ -1,15 +1,11 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddActionToCustomFilters < ActiveRecord::Migration[6.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured do
add_column_with_default :custom_filters, :action, :integer, allow_null: false, default: 0
add_column :custom_filters, :action, :integer, null: false, default: 0
execute 'UPDATE custom_filters SET action = 1 WHERE irreversible IS TRUE'
end
end

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddExclusiveToLists < ActiveRecord::Migration[6.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :lists, :exclusive, :boolean, default: false, allow_null: false }
safety_assured { add_column :lists, :exclusive, :boolean, default: false, null: false }
end
def down

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddImageDescriptionToPreviewCards < ActiveRecord::Migration[7.0]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :preview_cards, :image_description, :string, default: '', allow_null: false }
safety_assured { add_column :preview_cards, :image_description, :string, default: '', null: false }
end
def down

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddIndexableToAccounts < ActiveRecord::Migration[7.0]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :accounts, :indexable, :boolean, default: false, allow_null: false }
safety_assured { add_column :accounts, :indexable, :boolean, default: false, null: false }
end
def down

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FixCanadianFrenchLocale < ActiveRecord::Migration[7.0]
class User < ApplicationRecord
# Dummy class, to make migration possible across version changes
end
disable_ddl_transaction!
def up
User.where(locale: 'fr-QC').in_batches do |users|
users.update_all(locale: 'fr-CA')
end
end
def down
User.where(locale: 'fr-CA').in_batches do |users|
users.update_all(locale: 'fr-QC')
end
end
end

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class RemoveWholeWordFromCustomFilters < ActiveRecord::Migration[6.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
@ -15,7 +11,7 @@ class RemoveWholeWordFromCustomFilters < ActiveRecord::Migration[6.1]
def down
safety_assured do
add_column_with_default :custom_filters, :whole_word, :boolean, default: true, allow_null: false
add_column :custom_filters, :whole_word, :boolean, default: true, null: false
end
end
end

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class RemoveIrreversibleFromCustomFilters < ActiveRecord::Migration[6.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
@ -15,7 +11,7 @@ class RemoveIrreversibleFromCustomFilters < ActiveRecord::Migration[6.1]
def down
safety_assured do
add_column_with_default :custom_filters, :irreversible, :boolean, allow_null: false, default: false
add_column :custom_filters, :irreversible, :boolean, null: false, default: false
end
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2023_12_22_100226) do
ActiveRecord::Schema[7.1].define(version: 2024_01_09_103012) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

View File

@ -104,18 +104,7 @@ module Mastodon
'in the body of your migration class'
end
# If default value is presented, use `add_column_with_default` method instead.
if options[:default]
add_column_with_default(
table_name,
column_name,
:datetime_with_timezone,
default: options[:default],
allow_null: options[:null]
)
else
add_column(table_name, column_name, :datetime_with_timezone, **options)
end
add_column(table_name, column_name, :datetime_with_timezone, **options)
end
end
@ -377,34 +366,6 @@ module Mastodon
end
end
# Adds a column with a default value without locking an entire table.
#
# This method runs the following steps:
#
# 1. Add the column with a default value of NULL.
# 2. Change the default value of the column to the specified value.
# 3. Update all existing rows in batches.
# 4. Set a `NOT NULL` constraint on the column if desired (the default).
#
# These steps ensure a column can be added to a large and commonly used
# table without locking the entire table for the duration of the table
# modification.
#
# table - The name of the table to update.
# column - The name of the column to add.
# type - The column type (e.g. `:integer`).
# default - The default value for the column.
# limit - Sets a column limit. For example, for :integer, the default is
# 4-bytes. Set `limit: 8` to allow 8-byte integers.
# allow_null - When set to `true` the column will allow NULL values, the
# default is to not allow NULL values.
#
# This method can also take a block which is passed directly to the
# `update_column_in_batches` method.
def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block)
add_column(table, column, type, default: default, limit: limit, null: allow_null)
end
# Renames a column without requiring downtime.
#
# Concurrent renames work by using database triggers to ensure both the

View File

@ -83,6 +83,11 @@ namespace :tests do
puts 'Default posting language not migrated as expected for kmr users'
exit(1)
end
unless Account.find_local('qcuser').user.locale == 'fr-CA'
puts 'Locale for fr-QC users not updated to fr-CA as expected'
exit(1)
end
end
desc 'Populate the database with test data for 2.4.3'
@ -142,13 +147,19 @@ namespace :tests do
INSERT INTO "accounts"
(id, username, domain, private_key, public_key, created_at, updated_at)
VALUES
(10, 'kmruser', NULL, #{user_private_key}, #{user_public_key}, now(), now());
(10, 'kmruser', NULL, #{user_private_key}, #{user_public_key}, now(), now()),
(11, 'qcuser', NULL, #{user_private_key}, #{user_public_key}, now(), now());
INSERT INTO "users"
(id, account_id, email, created_at, updated_at, admin, locale, chosen_languages)
VALUES
(4, 10, 'kmruser@localhost', now(), now(), false, 'ku', '{en,kmr,ku,ckb}');
INSERT INTO "users"
(id, account_id, email, created_at, updated_at, locale)
VALUES
(5, 11, 'qcuser@localhost', now(), now(), 'fr-QC');
INSERT INTO "settings"
(id, thing_type, thing_id, var, value, created_at, updated_at)
VALUES

View File

@ -44,7 +44,7 @@ RSpec.describe Admin::Disputes::AppealsController do
expect(response).to redirect_to(disputes_strike_path(appeal.strike))
end
it 'notifies target account about approved appeal' do
it 'notifies target account about approved appeal', :sidekiq_inline do
expect(UserMailer.deliveries.size).to eq(1)
expect(UserMailer.deliveries.first.to.first).to eq(target_account.user.email)
expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at)))
@ -62,7 +62,7 @@ RSpec.describe Admin::Disputes::AppealsController do
expect(response).to redirect_to(disputes_strike_path(appeal.strike))
end
it 'notifies target account about rejected appeal' do
it 'notifies target account about rejected appeal', :sidekiq_inline do
expect(UserMailer.deliveries.size).to eq(1)
expect(UserMailer.deliveries.first.to.first).to eq(target_account.user.email)
expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at)))

View File

@ -176,7 +176,7 @@ RSpec.describe Admin::DomainBlocksController do
end
end
describe 'PUT #update' do
describe 'PUT #update', :sidekiq_inline do
subject do
post :update, params: { :id => domain_block.id, :domain_block => { domain: 'example.com', severity: new_severity }, 'confirm' => '' }
end

View File

@ -11,7 +11,7 @@ describe Admin::ResetsController do
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'POST #create' do
describe 'POST #create', :sidekiq_inline do
it 'redirects to admin accounts page' do
expect do
post :create, params: { account_id: account.id }

View File

@ -13,7 +13,7 @@ RSpec.describe Api::V1::ConversationsController do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
describe 'GET #index', :sidekiq_inline do
let(:scopes) { 'read:statuses' }
before do

View File

@ -9,7 +9,7 @@ describe Api::V1::Statuses::ReblogsController do
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses', application: app) }
context 'with an oauth token', :sidekiq_fake do
context 'with an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
@ -46,7 +46,7 @@ describe Api::V1::Statuses::ReblogsController do
end
end
describe 'POST #destroy' do
describe 'POST #destroy', :sidekiq_inline do
context 'with public status' do
let(:status) { Fabricate(:status, account: user.account) }

View File

@ -5,7 +5,7 @@ require 'rails_helper'
describe Api::V1::StreamingController do
around do |example|
before = Rails.configuration.x.streaming_api_base_url
Rails.configuration.x.streaming_api_base_url = Rails.configuration.x.web_domain
Rails.configuration.x.streaming_api_base_url = "wss://#{Rails.configuration.x.web_domain}"
example.run
Rails.configuration.x.streaming_api_base_url = before
end

View File

@ -138,7 +138,7 @@ RSpec.describe Auth::SessionsController do
expect(controller.current_user).to eq user
end
it 'sends a suspicious sign-in mail' do
it 'sends a suspicious sign-in mail', :sidekiq_inline do
expect(UserMailer.deliveries.size).to eq(1)
expect(UserMailer.deliveries.first.to.first).to eq(user.email)
expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.suspicious_sign_in.subject'))

View File

@ -75,7 +75,7 @@ describe UserTrackingConcern do
expect(redis.ttl("account:#{user.account_id}:regeneration")).to be >= 0
end
it 'regenerates feed when sign in is older than two weeks' do
it 'regenerates feed when sign in is older than two weeks', :sidekiq_inline do
get :show
expect_updated_sign_in_at(user)

View File

@ -17,7 +17,7 @@ RSpec.describe Disputes::AppealsController do
post :create, params: { strike_id: strike.id, appeal: { text: 'Foo' } }
end
it 'notifies staff about new appeal' do
it 'notifies staff about new appeal', :sidekiq_inline do
expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email])
end

View File

@ -50,7 +50,7 @@ describe Settings::DeletesController do
delete :destroy, params: { form_delete_confirmation: { password: 'petsmoldoggos' } }
end
it 'removes user record and redirects', :aggregate_failures do
it 'removes user record and redirects', :aggregate_failures, :sidekiq_inline do
expect(response).to redirect_to '/auth/sign_in'
expect(User.find_by(id: user.id)).to be_nil
expect(user.account.reload).to be_suspended

View File

@ -38,7 +38,7 @@ describe Settings::ExportsController do
expect(response).to redirect_to(settings_export_path)
end
it 'queues BackupWorker job by 1', :sidekiq_fake do
it 'queues BackupWorker job by 1' do
expect do
post :create
end.to change(BackupWorker.jobs, :size).by(1)

View File

@ -48,7 +48,7 @@ describe 'Admin::Accounts' do
end
end
context 'with action of `reject`' do
context 'with action of `reject`', :sidekiq_inline do
it 'rejects and removes the account' do
batch_checkbox_for(unapproved_user_account).check

View File

@ -23,7 +23,7 @@ RSpec.describe ActivityPub::Activity::Create do
stub_request(:get, 'http://example.com/emojib.png').to_return(body: attachment_fixture('emojo.png'), headers: { 'Content-Type' => 'application/octet-stream' })
end
describe 'processing posts received out of order', :sidekiq_fake do
describe 'processing posts received out of order' do
let(:follower) { Fabricate(:account, username: 'bob') }
let(:object_json) do

View File

@ -47,7 +47,7 @@ RSpec.describe ActivityPub::Activity::Delete do
expect(Status.find_by(id: status.id)).to be_nil
end
it 'sends delete activity to followers of rebloggers' do
it 'sends delete activity to followers of rebloggers', :sidekiq_inline do
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
end

View File

@ -38,7 +38,7 @@ RSpec.describe ActivityPub::Activity::Move do
subject.perform
end
context 'when all conditions are met' do
context 'when all conditions are met', :sidekiq_inline do
it 'sets moved account on old account' do
expect(old_account.reload.moved_to_account_id).to eq new_account.id
end

View File

@ -183,6 +183,21 @@ describe Mastodon::CLI::Media do
.to output_results('Downloaded 1 media')
end
end
context 'with --days option' do
before do
Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', id: Mastodon::Snowflake.id_at(50.days.ago))
Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', id: Mastodon::Snowflake.id_at(5.days.ago))
Fabricate(:media_attachment, remote_url: '', id: Mastodon::Snowflake.id_at(5.days.ago))
end
let(:options) { { days: 10 } }
it 'redownloads the attachment file for the remote records more recent than the option' do
expect { subject }
.to output_results('Downloaded 1 media')
end
end
end
describe '#remove_orphans' do

View File

@ -1009,4 +1009,27 @@ RSpec.describe Account do
expect(subject.reload.followers_count).to eq 15
end
end
describe '.followable_by' do
context 'with follows and follow requests' do
let!(:account) { Fabricate(:account) }
let!(:eligible_account) { Fabricate(:account) }
let!(:following_account) { Fabricate(:account) }
let!(:follow_requested_account) { Fabricate(:account) }
before do
Fabricate :follow, account: account, target_account: following_account
Fabricate :follow_request, account: account, target_account: follow_requested_account
end
it 'returns accounts not already following or requested to follow' do
results = described_class.followable_by(account)
expect(results)
.to include(eligible_account)
.and not_include(following_account)
.and not_include(follow_requested_account)
end
end
end
end

View File

@ -46,7 +46,7 @@ RSpec.describe Admin::AccountAction do
expect(target_account).to be_suspended
end
it 'queues Admin::SuspensionWorker by 1', :sidekiq_fake do
it 'queues Admin::SuspensionWorker by 1' do
expect do
subject
end.to change { Admin::SuspensionWorker.jobs.size }.by 1

View File

@ -13,189 +13,65 @@ RSpec.describe Setting do
end
describe '.[]' do
let(:key) { 'key' }
let(:cache_key) { 'cache-key' }
let(:cache_value) { 'cache-value' }
before do
allow(described_class).to receive(:rails_initialized?).and_return(rails_initialized)
allow(described_class).to receive(:cache_key).with(key).and_return(cache_key)
end
let(:key) { 'key' }
context 'when rails_initialized? is falsey' do
let(:rails_initialized) { false }
it 'calls RailsSettings::Base#[]' do
allow(RailsSettings::Base).to receive(:[]).with(key)
described_class[key]
expect(RailsSettings::Base).to have_received(:[]).with(key)
end
end
context 'when rails_initialized? is truthy' do
context 'when Rails.cache does not exists' do
before do
allow(RailsSettings::Base).to receive(:cache_key).with(key, nil).and_return(cache_key)
allow(described_class).to receive(:default_settings).and_return(default_settings)
Fabricate(:setting, var: key, value: 42) if save_setting
Rails.cache.delete(cache_key)
end
let(:rails_initialized) { true }
let(:cache_key) { 'cache-key' }
let(:cache_value) { 'cache-value' }
let(:default_value) { 'default_value' }
let(:default_settings) { { key => default_value } }
let(:save_setting) { true }
it 'calls not RailsSettings::Base#[]' do
allow(RailsSettings::Base).to receive(:[]).with(key)
described_class[key]
expect(RailsSettings::Base).to_not have_received(:[]).with(key)
end
context 'when Rails.cache does not exists' do
before do
allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
allow(described_class).to receive(:default_settings).and_return(default_settings)
settings_double = instance_double(Settings::ScopedSettings, thing_scoped: records)
allow(Settings::ScopedSettings).to receive(:new).and_return(settings_double)
Rails.cache.delete(cache_key)
end
let(:object) { nil }
let(:default_value) { 'default_value' }
let(:default_settings) { { key => default_value } }
let(:records) { [Fabricate(:setting, var: key, value: nil)] }
it 'calls RailsSettings::Settings.object' do
allow(RailsSettings::Settings).to receive(:object).with(key)
described_class[key]
expect(RailsSettings::Settings).to have_received(:object).with(key)
end
context 'when RailsSettings::Settings.object returns truthy' do
let(:object) { db_val }
let(:db_val) { instance_double(described_class, value: 'db_val') }
context 'when default_value is a Hash' do
let(:default_value) { { default_value: 'default_value' } }
it 'calls default_value.with_indifferent_access.merge!' do
indifferent_hash = instance_double(Hash, merge!: nil)
allow(default_value).to receive(:with_indifferent_access).and_return(indifferent_hash)
described_class[key]
expect(default_value).to have_received(:with_indifferent_access)
expect(indifferent_hash).to have_received(:merge!).with(db_val.value)
end
end
context 'when default_value is not a Hash' do
let(:default_value) { 'default_value' }
it 'returns db_val.value' do
expect(described_class[key]).to be db_val.value
end
end
end
context 'when RailsSettings::Settings.object returns falsey' do
let(:object) { nil }
it 'returns default_settings[key]' do
expect(described_class[key]).to be default_settings[key]
end
end
end
context 'when Rails.cache exists' do
before do
Rails.cache.write(cache_key, cache_value)
end
it 'does not query the database' do
context 'when the setting has been saved to database' do
it 'returns the value from database' do
callback = double
allow(callback).to receive(:call)
ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do
described_class[key]
expect(described_class[key]).to eq 42
end
expect(callback).to_not have_received(:call)
end
it 'returns the cached value' do
expect(described_class[key]).to eq cache_value
end
end
end
end
describe '.all_as_records' do
before do
settings_double = instance_double(Settings::ScopedSettings, thing_scoped: records)
allow(Settings::ScopedSettings).to receive(:new).and_return(settings_double)
allow(described_class).to receive(:default_settings).and_return(default_settings)
end
let(:key) { 'key' }
let(:default_value) { 'default_value' }
let(:default_settings) { { key => default_value } }
let(:original_setting) { Fabricate(:setting, var: key, value: nil) }
let(:records) { [original_setting] }
it 'returns a Hash' do
expect(described_class.all_as_records).to be_a Hash
end
context 'when records includes Setting with var as the key' do
let(:records) { [original_setting] }
it 'includes the original Setting' do
setting = described_class.all_as_records[key]
expect(setting).to eq original_setting
end
end
context 'when records includes nothing' do
let(:records) { [] }
context 'when default_value is not a Hash' do
it 'includes Setting with value of default_value' do
setting = described_class.all_as_records[key]
expect(setting).to be_a described_class
expect(setting).to have_attributes(var: key)
expect(setting).to have_attributes(value: 'default_value')
expect(callback).to have_received(:call)
end
end
context 'when default_value is a Hash' do
let(:default_value) { { 'foo' => 'fuga' } }
context 'when the setting has not been saved to database' do
let(:save_setting) { false }
it 'returns {}' do
expect(described_class.all_as_records).to eq({})
it 'returns default_settings[key]' do
expect(described_class[key]).to be default_settings[key]
end
end
end
end
describe '.default_settings' do
subject { described_class.default_settings }
before do
allow(RailsSettings::Default).to receive(:enabled?).and_return(enabled)
end
context 'when RailsSettings::Default.enabled? is false' do
let(:enabled) { false }
it 'returns {}' do
expect(subject).to eq({})
context 'when Rails.cache exists' do
before do
Rails.cache.write(cache_key, cache_value)
end
end
context 'when RailsSettings::Settings.enabled? is true' do
let(:enabled) { true }
it 'does not query the database' do
callback = double
allow(callback).to receive(:call)
ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do
described_class[key]
end
expect(callback).to_not have_received(:call)
end
it 'returns instance of RailsSettings::Default' do
expect(subject).to be_a RailsSettings::Default
it 'returns the cached value' do
expect(described_class[key]).to eq cache_value
end
end
end

View File

@ -38,27 +38,53 @@ RSpec.describe User do
user.save(validate: false)
expect(user.valid?).to be true
end
end
it 'cleans out invalid locale' do
user = Fabricate.build(:user, locale: 'toto')
expect(user.valid?).to be true
expect(user.locale).to be_nil
describe 'Normalizations' do
describe 'locale' do
it 'preserves valid locale' do
user = Fabricate.build(:user, locale: 'en')
expect(user.locale).to eq('en')
end
it 'cleans out invalid locale' do
user = Fabricate.build(:user, locale: 'toto')
expect(user.locale).to be_nil
end
end
it 'cleans out invalid timezone' do
user = Fabricate.build(:user, time_zone: 'toto')
expect(user.valid?).to be true
expect(user.time_zone).to be_nil
describe 'time_zone' do
it 'preserves valid timezone' do
user = Fabricate.build(:user, time_zone: 'UTC')
expect(user.time_zone).to eq('UTC')
end
it 'cleans out invalid timezone' do
user = Fabricate.build(:user, time_zone: 'toto')
expect(user.time_zone).to be_nil
end
end
it 'cleans out empty string from languages' do
user = Fabricate.build(:user, chosen_languages: [''])
user.valid?
expect(user.chosen_languages).to be_nil
describe 'languages' do
it 'preserves valid options for languages' do
user = Fabricate.build(:user, chosen_languages: ['en', 'fr', ''])
expect(user.chosen_languages).to eq(['en', 'fr'])
end
it 'cleans out empty string from languages' do
user = Fabricate.build(:user, chosen_languages: [''])
expect(user.chosen_languages).to be_nil
end
end
end
describe 'scopes' do
describe 'scopes', :sidekiq_inline do
describe 'recent' do
it 'returns an array of recent users ordered by id' do
first_user = Fabricate(:user)
@ -452,7 +478,7 @@ RSpec.describe User do
expect(user.confirmed_at).to be_present
end
it 'delivers mails' do
it 'delivers mails', :sidekiq_inline do
expect(ActionMailer::Base.deliveries.count).to eq 2
end
end

View File

@ -26,7 +26,6 @@ Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
ActiveRecord::Migration.maintain_test_schema!
WebMock.disable_net_connect!(allow: Chewy.settings[:host], allow_localhost: RUN_SYSTEM_SPECS)
Sidekiq::Testing.inline!
Sidekiq.logger = nil
# System tests config
@ -96,11 +95,8 @@ RSpec.configure do |config|
self.use_transactional_tests = true
end
config.around(:each, :sidekiq_fake) do |example|
Sidekiq::Testing.fake! do
example.run
Sidekiq::Worker.clear_all
end
config.around(:each, :sidekiq_inline) do |example|
Sidekiq::Testing.inline!(&example)
end
config.before :each, type: :cli do
@ -112,8 +108,6 @@ RSpec.configure do |config|
end
config.around :each, type: :system do |example|
driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200]
# The streaming server needs access to the database
# but with use_transactional_tests every transaction
# is rolled-back, so the streaming server never sees the data

View File

@ -147,7 +147,7 @@ RSpec.describe 'FeaturedTags' do
expect(body).to be_empty
end
it 'deletes the featured tag' do
it 'deletes the featured tag', :sidekiq_inline do
delete "/api/v1/featured_tags/#{id}", headers: headers
featured_tag = FeaturedTag.find_by(id: id)

View File

@ -8,7 +8,7 @@ RSpec.describe 'Notifications' do
let(:scopes) { 'read:notifications write:notifications' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/notifications' do
describe 'GET /api/v1/notifications', :sidekiq_inline do
subject do
get '/api/v1/notifications', headers: headers, params: params
end

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe 'Favourites' do
RSpec.describe 'Favourites', :sidekiq_inline do
let(:user) { Fabricate(:user) }
let(:scopes) { 'write:favourites' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@ -70,7 +70,7 @@ RSpec.describe 'Favourites' do
end
end
describe 'POST /api/v1/statuses/:status_id/unfavourite', :sidekiq_fake do
describe 'POST /api/v1/statuses/:status_id/unfavourite' do
subject do
post "/api/v1/statuses/#{status.id}/unfavourite", headers: headers
end
@ -88,9 +88,7 @@ RSpec.describe 'Favourites' do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
UnfavouriteWorker.drain
expect(user.account.favourited?(status)).to be false
end
@ -113,9 +111,7 @@ RSpec.describe 'Favourites' do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
UnfavouriteWorker.drain
expect(user.account.favourited?(status)).to be false
end

View File

@ -2,7 +2,7 @@
require 'rails_helper'
describe 'Home' do
describe 'Home', :sidekiq_inline do
let(:user) { Fabricate(:user) }
let(:scopes) { 'read:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }

View File

@ -2,7 +2,7 @@
require 'rails_helper'
describe Account::StatusesSearch do
describe Account::StatusesSearch, :sidekiq_inline do
describe 'a non-indexable account becoming indexable' do
let(:account) { Account.find_by(username: 'search_test_account_1') }

View File

@ -225,7 +225,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
end
end
context 'with statuses referencing other statuses' do
context 'with statuses referencing other statuses', :sidekiq_inline do
before do
stub_const 'ActivityPub::FetchRemoteStatusService::DISCOVERIES_PER_REQUEST', 5
end

View File

@ -193,7 +193,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
end
end
it 'creates accounts without exceeding rate limit' do
it 'creates accounts without exceeding rate limit', :sidekiq_inline do
expect { subject.call('user1', 'foo.test', payload) }
.to create_some_remote_accounts
.and create_fewer_than_rate_limit_accounts

View File

@ -41,7 +41,7 @@ RSpec.describe AuthorizeFollowService, type: :service do
expect(bob.following?(sender)).to be true
end
it 'sends an accept activity' do
it 'sends an accept activity', :sidekiq_inline do
expect(a_request(:post, bob.inbox_url)).to have_been_made.once
end
end

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BatchedRemoveStatusService, type: :service do
RSpec.describe BatchedRemoveStatusService, :sidekiq_inline, type: :service do
subject { described_class.new }
let!(:alice) { Fabricate(:account) }

View File

@ -68,7 +68,7 @@ RSpec.describe BlockDomainService, type: :service do
expect(already_banned_account.reload.silenced_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
end
it 'leaves the domains status and attachments, but clears media' do
it 'leaves the domains status and attachments, but clears media', :sidekiq_inline do
expect { bad_status_plain.reload }.to_not raise_error
expect { bad_status_with_attachment.reload }.to_not raise_error
expect { bad_attachment.reload }.to_not raise_error

Some files were not shown because too many files have changed in this diff Show More