From 4a61bbe1760f38474aa97d4bc0a38f228abc1075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Feb 2022 08:00:26 +0900 Subject: [PATCH 01/30] Bump rspec-rails from 5.0.2 to 5.1.0 (#17406) Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 5.0.2 to 5.1.0. - [Release notes](https://github.com/rspec/rspec-rails/releases) - [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md) - [Commits](https://github.com/rspec/rspec-rails/compare/v5.0.2...v5.1.0) --- updated-dependencies: - dependency-name: rspec-rails dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 9b52c0e6c..1a7193b19 100644 --- a/Gemfile +++ b/Gemfile @@ -103,7 +103,7 @@ group :development, :test do gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-byebug', '~> 3.9' gem 'pry-rails', '~> 0.3' - gem 'rspec-rails', '~> 5.0' + gem 'rspec-rails', '~> 5.1' end group :production, :test do diff --git a/Gemfile.lock b/Gemfile.lock index eee289056..f0ba4efdc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -181,7 +181,7 @@ GEM devise_pam_authenticatable2 (9.2.0) devise (>= 4.0.0) rpam2 (~> 4.0) - diff-lcs (1.4.4) + diff-lcs (1.5.0) discard (1.2.1) activerecord (>= 4.2, < 8) docile (1.3.4) @@ -354,7 +354,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.13.0) + loofah (2.14.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -510,15 +510,15 @@ GEM chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + rspec-core (3.11.0) + rspec-support (~> 3.11.0) + rspec-expectations (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-support (~> 3.11.0) + rspec-mocks (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-rails (5.0.2) + rspec-support (~> 3.11.0) + rspec-rails (5.1.0) actionpack (>= 5.2) activesupport (>= 5.2) railties (>= 5.2) @@ -529,7 +529,7 @@ GEM rspec-sidekiq (3.1.0) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.10.3) + rspec-support (3.11.0) rspec_junit_formatter (0.5.1) rspec-core (>= 2, < 4, != 2.12.0) rubocop (1.25.1) @@ -770,7 +770,7 @@ DEPENDENCIES redis-namespace (~> 1.8) rexml (~> 3.2) rqrcode (~> 2.1) - rspec-rails (~> 5.0) + rspec-rails (~> 5.1) rspec-sidekiq (~> 3.1) rspec_junit_formatter (~> 0.5) rubocop (~> 1.25) From b87c8538069a2763e407b93397d32379f2d5acc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Feb 2022 08:04:32 +0900 Subject: [PATCH 02/30] Bump annotate from 3.1.1 to 3.2.0 (#17503) Bumps [annotate](https://github.com/ctran/annotate_models) from 3.1.1 to 3.2.0. - [Release notes](https://github.com/ctran/annotate_models/releases) - [Changelog](https://github.com/ctran/annotate_models/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ctran/annotate_models/compare/v3.1.1...v3.2.0) --- updated-dependencies: - dependency-name: annotate dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 1a7193b19..55ed06bb7 100644 --- a/Gemfile +++ b/Gemfile @@ -124,7 +124,7 @@ end group :development do gem 'active_record_query_trace', '~> 1.8' - gem 'annotate', '~> 3.1' + gem 'annotate', '~> 3.2' gem 'better_errors', '~> 2.9' gem 'binding_of_caller', '~> 1.0' gem 'bullet', '~> 7.0' diff --git a/Gemfile.lock b/Gemfile.lock index f0ba4efdc..84a28f904 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,8 +71,8 @@ GEM airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) android_key_attestation (0.3.0) - annotate (3.1.1) - activerecord (>= 3.2, < 7.0) + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.2) attr_encrypted (3.1.0) @@ -682,7 +682,7 @@ DEPENDENCIES active_model_serializers (~> 0.10) active_record_query_trace (~> 1.8) addressable (~> 2.8) - annotate (~> 3.1) + annotate (~> 3.2) aws-sdk-s3 (~> 1.112) better_errors (~> 2.9) binding_of_caller (~> 1.0) From f76dd51aa5dadc1799a1e6bd178247069d5acceb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Feb 2022 08:05:22 +0900 Subject: [PATCH 03/30] Bump strong_migrations from 0.7.9 to 0.8.0 (#17504) Bumps [strong_migrations](https://github.com/ankane/strong_migrations) from 0.7.9 to 0.8.0. - [Release notes](https://github.com/ankane/strong_migrations/releases) - [Changelog](https://github.com/ankane/strong_migrations/blob/master/CHANGELOG.md) - [Commits](https://github.com/ankane/strong_migrations/compare/v0.7.9...v0.8.0) --- updated-dependencies: - dependency-name: strong_migrations dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 55ed06bb7..47fa180ca 100644 --- a/Gemfile +++ b/Gemfile @@ -85,7 +85,7 @@ gem 'simple-navigation', '~> 4.3' gem 'simple_form', '~> 5.1' gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie' gem 'stoplight', '~> 2.2.1' -gem 'strong_migrations', '~> 0.7' +gem 'strong_migrations', '~> 0.8' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2021' diff --git a/Gemfile.lock b/Gemfile.lock index 84a28f904..0e62f0a09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -606,8 +606,8 @@ GEM stackprof (0.2.17) statsd-ruby (1.5.0) stoplight (2.2.1) - strong_migrations (0.7.9) - activerecord (>= 5) + strong_migrations (0.8.0) + activerecord (>= 5.2) temple (0.8.2) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -789,7 +789,7 @@ DEPENDENCIES sprockets-rails (~> 3.4) stackprof stoplight (~> 2.2.1) - strong_migrations (~> 0.7) + strong_migrations (~> 0.8) thor (~> 1.2) tty-prompt (~> 0.23) twitter-text (~> 3.1.0) From 62404668664e3349d87484f73b802f83efccbafa Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 13 Feb 2022 01:58:26 +0100 Subject: [PATCH 04/30] Fix duplicate accounts when searching by IP range in admin UI (#17524) --- app/models/account_filter.rb | 2 +- app/models/user.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index dcb174122..86b7f5f41 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -49,7 +49,7 @@ class AccountFilter when 'email' accounts_with_users.merge(User.matches_email(value)) when 'ip' - valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value)) : Account.none + valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group('users.id, accounts.id')) : Account.none when 'invited_by' invited_by_scope(value) when 'order' diff --git a/app/models/user.rb b/app/models/user.rb index a11bc3824..fd1d7049a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -111,7 +111,7 @@ class User < ApplicationRecord scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) } scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) } - scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value) } + scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') } scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) } before_validation :sanitize_languages From bbd34744161fc46fa0e75d64e08a2f70d951bb40 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 13 Feb 2022 02:52:34 +0100 Subject: [PATCH 05/30] Fix privacy policy link not being visible on small screens (#17533) Fix #17482 --- app/javascript/styles/mastodon/footer.scss | 17 ++++++++++++++++- app/views/layouts/public.html.haml | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss index 00d290883..073ebda7e 100644 --- a/app/javascript/styles/mastodon/footer.scss +++ b/app/javascript/styles/mastodon/footer.scss @@ -90,6 +90,20 @@ .column-4 { display: none; } + + .column-2 h4 { + display: none; + } + } + } + + .legal-xs { + display: none; + text-align: center; + padding-top: 20px; + + @media screen and (max-width: $no-gap-breakpoint) { + display: block; } } @@ -105,7 +119,8 @@ } } - ul a { + ul a, + .legal-xs a { text-decoration: none; color: lighten($ui-base-color, 34%); diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index 069931cfd..3a9ca7ed7 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -54,5 +54,9 @@ %ul %li= link_to t('about.source_code'), Mastodon::Version.source_url %li= link_to t('about.apps'), 'https://joinmastodon.org/apps' + .legal-xs + = link_to "v#{Mastodon::Version.to_s}", Mastodon::Version.source_url + · + = link_to t('about.privacy_policy'), terms_path = render template: 'layouts/application' From 901bbf2e5fd83a4aadbb6e28f305e30a952e1939 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Feb 2022 20:21:09 +0900 Subject: [PATCH 06/30] Bump kt-paperclip from 7.0.1 to 7.1.1 (#17502) Bumps [kt-paperclip](https://github.com/kreeti/kt-paperclip) from 7.0.1 to 7.1.1. - [Release notes](https://github.com/kreeti/kt-paperclip/releases) - [Changelog](https://github.com/kreeti/kt-paperclip/blob/master/NEWS) - [Commits](https://github.com/kreeti/kt-paperclip/compare/v7.0.1...v7.1.1) --- updated-dependencies: - dependency-name: kt-paperclip dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 47fa180ca..c5bf5c73e 100644 --- a/Gemfile +++ b/Gemfile @@ -21,7 +21,7 @@ gem 'dotenv-rails', '~> 2.7' gem 'aws-sdk-s3', '~> 1.112', require: false gem 'fog-core', '<= 2.1.0' gem 'fog-openstack', '~> 0.3', require: false -gem 'kt-paperclip', '~> 7.0' +gem 'kt-paperclip', '~> 7.1' gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' diff --git a/Gemfile.lock b/Gemfile.lock index 0e62f0a09..9314fc27e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -330,7 +330,7 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - kt-paperclip (7.0.1) + kt-paperclip (7.1.1) activemodel (>= 4.2.0) activesupport (>= 4.2.0) marcel (~> 1.0.1) @@ -361,7 +361,7 @@ GEM mini_mime (>= 0.1.1) makara (0.5.1) activerecord (>= 5.2.0) - marcel (1.0.1) + marcel (1.0.2) mario-redis-lock (1.2.1) redis (>= 3.0.5) matrix (0.4.2) @@ -372,7 +372,7 @@ GEM nokogiri (~> 1.10) mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.1115) + mime-types-data (3.2022.0105) mini_mime (1.1.2) mini_portile2 (2.7.1) minitest (5.15.0) @@ -728,7 +728,7 @@ DEPENDENCIES json-ld json-ld-preloaded (~> 3.2) kaminari (~> 1.2) - kt-paperclip (~> 7.0) + kt-paperclip (~> 7.1) letter_opener (~> 1.7) letter_opener_web (~> 2.0) link_header (~> 0.0) From 9a015e43ef173a3afc93cc1d06fd7f76adb8876a Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Mon, 14 Feb 2022 08:17:09 +0900 Subject: [PATCH 07/30] Add `from:` query operator to search syntax (#16526) * Add 'by:userhandle' parameter to search api * Use search syntax for "by" prefix * Codeclimate * Use 'from' instead of 'by' --- app/lib/search_query_transformer.rb | 37 +++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index e07ebfffe..c685d7b6f 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -2,19 +2,21 @@ class SearchQueryTransformer < Parslet::Transform class Query - attr_reader :should_clauses, :must_not_clauses, :must_clauses + attr_reader :should_clauses, :must_not_clauses, :must_clauses, :filter_clauses def initialize(clauses) grouped = clauses.chunk(&:operator).to_h @should_clauses = grouped.fetch(:should, []) @must_not_clauses = grouped.fetch(:must_not, []) @must_clauses = grouped.fetch(:must, []) + @filter_clauses = grouped.fetch(:filter, []) end def apply(search) should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) } must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) } must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) } + filter_clauses.each { |clause| search = search.filter(**clause_to_filter(clause)) } search.query.minimum_should_match(1) end @@ -30,6 +32,15 @@ class SearchQueryTransformer < Parslet::Transform raise "Unexpected clause type: #{clause}" end end + + def clause_to_filter(clause) + case clause + when PrefixClause + { term: { clause.filter => clause.term } } + else + raise "Unexpected clause type: #{clause}" + end + end end class Operator @@ -69,11 +80,33 @@ class SearchQueryTransformer < Parslet::Transform end end + class PrefixClause + attr_reader :filter, :operator, :term + + def initialize(prefix, term) + @operator = :filter + case prefix + when 'from' + @filter = :account_id + username, domain = term.split('@') + account = Account.find_remote(username, domain) + + raise "Account not found: #{term}" unless account + + @term = account.id + else + raise "Unknown prefix: #{prefix}" + end + end + end + rule(clause: subtree(:clause)) do prefix = clause[:prefix][:term].to_s if clause[:prefix] operator = clause[:operator]&.to_s - if clause[:term] + if clause[:prefix] + PrefixClause.new(prefix, clause[:term].to_s) + elsif clause[:term] TermClause.new(prefix, operator, clause[:term].to_s) elsif clause[:shortcode] TermClause.new(prefix, operator, ":#{clause[:term]}:") From d39df35441d7e4b39a57402e554ed92b1ec2e1f1 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 15 Feb 2022 00:07:04 +0900 Subject: [PATCH 08/30] Fix admin statuses page order with media (#17538) --- app/models/admin/status_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb index ce5bb5f46..4fba612a6 100644 --- a/app/models/admin/status_filter.rb +++ b/app/models/admin/status_filter.rb @@ -31,7 +31,7 @@ class Admin::StatusFilter def scope_for(key, value) case key.to_s when 'media' - Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id) + Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id).reorder('statuses.id desc') when 'id' Status.where(id: value) else From 298491a8163fe2a015ac7b34b66b606a06e9798c Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 15 Feb 2022 00:08:02 +0900 Subject: [PATCH 09/30] Remove protobuf dependencies (#17539) --- .circleci/config.yml | 2 +- .github/workflows/build-image.yml | 6 +++++- .github/workflows/check-i18n.yml | 2 +- Aptfile | 2 -- Dockerfile | 4 ++-- Vagrantfile | 2 -- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 751ca95b1..a9ad92145 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,7 @@ commands: name: Install system dependencies command: | sudo apt-get update - sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler + sudo apt-get install -y libicu-dev libidn11-dev install-ruby-dependencies: parameters: ruby-version: diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 0aaea6b1f..92a164c40 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -6,6 +6,10 @@ on: - "main" tags: - "*" + pull_request: + paths: + - .github/workflows/build-image.yml + - Dockerfile jobs: build-image: runs-on: ubuntu-latest @@ -30,7 +34,7 @@ jobs: with: context: . platforms: linux/amd64,linux/arm64 - push: true + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} cache-from: type=registry,ref=tootsuite/mastodon:latest cache-to: type=inline diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index 2e8f230f3..9cb98dd12 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -18,7 +18,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler + sudo apt-get install -y libicu-dev libidn11-dev - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/Aptfile b/Aptfile index b2cbad714..9235141ad 100644 --- a/Aptfile +++ b/Aptfile @@ -4,10 +4,8 @@ libicu-dev libidn11 libidn11-dev libpq-dev -libprotobuf-dev libxdamage1 libxfixes3 -protobuf-compiler zlib1g-dev libcairo2 libcroco3 diff --git a/Dockerfile b/Dockerfile index c6287b5a7..1b3661561 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,7 +51,7 @@ RUN npm install -g npm@latest && \ gem install bundler && \ apt-get update && \ apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \ - libpq-dev libprotobuf-dev protobuf-compiler shared-mime-info + libpq-dev shared-mime-info COPY Gemfile* package.json yarn.lock /opt/mastodon/ @@ -88,7 +88,7 @@ RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selectio RUN apt-get update && \ apt-get -y --no-install-recommends install \ libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \ - libicu66 libprotobuf17 libidn11 libyaml-0-2 \ + libicu66 libidn11 libyaml-0-2 \ file ca-certificates tzdata libreadline8 gcc tini apt-utils && \ ln -s /opt/mastodon /mastodon && \ gem install bundler && \ diff --git a/Vagrantfile b/Vagrantfile index e086ddd98..3e73d9e47 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -33,11 +33,9 @@ sudo apt-get install \ redis-tools \ postgresql \ postgresql-contrib \ - protobuf-compiler \ yarn \ libicu-dev \ libidn11-dev \ - libprotobuf-dev \ libreadline-dev \ libpam0g-dev \ -y From 5be705e1e0e3c05486c6069a7c8387c123a6d405 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 14 Feb 2022 16:08:23 +0100 Subject: [PATCH 10/30] Revert "Bump strong_migrations from 0.7.9 to 0.8.0 (#17504)" (#17540) This reverts commit f76dd51aa5dadc1799a1e6bd178247069d5acceb. --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index c5bf5c73e..ee5395bfc 100644 --- a/Gemfile +++ b/Gemfile @@ -85,7 +85,7 @@ gem 'simple-navigation', '~> 4.3' gem 'simple_form', '~> 5.1' gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie' gem 'stoplight', '~> 2.2.1' -gem 'strong_migrations', '~> 0.8' +gem 'strong_migrations', '~> 0.7' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2021' diff --git a/Gemfile.lock b/Gemfile.lock index 9314fc27e..71e8cf4cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -606,8 +606,8 @@ GEM stackprof (0.2.17) statsd-ruby (1.5.0) stoplight (2.2.1) - strong_migrations (0.8.0) - activerecord (>= 5.2) + strong_migrations (0.7.9) + activerecord (>= 5) temple (0.8.2) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -789,7 +789,7 @@ DEPENDENCIES sprockets-rails (~> 3.4) stackprof stoplight (~> 2.2.1) - strong_migrations (~> 0.8) + strong_migrations (~> 0.7) thor (~> 1.2) tty-prompt (~> 0.23) twitter-text (~> 3.1.0) From 564efd06515edc524a8a1cdf7a3d8a7d9a376c04 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 14 Feb 2022 21:27:53 +0100 Subject: [PATCH 11/30] Add appeals (#17364) * Add appeals * Add ability to reject appeals and ability to browse pending appeals in admin UI * Add strikes to account page in settings * Various fixes and improvements - Add separate notification setting for appeals, separate from reports - Fix style of links in report/strike header - Change approving an appeal to not restore statuses (due to federation complexities) - Change style of successfully appealed strikes on account settings page - Change account settings page to only show unappealed or recently appealed strikes * Change appealed_at to overruled_at * Fix missing method error --- app/controllers/admin/accounts_controller.rb | 4 +- app/controllers/admin/dashboard_controller.rb | 1 + .../admin/disputes/appeals_controller.rb | 40 ++++++ .../auth/registrations_controller.rb | 11 +- .../disputes/appeals_controller.rb | 25 ++++ app/controllers/disputes/base_controller.rb | 18 +++ .../disputes/strikes_controller.rb | 17 +++ .../admin/account_moderation_notes_helper.rb | 4 +- app/helpers/admin/action_logs_helper.rb | 2 + app/helpers/admin/trends/statuses_helper.rb | 20 +++ app/javascript/styles/mastodon/admin.scss | 89 +++++++++++- app/mailers/admin_mailer.rb | 10 ++ app/mailers/user_mailer.rb | 20 +++ app/models/account.rb | 4 + app/models/account_filter.rb | 2 + app/models/account_warning.rb | 8 +- app/models/admin/action_log_filter.rb | 2 + app/models/admin/appeal_filter.rb | 49 +++++++ app/models/appeal.rb | 58 ++++++++ app/models/user.rb | 4 + app/policies/account_warning_policy.rb | 17 +++ app/policies/appeal_policy.rb | 13 ++ app/services/appeal_service.rb | 28 ++++ app/services/approve_appeal_service.rb | 74 ++++++++++ .../_account_moderation_note.html.haml | 7 - .../_account_warning.html.haml | 30 ++++- app/views/admin/accounts/show.html.haml | 21 ++- app/views/admin/dashboard/index.html.haml | 3 + .../admin/disputes/appeals/_appeal.html.haml | 21 +++ .../admin/disputes/appeals/index.html.haml | 22 +++ .../admin/report_notes/_report_note.html.haml | 2 +- app/views/admin/reports/show.html.haml | 2 +- app/views/admin_mailer/new_appeal.text.erb | 9 ++ .../registrations/_account_warning.html.haml | 20 +++ .../auth/registrations/_status.html.haml | 31 ++--- app/views/disputes/strikes/show.html.haml | 127 ++++++++++++++++++ .../preferences/notifications/show.html.haml | 1 + app/views/statuses/_detailed_status.html.haml | 2 +- .../user_mailer/appeal_approved.html.haml | 59 ++++++++ .../user_mailer/appeal_approved.text.erb | 7 + .../user_mailer/appeal_rejected.html.haml | 59 ++++++++ .../user_mailer/appeal_rejected.text.erb | 7 + app/views/user_mailer/warning.html.haml | 6 +- config/brakeman.ignore | 64 +++++---- config/locales/en.yml | 77 ++++++++++- config/locales/simple_form.en.yml | 9 +- config/navigation.rb | 4 +- config/routes.rb | 15 +++ config/settings.yml | 1 + db/migrate/20220124141035_create_appeals.rb | 14 ++ ...19_add_overruled_at_to_account_warnings.rb | 5 + db/schema.rb | 23 +++- .../admin/disputes/appeals_controller_spec.rb | 53 ++++++++ .../disputes/appeals_controller_spec.rb | 27 ++++ .../disputes/strikes_controller_spec.rb | 30 +++++ .../fabricators/account_warning_fabricator.rb | 7 +- spec/fabricators/appeal_fabricator.rb | 5 + spec/mailers/previews/admin_mailer_preview.rb | 5 + spec/mailers/previews/user_mailer_preview.rb | 5 + spec/models/appeal_spec.rb | 5 + 60 files changed, 1212 insertions(+), 93 deletions(-) create mode 100644 app/controllers/admin/disputes/appeals_controller.rb create mode 100644 app/controllers/disputes/appeals_controller.rb create mode 100644 app/controllers/disputes/base_controller.rb create mode 100644 app/controllers/disputes/strikes_controller.rb create mode 100644 app/helpers/admin/trends/statuses_helper.rb create mode 100644 app/models/admin/appeal_filter.rb create mode 100644 app/models/appeal.rb create mode 100644 app/policies/account_warning_policy.rb create mode 100644 app/policies/appeal_policy.rb create mode 100644 app/services/appeal_service.rb create mode 100644 app/services/approve_appeal_service.rb delete mode 100644 app/views/admin/account_moderation_notes/_account_moderation_note.html.haml create mode 100644 app/views/admin/disputes/appeals/_appeal.html.haml create mode 100644 app/views/admin/disputes/appeals/index.html.haml create mode 100644 app/views/admin_mailer/new_appeal.text.erb create mode 100644 app/views/auth/registrations/_account_warning.html.haml create mode 100644 app/views/disputes/strikes/show.html.haml create mode 100644 app/views/user_mailer/appeal_approved.html.haml create mode 100644 app/views/user_mailer/appeal_approved.text.erb create mode 100644 app/views/user_mailer/appeal_rejected.html.haml create mode 100644 app/views/user_mailer/appeal_rejected.text.erb create mode 100644 db/migrate/20220124141035_create_appeals.rb create mode 100644 db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb create mode 100644 spec/controllers/admin/disputes/appeals_controller_spec.rb create mode 100644 spec/controllers/disputes/appeals_controller_spec.rb create mode 100644 spec/controllers/disputes/strikes_controller_spec.rb create mode 100644 spec/fabricators/appeal_fabricator.rb create mode 100644 spec/models/appeal_spec.rb diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index e7f56e243..e0ae71b9f 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -28,7 +28,7 @@ module Admin @deletion_request = @account.deletion_request @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) @moderation_notes = @account.targeted_moderation_notes.latest - @warnings = @account.strikes.custom.latest + @warnings = @account.strikes.includes(:target_account, :account, :appeal).latest @domain_block = DomainBlock.rule_for(@account.domain) end @@ -146,7 +146,7 @@ module Admin end def filter_params - params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS) + params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS) end def form_account_batch_params diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index f0a935411..e376baab2 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -8,6 +8,7 @@ module Admin @pending_users_count = User.pending.count @pending_reports_count = Report.unresolved.count @pending_tags_count = Tag.pending_review.count + @pending_appeals_count = Appeal.pending.count end private diff --git a/app/controllers/admin/disputes/appeals_controller.rb b/app/controllers/admin/disputes/appeals_controller.rb new file mode 100644 index 000000000..32e5e2f6f --- /dev/null +++ b/app/controllers/admin/disputes/appeals_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class Admin::Disputes::AppealsController < Admin::BaseController + before_action :set_appeal, except: :index + + def index + authorize :appeal, :index? + + @appeals = filtered_appeals.page(params[:page]) + end + + def approve + authorize @appeal, :approve? + log_action :approve, @appeal + ApproveAppealService.new.call(@appeal, current_account) + redirect_to disputes_strike_path(@appeal.strike) + end + + def reject + authorize @appeal, :approve? + log_action :reject, @appeal + @appeal.reject!(current_account) + UserMailer.appeal_rejected(@appeal.account.user, @appeal) + redirect_to disputes_strike_path(@appeal.strike) + end + + private + + def filtered_appeals + Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account) + end + + def filter_params + params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS) + end + + def set_appeal + @appeal = Appeal.find(params[:id]) + end +end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index f37e906fd..3b025838b 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -9,6 +9,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :check_enabled_registrations, only: [:new, :create] before_action :configure_sign_up_params, only: [:create] before_action :set_sessions, only: [:edit, :update] + before_action :set_strikes, only: [:edit, :update] before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :require_not_suspended!, only: [:update] @@ -111,8 +112,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def set_invite - invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil - @invite = invite&.valid_for_use? ? invite : nil + @invite = begin + invite = Invite.find_by(code: invite_code) if invite_code.present? + invite if invite&.valid_for_use? + end end def determine_layout @@ -123,6 +126,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController @sessions = current_user.session_activations end + def set_strikes + @strikes = current_account.strikes.active.latest + end + def require_not_suspended! forbidden if current_account.suspended? end diff --git a/app/controllers/disputes/appeals_controller.rb b/app/controllers/disputes/appeals_controller.rb new file mode 100644 index 000000000..15367c879 --- /dev/null +++ b/app/controllers/disputes/appeals_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Disputes::AppealsController < Disputes::BaseController + before_action :set_strike + + def create + authorize @strike, :appeal? + + @appeal = AppealService.new.call(@strike, appeal_params[:text]) + + redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg') + rescue ActiveRecord::RecordInvalid + render template: 'disputes/strikes/show' + end + + private + + def set_strike + @strike = current_account.strikes.find(params[:strike_id]) + end + + def appeal_params + params.require(:appeal).permit(:text) + end +end diff --git a/app/controllers/disputes/base_controller.rb b/app/controllers/disputes/base_controller.rb new file mode 100644 index 000000000..865146b5c --- /dev/null +++ b/app/controllers/disputes/base_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Disputes::BaseController < ApplicationController + include Authorization + + layout 'admin' + + skip_before_action :require_functional! + + before_action :set_body_classes + before_action :authenticate_user! + + private + + def set_body_classes + @body_classes = 'admin' + end +end diff --git a/app/controllers/disputes/strikes_controller.rb b/app/controllers/disputes/strikes_controller.rb new file mode 100644 index 000000000..d41c5c727 --- /dev/null +++ b/app/controllers/disputes/strikes_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Disputes::StrikesController < Disputes::BaseController + before_action :set_strike + + def show + authorize @strike, :show? + + @appeal = @strike.appeal || @strike.build_appeal + end + + private + + def set_strike + @strike = AccountWarning.find(params[:id]) + end +end diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb index 40b2a5289..2f08538ca 100644 --- a/app/helpers/admin/account_moderation_notes_helper.rb +++ b/app/helpers/admin/account_moderation_notes_helper.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Admin::AccountModerationNotesHelper - def admin_account_link_to(account) + def admin_account_link_to(account, path: nil) return if account.nil? - link_to admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do + link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do safe_join([ image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username'), diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index f3aa4be4f..47eeeaac3 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -33,6 +33,8 @@ module Admin::ActionLogsHelper "#{record.ip}/#{record.ip.prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{record.severity}")})" when 'Instance' record.domain + when 'Appeal' + link_to record.account.acct, disputes_strike_path(record.strike) end end diff --git a/app/helpers/admin/trends/statuses_helper.rb b/app/helpers/admin/trends/statuses_helper.rb new file mode 100644 index 000000000..d16e3dd12 --- /dev/null +++ b/app/helpers/admin/trends/statuses_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Admin::Trends::StatusesHelper + def one_line_preview(status) + text = begin + if status.local? + status.text.split("\n").first + else + Nokogiri::HTML(status.text).css('html > body > *').first&.text + end + end + + return '' if text.blank? + + html = Formatter.instance.send(:encode, text) + html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?) + + html.html_safe # rubocop:disable Rails/OutputSafety + end +end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 546de1640..f5741bd50 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -578,12 +578,16 @@ body, } .log-entry { + display: block; line-height: 20px; padding: 15px; padding-left: 15px * 2 + 40px; background: $ui-base-color; border-bottom: 1px solid darken($ui-base-color, 8%); position: relative; + text-decoration: none; + color: $darker-text-color; + font-size: 14px; &:first-child { border-top-left-radius: 4px; @@ -596,15 +600,12 @@ body, border-bottom: 0; } - &:hover { + &:hover, + &:focus, + &:active { background: lighten($ui-base-color, 4%); } - &__header { - color: $darker-text-color; - font-size: 14px; - } - &__avatar { position: absolute; left: 15px; @@ -640,6 +641,18 @@ body, text-decoration: underline; } } + + &--inactive { + .log-entry__title { + text-decoration: line-through; + } + + a, + .username, + .target { + color: $darker-text-color; + } + } } a.name-tag, @@ -1175,6 +1188,17 @@ a.sparkline { font-weight: 600; padding: 4px 0; } + + a { + color: $ui-highlight-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } } &--horizontal { @@ -1451,3 +1475,56 @@ a.sparkline { } } } + +.strike-card { + padding: 15px; + border-radius: 4px; + background: $ui-base-color; + font-size: 15px; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + color: $primary-text-color; + + p { + margin-bottom: 20px; + unicode-bidi: plaintext; + + &:last-child { + margin-bottom: 0; + } + } + + &__statuses-list { + border-radius: 4px; + border: 1px solid darken($ui-base-color, 8%); + font-size: 13px; + line-height: 18px; + overflow: hidden; + + &__item { + padding: 16px; + background: lighten($ui-base-color, 2%); + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &__meta { + color: $darker-text-color; + } + + a { + color: inherit; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + } + } +} diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index b23bd1296..a9d00c000 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -15,6 +15,16 @@ class AdminMailer < ApplicationMailer end end + def new_appeal(recipient, appeal) + @appeal = appeal + @me = recipient + @instance = Rails.configuration.x.local_domain + + locale_for_account(@me) do + mail to: @me.user_email, subject: I18n.t('admin_mailer.new_appeal.subject', instance: @instance, username: @appeal.account.username) + end + end + def new_pending_account(recipient, user) @account = user.account @me = recipient diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 5221a4892..583c948b0 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -173,6 +173,26 @@ class UserMailer < Devise::Mailer end end + def appeal_approved(user, appeal) + @resource = user + @instance = Rails.configuration.x.local_domain + @appeal = appeal + + I18n.with_locale(@resource.locale || I18n.default_locale) do + mail to: @resource.email, subject: I18n.t('user_mailer.appeal_approved.subject', date: l(@appeal.created_at)) + end + end + + def appeal_rejected(user, appeal) + @resource = user + @instance = Rails.configuration.x.local_domain + @appeal = appeal + + I18n.with_locale(@resource.locale || I18n.default_locale) do + mail to: @resource.email, subject: I18n.t('user_mailer.appeal_rejected.subject', date: l(@appeal.created_at)) + end + end + def sign_in_token(user, remote_ip, user_agent, timestamp) @resource = user @instance = Rails.configuration.x.local_domain diff --git a/app/models/account.rb b/app/models/account.rb index 771cc0b1b..2ad45feda 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -270,6 +270,10 @@ class Account < ApplicationRecord true end + def previous_strikes_count + strikes.where(overruled_at: nil).count + end + def keypair @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key) end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 86b7f5f41..9da1522dd 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -24,6 +24,8 @@ class AccountFilter scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil) params.each do |key, value| + next if key.to_s == 'page' + scope.merge!(scope_for(key, value.to_s.strip)) if value.present? end diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb index fc0d988fd..05d01942d 100644 --- a/app/models/account_warning.rb +++ b/app/models/account_warning.rb @@ -12,6 +12,7 @@ # updated_at :datetime not null # report_id :bigint(8) # status_ids :string is an Array +# overruled_at :datetime # class AccountWarning < ApplicationRecord @@ -28,12 +29,17 @@ class AccountWarning < ApplicationRecord belongs_to :target_account, class_name: 'Account', inverse_of: :strikes belongs_to :report, optional: true - has_one :appeal, dependent: :destroy + has_one :appeal, dependent: :destroy, inverse_of: :strike scope :latest, -> { order(id: :desc) } scope :custom, -> { where.not(text: '') } + scope :active, -> { where(overruled_at: nil).or(where('account_warnings.overruled_at >= ?', 30.days.ago)) } def statuses Status.with_discarded.where(id: status_ids || []) end + + def overruled? + overruled_at.present? + end end diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index 12136223b..0f2f712a2 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -8,6 +8,8 @@ class Admin::ActionLogFilter ).freeze ACTION_TYPE_MAP = { + approve_appeal: { target_type: 'Appeal', action: 'approve' }.freeze, + reject_appeal: { target_type: 'Appeal', action: 'reject' }.freeze, assigned_to_self_report: { target_type: 'Report', action: 'assigned_to_self' }.freeze, change_email_user: { target_type: 'User', action: 'change_email' }.freeze, confirm_user: { target_type: 'User', action: 'confirm' }.freeze, diff --git a/app/models/admin/appeal_filter.rb b/app/models/admin/appeal_filter.rb new file mode 100644 index 000000000..b163d2e56 --- /dev/null +++ b/app/models/admin/appeal_filter.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class Admin::AppealFilter + KEYS = %i( + status + ).freeze + + attr_reader :params + + def initialize(params) + @params = params + end + + def results + scope = Appeal.order(id: :desc) + + params.each do |key, value| + next if %w(page).include?(key.to_s) + + scope.merge!(scope_for(key, value.to_s.strip)) if value.present? + end + + scope + end + + private + + def scope_for(key, value) + case key.to_s + when 'status' + status_scope(value) + else + raise "Unknown filter: #{key}" + end + end + + def status_scope(value) + case value + when 'approved' + Appeal.approved + when 'rejected' + Appeal.rejected + when 'pending' + Appeal.pending + else + raise "Unknown status: #{value}" + end + end +end diff --git a/app/models/appeal.rb b/app/models/appeal.rb new file mode 100644 index 000000000..46f35ae37 --- /dev/null +++ b/app/models/appeal.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: appeals +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# account_warning_id :bigint(8) not null +# text :text default(""), not null +# approved_at :datetime +# approved_by_account_id :bigint(8) +# rejected_at :datetime +# rejected_by_account_id :bigint(8) +# created_at :datetime not null +# updated_at :datetime not null +# +class Appeal < ApplicationRecord + belongs_to :account + belongs_to :strike, class_name: 'AccountWarning', foreign_key: 'account_warning_id' + belongs_to :approved_by_account, class_name: 'Account', optional: true + belongs_to :rejected_by_account, class_name: 'Account', optional: true + + validates :text, presence: true, length: { maximum: 2_000 } + validates :account_warning_id, uniqueness: true + + validate :validate_time_frame, on: :create + + scope :approved, -> { where.not(approved_at: nil) } + scope :rejected, -> { where.not(rejected_at: nil) } + scope :pending, -> { where(approved_at: nil, rejected_at: nil) } + + def pending? + !approved? && !rejected? + end + + def approved? + approved_at.present? + end + + def rejected? + rejected_at.present? + end + + def approve!(current_account) + update!(approved_at: Time.now.utc, approved_by_account: current_account) + end + + def reject!(current_account) + update!(rejected_at: Time.now.utc, rejected_by_account: current_account) + end + + private + + def validate_time_frame + errors.add(:base, I18n.t('strikes.errors.too_late')) if Time.now.utc > (strike.created_at + 20.days) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index fd1d7049a..517254a91 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -265,6 +265,10 @@ class User < ApplicationRecord settings.notification_emails['pending_account'] end + def allows_appeal_emails? + settings.notification_emails['appeal'] + end + def allows_trending_tag_emails? settings.notification_emails['trending_tag'] end diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb new file mode 100644 index 000000000..6b92da475 --- /dev/null +++ b/app/policies/account_warning_policy.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AccountWarningPolicy < ApplicationPolicy + def show? + target? || staff? + end + + def appeal? + target? + end + + private + + def target? + record.target_account_id == current_account&.id + end +end diff --git a/app/policies/appeal_policy.rb b/app/policies/appeal_policy.rb new file mode 100644 index 000000000..a25187172 --- /dev/null +++ b/app/policies/appeal_policy.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AppealPolicy < ApplicationPolicy + def index? + staff? + end + + def approve? + record.pending? && staff? + end + + alias reject? approve? +end diff --git a/app/services/appeal_service.rb b/app/services/appeal_service.rb new file mode 100644 index 000000000..1397c50f5 --- /dev/null +++ b/app/services/appeal_service.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class AppealService < BaseService + def call(strike, text) + @strike = strike + @text = text + + create_appeal! + notify_staff! + + @appeal + end + + private + + def create_appeal! + @appeal = @strike.create_appeal!( + text: @text, + account: @strike.target_account + ) + end + + def notify_staff! + User.staff.includes(:account).each do |u| + AdminMailer.new_appeal(u.account, @appeal).deliver_later if u.allows_appeal_emails? + end + end +end diff --git a/app/services/approve_appeal_service.rb b/app/services/approve_appeal_service.rb new file mode 100644 index 000000000..f76bf8943 --- /dev/null +++ b/app/services/approve_appeal_service.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +class ApproveAppealService < BaseService + def call(appeal, current_account) + @appeal = appeal + @strike = appeal.strike + @current_account = current_account + + ApplicationRecord.transaction do + undo_strike_action! + mark_strike_as_appealed! + end + + queue_workers! + notify_target_account! + end + + private + + def target_account + @strike.target_account + end + + def undo_strike_action! + case @strike.action + when 'disable' + undo_disable! + when 'delete_statuses' + undo_delete_statuses! + when 'sensitive' + undo_sensitive! + when 'silence' + undo_silence! + when 'suspend' + undo_suspend! + end + end + + def mark_strike_as_appealed! + @appeal.approve!(@current_account) + @strike.touch(:overruled_at) + end + + def undo_disable! + target_account.user.enable! + end + + def undo_delete_statuses! + # Cannot be undone + end + + def undo_sensitive! + target_account.unsensitize! + end + + def undo_silence! + target_account.unsilence! + end + + def undo_suspend! + target_account.unsuspend! + end + + def queue_workers! + case @strike.action + when 'suspend' + Admin::UnsuspensionWorker.perform_async(target_account.id) + end + end + + def notify_target_account! + UserMailer.appeal_approved(target_account.user, @appeal).deliver_later + end +end diff --git a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml deleted file mode 100644 index 432fb79a6..000000000 --- a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.speech-bubble - .speech-bubble__bubble - = simple_format(h(account_moderation_note.content)) - .speech-bubble__owner - = admin_account_link_to account_moderation_note.account - %time.formatted{ datetime: account_moderation_note.created_at.iso8601 }= l account_moderation_note.created_at - = table_link_to 'trash', t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note) diff --git a/app/views/admin/account_warnings/_account_warning.html.haml b/app/views/admin/account_warnings/_account_warning.html.haml index 8c9c9679c..ef23c3b77 100644 --- a/app/views/admin/account_warnings/_account_warning.html.haml +++ b/app/views/admin/account_warnings/_account_warning.html.haml @@ -1,6 +1,24 @@ -.speech-bubble.warning - .speech-bubble__bubble - = Formatter.instance.linkify(account_warning.text) - .speech-bubble__owner - = admin_account_link_to account_warning.account - %time.formatted{ datetime: account_warning.created_at.iso8601 }= l account_warning.created_at += link_to disputes_strike_path(account_warning), class: ['log-entry', account_warning.overruled? && 'log-entry--inactive'] do + .log-entry__header + .log-entry__avatar + = image_tag account_warning.target_account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' + .log-entry__content + .log-entry__title + = t(account_warning.action, scope: 'admin.strikes.actions', name: content_tag(:span, account_warning.account.username, class: 'username'), target: content_tag(:span, account_warning.target_account.acct, class: 'target')).html_safe + .log-entry__timestamp + %time.formatted{ datetime: account_warning.created_at.iso8601 } + = l(account_warning.created_at) + + - if account_warning.report_id.present? + · + = t('admin.reports.title', id: account_warning.report_id) + + - if account_warning.overruled? + · + %span.positive-hint= t('admin.strikes.appeal_approved') + - elsif account_warning.appeal&.pending? + · + %span.warning-hint= t('admin.strikes.appeal_pending') + - elsif account_warning.appeal&.rejected? + · + %span.negative-hint= t('admin.strikes.appeal_rejected') diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index f3853d629..9a1f07a06 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -246,18 +246,29 @@ %hr.spacer/ - unless @warnings.empty? - = render @warnings + + %h3= t 'admin.accounts.previous_strikes' + + %p= t('admin.accounts.previous_strikes_description_html', count: @account.previous_strikes_count) + + .account-strikes + = render @warnings %hr.spacer/ - = render @moderation_notes + %h3= t 'admin.reports.notes.title' + + %p= t 'admin.reports.notes_description_html' + + .report-notes + = render partial: 'admin/report_notes/report_note', collection: @moderation_notes = simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f| - = render 'shared/error_messages', object: @account_moderation_note - - = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6 = f.hidden_field :target_account_id + .field-group + = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6 + .actions = f.button :button, t('admin.account_moderation_notes.create'), type: :submit diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 4b581f5ea..59b75e0e1 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -46,6 +46,9 @@ %span= t('admin.dashboard.pending_tags_html', count: @pending_tags_count) = fa_icon 'chevron-right fw' + = link_to admin_disputes_appeals_path(status: 'pending'), class: 'dashboard__quick-access' do + %span= t('admin.dashboard.pending_appeals_html', count: @pending_appeals_count) + = fa_icon 'chevron-right fw' .dashboard__item = react_admin_component :dimension, dimension: 'sources', start_at: @time_period.first, end_at: @time_period.last, limit: 8, label: t('admin.dashboard.sources') diff --git a/app/views/admin/disputes/appeals/_appeal.html.haml b/app/views/admin/disputes/appeals/_appeal.html.haml new file mode 100644 index 000000000..02b8777e1 --- /dev/null +++ b/app/views/admin/disputes/appeals/_appeal.html.haml @@ -0,0 +1,21 @@ += link_to disputes_strike_path(appeal.strike), class: ['log-entry', appeal.approved? && 'log-entry--inactive'] do + .log-entry__header + .log-entry__avatar + = image_tag appeal.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' + .log-entry__content + .log-entry__title + = t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.acct, class: 'target')).html_safe + .log-entry__timestamp + %time.formatted{ datetime: appeal.strike.created_at.iso8601 } + = l(appeal.strike.created_at) + + - if appeal.strike.report_id.present? + · + = t('admin.reports.title', id: appeal.strike.report_id) + · + - if appeal.approved? + %span.positive-hint= t('admin.strikes.appeal_approved') + - elsif appeal.rejected? + %span.negative-hint= t('admin.strikes.appeal_rejected') + - else + %span.warning-hint= t('admin.strikes.appeal_pending') diff --git a/app/views/admin/disputes/appeals/index.html.haml b/app/views/admin/disputes/appeals/index.html.haml new file mode 100644 index 000000000..dd6a6f403 --- /dev/null +++ b/app/views/admin/disputes/appeals/index.html.haml @@ -0,0 +1,22 @@ +- content_for :page_title do + = t('admin.disputes.appeals.title') + +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +.filters + .filter-subset + %strong= t('admin.tags.review') + %ul + %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Appeal.pending.count})"], ' '), status: 'pending' + %li= filter_link_to t('admin.trends.approved'), status: 'approved' + %li= filter_link_to t('admin.trends.rejected'), status: 'rejected' + +- if @appeals.empty? + %div.muted-hint.center-text + = t 'admin.disputes.appeals.empty' +- else + .announcements-list + = render partial: 'appeal', collection: @appeals + += paginate @appeals diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml index 428b6cf59..f9d57c2ae 100644 --- a/app/views/admin/report_notes/_report_note.html.haml +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -3,7 +3,7 @@ .report-notes__item__header %span.username - = link_to display_name(report_note.account), admin_account_path(report_note.account_id) + = 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)) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 02c46e384..e53c180e5 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -57,7 +57,7 @@ .report-header__details__item__header %strong= t('admin.accounts.strikes') .report-header__details__item__content - = @report.target_account.strikes.count + = @report.target_account.previous_strikes_count .report-header__details .report-header__details__item diff --git a/app/views/admin_mailer/new_appeal.text.erb b/app/views/admin_mailer/new_appeal.text.erb new file mode 100644 index 000000000..db4529eb7 --- /dev/null +++ b/app/views/admin_mailer/new_appeal.text.erb @@ -0,0 +1,9 @@ +<%= raw t('application_mailer.salutation', name: display_name(@me)) %> + +<%= raw t('admin_mailer.new_appeal.body', target: @appeal.account.username, action_taken_by: @appeal.strike.account.username, date: l(@appeal.strike.created_at), type: t(@appeal.strike.action, scope: 'admin_mailer.new_appeal.actions')) %> + +> <%= raw word_wrap(@appeal.text, break_sequence: "\n> ") %> + +<%= raw t('admin_mailer.new_appeal.next_steps') %> + +<%= raw t('application_mailer.view')%> <%= disputes_strike_url(@appeal.strike) %> diff --git a/app/views/auth/registrations/_account_warning.html.haml b/app/views/auth/registrations/_account_warning.html.haml new file mode 100644 index 000000000..40e7e1296 --- /dev/null +++ b/app/views/auth/registrations/_account_warning.html.haml @@ -0,0 +1,20 @@ += link_to disputes_strike_path(account_warning), class: 'log-entry' do + .log-entry__header + .log-entry__avatar + .indicator-icon{ class: account_warning.overruled? ? 'success' : 'failure' } + = fa_icon 'warning' + .log-entry__content + .log-entry__title + = t('disputes.strikes.title', action: t(account_warning.action, scope: 'disputes.strikes.title_actions'), date: l(account_warning.created_at.to_date)) + .log-entry__timestamp + %time.formatted{ datetime: account_warning.created_at.iso8601 }= l(account_warning.created_at) + + - if account_warning.overruled? + · + %span.positive-hint= t('disputes.strikes.your_appeal_approved') + - elsif account_warning.appeal&.pending? + · + %span.warning-hint= t('disputes.strikes.your_appeal_pending') + - elsif account_warning.appeal&.rejected? + · + %span.negative-hint= t('disputes.strikes.your_appeal_rejected') diff --git a/app/views/auth/registrations/_status.html.haml b/app/views/auth/registrations/_status.html.haml index 47112dae0..3546510b2 100644 --- a/app/views/auth/registrations/_status.html.haml +++ b/app/views/auth/registrations/_status.html.haml @@ -1,22 +1,17 @@ +- if !@user.confirmed? + .flash-message.warning + = t('auth.status.confirming') + = link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path +- elsif !@user.approved? + .flash-message.warning + = t('auth.status.pending') +- elsif @user.account.moved_to_account_id.present? + .flash-message.warning + = t('auth.status.redirecting_to', acct: @user.account.moved_to_account.acct) + = link_to t('migrations.cancel'), settings_migration_path + %h3= t('auth.status.account_status') -.simple_form - %p.hint - - if @user.account.suspended? - %span.negative-hint= t('user_mailer.warning.explanation.suspend') - - elsif @user.disabled? - %span.negative-hint= t('user_mailer.warning.explanation.disable') - - elsif @user.account.silenced? - %span.warning-hint= t('user_mailer.warning.explanation.silence') - - elsif !@user.confirmed? - %span.warning-hint= t('auth.status.confirming') - = link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path - - elsif !@user.approved? - %span.warning-hint= t('auth.status.pending') - - elsif @user.account.moved_to_account_id.present? - %span.positive-hint= t('auth.status.redirecting_to', acct: @user.account.moved_to_account.acct) - = link_to t('migrations.cancel'), settings_migration_path - - else - %span.positive-hint= t('auth.status.functional') += render partial: 'account_warning', collection: @strikes %hr.spacer/ diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml new file mode 100644 index 000000000..3dcb19016 --- /dev/null +++ b/app/views/disputes/strikes/show.html.haml @@ -0,0 +1,127 @@ +- content_for :page_title do + = t('disputes.strikes.title', action: t(@strike.action, scope: 'disputes.strikes.title_actions'), date: l(@strike.created_at.to_date)) + +- content_for :heading_actions do + - if @appeal.persisted? + = link_to t('admin.accounts.approve'), approve_admin_disputes_appeal_path(@appeal), method: :post, class: 'button' if can?(:approve, @appeal) + = link_to t('admin.accounts.reject'), reject_admin_disputes_appeal_path(@appeal), method: :post, class: 'button button--destructive' if can?(:reject, @appeal) + +- if @strike.overruled? + %p.hint + %span.positive-hint + = fa_icon 'check' + = ' ' + = t 'disputes.strikes.appeal_approved' +- elsif @appeal.persisted? && @appeal.rejected? + %p.hint + %span.negative-hint + = fa_icon 'times' + = ' ' + = t 'disputes.strikes.appeal_rejected' + +.report-header + .report-header__card + .strike-card + - unless @strike.none_action? + %p= t "user_mailer.warning.explanation.#{@strike.action}" + + - unless @strike.text.blank? + = Formatter.instance.linkify(@strike.text) + + - if @strike.report && !@strike.report.other? + %p + %strong= t('user_mailer.warning.reason') + = t("user_mailer.warning.categories.#{@strike.report.category}") + + - if @strike.report.violation? && @strike.report.rule_ids.present? + %ul.rules-list + - @strike.report.rules.each do |rule| + %li= rule.text + + - if @strike.status_ids.present? && !@strike.status_ids.empty? + %p + %strong= t('user_mailer.warning.statuses') + + .strike-card__statuses-list + - status_map = @strike.statuses.includes(:application, :media_attachments).index_by(&:id) + + - @strike.status_ids.each do |status_id| + .strike-card__statuses-list__item + - if (status = status_map[status_id.to_i]) + .one-liner + = link_to short_account_status_url(@strike.target_account, status_id), class: 'emojify' do + = one_line_preview(status) + + - status.media_attachments.each do |media_attachment| + %abbr{ title: media_attachment.description } + = fa_icon 'link' + = media_attachment.file_file_name + .strike-card__statuses-list__item__meta + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) + · + = status.application.name + - else + .one-liner= t('disputes.strikes.status', id: status_id) + .strike-card__statuses-list__item__meta + = t('disputes.strikes.status_removed') + + .report-header__details + .report-header__details__item + .report-header__details__item__header + %strong= t('disputes.strikes.created_at') + .report-header__details__item__content + %time.formatted{ datetime: @strike.created_at.iso8601, title: l(@strike.created_at) }= l(@strike.created_at) + .report-header__details__item + .report-header__details__item__header + %strong= t('disputes.strikes.recipient') + .report-header__details__item__content + = admin_account_link_to @strike.target_account, path: can?(:show, @strike.target_account) ? admin_account_path(@strike.target_account_id) : ActivityPub::TagManager.instance.url_for(@strike.target_account) + .report-header__details__item + .report-header__details__item__header + %strong= t('disputes.strikes.action_taken') + .report-header__details__item__content + - if @strike.overruled? + %del= t(@strike.action, scope: 'user_mailer.warning.title') + - else + = t(@strike.action, scope: 'user_mailer.warning.title') + - if @strike.report && can?(:show, @strike.report) + .report-header__details__item + .report-header__details__item__header + %strong= t('disputes.strikes.associated_report') + .report-header__details__item__content + = link_to t('admin.reports.report', id: @strike.report.id), admin_report_path(@strike.report) + - if @appeal.persisted? + .report-header__details__item + .report-header__details__item__header + %strong= t('disputes.strikes.appeal_submitted_at') + .report-header__details__item__content + %time.formatted{ datetime: @appeal.created_at.iso8601, title: l(@appeal.created_at) }= l(@appeal.created_at) +%hr.spacer/ + +- if @appeal.persisted? + %h3= t('disputes.strikes.appeal') + + .report-notes + .report-notes__item + = image_tag @appeal.account.avatar.url, class: 'report-notes__item__avatar' + + .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 + + .report-notes__item__content + = simple_format(h(@appeal.text)) +- elsif can?(:appeal, @strike) + %h3= t('disputes.strikes.appeals.submit') + + = simple_form_for(@appeal, url: disputes_strike_appeal_path(@strike)) do |f| + .fields-group + = f.input :text, wrapper: :with_label, input_html: { maxlength: 500 } + + .actions + = f.button :button, t('disputes.strikes.appeals.submit'), type: :submit diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index d7cc1ed5d..223e5d740 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -21,6 +21,7 @@ - if current_user.staff? = ff.input :report, as: :boolean, wrapper: :with_label + = ff.input :appeal, as: :boolean, wrapper: :with_label = ff.input :pending_account, as: :boolean, wrapper: :with_label = ff.input :trending_tag, as: :boolean, wrapper: :with_label diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index cd5ed52af..1922f53ce 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -49,7 +49,7 @@ %span.detailed-status__visibility-icon = visibility_icon status · - - if status.application && @account.user&.setting_show_application + - if status.application && status.account.user&.setting_show_application - if status.application.website.blank? %strong.detailed-status__application= status.application.name - else diff --git a/app/views/user_mailer/appeal_approved.html.haml b/app/views/user_mailer/appeal_approved.html.haml new file mode 100644 index 000000000..962cab2e2 --- /dev/null +++ b/app/views/user_mailer/appeal_approved.html.haml @@ -0,0 +1,59 @@ +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell.hero + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center.padded + %table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td + = image_tag full_pack_url('media/images/mailer/icon_done.png'), alt: '' + + %h1= t 'user_mailer.appeal_approved.title' + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell.content-start + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center + %p= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.button-cell + %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.button-primary + = link_to root_url do + %span= t 'user_mailer.appeal_approved.action' diff --git a/app/views/user_mailer/appeal_approved.text.erb b/app/views/user_mailer/appeal_approved.text.erb new file mode 100644 index 000000000..290fa24c3 --- /dev/null +++ b/app/views/user_mailer/appeal_approved.text.erb @@ -0,0 +1,7 @@ +<%= t 'user_mailer.appeal_approved.title' %> + +=== + +<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %> + +=> <%= root_url %> diff --git a/app/views/user_mailer/appeal_rejected.html.haml b/app/views/user_mailer/appeal_rejected.html.haml new file mode 100644 index 000000000..75cd9d023 --- /dev/null +++ b/app/views/user_mailer/appeal_rejected.html.haml @@ -0,0 +1,59 @@ +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell.hero + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center.padded + %table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td + = image_tag full_pack_url('media/images/mailer/icon_warning.png'), alt: '' + + %h1= t 'user_mailer.appeal_rejected.title' + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell.content-start + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center + %p= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.button-cell + %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.button-primary + = link_to root_url do + %span= t 'user_mailer.appeal_approved.action' diff --git a/app/views/user_mailer/appeal_rejected.text.erb b/app/views/user_mailer/appeal_rejected.text.erb new file mode 100644 index 000000000..f47a76818 --- /dev/null +++ b/app/views/user_mailer/appeal_rejected.text.erb @@ -0,0 +1,7 @@ +<%= t 'user_mailer.appeal_rejected.title' %> + +=== + +<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %> + +=> <%= root_url %> diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml index bda1fef6c..b308e18f7 100644 --- a/app/views/user_mailer/warning.html.haml +++ b/app/views/user_mailer/warning.html.haml @@ -77,8 +77,8 @@ %tbody %tr %td.button-primary - = link_to about_more_url do - %span= t 'user_mailer.warning.review_server_policies' + = link_to disputes_strike_url(@warning) do + %span= t 'user_mailer.warning.appeal' %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody @@ -95,4 +95,4 @@ %tbody %tr %td.column-cell.text-center - %p= t 'user_mailer.warning.get_in_touch', instance: @instance + %p= t 'user_mailer.warning.appeal_description', instance: @instance diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 4245b7192..6ffe12ae0 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,25 +1,5 @@ { "ignored_warnings": [ - { - "warning_type": "SQL Injection", - "warning_code": 0, - "fingerprint": "04dbbc249b989db2e0119bbb0f59c9818e12889d2b97c529cdc0b1526002ba4b", - "check_name": "SQL", - "message": "Possible SQL injection", - "file": "app/models/report.rb", - "line": 113, - "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "Admin::ActionLog.from(\"(#{[Admin::ActionLog.where(:target_type => \"Report\", :target_id => id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Account\", :target_id => target_account_id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)].map do\n \"(#{query.to_sql})\"\n end.join(\" UNION ALL \")}) AS admin_action_logs\")", - "render_path": null, - "location": { - "type": "method", - "class": "Report", - "method": "history" - }, - "user_input": "Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)", - "confidence": "High", - "note": "" - }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -27,7 +7,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/status.rb", - "line": 100, + "line": 104, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "result.joins(\"INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")", "render_path": null, @@ -107,7 +87,7 @@ "check_name": "PermitAttributes", "message": "Potentially dangerous key allowed for mass assignment", "file": "app/controllers/api/v1/admin/reports_controller.rb", - "line": 78, + "line": 90, "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", "code": "params.permit(:resolved, :account_id, :target_account_id)", "render_path": null, @@ -140,6 +120,36 @@ "confidence": "Medium", "note": "" }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 2, + "fingerprint": "afad51718ae373b2f19d2513029fd2afccf58b9148e475934bc6a162ee33c352", + "check_name": "CrossSiteScripting", + "message": "Unescaped model attribute", + "file": "app/views/admin/disputes/appeals/_appeal.html.haml", + "line": 7, + "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting", + "code": "t((Unresolved Model).new.strike.action, :scope => \"admin.strikes.actions\", :name => content_tag(:span, (Unresolved Model).new.strike.account.username, :class => \"username\"), :target => content_tag(:span, (Unresolved Model).new.account.acct, :class => \"target\"))", + "render_path": [ + { + "type": "template", + "name": "admin/disputes/appeals/index", + "line": 16, + "file": "app/views/admin/disputes/appeals/index.html.haml", + "rendered": { + "name": "admin/disputes/appeals/_appeal", + "file": "app/views/admin/disputes/appeals/_appeal.html.haml" + } + } + ], + "location": { + "type": "template", + "template": "admin/disputes/appeals/_appeal" + }, + "user_input": "(Unresolved Model).new.strike", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Redirect", "warning_code": 18, @@ -194,7 +204,7 @@ { "type": "template", "name": "admin/trends/links/index", - "line": 37, + "line": 39, "file": "app/views/admin/trends/links/index.html.haml", "rendered": { "name": "admin/trends/links/_preview_card", @@ -213,13 +223,13 @@ { "warning_type": "Mass Assignment", "warning_code": 105, - "fingerprint": "e867661b2c9812bc8b75a5df12b28e2a53ab97015de0638b4e732fe442561b28", + "fingerprint": "f9de0ca4b04ae4b51b74d98db14dcbb6dae6809e627b58e711019cf9b4a47866", "check_name": "PermitAttributes", "message": "Potentially dangerous key allowed for mass assignment", "file": "app/controllers/api/v1/reports_controller.rb", "line": 36, "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", - "code": "params.permit(:account_id, :comment, :forward, :status_ids => ([]))", + "code": "params.permit(:account_id, :comment, :category, :forward, :status_ids => ([]), :rule_ids => ([]))", "render_path": null, "location": { "type": "method", @@ -231,6 +241,6 @@ "note": "" } ], - "updated": "2021-11-14 05:26:09 +0100", - "brakeman_version": "5.1.2" + "updated": "2022-02-13 02:24:12 +0100", + "brakeman_version": "5.2.1" } diff --git a/config/locales/en.yml b/config/locales/en.yml index 1809f123e..05c64e18a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -94,7 +94,6 @@ en: account_moderation_notes: create: Leave note created_msg: Moderation note successfully created! - delete: Delete destroyed_msg: Moderation note successfully destroyed! accounts: add_email_domain_block: Block e-mail domain @@ -163,6 +162,11 @@ en: not_subscribed: Not subscribed pending: Pending review perform_full_suspension: Suspend + previous_strikes: Previous strikes + previous_strikes_description_html: + one: This account has one strike. + other: This account has %{count} strikes. + zero: This account is in good standing. promote: Promote protocol: Protocol public: Public @@ -227,6 +231,7 @@ en: whitelisted: Allowed for federation action_logs: action_types: + approve_appeal: Approve Appeal approve_user: Approve User assigned_to_self_report: Assign Report change_email_user: Change E-mail for User @@ -258,6 +263,7 @@ en: enable_user: Enable User memorialize_account: Memorialize Account promote_user: Promote User + reject_appeal: Reject Appeal reject_user: Reject User remove_avatar_user: Remove Avatar reopen_report: Reopen Report @@ -276,6 +282,7 @@ en: update_domain_block: Update Domain Block update_status: Update Post actions: + approve_appeal_html: "%{name} approved moderation decision appeal from %{target}" approve_user_html: "%{name} approved sign-up from %{target}" assigned_to_self_report_html: "%{name} assigned report %{target} to themselves" change_email_user_html: "%{name} changed the e-mail address of user %{target}" @@ -307,6 +314,7 @@ en: enable_user_html: "%{name} enabled login for user %{target}" memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page" promote_user_html: "%{name} promoted user %{target}" + reject_appeal_html: "%{name} rejected moderation decision appeal from %{target}" reject_user_html: "%{name} rejected sign-up from %{target}" remove_avatar_user_html: "%{name} removed %{target}'s avatar" reopen_report_html: "%{name} reopened report %{target}" @@ -385,6 +393,9 @@ en: media_storage: Media storage new_users: new users opened_reports: reports opened + pending_appeals_html: + one: "1 pending appeal" + other: "%{count} pending appeals" pending_reports_html: one: "1 pending report" other: "%{count} pending reports" @@ -402,6 +413,10 @@ en: top_languages: Top active languages top_servers: Top active servers website: Website + disputes: + appeals: + empty: No appeals found. + title: Appeals domain_allows: add_new: Allow federation with domain created_msg: Domain has been successfully allowed for federation @@ -720,6 +735,16 @@ en: no_status_selected: No posts were changed as none were selected title: Account posts with_media: With media + strikes: + actions: + delete_statuses: "%{name} deleted %{target}'s posts" + disable: "%{name} froze %{target}'s account" + none: "%{name} sent a warning to %{target}" + sensitive: "%{name} marked %{target}'s account as sensitive" + silence: "%{name} limited %{target}'s account" + suspend: "%{name} suspended %{target}'s account" + appeal_approved: Appealed + appeal_pending: Appeal pending system_checks: database_schema_check: message_html: There are pending database migrations. Please run them to ensure the application behaves as expected @@ -781,6 +806,17 @@ en: empty: You haven't defined any warning presets yet. title: Manage warning presets admin_mailer: + new_appeal: + actions: + delete_statuses: to delete their posts + disable: to freeze their account + none: a warning + sensitive: to mark their account as sensitive + silence: to limit their account + suspend: to suspend their account + body: "%{target} is appealing a moderation decision by %{action_taken_by} from %{date}, which was %{type}. They wrote:" + next_steps: You can approve the appeal to undo the moderation decision, or ignore it. + subject: "%{username} is appealing a moderation decision on %{instance}" new_pending_account: body: The details of the new account are below. You can approve or reject this application. subject: New account up for review on %{instance} (%{username}) @@ -871,7 +907,6 @@ en: status: account_status: Account status confirming: Waiting for e-mail confirmation to be completed. - functional: Your account is fully operational. pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved. redirecting_to: Your account is inactive because it is currently redirecting to %{acct}. too_fast: Form submitted too fast, try again. @@ -937,6 +972,32 @@ en: directory: Profile directory explanation: Discover users based on their interests explore_mastodon: Explore %{title} + disputes: + strikes: + action_taken: Action taken + appeal: Appeal + appeal_approved: This strike has been successfully appealed and is no longer valid + appeal_rejected: The appeal has been rejected + appeal_submitted_at: Appeal submitted + appealed_msg: Your appeal has been submitted. If it is approved, you will be notified. + appeals: + submit: Submit appeal + associated_report: Associated report + created_at: Dated + recipient: Addressed to + status: 'Post #%{id}' + status_removed: Post already removed from system + title: "%{action} from %{date}" + title_actions: + delete_statuses: Post removal + disable: Freezing of account + none: Warning + sensitive: Marking as sensitive of account + silence: Limitation of account + suspend: Suspension of account + your_appeal_approved: Your appeal has been approved + your_appeal_pending: You have submitted an appeal + your_appeal_rejected: Your appeal has been rejected domain_validator: invalid_domain: is not a valid domain name errors: @@ -1501,6 +1562,15 @@ en: recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe. For example, you may print them and store them with other important documents. webauthn: Security keys user_mailer: + appeal_approved: + action: Go to your account + explanation: The appeal of the strike against your account on %{strike_date} that you submitted on %{appeal_date} has been approved. Your account is once again in good standing. + subject: Your appeal from %{date} has been approved + title: Appeal approved + appeal_rejected: + explanation: The appeal of the strike against your account on %{strike_date} that you submitted on %{appeal_date} has been rejected. + subject: Your appeal from %{date} has been rejected + title: Appeal rejected backup_ready: explanation: You requested a full backup of your Mastodon account. It's now ready for download! subject: Your archive is ready for download @@ -1512,6 +1582,8 @@ en: subject: Please confirm attempted sign in title: Sign in attempt warning: + appeal: Submit an appeal + appeal_description: If you believe this is an error, you can submit an appeal to the staff of %{instance}. categories: spam: Spam violation: Content violates the following community guidelines @@ -1523,7 +1595,6 @@ en: suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed in about 30 days, but we will retain some basic data to prevent you from evading the suspension. get_in_touch: If you believe this is an error, you can reply to this e-mail to get in touch with the staff of %{instance}. reason: 'Reason:' - review_server_policies: Review server policies statuses: 'Posts that have been found in violation:' subject: delete_statuses: Your posts on %{acct} have been removed diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index d6376782d..03eefd0d5 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -27,6 +27,8 @@ en: scheduled_at: Leave blank to publish the announcement immediately starts_at: Optional. In case your announcement is bound to a specific time range text: You can use post syntax. Please be mindful of the space the announcement will take up on the user's screen + appeal: + text: You can only appeal a strike once defaults: autofollow: People who sign up through the invite will automatically follow you avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px @@ -119,6 +121,8 @@ en: scheduled_at: Schedule publication starts_at: Start of event text: Announcement + appeal: + text: Explain why this decision should be reversed defaults: autofollow: Invite to follow your account avatar: Avatar @@ -197,6 +201,7 @@ en: sign_up_requires_approval: Limit sign-ups severity: Rule notification_emails: + appeal: Someone appeals a moderator decision digest: Send digest e-mails favourite: Someone favourited your post follow: Someone followed you @@ -204,8 +209,8 @@ en: mention: Someone mentioned you pending_account: New account needs review reblog: Someone boosted your post - report: A new report is submitted - trending_tag: A new trend requires approval + report: New report is submitted + trending_tag: New trend requires review rule: text: Rule tag: diff --git a/config/navigation.rb b/config/navigation.rb index fc03a2a77..3fc3747d5 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -20,7 +20,7 @@ SimpleNavigation::Configuration.run do |navigation| n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_url, if: -> { current_user.functional? } n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_url do |s| - s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities} + s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes} s.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_url, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys} s.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url end @@ -41,7 +41,7 @@ SimpleNavigation::Configuration.run do |navigation| n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s| s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} - s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts} + s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes} s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations} s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.admin? } diff --git a/config/routes.rb b/config/routes.rb index 5edb36519..f59f2a5bb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -164,6 +164,12 @@ Rails.application.routes.draw do resources :login_activities, only: [:index] end + namespace :disputes do + resources :strikes, only: [:show] do + resource :appeal, only: [:create] + end + end + resources :media, only: [:show] do get :player end @@ -324,6 +330,15 @@ Rails.application.routes.draw do end end end + + namespace :disputes do + resources :appeals, only: [:index] do + member do + post :approve + post :reject + end + end + end end get '/admin', to: redirect('/admin/dashboard', status: 302) diff --git a/config/settings.yml b/config/settings.yml index 06cee2532..e63788ba2 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -48,6 +48,7 @@ defaults: &defaults report: true pending_account: true trending_tag: true + appeal: true interactions: must_be_follower: false must_be_following: false diff --git a/db/migrate/20220124141035_create_appeals.rb b/db/migrate/20220124141035_create_appeals.rb new file mode 100644 index 000000000..afb3efbd5 --- /dev/null +++ b/db/migrate/20220124141035_create_appeals.rb @@ -0,0 +1,14 @@ +class CreateAppeals < ActiveRecord::Migration[6.1] + def change + create_table :appeals do |t| + t.belongs_to :account, null: false, foreign_key: { on_delete: :cascade } + t.belongs_to :account_warning, null: false, foreign_key: { on_delete: :cascade }, index: { unique: true } + t.text :text, null: false, default: '' + t.datetime :approved_at + t.belongs_to :approved_by_account, foreign_key: { to_table: :accounts, on_delete: :nullify } + t.datetime :rejected_at + t.belongs_to :rejected_by_account, foreign_key: { to_table: :accounts, on_delete: :nullify } + t.timestamps + end + end +end diff --git a/db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb b/db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb new file mode 100644 index 000000000..a082da774 --- /dev/null +++ b/db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb @@ -0,0 +1,5 @@ +class AddOverruledAtToAccountWarnings < ActiveRecord::Migration[6.1] + def change + add_column :account_warnings, :overruled_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index fd4633d69..8842dcd8c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_01_18_183123) do +ActiveRecord::Schema.define(version: 2022_02_10_153119) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -135,6 +135,7 @@ ActiveRecord::Schema.define(version: 2022_01_18_183123) do t.datetime "updated_at", null: false t.bigint "report_id" t.string "status_ids", array: true + t.datetime "overruled_at" t.index ["account_id"], name: "index_account_warnings_on_account_id" t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id" end @@ -243,6 +244,22 @@ ActiveRecord::Schema.define(version: 2022_01_18_183123) do t.bigint "status_ids", array: true end + create_table "appeals", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "account_warning_id", null: false + t.text "text", default: "", null: false + t.datetime "approved_at" + t.bigint "approved_by_account_id" + t.datetime "rejected_at" + t.bigint "rejected_by_account_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["account_id"], name: "index_appeals_on_account_id" + t.index ["account_warning_id"], name: "index_appeals_on_account_warning_id", unique: true + t.index ["approved_by_account_id"], name: "index_appeals_on_approved_by_account_id" + t.index ["rejected_by_account_id"], name: "index_appeals_on_rejected_by_account_id" + end + create_table "backups", force: :cascade do |t| t.bigint "user_id" t.string "dump_file_name" @@ -1031,6 +1048,10 @@ ActiveRecord::Schema.define(version: 2022_01_18_183123) do add_foreign_key "announcement_reactions", "accounts", on_delete: :cascade add_foreign_key "announcement_reactions", "announcements", on_delete: :cascade add_foreign_key "announcement_reactions", "custom_emojis", on_delete: :cascade + add_foreign_key "appeals", "account_warnings", on_delete: :cascade + add_foreign_key "appeals", "accounts", column: "approved_by_account_id", on_delete: :nullify + add_foreign_key "appeals", "accounts", column: "rejected_by_account_id", on_delete: :nullify + add_foreign_key "appeals", "accounts", on_delete: :cascade add_foreign_key "backups", "users", on_delete: :nullify add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb new file mode 100644 index 000000000..6a06f9406 --- /dev/null +++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +RSpec.describe Admin::Disputes::AppealsController, type: :controller do + render_views + + before { sign_in current_user, scope: :user } + + let(:target_account) { Fabricate(:account) } + let(:strike) { Fabricate(:account_warning, target_account: target_account, action: :suspend) } + let(:appeal) { Fabricate(:appeal, strike: strike, account: target_account) } + + before do + target_account.suspend! + end + + describe 'POST #approve' do + let(:current_user) { Fabricate(:user, admin: true) } + + before do + allow(UserMailer).to receive(:appeal_approved).and_return(double('email', deliver_later: nil)) + post :approve, params: { id: appeal.id } + end + + it 'unsuspends a suspended account' do + expect(target_account.reload.suspended?).to be false + end + + it 'redirects back to the strike page' do + expect(response).to redirect_to(disputes_strike_path(appeal.strike)) + end + + it 'notifies target account about approved appeal' do + expect(UserMailer).to have_received(:appeal_approved).with(target_account.user, appeal) + end + end + + describe 'POST #reject' do + let(:current_user) { Fabricate(:user, admin: true) } + + before do + allow(UserMailer).to receive(:appeal_rejected).and_return(double('email', deliver_later: nil)) + post :reject, params: { id: appeal.id } + end + + it 'redirects back to the strike page' do + expect(response).to redirect_to(disputes_strike_path(appeal.strike)) + end + + it 'notifies target account about rejected appeal' do + expect(UserMailer).to have_received(:appeal_rejected).with(target_account.user, appeal) + end + end +end diff --git a/spec/controllers/disputes/appeals_controller_spec.rb b/spec/controllers/disputes/appeals_controller_spec.rb new file mode 100644 index 000000000..faa571fc9 --- /dev/null +++ b/spec/controllers/disputes/appeals_controller_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +RSpec.describe Disputes::AppealsController, type: :controller do + render_views + + before { sign_in current_user, scope: :user } + + let!(:admin) { Fabricate(:user, admin: true) } + + describe '#create' do + let(:current_user) { Fabricate(:user) } + let(:strike) { Fabricate(:account_warning, target_account: current_user.account) } + + before do + allow(AdminMailer).to receive(:new_appeal).and_return(double('email', deliver_later: nil)) + post :create, params: { strike_id: strike.id, appeal: { text: 'Foo' } } + end + + it 'notifies staff about new appeal' do + expect(AdminMailer).to have_received(:new_appeal).with(admin.account, Appeal.last) + end + + it 'redirects back to the strike page' do + expect(response).to redirect_to(disputes_strike_path(strike.id)) + end + end +end diff --git a/spec/controllers/disputes/strikes_controller_spec.rb b/spec/controllers/disputes/strikes_controller_spec.rb new file mode 100644 index 000000000..157f9ec3c --- /dev/null +++ b/spec/controllers/disputes/strikes_controller_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe Disputes::StrikesController, type: :controller do + render_views + + before { sign_in current_user, scope: :user } + + describe '#show' do + let(:current_user) { Fabricate(:user) } + let(:strike) { Fabricate(:account_warning, target_account: current_user.account) } + + before do + get :show, params: { id: strike.id } + end + + context 'when meant for the user' do + it 'returns http success' do + expect(response).to have_http_status(:success) + end + end + + context 'when meant for a different user' do + let(:strike) { Fabricate(:account_warning) } + + it 'returns http forbidden' do + expect(response).to have_http_status(:forbidden) + end + end + end +end diff --git a/spec/fabricators/account_warning_fabricator.rb b/spec/fabricators/account_warning_fabricator.rb index db161d446..72fe835d9 100644 --- a/spec/fabricators/account_warning_fabricator.rb +++ b/spec/fabricators/account_warning_fabricator.rb @@ -1,5 +1,6 @@ Fabricator(:account_warning) do - account nil - target_account nil - text "MyText" + account + target_account(fabricator: :account) + text { Faker::Lorem.paragraph } + action 'suspend' end diff --git a/spec/fabricators/appeal_fabricator.rb b/spec/fabricators/appeal_fabricator.rb new file mode 100644 index 000000000..339363822 --- /dev/null +++ b/spec/fabricators/appeal_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator(:appeal) do + strike(fabricator: :account_warning) + account { |attrs| attrs[:strike].target_account } + text { Faker::Lorem.paragraph } +end diff --git a/spec/mailers/previews/admin_mailer_preview.rb b/spec/mailers/previews/admin_mailer_preview.rb index 75ffbbf40..9c0372b47 100644 --- a/spec/mailers/previews/admin_mailer_preview.rb +++ b/spec/mailers/previews/admin_mailer_preview.rb @@ -15,4 +15,9 @@ class AdminMailerPreview < ActionMailer::Preview def new_trending_links AdminMailer.new_trending_links(Account.first, PreviewCard.limit(3)) end + + # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_appeal + def new_appeal + AdminMailer.new_appeal(Account.first, Appeal.first) + end end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 69b9b971e..8de7d8669 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -82,6 +82,11 @@ class UserMailerPreview < ActionMailer::Preview UserMailer.warning(User.first, AccountWarning.last) end + # Preview this email at http://localhost:3000/rails/mailers/user_mailer/appeal_approved + def appeal_approved + UserMailer.appeal_approved(User.first, Appeal.last) + end + # Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token def sign_in_token UserMailer.sign_in_token(User.first.tap { |user| user.generate_sign_in_token }, '127.0.0.1', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0', Time.now.utc) diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb new file mode 100644 index 000000000..14062dc4f --- /dev/null +++ b/spec/models/appeal_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Appeal, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From f5c6ee4d54687a2824316447746fad45a9d05f2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:49:40 +0100 Subject: [PATCH 12/30] Bump dotenv from 10.0.0 to 16.0.0 (#17476) Bumps [dotenv](https://github.com/motdotla/dotenv) from 10.0.0 to 16.0.0. - [Release notes](https://github.com/motdotla/dotenv/releases) - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v10.0.0...v16.0.0) --- updated-dependencies: - dependency-name: dotenv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 017bbc053..5f642d7e6 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "css-loader": "^5.2.7", "cssnano": "^4.1.11", "detect-passive-events": "^2.0.3", - "dotenv": "^10.0.0", + "dotenv": "^16.0.0", "emoji-mart": "npm:emoji-mart-lazyload", "es6-symbol": "^3.1.3", "escape-html": "^1.0.3", diff --git a/yarn.lock b/yarn.lock index e9bcf882c..20e670656 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4073,10 +4073,10 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" + integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== duplexer@^0.1.2: version "0.1.2" From 61c2e74f07a0ca1001e64ea07cf10f457fd7f265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:05:18 +0900 Subject: [PATCH 13/30] Bump @babel/runtime from 7.17.0 to 7.17.2 (#17544) Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.17.0 to 7.17.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.17.2/packages/babel-runtime) --- updated-dependencies: - dependency-name: "@babel/runtime" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5f642d7e6..005808a0a 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@babel/plugin-transform-runtime": "^7.17.0", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", - "@babel/runtime": "^7.17.0", + "@babel/runtime": "^7.17.2", "@gamestdio/websocket": "^0.3.2", "@github/webauthn-json": "^0.5.7", "@rails/ujs": "^6.1.4", diff --git a/yarn.lock b/yarn.lock index 20e670656..a72b015e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1024,10 +1024,10 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.0", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.0.tgz#b8d142fc0f7664fb3d9b5833fd40dcbab89276c0" - integrity sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" + integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== dependencies: regenerator-runtime "^0.13.4" From fee7d5cce64b79cd000c7d387ba5788d824b1d48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:06:06 +0900 Subject: [PATCH 14/30] Bump babel-jest from 27.5.0 to 27.5.1 (#17549) Bumps [babel-jest](https://github.com/facebook/jest/tree/HEAD/packages/babel-jest) from 27.5.0 to 27.5.1. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/commits/v27.5.1/packages/babel-jest) --- updated-dependencies: - dependency-name: babel-jest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 120 +-------------------------------------------------- 2 files changed, 2 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index 005808a0a..d5c1f9651 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.2", "babel-eslint": "^10.1.0", - "babel-jest": "^27.5.0", + "babel-jest": "^27.5.1", "eslint": "^7.32.0", "eslint-plugin-import": "~2.25.4", "eslint-plugin-jsx-a11y": "~6.5.1", diff --git a/yarn.lock b/yarn.lock index a72b015e9..79a900a36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1338,27 +1338,6 @@ jest-haste-map "^27.5.1" jest-runtime "^27.5.1" -"@jest/transform@^27.5.0": - version "27.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.0.tgz#a4941e69ac51e8aa9a255ff4855b564c228c400b" - integrity sha512-yXUy/iO3TH1itxJ9BF7LLjuXt8TtgtjAl0PBQbUaCvRa+L0yYBob6uayW9dFRX/CDQweouLhvmXh44zRiaB+yA== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^27.5.0" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.0" - jest-regex-util "^27.5.0" - jest-util "^27.5.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - "@jest/transform@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" @@ -1390,18 +1369,7 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^27.0.2", "@jest/types@^27.5.0": - version "27.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.0.tgz#6ad04a5c5355fd9f46e5cf761850e0edb3c209dd" - integrity sha512-oDHEp7gwSgA82RZ6pzUL3ugM2njP/lVB1MsxRZNOBk+CoNvh9SpH1lQixPFc/kDlV50v59csiW4HLixWmhmgPQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jest/types@^27.5.1": +"@jest/types@^27.0.2", "@jest/types@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== @@ -2359,20 +2327,6 @@ babel-eslint@^10.1.0: eslint-visitor-keys "^1.0.0" resolve "^1.12.0" -babel-jest@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.0.tgz#c653985241af3c76f59d70d65a570860c2594a50" - integrity sha512-puhCyvBTNLevhbd1oyw6t3gWBicWoUARQYKCBB/B1moif17NbyhxbsfadqZIw8zfJJD+W7Vw0Nb20pEjLxkXqQ== - dependencies: - "@jest/transform" "^27.5.0" - "@jest/types" "^27.5.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^27.5.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" @@ -2415,16 +2369,6 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.0.tgz#8fdf07835f2165a068de3ce95fd7749a89801b51" - integrity sha512-ztwNkHl+g1GaoQcb8f2BER4C3LMvSXuF7KVqtUioXQgScSEnkl6lLgCILUYIR+CPTwL8H3F/PNLze64HPWF9JA== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" - babel-plugin-jest-hoist@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" @@ -2525,14 +2469,6 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.0.tgz#4e308711c3d2ff1f45cf5d9a23646e37b621fc9f" - integrity sha512-7bfu1cJBlgK/nKfTvMlElzA3jpi6GzDWX3fntnyP2cQSzoi/KUz6ewGlcb3PSRYZGyv+uPnVHY0Im3JbsViqgA== - dependencies: - babel-plugin-jest-hoist "^27.5.0" - babel-preset-current-node-syntax "^1.0.0" - babel-preset-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" @@ -6466,26 +6402,6 @@ jest-get-type@^27.5.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== -jest-haste-map@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.0.tgz#7cc3a920caf304c89fbfceb5d5717b929873f175" - integrity sha512-0KfckSBEKV+D6e0toXmIj4zzp72EiBnvkC0L+xYxenkLhAdkp2/8tye4AgMzz7Fqb1r8SWtz7+s1UQLrxMBang== - dependencies: - "@jest/types" "^27.5.0" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^27.5.0" - jest-serializer "^27.5.0" - jest-util "^27.5.0" - jest-worker "^27.5.0" - micromatch "^4.0.4" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.3.2" - jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -6575,11 +6491,6 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.0.tgz#26c26cf15a73edba13cb8930e261443d25ed8608" - integrity sha512-e9LqSd6HsDsqd7KS3rNyYwmQAaG9jq4U3LbnwVxN/y3nNlDzm2OFs596uo9zrUY+AV1opXq6ome78tRDUCRWfA== - jest-regex-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" @@ -6665,14 +6576,6 @@ jest-runtime@^27.5.1: slash "^3.0.0" strip-bom "^4.0.0" -jest-serializer@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.0.tgz#439a110df27f97a40c114a429b708c2ada15a81f" - integrity sha512-aSDFqQlVXtBH+Zb5dl9mCvTSFkabixk/9P9cpngL4yJKpmEi9USxfDhONFMzJrtftPvZw3PcltUVmtFZTB93rg== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.9" - jest-serializer@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" @@ -6709,18 +6612,6 @@ jest-snapshot@^27.5.1: pretty-format "^27.5.1" semver "^7.3.2" -jest-util@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.0.tgz#0b9540d91b0de65d288f235fa9899e6eeeab8d35" - integrity sha512-FUUqOx0gAzJy3ytatT1Ss372M1kmhczn8x7aE0++11oPGW1FyD/5NjYBI8w1KOXFm6IVjtaZm2szfJJL+CHs0g== - dependencies: - "@jest/types" "^27.5.0" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" @@ -6767,15 +6658,6 @@ jest-worker@^26.5.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^27.5.0: - version "27.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.0.tgz#99ee77e4d06168107c27328bd7f54e74c3a48d59" - integrity sha512-8OEHiPNOPTfaWnJ2SUHM8fmgeGq37uuGsQBvGKQJl1f+6WIy6g7G3fE2ruI5294bUKUI9FaCWt5hDvO8HSwsSg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" From 5939aa934b73cb7a4446e574aa8b3677bbe9d8f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:06:25 +0900 Subject: [PATCH 15/30] Bump @babel/plugin-proposal-decorators from 7.17.0 to 7.17.2 (#17548) Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-proposal-decorators) from 7.17.0 to 7.17.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.17.2/packages/babel-plugin-proposal-decorators) --- updated-dependencies: - dependency-name: "@babel/plugin-proposal-decorators" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d5c1f9651..65eef3403 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "private": true, "dependencies": { "@babel/core": "^7.17.2", - "@babel/plugin-proposal-decorators": "^7.17.0", + "@babel/plugin-proposal-decorators": "^7.17.2", "@babel/plugin-transform-react-inline-elements": "^7.16.7", "@babel/plugin-transform-runtime": "^7.17.0", "@babel/preset-env": "^7.16.11", diff --git a/yarn.lock b/yarn.lock index 79a900a36..c0bef8fc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -91,7 +91,7 @@ browserslist "^4.17.5" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.0": +"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.1": version "7.17.1" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz#9699f14a88833a7e055ce57dcd3ffdcd25186b21" integrity sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ== @@ -357,12 +357,12 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-decorators@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.0.tgz#fc0f689fe2535075056c587bc10c176fa9990443" - integrity sha512-JR8HTf3T1CsdMqfENrZ9pqncwsH4sPcvsyDLpvmv8iIbpDmeyBD7HPfGAIqkQph2j5d3B84hTm+m3qHPAedaPw== +"@babel/plugin-proposal-decorators@^7.17.2": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.2.tgz#c36372ddfe0360cac1ee331a238310bddca11493" + integrity sha512-WH8Z95CwTq/W8rFbMqb9p3hicpt4RX4f0K659ax2VHxgOyT6qQmUaEVEjIh4WR9Eh9NymkVn5vwsrE68fAQNUw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.0" + "@babel/helper-create-class-features-plugin" "^7.17.1" "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" "@babel/plugin-syntax-decorators" "^7.17.0" From b2896442278e5bdcac013c81762ac8a3aad76ee3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:07:14 +0900 Subject: [PATCH 16/30] Bump npmlog from 6.0.0 to 6.0.1 (#17545) Bumps [npmlog](https://github.com/npm/npmlog) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/npm/npmlog/releases) - [Changelog](https://github.com/npm/npmlog/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/npmlog/compare/v6.0.0...v6.0.1) --- updated-dependencies: - dependency-name: npmlog dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 65eef3403..01582b5f9 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "marky": "^1.2.2", "mini-css-extract-plugin": "^1.6.2", "mkdirp": "^1.0.4", - "npmlog": "^6.0.0", + "npmlog": "^6.0.1", "object-assign": "^4.1.1", "object-fit-images": "^3.2.3", "object.values": "^1.1.5", diff --git a/yarn.lock b/yarn.lock index c0bef8fc5..caefb1f44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2116,10 +2116,10 @@ aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -are-we-there-yet@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" - integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== +are-we-there-yet@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d" + integrity sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw== dependencies: delegates "^1.0.0" readable-stream "^3.6.0" @@ -7562,12 +7562,12 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npmlog@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.0.tgz#ba9ef39413c3d936ea91553db7be49c34ad0520c" - integrity sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q== +npmlog@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.1.tgz#06f1344a174c06e8de9c6c70834cfba2964bba17" + integrity sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg== dependencies: - are-we-there-yet "^2.0.0" + are-we-there-yet "^3.0.0" console-control-strings "^1.1.0" gauge "^4.0.0" set-blocking "^2.0.0" From db94a3d94d51e6223d41da412c18529e2db4156a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:07:42 +0900 Subject: [PATCH 17/30] Bump rqrcode from 2.1.0 to 2.1.1 (#17542) Bumps [rqrcode](https://github.com/whomwah/rqrcode) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/whomwah/rqrcode/releases) - [Changelog](https://github.com/whomwah/rqrcode/blob/master/CHANGELOG.md) - [Commits](https://github.com/whomwah/rqrcode/compare/v2.1.0...v2.1.1) --- updated-dependencies: - dependency-name: rqrcode dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 71e8cf4cd..5cb067603 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -506,7 +506,7 @@ GEM rexml (3.2.5) rotp (6.2.0) rpam2 (4.0.2) - rqrcode (2.1.0) + rqrcode (2.1.1) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) From 5f4b9e1bce3e722f3e058ea73c3e137aa1cc26d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:13:48 +0900 Subject: [PATCH 18/30] Bump rails from 6.1.4.4 to 6.1.4.6 (#17550) Bumps [rails](https://github.com/rails/rails) from 6.1.4.4 to 6.1.4.6. - [Release notes](https://github.com/rails/rails/releases) - [Commits](https://github.com/rails/rails/compare/v6.1.4.4...v6.1.4.6) --- updated-dependencies: - dependency-name: rails dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5cb067603..8c2062f58 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,40 +1,40 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) + actioncable (6.1.4.6) + actionpack (= 6.1.4.6) + activesupport (= 6.1.4.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.4.4) - actionpack (= 6.1.4.4) - activejob (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) + actionmailbox (6.1.4.6) + actionpack (= 6.1.4.6) + activejob (= 6.1.4.6) + activerecord (= 6.1.4.6) + activestorage (= 6.1.4.6) + activesupport (= 6.1.4.6) mail (>= 2.7.1) - actionmailer (6.1.4.4) - actionpack (= 6.1.4.4) - actionview (= 6.1.4.4) - activejob (= 6.1.4.4) - activesupport (= 6.1.4.4) + actionmailer (6.1.4.6) + actionpack (= 6.1.4.6) + actionview (= 6.1.4.6) + activejob (= 6.1.4.6) + activesupport (= 6.1.4.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.4.4) - actionview (= 6.1.4.4) - activesupport (= 6.1.4.4) + actionpack (6.1.4.6) + actionview (= 6.1.4.6) + activesupport (= 6.1.4.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.4.4) - actionpack (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) + actiontext (6.1.4.6) + actionpack (= 6.1.4.6) + activerecord (= 6.1.4.6) + activestorage (= 6.1.4.6) + activesupport (= 6.1.4.6) nokogiri (>= 1.8.5) - actionview (6.1.4.4) - activesupport (= 6.1.4.4) + actionview (6.1.4.6) + activesupport (= 6.1.4.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -45,22 +45,22 @@ GEM case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.8) - activejob (6.1.4.4) - activesupport (= 6.1.4.4) + activejob (6.1.4.6) + activesupport (= 6.1.4.6) globalid (>= 0.3.6) - activemodel (6.1.4.4) - activesupport (= 6.1.4.4) - activerecord (6.1.4.4) - activemodel (= 6.1.4.4) - activesupport (= 6.1.4.4) - activestorage (6.1.4.4) - actionpack (= 6.1.4.4) - activejob (= 6.1.4.4) - activerecord (= 6.1.4.4) - activesupport (= 6.1.4.4) + activemodel (6.1.4.6) + activesupport (= 6.1.4.6) + activerecord (6.1.4.6) + activemodel (= 6.1.4.6) + activesupport (= 6.1.4.6) + activestorage (6.1.4.6) + actionpack (= 6.1.4.6) + activejob (= 6.1.4.6) + activerecord (= 6.1.4.6) + activesupport (= 6.1.4.6) marcel (~> 1.0.0) mini_mime (>= 1.1.0) - activesupport (6.1.4.4) + activesupport (6.1.4.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -453,20 +453,20 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.4.4) - actioncable (= 6.1.4.4) - actionmailbox (= 6.1.4.4) - actionmailer (= 6.1.4.4) - actionpack (= 6.1.4.4) - actiontext (= 6.1.4.4) - actionview (= 6.1.4.4) - activejob (= 6.1.4.4) - activemodel (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) + rails (6.1.4.6) + actioncable (= 6.1.4.6) + actionmailbox (= 6.1.4.6) + actionmailer (= 6.1.4.6) + actionpack (= 6.1.4.6) + actiontext (= 6.1.4.6) + actionview (= 6.1.4.6) + activejob (= 6.1.4.6) + activemodel (= 6.1.4.6) + activerecord (= 6.1.4.6) + activestorage (= 6.1.4.6) + activesupport (= 6.1.4.6) bundler (>= 1.15.0) - railties (= 6.1.4.4) + railties (= 6.1.4.6) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -482,9 +482,9 @@ GEM railties (>= 6.0.0, < 7) rails-settings-cached (0.6.6) rails (>= 4.2.0) - railties (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) + railties (6.1.4.6) + actionpack (= 6.1.4.6) + activesupport (= 6.1.4.6) method_source rake (>= 0.13) thor (~> 1.0) From 073543a19c071286fba09adda6b4baf45d5239ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 13:05:10 +0900 Subject: [PATCH 19/30] Bump pg from 1.3.1 to 1.3.2 (#17555) Bumps [pg](https://github.com/ged/ruby-pg) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/ged/ruby-pg/releases) - [Changelog](https://github.com/ged/ruby-pg/blob/master/History.rdoc) - [Commits](https://github.com/ged/ruby-pg/compare/v1.3.1...v1.3.2) --- updated-dependencies: - dependency-name: pg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8c2062f58..988d507dc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -416,7 +416,7 @@ GEM parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.3.1) + pg (1.3.2) pghero (2.8.2) activerecord (>= 5) pkg-config (1.4.7) From 448d62058ca49203e220db8e912b5d9b3ca16918 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 19:18:09 +0900 Subject: [PATCH 20/30] Bump scenic from 1.5.5 to 1.6.0 (#17547) Bumps [scenic](https://github.com/scenic-views/scenic) from 1.5.5 to 1.6.0. - [Release notes](https://github.com/scenic-views/scenic/releases) - [Changelog](https://github.com/scenic-views/scenic/blob/main/CHANGELOG.md) - [Commits](https://github.com/scenic-views/scenic/compare/v1.5.5...v1.6.0) --- updated-dependencies: - dependency-name: scenic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index ee5395bfc..5ade2e8c4 100644 --- a/Gemfile +++ b/Gemfile @@ -76,7 +76,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 2.1' gem 'ruby-progressbar', '~> 1.11' gem 'sanitize', '~> 6.0' -gem 'scenic', '~> 1.5' +gem 'scenic', '~> 1.6' gem 'sidekiq', '~> 6.4' gem 'sidekiq-scheduler', '~> 3.1' gem 'sidekiq-unique-jobs', '~> 7.1' diff --git a/Gemfile.lock b/Gemfile.lock index 988d507dc..37d101be5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -559,7 +559,7 @@ GEM sanitize (6.0.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - scenic (1.5.5) + scenic (1.6.0) activerecord (>= 4.0.0) railties (>= 4.0.0) securecompare (1.0.0) @@ -777,7 +777,7 @@ DEPENDENCIES rubocop-rails (~> 2.13) ruby-progressbar (~> 1.11) sanitize (~> 6.0) - scenic (~> 1.5) + scenic (~> 1.6) sidekiq (~> 6.4) sidekiq-bulk (~> 0.2.0) sidekiq-scheduler (~> 3.1) From 436d1243e5e4ed25e8c871960e443e87d2266be0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 19:20:37 +0900 Subject: [PATCH 21/30] Bump axios from 0.25.0 to 0.26.0 (#17551) Bumps [axios](https://github.com/axios/axios) from 0.25.0 to 0.26.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 01582b5f9..387fc4bb6 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "array-includes": "^3.1.4", "arrow-key-navigation": "^1.2.0", "autoprefixer": "^9.8.8", - "axios": "^0.25.0", + "axios": "^0.26.0", "babel-loader": "^8.2.3", "babel-plugin-lodash": "^3.3.4", "babel-plugin-preval": "^5.1.0", diff --git a/yarn.lock b/yarn.lock index caefb1f44..1d67179fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2303,12 +2303,12 @@ axe-core@^4.3.5: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5" integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA== -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== +axios@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928" + integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og== dependencies: - follow-redirects "^1.14.7" + follow-redirects "^1.14.8" axobject-query@^2.2.0: version "2.2.0" @@ -4925,7 +4925,7 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@^1.0.0, follow-redirects@^1.14.7: +follow-redirects@^1.0.0, follow-redirects@^1.14.8: version "1.14.8" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== From 2fd2666eea571737fa689a2808b3e5f8786701f0 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Wed, 16 Feb 2022 21:14:53 +0900 Subject: [PATCH 22/30] Add test for user matching ip (#17572) --- spec/models/user_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 406438c22..1645ab59e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -89,6 +89,19 @@ RSpec.describe User, type: :model do expect(User.matches_email('specified')).to match_array([specified]) end end + + describe 'matches_ip' do + it 'returns a relation of users whose ip address is matching with the given CIDR' do + user1 = Fabricate(:user) + user2 = Fabricate(:user) + Fabricate(:session_activation, user: user1, ip: '2160:2160::22', session_id: '1') + Fabricate(:session_activation, user: user1, ip: '2160:2160::23', session_id: '2') + Fabricate(:session_activation, user: user2, ip: '2160:8888::24', session_id: '3') + Fabricate(:session_activation, user: user2, ip: '2160:8888::25', session_id: '4') + + expect(User.matches_ip('2160:2160::/32')).to match_array([user1]) + end + end end let(:account) { Fabricate(:account, username: 'alice') } From 793da08995d66793fed31ec1b889404e7808cbfb Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 13:17:28 +0100 Subject: [PATCH 23/30] =?UTF-8?q?Change=20dasbhoard=20links=20for=20?= =?UTF-8?q?=E2=80=9Cnew=20users=E2=80=9D=20and=20=E2=80=9Cactive=20users?= =?UTF-8?q?=E2=80=9D=20(#17570)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make them filter for local accounts by default --- app/views/admin/dashboard/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 59b75e0e1..8354f0b9f 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -19,10 +19,10 @@ .dashboard .dashboard__item - = react_admin_component :counter, measure: 'new_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.new_users'), href: admin_accounts_path + = react_admin_component :counter, measure: 'new_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.new_users'), href: admin_accounts_path(origin: 'local') .dashboard__item - = react_admin_component :counter, measure: 'active_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.active_users'), href: admin_accounts_path + = react_admin_component :counter, measure: 'active_users', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.active_users'), href: admin_accounts_path(origin: 'local') .dashboard__item = react_admin_component :counter, measure: 'interactions', start_at: @time_period.first, end_at: @time_period.last, label: t('admin.dashboard.interactions') From 73fce8d31115f44d6d3f20f563c656b1595e70f8 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 14:28:45 +0100 Subject: [PATCH 24/30] Fix performance of server-side filtering (#17575) Fixes #17567 --- app/lib/feed_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 7840afee8..ccd4d3610 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -441,7 +441,7 @@ class FeedManager return false if active_filters.empty? - combined_regex = active_filters.reduce { |memo, obj| Regexp.union(memo, obj) } + combined_regex = Regexp.union(active_filters) status = status.reblog if status.reblog? combined_text = [ From 8f537a116802ee39727d8b6c286883b9440ab51a Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 14:36:44 +0100 Subject: [PATCH 25/30] Change relays handling to not record boosts (#17571) * Change relays handling to not record boosts * Update tests --- app/lib/activitypub/activity/announce.rb | 1 + .../lib/activitypub/activity/announce_spec.rb | 32 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 1f9319290..12fad8da4 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -8,6 +8,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity original_status = status_from_object return reject_payload! if original_status.nil? || !announceable?(original_status) + return if requested_through_relay? @status = Status.find_by(account: @account, reblog: original_status) diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb index b93fcbe66..41806b258 100644 --- a/spec/lib/activitypub/activity/announce_spec.rb +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -113,26 +113,23 @@ RSpec.describe ActivityPub::Activity::Announce do let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox') } let!(:relay) { Fabricate(:relay, inbox_url: 'https://relay.example.com/inbox') } + let(:object_json) { 'https://example.com/actor/hello-world' } + subject { described_class.new(json, sender, relayed_through_account: relay_account) } + before do + stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json)) + end + context 'and the relay is enabled' do before do relay.update(state: :accepted) subject.perform end - let(:object_json) do - { - id: 'https://example.com/actor#bar', - type: 'Note', - content: 'Lorem ipsum', - to: 'http://example.com/followers', - attributedTo: 'https://example.com/actor', - } - end - - it 'creates a reblog by sender of status' do - expect(sender.statuses.count).to eq 2 + it 'fetches the remote status' do + expect(a_request(:get, 'https://example.com/actor/hello-world')).to have_been_made + expect(Status.find_by(uri: 'https://example.com/actor/hello-world').text).to eq 'Hello world' end end @@ -141,14 +138,9 @@ RSpec.describe ActivityPub::Activity::Announce do subject.perform end - let(:object_json) do - { - id: 'https://example.com/actor#bar', - type: 'Note', - content: 'Lorem ipsum', - to: 'http://example.com/followers', - attributedTo: 'https://example.com/actor', - } + it 'does not fetch the remote status' do + expect(a_request(:get, 'https://example.com/actor/hello-world')).not_to have_been_made + expect(Status.find_by(uri: 'https://example.com/actor/hello-world')).to be_nil end it 'does not create anything' do From 6ea80ba2a22aaeae4f773b28b33168395c5297af Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 14:37:26 +0100 Subject: [PATCH 26/30] Change streaming server error messages when failing to parse client input (#17559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #17541 - prefix JSON parsing error message by “Error parsing message from …” - output user id if a user is logged in, IP address otherwise - reduce log level from error to warning when a user is logged in, and to silly otherwise --- streaming/index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/streaming/index.js b/streaming/index.js index 47f938b86..3db94b160 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -92,13 +92,18 @@ const numWorkers = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' /** * @param {string} json + * @param {any} req * @return {Object.|null} */ -const parseJSON = (json) => { +const parseJSON = (json, req) => { try { return JSON.parse(json); } catch (err) { - log.error(err); + if (req.accountId) { + log.warn(req.requestId, `Error parsing message from user ${req.accountId}: ${err}`); + } else { + log.silly(req.requestId, `Error parsing message from ${req.remoteAddress}: ${err}`); + } return null; } }; @@ -450,7 +455,7 @@ const startWorker = async (workerId) => { */ const createSystemMessageListener = (req, eventHandlers) => { return message => { - const json = parseJSON(message); + const json = parseJSON(message, req); if (!json) return; @@ -573,7 +578,7 @@ const startWorker = async (workerId) => { log.verbose(req.requestId, `Starting stream from ${ids.join(', ')} for ${accountId}`); const listener = message => { - const json = parseJSON(message); + const json = parseJSON(message, req); if (!json) return; @@ -1037,7 +1042,7 @@ const startWorker = async (workerId) => { ws.on('error', onEnd); ws.on('message', data => { - const json = parseJSON(data); + const json = parseJSON(data, session.request); if (!json) return; From 1abf0f90000c86bfbc5d6ac9a976834dcd17983a Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 14:57:57 +0100 Subject: [PATCH 27/30] Fix 0 pluralization for some localization strings (#17576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently, when the `zero` case is not defined, the translation string used for `0` depends on the language. In French, `one` is used, causing some strings with hardcoded “one” or `1` to misrepresent the actual data. For instance, the dashboard would display « 1 utilisateur·rice en attente » for both 0 and 1 pending users. --- config/locales/en.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 05c64e18a..cf4c2cc37 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -394,16 +394,16 @@ en: new_users: new users opened_reports: reports opened pending_appeals_html: - one: "1 pending appeal" + one: "%{count} pending appeal" other: "%{count} pending appeals" pending_reports_html: - one: "1 pending report" + one: "%{count} pending report" other: "%{count} pending reports" pending_tags_html: - one: "1 pending hashtag" + one: "%{count} pending hashtag" other: "%{count} pending hashtags" pending_users_html: - one: "1 pending user" + one: "%{count} pending user" other: "%{count} pending users" resolved_reports: reports resolved software: Software @@ -457,6 +457,7 @@ en: affected_accounts: one: One account in the database affected other: "%{count} accounts in the database affected" + zero: No account in the database is affected retroactive: silence: Undo limit of existing affected accounts from this domain suspend: Unsuspend existing affected accounts from this domain @@ -510,6 +511,7 @@ en: known_accounts: one: "%{count} known account" other: "%{count} known accounts" + zero: No known account moderation: all: All limited: Limited @@ -769,6 +771,7 @@ en: shared_by_over_week: one: Shared by one person over the last week other: Shared by %{count} people over the last week + zero: Shared by noone over the last week title: Trending links usage_comparison: Shared %{today} times today, compared to %{yesterday} yesterday pending_review: Pending review @@ -798,6 +801,7 @@ en: used_by_over_week: one: Used by one person over the last week other: Used by %{count} people over the last week + zero: Used by noone over the last week title: Trends warning_presets: add_new: Add new From 00b45b967e0c92714e1ec54a2d5c924f8b1dd38b Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 21:44:19 +0100 Subject: [PATCH 28/30] Fix edge case where settings/admin page sidebar would be incorrectly hidden (#17580) --- app/javascript/packs/public.js | 8 +------- app/javascript/styles/mastodon/admin.scss | 4 ++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index c0c088646..3d0a937e1 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -276,13 +276,7 @@ function main() { }); delegate(document, '.sidebar__toggle__icon', 'click', () => { - const target = document.querySelector('.sidebar ul'); - - if (target.style.display === 'block') { - target.style.display = 'none'; - } else { - target.style.display = 'block'; - } + document.querySelector('.sidebar ul').classList.toggle('visible'); }); // Empty the honeypot fields in JS in case something like an extension diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index f5741bd50..1921eb146 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -322,6 +322,10 @@ $content-width: 840px; & > ul { display: none; + + &.visible { + display: block; + } } ul a, From aa86cf955755cd05ed9c274daebbec248c39d863 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 21:44:47 +0100 Subject: [PATCH 29/30] Fix opening the emoji picker scrolling the single-column view to the top (#17579) Fixes #17577 --- .../compose/components/emoji_picker_dropdown.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index 4a87714e6..f433e4de9 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -170,7 +170,7 @@ class EmojiPickerMenu extends React.PureComponent { state = { modifierOpen: false, - placement: null, + readyToFocus: false, }; handleDocumentClick = e => { @@ -182,6 +182,16 @@ class EmojiPickerMenu extends React.PureComponent { componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); + + // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need + // to wait for a frame before focusing + requestAnimationFrame(() => { + this.setState({ readyToFocus: true }); + if (this.node) { + const element = this.node.querySelector('input[type="search"]'); + if (element) element.focus(); + } + }); } componentWillUnmount () { @@ -281,7 +291,7 @@ class EmojiPickerMenu extends React.PureComponent { showSkinTones={false} backgroundImageFn={backgroundImageFn} notFound={notFoundFn} - autoFocus + autoFocus={this.state.readyToFocus} emojiTooltip /> @@ -314,6 +324,7 @@ class EmojiPickerDropdown extends React.PureComponent { state = { active: false, loading: false, + placement: null, }; setRef = (c) => { From ac99f586bb4138e083676579097d951434e90515 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 16 Feb 2022 22:29:48 +0100 Subject: [PATCH 30/30] Fix issues when attempting to appeal an old strike (#17554) * Display an error when an appeal could not be submitted * Do not offer users to appeal old strikes * Fix 500 error when trying to appeal a strike that is too old * Avoid using an extra translatable string --- app/controllers/disputes/appeals_controller.rb | 3 ++- app/models/appeal.rb | 4 +++- app/policies/account_warning_policy.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/disputes/appeals_controller.rb b/app/controllers/disputes/appeals_controller.rb index 15367c879..eefd92b5a 100644 --- a/app/controllers/disputes/appeals_controller.rb +++ b/app/controllers/disputes/appeals_controller.rb @@ -9,7 +9,8 @@ class Disputes::AppealsController < Disputes::BaseController @appeal = AppealService.new.call(@strike, appeal_params[:text]) redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg') - rescue ActiveRecord::RecordInvalid + rescue ActiveRecord::RecordInvalid => e + @appeal = e.record render template: 'disputes/strikes/show' end diff --git a/app/models/appeal.rb b/app/models/appeal.rb index 46f35ae37..1f32cfa8b 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -16,6 +16,8 @@ # updated_at :datetime not null # class Appeal < ApplicationRecord + MAX_STRIKE_AGE = 20.days + belongs_to :account belongs_to :strike, class_name: 'AccountWarning', foreign_key: 'account_warning_id' belongs_to :approved_by_account, class_name: 'Account', optional: true @@ -53,6 +55,6 @@ class Appeal < ApplicationRecord private def validate_time_frame - errors.add(:base, I18n.t('strikes.errors.too_late')) if Time.now.utc > (strike.created_at + 20.days) + errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago end end diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb index 6b92da475..65707dfa7 100644 --- a/app/policies/account_warning_policy.rb +++ b/app/policies/account_warning_policy.rb @@ -6,7 +6,7 @@ class AccountWarningPolicy < ApplicationPolicy end def appeal? - target? + target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago end private