From 78ef635980ff391ea3ba4c37de836947a97958a0 Mon Sep 17 00:00:00 2001 From: Evan <35814742+evanphilip@users.noreply.github.com> Date: Wed, 14 Dec 2022 19:50:07 +0100 Subject: [PATCH 01/66] Add command to remove avatar and header images of inactive remote accounts from the local database (#22149) * Add tootctl subcommand media remove-profile-media * Trigger workflows * Correcting external linting * External linting error * External linting fix * Merging with remove command * Linting * Correct long option names Co-authored-by: Claire * Correct long option names Co-authored-by: Claire * Correct long option names Co-authored-by: Claire * Remove saving a list of purged accounts Co-authored-by: Claire --- lib/mastodon/media_cli.rb | 78 ++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index bba4a1bd7..24cc98964 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -14,35 +14,78 @@ module Mastodon end option :days, type: :numeric, default: 7, aliases: [:d] + option :prune_profiles, type: :boolean, default: false + option :remove_headers, type: :boolean, default: false + option :include_follows, type: :boolean, default: false option :concurrency, type: :numeric, default: 5, aliases: [:c] - option :verbose, type: :boolean, default: false, aliases: [:v] option :dry_run, type: :boolean, default: false - desc 'remove', 'Remove remote media files' + desc 'remove', 'Remove remote media files, headers or avatars' long_desc <<-DESC - Removes locally cached copies of media attachments from other servers. - + Removes locally cached copies of media attachments (and optionally profile + headers and avatars) from other servers. By default, only media attachements + are removed. The --days option specifies how old media attachments have to be before - they are removed. It defaults to 7 days. + they are removed. In case of avatars and headers, it specifies how old + the last webfinger request and update to the user has to be before they + are pruned. It defaults to 7 days. + If --prune-profiles is specified, only avatars and headers are removed. + If --remove-headers is specified, only headers are removed. + If --include-follows is specified along with --prune-profiles or + --remove-headers, all non-local profiles will be pruned irrespective of + follow status. By default, only accounts that are not followed by or + following anyone locally are pruned. DESC + # rubocop:disable Metrics/PerceivedComplexity def remove - time_ago = options[:days].days.ago - dry_run = options[:dry_run] ? '(DRY RUN)' : '' + if options[:prune_profiles] && options[:remove_headers] + say('--prune-profiles and --remove-headers should not be specified simultaneously', :red, true) + exit(1) + end + if options[:include_follows] && !(options[:prune_profiles] || options[:remove_headers]) + say('--include-follows can only be used with --prune-profiles or --remove-headers', :red, true) + exit(1) + end + time_ago = options[:days].days.ago + dry_run = options[:dry_run] ? ' (DRY RUN)' : '' - processed, aggregate = parallelize_with_progress(MediaAttachment.cached.where.not(remote_url: '').where('created_at < ?', time_ago)) do |media_attachment| - next if media_attachment.file.blank? + if options[:prune_profiles] || options[:remove_headers] + processed, aggregate = parallelize_with_progress(Account.remote.where({ last_webfingered_at: ..time_ago, updated_at: ..time_ago })) do |account| + next if !options[:include_follows] && Follow.where(account: account).or(Follow.where(target_account: account)).exists? + next if account.avatar.blank? && account.header.blank? + next if options[:remove_headers] && account.header.blank? - size = (media_attachment.file_file_size || 0) + (media_attachment.thumbnail_file_size || 0) + size = (account.header_file_size || 0) + size += (account.avatar_file_size || 0) if options[:prune_profiles] - unless options[:dry_run] - media_attachment.file.destroy - media_attachment.thumbnail.destroy - media_attachment.save + unless options[:dry_run] + account.header.destroy + account.avatar.destroy if options[:prune_profiles] + account.save! + end + + size end - size + say("Visited #{processed} accounts and removed profile media totaling #{number_to_human_size(aggregate)}#{dry_run}", :green, true) end - say("Removed #{processed} media attachments (approx. #{number_to_human_size(aggregate)}) #{dry_run}", :green, true) + unless options[:prune_profiles] || options[:remove_headers] + processed, aggregate = parallelize_with_progress(MediaAttachment.cached.where.not(remote_url: '').where(created_at: ..time_ago)) do |media_attachment| + next if media_attachment.file.blank? + + size = (media_attachment.file_file_size || 0) + (media_attachment.thumbnail_file_size || 0) + + unless options[:dry_run] + media_attachment.file.destroy + media_attachment.thumbnail.destroy + media_attachment.save + end + + size + end + + say("Removed #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true) + end end option :start_after @@ -183,6 +226,7 @@ module Mastodon say("Removed #{removed} orphans (approx. #{number_to_human_size(reclaimed_bytes)})#{dry_run}", :green, true) end + # rubocop:enable Metrics/PerceivedComplexity option :account, type: :string option :domain, type: :string @@ -269,7 +313,7 @@ module Mastodon def lookup(url) path = Addressable::URI.parse(url).path - path_segments = path.split('/')[2..-1] + path_segments = path.split('/')[2..] path_segments.delete('cache') unless [7, 10].include?(path_segments.size) From 1f762f4271685e30373b15a2e204f8edff59d349 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 14:07:34 +0100 Subject: [PATCH 02/66] Fix wasteful request to /api/v1/custom_emojis when not logged in (#22326) --- app/javascript/mastodon/containers/mastodon.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 724719f74..002b71e93 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -23,7 +23,9 @@ export const store = configureStore(); const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); -store.dispatch(fetchCustomEmojis()); +if (initialState.meta.me) { + store.dispatch(fetchCustomEmojis()); +} const createIdentityContext = state => ({ signedIn: !!state.meta.me, From 5917b46c0530777b684cbd661d0f454264e4f046 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 14:15:50 +0100 Subject: [PATCH 03/66] Allow admins to refresh remotely-suspended accounts (#22327) * Change suspension text to mention that a remotely suspended account is not locally-suspended * Add ability to refresh profile of remotely suspended accounts --- app/views/admin/accounts/show.html.haml | 6 +++++- config/locales/en.yml | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index dc3b35956..db5c255c9 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -195,9 +195,13 @@ - if @account.suspended? %hr.spacer/ - %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') + - if @account.suspension_origin_remote? + %p.muted-hint= @deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible') + - else + %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.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) && @account.suspension_origin_remote? - if @deletion_request.present? = 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) diff --git a/config/locales/en.yml b/config/locales/en.yml index a045db1ab..0a0effbc1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -116,6 +116,8 @@ en: redownloaded_msg: Successfully refreshed %{username}'s profile from origin reject: Reject rejected_msg: Successfully rejected %{username}'s sign-up application + remote_suspension_irreversible: The data of this account has been irreversibly deleted. + remote_suspension_reversible_hint_html: The account has been suspended on their server, and the data will be fully removed on %{date}. Until then, the remote server can restore this account without any ill effects. If you wish to remove all of the account's data immediately, you can do so below. remove_avatar: Remove avatar remove_header: Remove header removed_avatar_msg: Successfully removed %{username}'s avatar image From f239d31f23b8bd55fb26f67906b815e4abe65d92 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 14:52:50 +0100 Subject: [PATCH 04/66] Add --email and --dry-run options to `tootctl accounts delete` (#22328) --- lib/mastodon/accounts_cli.rb | 41 ++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 77cbef84e..0dd852131 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -200,21 +200,44 @@ module Mastodon end end - desc 'delete USERNAME', 'Delete a user' + option :email + option :dry_run, type: :boolean + desc 'delete [USERNAME]', 'Delete a user' long_desc <<-LONG_DESC Remove a user account with a given USERNAME. - LONG_DESC - def delete(username) - account = Account.find_local(username) - if account.nil? - say('No user with such username', :red) + With the --email option, the user is selected based on email + rather than username. + LONG_DESC + def delete(username = nil) + if username.present? && options[:email].present? + say('Use username or --email, not both', :red) + exit(1) + elsif username.blank? && options[:email].blank? + say('No username provided', :red) exit(1) end - say("Deleting user with #{account.statuses_count} statuses, this might take a while...") - DeleteAccountService.new.call(account, reserve_email: false) - say('OK', :green) + dry_run = options[:dry_run] ? ' (DRY RUN)' : '' + account = nil + + if username.present? + account = Account.find_local(username) + if account.nil? + say('No user with such username', :red) + exit(1) + end + else + account = Account.left_joins(:user).find_by(user: { email: options[:email] }) + if account.nil? + say('No user with such email', :red) + exit(1) + end + end + + say("Deleting user with #{account.statuses_count} statuses, this might take a while...#{dry_run}") + DeleteAccountService.new.call(account, reserve_email: false) unless options[:dry_run] + say("OK#{dry_run}", :green) end option :force, type: :boolean, aliases: [:f], description: 'Override public key check' From 6cdbc345f4ed824b9491ac6b53406c44e23f7ba5 Mon Sep 17 00:00:00 2001 From: Meisam <39205857+MFTabriz@users.noreply.github.com> Date: Thu, 15 Dec 2022 15:43:05 +0100 Subject: [PATCH 05/66] Validate nodeinfo response by schema (#21395) * add json-schema to :test in Gemfile * Create node_info_2.0_schema.json * test match_response_schema * Create match_response_schema.rb * Update nodeinfo_controller_spec.rb * Rename spec/support/node_info_2.0_schema.json to spec/support/schema/node_info_2.0_schema.json * Update match_response_schema.rb * cleanup * additionally validate the json schema itself disable throwing errors test the schema matcher * rename nodeinfo schema to nodeinfo_2.0 * use Rails.root.join to construct the path * prettify json * sync Gemfile.lock --- Gemfile | 5 +- Gemfile.lock | 3 + .../well_known/nodeinfo_controller_spec.rb | 2 + .../matchers/json/match_json_schema.rb | 6 + spec/support/schema/nodeinfo_2.0.json | 170 ++++++++++++++++++ 5 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 spec/support/matchers/json/match_json_schema.rb create mode 100644 spec/support/schema/nodeinfo_2.0.json diff --git a/Gemfile b/Gemfile index a399f5102..9fe2a1120 100644 --- a/Gemfile +++ b/Gemfile @@ -117,13 +117,14 @@ group :test do gem 'capybara', '~> 3.38' gem 'climate_control', '~> 0.2' gem 'faker', '~> 3.0' + gem 'json-schema', '~> 3.0' gem 'microformats', '~> 4.4' + gem 'rack-test', '~> 2.0' gem 'rails-controller-testing', '~> 1.0' + gem 'rspec_junit_formatter', '~> 0.6' gem 'rspec-sidekiq', '~> 3.1' gem 'simplecov', '~> 0.21', require: false gem 'webmock', '~> 3.18' - gem 'rspec_junit_formatter', '~> 0.6' - gem 'rack-test', '~> 2.0' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 578a88436..ac3f560ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -344,6 +344,8 @@ GEM json-ld-preloaded (3.2.2) json-ld (~> 3.2) rdf (~> 3.2) + json-schema (3.0.0) + addressable (>= 2.8) jsonapi-renderer (0.2.2) jwt (2.4.1) kaminari (1.2.2) @@ -791,6 +793,7 @@ DEPENDENCIES idn-ruby json-ld json-ld-preloaded (~> 3.2) + json-schema (~> 3.0) kaminari (~> 1.2) kt-paperclip (~> 7.1) letter_opener (~> 1.8) diff --git a/spec/controllers/well_known/nodeinfo_controller_spec.rb b/spec/controllers/well_known/nodeinfo_controller_spec.rb index 694bb0fb9..36e85f20d 100644 --- a/spec/controllers/well_known/nodeinfo_controller_spec.rb +++ b/spec/controllers/well_known/nodeinfo_controller_spec.rb @@ -27,6 +27,8 @@ describe WellKnown::NodeInfoController, type: :controller do json = body_as_json + expect({ "foo" => 0 }).not_to match_json_schema("nodeinfo_2.0") + expect(json).to match_json_schema("nodeinfo_2.0") expect(json[:version]).to eq '2.0' expect(json[:usage]).to be_a Hash expect(json[:software]).to be_a Hash diff --git a/spec/support/matchers/json/match_json_schema.rb b/spec/support/matchers/json/match_json_schema.rb new file mode 100644 index 000000000..5d9c9a618 --- /dev/null +++ b/spec/support/matchers/json/match_json_schema.rb @@ -0,0 +1,6 @@ +RSpec::Matchers.define :match_json_schema do |schema| + match do |input_json| + schema_path = Rails.root.join('spec', 'support', 'schema', "#{schema}.json").to_s + JSON::Validator.validate(schema_path, input_json, validate_schema: true) + end +end diff --git a/spec/support/schema/nodeinfo_2.0.json b/spec/support/schema/nodeinfo_2.0.json new file mode 100644 index 000000000..085ce542b --- /dev/null +++ b/spec/support/schema/nodeinfo_2.0.json @@ -0,0 +1,170 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://nodeinfo.diaspora.software/ns/schema/2.0#", + "description": "NodeInfo schema version 2.0.", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "software", + "protocols", + "services", + "openRegistrations", + "usage", + "metadata" + ], + "properties": { + "version": { + "description": "The schema version, must be 2.0.", + "enum": ["2.0"] + }, + "software": { + "description": "Metadata about server software in use.", + "type": "object", + "additionalProperties": false, + "required": ["name", "version"], + "properties": { + "name": { + "description": "The canonical name of this server software.", + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "version": { + "description": "The version of this server software.", + "type": "string" + } + } + }, + "protocols": { + "description": "The protocols supported on this server.", + "type": "array", + "minItems": 1, + "items": { + "enum": [ + "activitypub", + "buddycloud", + "dfrn", + "diaspora", + "libertree", + "ostatus", + "pumpio", + "tent", + "xmpp", + "zot" + ] + } + }, + "services": { + "description": "The third party sites this server can connect to via their application API.", + "type": "object", + "additionalProperties": false, + "required": ["inbound", "outbound"], + "properties": { + "inbound": { + "description": "The third party sites this server can retrieve messages from for combined display with regular traffic.", + "type": "array", + "minItems": 0, + "items": { + "enum": [ + "atom1.0", + "gnusocial", + "imap", + "pnut", + "pop3", + "pumpio", + "rss2.0", + "twitter" + ] + } + }, + "outbound": { + "description": "The third party sites this server can publish messages to on the behalf of a user.", + "type": "array", + "minItems": 0, + "items": { + "enum": [ + "atom1.0", + "blogger", + "buddycloud", + "diaspora", + "dreamwidth", + "drupal", + "facebook", + "friendica", + "gnusocial", + "google", + "insanejournal", + "libertree", + "linkedin", + "livejournal", + "mediagoblin", + "myspace", + "pinterest", + "pnut", + "posterous", + "pumpio", + "redmatrix", + "rss2.0", + "smtp", + "tent", + "tumblr", + "twitter", + "wordpress", + "xmpp" + ] + } + } + } + }, + "openRegistrations": { + "description": "Whether this server allows open self-registration.", + "type": "boolean" + }, + "usage": { + "description": "Usage statistics for this server.", + "type": "object", + "additionalProperties": false, + "required": ["users"], + "properties": { + "users": { + "description": "statistics about the users of this server.", + "type": "object", + "additionalProperties": false, + "properties": { + "total": { + "description": "The total amount of on this server registered users.", + "type": "integer", + "minimum": 0 + }, + "activeHalfyear": { + "description": "The amount of users that signed in at least once in the last 180 days.", + "type": "integer", + "minimum": 0 + }, + "activeMonth": { + "description": "The amount of users that signed in at least once in the last 30 days.", + "type": "integer", + "minimum": 0 + } + } + }, + "localPosts": { + "description": "The amount of posts that were made by users that are registered on this server.", + "type": "integer", + "minimum": 0 + }, + "localComments": { + "description": "The amount of comments that were made by users that are registered on this server.", + "type": "integer", + "minimum": 0 + } + } + }, + "metadata": { + "description": "Free form key value pairs for software specific values. Clients should not rely on any specific key present.", + "type": "object", + "minProperties": 0, + "additionalProperties": true + } + } +} From fe9eab51d140ee0e0343eb07982f0a7ce825398c Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 15:43:16 +0100 Subject: [PATCH 06/66] =?UTF-8?q?Change=20dropdown=20menu=20to=20contain?= =?UTF-8?q?=20=E2=80=9CCopy=20link=20to=20post=E2=80=9D=20even=20for=20non?= =?UTF-8?q?-public=20posts=20(#21316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #21244 --- .../mastodon/components/status_action_bar.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 2a1fedb93..40c86afdf 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -246,12 +246,13 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); - if (publicStatus) { - if (isRemote) { - menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') }); - } + if (publicStatus && isRemote) { + menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') }); + } - menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy }); + menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy }); + + if (publicStatus) { menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); } From 5fb1c3e934a1a782972ac2732ce7f0208c341ac2 Mon Sep 17 00:00:00 2001 From: Francis Murillo Date: Thu, 15 Dec 2022 14:47:06 +0000 Subject: [PATCH 07/66] Revoke all authorized applications on password reset (#21325) * Clear sessions on password change * Rename User::clear_sessions to revoke_access for a clearer meaning * Add reset paassword controller test * Use User.find instead of User.find_for_authentication for reset password test * Use redirect and render for better test meaning in reset password Co-authored-by: Effy Elden --- app/controllers/auth/passwords_controller.rb | 2 + app/models/user.rb | 16 +++-- .../auth/passwords_controller_spec.rb | 61 +++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb index 2996c0431..a8ad66929 100644 --- a/app/controllers/auth/passwords_controller.rb +++ b/app/controllers/auth/passwords_controller.rb @@ -10,6 +10,8 @@ class Auth::PasswordsController < Devise::PasswordsController super do |resource| if resource.errors.empty? resource.session_activations.destroy_all + + resource.revoke_access! end end end diff --git a/app/models/user.rb b/app/models/user.rb index 5530a9070..ca98a0afa 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -377,6 +377,15 @@ class User < ApplicationRecord super end + def revoke_access! + Doorkeeper::AccessGrant.by_resource_owner(self).update_all(revoked_at: Time.now.utc) + + Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch| + batch.update_all(revoked_at: Time.now.utc) + Web::PushSubscription.where(access_token_id: batch).delete_all + end + end + def reset_password! # First, change password to something random and deactivate all sessions transaction do @@ -385,12 +394,7 @@ class User < ApplicationRecord end # Then, remove all authorized applications and connected push subscriptions - Doorkeeper::AccessGrant.by_resource_owner(self).in_batches.update_all(revoked_at: Time.now.utc) - - Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch| - batch.update_all(revoked_at: Time.now.utc) - Web::PushSubscription.where(access_token_id: batch).delete_all - end + revoke_access! # Finally, send a reset password prompt to the user send_reset_password_instructions diff --git a/spec/controllers/auth/passwords_controller_spec.rb b/spec/controllers/auth/passwords_controller_spec.rb index dcfdebb17..1c6874f08 100644 --- a/spec/controllers/auth/passwords_controller_spec.rb +++ b/spec/controllers/auth/passwords_controller_spec.rb @@ -35,4 +35,65 @@ describe Auth::PasswordsController, type: :controller do end end end + + describe 'POST #update' do + let(:user) { Fabricate(:user) } + + before do + @password = 'reset0password' + request.env['devise.mapping'] = Devise.mappings[:user] + end + + context 'with valid reset_password_token' do + let!(:session_activation) { Fabricate(:session_activation, user: user) } + let!(:access_token) { Fabricate(:access_token, resource_owner_id: user.id) } + let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) } + + before do + @token = user.send_reset_password_instructions + + post :update, params: { user: { password: @password, password_confirmation: @password, reset_password_token: @token } } + end + + it 'redirect to sign in' do + expect(response).to redirect_to '/auth/sign_in' + end + + it 'changes password' do + this_user = User.find(user.id) + + expect(this_user).to_not be_nil + expect(this_user.valid_password?(@password)).to be true + end + + it 'deactivates all sessions' do + expect(user.session_activations.count).to eq 0 + end + + it 'revokes all access tokens' do + expect(Doorkeeper::AccessToken.active_for(user).count).to eq 0 + end + + it 'removes push subscriptions' do + expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0 + end + end + + context 'with invalid reset_password_token' do + before do + post :update, params: { user: { password: @password, password_confirmation: @password, reset_password_token: 'some_invalid_value' } } + end + + it 'renders reset password' do + expect(response).to render_template(:new) + end + + it 'retains password' do + this_user = User.find(user.id) + + expect(this_user).to_not be_nil + expect(this_user.external_or_valid_password?(user.password)).to be true + end + end + end end From bae6ef315ef6cd5cef8213ce84edbcba3eb3cec8 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 15:47:23 +0100 Subject: [PATCH 08/66] Fix missing Javascript in domain block import confirmation page (#21471) Follow-up to #20597 --- app/views/admin/export_domain_blocks/import.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/admin/export_domain_blocks/import.html.haml b/app/views/admin/export_domain_blocks/import.html.haml index 01add232d..804e61199 100644 --- a/app/views/admin/export_domain_blocks/import.html.haml +++ b/app/views/admin/export_domain_blocks/import.html.haml @@ -1,6 +1,9 @@ - content_for :page_title do = t('admin.export_domain_blocks.import.title') +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + %p= t('admin.export_domain_blocks.import.description_html') - if defined?(@global_private_comment) && @global_private_comment.present? From e5d15a5b563863a07e7f91fc6f3bc0475bd6200a Mon Sep 17 00:00:00 2001 From: Justin Hutchings Date: Thu, 15 Dec 2022 06:51:13 -0800 Subject: [PATCH 09/66] Add CodeQL workflow (#21894) --- .github/workflows/codeql.yml | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..88ac2fb08 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,63 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '22 6 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript', 'ruby' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From e8cc63105fe9e5166182ccea28008d880ca43fd9 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Thu, 15 Dec 2022 23:52:06 +0900 Subject: [PATCH 10/66] Don't delivery a reply to domains which are blocked by author (#22117) Co-authored-by: Claire --- app/lib/status_reach_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/status_reach_finder.rb b/app/lib/status_reach_finder.rb index ccf1e9e3a..36fb0e80f 100644 --- a/app/lib/status_reach_finder.rb +++ b/app/lib/status_reach_finder.rb @@ -70,7 +70,7 @@ class StatusReachFinder def followers_inboxes if @status.in_reply_to_local_account? && distributable? - @status.account.followers.or(@status.thread.account.followers).inboxes + @status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).inboxes elsif @status.direct_visibility? || @status.limited_visibility? [] else From 7b68e6409bff40e60dd5c04c4e562177c2b14bc5 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 15:52:18 +0100 Subject: [PATCH 11/66] Fix invalid CSS for links in warning and strike cards (#22302) --- app/javascript/styles/mastodon/admin.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 2ed0d613e..ba64fde09 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -1666,7 +1666,7 @@ a.sparkline { min-height: 100%; a { - text: &highlight-text-color; + color: $highlight-text-color; text-decoration: none; &:hover { From 441cac758f759ba16744f80e1d981e84f415bd29 Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Fri, 16 Dec 2022 01:56:05 +1100 Subject: [PATCH 12/66] Allow adding relays while secure mode & limited federation mode are enabled (#22324) --- app/controllers/admin/relays_controller.rb | 6 +++--- config/locales/en.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin/relays_controller.rb b/app/controllers/admin/relays_controller.rb index 6fbb6e063..c1297c8b9 100644 --- a/app/controllers/admin/relays_controller.rb +++ b/app/controllers/admin/relays_controller.rb @@ -3,7 +3,7 @@ module Admin class RelaysController < BaseController before_action :set_relay, except: [:index, :new, :create] - before_action :require_signatures_enabled!, only: [:new, :create, :enable] + before_action :warn_signatures_not_enabled!, only: [:new, :create, :enable] def index authorize :relay, :update? @@ -56,8 +56,8 @@ module Admin params.require(:relay).permit(:inbox_url) end - def require_signatures_enabled! - redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode? + def warn_signatures_not_enabled! + flash.now[:error] = I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode? end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 0a0effbc1..2fcfd4ee1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -557,7 +557,7 @@ en: pending: Waiting for relay's approval save_and_enable: Save and enable setup: Setup a relay connection - signatures_not_enabled: Relays will not work correctly while secure mode or limited federation mode is enabled + signatures_not_enabled: Relays may not work correctly while secure mode or limited federation mode is enabled status: Status title: Relays report_notes: From 52540771b0e7f69d2d1e0c21b558976000e807e5 Mon Sep 17 00:00:00 2001 From: s0 Date: Fri, 16 Dec 2022 01:56:48 +1100 Subject: [PATCH 13/66] Fix crash in elasticsearch_check.rb (#21006) Nil unwrap causes the admin dashboard to crash/500 when the Chewy client info version number value is nil. This occurs when running another ES-compatible backend such as MeiliSearch. Obviously it would be good for chewy to recognise upstream but at least avoiding the crash would be fine. --- app/lib/admin/system_check/elasticsearch_check.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb index 8aee18267..a63988224 100644 --- a/app/lib/admin/system_check/elasticsearch_check.rb +++ b/app/lib/admin/system_check/elasticsearch_check.rb @@ -34,6 +34,7 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck end def compatible_version? + return false if running_version.nil? Gem::Version.new(running_version) >= Gem::Version.new(required_version) end end From c3388f4ab151a2603fabd67dadea435f851eaf12 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 15 Dec 2022 15:57:02 +0100 Subject: [PATCH 14/66] Fix profile avatar being slightly offset into left border (fixes #20822) (#20994) * hotfix for #20822 I don't know why it was shifted in the first place or why the width is specified twice, but this fixes the problem, so it looks fine to me. * realigned pfp with content below * fixed formatting my bad * added comment to explain the negative margin before I forget - comments are *important* ! Co-authored-by: Riedler --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1271fc7f3..f5d442a85 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7021,7 +7021,6 @@ noscript { display: block; flex: 0 0 auto; width: 94px; - margin-left: -2px; .account__avatar { background: darken($ui-base-color, 8%); @@ -7038,6 +7037,7 @@ noscript { padding-top: 10px; gap: 8px; overflow: hidden; + margin-left: -2px; // aligns the pfp with content below &__buttons { display: flex; From a0813806d6be42c2b1d466315b3bbecb4950f334 Mon Sep 17 00:00:00 2001 From: Moritz Heiber Date: Thu, 15 Dec 2022 15:57:17 +0100 Subject: [PATCH 15/66] Add hadolint as Dockerfile linter (#20993) * Added hadolint as Dockerfile linter in pipeline and resolved remaining hadolint issues in Dockerfile * Use more specific version of hadolint Action * Bumpt hadolint Action version to latest version to avoid deprecation notice * Being _really_ specific now --- .github/workflows/build-image.yml | 1 + Dockerfile | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 6c12bd073..c161cbf3d 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -18,6 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: hadolint/hadolint-action@v3.0.0 - uses: docker/setup-qemu-action@v2 - uses: docker/setup-buildx-action@v2 - uses: docker/login-action@v2 diff --git a/Dockerfile b/Dockerfile index 1a97965ac..ce7f4d718 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,8 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] WORKDIR /opt/mastodon COPY Gemfile* package.json yarn.lock /opt/mastodon/ -RUN apt update && \ +# hadolint ignore=DL3008 +RUN apt-get update && \ apt-get install -y --no-install-recommends build-essential \ ca-certificates \ git \ @@ -50,10 +51,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV DEBIAN_FRONTEND="noninteractive" \ PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" +# Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use +# hadolint ignore=DL3008,DL3009 RUN apt-get update && \ echo "Etc/UTC" > /etc/localtime && \ groupadd -g "${GID}" mastodon && \ - useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \ + useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \ apt-get -y --no-install-recommends install whois \ wget \ procps \ From 596923da4a4a0e384da5abeb0d987c127301630a Mon Sep 17 00:00:00 2001 From: luzpaz Date: Thu, 15 Dec 2022 09:57:26 -0500 Subject: [PATCH 16/66] Fix typos in source documentation (#21046) Fixed 2 source comment/documentation typos --- app/workers/scheduler/suspended_user_cleanup_scheduler.rb | 2 +- config/initializers/devise.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/workers/scheduler/suspended_user_cleanup_scheduler.rb b/app/workers/scheduler/suspended_user_cleanup_scheduler.rb index 50768f83c..87e22161b 100644 --- a/app/workers/scheduler/suspended_user_cleanup_scheduler.rb +++ b/app/workers/scheduler/suspended_user_cleanup_scheduler.rb @@ -9,7 +9,7 @@ class Scheduler::SuspendedUserCleanupScheduler MAX_PULL_SIZE = 50 # Since account deletion is very expensive, we want to avoid - # overloading the server by queing too much at once. + # overloading the server by queuing too much at once. # This job runs approximately once per 2 minutes, so with a # value of `MAX_DELETIONS_PER_JOB` of 10, a server can # handle the deletion of 7200 accounts per day, provided it diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index c55bea7a7..d7b252c3f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -159,7 +159,7 @@ Devise.setup do |config| # config.request_keys = [] # Configure which authentication keys should be case-insensitive. - # These keys will be downcased upon creating or modifying a user and when used + # These keys will be lowercased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. config.case_insensitive_keys = [:email] From 63b379c2d95a8ea7127f2621603037cc9013870d Mon Sep 17 00:00:00 2001 From: nametoolong Date: Thu, 15 Dec 2022 23:18:20 +0800 Subject: [PATCH 17/66] Fix N+1 queries from in NotificationsController (#21202) Co-authored-by: Nonexistent --- app/controllers/api/v1/notifications_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 6d464997e..7b1cfe264 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -31,7 +31,7 @@ class Api::V1::NotificationsController < Api::BaseController private def load_notifications - notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id( + notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_paginated_by_id( limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params_slice(:max_id, :since_id, :min_id) ) From 04c611daa1b7ff27a8fe0af882ff9339aeddac6d Mon Sep 17 00:00:00 2001 From: Jeremy Kescher Date: Thu, 15 Dec 2022 15:18:39 +0000 Subject: [PATCH 18/66] Fix being unable to react with the keycap number sign emoji (#22231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #⃣ This bug is caused by the emoji consisting of: U+23 # U+FE0F U+20E3 ⃣ Because it starts with a #, it's interpreted as an anchor link, which is not passed to the API. Therefore, the API sees no emoji to react with and answers correctly with a 404. --- app/javascript/mastodon/actions/announcements.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/actions/announcements.js b/app/javascript/mastodon/actions/announcements.js index 1bdea909f..586dcfd33 100644 --- a/app/javascript/mastodon/actions/announcements.js +++ b/app/javascript/mastodon/actions/announcements.js @@ -102,7 +102,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => { dispatch(addReactionRequest(announcementId, name, alreadyAdded)); } - api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => { + api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { dispatch(addReactionSuccess(announcementId, name, alreadyAdded)); }).catch(err => { if (!alreadyAdded) { @@ -136,7 +136,7 @@ export const addReactionFail = (announcementId, name, error) => ({ export const removeReaction = (announcementId, name) => (dispatch, getState) => { dispatch(removeReactionRequest(announcementId, name)); - api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => { + api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { dispatch(removeReactionSuccess(announcementId, name)); }).catch(err => { dispatch(removeReactionFail(announcementId, name, err)); From 4114a7088a7161a8aebf27d20433c0f47a4f178c Mon Sep 17 00:00:00 2001 From: Matt Hodges Date: Thu, 15 Dec 2022 09:18:59 -0600 Subject: [PATCH 19/66] Embed js height fix (#22141) * only begin iframe reheight once document state is complete * format * lint fixes * Update public/embed.js to use readystatechange event listener Co-authored-by: Claire * Call loaded() if ready, otherwise add listenter * lint fix Co-authored-by: Claire --- public/embed.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/public/embed.js b/public/embed.js index 5607c24d5..defba403e 100644 --- a/public/embed.js +++ b/public/embed.js @@ -1,24 +1,28 @@ // @ts-check -(function() { +(function () { 'use strict'; /** * @param {() => void} loaded */ - var ready = function(loaded) { - if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { + var ready = function (loaded) { + if (document.readyState === 'complete') { loaded(); } else { - document.addEventListener('DOMContentLoaded', loaded); + document.addEventListener('readystatechange', function () { + if (document.readyState === 'complete') { + loaded(); + } + }); } }; - ready(function() { + ready(function () { /** @type {Map} */ var iframes = new Map(); - window.addEventListener('message', function(e) { + window.addEventListener('message', function (e) { var data = e.data || {}; if (typeof data !== 'object' || data.type !== 'setHeight' || !iframes.has(data.id)) { @@ -34,7 +38,7 @@ iframe.height = data.height; }); - [].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function(iframe) { + [].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function (iframe) { // select unique id for each iframe var id = 0, failCount = 0, idBuffer = new Uint32Array(1); while (id === 0 || iframes.has(id)) { @@ -49,10 +53,10 @@ iframes.set(id, iframe); - iframe.scrolling = 'no'; + iframe.scrolling = 'no'; iframe.style.overflow = 'hidden'; - iframe.onload = function() { + iframe.onload = function () { iframe.contentWindow.postMessage({ type: 'setHeight', id: id, From 903e5a3f459d28b093438dc4827b2fb976aef406 Mon Sep 17 00:00:00 2001 From: Alex Stine Date: Thu, 15 Dec 2022 09:20:21 -0600 Subject: [PATCH 20/66] Fix hidden label causing accessibility issue for search inputs (#21275) * Try unhiding search label. * Use aria-label. Remove label as empty labels are useless. * Remove addition of package-lock.json. --- .../features/compose/components/search.js | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index ebb23d92f..03e6dcf2c 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -123,20 +123,18 @@ class Search extends React.PureComponent { return (
- +
From 3a59ffde8d1600729bf51ff42f1d646062edfc8d Mon Sep 17 00:00:00 2001 From: Pleclown Date: Thu, 15 Dec 2022 16:20:34 +0100 Subject: [PATCH 21/66] Adding 12 hours option for polls (#21131) * Adding 12 hours option for polls Adding 12 hours option for polls * Adding 12 hours option for polls Missing > on a line --- app/javascript/mastodon/features/compose/components/poll_form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js index ede29b8a0..c58db6467 100644 --- a/app/javascript/mastodon/features/compose/components/poll_form.js +++ b/app/javascript/mastodon/features/compose/components/poll_form.js @@ -165,6 +165,7 @@ class PollForm extends ImmutablePureComponent { + From 58200132d07bc0b641c95af614b9ac02ce48c9fc Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 16 Dec 2022 00:20:46 +0900 Subject: [PATCH 22/66] `FormattedMessage` must be used directly (#20982) * `FormattedMessage` must be used directly * rollback --- .../mastodon/features/explore/index.js | 32 +++++++++---------- .../mastodon/locales/defaultMessages.json | 16 ++++++++++ app/javascript/mastodon/locales/en.json | 4 +++ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js index 286170c9f..1ae249f45 100644 --- a/app/javascript/mastodon/features/explore/index.js +++ b/app/javascript/mastodon/features/explore/index.js @@ -24,16 +24,6 @@ const mapStateToProps = state => ({ isSearching: state.getIn(['search', 'submitted']) || !showTrends, }); -// Fix strange bug on Safari where (rendered by FormattedMessage) disappears -// after clicking around Explore top bar (issue #20885). -// Removing width=100% from also fixes it, as well as replacing with
-// We're choosing to wrap span with div to keep the changes local only to this tool bar. -const WrapFormattedMessage = ({ children, ...props }) =>
{children}
; -WrapFormattedMessage.propTypes = { - children: PropTypes.any, -}; - - export default @connect(mapStateToProps) @injectIntl class Explore extends React.PureComponent { @@ -78,12 +68,22 @@ class Explore extends React.PureComponent { {isSearching ? ( ) : ( - + <>
- - - - {signedIn && } + + + + + + + + + + {signedIn && ( + + + + )}
@@ -97,7 +97,7 @@ class Explore extends React.PureComponent { {intl.formatMessage(messages.title)} -
+ )}
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 445ab3894..230154eb1 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -2014,6 +2014,22 @@ { "defaultMessage": "Search results", "id": "explore.search_results" + }, + { + "defaultMessage": "Posts", + "id": "explore.trending_statuses" + }, + { + "defaultMessage": "Hashtags", + "id": "explore.trending_tags" + }, + { + "defaultMessage": "News", + "id": "explore.trending_links" + }, + { + "defaultMessage": "For you", + "id": "explore.suggested_follows" } ], "path": "app/javascript/mastodon/features/explore/index.json" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index dc165e74c..05b9353bf 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -235,7 +235,11 @@ "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard", "errors.unexpected_crash.report_issue": "Report issue", "explore.search_results": "Search results", + "explore.suggested_follows": "For you", "explore.title": "Explore", + "explore.trending_links": "News", + "explore.trending_statuses": "Posts", + "explore.trending_tags": "Hashtags", "filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.", "filter_modal.added.context_mismatch_title": "Context mismatch!", "filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.", From baecdf28820b267aebe0c9fa92bdc85d929746e7 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Fri, 16 Dec 2022 00:20:55 +0900 Subject: [PATCH 23/66] Fix typo in application_helper_spec.rb (#20981) enviroment -> environment --- spec/helpers/application_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 20ee32aa0..1dbd985bf 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -113,7 +113,7 @@ describe ApplicationHelper do Setting.site_title = site_title end - it 'returns site title on production enviroment' do + it 'returns site title on production environment' do Setting.site_title = 'site title' expect(Rails.env).to receive(:production?).and_return(true) expect(helper.title).to eq 'site title' From f0cebaee00f80270bef7a7ce8e03597dd118ad23 Mon Sep 17 00:00:00 2001 From: trwnh Date: Thu, 15 Dec 2022 09:21:13 -0600 Subject: [PATCH 24/66] Add localization for new admin scopes (#20979) * Add localization for new admin scopes * run bundle exec i18n-tasks normalize --- config/locales/doorkeeper.en.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index 5567724ae..2df0056c2 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -149,9 +149,19 @@ en: scopes: admin:read: read all data on the server admin:read:accounts: read sensitive information of all accounts + admin:read:canonical_email_blocks: read sensitive information of all canonical email blocks + admin:read:domain_allows: read sensitive information of all domain allows + admin:read:domain_blocks: read sensitive information of all domain blocks + admin:read:email_domain_blocks: read sensitive information of all email domain blocks + admin:read:ip_blocks: read sensitive information of all IP blocks admin:read:reports: read sensitive information of all reports and reported accounts admin:write: modify all data on the server admin:write:accounts: perform moderation actions on accounts + admin:write:canonical_email_blocks: perform moderation actions on canonical email blocks + admin:write:domain_allows: perform moderation actions on domain allows + admin:write:domain_blocks: perform moderation actions on domain blocks + admin:write:email_domain_blocks: perform moderation actions on email domain blocks + admin:write:ip_blocks: perform moderation actions on IP blocks admin:write:reports: perform moderation actions on reports crypto: use end-to-end encryption follow: modify account relationships From c50e9d078aa3c353afc140669f1cedcd354ee53e Mon Sep 17 00:00:00 2001 From: Francis Murillo Date: Thu, 15 Dec 2022 15:35:25 +0000 Subject: [PATCH 25/66] Render current day formats in the client timezone (#21878) * Fix remaining plain %time to %time.formatted * Add %time.relative-formatted to client format dates on the current day * Add missing comma dangle to formats * Use client side message format instead of the server * Add fallback message to relatve_format.today * Remove unused translation key and fix js lint issue Co-authored-by: Effy Elden --- app/javascript/mastodon/locales/en.json | 1 + app/javascript/packs/public.js | 38 +++++++++++++++++++ .../admin/report_notes/_report_note.html.haml | 7 +--- app/views/admin/reports/show.html.haml | 7 +--- app/views/disputes/strikes/show.html.haml | 7 +--- .../settings/featured_tags/index.html.haml | 2 +- config/locales/en.yml | 1 - 7 files changed, 46 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 05b9353bf..997b0d9e5 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -461,6 +461,7 @@ "refresh": "Refresh", "regeneration_indicator.label": "Loading…", "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "relative_format.today": "Today at {time}", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago", "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago", diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 786fc8ede..a5e2014f7 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -63,6 +63,18 @@ function main() { minute: 'numeric', }); + const dateFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + day: 'numeric', + timeFormat: false, + }); + + const timeFormat = new Intl.DateTimeFormat(locale, { + timeStyle: 'short', + hour12: false, + }); + [].forEach.call(document.querySelectorAll('.emojify'), (content) => { content.innerHTML = emojify(content.innerHTML); }); @@ -75,6 +87,32 @@ function main() { content.textContent = formattedDate; }); + const isToday = date => { + const today = new Date(); + + return date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear(); + }; + const todayFormat = new IntlMessageFormat(messages['relative_format.today'] || 'Today at {time}', locale); + + [].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => { + const datetime = new Date(content.getAttribute('datetime')); + + let formattedContent; + + if (isToday(datetime)) { + const formattedTime = timeFormat.format(datetime); + + formattedContent = todayFormat.format({ time: formattedTime }); + } else { + formattedContent = dateFormat.format(datetime); + } + + content.title = formattedContent; + content.textContent = formattedContent; + }); + [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { const datetime = new Date(content.getAttribute('datetime')); const now = new Date(); diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml index 3bccd3b4b..14df2f609 100644 --- a/app/views/admin/report_notes/_report_note.html.haml +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -4,11 +4,8 @@ .report-notes__item__header %span.username = link_to report_note.account.username, admin_account_path(report_note.account_id) - %time{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) } - - if report_note.created_at.today? - = t('admin.report_notes.today_at', time: l(report_note.created_at, format: :time)) - - else - = l report_note.created_at.to_date + %time.relative-formatted{ datetime: report_note.created_at } + = t('admin.report_notes.created_at') .report-notes__item__content = simple_format(h(report_note.content)) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index cf960565f..50ec64b06 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -144,11 +144,8 @@ = link_to @report.account.username, admin_account_path(@report.account_id) - else = link_to @report.account.domain, admin_instance_path(@report.account.domain) - %time{ datetime: @report.created_at.iso8601, title: l(@report.created_at) } - - if @report.created_at.today? - = t('admin.report_notes.today_at', time: l(@report.created_at, format: :time)) - - else - = l @report.created_at.to_date + %time.relative-formatted{ datetime: @report.created_at.iso8601 } + = t('admin.report_notes.created_at') .report-notes__item__content = simple_format(h(@report.comment)) diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml index 4a3005f72..cab0a17eb 100644 --- a/app/views/disputes/strikes/show.html.haml +++ b/app/views/disputes/strikes/show.html.haml @@ -110,11 +110,8 @@ .report-notes__item__header %span.username = link_to @appeal.account.username, can?(:show, @appeal.account) ? admin_account_path(@appeal.account_id) : short_account_url(@appeal.account) - %time{ datetime: @appeal.created_at.iso8601, title: l(@appeal.created_at) } - - if @appeal.created_at.today? - = t('admin.report_notes.today_at', time: l(@appeal.created_at, format: :time)) - - else - = l @appeal.created_at.to_date + %time.relative-formatted{ datetime: @appeal.created_at.iso8601 } + = t('admin.report_notes.created_at') .report-notes__item__content = simple_format(h(@appeal.text)) diff --git a/app/views/settings/featured_tags/index.html.haml b/app/views/settings/featured_tags/index.html.haml index 595094fc7..078abd788 100644 --- a/app/views/settings/featured_tags/index.html.haml +++ b/app/views/settings/featured_tags/index.html.haml @@ -26,6 +26,6 @@ - if featured_tag.last_status_at.nil? = t('accounts.nothing_here') - else - %time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at + %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at = table_link_to 'trash', t('filters.index.delete'), settings_featured_tag_path(featured_tag), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } .trends__item__current= friendly_number_to_human featured_tag.statuses_count diff --git a/config/locales/en.yml b/config/locales/en.yml index 2fcfd4ee1..9f71e5ed7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -563,7 +563,6 @@ en: report_notes: created_msg: Report note successfully created! destroyed_msg: Report note successfully deleted! - today_at: Today at %{time} reports: account: notes: From 08c0e43b6fca879d7435f63b1b2c718a53c6cacc Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 15 Dec 2022 08:37:07 -0700 Subject: [PATCH 26/66] Increase the width of the unread notification border. (#21692) The smaller border is difficult to see for some users, especially when the browser window was thinner, and so the unread border is at the very left edge of the window. --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f5d442a85..d03ab03c2 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7666,7 +7666,7 @@ noscript { left: 0; width: 100%; height: 100%; - border-left: 2px solid $highlight-text-color; + border-left: 4px solid $highlight-text-color; pointer-events: none; } } From 72a8af80886f270a27bd686f1fa86ef478748963 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 15 Dec 2022 10:37:17 -0500 Subject: [PATCH 27/66] Fix typo in handler function call name (#21829) --- app/javascript/mastodon/features/ui/components/columns_area.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index f4824f045..e7def800e 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -97,7 +97,7 @@ export default class ColumnsArea extends ImmutablePureComponent { if (this.mediaQuery.removeEventListener) { this.mediaQuery.removeEventListener('change', this.handleLayoutChange); } else { - this.mediaQuery.removeListener(this.handleLayouteChange); + this.mediaQuery.removeListener(this.handleLayoutChange); } } } From 22e36271c5c10fbf462fb385e12b50a015d0fd99 Mon Sep 17 00:00:00 2001 From: Colin Mitchell Date: Thu, 15 Dec 2022 10:38:37 -0500 Subject: [PATCH 28/66] Add environment variable to configure sidekiq concurrency (#19589) Co-authored-by: Effy Elden --- config/sidekiq.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 05c5b28c8..b8739aab3 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -1,5 +1,5 @@ --- -:concurrency: 5 +:concurrency: <%= ENV.fetch('SIDEKIQ_CONCURRENCY', 5) %> :queues: - [default, 8] - [push, 6] From 3d3429243fa0bacb20aac3db6c377441c0510f22 Mon Sep 17 00:00:00 2001 From: Dan Peterson Date: Thu, 15 Dec 2022 11:38:51 -0400 Subject: [PATCH 29/66] Fix default S3_HOSTNAME used in mastodon:setup (#19932) s3-us-east-1.amazonaws.com does not exist. Co-authored-by: Effy Elden --- lib/tasks/mastodon.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index c1e5bd2b4..3c891a07f 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -194,7 +194,7 @@ namespace :mastodon do env['S3_HOSTNAME'] = prompt.ask('S3 hostname:') do |q| q.required true - q.default 's3-us-east-1.amazonaws.com' + q.default 's3.us-east-1.amazonaws.com' q.modify :strip end From 1f5740e65cc50ef3cc1feb7c0e5609df73d4173a Mon Sep 17 00:00:00 2001 From: Neil Matatall <448516+oreoshake@users.noreply.github.com> Date: Thu, 15 Dec 2022 05:39:41 -1000 Subject: [PATCH 30/66] Use Rails tag API to build RSS feed for spoilers and polls (#20163) * Use Rails tag API to build RSS feed for spoilers and polls While the previous method did not contain a bug or a potential issue, the tag API can be very resilient against future problems and reduces the amount of manual management of the escape status of the content. I've added tests to ensure that the formatting is broken and still escapes control characters correctly. * this seems cleaner and passes * Incorporate feedback by moving the br to its own line and using the tag helper over the string constant for the br tag itself * whoops, tag helper doesn't use a self-closing tag --- app/helpers/formatting_helper.rb | 25 +++++++++++++++++-------- spec/helpers/formatting_helper_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 spec/helpers/formatting_helper_spec.rb diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index a9d2f9651..c70931489 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -23,19 +23,28 @@ module FormattingHelper before_html = begin if status.spoiler_text? - "

#{I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)} #{h(status.spoiler_text)}


" - else - '' + tag.p do + tag.strong do + I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale) + end + + status.spoiler_text + end + tag.hr end - end.html_safe # rubocop:disable Rails/OutputSafety + end after_html = begin if status.preloadable_poll - "

#{status.preloadable_poll.options.map { |o| " #{h(o)}" }.join('
')}

" - else - '' + tag.p do + safe_join( + status.preloadable_poll.options.map do |o| + tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', o, disabled: true) + end, + tag.br + ) + end end - end.html_safe # rubocop:disable Rails/OutputSafety + end prerender_custom_emojis( safe_join([before_html, html, after_html]), diff --git a/spec/helpers/formatting_helper_spec.rb b/spec/helpers/formatting_helper_spec.rb new file mode 100644 index 000000000..af604a87b --- /dev/null +++ b/spec/helpers/formatting_helper_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe FormattingHelper, type: :helper do + include Devise::Test::ControllerHelpers + + describe '#rss_status_content_format' do + let(:status) { Fabricate(:status, text: 'Hello world<>', spoiler_text: 'This is a spoiler<>', poll: Fabricate(:poll, options: %w(Yes<> No))) } + let(:html) { helper.rss_status_content_format(status) } + + it 'renders the spoiler text' do + expect(html).to include('

This is a spoiler<>


') + end + + it 'renders the status text' do + expect(html).to include('

Hello world<>

') + end + + it 'renders the poll' do + expect(html).to include('Yes<>
') + end + end +end From 19f78ea8fadc4626f7b2db5dbe37accbde6a968c Mon Sep 17 00:00:00 2001 From: Kaspar V Date: Thu, 15 Dec 2022 16:39:59 +0100 Subject: [PATCH 31/66] linting: RuboCop update, config fixes (#20574) * fix(rubocop): update gems and add performance and rspec fix(rubocop): update gems and add performance and rspec - update present rubocop gems - add rubocop-rspec and rubocop-performance gems - move rubocop gems to gem group :development, :test in order to make linting in a github action that runs with RAILS_ENV=test possible * feat(rubocop): disable some annoyance RSpec cops To mee these prooved to be more annoying than helpful. If not agreed, they can be enabled any time. * fix(rubocop): do not ignore spec/**/* Because rubocop-rspec should lint the specs as well, and they deserve to be readable in general. It is relevant code, after all. * fix(rubocop): change ignore db/**/* to db/schema.rb because rails cops do some lints for migrations. E.g. reversable migrations linting and more. * fix(rubocop): tune rules configs Bunch of commits squashed: fix(rubocop): enable Layout/LineLength cop Because this project has code with line lenghts > 500 chars. This is not good practice at all, so I strongly suggest to change the practice in the future. But allow heredoc, URI and comments to still be long lines and make the default Max: 120 explicit, by repeating it in the config. To me this max length seems reasonable. Perhaps a bit more could be ok for some. But > 500 chars in one line Seems to be way too long IMHO. fix(rubocop): Metrics/CyclomaticComplexity Max to 12 The default is 7, perhaps quite strict. But 25 is too loose, the rule becomes pointless like that. fix(rubocop): AllCops ruby version, cacheing and more info - fix the target ruby version from 2.5 to 3.0 - have the cop error messages to be more informative and helpful - enable cacheing in /tmp fix(rubocop): Metrics/AbcSize to 34 from 115 Rubocops default is 17. If the rule is at 115 is becomes pointless. fix(rubocop): Metrics/BlockLength improvements - instead of ignoring tasks completely, ignore only the long blocks that are specific to tasks (task, namespace) - ignore also concern specific block methods (included, class_methods) fix(rubocop): Metrics/ClassLength count heredoc array as one line fix(rubocop): Metrics/MethodLength Max to 25 - the default is 10, but 65 is too loose, so perhaps 25? fix(rubocop): Metrics/ModuleLength array and heredoc count as one fix(rubocop): Metrics/PerceivedComplexity to 16 from 25 Rubocops default is 8, so how about only doubling that, instead of > than tripple it? fix(rubocop): enable Style/RedundantAssignment Because I think that this rule would never really hurt, but improve code quality and readability. fix(rubocop): enable Style/RescueStandardError I think everyone that ever had to debug what this can bring will hopefully agree that this rule totally makes sense. In the super rare exeptions where this is totally needed, it can be excluded by disabling comment in that place. fix(rubocop): Metrics/ParameterLists add explicit defaults and some excludes --- .rubocop.yml | 200 ++++++++++++++++++++++++++++++++++++++++++++++----- Gemfile | 6 +- Gemfile.lock | 30 +++++--- 3 files changed, 209 insertions(+), 27 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index aec11b030..67284fe34 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,12 +1,18 @@ require: - rubocop-rails + - rubocop-rspec + - rubocop-performance AllCops: TargetRubyVersion: 2.7 - NewCops: disable + DisplayCopNames: true + DisplayStyleGuide: true + ExtraDetails: true + UseCache: true + CacheRootDirectory: tmp + NewCops: enable Exclude: - - 'spec/**/*' - - 'db/**/*' + - db/schema.rb - 'app/views/**/*' - 'config/**/*' - 'bin/*' @@ -67,15 +73,57 @@ Lint/UselessAccessModifier: - class_methods Metrics/AbcSize: - Max: 115 + Max: 34 # RuboCop default 17 Exclude: - - 'lib/mastodon/*_cli.rb' + - 'lib/**/*cli*.rb' + - db/*migrate/**/* + - lib/paperclip/color_extractor.rb + - app/workers/scheduler/follow_recommendations_scheduler.rb + - app/services/activitypub/fetch*_service.rb + - lib/paperclip/**/* + CountRepeatedAttributes: false + AllowedMethods: + - update_media_attachments! + - account_link_to + - attempt_oembed + - build_crutches + - calculate_scores + - cc + - dump_actor! + - filter_from_home? + - hydrate + - import_bookmarks! + - import_relationships! + - initialize + - link_to_mention + - log_target + - matches_time_window? + - parse_metadata + - perform_statuses_search! + - privatize_media_attachments! + - process_update + - publish_media_attachments! + - remotable_attachment + - render_initial_state + - render_with_cache + - searchable_by + - self.cached_filters_for + - set_fetchable_attributes! + - signed_request_actor + - statuses_to_delete + - update_poll! Metrics/BlockLength: Max: 55 Exclude: - - 'lib/tasks/**/*' - 'lib/mastodon/*_cli.rb' + CountComments: false + CountAsOne: [array, heredoc] + AllowedMethods: + - task + - namespace + - class_methods + - included Metrics/BlockNesting: Max: 3 @@ -85,34 +133,144 @@ Metrics/BlockNesting: Metrics/ClassLength: CountComments: false Max: 500 + CountAsOne: [array, heredoc] Exclude: - 'lib/mastodon/*_cli.rb' Metrics/CyclomaticComplexity: - Max: 25 + Max: 12 Exclude: - - 'lib/mastodon/*_cli.rb' + - lib/mastodon/*cli*.rb + - db/*migrate/**/* + AllowedMethods: + - attempt_oembed + - blocked? + - build_crutches + - calculate_scores + - cc + - discover_endpoint! + - filter_from_home? + - hydrate + - klass + - link_to_mention + - log_target + - matches_time_window? + - patch_for_forwarding! + - preprocess_attributes! + - process_update + - remotable_attachment + - scan_text! + - self.cached_filters_for + - set_fetchable_attributes! + - setup_redis_env_url + - update_media_attachments! Layout/LineLength: + Max: 140 # RuboCop default 120 + AllowHeredoc: true AllowURI: true - Enabled: false + IgnoreCopDirectives: true + AllowedPatterns: + # Allow comments to be long lines + - !ruby/regexp / \# .*$/ + - !ruby/regexp /^\# .*$/ + Exclude: + - lib/**/*cli*.rb + - db/*migrate/**/* + - db/seeds/**/* Metrics/MethodLength: CountComments: false - Max: 65 + CountAsOne: [array, heredoc] + Max: 25 # RuboCop default 10 Exclude: - 'lib/mastodon/*_cli.rb' + AllowedMethods: + - account_link_to + - attempt_oembed + - body_with_limit + - build_crutches + - cached_filters_for + - calculate_scores + - check_webfinger! + - clean_feeds! + - collection_items + - collection_presenter + - copy_account_notes! + - deduplicate_accounts! + - deduplicate_conversations! + - deduplicate_local_accounts! + - deduplicate_statuses! + - deduplicate_tags! + - deduplicate_users! + - discover_endpoint! + - extract_extra_uris_with_indices + - extract_hashtags_with_indices + - extract_mentions_or_lists_with_indices + - filter_from_home? + - from_elasticsearch + - handle_explicit_update! + - handle_mark_as_sensitive! + - hsl_to_rgb + - import_bookmarks! + - import_domain_blocks! + - import_relationships! + - ldap_options + - matches_time_window? + - outbox_presenter + - pam_get_user + - parallelize_with_progress + - parse_and_transform + - patch_for_forwarding! + - populate_home + - post_process_style + - preload_cache_collection_target_statuses + - privatize_media_attachments! + - provides_callback_for + - publish_media_attachments! + - relevant_account_timestamp + - remotable_attachment + - rgb_to_hsl + - rss_status_content_format + - set_fetchable_attributes! + - setup_redis_env_url + - signed_request_actor + - to_preview_card_attributes + - upgrade_storage_filesystem + - upgrade_storage_s3 + - user_settings_params + - hydrate + - cc + - self_destruct Metrics/ModuleLength: CountComments: false Max: 200 + CountAsOne: [array, heredoc] Metrics/ParameterLists: - Max: 5 - CountKeywordArgs: true + Max: 5 # RuboCop default 5 + CountKeywordArgs: true # RuboCop default true + MaxOptionalParameters: 3 # RuboCop default 3 + Exclude: + - app/models/concerns/account_interactions.rb + - app/services/activitypub/fetch_remote_account_service.rb + - app/services/activitypub/fetch_remote_actor_service.rb Metrics/PerceivedComplexity: - Max: 25 + Max: 16 # RuboCop default 8 + AllowedMethods: + - attempt_oembed + - build_crutches + - calculate_scores + - deduplicate_users! + - discover_endpoint! + - filter_from_home? + - hydrate + - patch_for_forwarding! + - process_update + - remove_orphans + - update_media_attachments! Naming/MemoizedInstanceVariableName: Enabled: false @@ -267,9 +425,6 @@ Style/PercentLiteralDelimiters: Style/PerlBackrefs: AutoCorrect: false -Style/RedundantAssignment: - Enabled: false - Style/RedundantFetchBlock: Enabled: true @@ -292,7 +447,7 @@ Style/RegexpLiteral: Enabled: false Style/RescueStandardError: - Enabled: false + Enabled: true Style/SignalException: Enabled: false @@ -311,3 +466,14 @@ Style/TrailingCommaInHashLiteral: Style/UnpackFirst: Enabled: false + +RSpec/ScatteredSetup: + Enabled: false +RSpec/ImplicitExpect: + Enabled: false +RSpec/NamedSubject: + Enabled: false +RSpec/DescribeClass: + Enabled: false +RSpec/LetSetup: + Enabled: false diff --git a/Gemfile b/Gemfile index 9fe2a1120..92620d991 100644 --- a/Gemfile +++ b/Gemfile @@ -107,6 +107,10 @@ group :development, :test do gem 'pry-byebug', '~> 3.10' gem 'pry-rails', '~> 0.3' gem 'rspec-rails', '~> 5.1' + gem 'rubocop-performance', require: false + gem 'rubocop-rails', require: false + gem 'rubocop-rspec', require: false + gem 'rubocop', require: false end group :production, :test do @@ -136,8 +140,6 @@ group :development do gem 'letter_opener', '~> 1.8' gem 'letter_opener_web', '~> 2.0' gem 'memory_profiler' - gem 'rubocop', '~> 1.30', require: false - gem 'rubocop-rails', '~> 2.15', require: false gem 'brakeman', '~> 5.4', require: false gem 'bundler-audit', '~> 0.9', require: false diff --git a/Gemfile.lock b/Gemfile.lock index ac3f560ea..25ffebdbe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -587,21 +587,27 @@ GEM rspec-support (3.11.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.30.1) + rubocop (1.39.0) + json (~> 2.3) parallel (~> 1.10) - parser (>= 3.1.0.0) + parser (>= 3.1.2.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.18.0, < 2.0) + rubocop-ast (>= 1.23.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.18.0) + rubocop-ast (1.23.0) parser (>= 3.1.1.0) - rubocop-rails (2.15.0) + rubocop-performance (1.15.1) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.17.2) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.15.0) + rubocop (~> 1.33) ruby-progressbar (1.11.0) ruby-saml (1.13.0) nokogiri (>= 1.10.5) @@ -843,8 +849,10 @@ DEPENDENCIES rspec-rails (~> 5.1) rspec-sidekiq (~> 3.1) rspec_junit_formatter (~> 0.6) - rubocop (~> 1.30) - rubocop-rails (~> 2.15) + rubocop + rubocop-performance + rubocop-rails + rubocop-rspec ruby-progressbar (~> 1.11) sanitize (~> 6.0) scenic (~> 1.6) @@ -869,3 +877,9 @@ DEPENDENCIES webpacker (~> 5.4) webpush! xorcist (~> 1.1) + +RUBY VERSION + ruby 3.0.4p208 + +BUNDLED WITH + 2.2.33 From 623d3d2e32ac8ec2819f2cd99e6565d06c9b0023 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 16:40:32 +0100 Subject: [PATCH 32/66] Change CSP directives on API to be tight and concise (#20960) --- app/controllers/api/base_controller.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index defef0656..41f3ce2ee 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -16,6 +16,26 @@ class Api::BaseController < ApplicationController protect_from_forgery with: :null_session + content_security_policy do |p| + # Set every directive that does not have a fallback + p.default_src :none + p.frame_ancestors :none + p.form_action :none + + # Disable every directive with a fallback to cut on response size + p.base_uri false + p.font_src false + p.img_src false + p.style_src false + p.media_src false + p.frame_src false + p.manifest_src false + p.connect_src false + p.script_src false + p.child_src false + p.worker_src false + end + rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| render json: { error: e.to_s }, status: 422 end From 38596e49d4ea00b9a538f32fd443afc6aa29824a Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 16:40:45 +0100 Subject: [PATCH 33/66] Fix the top action bar appearing in multi-column layout (#20943) --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d03ab03c2..0cde9f55c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2528,7 +2528,7 @@ $ui-header-height: 55px; } } - .ui__header { + .layout-single-column .ui__header { display: flex; background: $ui-base-color; border-bottom: 1px solid lighten($ui-base-color, 8%); From 725f21662f7ba287b36adc1d973c2aa57296c781 Mon Sep 17 00:00:00 2001 From: Fries <40834252+ayefries@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:40:57 +0100 Subject: [PATCH 34/66] Add Montenegrin (cnr) (#21013) --- app/helpers/languages_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index fff073ced..5e70a8f5c 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -190,6 +190,7 @@ module LanguagesHelper ISO_639_3 = { ast: ['Asturian', 'Asturianu'].freeze, ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze, + cnr: ['Montenegrin', 'crnogorski'].freeze, jbo: ['Lojban', 'la .lojban.'].freeze, kab: ['Kabyle', 'Taqbaylit'].freeze, kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze, From bbc49f15e030df4e75af06ece8c5302b80b69342 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 16:44:29 +0100 Subject: [PATCH 35/66] Add explanation text to log-in page (#20946) --- app/views/auth/sessions/new.html.haml | 2 ++ config/locales/en.yml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml index 304e3ab84..e98c1ff3d 100644 --- a/app/views/auth/sessions/new.html.haml +++ b/app/views/auth/sessions/new.html.haml @@ -6,6 +6,8 @@ - unless omniauth_only? = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| + %h1.title= t('auth.sign_in.title', domain: site_hostname) + %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 diff --git a/config/locales/en.yml b/config/locales/en.yml index 9f71e5ed7..075ce2136 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -975,6 +975,9 @@ en: email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail. email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings. title: Setup + sign_in: + preamble_html: Sign in with your %{domain} credentials. If your account is hosted on a different server, you will not be able to log in here. + title: Sign in to %{domain} sign_up: preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted. title: Let's get you set up on %{domain}. From 673c54f114be78e0588f2bb883f2962dbc7574a7 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 17:04:38 +0100 Subject: [PATCH 36/66] Fix inability to use local LibreTranslate without setting ALLOWED_PRIVATE_ADDRESSES (#21926) Fixes #20029 --- app/lib/request.rb | 3 ++- app/lib/translation_service/libre_translate.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index 96d934a8f..b2819c8ed 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -30,7 +30,8 @@ class Request @verb = verb @url = Addressable::URI.parse(url).normalize @http_client = options.delete(:http_client) - @options = options.merge(socket_class: use_proxy? ? ProxySocket : Socket) + @allow_local = options.delete(:allow_local) + @options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket) @options = @options.merge(proxy_url) if use_proxy? @headers = {} diff --git a/app/lib/translation_service/libre_translate.rb b/app/lib/translation_service/libre_translate.rb index 43576e306..4ebe21e45 100644 --- a/app/lib/translation_service/libre_translate.rb +++ b/app/lib/translation_service/libre_translate.rb @@ -27,7 +27,7 @@ class TranslationService::LibreTranslate < TranslationService def request(text, source_language, target_language) body = Oj.dump(q: text, source: source_language.presence || 'auto', target: target_language, format: 'html', api_key: @api_key) - req = Request.new(:post, "#{@base_url}/translate", body: body) + req = Request.new(:post, "#{@base_url}/translate", body: body, allow_local: true) req.add_headers('Content-Type': 'application/json') req end From 059d64a59ea27c40ca6b5b9bf34487169d2a741f Mon Sep 17 00:00:00 2001 From: Meisam <39205857+MFTabriz@users.noreply.github.com> Date: Thu, 15 Dec 2022 17:04:52 +0100 Subject: [PATCH 37/66] set activation for tag follow button (#21629) Co-authored-by: meisam --- app/javascript/mastodon/features/hashtag_timeline/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index b635c3529..733f54ff3 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -194,7 +194,7 @@ class HashtagTimeline extends React.PureComponent { const following = tag.get('following'); followButton = ( - ); From 1e95fa3df5ad65051bf598da91d589f69b652959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matth=C3=ADas=20P=C3=A1ll=20Gissurarson?= Date: Thu, 15 Dec 2022 17:05:40 +0100 Subject: [PATCH 38/66] Fix punycoded local domains not being prettified in initial state (#21440) --- app/serializers/initial_state_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 8d3f4f87d..70f40088d 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -16,7 +16,7 @@ class InitialStateSerializer < ActiveModel::Serializer streaming_api_base_url: Rails.configuration.x.streaming_api_base_url, access_token: object.token, locale: I18n.locale, - domain: instance_presenter.domain, + domain: Addressable::IDNA.to_unicode(instance_presenter.domain), title: instance_presenter.title, admin: object.admin&.id&.to_s, search_enabled: Chewy.enabled?, From 7972e5981c312eb11e5f7f5676e7d565881ccdaa Mon Sep 17 00:00:00 2001 From: Yurii Izorkin Date: Thu, 15 Dec 2022 19:07:36 +0300 Subject: [PATCH 39/66] Add brotli compression (#19025) --- config/webpack/production.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/webpack/production.js b/config/webpack/production.js index 79dcebc7c..143a23b99 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -34,6 +34,12 @@ module.exports = merge(sharedConfig, { cache: true, test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/, }), + new CompressionPlugin({ + filename: '[path][base].br[query]', + algorithm: 'brotliCompress', + cache: true, + test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/, + }), new BundleAnalyzerPlugin({ // generates report.html analyzerMode: 'static', openAnalyzer: false, From 8f8c0fe88c19b38602d3f9de7742211b1b690af0 Mon Sep 17 00:00:00 2001 From: Luxiaba <5391976+luxiaba@users.noreply.github.com> Date: Fri, 16 Dec 2022 00:10:34 +0800 Subject: [PATCH 40/66] Remove inline-css in logo (#20814) --- app/javascript/images/logo-symbol-icon.svg | 2 +- app/javascript/images/logo-symbol-wordmark.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/images/logo-symbol-icon.svg b/app/javascript/images/logo-symbol-icon.svg index 56cf03921..c4c14f098 100644 --- a/app/javascript/images/logo-symbol-icon.svg +++ b/app/javascript/images/logo-symbol-icon.svg @@ -1,2 +1,2 @@ - + diff --git a/app/javascript/images/logo-symbol-wordmark.svg b/app/javascript/images/logo-symbol-wordmark.svg index 7e7f7b087..ee0b636d9 100644 --- a/app/javascript/images/logo-symbol-wordmark.svg +++ b/app/javascript/images/logo-symbol-wordmark.svg @@ -7,5 +7,5 @@ - + From d412147d02e84cb76b252706a5357fe5d434c3db Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Fri, 16 Dec 2022 01:11:14 +0900 Subject: [PATCH 41/66] Save avatar or header correctly even if other one fails (#18465) * Save avatar or header correctly if other one fails * Fix test --- app/models/account.rb | 12 +++++++++--- spec/models/account_spec.rb | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index fc7359cfc..a7bda15d3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -341,9 +341,15 @@ class Account < ApplicationRecord def save_with_optional_media! save! - rescue ActiveRecord::RecordInvalid - self.avatar = nil - self.header = nil + rescue ActiveRecord::RecordInvalid => e + errors = e.record.errors.errors + errors.each do |err| + if err.attribute == :avatar + self.avatar = nil + elsif err.attribute == :header + self.header = nil + end + end save! end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index edae05f9d..c9d782cee 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -160,7 +160,7 @@ RSpec.describe Account, type: :model do expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar' expect(account.header_remote_url).to eq expectation.header_remote_url expect(account.avatar_file_name).to eq nil - expect(account.header_file_name).to eq nil + expect(account.header_file_name).to eq expectation.header_file_name end end end From 1b5d20713187465c13986eea0f3a487d254b6e63 Mon Sep 17 00:00:00 2001 From: David Vega Date: Thu, 15 Dec 2022 08:11:58 -0800 Subject: [PATCH 42/66] Fix single name variables on controller folder (#20092) Co-authored-by: petrokoriakin1 <116151189+petrokoriakin1@users.noreply.github.com> Co-authored-by: petrokoriakin1 <116151189+petrokoriakin1@users.noreply.github.com> Co-authored-by: Effy Elden --- app/controllers/auth/registrations_controller.rb | 4 ++-- app/controllers/concerns/rate_limit_headers.rb | 2 +- app/controllers/concerns/signature_verification.rb | 4 ++-- app/controllers/follower_accounts_controller.rb | 2 +- app/controllers/following_accounts_controller.rb | 2 +- app/controllers/media_controller.rb | 4 ++-- app/controllers/statuses_controller.rb | 4 ++-- app/controllers/tags_controller.rb | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index cd1c546b8..71c0cd827 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -56,8 +56,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def configure_sign_up_params - devise_parameter_sanitizer.permit(:sign_up) do |u| - u.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password) + devise_parameter_sanitizer.permit(:sign_up) do |user_params| + user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password) end end diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/rate_limit_headers.rb index 86fe58a71..b8696df73 100644 --- a/app/controllers/concerns/rate_limit_headers.rb +++ b/app/controllers/concerns/rate_limit_headers.rb @@ -58,7 +58,7 @@ module RateLimitHeaders end def api_throttle_data - most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] } + most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_key, value| value[:limit] - value[:count] } request.env['rack.attack.throttle_data'][most_limited_type] end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 2394574b3..4502da698 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -28,8 +28,8 @@ module SignatureVerification end class SignatureParamsTransformer < Parslet::Transform - rule(params: subtree(:p)) do - (p.is_a?(Array) ? p : [p]).each_with_object({}) { |(key, val), h| h[key] = val } + rule(params: subtree(:param)) do + (param.is_a?(Array) ? param : [param]).each_with_object({}) { |(key, value), hash| hash[key] = value } end rule(param: { key: simple(:key), value: simple(:val) }) do diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index e4d8cc495..9ced18449 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -63,7 +63,7 @@ class FollowerAccountsController < ApplicationController id: account_followers_url(@account, page: params.fetch(:page, 1)), type: :ordered, size: @account.followers_count, - items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) }, + items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.account) }, part_of: account_followers_url(@account), next: next_page_url, prev: prev_page_url diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index f84dca1e5..febd13c97 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -66,7 +66,7 @@ class FollowingAccountsController < ApplicationController id: account_following_index_url(@account, page: params.fetch(:page, 1)), type: :ordered, size: @account.following_count, - items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) }, + items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.target_account) }, part_of: account_following_index_url(@account), next: next_page_url, prev: prev_page_url diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index ee82625a0..3cdd97f06 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -12,8 +12,8 @@ class MediaController < ApplicationController before_action :check_playable, only: :player before_action :allow_iframing, only: :player - content_security_policy only: :player do |p| - p.frame_ancestors(false) + content_security_policy only: :player do |policy| + policy.frame_ancestors(false) end def show diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 9eb7ad691..0e0783b4b 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -17,8 +17,8 @@ class StatusesController < ApplicationController skip_around_action :set_locale, if: -> { request.format == :json } skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode? - content_security_policy only: :embed do |p| - p.frame_ancestors(false) + content_security_policy only: :embed do |policy| + policy.frame_ancestors(false) end def show diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index f0a099350..65017acba 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -65,7 +65,7 @@ class TagsController < ApplicationController id: tag_url(@tag), type: :ordered, size: @tag.statuses.count, - items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) } + items: @statuses.map { |status| ActivityPub::TagManager.instance.uri_for(status) } ) end end From cedf1383138b2e58ba6ff9aab46beddeaf1a1354 Mon Sep 17 00:00:00 2001 From: Mina Her Date: Fri, 16 Dec 2022 01:24:38 +0900 Subject: [PATCH 43/66] Fix margin for search field on medium window size (#21606) --- app/javascript/styles/mastodon/components.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 0cde9f55c..15fc6aa69 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2474,8 +2474,7 @@ $ui-header-height: 55px; height: calc(100% - 10px) !important; } - .getting-started__wrapper, - .search { + .getting-started__wrapper { margin-bottom: 10px; } @@ -4671,6 +4670,7 @@ a.status-card.compact:hover { } .search { + margin-bottom: 10px; position: relative; } From 3656a6b9cc353f7f08a2d8f00c1b3f2fd8e3fb21 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Fri, 16 Dec 2022 01:30:47 +0900 Subject: [PATCH 44/66] Add "disabled" user filter for admin/accounts UI (#21282) --- app/models/account_filter.rb | 2 +- app/views/admin/accounts/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 3a4ac0492..d27bb46fc 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -81,7 +81,7 @@ class AccountFilter when 'suspended' Account.suspended when 'disabled' - accounts_with_users.merge(User.disabled) + accounts_with_users.merge(User.disabled).without_suspended when 'silenced' Account.silenced when 'sensitized' diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index f33f788ed..d0897221d 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -13,7 +13,7 @@ .filter-subset.filter-subset--with-select %strong= t('admin.accounts.moderation.title') .input.select.optional - = select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all') + = select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.disabled'), 'disabled'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all') .filter-subset.filter-subset--with-select %strong= t('admin.accounts.role') .input.select.optional From 9f63c428e188a11f85741aac72a62ef2f7b5421b Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 15 Dec 2022 17:37:05 +0100 Subject: [PATCH 45/66] Don't autofocus the compose form (#16517) When opening a page such as /web/timelines/home in a desktop browser, the cursor was automatically placed in the textarea of the compose form. When using the keyboard for navigation (using a browser plugin like vimium or vim vixen, or just to hit 'space' to scroll down a page), you have remember to leave the field before using that. Since you only visit the page to write a new post some of the time, this PR attempts to have nothing focused initially (and require the user to click or e.g. use 'tab' to focus the textarea). Tested: * /web/timeslines/home no longer autofocuses the compose box * pressing the 'n' hotkey still focuses the compose box * clicking 'reply' for a post still focuses the compose box * replying to a CW'ed post still focuses the compose box * introducing the CW field still focuses the CW field * introducing the CW field for a reply still focuses the CW field * removing the CW field still focuses the compose box * /web/statuses/new still autofocuses the compose box fixes #15862 --- .../features/compose/components/compose_form.js | 13 ++++++------- .../compose/containers/compose_form_container.js | 1 - app/javascript/mastodon/features/compose/index.js | 3 ++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 6a65f44da..234a2afed 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -16,7 +16,6 @@ import PollFormContainer from '../containers/poll_form_container'; import UploadFormContainer from '../containers/upload_form_container'; import WarningContainer from '../containers/warning_container'; import LanguageDropdown from '../containers/language_dropdown_container'; -import { isMobile } from '../../../is_mobile'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { length } from 'stringz'; import { countableText } from '../util/counter'; @@ -61,14 +60,14 @@ class ComposeForm extends ImmutablePureComponent { onChangeSpoilerText: PropTypes.func.isRequired, onPaste: PropTypes.func.isRequired, onPickEmoji: PropTypes.func.isRequired, - showSearch: PropTypes.bool, + autoFocus: PropTypes.bool, anyMedia: PropTypes.bool, isInReply: PropTypes.bool, singleColumn: PropTypes.bool, }; static defaultProps = { - showSearch: false, + autoFocus: false, }; handleChange = (e) => { @@ -154,7 +153,7 @@ class ComposeForm extends ImmutablePureComponent { // - Replying to zero or one users, places the cursor at the end of the textbox. // - Replying to more than one user, selects any usernames past the first; // this provides a convenient shortcut to drop everyone else from the conversation. - if (this.props.focusDate !== prevProps.focusDate) { + if (this.props.focusDate && this.props.focusDate !== prevProps.focusDate) { let selectionEnd, selectionStart; if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply) { @@ -180,7 +179,7 @@ class ComposeForm extends ImmutablePureComponent { } else if (this.props.spoiler !== prevProps.spoiler) { if (this.props.spoiler) { this.spoilerText.input.focus(); - } else { + } else if (prevProps.spoiler) { this.autosuggestTextarea.textarea.focus(); } } @@ -207,7 +206,7 @@ class ComposeForm extends ImmutablePureComponent { } render () { - const { intl, onPaste, showSearch } = this.props; + const { intl, onPaste, autoFocus } = this.props; const disabled = this.props.isSubmitting; let publishText = ''; @@ -257,7 +256,7 @@ class ComposeForm extends ImmutablePureComponent { onSuggestionsClearRequested={this.onSuggestionsClearRequested} onSuggestionSelected={this.onSuggestionSelected} onPaste={onPaste} - autoFocus={!showSearch && !isMobile(window.innerWidth)} + autoFocus={autoFocus} > diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 1be7633cc..14cf9230b 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -24,7 +24,6 @@ const mapStateToProps = state => ({ isEditing: state.getIn(['compose', 'id']) !== null, isChangingUpload: state.getIn(['compose', 'is_changing_upload']), isUploading: state.getIn(['compose', 'is_uploading']), - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, isInReply: state.getIn(['compose', 'in_reply_to']) !== null, }); diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index f744fc611..aead7776a 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -18,6 +18,7 @@ import Icon from 'mastodon/components/icon'; import { logOut } from 'mastodon/utils/log_out'; import Column from 'mastodon/components/column'; import { Helmet } from 'react-helmet'; +import { isMobile } from '../../is_mobile'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -115,7 +116,7 @@ class Compose extends React.PureComponent {
- +
From ff414a5489fd2c6d154b18effca24dde54d92958 Mon Sep 17 00:00:00 2001 From: Terence Eden Date: Thu, 15 Dec 2022 16:38:35 +0000 Subject: [PATCH 46/66] Add transparancy to modal background for accessibility (#18081) Fixes #18080 This keeps the `ui-base-lighter-color` but adds enough transparency so that text is more easily readable. Tested in Firefox and Chrome. --- app/javascript/styles/mastodon/modal.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/modal.scss b/app/javascript/styles/mastodon/modal.scss index 6c6de4206..a333926dd 100644 --- a/app/javascript/styles/mastodon/modal.scss +++ b/app/javascript/styles/mastodon/modal.scss @@ -1,5 +1,5 @@ .modal-layout { - background: $ui-base-color url('data:image/svg+xml;utf8,') repeat-x bottom fixed; + background: $ui-base-color url('data:image/svg+xml;utf8,') repeat-x bottom fixed; display: flex; flex-direction: column; height: 100vh; From 726c7dea31d2ee60b327afd327e945e3ece09ac4 Mon Sep 17 00:00:00 2001 From: Rens Groothuijsen Date: Thu, 15 Dec 2022 17:38:50 +0100 Subject: [PATCH 47/66] Display search popout at fixed screen position (#16463) * Display search popout at fixed screen position * Attach search popout to search box --- app/javascript/mastodon/features/compose/components/search.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index 03e6dcf2c..8254fb607 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -140,8 +140,7 @@ class Search extends React.PureComponent {
- - +
From 8a56587d62d08606e5ed11eb1b93e238cf5aa2fe Mon Sep 17 00:00:00 2001 From: Avdi Grimm Date: Thu, 15 Dec 2022 10:40:36 -0600 Subject: [PATCH 48/66] Improve devcontainer for running tests (#22277) * Improve devcontainer for running tests - Pull devcontainer post-create out into its own script - Add asset precompilation - Add test-mode asset precompilation (needed to run tests without error) * Document Gemfile.lock re-checkout in devcontainer --- .devcontainer/devcontainer.json | 2 +- .devcontainer/post-create.sh | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100755 .devcontainer/post-create.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 47497794f..5ac56c842 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,7 @@ "forwardPorts": [3000, 4000], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup", + "postCreateCommand": ".devcontainer/post-create.sh", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 000000000..02f488f12 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e # Fail the whole script on first error + +# Fetch Ruby gem dependencies +bundle install --path vendor/bundle --with='development test' + +# Fetch Javascript dependencies +yarn install + +# Make Gemfile.lock pristine again +git checkout -- Gemfile.lock + +# [re]create, migrate, and seed the test database +RAILS_ENV=test ./bin/rails db:setup + +# Precompile assets for development +RAILS_ENV=development ./bin/rails assets:precompile + +# Precompile assets for test +RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile From fb1d9789dba288599e05ba813af0c61f484e205c Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 17:41:20 +0100 Subject: [PATCH 49/66] Fix attachment rendering of edited posts in OpenGraph (#22270) Fixes #22241 --- app/helpers/statuses_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index 488eabeec..d1e3fddaf 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -21,7 +21,7 @@ module StatusesHelper def media_summary(status) attachments = { image: 0, video: 0, audio: 0 } - status.media_attachments.each do |media| + status.ordered_media_attachments.each do |media| if media.video? attachments[:video] += 1 elsif media.audio? From 1e49be33289b969be64620b904d589158e7b579a Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Fri, 16 Dec 2022 03:43:26 +1100 Subject: [PATCH 50/66] Align everything to Node.js 16 (#22223) * Update nvmrc to Node.js 16 * Update package.json minimum Node engine to 16 * Update README requirements to Node.js 16 * Update devcontainer Node.js version to 16 * Update devcontainer Dockerfile Node.js choices to LTS versions that are still in support/maintenance * Pin CircleCI Node image to 16 * Fix YAML type issue * Update CircleCI Node.js to 16.18 to match #22019 --- .circleci/config.yml | 2 +- .devcontainer/Dockerfile | 2 +- .devcontainer/docker-compose.yml | 2 +- .nvmrc | 2 +- README.md | 2 +- package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bddfd2d27..82d9f9ef6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,5 +221,5 @@ workflows: pkg-manager: yarn requires: - build - version: lts + version: '16.18' yarn-run: test:jest diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac495e1c9..425b86a6b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT} # The value is a comma-separated list of allowed domains ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev" -# [Choice] Node.js version: lts/*, 16, 14, 12, 10 +# [Choice] Node.js version: lts/*, 18, 16, 14 ARG NODE_VERSION="lts/*" RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1" diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 46f42c454..a61116fca 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -11,7 +11,7 @@ services: # Use -bullseye variants on local arm64/Apple Silicon. VARIANT: '3.0-bullseye' # Optional Node.js version to install - NODE_VERSION: '14' + NODE_VERSION: '16' volumes: - ..:/workspaces/mastodon:cached environment: diff --git a/.nvmrc b/.nvmrc index 8351c1939..b6a7d89c6 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14 +16 diff --git a/README.md b/README.md index 277ae0cc1..ddd5e2c64 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre - **PostgreSQL** 9.5+ - **Redis** 4+ - **Ruby** 2.7+ -- **Node.js** 14+ +- **Node.js** 16+ The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. diff --git a/package.json b/package.json index dd93102ed..9f0c836fd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@mastodon/mastodon", "license": "AGPL-3.0-or-later", "engines": { - "node": ">=14" + "node": ">=16" }, "scripts": { "postversion": "git push --tags", From 8556a649d58a7291db6942a2594533f9b8c06165 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 17:45:02 +0100 Subject: [PATCH 51/66] Fix changing domain block severity not undoing individual account effects (#22135) * Fix changing domain block severity not undoing individual account effects Fixes #22133 * Add tests --- .../admin/domain_blocks_controller.rb | 8 +--- .../api/v1/admin/domain_blocks_controller.rb | 6 +-- .../admin/domain_blocks_controller_spec.rb | 47 +++++++++++++++++++ .../v1/admin/domain_blocks_controller_spec.rb | 47 +++++++++++++++++++ 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index e79f7a43e..74764640b 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -55,12 +55,8 @@ module Admin def update authorize :domain_block, :update? - @domain_block.update(update_params) - - severity_changed = @domain_block.severity_changed? - - if @domain_block.save - DomainBlockWorker.perform_async(@domain_block.id, severity_changed) + if @domain_block.update(update_params) + DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?) log_action :update, @domain_block redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') else diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb index df5b1b3fc..8b77e9717 100644 --- a/app/controllers/api/v1/admin/domain_blocks_controller.rb +++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb @@ -40,10 +40,8 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController def update authorize @domain_block, :update? - @domain_block.update(domain_block_params) - severity_changed = @domain_block.severity_changed? - @domain_block.save! - DomainBlockWorker.perform_async(@domain_block.id, severity_changed) + @domain_block.update!(domain_block_params) + DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?) log_action :update, @domain_block render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer end diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb index 98cda5004..f432060d9 100644 --- a/spec/controllers/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/domain_blocks_controller_spec.rb @@ -70,6 +70,53 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do end end + describe 'PUT #update' do + let!(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: original_severity) } + + before do + BlockDomainService.new.call(domain_block) + end + + let(:subject) do + post :update, params: { id: domain_block.id, domain_block: { domain: 'example.com', severity: new_severity } } + end + + context 'downgrading a domain suspension to silence' do + let(:original_severity) { 'suspend' } + let(:new_severity) { 'silence' } + + it 'changes the block severity' do + expect { subject }.to change { domain_block.reload.severity }.from('suspend').to('silence') + end + + it 'undoes individual suspensions' do + expect { subject }.to change { remote_account.reload.suspended? }.from(true).to(false) + end + + it 'performs individual silences' do + expect { subject }.to change { remote_account.reload.silenced? }.from(false).to(true) + end + end + + context 'upgrading a domain silence to suspend' do + let(:original_severity) { 'silence' } + let(:new_severity) { 'suspend' } + + it 'changes the block severity' do + expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend') + end + + it 'undoes individual silences' do + expect { subject }.to change { remote_account.reload.silenced? }.from(true).to(false) + end + + it 'performs individual suspends' do + expect { subject }.to change { remote_account.reload.suspended? }.from(false).to(true) + end + end + end + describe 'DELETE #destroy' do it 'unblocks the domain' do service = double(call: true) diff --git a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb index f12285b2a..606def602 100644 --- a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb @@ -71,6 +71,53 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do end end + describe 'PUT #update' do + let!(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: original_severity) } + + before do + BlockDomainService.new.call(domain_block) + end + + let(:subject) do + post :update, params: { id: domain_block.id, domain: 'example.com', severity: new_severity } + end + + context 'downgrading a domain suspension to silence' do + let(:original_severity) { 'suspend' } + let(:new_severity) { 'silence' } + + it 'changes the block severity' do + expect { subject }.to change { domain_block.reload.severity }.from('suspend').to('silence') + end + + it 'undoes individual suspensions' do + expect { subject }.to change { remote_account.reload.suspended? }.from(true).to(false) + end + + it 'performs individual silences' do + expect { subject }.to change { remote_account.reload.silenced? }.from(false).to(true) + end + end + + context 'upgrading a domain silence to suspend' do + let(:original_severity) { 'silence' } + let(:new_severity) { 'suspend' } + + it 'changes the block severity' do + expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend') + end + + it 'undoes individual silences' do + expect { subject }.to change { remote_account.reload.silenced? }.from(true).to(false) + end + + it 'performs individual suspends' do + expect { subject }.to change { remote_account.reload.suspended? }.from(false).to(true) + end + end + end + describe 'DELETE #destroy' do let!(:block) { Fabricate(:domain_block) } From ebf1d74e409ece10864a8615691cd80c434c9055 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 17:47:43 +0100 Subject: [PATCH 52/66] Fix being stuck in edit mode when deleting the edited status (#22126) --- app/javascript/mastodon/reducers/compose.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 9496b56f8..60b0cfb57 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -431,6 +431,8 @@ export default function compose(state = initialState, action) { case TIMELINE_DELETE: if (action.id === state.get('in_reply_to')) { return state.set('in_reply_to', null); + } else if (action.id === state.get('id')) { + return state.set('id', null); } else { return state; } From 2d1294822089a8f1467723bed425eed51dd7db79 Mon Sep 17 00:00:00 2001 From: Brian Campbell Date: Thu, 15 Dec 2022 12:08:40 -0500 Subject: [PATCH 53/66] Fix idempotency when database writes are slow (#21840) There is an idempotency key generated by clients when authoring a post, and stored in Redis, to ensure that if a user or client retries posting the same status, we don't get a duplicate. Hachyderm.io has been experiencing some filesystem and database performance issues, causing database writes to be slow. This can mean that there are successful posts, but the reverse proxy returns 504 Gateway Timeout before the idempotency status has been updated; users or clients who retry (such as Tusky which retries automatically, see tuskyapp/Tusky#2951) can re-try the same post with the same idempotency key before it has actually been recorded in Redis, leading to duplicate posts. To address this issue, move all of the database updates after the initial transaction that creates the status into the `postprocess_status!` method, so we can insert the idempotency key immediately after the status has been created, significantly reducing the window in which the status could be created but the idempotency key not yet stored. Note: this has not yet been tested; I'm submitting this PR for discussion and to offer to the Hachyderm.io admins to try out to fix the multiple posting problem. Co-authored-by: Brian Campbell --- app/services/post_status_service.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index c132930a9..bd3b69632 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -37,12 +37,15 @@ class PostStatusService < BaseService schedule_status! else process_status! - postprocess_status! - bump_potential_friendship! end redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given? + unless scheduled? + postprocess_status! + bump_potential_friendship! + end + @status end @@ -66,9 +69,6 @@ class PostStatusService < BaseService ApplicationRecord.transaction do @status = @account.statuses.create!(status_attributes) end - - process_hashtags_service.call(@status) - process_mentions_service.call(@status) end def schedule_status! @@ -92,6 +92,8 @@ class PostStatusService < BaseService end def postprocess_status! + process_hashtags_service.call(@status) + process_mentions_service.call(@status) Trends.tags.register(@status) LinkCrawlWorker.perform_async(@status.id) DistributionWorker.perform_async(@status.id) From 2644a28cb30dfb57b9543dd045657e8ed660876a Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 18:09:48 +0100 Subject: [PATCH 54/66] Change remote media files to be downloaded outside of transactions (#21796) --- app/models/media_attachment.rb | 2 + .../process_status_update_service.rb | 42 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 51b256482..5916b0b4b 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -210,6 +210,8 @@ class MediaAttachment < ApplicationRecord default_scope { order(id: :asc) } + attr_accessor :skip_download + def local? remote_url.blank? end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index fad19f87f..11b38ab92 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -45,6 +45,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService create_edits! end + download_media_files! queue_poll_notifications! next unless significant_changes? @@ -66,12 +67,12 @@ class ActivityPub::ProcessStatusUpdateService < BaseService def update_media_attachments! previous_media_attachments = @status.media_attachments.to_a previous_media_attachments_ids = @status.ordered_media_attachment_ids || previous_media_attachments.map(&:id) - next_media_attachments = [] + @next_media_attachments = [] as_array(@json['attachment']).each do |attachment| media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment) - next if media_attachment_parser.remote_url.blank? || next_media_attachments.size > 4 + next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > 4 begin media_attachment = previous_media_attachments.find { |previous_media_attachment| previous_media_attachment.remote_url == media_attachment_parser.remote_url } @@ -87,34 +88,39 @@ class ActivityPub::ProcessStatusUpdateService < BaseService media_attachment.focus = media_attachment_parser.focus media_attachment.thumbnail_remote_url = media_attachment_parser.thumbnail_remote_url media_attachment.blurhash = media_attachment_parser.blurhash + media_attachment.status_id = @status.id + media_attachment.skip_download = unsupported_media_type?(media_attachment_parser.file_content_type) || skip_download? media_attachment.save! - next_media_attachments << media_attachment - - next if unsupported_media_type?(media_attachment_parser.file_content_type) || skip_download? - - begin - media_attachment.download_file! if media_attachment.remote_url_previously_changed? - media_attachment.download_thumbnail! if media_attachment.thumbnail_remote_url_previously_changed? - media_attachment.save - rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError - RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id) - end + @next_media_attachments << media_attachment rescue Addressable::URI::InvalidURIError => e Rails.logger.debug "Invalid URL in attachment: #{e}" end end - added_media_attachments = next_media_attachments - previous_media_attachments + added_media_attachments = @next_media_attachments - previous_media_attachments - MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id) - - @status.ordered_media_attachment_ids = next_media_attachments.map(&:id) - @status.media_attachments.reload + @status.ordered_media_attachment_ids = @next_media_attachments.map(&:id) @media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids end + def download_media_files! + @next_media_attachments.each do |media_attachment| + next if media_attachment.skip_download + + media_attachment.download_file! if media_attachment.remote_url_previously_changed? + media_attachment.download_thumbnail! if media_attachment.thumbnail_remote_url_previously_changed? + media_attachment.save + rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError + RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id) + rescue Seahorse::Client::NetworkingError => e + Rails.logger.warn "Error storing media attachment: #{e}" + end + + @status.media_attachments.reload + end + def update_poll!(allow_significant_changes: true) previous_poll = @status.preloadable_poll @previous_expires_at = previous_poll&.expires_at From 99d26930719bb032860f6dd125116d84b93a3d0c Mon Sep 17 00:00:00 2001 From: Shlee Date: Fri, 16 Dec 2022 03:43:13 +1030 Subject: [PATCH 55/66] Update circleci (#21880) * Update config.yml * Update config.yml --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 82d9f9ef6..a373d685e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,8 @@ version: 2.1 orbs: - ruby: circleci/ruby@1.4.1 - node: circleci/node@5.0.1 + ruby: circleci/ruby@2.0.0 + node: circleci/node@5.0.3 executors: default: @@ -19,11 +19,11 @@ executors: DB_USER: root DISABLE_SIMPLECOV: true RAILS_ENV: test - - image: cimg/postgres:14.0 + - image: cimg/postgres:14.5 environment: POSTGRES_USER: root POSTGRES_HOST_AUTH_METHOD: trust - - image: cimg/redis:6.2 + - image: cimg/redis:7.0 commands: install-system-dependencies: @@ -45,7 +45,7 @@ commands: bundle config without 'development production' name: Set bundler settings - ruby/install-deps: - bundler-version: '2.3.8' + bundler-version: '2.3.26' key: ruby<< parameters.ruby-version >>-gems-v1 wait-db: steps: From 7fbc17afa246c5cd384123ace51e07622204b67c Mon Sep 17 00:00:00 2001 From: Bramus! Date: Thu, 15 Dec 2022 18:37:47 +0100 Subject: [PATCH 56/66] Fix media markup (#21420) This brings the markup of the MediaItem component on par with the Item component from media_gallery. Co-authored-by: Effy Elden --- .../mastodon/features/account_gallery/components/media_item.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js index f16fe07f1..13fd7fe03 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js @@ -104,6 +104,7 @@ export default class MediaItem extends ImmutablePureComponent {