diff options
author | Thibaut Girka <thib@sitedethib.com> | 2020-05-03 21:20:42 +0200 |
---|---|---|
committer | Thibaut Girka <thib@sitedethib.com> | 2020-05-03 21:23:49 +0200 |
commit | a22e6a368333f3563f8d8d56d8e98d02088e82dc (patch) | |
tree | 4146f9e8afe4257c6f33bc695a3cfbfb89aa81b6 | |
parent | 9c61dadc0db7009853c6b2345a02c3b219022929 (diff) | |
parent | e223fd8c6190661237ea43e7773e47513c48fd46 (diff) |
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - `app/controllers/statuses_controller.rb`: Upstream disabled the embed controller for reblogs. Not a real conflict, but glitch-soc has an extra line to deal with its theming system. Ported upstream changes. - `app/javascript/packs/public.js`: Upstream made changes to get rid of most inline CSS, this changes javascript for public pages, which in glitch are split between different files. Ported those changes. - `app/models/status.rb`: Upstream changed the block check in `Status#permitted_for` to include domain-block checks. Not a real conflict with glitch-soc, but our scope is slightly different, as our scope for unauthenticated access do not include instance-local toots. Ported upstream changes. - `app/serializers/rest/instance_serializer.rb`: Not a real conflict, upstream added a new field to the instance serializer, the conflict is one line above since we added more of that. Ported upstream changes. - `app/views/settings/profiles/show.html.haml`: Upstream got rid of most inline CSS and moved hidden elements to data attributes in the process, in fields were we have different values. Ported upstream changes while keeping our glitch-specific values. - `app/views/statuses/_simple_status.html.haml`: Upstream got rid of inline CSS on an HAML line we treat differently, stripping empty text nodes. Ported upstream changes to the style attribute, keeping the empty text node stripping behavior.
81 files changed, 2170 insertions, 624 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index dd943e327..9f43a0573 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,12 +5,13 @@ aliases: docker: - image: circleci/ruby:2.7-buster-node environment: &ruby_environment + BUNDLE_JOBS: 3 + BUNDLE_RETRY: 3 BUNDLE_APP_CONFIG: ./.bundle/ BUNDLE_PATH: ./vendor/bundle/ DB_HOST: localhost DB_USER: root RAILS_ENV: test - PARALLEL_TEST_PROCESSORS: 4 ALLOW_NOPAM: true CONTINUOUS_INTEGRATION: true DISABLE_SIMPLECOV: true @@ -32,9 +33,9 @@ aliases: - &restore_ruby_dependencies restore_cache: keys: - - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} - - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- - - v2-ruby-dependencies- + - v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + - v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- + - v3-ruby-dependencies- - &install_steps steps: @@ -42,11 +43,13 @@ aliases: - *attach_workspace - restore_cache: keys: - - v1-node-dependencies-{{ checksum "yarn.lock" }} - - v1-node-dependencies- - - run: yarn install --frozen-lockfile + - v2-node-dependencies-{{ checksum "yarn.lock" }} + - v2-node-dependencies- + - run: + name: Install yarn dependencies + command: yarn install --frozen-lockfile - save_cache: - key: v1-node-dependencies-{{ checksum "yarn.lock" }} + key: v2-node-dependencies-{{ checksum "yarn.lock" }} paths: - ./node_modules/ - *persist_to_workspace @@ -57,27 +60,28 @@ aliases: command: | sudo apt-get update sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler - - ## TODO: FIX THESE BUSTER DEPENDANCES - sudo wget http://ftp.au.debian.org/debian/pool/main/i/icu/libicu57_57.1-6+deb9u3_amd64.deb - sudo dpkg -i libicu57_57.1-6+deb9u3_amd64.deb - sudo wget http://ftp.au.debian.org/debian/pool/main/p/protobuf/libprotobuf10_3.0.0-9_amd64.deb - sudo dpkg -i libprotobuf10_3.0.0-9_amd64.deb - &install_ruby_dependencies steps: - *attach_workspace - *install_system_dependencies - - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - run: + name: Set Ruby version + command: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - *restore_ruby_dependencies - - run: bundle config set clean 'true' - - run: bundle config set deployment 'true' - - run: bundle config set with 'pam_authentication' - - run: bundle config set without 'development production' - - run: bundle config set frozen 'true' - - run: bundle install --jobs 16 --retry 3 && bundle clean + - run: + name: Set bundler settings + command: | + bundle config clean 'true' + bundle config deployment 'true' + bundle config with 'pam_authentication' + bundle config without 'development production' + bundle config frozen 'true' + - run: + name: Install bundler dependencies + command: bundle check || (bundle install && bundle clean) - save_cache: - key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + key: v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} paths: - ./.bundle/ - ./vendor/bundle/ @@ -88,17 +92,26 @@ aliases: - ./mastodon/vendor/bundle/ - &test_steps + parallelism: 4 steps: - *attach_workspace - *install_system_dependencies - - run: sudo apt-get install -y ffmpeg - run: - name: Prepare Tests - command: ./bin/rails parallel:create parallel:load_schema parallel:prepare + name: Install FFMPEG + command: sudo apt-get install -y ffmpeg - run: - name: Run Tests - command: ./bin/retry bundle exec parallel_test ./spec/ --group-by filesize --type rspec - + name: Load database schema + command: ./bin/rails db:create db:schema:load db:seed + - run: + name: Run rspec in parallel + command: | + bundle exec rspec --profile 10 \ + --format RspecJunitFormatter \ + --out test_results/rspec.xml \ + --format progress \ + $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) + - store_test_results: + path: test_results jobs: install: <<: *defaults @@ -115,19 +128,14 @@ jobs: environment: *ruby_environment <<: *install_ruby_dependencies - install-ruby2.5: - <<: *defaults - docker: - - image: circleci/ruby:2.5-buster-node - environment: *ruby_environment - <<: *install_ruby_dependencies - build: <<: *defaults steps: - *attach_workspace - *install_system_dependencies - - run: ./bin/rails assets:precompile + - run: + name: Precompile assets + command: ./bin/rails assets:precompile - persist_to_workspace: root: ~/projects/ paths: @@ -149,10 +157,10 @@ jobs: - *install_system_dependencies - run: name: Create database - command: ./bin/rails parallel:create + command: ./bin/rails db:create - run: name: Run migrations - command: ./bin/rails parallel:migrate + command: ./bin/rails db:migrate test-ruby2.7: <<: *defaults @@ -178,35 +186,33 @@ jobs: - image: circleci/redis:5-alpine <<: *test_steps - test-ruby2.5: - <<: *defaults - docker: - - image: circleci/ruby:2.5-buster-node - environment: *ruby_environment - - image: circleci/postgres:12.2 - environment: - POSTGRES_USER: root - POSTGRES_HOST_AUTH_METHOD: trust - - image: circleci/redis:5-alpine - <<: *test_steps - test-webui: <<: *defaults docker: - image: circleci/node:12-buster steps: - *attach_workspace - - run: ./bin/retry yarn test:jest + - run: + name: Run jest + command: yarn test:jest check-i18n: <<: *defaults steps: - *attach_workspace - *install_system_dependencies - - run: bundle exec i18n-tasks check-normalized - - run: bundle exec i18n-tasks unused -l en - - run: bundle exec i18n-tasks check-consistent-interpolations - - run: bundle exec rake repo:check_locales_files + - run: + name: Check locale file normalization + command: bundle exec i18n-tasks check-normalized + - run: + name: Check for unused strings + command: bundle exec i18n-tasks unused -l en + - run: + name: Check for wrong string interpolations + command: bundle exec i18n-tasks check-consistent-interpolations + - run: + name: Check that all required locale files exist + command: bundle exec rake repo:check_locales_files workflows: version: 2 @@ -220,10 +226,6 @@ workflows: requires: - install - install-ruby2.7 - - install-ruby2.5: - requires: - - install - - install-ruby2.7 - build: requires: - install-ruby2.7 @@ -238,10 +240,6 @@ workflows: requires: - install-ruby2.6 - build - - test-ruby2.5: - requires: - - install-ruby2.5 - - build - test-webui: requires: - install diff --git a/Gemfile b/Gemfile index d59aa9d12..c5b957114 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,7 @@ gem 'makara', '~> 0.4' gem 'pghero', '~> 2.4' gem 'dotenv-rails', '~> 2.7' -gem 'aws-sdk-s3', '~> 1.61', require: false +gem 'aws-sdk-s3', '~> 1.63', require: false gem 'fog-core', '<= 2.1.0' gem 'fog-openstack', '~> 0.3', require: false gem 'paperclip', '~> 6.0' @@ -129,6 +129,7 @@ group :test do gem 'simplecov', '~> 0.18', require: false gem 'webmock', '~> 3.8' gem 'parallel_tests', '~> 2.32' + gem 'rspec_junit_formatter', '~> 0.4' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 13f6bed59..7525911ff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,7 +92,7 @@ GEM av (0.9.0) cocaine (~> 0.5.3) aws-eventstream (1.1.0) - aws-partitions (1.296.0) + aws-partitions (1.303.0) aws-sdk-core (3.94.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) @@ -101,7 +101,7 @@ GEM aws-sdk-kms (1.30.0) aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.61.2) + aws-sdk-s3 (1.63.0) aws-sdk-core (~> 3, >= 3.83.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) @@ -277,7 +277,7 @@ GEM http-parser (~> 1.2.0) http-cookie (1.0.3) domain_name (~> 0.5) - http-form_data (2.2.0) + http-form_data (2.3.0) http-parser (1.2.1) ffi-compiler (>= 1.0, < 2.0) http_accept_language (2.1.1) @@ -303,7 +303,7 @@ GEM jmespath (1.4.0) json (2.3.0) json-canonicalization (0.2.0) - json-ld (3.1.2) + json-ld (3.1.3) htmlentities (~> 4.3) json-canonicalization (~> 0.2) link_header (~> 0.0, >= 0.0.8) @@ -359,7 +359,7 @@ GEM nokogiri (~> 1.10) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) + mime-types-data (3.2020.0425) mimemagic (0.3.4) mini_mime (1.0.2) mini_portile2 (2.4.0) @@ -382,7 +382,7 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.10.5) + oj (3.10.6) omniauth (1.9.1) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) @@ -409,7 +409,7 @@ GEM parallel parser (2.7.1.1) ast (~> 2.4.0) - parslet (1.8.2) + parslet (2.0.0) pastel (0.7.3) equatable (~> 0.6) tty-color (~> 0.5) @@ -542,6 +542,8 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.9.2) + rspec_junit_formatter (0.4.1) + rspec-core (>= 2, < 4, != 2.12.0) rubocop (0.79.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) @@ -554,7 +556,7 @@ GEM rack (>= 1.1) rubocop (>= 0.72.0) ruby-progressbar (1.10.1) - ruby-saml (1.9.0) + ruby-saml (1.11.0) nokogiri (>= 1.5.10) rufus-scheduler (3.6.0) fugit (~> 1.1, >= 1.1.6) @@ -668,7 +670,7 @@ DEPENDENCIES active_record_query_trace (~> 1.7) addressable (~> 2.7) annotate (~> 3.1) - aws-sdk-s3 (~> 1.61) + aws-sdk-s3 (~> 1.63) better_errors (~> 2.6) binding_of_caller (~> 0.7) blurhash (~> 0.1) @@ -765,6 +767,7 @@ DEPENDENCIES rqrcode (~> 1.1) rspec-rails (~> 4.0) rspec-sidekiq (~> 3.0) + rspec_junit_formatter (~> 0.4) rubocop (~> 0.79) rubocop-rails (~> 2.5) ruby-progressbar (~> 1.10) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index ee48da177..52d09cff8 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -28,7 +28,7 @@ class AccountsController < ApplicationController end @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? - @statuses = filtered_status_page(params) + @statuses = filtered_status_page @statuses = cache_collection(@statuses, Status) @rss_url = rss_url @@ -141,12 +141,12 @@ class AccountsController < ApplicationController request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) end - def filtered_status_page(params) - if params[:min_id].present? - filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse - else - filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a - end + def filtered_status_page + filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id)) + end + + def params_slice(*keys) + params.slice(*keys).permit(*keys) end def restrict_fields_to diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index 910fefb1c..c1e7aa550 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController def set_size case params[:id] when 'featured' - @account.pinned_statuses.count + @size = @account.pinned_statuses.count else - raise ActiveRecord::RecordNotFound + not_found end end def scope_for_collection case params[:id] when 'featured' - return Status.none if @account.blocking?(signed_request_account) - - @account.pinned_statuses - else - raise ActiveRecord::RecordNotFound + # Because in public fetch mode we cache the response, there would be no + # benefit from performing the check below, since a blocked account or domain + # would likely be served the cache from the reverse proxy anyway + if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain))) + Status.none + else + @account.pinned_statuses + end end end diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 891756b7e..e25a4bc07 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController before_action :set_cache_headers def show - expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) + expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?)) render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end @@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController return unless page_requested? @statuses = @account.statuses.permitted_for(@account, signed_request_account) - @statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id]) + @statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id)) @statuses = cache_collection(@statuses, Status) end def page_requested? - params[:page] == 'true' + truthy_param?(:page) end def page_params diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index c62061555..43bf4e657 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub::RepliesController < ActivityPub::BaseController - include SignatureAuthentication + include SignatureVerification include Authorization include AccountOwnedConcern @@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController private + def pundit_user + signed_request_account + end + def set_status @status = @account.statuses.find(params[:status_id]) authorize @status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def set_replies - @replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses + @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) end @@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController type: :unordered, part_of: account_status_replies_url(@account, @status), next: next_page, - items: @replies.map { |status| status.local ? status : status.uri } + items: @replies.map { |status| status.local? ? status : status.uri } ) return page if page_requested? @@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController end def page_requested? - params[:page] == 'true' + truthy_param?(:page) + end + + def only_other_accounts? + truthy_param?(:only_other_accounts) end def next_page only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT) + account_status_replies_url( @account, @status, page: true, - min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id, + min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id, only_other_accounts: only_other_accounts ) end diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index e1d26106a..513b937ef 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -18,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController @poll = Poll.attached.find(params[:poll_id]) authorize @poll.status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def vote_params diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index 744baf7bb..6435e9f0d 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController @poll = Poll.attached.find(params[:id]) authorize @poll.status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def refresh_poll diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 1cbc92b93..d34b333eb 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController before_action -> { doorkeeper_authorize! :push } before_action :require_user! before_action :set_web_push_subscription + before_action :check_web_push_subscription, only: [:show, :update] def create @web_subscription&.destroy! @@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController end def show - raise ActiveRecord::RecordNotFound if @web_subscription.nil? - render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer end def update - raise ActiveRecord::RecordNotFound if @web_subscription.nil? - @web_subscription.update!(data: data_params) - render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer end @@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController @web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) end + def check_web_push_subscription + not_found if @web_subscription.nil? + end + def subscription_params params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) end def data_params return {} if params[:data].blank? + params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb index 43c7a525a..87071a2b9 100644 --- a/app/controllers/api/v1/statuses/mutes_controller.rb +++ b/app/controllers/api/v1/statuses/mutes_controller.rb @@ -28,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController @status = Status.find(params[:status_id]) authorize @status, :show? rescue Mastodon::NotPermittedError - # Reraise in order to get a 404 instead of a 403 error code - raise ActiveRecord::RecordNotFound + not_found end def set_conversation diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 29ae91762..b3edce676 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -68,7 +68,7 @@ class Api::V1::StatusesController < Api::BaseController @status = Status.find(params[:id]) authorize @status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def set_thread diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 05cf09c28..1d166d6e7 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -33,7 +33,7 @@ class MediaController < ApplicationController def verify_permitted_status! authorize @media_attachment.status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def check_playable diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb index e058d0ed5..51bb9bdea 100644 --- a/app/controllers/remote_interaction_controller.rb +++ b/app/controllers/remote_interaction_controller.rb @@ -42,7 +42,7 @@ class RemoteInteractionController < ApplicationController @status = Status.find(params[:id]) authorize @status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def set_body_classes diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 588063d01..a1b7f4320 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -49,7 +49,7 @@ class StatusesController < ApplicationController def embed use_pack 'embed' - return not_found if @status.hidden? + return not_found if @status.hidden? || @status.reblog? expires_in 180, public: true response.headers['X-Frame-Options'] = 'ALLOWALL' diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js index e02c91cc7..9fe03f90c 100644 --- a/app/javascript/core/settings.js +++ b/app/javascript/core/settings.js @@ -10,7 +10,7 @@ delegate(document, '#account_display_name', 'input', ({ target }) => { if (target.value) { name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); } else { - name.textContent = document.querySelector('#default_account_display_name').textContent; + name.textContent = name.textContent = target.dataset.default; } } }); diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index e5a567205..58febcf5b 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -99,15 +99,13 @@ function main() { delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.status__content__spoiler-link', 'click', function() { - const contentEl = this.parentNode.parentNode.querySelector('.e-content'); + const statusEl = this.parentNode.parentNode; - if (contentEl.style.display === 'block') { - contentEl.style.display = 'none'; - this.parentNode.style.marginBottom = 0; + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); } else { - contentEl.style.display = 'block'; - this.parentNode.style.marginBottom = null; + statusEl.dataset.spoiler = 'expanded'; this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); } @@ -115,8 +113,8 @@ function main() { }); [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { - const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content'); - const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); + const statusEl = spoilerLink.parentNode.parentNode; + const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); }); }); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 50840cacc..861827d33 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -42,7 +42,7 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); dispatch({ diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 31c02d735..4734e0f3f 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent { document.addEventListener('keydown', this.handleKeyDown, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem && this.props.openedViaKeyboard) { - this.focusedItem.focus(); + this.focusedItem.focus({ preventScroll: true }); } this.setState({ mounted: true }); } diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index 57588fe96..96028e042 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -100,7 +100,7 @@ class PrivacyDropdownMenu extends React.PureComponent { componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); - if (this.focusedItem) this.focusedItem.focus(); + if (this.focusedItem) this.focusedItem.focus({ preventScroll: true }); this.setState({ mounted: true }); } diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 2554c008d..53dec9585 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -25,7 +25,7 @@ const importStatuses = (state, statuses) => const deleteStatus = (state, id, references) => { references.forEach(ref => { - state = deleteStatus(state, ref[0], []); + state = deleteStatus(state, ref, []); }); return state.delete(id); diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 63b76773d..9156db021 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -89,7 +89,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => { })); }; -const deleteStatus = (state, id, accountId, references, exclude_account = null) => { +const deleteStatus = (state, id, references, exclude_account = null) => { state.keySeq().forEach(timeline => { if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) { const helper = list => list.filterNot(item => item === id); @@ -99,7 +99,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null) // Remove reblogs of deleted status references.forEach(ref => { - state = deleteStatus(state, ref[0], ref[1], [], exclude_account); + state = deleteStatus(state, ref, [], exclude_account); }); return state; @@ -117,8 +117,8 @@ const filterTimelines = (state, relationship, statuses) => { return; } - references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]); - state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id); + references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')); + state = deleteStatus(state, status.get('id'), references, relationship.id); }); return state; @@ -150,7 +150,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); + return deleteStatus(state, action.id, action.references, action.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case ACCOUNT_BLOCK_SUCCESS: diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 5b699e767..3d190d2da 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -103,15 +103,13 @@ function main() { delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.status__content__spoiler-link', 'click', function() { - const contentEl = this.parentNode.parentNode.querySelector('.e-content'); + const statusEl = this.parentNode.parentNode; - if (contentEl.style.display === 'block') { - contentEl.style.display = 'none'; - this.parentNode.style.marginBottom = 0; + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); } else { - contentEl.style.display = 'block'; - this.parentNode.style.marginBottom = null; + statusEl.dataset.spoiler = 'expanded'; this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); } @@ -119,8 +117,8 @@ function main() { }); [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { - const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content'); - const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); + const statusEl = spoilerLink.parentNode.parentNode; + const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); }); }); diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index cf16b54ac..711f34965 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -757,8 +757,13 @@ $small-breakpoint: 960px; } } + &__counters__wrapper { + display: flex; + } + &__counter { padding: 10px; + width: 50%; strong { font-family: $font-display, sans-serif; diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 7bff2daa1..78dea92b9 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -583,6 +583,18 @@ body, } } +.special-action-button, +.back-link { + text-align: right; + flex: 1 1 auto; +} + +.action-buttons { + display: flex; + overflow: hidden; + justify-content: space-between; +} + .spacer { flex: 1 1 auto; } @@ -920,3 +932,11 @@ a.name-tag, } } } + +.account-badges { + margin: -2px 0; +} + +.dashboard__counters.admin-account-counters { + margin-top: 10px; +} diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 2b10b5ad3..a5dbe75fb 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -229,3 +229,19 @@ button { } } } + +.logo-resources { + display: none; +} + +// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements, +// to set the z-index to a high value, which messes with modals and dropdowns. +// Blocked elements can in theory only be media and frames/embeds, so they +// should only appear in statuses, under divs and articles. +body, +div, +article { + .__ns__pop2top { + z-index: unset !important; + } +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index c7835b878..6c33b709d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1362,6 +1362,12 @@ a .account__avatar { &-base { @include avatar-radius; @include avatar-size(36px); + + img { + @include avatar-radius; + width: 100%; + height: 100%; + } } &-overlay { @@ -1372,6 +1378,12 @@ a .account__avatar { bottom: 0; right: 0; z-index: 1; + + img { + @include avatar-radius; + width: 100%; + height: 100%; + } } } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index c9ad68f94..0e5b00e8f 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -142,6 +142,10 @@ code { } } + .otp-hint { + margin-bottom: 25px; + } + .card { margin-bottom: 15px; } @@ -285,6 +289,14 @@ code { margin-bottom: 25px; } } + + .fields-group.invited-by { + margin-bottom: 30px; + + .hint { + text-align: center; + } + } } .input.radio_buttons .radio label { @@ -635,6 +647,15 @@ code { @media screen and (max-width: 740px) and (min-width: 441px) { margin-top: 40px; } + + &.translation-prompt { + text-align: unset; + color: unset; + + a { + text-decoration: underline; + } + } } .form-footer { diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index 1ecc8434d..ad7088982 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -19,6 +19,36 @@ } } + progress { + border: 0; + display: block; + width: 100%; + height: 5px; + appearance: none; + background: transparent; + + &::-webkit-progress-bar { + background: transparent; + } + + // Those rules need to be entirely separate or they won't work, hence the + // duplication + &::-moz-progress-bar { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + + &::-ms-fill { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + + &::-webkit-progress-value { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + } + &__option { position: relative; display: flex; diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss index 19ce0ab8f..0b7be7afd 100644 --- a/app/javascript/styles/mastodon/statuses.scss +++ b/app/javascript/styles/mastodon/statuses.scss @@ -128,6 +128,16 @@ .embed, .public-layout { + .status__content[data-spoiler=folded] { + .e-content { + display: none; + } + + p:first-child { + margin-bottom: 0; + } + } + .detailed-status { padding: 15px; } @@ -159,5 +169,12 @@ .video-player { margin-top: 10px; } + + &__action-bar-button { + font-size: 18px; + width: 23.1429px; + height: 23.1429px; + line-height: 23.15px; + } } } diff --git a/app/models/account.rb b/app/models/account.rb index e56db3126..5038d4768 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -3,50 +3,52 @@ # # Table name: accounts # -# id :bigint(8) not null, primary key -# username :string default(""), not null -# domain :string -# secret :string default(""), not null -# private_key :text -# public_key :text default(""), not null -# remote_url :string default(""), not null -# salmon_url :string default(""), not null -# hub_url :string default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# note :text default(""), not null -# display_name :string default(""), not null -# uri :string default(""), not null -# url :string -# avatar_file_name :string -# avatar_content_type :string -# avatar_file_size :integer -# avatar_updated_at :datetime -# header_file_name :string -# header_content_type :string -# header_file_size :integer -# header_updated_at :datetime -# avatar_remote_url :string -# subscription_expires_at :datetime -# locked :boolean default(FALSE), not null -# header_remote_url :string default(""), not null -# last_webfingered_at :datetime -# inbox_url :string default(""), not null -# outbox_url :string default(""), not null -# shared_inbox_url :string default(""), not null -# followers_url :string default(""), not null -# protocol :integer default("ostatus"), not null -# memorial :boolean default(FALSE), not null -# moved_to_account_id :bigint(8) -# featured_collection_url :string -# fields :jsonb -# actor_type :string -# discoverable :boolean -# also_known_as :string is an Array -# silenced_at :datetime -# suspended_at :datetime -# trust_level :integer -# hide_collections :boolean +# id :bigint(8) not null, primary key +# username :string default(""), not null +# domain :string +# secret :string default(""), not null +# private_key :text +# public_key :text default(""), not null +# remote_url :string default(""), not null +# salmon_url :string default(""), not null +# hub_url :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# note :text default(""), not null +# display_name :string default(""), not null +# uri :string default(""), not null +# url :string +# avatar_file_name :string +# avatar_content_type :string +# avatar_file_size :integer +# avatar_updated_at :datetime +# header_file_name :string +# header_content_type :string +# header_file_size :integer +# header_updated_at :datetime +# avatar_remote_url :string +# subscription_expires_at :datetime +# locked :boolean default(FALSE), not null +# header_remote_url :string default(""), not null +# last_webfingered_at :datetime +# inbox_url :string default(""), not null +# outbox_url :string default(""), not null +# shared_inbox_url :string default(""), not null +# followers_url :string default(""), not null +# protocol :integer default("ostatus"), not null +# memorial :boolean default(FALSE), not null +# moved_to_account_id :bigint(8) +# featured_collection_url :string +# fields :jsonb +# actor_type :string +# discoverable :boolean +# also_known_as :string is an Array +# silenced_at :datetime +# suspended_at :datetime +# trust_level :integer +# hide_collections :boolean +# avatar_storage_schema_version :integer +# header_storage_schema_version :integer # class Account < ApplicationRecord diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb index 960784222..736da6c1d 100644 --- a/app/models/concerns/omniauthable.rb +++ b/app/models/concerns/omniauthable.rb @@ -82,7 +82,7 @@ module Omniauthable username = starting_username i = 0 - while Account.exists?(username: username) + while Account.exists?(username: username, domain: nil) i += 1 username = "#{starting_username}_#{i}" end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index d177cf281..7cb03b819 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -3,20 +3,21 @@ # # Table name: custom_emojis # -# id :bigint(8) not null, primary key -# shortcode :string default(""), not null -# domain :string -# image_file_name :string -# image_content_type :string -# image_file_size :integer -# image_updated_at :datetime -# created_at :datetime not null -# updated_at :datetime not null -# disabled :boolean default(FALSE), not null -# uri :string -# image_remote_url :string -# visible_in_picker :boolean default(TRUE), not null -# category_id :bigint(8) +# id :bigint(8) not null, primary key +# shortcode :string default(""), not null +# domain :string +# image_file_name :string +# image_content_type :string +# image_file_size :integer +# image_updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# disabled :boolean default(FALSE), not null +# uri :string +# image_remote_url :string +# visible_in_picker :boolean default(TRUE), not null +# category_id :bigint(8) +# image_storage_schema_version :integer # class CustomEmoji < ApplicationRecord diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 40624c73c..f789bdc55 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -3,23 +3,24 @@ # # Table name: media_attachments # -# id :bigint(8) not null, primary key -# status_id :bigint(8) -# file_file_name :string -# file_content_type :string -# file_file_size :integer -# file_updated_at :datetime -# remote_url :string default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# shortcode :string -# type :integer default("image"), not null -# file_meta :json -# account_id :bigint(8) -# description :text -# scheduled_status_id :bigint(8) -# blurhash :string -# processing :integer +# id :bigint(8) not null, primary key +# status_id :bigint(8) +# file_file_name :string +# file_content_type :string +# file_file_size :integer +# file_updated_at :datetime +# remote_url :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# shortcode :string +# type :integer default("image"), not null +# file_meta :json +# account_id :bigint(8) +# description :text +# scheduled_status_id :bigint(8) +# blurhash :string +# processing :integer +# file_storage_schema_version :integer # class MediaAttachment < ApplicationRecord diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 4e89fbf85..2802f4667 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -3,25 +3,26 @@ # # Table name: preview_cards # -# id :bigint(8) not null, primary key -# url :string default(""), not null -# title :string default(""), not null -# description :string default(""), not null -# image_file_name :string -# image_content_type :string -# image_file_size :integer -# image_updated_at :datetime -# type :integer default("link"), not null -# html :text default(""), not null -# author_name :string default(""), not null -# author_url :string default(""), not null -# provider_name :string default(""), not null -# provider_url :string default(""), not null -# width :integer default(0), not null -# height :integer default(0), not null -# created_at :datetime not null -# updated_at :datetime not null -# embed_url :string default(""), not null +# id :bigint(8) not null, primary key +# url :string default(""), not null +# title :string default(""), not null +# description :string default(""), not null +# image_file_name :string +# image_content_type :string +# image_file_size :integer +# image_updated_at :datetime +# type :integer default("link"), not null +# html :text default(""), not null +# author_name :string default(""), not null +# author_url :string default(""), not null +# provider_name :string default(""), not null +# provider_url :string default(""), not null +# width :integer default(0), not null +# height :integer default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# embed_url :string default(""), not null +# image_storage_schema_version :integer # class PreviewCard < ApplicationRecord @@ -47,6 +48,10 @@ class PreviewCard < ApplicationRecord before_save :extract_dimensions, if: :link? + def local? + false + end + def missing_image? width.present? && height.present? && image_file_name.blank? end diff --git a/app/models/status.rb b/app/models/status.rb index 31e77770d..34fa00912 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -206,12 +206,8 @@ class Status < ApplicationRecord def title if destroyed? "#{account.acct} deleted status" - elsif reblog? - preview = sensitive ? '<sensitive>' : text.slice(0, 10).split("\n")[0] - "#{account.acct} shared #{reblog.account.acct}'s: #{preview}" else - preview = sensitive ? '<sensitive>' : text.slice(0, 20).split("\n")[0] - "#{account.acct}: #{preview}" + reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}" end end @@ -404,7 +400,7 @@ class Status < ApplicationRecord if account.nil? where(visibility: visibility).not_local_only - elsif target_account.blocking?(account) # get rid of blocked peeps + elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps none elsif account.id == target_account.id # author can see own stuff all diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index e913f0c64..54e7c450c 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer attributes :uri, :title, :short_description, :description, :email, :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits, - :languages, :registrations, :approval_required + :languages, :registrations, :approval_required, :invites_enabled has_one :contact_account, serializer: REST::AccountSerializer @@ -76,6 +76,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer Setting.registrations_mode == 'approved' end + def invites_enabled + Setting.min_invite_role == 'user' + end + private def instance_presenter diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb index 880cdde92..6c0093cd4 100644 --- a/app/services/fetch_resource_service.rb +++ b/app/services/fetch_resource_service.rb @@ -25,7 +25,18 @@ class FetchResourceService < BaseService end def perform_request(&block) - Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block) + Request.new(:get, @url).tap do |request| + request.add_headers('Accept' => ACCEPT_HEADER) + + # In a real setting we want to sign all outgoing requests, + # in case the remote server has secure mode enabled and requires + # authentication on all resources. However, during development, + # sending request signatures with an inaccessible host is useless + # and prevents even public resources from being fetched, so + # don't do it + + request.on_behalf_of(Account.representative) unless Rails.env.development? + end.perform(&block) end def process_response(response, terminal = false) diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index e0ec98ec9..07e06100a 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -68,11 +68,11 @@ .hero-widget__footer__column %h4= t 'about.server_stats' - %div{ style: 'display: flex' } - .hero-widget__counter{ style: 'width: 50%' } + .hero-widget__counters__wrapper + .hero-widget__counter %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true %span= t 'about.user_count_after', count: @instance_presenter.user_count - .hero-widget__counter{ style: 'width: 50%' } + .hero-widget__counter %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true %span = t 'about.active_count_after' diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml index a82f277b1..4f71b062d 100644 --- a/app/views/accounts/_moved.html.haml +++ b/app/views/accounts/_moved.html.haml @@ -9,8 +9,10 @@ = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do .detailed-status__display-avatar .account__avatar-overlay - .account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" } - .account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" } + .account__avatar-overlay-base + = image_tag moved_to_account.avatar_static_url + .account__avatar-overlay-overlay + = image_tag account.avatar_static_url %span.display-name %bdi diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 44b10af6e..c9bd8c686 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -2,7 +2,7 @@ %td = admin_account_link_to(account) %td - %div{ style: 'margin: -2px 0' }= account_badge(account, all: true) + %div.account-badges= account_badge(account, all: true) %td - if account.user_current_sign_in_ip %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 408f94eed..e6461aad0 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -31,7 +31,7 @@ %div .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) -.dashboard__counters{ style: 'margin-top: 10px' } +.dashboard__counters.admin-account-counters %div = link_to admin_account_statuses_path(@account.id) do .dashboard__counters__num= number_with_delimiter @account.statuses_count @@ -178,18 +178,8 @@ = @account.shared_inbox_url = fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times' - %div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - - if @account.local? - = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) - - if @account.user&.otp_required_for_login? - = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) - - if !@account.memorial? && @account.user_approved? - = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) - - else - = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) - - %div{ style: 'float: left' } + %div.action-buttons + %div - if @account.local? && @account.user_approved? = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account) - if @account.silenced? @@ -216,6 +206,16 @@ - else = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' + %div + - if @account.local? + = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) + - if @account.user&.otp_required_for_login? + = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) + - if !@account.memorial? && @account.user_approved? + = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) + - else + = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) + %hr.spacer/ - unless @warnings.empty? diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml index 0b299acc5..bd67eb4fc 100644 --- a/app/views/admin/instances/index.html.haml +++ b/app/views/admin/instances/index.html.haml @@ -10,7 +10,7 @@ - unless whitelist_mode? %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' - %div{ style: 'flex: 1 1 auto; text-align: right' } + %div.special-action-button - if whitelist_mode? = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button' - else diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index 49a666a5a..92e14c0df 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -45,11 +45,11 @@ %hr.spacer/ -%div{ style: 'overflow: hidden' } - %div{ style: 'float: left' } +%div.action-buttons + %div = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button' - %div{ style: 'float: right' } + %div - if @domain_allow = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } - elsif @domain_block diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml index 171976e33..8101d7f99 100644 --- a/app/views/admin/pending_accounts/index.html.haml +++ b/app/views/admin/pending_accounts/index.html.haml @@ -22,9 +22,9 @@ %hr.spacer/ -%div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' - +%div.action-buttons %div = link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + + %div + = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml index 3afaff615..907477f24 100644 --- a/app/views/admin/relationships/index.html.haml +++ b/app/views/admin/relationships/index.html.haml @@ -17,7 +17,7 @@ %li= filter_link_to t('admin.accounts.location.local'), location: 'local' %li= filter_link_to t('admin.accounts.location.remote'), location: 'remote' - .back-link{ style: 'flex: 1 1 auto; text-align: right' } + .back-link = link_to admin_account_path(@account.id) do = fa_icon 'chevron-left fw' = t('admin.statuses.back_to_account') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index b12ea4270..4ecc8dc93 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -65,9 +65,11 @@ %hr.spacer -%div{ style: 'overflow: hidden; margin-bottom: 20px; clear: both' } +%div.action-buttons + %div + - if @report.unresolved? - %div{ style: 'float: right' } + %div - if @report.target_account.local? = link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button' = link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive' diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index 55926f3b3..5414d69d5 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -9,7 +9,7 @@ %ul %li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected' %li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected' - .back-link{ style: 'flex: 1 1 auto; text-align: right' } + .back-link = link_to admin_account_path(@account.id) do = fa_icon 'chevron-left fw' = t('admin.statuses.back_to_account') diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml index a7a392272..e2470198d 100644 --- a/app/views/admin/statuses/show.html.haml +++ b/app/views/admin/statuses/show.html.haml @@ -4,7 +4,7 @@ = "@#{@account.acct}" .filters - .back-link{ style: 'flex: 1 1 auto; text-align: right' } + .back-link = link_to admin_account_path(@account.id) do %i.fa.fa-chevron-left.fa-fw = t('admin.statuses.back_to_account') diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml index d20ed80f8..e64802275 100644 --- a/app/views/admin/tags/index.html.haml +++ b/app/views/admin/tags/index.html.haml @@ -68,9 +68,9 @@ - if params[:pending_review] == '1' || params[:unreviewed] == '1' %hr.spacer/ - %div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' - + %div.action-buttons %div = link_to t('admin.accounts.approve_all'), approve_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + + %div + = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml index 808dce514..e7ecfecd9 100644 --- a/app/views/application/_card.html.haml +++ b/app/views/application/_card.html.haml @@ -9,7 +9,6 @@ = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' .display-name - %span{ id: "default_account_display_name", style: "display: none" }= account.username %bdi %strong.emojify.p-name= display_name(account, custom_emojify: true) %span diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index bcd66fb8a..457bc1d23 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -8,8 +8,8 @@ = render 'shared/error_messages', object: resource - if @invite.present? && @invite.autofollow? - .fields-group{ style: 'margin-bottom: 30px' } - %p.hint{ style: 'text-align: center' }= t('invites.invited_by') + .fields-group.invited-by + %p.hint= t('invites.invited_by') = render 'application/card', account: @invite.user.account = f.simple_fields_for :account do |ff| diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml index 4e6bbd7a9..b2e36f6bc 100644 --- a/app/views/auth/sessions/two_factor.html.haml +++ b/app/views/auth/sessions/two_factor.html.haml @@ -2,7 +2,7 @@ = t('auth.login') = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| - %p.hint{ style: 'margin-bottom: 25px' }= t('simple_form.hints.sessions.otp') + %p.hint.otp-hint= t('simple_form.hints.sessions.otp') .fields-group = f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, autofocus: true diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index ecf12b649..1170332ff 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -28,7 +28,6 @@ = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' .display-name - %span{ id: "default_account_display_name", style: "display: none" }= account.username %bdi %strong.emojify.p-name= display_name(account, custom_emojify: true) %span= acct(account) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2be9427c5..99ab3729e 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -40,6 +40,6 @@ %body{ class: body_classes } = content_for?(:content) ? yield(:content) : yield - %div{ style: 'display: none'} + .logo-resources = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') = render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg') diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml index 6695b12dd..75441b452 100644 --- a/app/views/layouts/embedded.html.haml +++ b/app/views/layouts/embedded.html.haml @@ -23,5 +23,5 @@ %body.embed = yield - %div{ style: 'display: none'} + .logo-resources = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml index 063089a7f..e32bd49ec 100644 --- a/app/views/public_timelines/show.html.haml +++ b/app/views/public_timelines/show.html.haml @@ -12,5 +12,5 @@ - else %p= t('about.browse_local_posts') -#mastodon-timeline{ data: { props: Oj.dump(default_props) }} +#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }} #modal-container diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 5453177fd..5fc865814 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -9,8 +9,8 @@ = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, selected: I18n.locale, hint: false - unless I18n.locale == :en - .flash-message{ style: "text-align: unset; color: unset" } - #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener", style: "text-decoration: underline")} + .flash-message.translation-prompt + #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener")} %h4= t 'appearance.advanced_web_interface' diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 841c01fd7..6061e9cfd 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -9,7 +9,7 @@ .fields-row .fields-row__column.fields-group.fields-row__column-6 - = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH }, hint: false + = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH, data: { default: @account.username } }, hint: false = f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false .fields-row diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 021390e47..544b92330 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -15,12 +15,12 @@ = account_action_button(status.account) - .status__content.emojify< + .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? - %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< + %p< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } + .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' } = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - if status.preloadable_poll = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do diff --git a/app/views/statuses/_poll.html.haml b/app/views/statuses/_poll.html.haml index de5357e6d..64e62e97c 100644 --- a/app/views/statuses/_poll.html.haml +++ b/app/views/statuses/_poll.html.haml @@ -10,13 +10,15 @@ - percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0 %label.poll__option>< %span.poll__number>< - - if own_votes.include?(index) - %i.poll__voted__mark.fa.fa-check = "#{percent.round}%" %span.poll__option__text = Formatter.instance.format_poll_option(status, option, autoplay: autoplay) + - if own_votes.include?(index) + %span.poll__voted + %i.poll__voted__mark.fa.fa-check - %span.poll__chart{ style: "width: #{percent}%" } + %progress{ max: 100, value: percent < 1 ? 1 : percent, 'aria-hidden': 'true' } + %span.poll__chart - else %label.poll__option>< %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}>< diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index 8a418a1d5..f959056cd 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -19,12 +19,12 @@ %span.display-name__account = acct(status.account) = fa_icon('lock') if status.account.locked? - .status__content.emojify< + .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? - %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< + %p< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< + .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }< = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - if status.preloadable_poll = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do @@ -51,18 +51,18 @@ .status__action-bar .status__action-bar__counter - = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button' do - if status.in_reply_to_id.nil? = fa_icon 'reply fw' - else = fa_icon 'reply-all fw' .status__action-bar__counter__label= obscured_counter status.replies_count - = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do - if status.distributable? = fa_icon 'retweet fw' - elsif status.private_visibility? || status.limited_visibility? = fa_icon 'lock fw' - else = fa_icon 'envelope fw' - = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do = fa_icon 'star fw' diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index 8909678d6..0dd7f8cf8 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -10,9 +10,25 @@ Paperclip.interpolates :filename do |attachment, style| end end +Paperclip.interpolates :prefix_path do |attachment, style| + if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local? + 'cache' + File::SEPARATOR + else + '' + end +end + +Paperclip.interpolates :prefix_url do |attachment, style| + if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local? + 'cache/' + else + '' + end +end + Paperclip::Attachment.default_options.merge!( use_timestamp: false, - path: ':class/:attachment/:id_partition/:style/:filename', + path: ':prefix_url:class/:attachment/:id_partition/:style/:filename', storage: :fog ) @@ -91,7 +107,7 @@ else Paperclip::Attachment.default_options.merge!( storage: :filesystem, use_timestamp: true, - path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':class', ':attachment', ':id_partition', ':style', ':filename'), - url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:class/:attachment/:id_partition/:style/:filename', + path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':prefix_path:class', ':attachment', ':id_partition', ':style', ':filename'), + url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename', ) end diff --git a/db/migrate/20200417125749_add_storage_schema_version.rb b/db/migrate/20200417125749_add_storage_schema_version.rb new file mode 100644 index 000000000..7438f97ba --- /dev/null +++ b/db/migrate/20200417125749_add_storage_schema_version.rb @@ -0,0 +1,9 @@ +class AddStorageSchemaVersion < ActiveRecord::Migration[5.2] + def change + add_column :preview_cards, :image_storage_schema_version, :integer + add_column :accounts, :avatar_storage_schema_version, :integer + add_column :accounts, :header_storage_schema_version, :integer + add_column :media_attachments, :file_storage_schema_version, :integer + add_column :custom_emojis, :image_storage_schema_version, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 36e9017b5..2b27a1877 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: 2020_04_07_202420) do +ActiveRecord::Schema.define(version: 2020_04_17_125749) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -172,6 +172,8 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do t.datetime "suspended_at" t.integer "trust_level" t.boolean "hide_collections" + t.integer "avatar_storage_schema_version" + t.integer "header_storage_schema_version" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" @@ -299,6 +301,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do t.string "image_remote_url" t.boolean "visible_in_picker", default: true, null: false t.bigint "category_id" + t.integer "image_storage_schema_version" t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true end @@ -465,6 +468,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do t.bigint "scheduled_status_id" t.string "blurhash" t.integer "processing" + t.integer "file_storage_schema_version" t.index ["account_id"], name: "index_media_attachments_on_account_id" t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id" t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true @@ -605,6 +609,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "embed_url", default: "", null: false + t.integer "image_storage_schema_version" t.index ["url"], name: "index_preview_cards_on_url", unique: true end diff --git a/ide-helper.js b/ide-helper.js new file mode 100644 index 000000000..9e645cb0e --- /dev/null +++ b/ide-helper.js @@ -0,0 +1,12 @@ +/* global path */ +/* +Preferences | Languages & Frameworks | JavaScript | Webpack | webpack configuration file +jetbrains://WebStorm/settings?name=Languages+%26+Frameworks--JavaScript--Webpack +*/ +module.exports = { + resolve: { + alias: { + 'mastodon': path.resolve(__dirname, 'app/javascript/mastodon'), + }, + }, +}; diff --git a/lib/cli.rb b/lib/cli.rb index 19cc5d6b5..313a36a3d 100644 --- a/lib/cli.rb +++ b/lib/cli.rb @@ -11,6 +11,7 @@ require_relative 'mastodon/statuses_cli' require_relative 'mastodon/domains_cli' require_relative 'mastodon/preview_cards_cli' require_relative 'mastodon/cache_cli' +require_relative 'mastodon/upgrade_cli' require_relative 'mastodon/version' module Mastodon @@ -49,6 +50,9 @@ module Mastodon desc 'cache SUBCOMMAND ...ARGS', 'Manage cache' subcommand 'cache', Mastodon::CacheCLI + desc 'upgrade SUBCOMMAND ...ARGS', 'Various version upgrade utilities' + subcommand 'upgrade', Mastodon::UpgradeCLI + option :dry_run, type: :boolean desc 'self-destruct', 'Erase the server from the federation' long_desc <<~LONG_DESC diff --git a/lib/mastodon/cli_helper.rb b/lib/mastodon/cli_helper.rb index ec4d9a81e..4a20fa8d6 100644 --- a/lib/mastodon/cli_helper.rb +++ b/lib/mastodon/cli_helper.rb @@ -10,6 +10,10 @@ Paperclip.options[:log] = false module Mastodon module CLIHelper + def dry_run? + options[:dry_run] + end + def create_progress_bar(total = nil) ProgressBar.create(total: total, format: '%c/%u |%b%i| %e') end diff --git a/lib/mastodon/emoji_cli.rb b/lib/mastodon/emoji_cli.rb index dbaf12018..da8fd6a0d 100644 --- a/lib/mastodon/emoji_cli.rb +++ b/lib/mastodon/emoji_cli.rb @@ -23,7 +23,7 @@ module Mastodon Existing emoji will be skipped unless the --overwrite option is provided, in which case they will be overwritten. - You can specifiy a --category under which the emojis will be + You can specify a --category under which the emojis will be grouped together. With the --prefix option, a prefix can be added to all @@ -72,6 +72,48 @@ module Mastodon say("Imported #{imported}, skipped #{skipped}, failed to import #{failed}", color(imported, skipped, failed)) end + option :category + option :overwrite, type: :boolean + desc 'export PATH', 'Export emoji to a TAR GZIP archive at PATH' + long_desc <<-LONG_DESC + Exports custom emoji to 'export.tar.gz' at PATH. + + The --category option dumps only the specified category. + If this option is not specified, all emoji will be exported. + + The --overwrite option will overwrite an existing archive. + LONG_DESC + def export(path) + exported = 0 + category = CustomEmojiCategory.find_by(name: options[:category]) + export_file_name = File.join(path, 'export.tar.gz') + + if File.file?(export_file_name) && !options[:overwrite] + say("Archive already exists! Use '--overwrite' to overwrite it!") + exit 1 + end + if category.nil? && options[:category] + say("Unable to find category '#{options[:category]}'!") + exit 1 + end + + File.open(export_file_name, 'wb') do |file| + Zlib::GzipWriter.wrap(file) do |gzip| + Gem::Package::TarWriter.new(gzip) do |tar| + scope = !options[:category] || category.nil? ? CustomEmoji.local : category.emojis + scope.find_each do |emoji| + say("Adding '#{emoji.shortcode}'...") + tar.add_file_simple(emoji.shortcode + File.extname(emoji.image_file_name), 0o644, emoji.image_file_size) do |io| + io.write Paperclip.io_adapters.for(emoji.image).read + exported += 1 + end + end + end + end + end + say("Exported #{exported}") + end + option :remote_only, type: :boolean desc 'purge', 'Remove all custom emoji' long_desc <<-LONG_DESC diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index 0f211f272..424d65a5f 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -85,7 +85,9 @@ module Mastodon record_map = preload_records_from_mixed_objects(objects) objects.each do |object| - path_segments = object.key.split('/') + path_segments = object.key.split('/') + path_segments.delete('cache') + model_name = path_segments.first.classify attachment_name = path_segments[1].singularize record_id = path_segments[2..-2].join.to_i @@ -120,8 +122,11 @@ module Mastodon Find.find(File.join(*[root_path, prefix].compact)) do |path| next if File.directory?(path) - key = path.gsub("#{root_path}#{File::SEPARATOR}", '') - path_segments = key.split(File::SEPARATOR) + key = path.gsub("#{root_path}#{File::SEPARATOR}", '') + + path_segments = key.split(File::SEPARATOR) + path_segments.delete('cache') + model_name = path_segments.first.classify record_id = path_segments[2..-2].join.to_i attachment_name = path_segments[1].singularize @@ -229,10 +234,13 @@ module Mastodon desc 'lookup URL', 'Lookup where media is displayed by passing a media URL' def lookup(url) - path = Addressable::URI.parse(url).path + path = Addressable::URI.parse(url).path + path_segments = path.split('/')[2..-1] - model_name = path_segments.first.classify - record_id = path_segments[2..-2].join.to_i + path_segments.delete('cache') + + model_name = path_segments.first.classify + record_id = path_segments[2..-2].join.to_i unless PRELOAD_MODEL_WHITELIST.include?(model_name) say("Cannot find corresponding model: #{model_name}", :red) @@ -276,7 +284,9 @@ module Mastodon preload_map = Hash.new { |hash, key| hash[key] = [] } objects.map do |object| - segments = object.key.split('/') + segments = object.key.split('/') + segments.delete('cache') + model_name = segments.first.classify record_id = segments[2..-2].join.to_i diff --git a/lib/mastodon/upgrade_cli.rb b/lib/mastodon/upgrade_cli.rb new file mode 100644 index 000000000..74d13f62d --- /dev/null +++ b/lib/mastodon/upgrade_cli.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require_relative '../../config/boot' +require_relative '../../config/environment' +require_relative 'cli_helper' + +module Mastodon + class UpgradeCLI < Thor + include CLIHelper + + def self.exit_on_failure? + true + end + + CURRENT_STORAGE_SCHEMA_VERSION = 1 + + option :dry_run, type: :boolean, default: false + option :verbose, type: :boolean, default: false, aliases: [:v] + desc 'storage-schema', 'Upgrade storage schema of various file attachments to the latest version' + long_desc <<~LONG_DESC + Iterates over every file attachment of every record and, if its storage schema is outdated, performs the + necessary upgrade to the latest one. In practice this means e.g. moving files to different directories. + + Will most likely take a long time. + LONG_DESC + def storage_schema + progress = create_progress_bar(nil) + dry_run = dry_run? ? ' (DRY RUN)' : '' + records = 0 + + klasses = [ + Account, + CustomEmoji, + MediaAttachment, + PreviewCard, + ] + + klasses.each do |klass| + attachment_names = klass.attachment_definitions.keys + + klass.find_each do |record| + attachment_names.each do |attachment_name| + attachment = record.public_send(attachment_name) + + next if attachment.blank? || attachment.storage_schema_version >= CURRENT_STORAGE_SCHEMA_VERSION + + attachment.styles.each_key do |style| + case Paperclip::Attachment.default_options[:storage] + when :s3 + upgrade_storage_s3(progress, attachment, style) + when :fog + upgrade_storage_fog(progress, attachment, style) + when :filesystem + upgrade_storage_filesystem(progress, attachment, style) + end + + progress.increment + end + + attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) + end + + if record.changed? + record.save unless dry_run? + records += 1 + end + end + end + + progress.total = progress.progress + progress.finish + + say("Upgraded storage schema of #{records} records#{dry_run}", :green, true) + end + + private + + def upgrade_storage_s3(progress, attachment, style) + previous_storage_schema_version = attachment.storage_schema_version + object = attachment.s3_object(style) + + attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) + + upgraded_path = attachment.path(style) + + if upgraded_path != object.key && object.exists? + progress.log("Moving #{object.key} to #{upgraded_path}") if options[:verbose] + + begin + object.move_to(upgraded_path) unless dry_run? + rescue => e + progress.log(pastel.red("Error processing #{object.key}: #{e}")) + end + end + + # Because we move files style-by-style, it's important to restore + # previous version at the end. The upgrade will be recorded after + # all styles are updated + attachment.instance_write(:storage_schema_version, previous_storage_schema_version) + end + + def upgrade_storage_fog(_progress, _attachment, _style) + say('The fog storage driver is not supported for this operation at this time', :red) + exit(1) + end + + def upgrade_storage_filesystem(progress, attachment, style) + previous_storage_schema_version = attachment.storage_schema_version + previous_path = attachment.path(style) + + attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) + + upgraded_path = attachment.path(style) + + if upgraded_path != previous_path && File.exist?(previous_path) + progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose] + + begin + unless dry_run? + FileUtils.mkdir_p(File.dirname(upgraded_path)) + FileUtils.mv(previous_path, upgraded_path) + + begin + FileUtils.rmdir(previous_path, parents: true) + rescue Errno::ENOTEMPTY + # OK + end + end + rescue => e + progress.log(pastel.red("Error processing #{previous_path}: #{e}")) + + unless dry_run? + begin + FileUtils.rmdir(upgraded_path, parents: true) + rescue Errno::ENOTEMPTY + # OK + end + end + end + end + + # Because we move files style-by-style, it's important to restore + # previous version at the end. The upgrade will be recorded after + # all styles are updated + attachment.instance_write(:storage_schema_version, previous_storage_schema_version) + end + end +end diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb index ce5780557..f3e51dbd3 100644 --- a/lib/paperclip/attachment_extensions.rb +++ b/lib/paperclip/attachment_extensions.rb @@ -14,6 +14,15 @@ module Paperclip end end + def storage_schema_version + instance_read(:storage_schema_version) || 0 + end + + def assign_attributes + super + instance_write(:storage_schema_version, 1) + end + def variant?(other_filename) return true if original_filename == other_filename return false if original_filename.nil? diff --git a/spec/controllers/activitypub/collections_controller_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb index 34114cc85..56be49be3 100644 --- a/spec/controllers/activitypub/collections_controller_spec.rb +++ b/spec/controllers/activitypub/collections_controller_spec.rb @@ -3,21 +3,133 @@ require 'rails_helper' RSpec.describe ActivityPub::CollectionsController, type: :controller do - describe 'POST #show' do - let(:account) { Fabricate(:account) } + let!(:account) { Fabricate(:account) } + let(:remote_account) { nil } - context 'id is "featured"' do - it 'returns 200 with "application/activity+json"' do - post :show, params: { id: 'featured', account_username: account.username } + before do + allow(controller).to receive(:signed_request_account).and_return(remote_account) - expect(response).to have_http_status(200) - expect(response.content_type).to eq 'application/activity+json' + Fabricate(:status_pin, account: account) + Fabricate(:status_pin, account: account) + Fabricate(:status, account: account, visibility: :private) + end + + describe 'GET #show' do + context 'when id is "featured"' do + context 'without signature' do + let(:remote_account) { nil } + + before do + get :show, params: { id: 'featured', account_username: account.username } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'returns orderedItems with pinned statuses' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 2 + end + end + + context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + + context do + before do + get :show, params: { id: 'featured', account_username: account.username } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'returns orderedItems with pinned statuses' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 2 + end + end + + context 'in authorized fetch mode' do + before do + allow(controller).to receive(:authorized_fetch_mode?).and_return(true) + end + + context 'when signed request account is blocked' do + before do + account.block!(remote_account) + get :show, params: { id: 'featured', account_username: account.username } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'private' + end + + it 'returns empty orderedItems' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 0 + end + end + + context 'when signed request account is domain blocked' do + before do + account.block_domain!(remote_account.domain) + get :show, params: { id: 'featured', account_username: account.username } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'private' + end + + it 'returns empty orderedItems' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 0 + end + end + end end end - context 'id is not "featured"' do - it 'returns 404' do - post :show, params: { id: 'hoge', account_username: account.username } + context 'when id is not "featured"' do + it 'returns http not found' do + get :show, params: { id: 'hoge', account_username: account.username } expect(response).to have_http_status(404) end end diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb index a9ee75490..f3bc23953 100644 --- a/spec/controllers/activitypub/inboxes_controller_spec.rb +++ b/spec/controllers/activitypub/inboxes_controller_spec.rb @@ -3,25 +3,31 @@ require 'rails_helper' RSpec.describe ActivityPub::InboxesController, type: :controller do + let(:remote_account) { nil } + + before do + allow(controller).to receive(:signed_request_account).and_return(remote_account) + end + describe 'POST #create' do - context 'with signed_request_account' do - it 'returns 202' do - allow(controller).to receive(:signed_request_account) do - Fabricate(:account) - end + context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub) } + before do post :create, body: '{}' + end + + it 'returns http accepted' do expect(response).to have_http_status(202) end end - context 'without signed_request_account' do - it 'returns 401' do - allow(controller).to receive(:signed_request_account) do - false - end - + context 'without signature' do + before do post :create, body: '{}' + end + + it 'returns http not authorized' do expect(response).to have_http_status(401) end end diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb index 47460b22c..03490533d 100644 --- a/spec/controllers/activitypub/outboxes_controller_spec.rb +++ b/spec/controllers/activitypub/outboxes_controller_spec.rb @@ -4,20 +4,174 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do let!(:account) { Fabricate(:account) } before do - Fabricate(:status, account: account) + Fabricate(:status, account: account, visibility: :public) + Fabricate(:status, account: account, visibility: :unlisted) + Fabricate(:status, account: account, visibility: :private) + Fabricate(:status, account: account, visibility: :direct) + Fabricate(:status, account: account, visibility: :limited) + end + + before do + allow(controller).to receive(:signed_request_account).and_return(remote_account) end describe 'GET #show' do - before do - get :show, params: { account_username: account.username } - end + context 'without signature' do + let(:remote_account) { nil } + + before do + get :show, params: { account_username: account.username, page: page } + end + + context 'with page not requested' do + let(:page) { nil } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns totalItems' do + json = body_as_json + expect(json[:totalItems]).to eq 4 + end - it 'returns http success' do - expect(response).to have_http_status(200) + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + end + + context 'with page requested' do + let(:page) { 'true' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns orderedItems with public or unlisted statuses' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 2 + expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + end end - it 'returns application/activity+json' do - expect(response.content_type).to eq 'application/activity+json' + context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:page) { 'true' } + + context 'when signed request account does not follow account' do + before do + get :show, params: { account_username: account.username, page: page } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns orderedItems with public or unlisted statuses' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 2 + expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + end + end + + context 'when signed request account follows account' do + before do + remote_account.follow!(account) + get :show, params: { account_username: account.username, page: page } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns orderedItems with private statuses' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 3 + expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:to].include?(account_followers_url(account, ActionMailer::Base.default_url_options)) }).to be true + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + end + end + + context 'when signed request account is blocked' do + before do + account.block!(remote_account) + get :show, params: { account_username: account.username, page: page } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns empty orderedItems' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 0 + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + end + end + + context 'when signed request account is domain blocked' do + before do + account.block_domain!(remote_account.domain) + get :show, params: { account_username: account.username, page: page } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns empty orderedItems' do + json = body_as_json + expect(json[:orderedItems]).to be_an Array + expect(json[:orderedItems].size).to eq 0 + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to eq 'max-age=0, private' + end + end end end end diff --git a/spec/controllers/activitypub/replies_controller_spec.rb b/spec/controllers/activitypub/replies_controller_spec.rb new file mode 100644 index 000000000..a5ed14180 --- /dev/null +++ b/spec/controllers/activitypub/replies_controller_spec.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::RepliesController, type: :controller do + let(:status) { Fabricate(:status, visibility: parent_visibility) } + let(:remote_account) { nil } + + before do + allow(controller).to receive(:signed_request_account).and_return(remote_account) + + Fabricate(:status, thread: status, visibility: :public) + Fabricate(:status, thread: status, visibility: :public) + Fabricate(:status, thread: status, visibility: :private) + Fabricate(:status, account: status.account, thread: status, visibility: :public) + Fabricate(:status, account: status.account, thread: status, visibility: :private) + end + + describe 'GET #index' do + context 'with no signature' do + before do + get :index, params: { account_username: status.account.username, status_id: status.id } + end + + context 'when status is public' do + let(:parent_visibility) { :public } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'returns items with account\'s own replies' do + json = body_as_json + + expect(json[:first]).to be_a Hash + expect(json[:first][:items]).to be_an Array + expect(json[:first][:items].size).to eq 1 + expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true + end + end + + context 'when status is private' do + let(:parent_visibility) { :private } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is direct' do + let(:parent_visibility) { :direct } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:only_other_accounts) { nil } + + context do + before do + get :index, params: { account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts } + end + + context 'when status is public' do + let(:parent_visibility) { :public } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns application/activity+json' do + expect(response.content_type).to eq 'application/activity+json' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + context 'without only_other_accounts' do + it 'returns items with account\'s own replies' do + json = body_as_json + + expect(json[:first]).to be_a Hash + expect(json[:first][:items]).to be_an Array + expect(json[:first][:items].size).to eq 1 + expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true + end + end + + context 'with only_other_accounts' do + let(:only_other_accounts) { 'true' } + + it 'returns items with other public or unlisted replies' do + json = body_as_json + + expect(json[:first]).to be_a Hash + expect(json[:first][:items]).to be_an Array + expect(json[:first][:items].size).to eq 2 + expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true + end + end + end + + context 'when status is private' do + let(:parent_visibility) { :private } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is direct' do + let(:parent_visibility) { :direct } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + context 'when signed request account is blocked' do + before do + status.account.block!(remote_account) + get :index, params: { account_username: status.account.username, status_id: status.id } + end + + context 'when status is public' do + let(:parent_visibility) { :public } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is private' do + let(:parent_visibility) { :private } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is direct' do + let(:parent_visibility) { :direct } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + context 'when signed request account is domain blocked' do + before do + status.account.block_domain!(remote_account.domain) + get :index, params: { account_username: status.account.username, status_id: status.id } + end + + context 'when status is public' do + let(:parent_visibility) { :public } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is private' do + let(:parent_visibility) { :private } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is direct' do + let(:parent_visibility) { :direct } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + end + end +end diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 6905dae10..ba1f1370a 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -5,128 +5,821 @@ require 'rails_helper' describe StatusesController do render_views - describe '#show' do - context 'account is suspended' do - it 'returns gone' do - account = Fabricate(:account, suspended: true) - status = Fabricate(:status, account: account) + describe 'GET #show' do + let(:account) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: account) } + context 'when account is suspended' do + let(:account) { Fabricate(:account, suspended: true) } + + before do get :show, params: { account_username: account.username, id: status.id } + end + it 'returns http gone' do expect(response).to have_http_status(410) end end - context 'status is not permitted' do - it 'raises ActiveRecord::RecordNotFound' do - user = Fabricate(:user) - status = Fabricate(:status) - status.account.block!(user.account) + context 'when status is a reblog' do + let(:original_account) { Fabricate(:account, domain: 'example.com') } + let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') } + let(:status) { Fabricate(:status, account: account, reblog: original_status) } - sign_in(user) + before do get :show, params: { account_username: status.account.username, id: status.id } + end - expect(response).to have_http_status(404) + it 'redirects to the original status' do + expect(response).to redirect_to(original_status.url) end end - context 'status is a reblog' do - it 'redirects to the original status' do - original_account = Fabricate(:account, domain: 'example.com') - original_status = Fabricate(:status, account: original_account, uri: 'tag:example.com,2017:foo', url: 'https://example.com/123') - status = Fabricate(:status, reblog: original_status) + context 'when status is public' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end - get :show, params: { account_username: status.account.username, id: status.id } + context 'as HTML' do + let(:format) { 'html' } - expect(response).to redirect_to(original_status.url) + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end end end - context 'account is not suspended and status is permitted' do - it 'assigns @account' do - status = Fabricate(:status) - get :show, params: { account_username: status.account.username, id: status.id } - expect(assigns(:account)).to eq status.account + context 'when status is private' do + let(:status) { Fabricate(:status, account: account, visibility: :private) } + + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } end - it 'assigns @status' do - status = Fabricate(:status) - get :show, params: { account_username: status.account.username, id: status.id } - expect(assigns(:status)).to eq status + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end end - it 'assigns @ancestors for ancestors of the status if it is a reply' do - ancestor = Fabricate(:status) - status = Fabricate(:status, in_reply_to_id: ancestor.id) + context 'as HTML' do + let(:format) { 'html' } - get :show, params: { account_username: status.account.username, id: status.id } + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + context 'when status is direct' do + let(:status) { Fabricate(:status, account: account, visibility: :direct) } - expect(assigns(:ancestors)).to eq [ancestor] + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } end - it 'assigns @ancestors for [] if it is not a reply' do - status = Fabricate(:status) - get :show, params: { account_username: status.account.username, id: status.id } - expect(assigns(:ancestors)).to eq [] + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end end - it 'assigns @descendant_threads for a thread with several statuses' do - status = Fabricate(:status) - child = Fabricate(:status, in_reply_to_id: status.id) - grandchild = Fabricate(:status, in_reply_to_id: child.id) + context 'as HTML' do + let(:format) { 'html' } - get :show, params: { account_username: status.account.username, id: status.id } + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + context 'when signed-in' do + let(:user) { Fabricate(:user) } - expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchild.id] + before do + sign_in(user) end - it 'assigns @descendant_threads for several threads sharing the same descendant' do - status = Fabricate(:status) - child = Fabricate(:status, in_reply_to_id: status.id) - grandchildren = 2.times.map { Fabricate(:status, in_reply_to_id: child.id) } + context 'when account blocks user' do + before do + account.block!(user.account) + get :show, params: { account_username: status.account.username, id: status.id } + end - get :show, params: { account_username: status.account.username, id: status.id } + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is public' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end - expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchildren[0].id] - expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).to eq [grandchildren[1].id] + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns no Cache-Control header' do + expect(response.headers).to_not include 'Cache-Control' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end + end end - it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do - stub_const 'StatusControllerConcern::DESCENDANTS_LIMIT', 1 - status = Fabricate(:status) - child = Fabricate(:status, in_reply_to_id: status.id) + context 'when status is private' do + let(:status) { Fabricate(:status, account: account, visibility: :private) } - get :show, params: { account_username: status.account.username, id: status.id } + context 'when user is authorized to see it' do + before do + user.account.follow!(account) + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end - expect(assigns(:descendant_threads)).to eq [] - expect(assigns(:max_descendant_thread_id)).to eq child.id + it 'returns no Cache-Control header' do + expect(response.headers).to_not include 'Cache-Control' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'private' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end + end + end + + context 'when user is not authorized to see it' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end end - it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do - stub_const 'StatusControllerConcern::DESCENDANTS_DEPTH_LIMIT', 2 - status = Fabricate(:status) - child0 = Fabricate(:status, in_reply_to_id: status.id) - child1 = Fabricate(:status, in_reply_to_id: child0.id) - child2 = Fabricate(:status, in_reply_to_id: child0.id) + context 'when status is direct' do + let(:status) { Fabricate(:status, account: account, visibility: :direct) } - get :show, params: { account_username: status.account.username, id: status.id } + context 'when user is authorized to see it' do + before do + Fabricate(:mention, account: user.account, status: status) + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns no Cache-Control header' do + expect(response.headers).to_not include 'Cache-Control' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'private' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end + end + end + + context 'when user is not authorized to see it' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as JSON' do + let(:format) { 'json' } - expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).not_to include child1.id - expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).not_to include child2.id - expect(assigns(:descendant_threads)[0][:next_status].id).to eq child1.id - expect(assigns(:descendant_threads)[1][:next_status].id).to eq child2.id + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end end + end - it 'returns a success' do - status = Fabricate(:status) - get :show, params: { account_username: status.account.username, id: status.id } + context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + + before do + allow(controller).to receive(:signed_request_account).and_return(remote_account) + end + + context 'when account blocks account' do + before do + account.block!(remote_account) + get :show, params: { account_username: status.account.username, id: status.id } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when account domain blocks account' do + before do + account.block_domain!(remote_account.domain) + get :show, params: { account_username: status.account.username, id: status.id } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is public' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns no Cache-Control header' do + expect(response.headers).to_not include 'Cache-Control' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end + end + end + + context 'when status is private' do + let(:status) { Fabricate(:status, account: account, visibility: :private) } + + context 'when user is authorized to see it' do + before do + remote_account.follow!(account) + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns no Cache-Control header' do + expect(response.headers).to_not include 'Cache-Control' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'private' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end + end + end + + context 'when user is not authorized to see it' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + end + + context 'when status is direct' do + let(:status) { Fabricate(:status, account: account, visibility: :direct) } + + context 'when user is authorized to see it' do + before do + Fabricate(:mention, account: remote_account, status: status) + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns no Cache-Control header' do + expect(response.headers).to_not include 'Cache-Control' + end + + it 'renders status' do + expect(response).to render_template(:show) + expect(response.body).to include status.text + end + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns private Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'private' + end + + it 'returns Content-Type header' do + expect(response.headers['Content-Type']).to include 'application/activity+json' + end + + it 'renders ActivityPub Note object' do + json = body_as_json + expect(json[:content]).to include status.text + end + end + end + + context 'when user is not authorized to see it' do + before do + get :show, params: { account_username: status.account.username, id: status.id, format: format } + end + + context 'as JSON' do + let(:format) { 'json' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'as HTML' do + let(:format) { 'html' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + end + end + end + + describe 'GET #activity' do + let(:account) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: account) } + + context 'when account is suspended' do + let(:account) { Fabricate(:account, suspended: true) } + + before do + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http gone' do + expect(response).to have_http_status(410) + end + end + + context 'when status is public' do + pending + end + + context 'when status is private' do + pending + end + + context 'when status is direct' do + pending + end + + context 'when signed-in' do + context 'when status is public' do + pending + end + + context 'when status is private' do + context 'when user is authorized to see it' do + pending + end + + context 'when user is not authorized to see it' do + pending + end + end + + context 'when status is direct' do + context 'when user is authorized to see it' do + pending + end + + context 'when user is not authorized to see it' do + pending + end + end + end + + context 'with signature' do + context 'when status is public' do + pending + end + + context 'when status is private' do + context 'when user is authorized to see it' do + pending + end + + context 'when user is not authorized to see it' do + pending + end + end + + context 'when status is direct' do + context 'when user is authorized to see it' do + pending + end + + context 'when user is not authorized to see it' do + pending + end + end + end + end + + describe 'GET #embed' do + let(:account) { Fabricate(:account) } + let(:status) { Fabricate(:status, account: account) } + + context 'when account is suspended' do + let(:account) { Fabricate(:account, suspended: true) } + + before do + get :embed, params: { account_username: account.username, id: status.id } + end + + it 'returns http gone' do + expect(response).to have_http_status(410) + end + end + + context 'when status is a reblog' do + let(:original_account) { Fabricate(:account, domain: 'example.com') } + let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') } + let(:status) { Fabricate(:status, account: account, reblog: original_status) } + + before do + get :embed, params: { account_username: status.account.username, id: status.id } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is public' do + before do + get :embed, params: { account_username: status.account.username, id: status.id } + end + + it 'returns http success' do expect(response).to have_http_status(200) end - it 'renders statuses/show' do - status = Fabricate(:status) - get :show, params: { account_username: status.account.username, id: status.id } - expect(response).to render_template 'statuses/show' + it 'returns Link header' do + expect(response.headers['Link'].to_s).to include 'activity+json' + end + + it 'returns Vary header' do + expect(response.headers['Vary']).to eq 'Accept' + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include 'public' + end + + it 'renders status' do + expect(response).to render_template(:embed) + expect(response.body).to include status.text + end + end + + context 'when status is private' do + let(:status) { Fabricate(:status, account: account, visibility: :private) } + + before do + get :embed, params: { account_username: status.account.username, id: status.id } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + + context 'when status is direct' do + let(:status) { Fabricate(:status, account: account, visibility: :direct) } + + before do + get :embed, params: { account_username: status.account.username, id: status.id } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) end end end diff --git a/spec/fabricators/status_pin_fabricator.rb b/spec/fabricators/status_pin_fabricator.rb index 6a9006c9f..f1f1c05f3 100644 --- a/spec/fabricators/status_pin_fabricator.rb +++ b/spec/fabricators/status_pin_fabricator.rb @@ -1,4 +1,4 @@ Fabricator(:status_pin) do account - status + status { |attrs| Fabricate(:status, account: attrs[:account], visibility: :public) } end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 38537da44..02f533287 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -96,20 +96,16 @@ RSpec.describe Status, type: :model do context 'unless destroyed?' do context 'if reblog?' do - it 'returns "#{account.acct} shared #{reblog.account.acct}\'s: #{preview}"' do + it 'returns "#{account.acct} shared a status by #{reblog.account.acct}"' do reblog = subject.reblog = other - preview = subject.text.slice(0, 10).split("\n")[0] - expect(subject.title).to( - eq "#{account.acct} shared #{reblog.account.acct}'s: #{preview}" - ) + expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}" end end context 'unless reblog?' do - it 'returns "#{account.acct}: #{preview}"' do + it 'returns "New status by #{account.acct}"' do subject.reblog = nil - preview = subject.text.slice(0, 20).split("\n")[0] - expect(subject.title).to eq "#{account.acct}: #{preview}" + expect(subject.title).to eq "New status by #{account.acct}" end end end diff --git a/spec/services/fetch_resource_service_spec.rb b/spec/services/fetch_resource_service_spec.rb index 3af6a0689..ded05ffbc 100644 --- a/spec/services/fetch_resource_service_spec.rb +++ b/spec/services/fetch_resource_service_spec.rb @@ -21,7 +21,11 @@ RSpec.describe FetchResourceService, type: :service do context 'when OpenSSL::SSL::SSLError is raised' do before do - allow(Request).to receive_message_chain(:new, :add_headers, :on_behalf_of, :perform).and_raise(OpenSSL::SSL::SSLError) + request = double() + allow(Request).to receive(:new).and_return(request) + allow(request).to receive(:add_headers) + allow(request).to receive(:on_behalf_of) + allow(request).to receive(:perform).and_raise(OpenSSL::SSL::SSLError) end it { is_expected.to be_nil } @@ -29,7 +33,11 @@ RSpec.describe FetchResourceService, type: :service do context 'when HTTP::ConnectionError is raised' do before do - allow(Request).to receive_message_chain(:new, :add_headers, :on_behalf_of, :perform).and_raise(HTTP::ConnectionError) + request = double() + allow(Request).to receive(:new).and_return(request) + allow(request).to receive(:add_headers) + allow(request).to receive(:on_behalf_of) + allow(request).to receive(:perform).and_raise(HTTP::ConnectionError) end it { is_expected.to be_nil } diff --git a/yarn.lock b/yarn.lock index 2d57ec0ba..5715672cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1327,9 +1327,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/q@^1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18" - integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA== + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" + integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== "@types/react@16.4.6": version "16.4.6" @@ -1632,7 +1632,7 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.9.1: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== @@ -1652,6 +1652,16 @@ ajv@^6.12.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.5.5: + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -1971,9 +1981,9 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== axios@^0.18.0: version "0.18.1" @@ -2249,9 +2259,9 @@ bindings@^1.5.0: file-uri-to-path "1.0.0" bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== blurhash@^1.1.3: version "1.1.3" @@ -2461,11 +2471,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -2482,9 +2487,9 @@ bytes@3.1.0: integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== cacache@^12.0.2, cacache@^12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== dependencies: bluebird "^3.5.5" chownr "^1.1.1" @@ -2703,16 +2708,11 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: +chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chownr@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" - integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== - chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -2818,7 +2818,7 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -coa@~2.0.1: +coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== @@ -2880,15 +2880,10 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= - combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" @@ -3052,9 +3047,9 @@ core-js-compat@^3.6.2: semver "7.0.0" core-js-pure@^3.0.0: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" - integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" + integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== core-js@^2.4.0: version "2.6.1" @@ -3235,18 +3230,18 @@ css-loader@^3.4.2: postcss-value-parser "^4.0.2" schema-utils "^2.6.0" -css-select-base-adapter@~0.1.0: +css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== css-select@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.0.2.tgz#ab4386cec9e1f668855564b17c3733b43b2a5ede" - integrity sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== dependencies: boolbase "^1.0.0" - css-what "^2.1.2" + css-what "^3.2.1" domutils "^1.7.0" nth-check "^1.0.2" @@ -3265,37 +3260,37 @@ css-system-font-keywords@^1.0.0: resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" integrity sha1-hcbwhquk6zLFcaMIav/ENLhII+0= -css-tree@1.0.0-alpha.28: - version "1.0.0-alpha.28" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.28.tgz#8e8968190d886c9477bc8d61e96f61af3f7ffa7f" - integrity sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w== +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== dependencies: - mdn-data "~1.1.0" - source-map "^0.5.3" + mdn-data "2.0.4" + source-map "^0.6.1" -css-tree@1.0.0-alpha.29: - version "1.0.0-alpha.29" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" - integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg== +css-tree@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== dependencies: - mdn-data "~1.1.0" - source-map "^0.5.3" + mdn-data "2.0.6" + source-map "^0.6.1" css-unit-converter@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= -css-url-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" - integrity sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w= - -css-what@2.1, css-what@^2.1.2: +css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +css-what@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" + integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== + cssesc@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" @@ -3374,12 +3369,12 @@ cssnano@^4.1.10: is-resolvable "^1.0.0" postcss "^7.0.0" -csso@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.1.tgz#7b9eb8be61628973c1b261e169d2f024008e758b" - integrity sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg== +csso@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== dependencies: - css-tree "1.0.0-alpha.29" + css-tree "1.0.0-alpha.39" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.8" @@ -3961,7 +3956,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5, es-abstract@^1.5.1: +es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== @@ -4524,9 +4519,9 @@ fast-deep-equal@^3.1.1: integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@~2.0.6: version "2.0.6" @@ -4560,9 +4555,9 @@ fb-watchman@^2.0.0: bser "^2.0.0" figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^1.3.5: version "1.7.0" @@ -4722,9 +4717,9 @@ flat-cache@^2.0.1: write "1.0.3" flatted@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" - integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flush-write-stream@^1.0.0: version "1.1.1" @@ -4890,9 +4885,9 @@ functional-red-black-tree@^1.0.1: integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= functions-have-names@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.0.tgz#83da7583e4ea0c9ac5ff530f73394b033e0bf77d" - integrity sha512-zKXyzksTeaCSw5wIX79iCA40YAa6CJMJgNg9wdkU/ERBrIdPSimPICYiLp65lRbSBqtiHql/HZfS2DyI/AH6tQ== + version "1.2.1" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" + integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== gauge@~2.7.3: version "2.7.4" @@ -5119,7 +5114,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -5275,9 +5270,9 @@ hoopy@^0.1.4: integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== hpack.js@^2.1.6: version "2.1.6" @@ -5759,13 +5754,6 @@ is-buffer@^2.0.2: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= - dependencies: - builtin-modules "^1.0.0" - is-callable@^1.1.4, is-callable@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" @@ -6561,7 +6549,7 @@ js-string-escape@1.0.1: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0: +js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -6928,9 +6916,9 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== loglevel@^1.6.6: - version "1.6.6" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312" - integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ== + version "1.6.8" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" + integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" @@ -7013,10 +7001,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -mdn-data@~1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" - integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== media-typer@0.3.0: version "0.3.0" @@ -7100,34 +7093,22 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.40.0: - version "1.40.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" - integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== "mime-db@>= 1.40.0 < 2": version "1.42.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "~1.37.0" - -mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.24" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" - integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== - dependencies: - mime-db "1.40.0" + mime-db "1.44.0" mime@1.6.0: version "1.6.0" @@ -7253,14 +7234,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5, mkdirp@^0.5.3, mkdirp@~0.5.1: - version "0.5.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" - integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== - dependencies: - minimist "^1.2.5" - -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -7272,10 +7246,10 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moo@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" - integrity sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw== +moo@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" + integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w== mousetrap@^1.5.2: version "1.6.5" @@ -7360,12 +7334,12 @@ natural-compare@^1.4.0: integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= nearley@^2.7.10: - version "2.16.0" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.16.0.tgz#77c297d041941d268290ec84b739d0ee297e83a7" - integrity sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg== + version "2.19.2" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.2.tgz#40cafbf235121ae94b1aa1e585890d24fade182d" + integrity sha512-h6lygT0BWAGErDvoE2LfI+tDeY2+UUrqG5dcBPdCmjnjud9z1wE0P7ljb85iNbE93YA+xJLpoSYGMuUqhnSSSA== dependencies: commander "^2.19.0" - moo "^0.4.3" + moo "^0.5.0" railroad-diagrams "^1.0.0" randexp "0.4.6" semver "^5.4.1" @@ -7484,12 +7458,12 @@ nopt@^4.0.1: osenv "^0.1.4" normalize-package-data@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" - integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" + resolve "^1.10.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" @@ -7666,13 +7640,13 @@ object.fromentries@^2.0.2: function-bind "^1.1.1" has "^1.0.3" -object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" object.pick@^1.3.0: version "1.3.0" @@ -7681,7 +7655,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.0.4, object.values@^1.1.0, object.values@^1.1.1: +object.values@^1.1.0, object.values@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== @@ -8745,10 +8719,10 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= -psl@^1.1.24, psl@^1.1.28: - version "1.1.31" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" - integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" @@ -8792,7 +8766,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -9223,10 +9197,10 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -9236,10 +9210,10 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^2.0.2, readable-stream@^2.0.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +readable-stream@^2.0.1, readable-stream@^2.3.3: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -9493,9 +9467,9 @@ request-promise-native@^1.0.5: tough-cookie ">=2.3.3" request@^2.87.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -9504,7 +9478,7 @@ request@^2.87.0: extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.0" + har-validator "~5.1.3" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -9514,7 +9488,7 @@ request@^2.87.0: performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" - tough-cookie "~2.4.3" + tough-cookie "~2.5.0" tunnel-agent "^0.6.0" uuid "^3.3.2" @@ -9621,10 +9595,10 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -10168,7 +10142,7 @@ source-map@0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -10187,9 +10161,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.0" @@ -10200,9 +10174,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" - integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== spdy-transport@^3.0.0: version "3.0.0" @@ -10276,7 +10250,7 @@ ssri@^7.0.0: figgy-pudding "^3.5.1" minipass "^3.1.1" -stable@~0.1.6: +stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== @@ -10590,22 +10564,21 @@ supports-color@^7.0.0, supports-color@^7.1.0: has-flag "^4.0.0" svgo@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985" - integrity sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g== + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== dependencies: - coa "~2.0.1" - colors "~1.1.2" + chalk "^2.4.1" + coa "^2.0.2" css-select "^2.0.0" - css-select-base-adapter "~0.1.0" - css-tree "1.0.0-alpha.28" - css-url-regex "^1.1.0" - csso "^3.5.0" - js-yaml "^3.12.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" mkdirp "~0.5.1" - object.values "^1.0.4" + object.values "^1.1.0" sax "~1.2.4" - stable "~0.1.6" + stable "^0.1.8" unquote "~1.1.1" util.promisify "~1.0.0" @@ -10873,7 +10846,7 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tough-cookie@>=2.3.3, tough-cookie@^2.3.4: +tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -10881,14 +10854,6 @@ tough-cookie@>=2.3.3, tough-cookie@^2.3.4: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -11104,12 +11069,14 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= util.promisify@^1.0.0, util.promisify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" util@0.10.3: version "0.10.3" |