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 bba4a1bd72..24cc989646 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 724719f74f..002b71e93d 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 dc3b359566..db5c255c91 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 a045db1ab1..0a0effbc1d 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 77cbef84ec..0dd8521313 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 a399f51023..9fe2a11209 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 578a88436b..ac3f560ea1 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 694bb0fb9f..36e85f20d1 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 0000000000..5d9c9a618e --- /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 0000000000..085ce542bd --- /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 2a1fedb93b..40c86afdf0 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 2996c0431b..a8ad669297 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 5530a9070d..ca98a0afab 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 dcfdebb173..1c6874f08c 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 01add232d1..804e61199e 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 0000000000..88ac2fb085 --- /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 ccf1e9e3a7..36fb0e80fb 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 2ed0d613ed..ba64fde09b 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 6fbb6e0630..c1297c8b99 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 0a0effbc1d..2fcfd4ee16 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 8aee182674..a63988224b 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 1271fc7f3a..f5d442a858 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 6c12bd0730..c161cbf3dd 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 1a97965ac6..ce7f4d7186 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 50768f83cc..87e22161bc 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 c55bea7a75..d7b252c3f2 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 6d464997ea..7b1cfe2643 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 1bdea909f7..586dcfd337 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 5607c24d5f..defba403e4 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 ebb23d92ff..03e6dcf2c8 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 ede29b8a08..c58db64670 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 286170c9ff..1ae249f458 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 445ab38947..230154eb10 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 dc165e74c6..05b9353bf6 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 20ee32aa0f..1dbd985bf4 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 5567724ae4..2df0056c21 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 05b9353bf6..997b0d9e53 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 786fc8ede2..a5e2014f7e 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 3bccd3b4bc..14df2f6090 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 cf960565f7..50ec64b065 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 4a3005f72a..cab0a17eb1 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 595094fc76..078abd7882 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 2fcfd4ee16..9f71e5ed70 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 f5d442a858..d03ab03c23 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 f4824f0457..e7def800e8 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 05c5b28c86..b8739aab33 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 c1e5bd2b45..3c891a07f0 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 a9d2f96512..c709314897 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 0000000000..af604a87b5 --- /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 aec11b0306..67284fe34b 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 9fe2a11209..92620d991c 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 ac3f560ea1..25ffebdbe1 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 defef0656f..41f3ce2ee3 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 d03ab03c23..0cde9f55c0 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 fff073cedb..5e70a8f5cf 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 304e3ab849..e98c1ff3de 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 9f71e5ed70..075ce2136f 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 96d934a8f2..b2819c8edc 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 43576e3062..4ebe21e454 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 b635c35291..733f54ff3a 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 8d3f4f87df..70f40088dc 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 79dcebc7c4..143a23b99e 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 56cf03921a..c4c14f098a 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 7e7f7b087c..ee0b636d93 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 fc7359cfc4..a7bda15d3a 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 edae05f9db..c9d782cee9 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 cd1c546b80..71c0cd8271 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 86fe58a71c..b8696df736 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 2394574b3b..4502da698c 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 e4d8cc4950..9ced184491 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 f84dca1e5a..febd13c975 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 ee82625a03..3cdd97f067 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 9eb7ad691d..0e0783b4b2 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 f0a0993506..65017acba3 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 0cde9f55c0..15fc6aa690 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 3a4ac04923..d27bb46fcf 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 f33f788ed2..d0897221db 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 6a65f44da3..234a2afed7 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 1be7633ccd..14cf9230bc 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 f744fc6115..aead7776ac 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 6c6de4206c..a333926dd1 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 03e6dcf2c8..8254fb607a 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 47497794fb..5ac56c8428 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 0000000000..02f488f120 --- /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 488eabeec5..d1e3fddafe 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 bddfd2d27a..82d9f9ef63 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 ac495e1c91..425b86a6bb 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 46f42c4549..a61116fca4 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 8351c19397..b6a7d89c68 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14 +16 diff --git a/README.md b/README.md index 277ae0cc16..ddd5e2c648 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 dd93102ed6..9f0c836fda 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 e79f7a43e1..74764640b8 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 df5b1b3fcb..8b77e9717d 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 98cda50047..f432060d98 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 f12285b2a6..606def602f 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 9496b56f87..60b0cfb577 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 c132930a98..bd3b696329 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 51b256482a..5916b0b4b3 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 fad19f87fd..11b38ab92b 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 82d9f9ef63..a373d685e0 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 f16fe07f1d..13fd7fe03d 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 {