Merge branch 'hometown-dev' into hometown-dev-max-chars-patch

This commit is contained in:
sable 2020-06-30 21:11:37 -07:00 committed by GitHub
commit 7c5f812c92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2110 changed files with 48360 additions and 15273 deletions

View File

@ -3,13 +3,15 @@ version: 2
aliases: aliases:
- &defaults - &defaults
docker: docker:
- image: circleci/ruby:2.6-stretch-node - image: circleci/ruby:2.7-buster-node
environment: &ruby_environment environment: &ruby_environment
BUNDLE_JOBS: 3
BUNDLE_RETRY: 3
BUNDLE_APP_CONFIG: ./.bundle/ BUNDLE_APP_CONFIG: ./.bundle/
BUNDLE_PATH: ./vendor/bundle/
DB_HOST: localhost DB_HOST: localhost
DB_USER: root DB_USER: root
RAILS_ENV: test RAILS_ENV: test
PARALLEL_TEST_PROCESSORS: 4
ALLOW_NOPAM: true ALLOW_NOPAM: true
CONTINUOUS_INTEGRATION: true CONTINUOUS_INTEGRATION: true
DISABLE_SIMPLECOV: true DISABLE_SIMPLECOV: true
@ -31,25 +33,25 @@ aliases:
- &restore_ruby_dependencies - &restore_ruby_dependencies
restore_cache: restore_cache:
keys: keys:
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} - v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- - v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-
- v2-ruby-dependencies- - v3-ruby-dependencies-
- &install_steps - &install_steps
steps: steps:
- checkout - checkout
- *attach_workspace - *attach_workspace
- restore_cache: - restore_cache:
keys: keys:
- v1-node-dependencies-{{ checksum "yarn.lock" }} - v2-node-dependencies-{{ checksum "yarn.lock" }}
- v1-node-dependencies- - v2-node-dependencies-
- run: yarn install --frozen-lockfile - run:
name: Install yarn dependencies
command: yarn install --frozen-lockfile
- save_cache: - save_cache:
key: v1-node-dependencies-{{ checksum "yarn.lock" }} key: v2-node-dependencies-{{ checksum "yarn.lock" }}
paths: paths:
- ./node_modules/ - ./node_modules/
- *persist_to_workspace - *persist_to_workspace
- &install_system_dependencies - &install_system_dependencies
@ -62,14 +64,24 @@ aliases:
- &install_ruby_dependencies - &install_ruby_dependencies
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run:
- run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version name: Set Ruby version
command: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
- *restore_ruby_dependencies - *restore_ruby_dependencies
- run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && 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: - 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: paths:
- ./.bundle/ - ./.bundle/
- ./vendor/bundle/ - ./vendor/bundle/
@ -80,39 +92,39 @@ aliases:
- ./mastodon/vendor/bundle/ - ./mastodon/vendor/bundle/
- &test_steps - &test_steps
parallelism: 4
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: sudo apt-get install -y ffmpeg
- run: - run:
name: Prepare Tests name: Install FFMPEG
command: ./bin/rails parallel:create parallel:load_schema parallel:prepare command: sudo apt-get install -y ffmpeg
- run: - run:
name: Run Tests name: Load database schema
command: ./bin/retry bundle exec parallel_test ./spec/ --group-by filesize --type rspec 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: jobs:
install: install:
<<: *defaults <<: *defaults
<<: *install_steps <<: *install_steps
install-ruby2.7:
<<: *defaults
<<: *install_ruby_dependencies
install-ruby2.6: install-ruby2.6:
<<: *defaults
<<: *install_ruby_dependencies
install-ruby2.5:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.5-stretch-node - image: circleci/ruby:2.6-buster-node
environment: *ruby_environment
<<: *install_ruby_dependencies
install-ruby2.4:
<<: *defaults
docker:
- image: circleci/ruby:2.4-stretch-node
environment: *ruby_environment environment: *ruby_environment
<<: *install_ruby_dependencies <<: *install_ruby_dependencies
@ -121,98 +133,116 @@ jobs:
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: ./bin/rails assets:precompile - run:
name: Precompile assets
command: ./bin/rails assets:precompile
- persist_to_workspace: - persist_to_workspace:
root: ~/projects/ root: ~/projects/
paths: paths:
- ./mastodon/public/assets - ./mastodon/public/assets
- ./mastodon/public/packs-test/ - ./mastodon/public/packs-test/
test-migrations:
<<: *defaults
docker:
- image: circleci/ruby:2.7-buster-node
environment: *ruby_environment
- image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
steps:
- *attach_workspace
- *install_system_dependencies
- run:
name: Create database
command: ./bin/rails db:create
- run:
name: Run migrations
command: ./bin/rails db:migrate
test-ruby2.7:
<<: *defaults
docker:
- image: circleci/ruby:2.7-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-ruby2.6: test-ruby2.6:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.6-stretch-node - image: circleci/ruby:2.6-buster-node
environment: *ruby_environment environment: *ruby_environment
- image: circleci/postgres:10.6-alpine - image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
- image: circleci/redis:5-alpine
<<: *test_steps
test-ruby2.5:
<<: *defaults
docker:
- image: circleci/ruby:2.5-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.6-alpine
environment:
POSTGRES_USER: root
- image: circleci/redis:5-alpine
<<: *test_steps
test-ruby2.4:
<<: *defaults
docker:
- image: circleci/ruby:2.4-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.6-alpine
environment: environment:
POSTGRES_USER: root POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine - image: circleci/redis:5-alpine
<<: *test_steps <<: *test_steps
test-webui: test-webui:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/node:12.9-stretch - image: circleci/node:12-buster
steps: steps:
- *attach_workspace - *attach_workspace
- run: ./bin/retry yarn test:jest - run:
name: Run jest
command: yarn test:jest
check-i18n: check-i18n:
<<: *defaults <<: *defaults
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: bundle exec i18n-tasks check-normalized - run:
- run: bundle exec i18n-tasks unused -l en name: Check locale file normalization
- run: bundle exec i18n-tasks check-consistent-interpolations command: bundle exec i18n-tasks check-normalized
- run: bundle exec rake repo:check_locales_files - 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: workflows:
version: 2 version: 2
build-and-test: build-and-test:
jobs: jobs:
- install - install
- install-ruby2.7:
requires:
- install
- install-ruby2.6: - install-ruby2.6:
requires: requires:
- install - install
- install-ruby2.5: - install-ruby2.7
requires:
- install
- install-ruby2.6
- install-ruby2.4:
requires:
- install
- install-ruby2.6
- build: - build:
requires: requires:
- install-ruby2.6 - install-ruby2.7
- test-migrations:
requires:
- install-ruby2.7
- test-ruby2.7:
requires:
- install-ruby2.7
- build
- test-ruby2.6: - test-ruby2.6:
requires: requires:
- install-ruby2.6 - install-ruby2.6
- build - build
- test-ruby2.5:
requires:
- install-ruby2.5
- build
- test-ruby2.4:
requires:
- install-ruby2.4
- build
- test-webui: - test-webui:
requires: requires:
- install - install
- check-i18n: - check-i18n:
requires: requires:
- install-ruby2.6 - install-ruby2.7

View File

@ -27,10 +27,10 @@ plugins:
enabled: true enabled: true
eslint: eslint:
enabled: true enabled: true
channel: eslint-5 channel: eslint-6
rubocop: rubocop:
enabled: true enabled: true
channel: rubocop-0-71 channel: rubocop-0-82
sass-lint: sass-lint:
enabled: true enabled: true
exclude_patterns: exclude_patterns:

View File

@ -4,7 +4,25 @@ update_configs:
- package_manager: "ruby:bundler" - package_manager: "ruby:bundler"
directory: "/" directory: "/"
update_schedule: "weekly" update_schedule: "weekly"
# Supported update schedule: live daily weekly monthly
version_requirement_updates: "auto"
# Supported version requirements: auto widen_ranges increase_versions increase_versions_if_necessary
allowed_updates:
- match:
dependency_type: "all"
# Supported dependency types: all indirect direct production development
update_type: "all"
# Supported update types: all security
- package_manager: "javascript" - package_manager: "javascript"
directory: "/" directory: "/"
update_schedule: "weekly" update_schedule: "weekly"
# Supported update schedule: live daily weekly monthly
version_requirement_updates: "auto"
# Supported version requirements: auto widen_ranges increase_versions increase_versions_if_necessary
allowed_updates:
- match:
dependency_type: "all"
# Supported dependency types: all indirect direct production development
update_type: "all"
# Supported update types: all security

View File

@ -183,6 +183,11 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# LDAP_BIND_DN= # LDAP_BIND_DN=
# LDAP_PASSWORD= # LDAP_PASSWORD=
# LDAP_UID=cn # LDAP_UID=cn
# LDAP_MAIL=mail
# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email}))
# LDAP_UID_CONVERSION_ENABLED=true
# LDAP_UID_CONVERSION_SEARCH=., -
# LDAP_UID_CONVERSION_REPLACE=_
# PAM authentication (optional) # PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable # PAM authentication uses for the email generation the "email" pam variable
@ -226,8 +231,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# Optional SAML authentication (cf. omniauth-saml) # Optional SAML authentication (cf. omniauth-saml)
# SAML_ENABLED=true # SAML_ENABLED=true
# SAML_ACS_URL= # SAML_ACS_URL=http://localhost:3000/auth/auth/saml/callback
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback # SAML_ISSUER=https://example.com
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO # SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
# SAML_IDP_CERT= # SAML_IDP_CERT=
# SAML_IDP_CERT_FINGERPRINT= # SAML_IDP_CERT_FINGERPRINT=

View File

@ -33,7 +33,7 @@ LOCAL_DOMAIN=example.com
# ALTERNATE_DOMAINS=example1.com,example2.com # ALTERNATE_DOMAINS=example1.com,example2.com
# Application secrets # Application secrets
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose) # Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web bundle exec rake secret` if you use docker compose)
SECRET_KEY_BASE= SECRET_KEY_BASE=
OTP_SECRET= OTP_SECRET=
@ -42,7 +42,7 @@ OTP_SECRET=
# You should only generate this once per instance. If you later decide to change it, all push subscription will # You should only generate this once per instance. If you later decide to change it, all push subscription will
# be invalidated, requiring the users to access the website again to resubscribe. # be invalidated, requiring the users to access the website again to resubscribe.
# #
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose) # Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key` if you use docker compose)
# #
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html # For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
VAPID_PRIVATE_KEY= VAPID_PRIVATE_KEY=
@ -181,7 +181,11 @@ STREAMING_CLUSTER_NUM=1
# LDAP_BIND_DN= # LDAP_BIND_DN=
# LDAP_PASSWORD= # LDAP_PASSWORD=
# LDAP_UID=cn # LDAP_UID=cn
# LDAP_SEARCH_FILTER=%{uid}=%{email} # LDAP_MAIL=mail
# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email}))
# LDAP_UID_CONVERSION_ENABLED=true
# LDAP_UID_CONVERSION_SEARCH=., -
# LDAP_UID_CONVERSION_REPLACE=_
# PAM authentication (optional) # PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable # PAM authentication uses for the email generation the "email" pam variable
@ -225,8 +229,8 @@ STREAMING_CLUSTER_NUM=1
# Optional SAML authentication (cf. omniauth-saml) # Optional SAML authentication (cf. omniauth-saml)
# SAML_ENABLED=true # SAML_ENABLED=true
# SAML_ACS_URL= # SAML_ACS_URL=http://localhost:3000/auth/auth/saml/callback
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback # SAML_ISSUER=https://example.com
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO # SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
# SAML_IDP_CERT= # SAML_IDP_CERT=
# SAML_IDP_CERT_FINGERPRINT= # SAML_IDP_CERT_FINGERPRINT=
@ -249,3 +253,13 @@ STREAMING_CLUSTER_NUM=1
# http_proxy=http://gateway.local:8118 # http_proxy=http://gateway.local:8118
# Access control for hidden service. # Access control for hidden service.
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true # ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
# Authorized fetch mode (optional)
# Require remote servers to authentify when fetching toots, see
# https://docs.joinmastodon.org/admin/config/#authorized_fetch
# AUTHORIZED_FETCH=true
# Whitelist mode (optional)
# Only allow federation with whitelisted domains, see
# https://docs.joinmastodon.org/admin/config/#whitelist_mode
# WHITELIST_MODE=true

View File

@ -1,5 +1,5 @@
# Node.js # Node.js
NODE_ENV=test NODE_ENV=tests
# Federation # Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true LOCAL_HTTPS=true

View File

@ -1,2 +1,3 @@
VAGRANT=true VAGRANT=true
LOCAL_DOMAIN=mastodon.local LOCAL_DOMAIN=mastodon.local
BIND=0.0.0.0

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Mastodon Meta Discussion Board
url: https://discourse.joinmastodon.org/
about: Please ask and answer questions here.

10
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,10 @@
daysUntilStale: 120
daysUntilClose: 7
exemptLabels:
- security
staleLabel: wontfix
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
only: pulls

6
.gitignore vendored
View File

@ -13,6 +13,7 @@
/db/*.sqlite3-journal /db/*.sqlite3-journal
# Ignore all logfiles and tempfiles. # Ignore all logfiles and tempfiles.
.eslintcache
/log/* /log/*
!/log/.keep !/log/.keep
/tmp /tmp
@ -23,6 +24,7 @@ public/packs
public/packs-test public/packs-test
.env .env
.env.production .env.production
.env.development
node_modules/ node_modules/
build/ build/
@ -55,6 +57,8 @@ npm-debug.log
yarn-error.log yarn-error.log
yarn-debug.log yarn-debug.log
# Ignore vagrant log files
*-cloudimg-console.log
# Ignore Docker option files # Ignore Docker option files
docker-compose.override.yml docker-compose.override.yml

2
.nvmrc
View File

@ -1 +1 @@
8 12

View File

@ -2,7 +2,7 @@ require:
- rubocop-rails - rubocop-rails
AllCops: AllCops:
TargetRubyVersion: 2.3 TargetRubyVersion: 2.4
Exclude: Exclude:
- 'spec/**/*' - 'spec/**/*'
- 'db/**/*' - 'db/**/*'
@ -46,7 +46,7 @@ Metrics/ClassLength:
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Max: 25 Max: 25
Metrics/LineLength: Layout/LineLength:
AllowURI: true AllowURI: true
Enabled: false Enabled: false
@ -71,6 +71,9 @@ Naming/MemoizedInstanceVariableName:
Rails: Rails:
Enabled: true Enabled: true
Rails/EnumHash:
Enabled: false
Rails/HasAndBelongsToMany: Rails/HasAndBelongsToMany:
Enabled: false Enabled: false
@ -102,6 +105,9 @@ Style/Documentation:
Style/DoubleNegation: Style/DoubleNegation:
Enabled: true Enabled: true
Style/FormatStringToken:
Enabled: false
Style/FrozenStringLiteralComment: Style/FrozenStringLiteralComment:
Enabled: true Enabled: true

View File

@ -1 +1 @@
2.6.5 2.6.6

View File

@ -3,6 +3,341 @@ Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [v3.1.4] - 2020-05-14
### Added
- Add `vi` to available locales ([taicv](https://github.com/tootsuite/mastodon/pull/13542))
- Add ability to remove identity proofs from account ([Gargron](https://github.com/tootsuite/mastodon/pull/13682))
- Add ability to exclude local content from federated timeline ([noellabo](https://github.com/tootsuite/mastodon/pull/13504), [noellabo](https://github.com/tootsuite/mastodon/pull/13745))
- Add `remote` param to `GET /api/v1/timelines/public` REST API
- Add `public/remote` / `public:remote` variants to streaming API
- "Remote only" option in federated timeline column settings in web UI
- Add ability to exclude remote content from hashtag timelines in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/13502))
- No changes to REST API
- "Local only" option in hashtag column settings in web UI
- Add Capistrano tasks that reload the services after deploying ([berkes](https://github.com/tootsuite/mastodon/pull/12642))
- Add `invites_enabled` attribute to `GET /api/v1/instance` in REST API ([ThibG](https://github.com/tootsuite/mastodon/pull/13501))
- Add `tootctl emoji export` command ([lfuelling](https://github.com/tootsuite/mastodon/pull/13534))
- Add separate cache directory for non-local uploads ([Gargron](https://github.com/tootsuite/mastodon/pull/12821), [Hanage999](https://github.com/tootsuite/mastodon/pull/13593), [mayaeh](https://github.com/tootsuite/mastodon/pull/13551))
- Add `tootctl upgrade storage-schema` command to move old non-local uploads to the cache directory
- Add buttons to delete header and avatar from profile settings ([sternenseemann](https://github.com/tootsuite/mastodon/pull/13234))
- Add emoji graphics and shortcodes from Twemoji 12.1.5 ([DeeUnderscore](https://github.com/tootsuite/mastodon/pull/13021))
### Changed
- Change error message when trying to migrate to an account that does not have current account set as an alias to be more clear ([TheEvilSkeleton](https://github.com/tootsuite/mastodon/pull/13746))
- Change delivery failure tracking to work with hostnames instead of URLs ([Gargron](https://github.com/tootsuite/mastodon/pull/13437), [noellabo](https://github.com/tootsuite/mastodon/pull/13481), [noellabo](https://github.com/tootsuite/mastodon/pull/13482), [noellabo](https://github.com/tootsuite/mastodon/pull/13535))
- Change Content-Security-Policy to not need unsafe-inline style-src ([ThibG](https://github.com/tootsuite/mastodon/pull/13679), [ThibG](https://github.com/tootsuite/mastodon/pull/13692), [ThibG](https://github.com/tootsuite/mastodon/pull/13576), [ThibG](https://github.com/tootsuite/mastodon/pull/13575), [ThibG](https://github.com/tootsuite/mastodon/pull/13438))
- Change how RSS items are titled and formatted ([ThibG](https://github.com/tootsuite/mastodon/pull/13592), [ykzts](https://github.com/tootsuite/mastodon/pull/13591))
### Fixed
- Fix dropdown of muted and followed accounts offering option to hide boosts in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13748))
- Fix "You are already signed in" alert being shown at wrong times ([ThibG](https://github.com/tootsuite/mastodon/pull/13547))
- Fix retrying of failed-to-download media files not actually working ([noellabo](https://github.com/tootsuite/mastodon/pull/13741))
- Fix first poll option not being focused when adding a poll in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13740))
- Fix `sr` locale being selected over `sr-Latn` ([ThibG](https://github.com/tootsuite/mastodon/pull/13693))
- Fix error within error when limiting backtrace to 3 lines ([Gargron](https://github.com/tootsuite/mastodon/pull/13120))
- Fix `tootctl media remove-orphans` crashing on "Import" files ([ThibG](https://github.com/tootsuite/mastodon/pull/13685))
- Fix regression in `tootctl media remove-orphans` ([Gargron](https://github.com/tootsuite/mastodon/pull/13405))
- Fix old unique jobs digests not having been cleaned up ([Gargron](https://github.com/tootsuite/mastodon/pull/13683))
- Fix own following/followers not showing muted users ([ThibG](https://github.com/tootsuite/mastodon/pull/13614))
- Fix list of followed people ignoring sorting on Follows & Followers page ([taras2358](https://github.com/tootsuite/mastodon/pull/13676))
- Fix wrong pgHero Content-Security-Policy when `CDN_HOST` is set ([ThibG](https://github.com/tootsuite/mastodon/pull/13595))
- Fix needlessly deduplicating usernames on collisions with remote accounts when signing-up through SAML/CAS ([kaiyou](https://github.com/tootsuite/mastodon/pull/13581))
- Fix page incorrectly scrolling when bringing up dropdown menus in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13574))
- Fix messed up z-index when NoScript blocks media/previews in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13449))
- Fix "See what's happening" page showing public instead of local timeline for logged-in users ([ThibG](https://github.com/tootsuite/mastodon/pull/13499))
- Fix not being able to resolve public resources in development environment ([Gargron](https://github.com/tootsuite/mastodon/pull/13505))
- Fix uninformative error message when uploading unsupported image files ([ThibG](https://github.com/tootsuite/mastodon/pull/13540))
- Fix expanded video player issues in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13541), [eai04191](https://github.com/tootsuite/mastodon/pull/13533))
- Fix and refactor keyboard navigation in dropdown menus in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13528))
- Fix uploaded image orientation being messed up in some browsers in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13493))
- Fix actions log crash when displaying updates of deleted announcements in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13489))
- Fix search not working due to proxy settings when using hidden services ([Gargron](https://github.com/tootsuite/mastodon/pull/13488))
- Fix poll refresh button not being debounced in web UI ([rasjonell](https://github.com/tootsuite/mastodon/pull/13485), [ThibG](https://github.com/tootsuite/mastodon/pull/13490))
- Fix confusing error when failing to add an alias to an unknown account ([ThibG](https://github.com/tootsuite/mastodon/pull/13480))
- Fix "Email changed" notification sometimes having wrong e-mail ([ThibG](https://github.com/tootsuite/mastodon/pull/13475))
- Fix varioues issues on the account aliases page ([ThibG](https://github.com/tootsuite/mastodon/pull/13452))
- Fix API footer link in web UI ([bubblineyuri](https://github.com/tootsuite/mastodon/pull/13441))
- Fix pagination of following, followers, follow requests, blocks and mutes lists in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13445))
- Fix styling of polls in JS-less fallback on public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/13436))
- Fix trying to delete already deleted file when post-processing ([Gargron](https://github.com/tootsuite/mastodon/pull/13406))
### Security
- Fix Doorkeeper vulnerability that exposed app secret to users who authorized the app and reset secret of the web UI that could have been exposed ([dependabot-preview[bot]](https://github.com/tootsuite/mastodon/pull/13613), [Gargron](https://github.com/tootsuite/mastodon/pull/13688))
- For apps that self-register on behalf of every individual user (such as most mobile apps), this is a non-issue
- The issue only affects developers of apps who are shared between multiple users, such as server-side apps like cross-posters
## [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
- Add `--reset-password` option to `tootctl accounts modify` ([ThibG](https://github.com/tootsuite/mastodon/pull/13126))
- Add source-mapped stacktrace to error message in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13082))
### Fixed
- Fix dismissing an announcement twice raising an obscure error ([ThibG](https://github.com/tootsuite/mastodon/pull/13124))
- Fix misleading error when attempting to re-send a pending follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/13133))
- Fix backups failing when files are missing from media attachments ([ThibG](https://github.com/tootsuite/mastodon/pull/13146))
- Fix duplicate accounts being created when fetching an account for its key only ([ThibG](https://github.com/tootsuite/mastodon/pull/13147))
- Fix `/web` redirecting to `/web/web` in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13128))
- Fix previously OStatus-based accounts not being detected as ActivityPub ([ThibG](https://github.com/tootsuite/mastodon/pull/13129))
- Fix account JSON/RSS not being cacheable due to wrong mime type comparison ([ThibG](https://github.com/tootsuite/mastodon/pull/13116))
- Fix old browsers crashing because of missing `finally` polyfill in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13115))
- Fix account's bio not being shown if there are no proofs/fields in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13075))
- Fix sign-ups without checked user agreement being accepted through the web form ([ThibG](https://github.com/tootsuite/mastodon/pull/13088))
- Fix non-x64 architectures not being able to build Docker image because of hardcoded Node.js architecture ([SaraSmiseth](https://github.com/tootsuite/mastodon/pull/13081))
- Fix invite request input not being shown on sign-up error if left empty ([ThibG](https://github.com/tootsuite/mastodon/pull/13089))
- Fix some migration hints mentioning GitLab instead of Mastodon ([saper](https://github.com/tootsuite/mastodon/pull/13084))
### Security
- Fix leak of arbitrary statuses through unfavourite action in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/13161))
## [3.1.1] - 2020-02-10
### Fixed
- Fix yanked dependency preventing installation ([mayaeh](https://github.com/tootsuite/mastodon/pull/13059))
## [3.1.0] - 2020-02-09
### Added
- Add bookmarks ([ThibG](https://github.com/tootsuite/mastodon/pull/7107), [Gargron](https://github.com/tootsuite/mastodon/pull/12494), [Gomasy](https://github.com/tootsuite/mastodon/pull/12381))
- Add announcements ([Gargron](https://github.com/tootsuite/mastodon/pull/12662), [Gargron](https://github.com/tootsuite/mastodon/pull/12967), [Gargron](https://github.com/tootsuite/mastodon/pull/12970), [Gargron](https://github.com/tootsuite/mastodon/pull/12963), [Gargron](https://github.com/tootsuite/mastodon/pull/12950), [Gargron](https://github.com/tootsuite/mastodon/pull/12990), [Gargron](https://github.com/tootsuite/mastodon/pull/12949), [Gargron](https://github.com/tootsuite/mastodon/pull/12989), [Gargron](https://github.com/tootsuite/mastodon/pull/12964), [Gargron](https://github.com/tootsuite/mastodon/pull/12965), [ThibG](https://github.com/tootsuite/mastodon/pull/12958), [ThibG](https://github.com/tootsuite/mastodon/pull/12957), [Gargron](https://github.com/tootsuite/mastodon/pull/12955), [ThibG](https://github.com/tootsuite/mastodon/pull/12946), [ThibG](https://github.com/tootsuite/mastodon/pull/12954))
- Add number animations in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12948), [Gargron](https://github.com/tootsuite/mastodon/pull/12971))
- Add `kab`, `is`, `kn`, `mr`, `ur` to available locales ([Gargron](https://github.com/tootsuite/mastodon/pull/12882), [BoFFire](https://github.com/tootsuite/mastodon/pull/12962), [Gargron](https://github.com/tootsuite/mastodon/pull/12379))
- Add profile filter category ([ThibG](https://github.com/tootsuite/mastodon/pull/12918))
- Add ability to add oneself to lists ([ThibG](https://github.com/tootsuite/mastodon/pull/12271))
- Add hint how to contribute translations to preferences page ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12736))
- Add signatures to statuses in archive takeout ([noellabo](https://github.com/tootsuite/mastodon/pull/12649))
- Add support for `magnet:` and `xmpp` links ([ThibG](https://github.com/tootsuite/mastodon/pull/12905), [ThibG](https://github.com/tootsuite/mastodon/pull/12709))
- Add `follow_request` notification type ([ThibG](https://github.com/tootsuite/mastodon/pull/12198))
- Add ability to filter reports by account domain in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12154))
- Add link to search for users connected from the same IP address to admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12157))
- Add link to reports targeting a specific domain in admin view ([ThibG](https://github.com/tootsuite/mastodon/pull/12513))
- Add support for EventSource streaming in web UI ([BenLubar](https://github.com/tootsuite/mastodon/pull/12887))
- Add hotkey for opening media attachments in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12498), [Kjwon15](https://github.com/tootsuite/mastodon/pull/12546))
- Add relationship-based options to status dropdowns in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12377), [ThibG](https://github.com/tootsuite/mastodon/pull/12535), [Gargron](https://github.com/tootsuite/mastodon/pull/12430))
- Add support for submitting media description with `ctrl`+`enter` in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12272))
- Add download button to audio and video players in web UI ([NimaBoscarino](https://github.com/tootsuite/mastodon/pull/12179))
- Add setting for whether to crop images in timelines in web UI ([duxovni](https://github.com/tootsuite/mastodon/pull/12126))
- Add support for `Event` activities ([tcitworld](https://github.com/tootsuite/mastodon/pull/12637))
- Add basic support for `Group` actors ([noellabo](https://github.com/tootsuite/mastodon/pull/12071))
- Add `S3_OVERRIDE_PATH_STYLE` environment variable ([Gargron](https://github.com/tootsuite/mastodon/pull/12594))
- Add `S3_OPEN_TIMEOUT` environment variable ([tateisu](https://github.com/tootsuite/mastodon/pull/12459))
- Add `LDAP_MAIL` environment variable ([madmath03](https://github.com/tootsuite/mastodon/pull/12053))
- Add `LDAP_UID_CONVERSION_ENABLED` environment variable ([madmath03](https://github.com/tootsuite/mastodon/pull/12461))
- Add `--remote-only` option to `tootctl emoji purge` ([ThibG](https://github.com/tootsuite/mastodon/pull/12810))
- Add `tootctl media remove-orphans` ([Gargron](https://github.com/tootsuite/mastodon/pull/12568), [Gargron](https://github.com/tootsuite/mastodon/pull/12571))
- Add `tootctl media lookup` command ([irlcatgirl](https://github.com/tootsuite/mastodon/pull/12283))
- Add cache for OEmbed endpoints to avoid extra HTTP requests ([Gargron](https://github.com/tootsuite/mastodon/pull/12403))
- Add support for KaiOS arrow navigation to public pages ([nolanlawson](https://github.com/tootsuite/mastodon/pull/12251))
- Add `discoverable` to accounts in REST API ([trwnh](https://github.com/tootsuite/mastodon/pull/12508))
- Add admin setting to disable default follows ([ArisuOngaku](https://github.com/tootsuite/mastodon/pull/12566))
- Add support for LDAP and PAM in the OAuth password grant strategy ([ntl-purism](https://github.com/tootsuite/mastodon/pull/12390), [Gargron](https://github.com/tootsuite/mastodon/pull/12743))
- Allow support for `Accept`/`Reject` activities with a non-embedded object ([puckipedia](https://github.com/tootsuite/mastodon/pull/12199))
- Add "Show thread" button to public profiles ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/13000))
### Changed
- Change `last_status_at` to be a date, not datetime in REST API ([ThibG](https://github.com/tootsuite/mastodon/pull/12966))
- Change followers page to relationships page in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12927), [Gargron](https://github.com/tootsuite/mastodon/pull/12934))
- Change reported media attachments to always be hidden in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12879), [ThibG](https://github.com/tootsuite/mastodon/pull/12907))
- Change string from "Disable" to "Disable login" in admin UI ([nileshkumar](https://github.com/tootsuite/mastodon/pull/12201))
- Change report page structure in admin UI ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12615))
- Change swipe sensitivity to be lower on small screens in web UI ([umonaca](https://github.com/tootsuite/mastodon/pull/12168))
- Change audio/video playback to stop playback when out of view in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12486))
- Change media description label based on upload type in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12270))
- Change large numbers to render without decimal units in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/12706))
- Change "Add a choice" button to be disabled rather than hidden when poll limit reached in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12319), [hinaloe](https://github.com/tootsuite/mastodon/pull/12544))
- Change `tootctl statuses remove` to keep statuses favourited or bookmarked by local users ([ThibG](https://github.com/tootsuite/mastodon/pull/11267), [Gomasy](https://github.com/tootsuite/mastodon/pull/12818))
- Change domain block behavior to update user records (fast) before deleting data (slower) ([ThibG](https://github.com/tootsuite/mastodon/pull/12247))
- Change behaviour to strip audio metadata on uploads ([hugogameiro](https://github.com/tootsuite/mastodon/pull/12171))
- Change accepted length of remote media descriptions from 420 to 1,500 characters ([ThibG](https://github.com/tootsuite/mastodon/pull/12262))
- Change preferences pages structure ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12497), [mayaeh](https://github.com/tootsuite/mastodon/pull/12517), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12801), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12797), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12799), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12793))
- Change format of titles in RSS ([devkral](https://github.com/tootsuite/mastodon/pull/8596))
- Change favourite icon animation from spring-based motion to CSS animation in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12175))
- Change minimum required Node.js version to 10, and default to 12 ([Shleeble](https://github.com/tootsuite/mastodon/pull/12791), [mkody](https://github.com/tootsuite/mastodon/pull/12906), [Shleeble](https://github.com/tootsuite/mastodon/pull/12703))
- Change spam check to exempt server staff ([ThibG](https://github.com/tootsuite/mastodon/pull/12874))
- Change to fallback to to `Create` audience when `object` has no defined audience ([ThibG](https://github.com/tootsuite/mastodon/pull/12249))
- Change Twemoji library to 12.1.3 in web UI ([koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/12342))
- Change blocked users to be hidden from following/followers lists ([ThibG](https://github.com/tootsuite/mastodon/pull/12733))
- Change signature verification to ignore signatures with invalid host ([Gargron](https://github.com/tootsuite/mastodon/pull/13033))
### Removed
- Remove unused dependencies ([ykzts](https://github.com/tootsuite/mastodon/pull/12861), [mayaeh](https://github.com/tootsuite/mastodon/pull/12826), [ThibG](https://github.com/tootsuite/mastodon/pull/12822), [ykzts](https://github.com/tootsuite/mastodon/pull/12533))
### Fixed
- Fix some translatable strings being used wrongly ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12569), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12589), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12502), [mayaeh](https://github.com/tootsuite/mastodon/pull/12231))
- Fix headline of public timeline page when set to local-only ([ykzts](https://github.com/tootsuite/mastodon/pull/12224))
- Fix space between tabs not being spread evenly in web UI ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12944), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12961), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12446))
- Fix interactive delays in database migrations with no TTY ([Gargron](https://github.com/tootsuite/mastodon/pull/12969))
- Fix status overflowing in report dialog in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12959))
- Fix unlocalized dropdown button title in web UI ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/12947))
- Fix media attachments without file being uploadable ([Gargron](https://github.com/tootsuite/mastodon/pull/12562))
- Fix unfollow confirmations in profile directory in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12922))
- Fix duplicate `description` meta tag on accounts public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/12923))
- Fix slow query of federated timeline ([notozeki](https://github.com/tootsuite/mastodon/pull/12886))
- Fix not all of account's active IPs showing up in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12909), [Gargron](https://github.com/tootsuite/mastodon/pull/12943))
- Fix search by IP not using alternative browser sessions in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12904))
- Fix “X new items” not showing up for slow mode on empty timelines in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12875))
- Fix OEmbed endpoint being inaccessible in secure mode ([Gargron](https://github.com/tootsuite/mastodon/pull/12864))
- Fix proofs API being inaccessible in secure mode ([Gargron](https://github.com/tootsuite/mastodon/pull/12495))
- Fix Ruby 2.7 incompatibilities ([ThibG](https://github.com/tootsuite/mastodon/pull/12831), [ThibG](https://github.com/tootsuite/mastodon/pull/12824), [Shleeble](https://github.com/tootsuite/mastodon/pull/12759), [zunda](https://github.com/tootsuite/mastodon/pull/12769))
- Fix invalid poll votes being accepted in REST API ([ThibG](https://github.com/tootsuite/mastodon/pull/12601))
- Fix old migrations failing because of strong migrations update ([ThibG](https://github.com/tootsuite/mastodon/pull/12787), [ThibG](https://github.com/tootsuite/mastodon/pull/12692))
- Fix reuse of detailed status components in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12792))
- Fix base64-encoded file uploads not being possible in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/12748), [Gargron](https://github.com/tootsuite/mastodon/pull/12857))
- Fix error due to missing authentication call in filters controller ([Gargron](https://github.com/tootsuite/mastodon/pull/12746))
- Fix uncaught unknown format error in host meta controller ([Gargron](https://github.com/tootsuite/mastodon/pull/12747))
- Fix URL search not returning private toots user has access to ([ThibG](https://github.com/tootsuite/mastodon/pull/12742), [ThibG](https://github.com/tootsuite/mastodon/pull/12336))
- Fix cache digesting log noise on status embeds ([Gargron](https://github.com/tootsuite/mastodon/pull/12750))
- Fix slowness due to layout thrashing when reloading a large set of statuses in web UI ([panarom](https://github.com/tootsuite/mastodon/pull/12661), [panarom](https://github.com/tootsuite/mastodon/pull/12744), [Gargron](https://github.com/tootsuite/mastodon/pull/12712))
- Fix error when fetching followers/following from REST API when user has network hidden ([Gargron](https://github.com/tootsuite/mastodon/pull/12716))
- Fix IDN mentions not being processed, IDN domains not being rendered ([Gargron](https://github.com/tootsuite/mastodon/pull/12715), [Gargron](https://github.com/tootsuite/mastodon/pull/13035), [Gargron](https://github.com/tootsuite/mastodon/pull/13030))
- Fix error when searching for empty phrase ([Gargron](https://github.com/tootsuite/mastodon/pull/12711))
- Fix backups stopping due to read timeouts ([chr-1x](https://github.com/tootsuite/mastodon/pull/12281))
- Fix batch actions on non-pending tags in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12537))
- Fix sample `SAML_ACS_URL`, `SAML_ISSUER` ([orlea](https://github.com/tootsuite/mastodon/pull/12669))
- Fix manual scrolling issue on Firefox/Windows in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12648))
- Fix archive takeout failing if total dump size exceeds 2GB ([scd31](https://github.com/tootsuite/mastodon/pull/12602), [Gargron](https://github.com/tootsuite/mastodon/pull/12653))
- Fix custom emoji category creation silently erroring out on duplicate category ([ThibG](https://github.com/tootsuite/mastodon/pull/12647))
- Fix link crawler not specifying preferred content type ([ThibG](https://github.com/tootsuite/mastodon/pull/12646))
- Fix featured hashtag setting page erroring out instead of rejecting invalid tags ([ThibG](https://github.com/tootsuite/mastodon/pull/12436))
- Fix tooltip messages of single/multiple-choice polls switcher being reversed in web UI ([acid-chicken](https://github.com/tootsuite/mastodon/pull/12616))
- Fix typo in help text of `tootctl statuses remove` ([trwnh](https://github.com/tootsuite/mastodon/pull/12603))
- Fix generic HTTP 500 error on duplicate records ([Gargron](https://github.com/tootsuite/mastodon/pull/12563))
- Fix old migration failing with new status default scope ([ThibG](https://github.com/tootsuite/mastodon/pull/12493))
- Fix errors when using search API with no query ([Gargron](https://github.com/tootsuite/mastodon/pull/12541), [trwnh](https://github.com/tootsuite/mastodon/pull/12549))
- Fix poll options not being selectable via keyboard in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12538))
- Fix conversations not having an unread indicator in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12506))
- Fix lost focus when modals open/close in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12437))
- Fix pending upload count not being decremented on error in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12499))
- Fix empty poll options not being removed on remote poll update ([ThibG](https://github.com/tootsuite/mastodon/pull/12484))
- Fix OCR with delete & redraft in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12465))
- Fix blur behind closed registration message ([ThibG](https://github.com/tootsuite/mastodon/pull/12442))
- Fix OEmbed discovery not handling different URL variants in query ([Gargron](https://github.com/tootsuite/mastodon/pull/12439))
- Fix link crawler crashing on `<a>` tags without `href` ([ThibG](https://github.com/tootsuite/mastodon/pull/12159))
- Fix whitelisted subdomains being ignored in whitelist mode ([noiob](https://github.com/tootsuite/mastodon/pull/12435))
- Fix broken audit log in whitelist mode in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12303))
- Fix unread indicator not honoring "Only media" option in local and federated timelines in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12330))
- Fix error when rebuilding home feeds ([dariusk](https://github.com/tootsuite/mastodon/pull/12324))
- Fix relationship caches being broken as result of a follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/12299))
- Fix more items than the limit being uploadable in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12300))
- Fix various issues with account migration ([ThibG](https://github.com/tootsuite/mastodon/pull/12301))
- Fix filtered out items being counted as pending items in slow mode in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12266))
- Fix notification filters not applying to poll options ([ThibG](https://github.com/tootsuite/mastodon/pull/12269))
- Fix notification message for user's own poll saying it's a poll they voted on in web UI ([ykzts](https://github.com/tootsuite/mastodon/pull/12219))
- Fix polls with an expiration not showing up as expired in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/12222))
- Fix volume slider having an offset between cursor and slider in Chromium in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12158))
- Fix Vagrant image not accepting connections ([shrft](https://github.com/tootsuite/mastodon/pull/12180))
- Fix batch actions being hidden on small screens in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/12183))
- Fix incoming federation not working in whitelist mode ([ThibG](https://github.com/tootsuite/mastodon/pull/12185))
- Fix error when passing empty `source` param to `PUT /api/v1/accounts/update_credentials` ([jglauche](https://github.com/tootsuite/mastodon/pull/12259))
- Fix HTTP-based streaming API being cacheable by proxies ([BenLubar](https://github.com/tootsuite/mastodon/pull/12945))
- Fix users being able to register while `tootctl self-destruct` is in progress ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12877))
- Fix microformats detection in link crawler not ignoring `h-card` links ([nightpool](https://github.com/tootsuite/mastodon/pull/12189))
- Fix outline on full-screen video in web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/12176))
- Fix TLD domain blocks not being editable ([ThibG](https://github.com/tootsuite/mastodon/pull/12805))
- Fix Nanobox deploy hooks ([danhunsaker](https://github.com/tootsuite/mastodon/pull/12663))
- Fix needlessly complicated SQL query when performing account search amongst followings ([ThibG](https://github.com/tootsuite/mastodon/pull/12302))
- Fix favourites count not updating when unfavouriting in web UI ([NimaBoscarino](https://github.com/tootsuite/mastodon/pull/12140))
- Fix occasional crash on scroll in Chromium in web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/12274))
- Fix intersection observer not working in single-column mode web UI ([panarom](https://github.com/tootsuite/mastodon/pull/12735))
- Fix voting issue with remote polls that contain trailing spaces ([ThibG](https://github.com/tootsuite/mastodon/pull/12515))
- Fix dynamic elements not working in pgHero due to CSP rules ([ykzts](https://github.com/tootsuite/mastodon/pull/12489))
- Fix overly verbose backtraces when delivering ActivityPub payloads ([zunda](https://github.com/tootsuite/mastodon/pull/12798))
- Fix rendering `<a>` without `href` when scheme unsupported ([Gargron](https://github.com/tootsuite/mastodon/pull/13040))
- Fix unfiltered params error when generating ActivityPub tag pagination ([Gargron](https://github.com/tootsuite/mastodon/pull/13049))
- Fix malformed HTML causing uncaught error ([Gargron](https://github.com/tootsuite/mastodon/pull/13042))
- Fix native share button not being displayed for unlisted toots ([ThibG](https://github.com/tootsuite/mastodon/pull/13045))
- Fix remote convertible media attachments (e.g. GIFs) not being saved ([Gargron](https://github.com/tootsuite/mastodon/pull/13032))
- Fix account query not using faster index ([abcang](https://github.com/tootsuite/mastodon/pull/13016))
- Fix error when sending moderation notification ([renatolond](https://github.com/tootsuite/mastodon/pull/13014))
### Security
- Fix OEmbed leaking information about existence of non-public statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/12930))
- Fix password change/reset not immediately invalidating other sessions ([Gargron](https://github.com/tootsuite/mastodon/pull/12928))
- Fix settings pages being cacheable by the browser ([Gargron](https://github.com/tootsuite/mastodon/pull/12714))
## [3.0.1] - 2019-10-10 ## [3.0.1] - 2019-10-10
### Added ### Added

View File

@ -14,13 +14,13 @@ If your contributions are accepted into Mastodon, you can request to be paid thr
## Bug reports ## Bug reports
Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles. Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
## Translations ## Translations
You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase. You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase.
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin] [![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)](https://crowdin.com/project/mastodon)
## Pull requests ## Pull requests

View File

@ -3,16 +3,27 @@ FROM ubuntu:18.04 as build-dep
# Use bash for the shell # Use bash for the shell
SHELL ["bash", "-c"] SHELL ["bash", "-c"]
# Install Node # Install Node v12 (LTS)
ENV NODE_VER="12.11.1" ENV NODE_VER="12.16.1"
RUN echo "Etc/UTC" > /etc/localtime && \ RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \
amd64) ARCH='x64';; \
ppc64el) ARCH='ppc64le';; \
s390x) ARCH='s390x';; \
arm64) ARCH='arm64';; \
armhf) ARCH='armv7l';; \
i386) ARCH='x86';; \
*) echo "unsupported architecture"; exit 1 ;; \
esac && \
echo "Etc/UTC" > /etc/localtime && \
apt update && \ apt update && \
apt -y install wget python && \ apt -y install wget python && \
cd ~ && \ cd ~ && \
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-x64.tar.gz && \ wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
tar xf node-v$NODE_VER-linux-x64.tar.gz && \ tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
rm node-v$NODE_VER-linux-x64.tar.gz && \ rm node-v$NODE_VER-linux-$ARCH.tar.gz && \
mv node-v$NODE_VER-linux-x64 /opt/node mv node-v$NODE_VER-linux-$ARCH /opt/node
# Install jemalloc # Install jemalloc
ENV JE_VER="5.2.1" ENV JE_VER="5.2.1"
@ -27,8 +38,8 @@ RUN apt update && \
make -j$(nproc) > /dev/null && \ make -j$(nproc) > /dev/null && \
make install_bin install_include install_lib make install_bin install_include install_lib
# Install ruby # Install Ruby
ENV RUBY_VER="2.6.5" ENV RUBY_VER="2.6.6"
ENV CPPFLAGS="-I/opt/jemalloc/include" ENV CPPFLAGS="-I/opt/jemalloc/include"
ENV LDFLAGS="-L/opt/jemalloc/lib/" ENV LDFLAGS="-L/opt/jemalloc/lib/"
RUN apt update && \ RUN apt update && \
@ -58,7 +69,9 @@ RUN npm install -g yarn && \
COPY Gemfile* package.json yarn.lock /opt/mastodon/ COPY Gemfile* package.json yarn.lock /opt/mastodon/
RUN cd /opt/mastodon && \ RUN cd /opt/mastodon && \
bundle install -j$(nproc) --deployment --without development test && \ bundle config set deployment 'true' && \
bundle config set without 'development test' && \
bundle install -j$(nproc) && \
yarn install --pure-lockfile yarn install --pure-lockfile
FROM ubuntu:18.04 FROM ubuntu:18.04
@ -123,3 +136,4 @@ RUN cd ~ && \
# Set the work dir and the container entry point # Set the work dir and the container entry point
WORKDIR /opt/mastodon WORKDIR /opt/mastodon
ENTRYPOINT ["/tini", "--"] ENTRYPOINT ["/tini", "--"]
EXPOSE 3000 4000

112
Gemfile
View File

@ -1,21 +1,26 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '>= 2.4.0', '< 2.7.0' ruby '>= 2.5.0', '< 3.0.0'
gem 'pkg-config', '~> 1.3' gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 4.2' gem 'puma', '~> 4.3'
gem 'rails', '~> 5.2.3' gem 'rails', '~> 5.2.4.2'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 0.20' gem 'thor', '~> 0.20'
gem 'rack', '~> 2.2.2'
gem 'thwait', '~> 0.1.0'
gem 'e2mmap', '~> 0.1.0'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.1' gem 'pg', '~> 1.2'
gem 'makara', '~> 0.4' gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.3' gem 'pghero', '~> 2.4'
gem 'dotenv-rails', '~> 2.7' gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.48', require: false gem 'aws-sdk-s3', '~> 1.64', require: false
gem 'fog-core', '<= 2.1.0' gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0' gem 'paperclip', '~> 6.0'
@ -27,10 +32,10 @@ gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.7' gem 'addressable', '~> 2.7'
gem 'bootsnap', '~> 1.4', require: false gem 'bootsnap', '~> 1.4', require: false
gem 'browser' gem 'browser'
gem 'charlock_holmes', '~> 0.7.6' gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639' gem 'iso-639'
gem 'chewy', '~> 5.1' gem 'chewy', '~> 5.1'
gem 'cld3', '~> 3.2.4' gem 'cld3', '~> 3.3.0'
gem 'devise', '~> 4.7' gem 'devise', '~> 4.7'
gem 'devise-two-factor', '~> 3.1' gem 'devise-two-factor', '~> 3.1'
@ -38,75 +43,74 @@ group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.2' gem 'devise_pam_authenticatable2', '~> 9.2'
end end
gem 'net-ldap', '~> 0.10' gem 'net-ldap', '~> 0.16'
gem 'omniauth-cas', '~> 1.1' gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10' gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.9' gem 'omniauth', '~> 1.9'
gem 'discard', '~> 1.1' gem 'discard', '~> 1.2'
gem 'doorkeeper', '~> 5.2' gem 'doorkeeper', '~> 5.4'
gem 'fast_blank', '~> 1.0' gem 'fast_blank', '~> 1.0'
gem 'fastimage' gem 'fastimage'
gem 'goldfinger', '~> 2.1' gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6' gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.5' gem 'redis-namespace', '~> 1.7'
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b' gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
gem 'htmlentities', '~> 4.3' gem 'htmlentities', '~> 4.3'
gem 'http', '~> 3.3' gem 'http', '~> 4.4'
gem 'http_accept_language', '~> 2.1' gem 'http_accept_language', '~> 2.1'
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
gem 'httplog', '~> 1.3' gem 'httplog', '~> 1.4.2'
gem 'idn-ruby', require: 'idn' gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1' gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0' gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.3', require: 'mime/types/columnar' gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532' gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
gem 'nokogiri', '~> 1.10' gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2' gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.9' gem 'oj', '~> 3.10'
gem 'ostatus2', '~> 2.0' gem 'ox', '~> 2.13'
gem 'ox', '~> 2.11'
gem 'parslet' gem 'parslet'
gem 'parallel', '~> 1.17' gem 'parallel', '~> 1.19'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
gem 'pundit', '~> 2.1' gem 'pundit', '~> 2.1'
gem 'premailer-rails' gem 'premailer-rails'
gem 'rack-attack', '~> 6.1' gem 'rack-attack', '~> 6.3'
gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rack-cors', '~> 1.1', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1' gem 'rails-i18n', '~> 5.1'
gem 'rails-settings-cached', '~> 0.6' gem 'rails-settings-cached', '~> 0.6'
gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis'] gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10' gem 'rqrcode', '~> 1.1'
gem 'ruby-progressbar', '~> 1.10' gem 'ruby-progressbar', '~> 1.10'
gem 'sanitize', '~> 5.1' gem 'sanitize', '~> 5.1'
gem 'sidekiq', '~> 5.2' gem 'sidekiq', '~> 6.0'
gem 'sidekiq-scheduler', '~> 3.0' gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0' gem 'sidekiq-unique-jobs', '~> 6.0'
gem 'sidekiq-bulk', '~>0.2.0' gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.1' gem 'simple-navigation', '~> 4.1'
gem 'simple_form', '~> 4.1' gem 'simple_form', '~> 5.0'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.1.3' gem 'stoplight', '~> 2.2.0'
gem 'strong_migrations', '~> 0.4' gem 'strong_migrations', '~> 0.6'
gem 'tty-command', '~> 0.9', require: false gem 'tty-command', '~> 0.9', require: false
gem 'tty-prompt', '~> 0.19', require: false gem 'tty-prompt', '~> 0.21', require: false
gem 'twitter-text', '~> 1.14' gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2019' gem 'tzinfo-data', '~> 1.2020'
gem 'webpacker', '~> 4.0' gem 'webpacker', '~> 5.1'
gem 'webpush' gem 'webpush'
gem 'json-ld', git: 'https://github.com/ruby-rdf/json-ld.git', ref: 'e742697a0906e74e8bb777ef98137bc3955d981d' gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.0' gem 'json-ld-preloaded', '~> 3.1'
gem 'rdf-normalize', '~> 0.3' gem 'rdf-normalize', '~> 0.4'
group :development, :test do group :development, :test do
gem 'fabrication', '~> 2.20' gem 'fabrication', '~> 2.21'
gem 'fuubar', '~> 2.4' gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 0.9', require: false gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-byebug', '~> 3.7' gem 'pry-byebug', '~> 3.9'
gem 'pry-rails', '~> 0.3' gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 3.8' gem 'rspec-rails', '~> 4.0'
end end
group :production, :test do group :production, :test do
@ -114,37 +118,37 @@ group :production, :test do
end end
group :test do group :test do
gem 'capybara', '~> 3.29' gem 'capybara', '~> 3.32'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.5' gem 'faker', '~> 2.11'
gem 'microformats', '~> 4.1' gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0' gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.17', require: false gem 'simplecov', '~> 0.18', require: false
gem 'webmock', '~> 3.7' gem 'webmock', '~> 3.8'
gem 'parallel_tests', '~> 2.29' gem 'parallel_tests', '~> 2.32'
gem 'rspec_junit_formatter', '~> 0.4'
end end
group :development do group :development do
gem 'active_record_query_trace', '~> 1.6' gem 'active_record_query_trace', '~> 1.7'
gem 'annotate', '~> 2.7' gem 'annotate', '~> 3.1'
gem 'better_errors', '~> 2.5' gem 'better_errors', '~> 2.7'
gem 'binding_of_caller', '~> 0.7' gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.0' gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7' gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3' gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler' gem 'memory_profiler'
gem 'rubocop', '~> 0.74', require: false gem 'rubocop', '~> 0.82', require: false
gem 'rubocop-rails', '~> 2.3', require: false gem 'rubocop-rails', '~> 2.5', require: false
gem 'brakeman', '~> 4.6', require: false gem 'brakeman', '~> 4.8', require: false
gem 'bundler-audit', '~> 0.6', require: false gem 'bundler-audit', '~> 0.6', require: false
gem 'capistrano', '~> 3.11' gem 'capistrano', '~> 3.14'
gem 'capistrano-rails', '~> 1.4' gem 'capistrano-rails', '~> 1.4'
gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-rbenv', '~> 2.1'
gem 'capistrano-yarn', '~> 2.0' gem 'capistrano-yarn', '~> 2.0'
gem 'derailed_benchmarks'
gem 'stackprof' gem 'stackprof'
end end

File diff suppressed because it is too large Load Diff

View File

@ -64,13 +64,7 @@ What does this have to do with content types? Well, if we support an `Article` c
If Hometown is going to be a universal reader, you're going to need better control over organizing your feeds than mainline Mastodon provides. If Hometown is going to be a universal reader, you're going to need better control over organizing your feeds than mainline Mastodon provides.
### Exclusive lists I've introduced a new kind of [exclusive list](https://github.com/hometown-fork/hometown/wiki/Exclusive-lists). In vanilla Mastodon, if you add an account to your "friends I like" list, posts from people on that list appear on that list. But they also appear on your home timeline, and maybe you don't want that! You'd rather treat your "friends I like" list as your "real" home timeline, and then check your home timeline when you're bored. Check out [more details about exclusive lists on the wiki](https://github.com/hometown-fork/hometown/wiki/Exclusive-lists).
My first plan is to introduce a new kind of _exclusive list_. Right now if you add an account to your "friends I like" list in Mastodon, posts from people on that list appear on that list. But they also appear on your home timeline, and maybe you don't want that! You'd rather treat your "friends I like" list as your "real" home timeline, and then check your home timeline when you're bored.
Or another case: I might have all the blogs I read in one list, but I only check it on Saturdays when I have time to read things. In that case I don't want updates from those blogs clogging up my home timeline.
> This is not yet implemented but will be available in the first release.
## Better accessibility defaults ## Better accessibility defaults
@ -88,11 +82,11 @@ Hometown uses [semantic versioning](https://semver.org) and follows a versioning
## Contributing to Hometown ## Contributing to Hometown
Setting up your Hometown development environment is [exactly like setting up your Mastodon development environment](https://docs.joinmastodon.org/development/overview/). Pull requests should be made to the `hometown-dev` branch, which is our default branch in Github. Setting up your Hometown development environment is [exactly like setting up your Mastodon development environment](https://docs.joinmastodon.org/dev/overview/). Pull requests should be made to the `hometown-dev` branch, which is our default branch in Github.
## License ## License
Copyright (C) 2016-2019 Eugen Rochko and other Mastodon contributors; see [AUTHORS.md](AUTHORS.md). Copyright (C) 2016-2020 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

4
Vagrantfile vendored
View File

@ -12,7 +12,7 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main' sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS # Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_8.x | sudo bash - curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
# Add firewall rule to redirect 80 to PORT and save # Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]} sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
@ -91,7 +91,7 @@ VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/xenial64" config.vm.box = "ubuntu/bionic64"
config.vm.provider :virtualbox do |vb| config.vm.provider :virtualbox do |vb|
vb.name = "mastodon" vb.name = "mastodon"

View File

@ -47,6 +47,11 @@ class StatusesIndex < Chewy::Index
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end 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 root date_detection: false do
field :id, type: 'long' field :id, type: 'long'
field :account_id, type: 'long' field :account_id, type: 'long'

View File

@ -6,7 +6,7 @@ class AccountFollowController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def create 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) redirect_to account_path(@account)
end end
end end

View File

@ -9,7 +9,7 @@ class AccountsController < ApplicationController
before_action :set_cache_headers before_action :set_cache_headers
before_action :set_body_classes before_action :set_body_classes
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format) } skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
skip_before_action :require_functional! skip_before_action :require_functional!
def show def show
@ -26,8 +26,12 @@ class AccountsController < ApplicationController
return return
end end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? if current_user.nil?
@statuses = filtered_status_page(params) @pinned_statuses = cache_collection(@account.pinned_statuses.without_local_only, Status) if show_pinned_statuses?
else
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
end
@statuses = filtered_status_page
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
@rss_url = rss_url @rss_url = rss_url
@ -40,7 +44,7 @@ class AccountsController < ApplicationController
format.rss do format.rss do
expires_in 1.minute, public: true expires_in 1.minute, public: true
@statuses = filtered_statuses.without_local_only.without_reblogs.without_replies.limit(PAGE_SIZE) @statuses = filtered_statuses.without_reblogs.without_local_only.limit(PAGE_SIZE)
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag]) render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
end end
@ -133,23 +137,23 @@ class AccountsController < ApplicationController
end end
def media_requested? def media_requested?
request.path.ends_with?('/media') && !tag_requested? request.path.split('.').first.ends_with?('/media') && !tag_requested?
end end
def replies_requested? def replies_requested?
request.path.ends_with?('/with_replies') && !tag_requested? request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
end end
def tag_requested? def tag_requested?
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end end
def filtered_status_page(params) def filtered_status_page
if params[:min_id].present? filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse end
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a def params_slice(*keys)
end params.slice(*keys).permit(*keys)
end end
def restrict_fields_to def restrict_fields_to

View File

@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size def set_size
case params[:id] case params[:id]
when 'featured' when 'featured'
@account.pinned_statuses.count @size = @account.pinned_statuses.count
else else
raise ActiveRecord::RecordNotFound not_found
end end
end end
def scope_for_collection def scope_for_collection
case params[:id] case params[:id]
when 'featured' when 'featured'
return Status.none if @account.blocking?(signed_request_account) # 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
@account.pinned_statuses # would likely be served the cache from the reverse proxy anyway
else 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)))
raise ActiveRecord::RecordNotFound Status.none
else
@account.pinned_statuses
end
end end
end end

View File

@ -7,6 +7,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
before_action :skip_unknown_actor_delete before_action :skip_unknown_actor_delete
before_action :require_signature! before_action :require_signature!
skip_before_action :authenticate_user!
def create def create
upgrade_account upgrade_account
@ -48,7 +49,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
ResolveAccountWorker.perform_async(signed_request_account.acct) ResolveAccountWorker.perform_async(signed_request_account.acct)
end end
DeliveryFailureTracker.track_inverse_success!(signed_request_account) DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
end end
def process_payload def process_payload

View File

@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
before_action :set_cache_headers before_action :set_cache_headers
def show 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' render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end end
@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
return unless page_requested? return unless page_requested?
@statuses = @account.statuses.permitted_for(@account, signed_request_account) @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) @statuses = cache_collection(@statuses, Status)
end end
def page_requested? def page_requested?
params[:page] == 'true' truthy_param?(:page)
end end
def page_params def page_params

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::RepliesController < ActivityPub::BaseController class ActivityPub::RepliesController < ActivityPub::BaseController
include SignatureAuthentication include SignatureVerification
include Authorization include Authorization
include AccountOwnedConcern include AccountOwnedConcern
@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
private private
def pundit_user
signed_request_account
end
def set_status def set_status
@status = @account.statuses.find(params[:status_id]) @status = @account.statuses.find(params[:status_id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def set_replies 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.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end end
@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
type: :unordered, type: :unordered,
part_of: account_status_replies_url(@account, @status), part_of: account_status_replies_url(@account, @status),
next: next_page, 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? return page if page_requested?
@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end end
def page_requested? def page_requested?
params[:page] == 'true' truthy_param?(:page)
end
def only_other_accounts?
truthy_param?(:only_other_accounts)
end end
def next_page def next_page
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT) only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
account_status_replies_url( account_status_replies_url(
@account, @account,
@status, @status,
page: true, 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 only_other_accounts: only_other_accounts
) )
end end

View File

@ -109,21 +109,7 @@ module Admin
end end
def filter_params def filter_params
params.permit( params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
:local,
:remote,
:by_domain,
:active,
:pending,
:disabled,
:silenced,
:suspended,
:username,
:display_name,
:email,
:ip,
:staff
)
end end
end end
end end

View File

@ -2,8 +2,18 @@
module Admin module Admin
class ActionLogsController < BaseController class ActionLogsController < BaseController
def index before_action :set_action_logs
@action_logs = Admin::ActionLog.page(params[:page])
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 end
end end

View File

@ -0,0 +1,88 @@
# frozen_string_literal: true
class Admin::AnnouncementsController < Admin::BaseController
before_action :set_announcements, only: :index
before_action :set_announcement, except: [:index, :new, :create]
def index
authorize :announcement, :index?
end
def new
authorize :announcement, :create?
@announcement = Announcement.new
end
def create
authorize :announcement, :create?
@announcement = Announcement.new(resource_params)
if @announcement.save
PublishScheduledAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
log_action :create, @announcement
redirect_to admin_announcements_path, notice: @announcement.published? ? I18n.t('admin.announcements.published_msg') : I18n.t('admin.announcements.scheduled_msg')
else
render :new
end
end
def edit
authorize :announcement, :update?
end
def update
authorize :announcement, :update?
if @announcement.update(resource_params)
PublishScheduledAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
log_action :update, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.updated_msg')
else
render :edit
end
end
def publish
authorize :announcement, :update?
@announcement.publish!
PublishScheduledAnnouncementWorker.perform_async(@announcement.id)
log_action :update, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.published_msg')
end
def unpublish
authorize :announcement, :update?
@announcement.unpublish!
UnpublishAnnouncementWorker.perform_async(@announcement.id)
log_action :update, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.unpublished_msg')
end
def destroy
authorize :announcement, :destroy?
@announcement.destroy!
UnpublishAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
log_action :destroy, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.destroyed_msg')
end
private
def set_announcements
@announcements = AnnouncementFilter.new(filter_params).results.page(params[:page])
end
def set_announcement
@announcement = Announcement.find(params[:id])
end
def filter_params
params.slice(*AnnouncementFilter::KEYS).permit(*AnnouncementFilter::KEYS)
end
def resource_params
params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
end
end

View File

@ -2,10 +2,6 @@
module Admin module Admin
class CustomEmojisController < BaseController class CustomEmojisController < BaseController
include ObfuscateFilename
obfuscate_filename [:custom_emoji, :image]
def index def index
authorize :custom_emoji, :index? authorize :custom_emoji, :index?
@ -52,7 +48,7 @@ module Admin
end end
def filter_params def filter_params
params.slice(:local, :remote, :by_domain, :shortcode, :page).permit(:local, :remote, :by_domain, :shortcode, :page) params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
end end
def action_from_button def action_from_button

View File

@ -6,12 +6,12 @@ module Admin
def index def index
authorize :email_domain_block, :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 end
def new def new
authorize :email_domain_block, :create? authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new @email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
end end
def create def create
@ -21,6 +21,28 @@ module Admin
if @email_domain_block.save if @email_domain_block.save
log_action :create, @email_domain_block 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') redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else else
render :new render :new
@ -41,7 +63,7 @@ module Admin
end end
def resource_params def resource_params
params.require(:email_domain_block).permit(:domain) params.require(:email_domain_block).permit(:domain, :with_dns_records)
end end
end end
end end

View File

@ -1,18 +0,0 @@
# frozen_string_literal: true
module Admin
class FollowersController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@followers = @account.followers.local.recent.page(params[:page]).per(PER_PAGE)
end
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@ -19,7 +19,7 @@ module Admin
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count @followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count @reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count @blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url) @available = DeliveryFailureTracker.available?(params[:id])
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@private_comment = @domain_block&.private_comment @private_comment = @domain_block&.private_comment
@public_comment = @domain_block&.public_comment @public_comment = @domain_block&.public_comment
@ -62,7 +62,7 @@ module Admin
end end
def filter_params def filter_params
params.permit(:limited, :by_domain) params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
end end
end end
end end

View File

@ -47,7 +47,7 @@ module Admin
end end
def filter_params def filter_params
params.permit(:available, :expired) params.slice(*InviteFilter::KEYS).permit(*InviteFilter::KEYS)
end end
end end
end end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Admin
class RelationshipsController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
end
private
def set_account
@account = Account.find(params[:account_id])
end
def filter_params
params.slice(*RelationshipFilter::KEYS).permit(*RelationshipFilter::KEYS)
end
end
end

View File

@ -52,11 +52,7 @@ module Admin
end end
def filter_params def filter_params
params.permit( params.slice(*ReportFilter::KEYS).permit(*ReportFilter::KEYS)
:account_id,
:resolved,
:target_account_id
)
end end
def set_report def set_report

View File

@ -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

View File

@ -73,7 +73,7 @@ module Admin
end end
def filter_params def filter_params
params.slice(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name).permit(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name) params.slice(:page, *TagFilter::KEYS).permit(:page, *TagFilter::KEYS)
end end
def tag_params def tag_params

View File

@ -7,7 +7,7 @@ module Admin
def index def index
authorize :account_warning_preset, :index? authorize :account_warning_preset, :index?
@warning_presets = AccountWarningPreset.all @warning_presets = AccountWarningPreset.alphabetic
@warning_preset = AccountWarningPreset.new @warning_preset = AccountWarningPreset.new
end end
@ -19,7 +19,7 @@ module Admin
if @warning_preset.save if @warning_preset.save
redirect_to admin_warning_presets_path redirect_to admin_warning_presets_path
else else
@warning_presets = AccountWarningPreset.all @warning_presets = AccountWarningPreset.alphabetic
render :index render :index
end end
end end
@ -52,7 +52,7 @@ module Admin
end end
def warning_preset_params def warning_preset_params
params.require(:account_warning_preset).permit(:text) params.require(:account_warning_preset).permit(:title, :text)
end end
end end
end end

View File

@ -20,6 +20,10 @@ class Api::BaseController < ApplicationController
render json: { error: e.to_s }, status: 422 render json: { error: e.to_s }, status: 422
end end
rescue_from ActiveRecord::RecordNotUnique do
render json: { error: 'Duplicate record' }, status: 422
end
rescue_from ActiveRecord::RecordNotFound do rescue_from ActiveRecord::RecordNotFound do
render json: { error: 'Record not found' }, status: 404 render json: { error: 'Record not found' }, status: 404
end end
@ -40,6 +44,10 @@ class Api::BaseController < ApplicationController
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503 render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
end end
rescue_from Mastodon::RateLimitExceededError do
render json: { error: I18n.t('errors.429') }, status: 429
end
rescue_from ActionController::ParameterMissing do |e| rescue_from ActionController::ParameterMissing do |e|
render json: { error: e.to_s }, status: 400 render json: { error: e.to_s }, status: 400
end end
@ -81,7 +89,7 @@ class Api::BaseController < ApplicationController
end end
def require_authenticated_user! def require_authenticated_user!
render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
end end
def require_user! def require_user!

View File

@ -1,15 +1,25 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::OEmbedController < Api::BaseController class Api::OEmbedController < Api::BaseController
respond_to :json skip_before_action :require_authenticated_user!
before_action :set_status
before_action :require_public_status!
def show def show
@status = status_finder.status
render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
end end
private private
def set_status
@status = status_finder.status
end
def require_public_status!
not_found if @status.hidden?
end
def status_finder def status_finder
StatusFinder.new(params[:url]) StatusFinder.new(params[:url])
end end

View File

@ -3,6 +3,8 @@
class Api::ProofsController < Api::BaseController class Api::ProofsController < Api::BaseController
include AccountOwnedConcern include AccountOwnedConcern
skip_before_action :require_authenticated_user!
before_action :set_provider before_action :set_provider
def index def index

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
end end
def user_settings_params def user_settings_params
return nil unless params.key?(:source) return nil if params[:source].blank?
source_params = params.require(:source) source_params = params.require(:source)

View File

@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
before_action :set_account before_action :set_account
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer
@ -21,11 +19,13 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
def load_accounts def load_accounts
return [] if hide_results? return [] if hide_results?
default_accounts.merge(paginated_follows).to_a scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
before_action :set_account before_action :set_account
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer
@ -21,11 +19,13 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
def load_accounts def load_accounts
return [] if hide_results? return [] if hide_results?
default_accounts.merge(paginated_follows).to_a scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -4,8 +4,6 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_account before_action :set_account
respond_to :json
def index def index
@proofs = @account.identity_proofs.active @proofs = @account.identity_proofs.active
render json: @proofs, each_serializer: REST::IdentityProofSerializer render json: @proofs, each_serializer: REST::IdentityProofSerializer

View File

@ -5,8 +5,6 @@ class Api::V1::Accounts::ListsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_account before_action :set_account
respond_to :json
def index def index
@lists = @account.lists.where(account: current_account) @lists = @account.lists.where(account: current_account)
render json: @lists, each_serializer: REST::ListSerializer render json: @lists, each_serializer: REST::ListSerializer

View File

@ -7,8 +7,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_account before_action :set_account
respond_to :json
def create def create
AccountPin.create!(account: current_account, target_account: @account) AccountPin.create!(account: current_account, target_account: @account)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter

View File

@ -4,8 +4,6 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:follows' } before_action -> { doorkeeper_authorize! :read, :'read:follows' }
before_action :require_user! before_action :require_user!
respond_to :json
def index def index
accounts = Account.where(id: account_ids).select('id') accounts = Account.where(id: account_ids).select('id')
# .where doesn't guarantee that our results are in the same order # .where doesn't guarantee that our results are in the same order

View File

@ -4,8 +4,6 @@ class Api::V1::Accounts::SearchController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' } before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user! before_action :require_user!
respond_to :json
def show def show
@accounts = account_search @accounts = account_search
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -6,8 +6,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
respond_to :json
def index def index
@statuses = load_statuses @statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -14,7 +14,7 @@ class Api::V1::AccountsController < Api::BaseController
skip_before_action :require_authenticated_user!, only: :create skip_before_action :require_authenticated_user!, only: :create
respond_to :json override_rate_limit_headers :follow, family: :follows
def show def show
render json: @account, serializer: REST::AccountSerializer render json: @account, serializer: REST::AccountSerializer
@ -31,7 +31,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def follow 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 } } options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class Api::V1::Announcements::ReactionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
before_action :set_announcement
before_action :set_reaction, except: :update
def update
@announcement.announcement_reactions.create!(account: current_account, name: params[:id])
render_empty
end
def destroy
@reaction.destroy!
render_empty
end
private
def set_reaction
@reaction = @announcement.announcement_reactions.where(account: current_account).find_by!(name: params[:id])
end
def set_announcement
@announcement = Announcement.published.find(params[:announcement_id])
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class Api::V1::AnnouncementsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: :dismiss
before_action :require_user!
before_action :set_announcements, only: :index
before_action :set_announcement, except: :index
def index
render json: @announcements, each_serializer: REST::AnnouncementSerializer
end
def dismiss
AnnouncementMute.find_or_create_by!(account: current_account, announcement: @announcement)
render_empty
end
private
def set_announcements
@announcements = begin
Announcement.published.chronological
end
end
def set_announcement
@announcement = Announcement.published.find(params[:id])
end
end

View File

@ -3,8 +3,6 @@
class Api::V1::Apps::CredentialsController < Api::BaseController class Api::V1::Apps::CredentialsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read } before_action -> { doorkeeper_authorize! :read }
respond_to :json
def show def show
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key) render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
end end

View File

@ -5,8 +5,6 @@ class Api::V1::BlocksController < Api::BaseController
before_action :require_user! before_action :require_user!
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
class Api::V1::BookmarksController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:bookmarks' }
before_action :require_user!
after_action :insert_pagination_headers
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def load_statuses
cached_bookmarks
end
def cached_bookmarks
cache_collection(
Status.reorder(nil).joins(:bookmarks).merge(results),
Status
)
end
def results
@_results ||= account_bookmarks.paginate_by_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
end
def account_bookmarks
current_account.bookmarks
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
api_v1_bookmarks_url pagination_params(max_id: pagination_max_id) if records_continue?
end
def prev_path
api_v1_bookmarks_url pagination_params(min_id: pagination_since_id) unless results.empty?
end
def pagination_max_id
results.last.id
end
def pagination_since_id
results.first.id
end
def records_continue?
results.size == limit_param(DEFAULT_STATUSES_LIMIT)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end

View File

@ -9,8 +9,6 @@ class Api::V1::ConversationsController < Api::BaseController
before_action :set_conversation, except: :index before_action :set_conversation, except: :index
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
respond_to :json
def index def index
@conversations = paginated_conversations @conversations = paginated_conversations
render json: @conversations, each_serializer: REST::ConversationSerializer render json: @conversations, each_serializer: REST::ConversationSerializer

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::CustomEmojisController < Api::BaseController class Api::V1::CustomEmojisController < Api::BaseController
respond_to :json
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
def index def index

View File

@ -8,8 +8,6 @@ class Api::V1::DomainBlocksController < Api::BaseController
before_action :require_user! before_action :require_user!
after_action :insert_pagination_headers, only: :show after_action :insert_pagination_headers, only: :show
respond_to :json
def show def show
@blocks = load_domain_blocks @blocks = load_domain_blocks
render json: @blocks.map(&:domain) render json: @blocks.map(&:domain)

View File

@ -5,8 +5,6 @@ class Api::V1::EndorsementsController < Api::BaseController
before_action :require_user! before_action :require_user!
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -5,8 +5,6 @@ class Api::V1::FavouritesController < Api::BaseController
before_action :require_user! before_action :require_user!
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@statuses = load_statuses @statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -2,12 +2,9 @@
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
before_action :require_user! before_action :require_user!
before_action :set_most_used_tags, only: :index before_action :set_most_used_tags, only: :index
respond_to :json
def index def index
render json: @most_used_tags, each_serializer: REST::TagSerializer render json: @most_used_tags, each_serializer: REST::TagSerializer
end end

View File

@ -7,8 +7,6 @@ class Api::V1::FiltersController < Api::BaseController
before_action :set_filters, only: :index before_action :set_filters, only: :index
before_action :set_filter, only: [:show, :update, :destroy] before_action :set_filter, only: [:show, :update, :destroy]
respond_to :json
def index def index
render json: @filters, each_serializer: REST::FilterSerializer render json: @filters, each_serializer: REST::FilterSerializer
end end

View File

@ -6,8 +6,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode? skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
respond_to :json
def show def show
expires_in 1.day, public: true expires_in 1.day, public: true
render_with_cache json: :activity, expires_in: 1.day render_with_cache json: :activity, expires_in: 1.day

View File

@ -6,8 +6,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode? skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
respond_to :json
def index def index
expires_in 1.day, public: true expires_in 1.day, public: true
render_with_cache(expires_in: 1.day) { Account.remote.domains } render_with_cache(expires_in: 1.day) { Account.remote.domains }

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::InstancesController < Api::BaseController class Api::V1::InstancesController < Api::BaseController
respond_to :json
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode? skip_before_action :require_authenticated_user!, unless: :whitelist_mode?

View File

@ -3,30 +3,42 @@
class Api::V1::MediaController < Api::BaseController class Api::V1::MediaController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action -> { doorkeeper_authorize! :write, :'write:media' }
before_action :require_user! before_action :require_user!
before_action :set_media_attachment, except: [:create]
include ObfuscateFilename before_action :check_processing, except: [:create]
obfuscate_filename :file
respond_to :json
def create def create
@media = current_account.media_attachments.create!(media_params) @media_attachment = current_account.media_attachments.create!(media_attachment_params)
render json: @media, serializer: REST::MediaAttachmentSerializer render json: @media_attachment, serializer: REST::MediaAttachmentSerializer
rescue Paperclip::Errors::NotIdentifiedByImageMagickError rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422 render json: file_type_error, status: 422
rescue Paperclip::Error rescue Paperclip::Error
render json: processing_error, status: 500 render json: processing_error, status: 500
end end
def show
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
def update def update
@media = current_account.media_attachments.where(status_id: nil).find(params[:id]) @media_attachment.update!(media_attachment_params)
@media.update!(media_params) render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
render json: @media, serializer: REST::MediaAttachmentSerializer
end end
private 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) params.permit(:file, :description, :focus)
end end

View File

@ -5,8 +5,6 @@ class Api::V1::MutesController < Api::BaseController
before_action :require_user! before_action :require_user!
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer

View File

@ -6,8 +6,6 @@ class Api::V1::NotificationsController < Api::BaseController
before_action :require_user! before_action :require_user!
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
respond_to :json
DEFAULT_NOTIFICATIONS_LIMIT = 15 DEFAULT_NOTIFICATIONS_LIMIT = 15
def index def index

View File

@ -7,8 +7,6 @@ class Api::V1::Polls::VotesController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_poll before_action :set_poll
respond_to :json
def create def create
VoteService.new.call(current_account, @poll, vote_params[:choices]) VoteService.new.call(current_account, @poll, vote_params[:choices])
render json: @poll, serializer: REST::PollSerializer render json: @poll, serializer: REST::PollSerializer
@ -20,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
@poll = Poll.attached.find(params[:poll_id]) @poll = Poll.attached.find(params[:poll_id])
authorize @poll.status, :show? authorize @poll.status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def vote_params def vote_params

View File

@ -7,8 +7,6 @@ class Api::V1::PollsController < Api::BaseController
before_action :set_poll before_action :set_poll
before_action :refresh_poll before_action :refresh_poll
respond_to :json
def show def show
render json: @poll, serializer: REST::PollSerializer, include_results: true render json: @poll, serializer: REST::PollSerializer, include_results: true
end end
@ -19,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
@poll = Poll.attached.find(params[:id]) @poll = Poll.attached.find(params[:id])
authorize @poll.status, :show? authorize @poll.status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def refresh_poll def refresh_poll

View File

@ -4,8 +4,6 @@ class Api::V1::PreferencesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' } before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user! before_action :require_user!
respond_to :json
def index def index
render json: current_account, serializer: REST::PreferencesSerializer render json: current_account, serializer: REST::PreferencesSerializer
end end

View File

@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :push } before_action -> { doorkeeper_authorize! :push }
before_action :require_user! before_action :require_user!
before_action :set_web_push_subscription before_action :set_web_push_subscription
before_action :check_web_push_subscription, only: [:show, :update]
def create def create
@web_subscription&.destroy! @web_subscription&.destroy!
@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end end
def show def show
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end end
def update def update
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
@web_subscription.update!(data: data_params) @web_subscription.update!(data: data_params)
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end end
@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) @web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end end
def check_web_push_subscription
not_found if @web_subscription.nil?
end
def subscription_params def subscription_params
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end end
def data_params def data_params
return {} if params[:data].blank? return {} if params[:data].blank?
params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll])
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end end
end end

View File

@ -4,7 +4,7 @@ class Api::V1::ReportsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create] before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create]
before_action :require_user! before_action :require_user!
respond_to :json override_rate_limit_headers :create, family: :reports
def create def create
@report = ReportService.new.call( @report = ReportService.new.call(

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
class Api::V1::Statuses::BookmarksController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user!
before_action :set_status
def create
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
render json: @status, serializer: REST::StatusSerializer
end
def destroy
bookmark = current_account.bookmarks.find_by(status: @status)
bookmark&.destroy!
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
before_action :set_status before_action :set_status
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer
@ -17,7 +15,9 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
private private
def load_accounts def load_accounts
default_accounts.merge(paginated_favourites).to_a scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope.merge(paginated_favourites).to_a
end end
def default_accounts def default_accounts
@ -67,8 +67,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
@status = Status.find(params[:status_id]) @status = Status.find(params[:status_id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code not_found
raise ActiveRecord::RecordNotFound
end end
def pagination_params(core_params) def pagination_params(core_params)

View File

@ -5,34 +5,24 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' } before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user! before_action :require_user!
before_action :set_status
respond_to :json
def create def create
@status = favourited_status FavouriteService.new.call(current_account, @status)
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusSerializer
end end
def destroy def destroy
@status = requested_status UnfavouriteWorker.perform_async(current_account.id, @status.id)
@favourites_map = { @status.id => false } render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, favourites_map: @favourites_map)
end end
private private
def favourited_status def set_status
service_result.status.reload @status = Status.find(params[:status_id])
end authorize @status, :show?
rescue Mastodon::NotPermittedError
def service_result not_found
FavouriteService.new.call(current_user.account, requested_status)
end
def requested_status
Status.find(params[:status_id])
end end
end end

View File

@ -8,8 +8,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
before_action :set_status before_action :set_status
before_action :set_conversation before_action :set_conversation
respond_to :json
def create def create
current_account.mute_conversation!(@conversation) current_account.mute_conversation!(@conversation)
@mutes_map = { @conversation.id => true } @mutes_map = { @conversation.id => true }
@ -30,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController
@status = Status.find(params[:status_id]) @status = Status.find(params[:status_id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code not_found
raise ActiveRecord::RecordNotFound
end end
def set_conversation def set_conversation

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_status before_action :set_status
respond_to :json
def create def create
StatusPin.create!(account: current_account, status: @status) StatusPin.create!(account: current_account, status: @status)
distribute_add_activity! distribute_add_activity!

View File

@ -7,8 +7,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
before_action :set_status before_action :set_status
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer
@ -17,7 +15,9 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
private private
def load_accounts def load_accounts
default_accounts.merge(paginated_statuses).to_a scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope.merge(paginated_statuses).to_a
end end
def default_accounts def default_accounts
@ -64,8 +64,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
@status = Status.find(params[:status_id]) @status = Status.find(params[:status_id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code not_found
raise ActiveRecord::RecordNotFound
end end
def pagination_params(core_params) def pagination_params(core_params)

View File

@ -5,33 +5,35 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:statuses' } before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user! before_action :require_user!
before_action :set_reblog
respond_to :json override_rate_limit_headers :create, family: :statuses
def create def create
@status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params) @status = ReblogService.new.call(current_account, @reblog, reblog_params)
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusSerializer
end end
def destroy def destroy
@status = status_for_destroy.reblog @status = current_account.statuses.find_by(reblog_of_id: @reblog.id)
@reblogs_map = { @status.id => false }
authorize status_for_destroy, :unreblog? if @status
status_for_destroy.discard authorize @status, :unreblog?
RemovalWorker.perform_async(status_for_destroy.id) @status.discard
RemovalWorker.perform_async(@status.id)
end
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map) render json: @reblog, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false })
end end
private private
def status_for_reblog def set_reblog
Status.find params[:status_id] @reblog = Status.find(params[:status_id])
end authorize @reblog, :show?
rescue Mastodon::NotPermittedError
def status_for_destroy not_found
@status_for_destroy ||= current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
end end
def reblog_params def reblog_params

View File

@ -7,8 +7,9 @@ class Api::V1::StatusesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
before_action :require_user!, except: [:show, :context] before_action :require_user!, except: [:show, :context]
before_action :set_status, only: [: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 # This API was originally unlimited, pagination cannot be introduced without
# breaking backwards-compatibility. Arbitrarily high number to cover most # breaking backwards-compatibility. Arbitrarily high number to cover most
@ -36,7 +37,7 @@ class Api::V1::StatusesController < Api::BaseController
def create def create
@status = PostStatusService.new.call(current_user.account, @status = PostStatusService.new.call(current_user.account,
text: status_params[:status], 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], media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive], sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text], spoiler_text: status_params[:spoiler_text],
@ -45,7 +46,8 @@ class Api::V1::StatusesController < Api::BaseController
application: doorkeeper_token.application, application: doorkeeper_token.application,
poll: status_params[:poll], poll: status_params[:poll],
idempotency: request.headers['Idempotency-Key'], idempotency: request.headers['Idempotency-Key'],
local_only: status_params[:local_only]) local_only: status_params[:local_only],
with_rate_limit: true)
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
end end
@ -66,7 +68,13 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.find(params[:id]) @status = Status.find(params[:id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
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 end
def status_params def status_params

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::StreamingController < Api::BaseController class Api::V1::StreamingController < Api::BaseController
respond_to :json
def index def index
if Rails.configuration.x.streaming_api_base_url != request.host if Rails.configuration.x.streaming_api_base_url != request.host
redirect_to streaming_api_url, status: 301 redirect_to streaming_api_url, status: 301

View File

@ -7,8 +7,6 @@ class Api::V1::SuggestionsController < Api::BaseController
before_action :require_user! before_action :require_user!
before_action :set_accounts before_action :set_accounts
respond_to :json
def index def index
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::AccountSerializer
end end

View File

@ -5,8 +5,6 @@ class Api::V1::Timelines::HomeController < Api::BaseController
before_action :require_user!, only: [:show] before_action :require_user!, only: [:show]
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show def show
@statuses = load_statuses @statuses = load_statuses

View File

@ -4,8 +4,6 @@ class Api::V1::Timelines::PublicController < Api::BaseController
before_action :require_user!, only: [:show], if: :require_auth? before_action :require_user!, only: [:show], if: :require_auth?
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show def show
@statuses = load_statuses @statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@ -41,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end end
def public_timeline_statuses def public_timeline_statuses
Status.as_public_timeline(current_account, truthy_param?(:local)) Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local))
end end
def insert_pagination_headers def insert_pagination_headers
@ -49,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params) params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params)
end end
def next_path def next_path

View File

@ -4,8 +4,6 @@ class Api::V1::Timelines::TagController < Api::BaseController
before_action :load_tag before_action :load_tag
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show def show
@statuses = load_statuses @statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)

View File

@ -3,8 +3,6 @@
class Api::V1::TrendsController < Api::BaseController class Api::V1::TrendsController < Api::BaseController
before_action :set_tags before_action :set_tags
respond_to :json
def index def index
render json: @tags, each_serializer: REST::TagSerializer render json: @tags, each_serializer: REST::TagSerializer
end end

View File

@ -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

View File

@ -8,8 +8,6 @@ class Api::V2::SearchController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:search' } before_action -> { doorkeeper_authorize! :read, :'read:search' }
before_action :require_user! before_action :require_user!
respond_to :json
def index def index
@search = Search.new(search_results) @search = Search.new(search_results)
render json: @search, serializer: REST::SearchSerializer render json: @search, serializer: REST::SearchSerializer

View File

@ -1,21 +1,25 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::Web::EmbedsController < Api::Web::BaseController class Api::Web::EmbedsController < Api::Web::BaseController
respond_to :json
before_action :require_user! before_action :require_user!
def create def create
status = StatusFinder.new(params[:url]).status status = StatusFinder.new(params[:url]).status
return not_found if status.hidden?
render json: status, serializer: OEmbedSerializer, width: 400 render json: status, serializer: OEmbedSerializer, width: 400
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
oembed = FetchOEmbedService.new.call(params[:url]) oembed = FetchOEmbedService.new.call(params[:url])
oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED) if oembed[:html].present?
if oembed return not_found if oembed.nil?
render json: oembed
else begin
render json: {}, status: :not_found oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
rescue ArgumentError
return not_found
end end
render json: oembed
end end
end end

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::Web::PushSubscriptionsController < Api::Web::BaseController class Api::Web::PushSubscriptionsController < Api::Web::BaseController
respond_to :json
before_action :require_user! before_action :require_user!
def create def create
@ -19,6 +17,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
data = { data = {
alerts: { alerts: {
follow: alerts_enabled, follow: alerts_enabled,
follow_request: false,
favourite: alerts_enabled, favourite: alerts_enabled,
reblog: alerts_enabled, reblog: alerts_enabled,
mention: alerts_enabled, mention: alerts_enabled,
@ -58,6 +57,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end end
def data_params def data_params
@data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end end
end end

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::Web::SettingsController < Api::Web::BaseController class Api::Web::SettingsController < Api::Web::BaseController
respond_to :json
before_action :require_user! before_action :require_user!
def update def update

View File

@ -24,10 +24,12 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from ActionController::UnknownFormat, with: :not_acceptable rescue_from ActionController::UnknownFormat, with: :not_acceptable
rescue_from ActionController::ParameterMissing, with: :bad_request rescue_from ActionController::ParameterMissing, with: :bad_request
rescue_from Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, with: :service_unavailable 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 :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :require_functional!, if: :user_signed_in? before_action :require_functional!, if: :user_signed_in?
@ -110,6 +112,10 @@ class ApplicationController < ActionController::Base
respond_with_error(503) respond_with_error(503)
end end
def too_many_requests
respond_with_error(429)
end
def single_user_mode? def single_user_mode?
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists? @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
end end
@ -137,8 +143,8 @@ class ApplicationController < ActionController::Base
def respond_with_error(code) def respond_with_error(code)
respond_to do |format| respond_to do |format|
format.any { head code } format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
format.html { render "errors/#{code}", layout: 'error', status: code } format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
end end
end end
end end

View File

@ -6,6 +6,12 @@ class Auth::PasswordsController < Devise::PasswordsController
layout 'auth' layout 'auth'
def update
super do |resource|
resource.session_activations.destroy_all if resource.errors.empty?
end
end
private private
def check_validity_of_reset_password_token def check_validity_of_reset_password_token

View File

@ -10,6 +10,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update] before_action :require_not_suspended!, only: [:update]
before_action :set_cache_headers, only: [:edit, :update]
skip_before_action :require_functional!, only: [:edit, :update] skip_before_action :require_functional!, only: [:edit, :update]
@ -21,10 +22,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController
not_found not_found
end end
def update
super do |resource|
resource.clear_other_sessions(current_session.session_id) if resource.saved_change_to_encrypted_password?
end
end
protected protected
def update_resource(resource, params) def update_resource(resource, params)
params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank? params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
super super
end end
@ -33,7 +41,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
resource.locale = I18n.locale resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank? resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.agreement = true
resource.current_sign_in_ip = request.remote_ip resource.current_sign_in_ip = request.remote_ip
resource.build_account if resource.account.nil? resource.build_account if resource.account.nil?
@ -41,7 +48,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def configure_sign_up_params def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u| devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code) u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement)
end end
end end
@ -109,4 +116,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def require_not_suspended! def require_not_suspended!
forbidden if current_account.suspended? forbidden if current_account.suspended?
end end
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end
end end

View File

@ -111,6 +111,13 @@ class Auth::SessionsController < Devise::SessionsController
render :two_factor render :two_factor
end end
def require_no_authentication
super
# Delete flash message that isn't entirely useful and may be confusing in
# most cases because /web doesn't display/clear flash messages.
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
end
private private
def set_instance_presenter def set_instance_presenter

View File

@ -20,7 +20,7 @@ class AuthorizeInteractionsController < ApplicationController
end end
def create 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 render :success
else else
render :error render :error

Some files were not shown because too many files have changed in this diff Show More