diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ba027d953003c..dd943e32795e40 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,7 @@ aliases: - image: circleci/ruby:2.7-buster-node environment: &ruby_environment BUNDLE_APP_CONFIG: ./.bundle/ + BUNDLE_PATH: ./vendor/bundle/ DB_HOST: localhost DB_USER: root RAILS_ENV: test @@ -138,9 +139,10 @@ jobs: docker: - image: circleci/ruby:2.7-buster-node environment: *ruby_environment - - image: circleci/postgres:10.6-alpine + - image: circleci/postgres:12.2 environment: POSTGRES_USER: root + POSTGRES_HOST_AUTH_METHOD: trust - image: circleci/redis:5-alpine steps: - *attach_workspace @@ -157,9 +159,10 @@ jobs: docker: - image: circleci/ruby:2.7-buster-node environment: *ruby_environment - - image: circleci/postgres:10.6-alpine + - image: circleci/postgres:12.2 environment: POSTGRES_USER: root + POSTGRES_HOST_AUTH_METHOD: trust - image: circleci/redis:5-alpine <<: *test_steps @@ -168,9 +171,10 @@ jobs: docker: - image: circleci/ruby:2.6-buster-node environment: *ruby_environment - - image: circleci/postgres:10.6-alpine + - image: circleci/postgres:12.2 environment: POSTGRES_USER: root + POSTGRES_HOST_AUTH_METHOD: trust - image: circleci/redis:5-alpine <<: *test_steps @@ -179,9 +183,10 @@ jobs: docker: - image: circleci/ruby:2.5-buster-node environment: *ruby_environment - - image: circleci/postgres:10.6-alpine + - image: circleci/postgres:12.2 environment: POSTGRES_USER: root + POSTGRES_HOST_AUTH_METHOD: trust - image: circleci/redis:5-alpine <<: *test_steps diff --git a/.ruby-version b/.ruby-version index 57cf282ebbc41e..338a5b5d8fec49 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.5 +2.6.6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 33663c2ad9dfe7..ad353e2ad656b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,79 @@ Changelog All notable changes to this project will be documented in this file. +## [v3.1.3] - 2020-04-05 +### Added + +- Add ability to filter audit log in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13381)) +- Add titles to warning presets in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13252)) +- Add option to include resolved DNS records when blacklisting e-mail domains in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13254)) +- Add ability to delete files uploaded for settings in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13192)) +- Add sorting by username, creation and last activity in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13076)) +- Add explanation as to why unlocked accounts may have follow requests in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13385)) +- Add link to bookmarks to dropdown in web UI ([mayaeh](https://github.com/tootsuite/mastodon/pull/13273)) +- Add support for links to statuses in announcements to be opened in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13212), [ThibG](https://github.com/tootsuite/mastodon/pull/13250)) +- Add tooltips to audio/video player buttons in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13203)) +- Add submit button to the top of preferences pages ([guigeekz](https://github.com/tootsuite/mastodon/pull/13068)) +- Add specific rate limits for posting, following and reporting ([Gargron](https://github.com/tootsuite/mastodon/pull/13172), [Gargron](https://github.com/tootsuite/mastodon/pull/13390)) + - 300 posts every 3 hours + - 400 follows or follow requests every 24 hours + - 400 reports every 24 hours +- Add federation support for the "hide network" preference ([ThibG](https://github.com/tootsuite/mastodon/pull/11673)) +- Add `--skip-media-remove` option to `tootctl statuses remove` ([tateisu](https://github.com/tootsuite/mastodon/pull/13080)) + +### Changed + +- **Change design of polls in web UI** ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/13257), [ThibG](https://github.com/tootsuite/mastodon/pull/13313)) +- Change status click areas in web UI to be bigger ([ariasuni](https://github.com/tootsuite/mastodon/pull/13327)) +- **Change `tootctl media remove-orphans` to work for all classes** ([Gargron](https://github.com/tootsuite/mastodon/pull/13316)) +- **Change local media attachments to perform heavy processing asynchronously** ([Gargron](https://github.com/tootsuite/mastodon/pull/13210)) +- Change video uploads to always be converted to H264/MP4 ([Gargron](https://github.com/tootsuite/mastodon/pull/13220), [ThibG](https://github.com/tootsuite/mastodon/pull/13239), [ThibG](https://github.com/tootsuite/mastodon/pull/13242)) +- Change video uploads to enforce certain limits ([Gargron](https://github.com/tootsuite/mastodon/pull/13218)) + - Dimensions smaller than 1920x1200px + - Frame rate at most 60fps +- Change the tooltip "Toggle visibility" to "Hide media" in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13199)) +- Change description of privacy levels to be more intuitive in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13197)) +- Change GIF label to be displayed even when autoplay is enabled in web UI ([koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/13209)) +- Change the string "Hide everything from …" to "Block domain …" in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13178), [mayaeh](https://github.com/tootsuite/mastodon/pull/13221)) +- Change wording of media display preferences to be more intuitive ([ariasuni](https://github.com/tootsuite/mastodon/pull/13198)) + +### Deprecated + +- `POST /api/v1/media` → `POST /api/v2/media` ([Gargron](https://github.com/tootsuite/mastodon/pull/13210)) + +### Fixed + +- Fix `tootctl media remove-orphans` ignoring `PAPERCLIP_ROOT_PATH` ([Gargron](https://github.com/tootsuite/mastodon/pull/13375)) +- Fix returning results when searching for URL with non-zero offset ([Gargron](https://github.com/tootsuite/mastodon/pull/13377)) +- Fix pinning a column in web UI sometimes redirecting out of web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13376)) +- Fix background jobs not using locks like they are supposed to ([Gargron](https://github.com/tootsuite/mastodon/pull/13361)) +- Fix content warning being unnecessarily cleared when hiding content warning input in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13348)) +- Fix "Show more" not switching to "Show less" on public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/13174)) +- Fix import overwrite option not being selectable ([noellabo](https://github.com/tootsuite/mastodon/pull/13347)) +- Fix wrong color for ellipsis in boost confirmation dialog in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13355)) +- Fix unnecessary unfollowing when importing follows with overwrite option ([noellabo](https://github.com/tootsuite/mastodon/pull/13350)) +- Fix 404 and 410 API errors being silently discarded in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13279)) +- Fix OCR not working on Safari because of unsupported worker-src CSP ([ThibG](https://github.com/tootsuite/mastodon/pull/13323)) +- Fix media not being marked sensitive when a content warning is set with no text ([ThibG](https://github.com/tootsuite/mastodon/pull/13277)) +- Fix crash after deleting announcements in web UI ([codesections](https://github.com/tootsuite/mastodon/pull/13283), [ThibG](https://github.com/tootsuite/mastodon/pull/13312)) +- Fix bookmarks not being searchable ([Kjwon15](https://github.com/tootsuite/mastodon/pull/13271), [noellabo](https://github.com/tootsuite/mastodon/pull/13293)) +- Fix reported accounts not being whitelisted from further spam checks when resolving a spam check report ([ThibG](https://github.com/tootsuite/mastodon/pull/13289)) +- Fix web UI crash in single-column mode on prehistoric browsers ([ThibG](https://github.com/tootsuite/mastodon/pull/13267)) +- Fix some timeouts when searching for URLs ([ThibG](https://github.com/tootsuite/mastodon/pull/13253)) +- Fix detailed view of direct messages displaying a 0 boost count in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13244)) +- Fix regression in “Edit media” modal in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13243)) +- Fix public posts from silenced accounts not being changed to unlisted visibility ([ThibG](https://github.com/tootsuite/mastodon/pull/13096)) +- Fix error when searching for URLs that contain the mention syntax ([ThibG](https://github.com/tootsuite/mastodon/pull/13151)) +- Fix text area above/right of emoji picker being accidentally clickable in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13148)) +- Fix too large announcements not being scrollable in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13211)) +- Fix `tootctl media remove-orphans` crashing when encountering invalid media ([ThibG](https://github.com/tootsuite/mastodon/pull/13170)) +- Fix installation failing when Redis password contains special characters ([ThibG](https://github.com/tootsuite/mastodon/pull/13156)) +- Fix announcements with fully-qualified mentions to local users crashing web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13164)) + +### Security + +- Fix re-sending of e-mail confirmation not being rate limited ([Gargron](https://github.com/tootsuite/mastodon/pull/13360)) + ## [v3.1.2] - 2020-02-27 ### Added diff --git a/Dockerfile b/Dockerfile index a22efe67234edd..0537d8facdd416 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep SHELL ["bash", "-c"] # Install Node v12 (LTS) -ENV NODE_VER="12.14.0" +ENV NODE_VER="12.16.1" RUN ARCH= && \ dpkgArch="$(dpkg --print-architecture)" && \ case "${dpkgArch##*-}" in \ @@ -38,8 +38,8 @@ RUN apt update && \ make -j$(nproc) > /dev/null && \ make install_bin install_include install_lib -# Install ruby -ENV RUBY_VER="2.6.5" +# Install Ruby +ENV RUBY_VER="2.6.6" ENV CPPFLAGS="-I/opt/jemalloc/include" ENV LDFLAGS="-L/opt/jemalloc/lib/" RUN apt update && \ diff --git a/Gemfile b/Gemfile index c35b21be49e55d..57bf27183ec6e6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,12 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '>= 2.4.0', '< 3.0.0' +ruby '>= 2.5.0', '< 3.0.0' gem 'pkg-config', '~> 1.4' gem 'puma', '~> 4.3' -gem 'rails', '~> 5.2.4' +gem 'rails', '~> 5.2.4.2' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 0.20' gem 'rack', '~> 2.2.2' @@ -20,7 +20,7 @@ gem 'makara', '~> 0.4' gem 'pghero', '~> 2.4' gem 'dotenv-rails', '~> 2.7' -gem 'aws-sdk-s3', '~> 1.60', require: false +gem 'aws-sdk-s3', '~> 1.61', require: false gem 'fog-core', '<= 2.1.0' gem 'fog-openstack', '~> 0.3', require: false gem 'paperclip', '~> 6.0' @@ -35,7 +35,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'iso-639' gem 'chewy', '~> 5.1' -gem 'cld3', '~> 3.2.6' +gem 'cld3', '~> 3.3.0' gem 'devise', '~> 4.7' gem 'devise-two-factor', '~> 3.1' @@ -48,8 +48,8 @@ gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' gem 'omniauth', '~> 1.9' -gem 'discard', '~> 1.1' -gem 'doorkeeper', '~> 5.2' +gem 'discard', '~> 1.2' +gem 'doorkeeper', '~> 5.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' @@ -69,7 +69,7 @@ gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b gem 'nokogiri', '~> 1.10' gem 'nsa', '~> 0.2' gem 'oj', '~> 3.10' -gem 'ox', '~> 2.12' +gem 'ox', '~> 2.13' gem 'parslet' gem 'parallel', '~> 1.19' gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' @@ -84,7 +84,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 1.1' gem 'ruby-progressbar', '~> 1.10' gem 'sanitize', '~> 5.1' -gem 'sidekiq', '~> 5.2' +gem 'sidekiq', '~> 6.0' gem 'sidekiq-scheduler', '~> 3.0' gem 'sidekiq-unique-jobs', '~> 6.0' gem 'sidekiq-bulk', '~>0.2.0' @@ -92,9 +92,9 @@ gem 'simple-navigation', '~> 4.1' gem 'simple_form', '~> 5.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.2.0' -gem 'strong_migrations', '~> 0.5' +gem 'strong_migrations', '~> 0.6' gem 'tty-command', '~> 0.9', require: false -gem 'tty-prompt', '~> 0.20', require: false +gem 'tty-prompt', '~> 0.21', require: false gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2019' gem 'webpacker', '~> 4.2' @@ -112,7 +112,7 @@ group :development, :test do gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-byebug', '~> 3.8' gem 'pry-rails', '~> 0.3' - gem 'rspec-rails', '~> 3.9' + gem 'rspec-rails', '~> 4.0' end group :production, :test do @@ -122,19 +122,19 @@ end group :test do gem 'capybara', '~> 3.31' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 2.10' + gem 'faker', '~> 2.11' gem 'microformats', '~> 4.2' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.18', require: false gem 'webmock', '~> 3.8' - gem 'parallel_tests', '~> 2.30' + gem 'parallel_tests', '~> 2.32' end group :development do gem 'active_record_query_trace', '~> 1.7' gem 'annotate', '~> 3.0' - gem 'better_errors', '~> 2.5' + gem 'better_errors', '~> 2.6' gem 'binding_of_caller', '~> 0.7' gem 'bullet', '~> 6.1' gem 'letter_opener', '~> 1.7' @@ -142,10 +142,10 @@ group :development do gem 'memory_profiler' gem 'rubocop', '~> 0.79', require: false gem 'rubocop-rails', '~> 2.4', require: false - gem 'brakeman', '~> 4.7', require: false + gem 'brakeman', '~> 4.8', require: false gem 'bundler-audit', '~> 0.6', require: false - gem 'capistrano', '~> 3.11' + gem 'capistrano', '~> 3.12' gem 'capistrano-rails', '~> 1.4' gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-yarn', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 9d0b75f911d951..3ac0a06ed0a404 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,25 +31,25 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.1) - actionpack (= 5.2.4.1) + actioncable (5.2.4.2) + actionpack (= 5.2.4.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) + actionmailer (5.2.4.2) + actionpack (= 5.2.4.2) + actionview (= 5.2.4.2) + activejob (= 5.2.4.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.1) - actionview (= 5.2.4.1) - activesupport (= 5.2.4.1) + actionpack (5.2.4.2) + actionview (= 5.2.4.2) + activesupport (= 5.2.4.2) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.1) - activesupport (= 5.2.4.1) + actionview (5.2.4.2) + activesupport (= 5.2.4.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -60,20 +60,20 @@ GEM case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.7) - activejob (5.2.4.1) - activesupport (= 5.2.4.1) + activejob (5.2.4.2) + activesupport (= 5.2.4.2) globalid (>= 0.3.6) - activemodel (5.2.4.1) - activesupport (= 5.2.4.1) - activerecord (5.2.4.1) - activemodel (= 5.2.4.1) - activesupport (= 5.2.4.1) + activemodel (5.2.4.2) + activesupport (= 5.2.4.2) + activerecord (5.2.4.2) + activemodel (= 5.2.4.2) + activesupport (= 5.2.4.2) arel (>= 9.0) - activestorage (5.2.4.1) - actionpack (= 5.2.4.1) - activerecord (= 5.2.4.1) + activestorage (5.2.4.2) + actionpack (= 5.2.4.2) + activerecord (= 5.2.4.2) marcel (~> 0.3.1) - activesupport (5.2.4.1) + activesupport (5.2.4.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -92,23 +92,23 @@ GEM av (0.9.0) cocaine (~> 0.5.3) aws-eventstream (1.0.3) - aws-partitions (1.261.0) - aws-sdk-core (3.86.0) + aws-partitions (1.286.0) + aws-sdk-core (3.92.0) aws-eventstream (~> 1.0, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.27.0) + aws-sdk-kms (1.30.0) aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.60.1) + aws-sdk-s3 (1.61.1) aws-sdk-core (~> 3, >= 3.83.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.0) + aws-sigv4 (1.1.1) aws-eventstream (~> 1.0, >= 1.0.2) bcrypt (3.1.12) - better_errors (2.5.1) + better_errors (2.6.0) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) @@ -116,10 +116,10 @@ GEM debug_inspector (>= 0.0.1) blurhash (0.1.4) ffi (~> 1.10.0) - bootsnap (1.4.5) + bootsnap (1.4.6) msgpack (~> 1.0) - brakeman (4.7.2) - browser (3.0.3) + brakeman (4.8.0) + browser (4.0.0) builder (3.2.4) bullet (6.1.0) activesupport (>= 3.0.0) @@ -128,7 +128,7 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 0.18) byebug (11.1.1) - capistrano (3.11.2) + capistrano (3.12.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -160,13 +160,13 @@ GEM elasticsearch (>= 2.0.0) elasticsearch-dsl chunky_png (1.3.11) - cld3 (3.2.6) + cld3 (3.3.0) ffi (>= 1.1.0, < 1.12.0) climate_control (0.2.0) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.1.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.6) connection_pool (2.2.2) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -190,37 +190,37 @@ GEM devise (>= 4.0.0) rpam2 (~> 4.0) diff-lcs (1.3) - discard (1.1.0) + discard (1.2.0) activerecord (>= 4.2, < 7) docile (1.3.2) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - doorkeeper (5.2.3) + doorkeeper (5.3.1) railties (>= 5) dotenv (2.7.5) dotenv-rails (2.7.5) dotenv (= 2.7.5) railties (>= 3.2, < 6.1) e2mmap (0.1.0) - elasticsearch (7.3.0) - elasticsearch-api (= 7.3.0) - elasticsearch-transport (= 7.3.0) - elasticsearch-api (7.3.0) + elasticsearch (7.5.0) + elasticsearch-api (= 7.5.0) + elasticsearch-transport (= 7.5.0) + elasticsearch-api (7.5.0) multi_json elasticsearch-dsl (0.1.8) - elasticsearch-transport (7.3.0) - faraday + elasticsearch-transport (7.5.0) + faraday (>= 0.14, < 1) multi_json encryptor (3.0.0) equatable (0.6.1) erubi (1.9.0) - et-orbi (1.1.6) + et-orbi (1.2.3) tzinfo excon (0.71.0) fabrication (2.21.0) - faker (2.10.1) + faker (2.11.0) i18n (>= 1.6, < 2) - faraday (1.0.0) + faraday (0.17.3) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.7) @@ -241,8 +241,8 @@ GEM fog-json (>= 1.0) ipaddress (>= 0.8) formatador (0.2.5) - fugit (1.1.6) - et-orbi (~> 1.1, >= 1.1.6) + fugit (1.3.3) + et-orbi (~> 1.1, >= 1.1.8) raabro (~> 1.1) fuubar (2.5.0) rspec-core (~> 3.0) @@ -265,8 +265,8 @@ GEM railties (>= 4.0.1) hamster (3.0.0) concurrent-ruby (~> 1.0) - hashdiff (1.0.0) - hashie (3.6.0) + hashdiff (1.0.1) + hashie (4.1.0) highline (2.0.3) hiredis (0.6.3) hkdf (0.3.0) @@ -287,7 +287,7 @@ GEM rainbow (>= 2.0.0) i18n (1.8.2) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.30) + i18n-tasks (0.9.31) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi @@ -299,19 +299,19 @@ GEM terminal-table (>= 1.5.1) idn-ruby (0.1.0) ipaddress (0.8.3) - iso-639 (0.2.8) + iso-639 (0.3.5) jaro_winkler (1.5.4) jmespath (1.4.0) json (2.3.0) json-canonicalization (0.2.0) - json-ld (3.1.0) + json-ld (3.1.2) htmlentities (~> 4.3) - json-canonicalization (~> 0.1) + json-canonicalization (~> 0.2) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.14) rack (~> 2.0) rdf (~> 3.1) - json-ld-preloaded (3.1.0) + json-ld-preloaded (3.1.2) json-ld (~> 3.1) rdf (~> 3.1) jsonapi-renderer (0.2.2) @@ -361,11 +361,11 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2019.1009) - mimemagic (0.3.3) + mimemagic (0.3.4) mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.0) - msgpack (1.3.1) + msgpack (1.3.3) multi_json (1.14.1) multipart-post (2.1.1) necromancer (0.5.1) @@ -374,7 +374,7 @@ GEM net-ssh (>= 2.6.5, < 6.0.0) net-ssh (5.2.0) nio4r (2.5.2) - nokogiri (1.10.8) + nokogiri (1.10.9) mini_portile2 (~> 2.4.0) nokogumbo (2.0.1) nokogiri (~> 1.8, >= 1.8.4) @@ -383,9 +383,9 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.10.1) - omniauth (1.9.0) - hashie (>= 3.4.6, < 3.7.0) + oj (3.10.5) + omniauth (1.9.1) + hashie (>= 3.4.6) rack (>= 1.6.2, < 3) omniauth-cas (1.1.1) addressable (~> 2.3) @@ -395,7 +395,7 @@ GEM omniauth (~> 1.3, >= 1.3.2) ruby-saml (~> 1.7) orm_adapter (0.5.0) - ox (2.12.1) + ox (2.13.2) paperclip (6.0.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -406,15 +406,15 @@ GEM av (~> 0.9.0) paperclip (>= 2.5.2) parallel (1.19.1) - parallel_tests (2.30.1) + parallel_tests (2.32.0) parallel - parser (2.7.0.2) + parser (2.7.0.5) ast (~> 2.4.0) parslet (1.8.2) pastel (0.7.3) equatable (~> 0.6) tty-color (~> 0.5) - pg (1.2.2) + pg (1.2.3) pghero (2.4.1) activerecord (>= 5) pkg-config (1.4.1) @@ -435,7 +435,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.3) - puma (4.3.1) + puma (4.3.3) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) @@ -445,24 +445,24 @@ GEM rack (>= 1.0, < 3) rack-cors (1.1.1) rack (>= 2.0.0) - rack-protection (2.0.7) + rack-protection (2.0.8.1) rack rack-proxy (0.6.5) rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.4.1) - actioncable (= 5.2.4.1) - actionmailer (= 5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) - activemodel (= 5.2.4.1) - activerecord (= 5.2.4.1) - activestorage (= 5.2.4.1) - activesupport (= 5.2.4.1) + rails (5.2.4.2) + actioncable (= 5.2.4.2) + actionmailer (= 5.2.4.2) + actionpack (= 5.2.4.2) + actionview (= 5.2.4.2) + activejob (= 5.2.4.2) + activemodel (= 5.2.4.2) + activerecord (= 5.2.4.2) + activestorage (= 5.2.4.2) + activesupport (= 5.2.4.2) bundler (>= 1.3.0) - railties (= 5.2.4.1) + railties (= 5.2.4.2) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.4) actionpack (>= 5.0.1.x) @@ -478,9 +478,9 @@ GEM railties (>= 5.0, < 6) rails-settings-cached (0.6.6) rails (>= 4.2.0) - railties (5.2.4.1) - actionpack (= 5.2.4.1) - activesupport (= 5.2.4.1) + railties (5.2.4.2) + actionpack (= 5.2.4.2) + activesupport (= 5.2.4.2) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -523,26 +523,26 @@ GEM chunky_png (~> 1.0) rqrcode_core (~> 0.1) rqrcode_core (0.1.1) - rspec-core (3.9.0) - rspec-support (~> 3.9.0) - rspec-expectations (3.9.0) + rspec-core (3.9.1) + rspec-support (~> 3.9.1) + rspec-expectations (3.9.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-mocks (3.9.0) + rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-rails (3.9.0) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-support (~> 3.9.0) + rspec-rails (4.0.0) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.9.0) + rspec-support (3.9.2) rubocop (0.79.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) @@ -556,38 +556,40 @@ GEM ruby-progressbar (1.10.1) ruby-saml (1.9.0) nokogiri (>= 1.5.10) - rufus-scheduler (3.5.2) - fugit (~> 1.1, >= 1.1.5) + rufus-scheduler (3.6.0) + fugit (~> 1.1, >= 1.1.6) safe_yaml (1.0.5) sanitize (5.1.0) crass (~> 1.0.2) nokogiri (>= 1.8.0) nokogumbo (~> 2.0) - sidekiq (5.2.7) - connection_pool (~> 2.2, >= 2.2.2) - rack (>= 1.5.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 5) + sidekiq (6.0.4) + connection_pool (>= 2.2.2) + rack (>= 2.0.0) + rack-protection (>= 2.0.0) + redis (>= 4.1.0) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (3.0.0) + sidekiq-scheduler (3.0.1) + e2mmap redis (>= 3, < 5) rufus-scheduler (~> 3.2) sidekiq (>= 3) + thwait tilt (>= 1.4.0) - sidekiq-unique-jobs (6.0.18) + sidekiq-unique-jobs (6.0.21) concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 4.0, < 7.0) thor (~> 0) simple-navigation (4.1.0) activesupport (>= 2.3.2) - simple_form (5.0.1) + simple_form (5.0.2) actionpack (>= 5.0) activemodel (>= 5.0) - simplecov (0.18.2) + simplecov (0.18.5) docile (~> 1.1) simplecov-html (~> 0.11) - simplecov-html (0.12.0) + simplecov-html (0.12.2) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -595,7 +597,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.20.0) + sshkit (1.21.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.15) @@ -603,7 +605,7 @@ GEM stoplight (2.2.0) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) - strong_migrations (0.5.1) + strong_migrations (0.6.2) activerecord (>= 5) temple (0.8.2) terminal-table (1.8.0) @@ -614,11 +616,11 @@ GEM thread_safe (0.3.6) thwait (0.1.0) tilt (2.0.10) - tty-color (0.5.0) + tty-color (0.5.1) tty-command (0.9.0) pastel (~> 0.7.0) - tty-cursor (0.7.0) - tty-prompt (0.20.0) + tty-cursor (0.7.1) + tty-prompt (0.21.0) necromancer (~> 0.5.0) pastel (~> 0.7.0) tty-reader (~> 0.7.0) @@ -626,10 +628,10 @@ GEM tty-cursor (~> 0.7) tty-screen (~> 0.7) wisper (~> 2.0.0) - tty-screen (0.7.0) + tty-screen (0.7.1) twitter-text (1.14.7) unf (~> 0.1.0) - tzinfo (1.2.6) + tzinfo (1.2.7) thread_safe (~> 0.1) tzinfo-data (1.2019.3) tzinfo (>= 1.0.0) @@ -640,7 +642,7 @@ GEM uniform_notifier (1.13.0) warden (1.2.8) rack (>= 2.0.6) - webmock (3.8.0) + webmock (3.8.3) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -666,35 +668,35 @@ DEPENDENCIES active_record_query_trace (~> 1.7) addressable (~> 2.7) annotate (~> 3.0) - aws-sdk-s3 (~> 1.60) - better_errors (~> 2.5) + aws-sdk-s3 (~> 1.61) + better_errors (~> 2.6) binding_of_caller (~> 0.7) blurhash (~> 0.1) bootsnap (~> 1.4) - brakeman (~> 4.7) + brakeman (~> 4.8) browser bullet (~> 6.1) bundler-audit (~> 0.6) - capistrano (~> 3.11) + capistrano (~> 3.12) capistrano-rails (~> 1.4) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) capybara (~> 3.31) charlock_holmes (~> 0.7.7) chewy (~> 5.1) - cld3 (~> 3.2.6) + cld3 (~> 3.3.0) climate_control (~> 0.2) concurrent-ruby connection_pool devise (~> 4.7) devise-two-factor (~> 3.1) devise_pam_authenticatable2 (~> 9.2) - discard (~> 1.1) - doorkeeper (~> 5.2) + discard (~> 1.2) + doorkeeper (~> 5.3) dotenv-rails (~> 2.7) e2mmap (~> 0.1.0) fabrication (~> 2.21) - faker (~> 2.10) + faker (~> 2.11) fast_blank (~> 1.0) fastimage fog-core (<= 2.1.0) @@ -732,11 +734,11 @@ DEPENDENCIES omniauth (~> 1.9) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) - ox (~> 2.12) + ox (~> 2.13) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) parallel (~> 1.19) - parallel_tests (~> 2.30) + parallel_tests (~> 2.32) parslet pg (~> 1.2) pghero (~> 2.4) @@ -751,7 +753,7 @@ DEPENDENCIES rack (~> 2.2.2) rack-attack (~> 6.2) rack-cors (~> 1.1) - rails (~> 5.2.4) + rails (~> 5.2.4.2) rails-controller-testing (~> 1.0) rails-i18n (~> 5.1) rails-settings-cached (~> 0.6) @@ -761,13 +763,13 @@ DEPENDENCIES redis-namespace (~> 1.7) redis-rails (~> 5.0) rqrcode (~> 1.1) - rspec-rails (~> 3.9) + rspec-rails (~> 4.0) rspec-sidekiq (~> 3.0) rubocop (~> 0.79) rubocop-rails (~> 2.4) ruby-progressbar (~> 1.10) sanitize (~> 5.1) - sidekiq (~> 5.2) + sidekiq (~> 6.0) sidekiq-bulk (~> 0.2.0) sidekiq-scheduler (~> 3.0) sidekiq-unique-jobs (~> 6.0) @@ -779,11 +781,11 @@ DEPENDENCIES stackprof stoplight (~> 2.2.0) streamio-ffmpeg (~> 3.0) - strong_migrations (~> 0.5) + strong_migrations (~> 0.6) thor (~> 0.20) thwait (~> 0.1.0) tty-command (~> 0.9) - tty-prompt (~> 0.20) + tty-prompt (~> 0.21) twitter-text (~> 1.14) tzinfo-data (~> 1.2019) webmock (~> 3.8) diff --git a/README.md b/README.md index 7db66abfa02c14..9ef4acb177b9b9 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Strea **Requirements:** - **PostgreSQL** 9.5+ -- **Redis** -- **Ruby** 2.4+ +- **Redis** 4+ +- **Ruby** 2.5+ - **Node.js** 10.13+ The repository includes deployment configurations for **Docker and docker-compose**, but also a few specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**stand-alone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. diff --git a/Vagrantfile b/Vagrantfile index 79af1dc5d70dbc..7d0f7b3defc1cf 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -91,7 +91,7 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "ubuntu/xenial64" + config.vm.box = "ubuntu/bionic64" config.vm.provider :virtualbox do |vb| vb.name = "mastodon" diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index f5735421c7c90b..bec9ed88b22b94 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -47,6 +47,11 @@ class StatusesIndex < Chewy::Index data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } end + crutch :bookmarks do |collection| + data = ::Bookmark.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id) + data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } + end + root date_detection: false do field :id, type: 'long' field :account_id, type: 'long' diff --git a/app/controllers/account_follow_controller.rb b/app/controllers/account_follow_controller.rb index 185a355f8f3ac0..33394074db4453 100644 --- a/app/controllers/account_follow_controller.rb +++ b/app/controllers/account_follow_controller.rb @@ -6,7 +6,7 @@ class AccountFollowController < ApplicationController before_action :authenticate_user! def create - FollowService.new.call(current_user.account, @account.acct) + FollowService.new.call(current_user.account, @account, with_rate_limit: true) redirect_to account_path(@account) end end diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb index e273dfeaef196f..2d77620df7b8d9 100644 --- a/app/controllers/admin/action_logs_controller.rb +++ b/app/controllers/admin/action_logs_controller.rb @@ -2,8 +2,18 @@ module Admin class ActionLogsController < BaseController - def index - @action_logs = Admin::ActionLog.page(params[:page]) + before_action :set_action_logs + + def index; end + + private + + def set_action_logs + @action_logs = Admin::ActionLogFilter.new(filter_params).results.page(params[:page]) + end + + def filter_params + params.slice(:page, *Admin::ActionLogFilter::KEYS).permit(:page, *Admin::ActionLogFilter::KEYS) end end end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index 9fe85064e34e30..c2591972624e30 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -6,12 +6,12 @@ class EmailDomainBlocksController < BaseController def index authorize :email_domain_block, :index? - @email_domain_blocks = EmailDomainBlock.page(params[:page]) + @email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page]) end def new authorize :email_domain_block, :create? - @email_domain_block = EmailDomainBlock.new + @email_domain_block = EmailDomainBlock.new(domain: params[:_domain]) end def create @@ -21,6 +21,28 @@ def create if @email_domain_block.save log_action :create, @email_domain_block + + if @email_domain_block.with_dns_records? + hostnames = [] + ips = [] + + Resolv::DNS.open do |dns| + dns.timeouts = 1 + + hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } + + ([@email_domain_block.domain] + hostnames).uniq.each do |hostname| + ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s }) + ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s }) + end + end + + (hostnames + ips).each do |hostname| + another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block) + log_action :create, another_email_domain_block if another_email_domain_block.save + end + end + redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg') else render :new @@ -41,7 +63,7 @@ def set_email_domain_block end def resource_params - params.require(:email_domain_block).permit(:domain) + params.require(:email_domain_block).permit(:domain, :with_dns_records) end end end diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb new file mode 100644 index 00000000000000..cacecedb0a1255 --- /dev/null +++ b/app/controllers/admin/site_uploads_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Admin + class SiteUploadsController < BaseController + before_action :set_site_upload + + def destroy + authorize :settings, :destroy? + + @site_upload.destroy! + + redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + end + + private + + def set_site_upload + @site_upload = SiteUpload.find(params[:id]) + end + end +end diff --git a/app/controllers/admin/warning_presets_controller.rb b/app/controllers/admin/warning_presets_controller.rb index 37be842c5b055f..b376f8d9b1e905 100644 --- a/app/controllers/admin/warning_presets_controller.rb +++ b/app/controllers/admin/warning_presets_controller.rb @@ -7,7 +7,7 @@ class WarningPresetsController < BaseController def index authorize :account_warning_preset, :index? - @warning_presets = AccountWarningPreset.all + @warning_presets = AccountWarningPreset.alphabetic @warning_preset = AccountWarningPreset.new end @@ -19,7 +19,7 @@ def create if @warning_preset.save redirect_to admin_warning_presets_path else - @warning_presets = AccountWarningPreset.all + @warning_presets = AccountWarningPreset.alphabetic render :index end end @@ -52,7 +52,7 @@ def set_warning_preset end def warning_preset_params - params.require(:account_warning_preset).permit(:text) + params.require(:account_warning_preset).permit(:title, :text) end end end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 68bf425f4d0254..153ade253d69b0 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -44,6 +44,10 @@ class Api::BaseController < ApplicationController render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503 end + rescue_from Mastodon::RateLimitExceededError do + render json: { error: I18n.t('errors.429') }, status: 429 + end + rescue_from ActionController::ParameterMissing do |e| render json: { error: e.to_s }, status: 400 end diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index e360b8a9296b2c..1daa1ed0d3a393 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController before_action :set_account after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer @@ -27,7 +25,7 @@ def load_accounts end def hide_results? - (@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) + (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index a405b365f2c244..6fc23cf75e562b 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController before_action :set_account after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer @@ -27,7 +25,7 @@ def load_accounts end def hide_results? - (@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) + (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/accounts/identity_proofs_controller.rb b/app/controllers/api/v1/accounts/identity_proofs_controller.rb index bea51ae1194c73..8dad6fee962748 100644 --- a/app/controllers/api/v1/accounts/identity_proofs_controller.rb +++ b/app/controllers/api/v1/accounts/identity_proofs_controller.rb @@ -4,8 +4,6 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController before_action :require_user! before_action :set_account - respond_to :json - def index @proofs = @account.identity_proofs.active render json: @proofs, each_serializer: REST::IdentityProofSerializer diff --git a/app/controllers/api/v1/accounts/lists_controller.rb b/app/controllers/api/v1/accounts/lists_controller.rb index 72392453c4e35b..ccb751f8f7dc64 100644 --- a/app/controllers/api/v1/accounts/lists_controller.rb +++ b/app/controllers/api/v1/accounts/lists_controller.rb @@ -5,8 +5,6 @@ class Api::V1::Accounts::ListsController < Api::BaseController before_action :require_user! before_action :set_account - respond_to :json - def index @lists = @account.lists.where(account: current_account) render json: @lists, each_serializer: REST::ListSerializer diff --git a/app/controllers/api/v1/accounts/pins_controller.rb b/app/controllers/api/v1/accounts/pins_controller.rb index 0a0239c424e8bf..3915b566933501 100644 --- a/app/controllers/api/v1/accounts/pins_controller.rb +++ b/app/controllers/api/v1/accounts/pins_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController before_action :require_user! before_action :set_account - respond_to :json - def create AccountPin.create!(account: current_account, target_account: @account) render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter diff --git a/app/controllers/api/v1/accounts/relationships_controller.rb b/app/controllers/api/v1/accounts/relationships_controller.rb index ab8a0461f5e0d3..1d3992a285770c 100644 --- a/app/controllers/api/v1/accounts/relationships_controller.rb +++ b/app/controllers/api/v1/accounts/relationships_controller.rb @@ -4,8 +4,6 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:follows' } before_action :require_user! - respond_to :json - def index accounts = Account.where(id: account_ids).select('id') # .where doesn't guarantee that our results are in the same order diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb index 4217b527a59377..3061fcb7e715c1 100644 --- a/app/controllers/api/v1/accounts/search_controller.rb +++ b/app/controllers/api/v1/accounts/search_controller.rb @@ -4,8 +4,6 @@ class Api::V1::Accounts::SearchController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:accounts' } before_action :require_user! - respond_to :json - def show @accounts = account_search render json: @accounts, each_serializer: REST::AccountSerializer diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 333db96186cf67..114ee0a8244b9b 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -6,8 +6,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } - respond_to :json - def index @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index d68d2715f71541..0080faf33071c5 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -14,7 +14,7 @@ class Api::V1::AccountsController < Api::BaseController skip_before_action :require_authenticated_user!, only: :create - respond_to :json + override_rate_limit_headers :follow, family: :follows def show render json: @account, serializer: REST::AccountSerializer @@ -31,7 +31,7 @@ def create end def follow - FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs)) + FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true) options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb index 8b63d0490d173a..0475b2d4a208d4 100644 --- a/app/controllers/api/v1/apps/credentials_controller.rb +++ b/app/controllers/api/v1/apps/credentials_controller.rb @@ -3,8 +3,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController before_action -> { doorkeeper_authorize! :read } - respond_to :json - def show render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key) end diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 4cff04cad7fd15..a2baeef900cbf6 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -5,8 +5,6 @@ class Api::V1::BlocksController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index e1b244e76fea8d..c15212f0a91dd4 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -5,8 +5,6 @@ class Api::V1::BookmarksController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers - respond_to :json - def index @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb index b19f27ebfb6c47..bc801337945d6d 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/conversations_controller.rb @@ -9,8 +9,6 @@ class Api::V1::ConversationsController < Api::BaseController before_action :set_conversation, except: :index after_action :insert_pagination_headers, only: :index - respond_to :json - def index @conversations = paginated_conversations render json: @conversations, each_serializer: REST::ConversationSerializer diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index 4e6d5d7c613a65..08b3474cc85865 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::V1::CustomEmojisController < Api::BaseController - respond_to :json - skip_before_action :set_cache_headers def index diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb index af9e7a20f75bf9..5bb02d834cf6fe 100644 --- a/app/controllers/api/v1/domain_blocks_controller.rb +++ b/app/controllers/api/v1/domain_blocks_controller.rb @@ -8,8 +8,6 @@ class Api::V1::DomainBlocksController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers, only: :show - respond_to :json - def show @blocks = load_domain_blocks render json: @blocks.map(&:domain) diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb index 2770c7aef67825..c87dbc4ce836a7 100644 --- a/app/controllers/api/v1/endorsements_controller.rb +++ b/app/controllers/api/v1/endorsements_controller.rb @@ -5,8 +5,6 @@ class Api::V1::EndorsementsController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index db827f9d4aa2aa..3e242905da753f 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -5,8 +5,6 @@ class Api::V1::FavouritesController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers - respond_to :json - def index @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) diff --git a/app/controllers/api/v1/featured_tags/suggestions_controller.rb b/app/controllers/api/v1/featured_tags/suggestions_controller.rb index fb27ef88b93a95..8c1b81a0f007ad 100644 --- a/app/controllers/api/v1/featured_tags/suggestions_controller.rb +++ b/app/controllers/api/v1/featured_tags/suggestions_controller.rb @@ -2,12 +2,9 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index - before_action :require_user! before_action :set_most_used_tags, only: :index - respond_to :json - def index render json: @most_used_tags, each_serializer: REST::TagSerializer end diff --git a/app/controllers/api/v1/filters_controller.rb b/app/controllers/api/v1/filters_controller.rb index e5ebaff4d3b1d1..b0ace3af04c2f7 100644 --- a/app/controllers/api/v1/filters_controller.rb +++ b/app/controllers/api/v1/filters_controller.rb @@ -7,8 +7,6 @@ class Api::V1::FiltersController < Api::BaseController before_action :set_filters, only: :index before_action :set_filter, only: [:show, :update, :destroy] - respond_to :json - def index render json: @filters, each_serializer: REST::FilterSerializer end diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index b30e8464c3eb5d..4f6b4bcbfa0878 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -6,8 +6,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController skip_before_action :set_cache_headers skip_before_action :require_authenticated_user!, unless: :whitelist_mode? - respond_to :json - def show expires_in 1.day, public: true render_with_cache json: :activity, expires_in: 1.day diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index cc00d8a6bc3ae1..9fa4409357b62d 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -6,8 +6,6 @@ class Api::V1::Instances::PeersController < Api::BaseController skip_before_action :set_cache_headers skip_before_action :require_authenticated_user!, unless: :whitelist_mode? - respond_to :json - def index expires_in 1.day, public: true render_with_cache(expires_in: 1.day) { Account.remote.domains } diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index c323b60b4292c8..5b5058a7bf7516 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::V1::InstancesController < Api::BaseController - respond_to :json - skip_before_action :set_cache_headers skip_before_action :require_authenticated_user!, unless: :whitelist_mode? diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index 81825db1552a1d..0bb3d0d27b7ec2 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -3,27 +3,42 @@ class Api::V1::MediaController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action :require_user! - - respond_to :json + before_action :set_media_attachment, except: [:create] + before_action :check_processing, except: [:create] def create - @media = current_account.media_attachments.create!(media_params) - render json: @media, serializer: REST::MediaAttachmentSerializer + @media_attachment = current_account.media_attachments.create!(media_attachment_params) + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer rescue Paperclip::Errors::NotIdentifiedByImageMagickError render json: file_type_error, status: 422 rescue Paperclip::Error render json: processing_error, status: 500 end + def show + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment + end + def update - @media = current_account.media_attachments.where(status_id: nil).find(params[:id]) - @media.update!(media_params) - render json: @media, serializer: REST::MediaAttachmentSerializer + @media_attachment.update!(media_attachment_params) + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment end private - def media_params + def status_code_for_media_attachment + @media_attachment.not_processed? ? 206 : 200 + end + + def set_media_attachment + @media_attachment = current_account.media_attachments.unattached.find(params[:id]) + end + + def check_processing + render json: processing_error, status: 422 if @media_attachment.processing_failed? + end + + def media_attachment_params params.permit(:file, :description, :focus) end diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index df6c8e86c59609..65439fe9bc1505 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -5,8 +5,6 @@ class Api::V1::MutesController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index bf3002e79f5a52..8ac22776500b9b 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -6,8 +6,6 @@ class Api::V1::NotificationsController < Api::BaseController before_action :require_user! after_action :insert_pagination_headers, only: :index - respond_to :json - DEFAULT_NOTIFICATIONS_LIMIT = 15 def index diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index 3fa0b6a760787c..e1d26106afb16f 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Polls::VotesController < Api::BaseController before_action :require_user! before_action :set_poll - respond_to :json - def create VoteService.new.call(current_account, @poll, vote_params[:choices]) render json: @poll, serializer: REST::PollSerializer diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index 031e6d42d668e3..744baf7bb428a6 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -7,8 +7,6 @@ class Api::V1::PollsController < Api::BaseController before_action :set_poll before_action :refresh_poll - respond_to :json - def show render json: @poll, serializer: REST::PollSerializer, include_results: true end diff --git a/app/controllers/api/v1/preferences_controller.rb b/app/controllers/api/v1/preferences_controller.rb index 077d39f5d740a4..1640a8224be681 100644 --- a/app/controllers/api/v1/preferences_controller.rb +++ b/app/controllers/api/v1/preferences_controller.rb @@ -4,8 +4,6 @@ class Api::V1::PreferencesController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:accounts' } before_action :require_user! - respond_to :json - def index render json: current_account, serializer: REST::PreferencesSerializer end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 1b0b4b05b2e72d..e10083d450f1a5 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -4,7 +4,7 @@ class Api::V1::ReportsController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create] before_action :require_user! - respond_to :json + override_rate_limit_headers :create, family: :reports def create @report = ReportService.new.call( diff --git a/app/controllers/api/v1/statuses/bookmarks_controller.rb b/app/controllers/api/v1/statuses/bookmarks_controller.rb index a7f1eed003be2e..3954af3c9bf2f1 100644 --- a/app/controllers/api/v1/statuses/bookmarks_controller.rb +++ b/app/controllers/api/v1/statuses/bookmarks_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController before_action :require_user! before_action :set_status - respond_to :json - def create current_account.bookmarks.find_or_create_by!(account: current_account, status: @status) render json: @status, serializer: REST::StatusSerializer diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index 05f4acc331b89d..8229786d6cccab 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController before_action :set_status after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer diff --git a/app/controllers/api/v1/statuses/favourites_controller.rb b/app/controllers/api/v1/statuses/favourites_controller.rb index f18ace996c101c..7afa822ed8740c 100644 --- a/app/controllers/api/v1/statuses/favourites_controller.rb +++ b/app/controllers/api/v1/statuses/favourites_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController before_action :require_user! before_action :set_status - respond_to :json - def create FavouriteService.new.call(current_account, @status) render json: @status, serializer: REST::StatusSerializer diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb index b02469b4f4b674..43c7a525ad646b 100644 --- a/app/controllers/api/v1/statuses/mutes_controller.rb +++ b/app/controllers/api/v1/statuses/mutes_controller.rb @@ -8,8 +8,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController before_action :set_status before_action :set_conversation - respond_to :json - def create current_account.mute_conversation!(@conversation) @mutes_map = { @conversation.id => true } diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb index 4118a8ce4e51be..51b1621b6f125a 100644 --- a/app/controllers/api/v1/statuses/pins_controller.rb +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController before_action :require_user! before_action :set_status - respond_to :json - def create StatusPin.create!(account: current_account, status: @status) distribute_add_activity! diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index fa60e7d84685e8..6c9e49d903a412 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -7,8 +7,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController before_action :set_status after_action :insert_pagination_headers - respond_to :json - def index @accounts = load_accounts render json: @accounts, each_serializer: REST::AccountSerializer diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index 67106ccbe8101a..7fa774a4d727b6 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -7,10 +7,11 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController before_action :require_user! before_action :set_reblog - respond_to :json + override_rate_limit_headers :create, family: :statuses def create @status = ReblogService.new.call(current_account, @reblog, reblog_params) + render json: @status, serializer: REST::StatusSerializer end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index bba3c0651beedb..93a253cbb2434c 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -7,8 +7,9 @@ class Api::V1::StatusesController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy] before_action :require_user!, except: [:show, :context] before_action :set_status, only: [:show, :context] + before_action :set_thread, only: [:create] - respond_to :json + override_rate_limit_headers :create, family: :statuses # This API was originally unlimited, pagination cannot be introduced without # breaking backwards-compatibility. Arbitrarily high number to cover most @@ -36,7 +37,7 @@ def context def create @status = PostStatusService.new.call(current_user.account, text: status_params[:status], - thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), + thread: @thread, media_ids: status_params[:media_ids], sensitive: status_params[:sensitive], spoiler_text: status_params[:spoiler_text], @@ -44,7 +45,8 @@ def create scheduled_at: status_params[:scheduled_at], application: doorkeeper_token.application, poll: status_params[:poll], - idempotency: request.headers['Idempotency-Key']) + idempotency: request.headers['Idempotency-Key'], + with_rate_limit: true) render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer end @@ -68,6 +70,12 @@ def set_status raise ActiveRecord::RecordNotFound end + def set_thread + @thread = status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]) + rescue ActiveRecord::RecordNotFound + render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404 + end + def status_params params.permit( :status, diff --git a/app/controllers/api/v1/streaming_controller.rb b/app/controllers/api/v1/streaming_controller.rb index ebb17608c107df..7cd60615ab554f 100644 --- a/app/controllers/api/v1/streaming_controller.rb +++ b/app/controllers/api/v1/streaming_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::V1::StreamingController < Api::BaseController - respond_to :json - def index if Rails.configuration.x.streaming_api_base_url != request.host redirect_to streaming_api_url, status: 301 diff --git a/app/controllers/api/v1/suggestions_controller.rb b/app/controllers/api/v1/suggestions_controller.rb index 9da2b60ae206e7..52054160dfc3c8 100644 --- a/app/controllers/api/v1/suggestions_controller.rb +++ b/app/controllers/api/v1/suggestions_controller.rb @@ -7,8 +7,6 @@ class Api::V1::SuggestionsController < Api::BaseController before_action :require_user! before_action :set_accounts - respond_to :json - def index render json: @accounts, each_serializer: REST::AccountSerializer end diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index ff5ede1385234e..ae6dbcb8b378ee 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -5,8 +5,6 @@ class Api::V1::Timelines::HomeController < Api::BaseController before_action :require_user!, only: [:show] after_action :insert_pagination_headers, unless: -> { @statuses.empty? } - respond_to :json - def show @statuses = load_statuses diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index ccc10f966ca647..581befef13e084 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -4,8 +4,6 @@ class Api::V1::Timelines::PublicController < Api::BaseController before_action :require_user!, only: [:show], if: :require_auth? after_action :insert_pagination_headers, unless: -> { @statuses.empty? } - respond_to :json - def show @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 9adc4ad2912924..2d6ad5a80c5bc3 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -4,8 +4,6 @@ class Api::V1::Timelines::TagController < Api::BaseController before_action :load_tag after_action :insert_pagination_headers, unless: -> { @statuses.empty? } - respond_to :json - def show @statuses = load_statuses render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) diff --git a/app/controllers/api/v1/trends_controller.rb b/app/controllers/api/v1/trends_controller.rb index bcea9857e8fa71..c875e90414de37 100644 --- a/app/controllers/api/v1/trends_controller.rb +++ b/app/controllers/api/v1/trends_controller.rb @@ -3,8 +3,6 @@ class Api::V1::TrendsController < Api::BaseController before_action :set_tags - respond_to :json - def index render json: @tags, each_serializer: REST::TagSerializer end diff --git a/app/controllers/api/v2/media_controller.rb b/app/controllers/api/v2/media_controller.rb new file mode 100644 index 00000000000000..0c1baf01d0bbd6 --- /dev/null +++ b/app/controllers/api/v2/media_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Api::V2::MediaController < Api::V1::MediaController + def create + @media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params)) + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: 202 + rescue Paperclip::Errors::NotIdentifiedByImageMagickError + render json: file_type_error, status: 422 + rescue Paperclip::Error + render json: processing_error, status: 500 + end +end diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index cbd9b551d091ba..f17431dd10b8e9 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -8,8 +8,6 @@ class Api::V2::SearchController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:search' } before_action :require_user! - respond_to :json - def index @search = Search.new(search_results) render json: @search, serializer: REST::SearchSerializer diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index 4aa31695c70146..741ba910fb8769 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::Web::EmbedsController < Api::Web::BaseController - respond_to :json - before_action :require_user! def create diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index f388b17e5de2c2..7916b82fa08153 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::Web::PushSubscriptionsController < Api::Web::BaseController - respond_to :json - before_action :require_user! def create diff --git a/app/controllers/api/web/settings_controller.rb b/app/controllers/api/web/settings_controller.rb index e3178bf48bdd66..3d65e46ed95717 100644 --- a/app/controllers/api/web/settings_controller.rb +++ b/app/controllers/api/web/settings_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Api::Web::SettingsController < Api::Web::BaseController - respond_to :json - before_action :require_user! def update diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0cfa2b38606f2e..973db6aca97654 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,6 +29,7 @@ class ApplicationController < ActionController::Base rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error rescue_from Mastodon::RaceConditionError, with: :service_unavailable + rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? before_action :require_functional!, if: :user_signed_in? @@ -111,6 +112,10 @@ def service_unavailable respond_with_error(503) end + def too_many_requests + respond_with_error(429) + end + def single_user_mode? @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists? end diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb index e27366ea363880..29c0288d09dc9c 100644 --- a/app/controllers/authorize_interactions_controller.rb +++ b/app/controllers/authorize_interactions_controller.rb @@ -20,7 +20,7 @@ def show end def create - if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource) + if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource, with_rate_limit: true) render :success else render :error diff --git a/app/controllers/concerns/rate_limit_headers.rb b/app/controllers/concerns/rate_limit_headers.rb index b79c558d8157ec..86fe58a71c9964 100644 --- a/app/controllers/concerns/rate_limit_headers.rb +++ b/app/controllers/concerns/rate_limit_headers.rb @@ -3,6 +3,20 @@ module RateLimitHeaders extend ActiveSupport::Concern + class_methods do + def override_rate_limit_headers(method_name, options = {}) + around_action(only: method_name, if: :current_account) do |_controller, block| + begin + block.call + ensure + rate_limiter = RateLimiter.new(current_account, options) + rate_limit_headers = rate_limiter.to_headers + response.headers.merge!(rate_limit_headers) unless response.headers['X-RateLimit-Remaining'].present? && rate_limit_headers['X-RateLimit-Remaining'].to_i > response.headers['X-RateLimit-Remaining'].to_i + end + end + end + end + included do before_action :set_rate_limit_headers, if: :rate_limited_request? end @@ -44,7 +58,7 @@ def rate_limit_reset end def api_throttle_data - most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] } + most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] } request.env['rack.attack.throttle_data'][most_limited_type] end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 7103749adecf3d..14e22dd1ec1bce 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -28,7 +28,8 @@ def index render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' + content_type: 'application/activity+json', + fields: restrict_fields_to end end end @@ -71,4 +72,12 @@ def collection_presenter ) end end + + def restrict_fields_to + if page_requested? || !@account.user_hides_network? + # Return all fields + else + %i(id type totalItems) + end + end end diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 6c8fb84d86b47c..95849ffb983ac6 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -28,7 +28,8 @@ def index render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' + content_type: 'application/activity+json', + fields: restrict_fields_to end end end @@ -71,4 +72,12 @@ def collection_presenter ) end end + + def restrict_fields_to + if page_requested? || !@account.user_hides_network? + # Return all fields + else + %i(id type totalItems) + end + end end diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index 38f2e39c1d179c..7b8c4ae235050a 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -29,6 +29,6 @@ def set_account end def import_params - params.require(:import).permit(:data, :type) + params.require(:import).permit(:data, :type, :mode) end end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 6bc75aa56658f3..88d6e4580d3250 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -9,79 +9,8 @@ def log_target(log) end end - def relevant_log_changes(log) - if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action) - log.recorded_changes.slice('domain') - elsif log.target_type == 'CustomEmoji' && log.action == :update - log.recorded_changes.slice('domain', 'visible_in_picker') - elsif log.target_type == 'User' && [:promote, :demote].include?(log.action) - log.recorded_changes.slice('moderator', 'admin') - elsif log.target_type == 'User' && [:change_email].include?(log.action) - log.recorded_changes.slice('email', 'unconfirmed_email') - elsif log.target_type == 'DomainBlock' - log.recorded_changes.slice('severity', 'reject_media') - elsif log.target_type == 'Status' && log.action == :update - log.recorded_changes.slice('sensitive') - elsif log.target_type == 'Announcement' && log.action == :update - log.recorded_changes.slice('text', 'starts_at', 'ends_at', 'all_day') - end - end - - def log_extra_attributes(hash) - safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ') - end - - def log_change(val) - return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array) - safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→') - end - - def icon_for_log(log) - case log.target_type - when 'Account', 'User' - 'user' - when 'CustomEmoji' - 'file' - when 'Report' - 'flag' - when 'DomainBlock' - 'lock' - when 'DomainAllow' - 'plus-circle' - when 'EmailDomainBlock' - 'envelope' - when 'Status' - 'pencil' - when 'AccountWarning' - 'warning' - when 'Announcement' - 'bullhorn' - end - end - - def class_for_log_icon(log) - case log.action - when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve - 'positive' - when :create - opposite_verbs?(log) ? 'negative' : 'positive' - when :update, :reset_password, :disable_2fa, :memorialize, :change_email - 'neutral' - when :demote, :silence, :disable, :suspend, :remove_avatar, :remove_header, :reopen - 'negative' - when :destroy - opposite_verbs?(log) ? 'positive' : 'negative' - else - '' - end - end - private - def opposite_verbs?(log) - %w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type) - end - def linkable_log_target(record) case record.class.name when 'Account' @@ -99,7 +28,7 @@ def linkable_log_target(record) when 'AccountWarning' link_to record.target_account.acct, admin_account_path(record.target_account_id) when 'Announcement' - link_to "##{record.id}", edit_admin_announcement_path(record.id) + link_to truncate(record.text), edit_admin_announcement_path(record.id) end end @@ -118,7 +47,7 @@ def log_target_from_history(type, attributes) I18n.t('admin.action_logs.deleted_status') end when 'Announcement' - "##{attributes['id']}" + truncate(attributes['text']) end end end diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 6ab92939d82a6b..ba0ca963895efb 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -10,6 +10,7 @@ module Admin::FilterHelper InviteFilter::KEYS, RelationshipFilter::KEYS, AnnouncementFilter::KEYS, + Admin::ActionLogFilter::KEYS, ].flatten.freeze def filter_link_to(text, link_to_params, link_class_params = link_to_params) diff --git a/app/helpers/admin/settings_helper.rb b/app/helpers/admin/settings_helper.rb new file mode 100644 index 00000000000000..baf14ab2574e3f --- /dev/null +++ b/app/helpers/admin/settings_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Admin::SettingsHelper + def site_upload_delete_hint(hint, var) + upload = SiteUpload.find_by(var: var.to_s) + return hint unless upload + + link = link_to t('admin.site_uploads.delete'), admin_site_upload_path(upload), data: { method: :delete } + safe_join([hint, link], '
'.html_safe) + end +end diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index d4a824e2c9d646..cb2c682a45ba18 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -106,7 +106,7 @@ export function fetchAccount(id) { dispatch, getState, db.transaction('accounts', 'read').objectStore('accounts').index('id'), - id + id, ).then(() => db.close(), error => { db.close(); throw error; @@ -396,6 +396,7 @@ export function fetchFollowersFail(id, error) { type: FOLLOWERS_FETCH_FAIL, id, error, + skipNotFound: true, }; }; @@ -482,6 +483,7 @@ export function fetchFollowingFail(id, error) { type: FOLLOWING_FETCH_FAIL, id, error, + skipNotFound: true, }; }; @@ -571,6 +573,7 @@ export function fetchRelationshipsFail(error) { type: RELATIONSHIPS_FETCH_FAIL, error, skipLoading: true, + skipNotFound: true, }; }; diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index cd36d8007bccee..1670f9c10de9f5 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -34,11 +34,11 @@ export function showAlert(title = messages.unexpectedTitle, message = messages.u }; }; -export function showAlertForError(error) { +export function showAlertForError(error, skipNotFound = false) { if (error.response) { const { data, status, statusText, headers } = error.response; - if (status === 404 || status === 410) { + if (skipNotFound && (status === 404 || status === 410)) { // Skip these errors as they are reflected in the UI return { type: ALERT_NOOP }; } diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index c3c6ff1a1de704..6b73fc90e09a18 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -230,12 +230,31 @@ export function uploadCompose(files) { // Account for disparity in size of original image and resized data total += file.size - f.size; - return api(getState).post('/api/v1/media', data, { + return api(getState).post('/api/v2/media', data, { onUploadProgress: function({ loaded }){ progress[i] = loaded; dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); }, - }).then(({ data }) => dispatch(uploadComposeSuccess(data, f))); + }).then(({ status, data }) => { + // If server-side processing of the media attachment has not completed yet, + // poll the server until it is, before showing the media attachment as uploaded + + if (status === 200) { + dispatch(uploadComposeSuccess(data, f)); + } else if (status === 202) { + const poll = () => { + api(getState).get(`/api/v1/media/${data.id}`).then(response => { + if (response.status === 200) { + dispatch(uploadComposeSuccess(response.data, f)); + } else if (response.status === 206) { + setTimeout(() => poll(), 1000); + } + }).catch(error => dispatch(uploadComposeFail(error))); + }; + + poll(); + } + }); }).catch(error => dispatch(uploadComposeFail(error))); }; }; diff --git a/app/javascript/mastodon/actions/identity_proofs.js b/app/javascript/mastodon/actions/identity_proofs.js index 449debf616fa5f..10398395660345 100644 --- a/app/javascript/mastodon/actions/identity_proofs.js +++ b/app/javascript/mastodon/actions/identity_proofs.js @@ -27,4 +27,5 @@ export const fetchAccountIdentityProofsFail = (accountId, err) => ({ type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL, accountId, err, + skipNotFound: true, }); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 05466865597d47..cdd2111f8e539a 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -149,6 +149,7 @@ export function expandTimelineFail(timeline, error, isLoadingMore) { timeline, error, skipLoading: !isLoadingMore, + skipNotFound: timeline.startsWith('account:'), }; }; diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js index fba21316a77dc9..6818aa5d540ddb 100644 --- a/app/javascript/mastodon/common.js +++ b/app/javascript/mastodon/common.js @@ -1,4 +1,4 @@ -import Rails from 'rails-ujs'; +import Rails from '@rails/ujs'; export function start() { require('font-awesome/css/font-awesome.css'); diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index ea82f9ef9d3fe3..1bb583583a3558 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -76,8 +76,9 @@ class ColumnHeader extends React.PureComponent { handlePin = () => { if (!this.props.pinned) { - this.historyBack(); + this.context.router.history.replace('/'); } + this.props.onPin(); } diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js index 85729ca9438ea2..697065d874d1f6 100644 --- a/app/javascript/mastodon/components/domain.js +++ b/app/javascript/mastodon/components/domain.js @@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ - unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, }); export default @injectIntl diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js index e453730ba4fccd..124b34b02f9eca 100644 --- a/app/javascript/mastodon/components/intersection_observer_article.js +++ b/app/javascript/mastodon/components/intersection_observer_article.js @@ -44,7 +44,7 @@ export default class IntersectionObserverArticle extends React.Component { intersectionObserverWrapper.observe( id, this.node, - this.handleIntersection + this.handleIntersection, ); this.componentMounted = true; diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index cfe164a507d822..283d7e0a502634 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -10,7 +10,7 @@ import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_s import { decode } from 'blurhash'; const messages = defineMessages({ - toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, + toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide media' }, }); class Item extends React.PureComponent { diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js index 3a17e80e726795..7525a1030a5d5f 100644 --- a/app/javascript/mastodon/components/poll.js +++ b/app/javascript/mastodon/components/poll.js @@ -127,15 +127,7 @@ class Poll extends ImmutablePureComponent { return (
  • - {showResults && ( - - {({ width }) => - - } - - )} - -
  • ); } diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index 3a490e78ea76ad..65ca43911c78ef 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -82,15 +82,19 @@ export default class ScrollableList extends PureComponent { lastScrollWasSynthetic = false; scrollToTopOnMouseIdle = false; + _getScrollingElement = () => { + if (this.props.bindToDocument) { + return (document.scrollingElement || document.body); + } else { + return this.node; + } + } + setScrollTop = newScrollTop => { if (this.getScrollTop() !== newScrollTop) { this.lastScrollWasSynthetic = true; - if (this.props.bindToDocument) { - document.scrollingElement.scrollTop = newScrollTop; - } else { - this.node.scrollTop = newScrollTop; - } + this._getScrollingElement().scrollTop = newScrollTop; } }; @@ -151,15 +155,15 @@ export default class ScrollableList extends PureComponent { } getScrollTop = () => { - return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop; + return this._getScrollingElement().scrollTop; } getScrollHeight = () => { - return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight; + return this._getScrollingElement().scrollHeight; } getClientHeight = () => { - return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight; + return this._getScrollingElement().clientHeight; } updateScrollBottom = (snapshot) => { diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 0dc00cb98d59b5..075ee1b870c6db 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -432,16 +432,10 @@ class Status extends ImmutablePureComponent { - + {media} - {showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && ( - - )} - diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index ea303a58ba1994..c3fc6c8536c521 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -36,8 +36,8 @@ const messages = defineMessages({ admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to status' }, - blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, - unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, + blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, }); diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 5d921fd412f42d..3200f2d82f6d7f 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -20,6 +20,7 @@ export default class StatusContent extends React.PureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, expanded: PropTypes.bool, + showThread: PropTypes.bool, onExpandedToggle: PropTypes.func, onClick: PropTypes.func, collapsable: PropTypes.bool, @@ -181,6 +182,7 @@ export default class StatusContent extends React.PureComponent { const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const renderReadMore = this.props.onClick && status.get('collapsed'); + const renderViewThread = this.props.showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']); const content = { __html: status.get('contentHtml') }; const spoilerContent = { __html: status.get('spoilerHtml') }; @@ -195,6 +197,12 @@ export default class StatusContent extends React.PureComponent { directionStyle.direction = 'rtl'; } + const showThreadButton = ( + + ); + const readMoreButton = ( - + +
      @@ -236,7 +236,7 @@ class Audio extends React.PureComponent {
    - + {/* eslint-disable-next-line jsx-a11y/no-onchange */}