Merge tag 'v3.0.0' into hometown-dev
This commit is contained in:
commit
228e0f0f6e
.circleci
.env.nanobox.env.production.sample.ruby-versionCHANGELOG.mdDockerfileGemfileGemfile.lockapp.jsonapp
chewy
controllers
about_controller.rbaccounts_controller.rb
activitypub
base_controller.rbcollections_controller.rbinboxes_controller.rboutboxes_controller.rbreplies_controller.rb
admin
account_actions_controller.rbaccounts_controller.rbcustom_emojis_controller.rbdashboard_controller.rbdomain_allows_controller.rbdomain_blocks_controller.rbinstances_controller.rbrelays_controller.rbreport_notes_controller.rbtags_controller.rbtwo_factor_authentications_controller.rb
api
base_controller.rbproofs_controller.rbpush_controller.rbsalmon_controller.rbsubscriptions_controller.rb
application_controller.rbv1
accounts
accounts_controller.rbadmin
apps_controller.rbcustom_emojis_controller.rbdirectories_controller.rbfeatured_tags
featured_tags_controller.rbfollow_requests_controller.rbfollows_controller.rbinstances
instances_controller.rbmarkers_controller.rbreports_controller.rbsearch_controller.rbstatuses
statuses_controller.rbtimelines
trends_controller.rbv2
auth
challenges_controller.rbconfirmations_controller.rbomniauth_callbacks_controller.rbregistrations_controller.rbsessions_controller.rbsetup_controller.rb
concerns
account_controller_concern.rbaccount_owned_concern.rbcache_concern.rbchallengable_concern.rbexport_controller_concern.rbsignature_verification.rbstatus_controller_concern.rb
custom_css_controller.rbdirectories_controller.rbemojis_controller.rbfollower_accounts_controller.rbfollowing_accounts_controller.rbhome_controller.rbinstance_actors_controller.rbintents_controller.rbinvites_controller.rbmanifests_controller.rbmedia_controller.rbmedia_proxy_controller.rboauth
public_timelines_controller.rbremote_follow_controller.rbremote_interaction_controller.rbremote_unfollows_controller.rbsettings
aliases_controller.rbdeletes_controller.rbexports_controller.rb
shares_controller.rbstatuses_controller.rbmigration
migrations_controller.rbpreferences_controller.rbsessions_controller.rbtwo_factor_authentication
two_factor_authentications_controller.rb@ -3,7 +3,7 @@ version: 2
|
||||
aliases:
|
||||
- &defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.6.0-stretch-node
|
||||
- image: circleci/ruby:2.6-stretch-node
|
||||
environment: &ruby_environment
|
||||
BUNDLE_APP_CONFIG: ./.bundle/
|
||||
DB_HOST: localhost
|
||||
@ -105,14 +105,14 @@ jobs:
|
||||
install-ruby2.5:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.5.3-stretch-node
|
||||
- image: circleci/ruby:2.5-stretch-node
|
||||
environment: *ruby_environment
|
||||
<<: *install_ruby_dependencies
|
||||
|
||||
install-ruby2.4:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.4.5-stretch-node
|
||||
- image: circleci/ruby:2.4-stretch-node
|
||||
environment: *ruby_environment
|
||||
<<: *install_ruby_dependencies
|
||||
|
||||
@ -131,40 +131,40 @@ jobs:
|
||||
test-ruby2.6:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.6.0-stretch-node
|
||||
- image: circleci/ruby:2.6-stretch-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:10.6-alpine
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
- image: circleci/redis:5.0.3-alpine3.8
|
||||
- image: circleci/redis:5-alpine
|
||||
<<: *test_steps
|
||||
|
||||
test-ruby2.5:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.5.3-stretch-node
|
||||
- image: circleci/ruby:2.5-stretch-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:10.6-alpine
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
- image: circleci/redis:4.0.12-alpine
|
||||
- image: circleci/redis:5-alpine
|
||||
<<: *test_steps
|
||||
|
||||
test-ruby2.4:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.4.5-stretch-node
|
||||
- image: circleci/ruby:2.4-stretch-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:10.6-alpine
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
- image: circleci/redis:4.0.12-alpine
|
||||
- image: circleci/redis:5-alpine
|
||||
<<: *test_steps
|
||||
|
||||
test-webui:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8.15.0-stretch
|
||||
- image: circleci/node:12.9-stretch
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- run: ./bin/retry yarn test:jest
|
||||
@ -173,9 +173,11 @@ jobs:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *install_system_dependencies
|
||||
- run: bundle exec i18n-tasks check-normalized
|
||||
- run: bundle exec i18n-tasks unused -l en
|
||||
- run: bundle exec i18n-tasks check-consistent-interpolations
|
||||
- run: bundle exec rake repo:check_locales_files
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
56
.env.nanobox
56
.env.nanobox
@ -11,24 +11,14 @@ DB_NAME=gonano
|
||||
DB_PASS=$DATA_DB_PASS
|
||||
DB_PORT=5432
|
||||
|
||||
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||
# DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||
|
||||
# Optional ElasticSearch configuration
|
||||
ES_ENABLED=true
|
||||
ES_HOST=$DATA_ELASTIC_HOST
|
||||
ES_PORT=9200
|
||||
|
||||
# Optimizations
|
||||
LD_PRELOAD=/data/lib/libjemalloc.so
|
||||
|
||||
# ImageMagick optimizations
|
||||
MAGICK_TEMPORARY_PATH=/app/tmp
|
||||
MAGICK_MEMORY_LIMIT=128MiB
|
||||
MAGICK_MAP_LIMIT=64MiB
|
||||
MAGICK_TIME_LIMIT=15
|
||||
MAGICK_AREA_LIMIT=16MP
|
||||
MAGICK_WIDTH_LIMIT=8KP
|
||||
MAGICK_HEIGHT_LIMIT=8KP
|
||||
BIND=0.0.0.0
|
||||
|
||||
# Federation
|
||||
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
|
||||
@ -84,6 +74,7 @@ SMTP_PORT=587
|
||||
SMTP_LOGIN=$SMTP_LOGIN
|
||||
SMTP_PASSWORD=$SMTP_PASSWORD
|
||||
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
#SMTP_REPLY_TO=
|
||||
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
|
||||
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||
#SMTP_AUTH_METHOD=plain
|
||||
@ -97,9 +88,17 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
# PAPERCLIP_ROOT_URL=/system
|
||||
|
||||
# Optional asset host for multi-server setups
|
||||
# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
|
||||
# if WEB_DOMAIN is not set. For example, the server may have the
|
||||
# following header field:
|
||||
# Access-Control-Allow-Origin: https://example.com/
|
||||
# CDN_HOST=https://assets.example.com
|
||||
|
||||
# S3 (optional)
|
||||
# The attachment host must allow cross origin request from WEB_DOMAIN or
|
||||
# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
|
||||
# following header field:
|
||||
# Access-Control-Allow-Origin: https://192.168.1.123:9000/
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
@ -109,6 +108,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
# S3_HOSTNAME=192.168.1.123:9000
|
||||
|
||||
# S3 (Minio Config (optional) Please check Minio instance for details)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
@ -119,12 +120,30 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
# S3_ENDPOINT=
|
||||
# S3_SIGNATURE_VERSION=
|
||||
|
||||
# Google Cloud Storage (optional)
|
||||
# Use S3 compatible API. Since GCS does not support Multipart Upload,
|
||||
# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=storage.googleapis.com
|
||||
# S3_ENDPOINT=https://storage.googleapis.com
|
||||
# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
|
||||
|
||||
# Swift (optional)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# SWIFT_ENABLED=true
|
||||
# SWIFT_USERNAME=
|
||||
# For Keystone V3, the value for SWIFT_TENANT should be the project name
|
||||
# SWIFT_TENANT=
|
||||
# SWIFT_PASSWORD=
|
||||
# Some OpenStack V3 providers require PROJECT_ID (optional)
|
||||
# SWIFT_PROJECT_ID=
|
||||
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
|
||||
# issues with token rate-limiting during high load.
|
||||
# SWIFT_AUTH_URL=
|
||||
@ -171,8 +190,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
# The pam environment variable "email" is provided by:
|
||||
# https://github.com/devkral/pam_email_extractor
|
||||
# PAM_ENABLED=true
|
||||
# Fallback Suffix for email address generation (nil by default)
|
||||
# PAM_DEFAULT_SUFFIX=pam
|
||||
# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
|
||||
# PAM_EMAIL_DOMAIN=example.com
|
||||
# Name of the pam service (pam "auth" section is evaluated)
|
||||
# PAM_DEFAULT_SERVICE=rpam
|
||||
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
|
||||
@ -220,7 +239,14 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
|
||||
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
|
||||
|
||||
# Use HTTP proxy for outgoing request (optional)
|
||||
# http_proxy=http://gateway.local:8118
|
||||
# Access control for hidden service.
|
||||
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
|
||||
|
@ -69,6 +69,7 @@ SMTP_PORT=587
|
||||
SMTP_LOGIN=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_ADDRESS=notifications@example.com
|
||||
#SMTP_REPLY_TO=
|
||||
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
|
||||
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||
#SMTP_AUTH_METHOD=plain
|
||||
@ -114,6 +115,20 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
||||
# S3_ENDPOINT=
|
||||
# S3_SIGNATURE_VERSION=
|
||||
|
||||
# Google Cloud Storage (optional)
|
||||
# Use S3 compatible API. Since GCS does not support Multipart Upload,
|
||||
# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=storage.googleapis.com
|
||||
# S3_ENDPOINT=https://storage.googleapis.com
|
||||
# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
|
||||
|
||||
# Swift (optional)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
@ -163,7 +178,7 @@ STREAMING_CLUSTER_NUM=1
|
||||
# LDAP_BIND_DN=
|
||||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
# LDAP_SEARCH_FILTER="%{uid}=%{email}"
|
||||
# LDAP_SEARCH_FILTER=%{uid}=%{email}
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
|
@ -1 +1 @@
|
||||
2.6.1
|
||||
2.6.5
|
||||
|
196
CHANGELOG.md
196
CHANGELOG.md
@ -3,6 +3,202 @@ Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.0.0] - 2019-10-03
|
||||
### Added
|
||||
|
||||
- Add "not available" label to unloaded media attachments in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11715), [Gargron](https://github.com/tootsuite/mastodon/pull/11745))
|
||||
- **Add profile directory to web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11688), [mayaeh](https://github.com/tootsuite/mastodon/pull/11872))
|
||||
- Add profile directory opt-in federation
|
||||
- Add profile directory REST API
|
||||
- Add special alert for throttled requests in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11677))
|
||||
- Add confirmation modal when logging out from the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11671))
|
||||
- **Add audio player in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11644), [Gargron](https://github.com/tootsuite/mastodon/pull/11652), [Gargron](https://github.com/tootsuite/mastodon/pull/11654), [ThibG](https://github.com/tootsuite/mastodon/pull/11629), [Gargron](https://github.com/tootsuite/mastodon/pull/12056))
|
||||
- **Add autosuggestions for hashtags in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11422), [ThibG](https://github.com/tootsuite/mastodon/pull/11632), [Gargron](https://github.com/tootsuite/mastodon/pull/11764), [Gargron](https://github.com/tootsuite/mastodon/pull/11588), [Gargron](https://github.com/tootsuite/mastodon/pull/11442))
|
||||
- **Add media editing modal with OCR tool in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11563), [Gargron](https://github.com/tootsuite/mastodon/pull/11566), [ThibG](https://github.com/tootsuite/mastodon/pull/11575), [ThibG](https://github.com/tootsuite/mastodon/pull/11576), [Gargron](https://github.com/tootsuite/mastodon/pull/11577), [Gargron](https://github.com/tootsuite/mastodon/pull/11573), [Gargron](https://github.com/tootsuite/mastodon/pull/11571))
|
||||
- Add indicator of unread notifications to window title when web UI is out of focus ([Gargron](https://github.com/tootsuite/mastodon/pull/11560), [Gargron](https://github.com/tootsuite/mastodon/pull/11572))
|
||||
- Add indicator for which options you voted for in a poll in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11195))
|
||||
- **Add search results pagination to web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11409), [ThibG](https://github.com/tootsuite/mastodon/pull/11447))
|
||||
- **Add option to disable real-time updates in web UI ("slow mode")** ([Gargron](https://github.com/tootsuite/mastodon/pull/9984), [ykzts](https://github.com/tootsuite/mastodon/pull/11880), [ThibG](https://github.com/tootsuite/mastodon/pull/11883), [Gargron](https://github.com/tootsuite/mastodon/pull/11898), [ThibG](https://github.com/tootsuite/mastodon/pull/11859))
|
||||
- Add option to disable blurhash previews in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11188))
|
||||
- Add native smooth scrolling when supported in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11207))
|
||||
- Add scrolling to the search bar on focus in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12032))
|
||||
- Add refresh button to list of rebloggers/favouriters in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12031))
|
||||
- Add error description and button to copy stack trace to web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12033))
|
||||
- Add search and sort functions to hashtag admin UI ([mayaeh](https://github.com/tootsuite/mastodon/pull/11829), [Gargron](https://github.com/tootsuite/mastodon/pull/11897), [mayaeh](https://github.com/tootsuite/mastodon/pull/11875))
|
||||
- Add setting for default search engine indexing in admin UI ([brortao](https://github.com/tootsuite/mastodon/pull/11804))
|
||||
- Add account bio to account view in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11473))
|
||||
- **Add option to include reported statuses in warning e-mail from admin UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11639), [Gargron](https://github.com/tootsuite/mastodon/pull/11812), [Gargron](https://github.com/tootsuite/mastodon/pull/11741), [Gargron](https://github.com/tootsuite/mastodon/pull/11698), [mayaeh](https://github.com/tootsuite/mastodon/pull/11765))
|
||||
- Add number of pending accounts and pending hashtags to dashboard in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11514))
|
||||
- **Add account migration UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11846), [noellabo](https://github.com/tootsuite/mastodon/pull/11905), [noellabo](https://github.com/tootsuite/mastodon/pull/11907), [noellabo](https://github.com/tootsuite/mastodon/pull/11906), [noellabo](https://github.com/tootsuite/mastodon/pull/11902))
|
||||
- **Add table of contents to about page** ([Gargron](https://github.com/tootsuite/mastodon/pull/11885), [ykzts](https://github.com/tootsuite/mastodon/pull/11941), [ykzts](https://github.com/tootsuite/mastodon/pull/11895), [Kjwon15](https://github.com/tootsuite/mastodon/pull/11916))
|
||||
- **Add password challenge to 2FA settings, e-mail notifications** ([Gargron](https://github.com/tootsuite/mastodon/pull/11878))
|
||||
- **Add optional public list of domain blocks with comments** ([ThibG](https://github.com/tootsuite/mastodon/pull/11298), [ThibG](https://github.com/tootsuite/mastodon/pull/11515), [Gargron](https://github.com/tootsuite/mastodon/pull/11908))
|
||||
- Add an RSS feed for featured hashtags ([noellabo](https://github.com/tootsuite/mastodon/pull/10502))
|
||||
- Add explanations to featured hashtags UI and profile ([Gargron](https://github.com/tootsuite/mastodon/pull/11586))
|
||||
- **Add hashtag trends with admin and user settings** ([Gargron](https://github.com/tootsuite/mastodon/pull/11490), [Gargron](https://github.com/tootsuite/mastodon/pull/11502), [Gargron](https://github.com/tootsuite/mastodon/pull/11641), [Gargron](https://github.com/tootsuite/mastodon/pull/11594), [Gargron](https://github.com/tootsuite/mastodon/pull/11517), [mayaeh](https://github.com/tootsuite/mastodon/pull/11845), [Gargron](https://github.com/tootsuite/mastodon/pull/11774), [Gargron](https://github.com/tootsuite/mastodon/pull/11712), [Gargron](https://github.com/tootsuite/mastodon/pull/11791), [Gargron](https://github.com/tootsuite/mastodon/pull/11743), [Gargron](https://github.com/tootsuite/mastodon/pull/11740), [Gargron](https://github.com/tootsuite/mastodon/pull/11714), [ThibG](https://github.com/tootsuite/mastodon/pull/11631), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/11569), [Gargron](https://github.com/tootsuite/mastodon/pull/11524), [Gargron](https://github.com/tootsuite/mastodon/pull/11513))
|
||||
- Add hashtag usage breakdown to admin UI
|
||||
- Add batch actions for hashtags to admin UI
|
||||
- Add trends to web UI
|
||||
- Add trends to public pages
|
||||
- Add user preference to hide trends
|
||||
- Add admin setting to disable trends
|
||||
- **Add categories for custom emojis** ([Gargron](https://github.com/tootsuite/mastodon/pull/11196), [Gargron](https://github.com/tootsuite/mastodon/pull/11793), [Gargron](https://github.com/tootsuite/mastodon/pull/11920), [highemerly](https://github.com/tootsuite/mastodon/pull/11876))
|
||||
- Add custom emoji categories to emoji picker in web UI
|
||||
- Add `category` to custom emojis in REST API
|
||||
- Add batch actions for custom emojis in admin UI
|
||||
- Add max image dimensions to error message ([raboof](https://github.com/tootsuite/mastodon/pull/11552))
|
||||
- Add aac, m4a, 3gp, amr, wma to allowed audio formats ([Gargron](https://github.com/tootsuite/mastodon/pull/11342), [umonaca](https://github.com/tootsuite/mastodon/pull/11687))
|
||||
- **Add search syntax for operators and phrases** ([Gargron](https://github.com/tootsuite/mastodon/pull/11411))
|
||||
- **Add REST API for managing featured hashtags** ([noellabo](https://github.com/tootsuite/mastodon/pull/11778))
|
||||
- **Add REST API for managing timeline read markers** ([Gargron](https://github.com/tootsuite/mastodon/pull/11762))
|
||||
- Add `exclude_unreviewed` param to `GET /api/v2/search` REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/11977))
|
||||
- Add `reason` param to `POST /api/v1/accounts` REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/12064))
|
||||
- **Add ActivityPub secure mode** ([Gargron](https://github.com/tootsuite/mastodon/pull/11269), [ThibG](https://github.com/tootsuite/mastodon/pull/11332), [ThibG](https://github.com/tootsuite/mastodon/pull/11295))
|
||||
- Add HTTP signatures to all outgoing ActivityPub GET requests ([Gargron](https://github.com/tootsuite/mastodon/pull/11284), [ThibG](https://github.com/tootsuite/mastodon/pull/11300))
|
||||
- Add support for ActivityPub Audio activities ([ThibG](https://github.com/tootsuite/mastodon/pull/11189))
|
||||
- Add ActivityPub actor representing the entire server ([ThibG](https://github.com/tootsuite/mastodon/pull/11321), [rtucker](https://github.com/tootsuite/mastodon/pull/11400), [ThibG](https://github.com/tootsuite/mastodon/pull/11561), [Gargron](https://github.com/tootsuite/mastodon/pull/11798))
|
||||
- **Add whitelist mode** ([Gargron](https://github.com/tootsuite/mastodon/pull/11291), [mayaeh](https://github.com/tootsuite/mastodon/pull/11634))
|
||||
- Add config of multipart threshold for S3 ([ykzts](https://github.com/tootsuite/mastodon/pull/11924), [ykzts](https://github.com/tootsuite/mastodon/pull/11944))
|
||||
- Add health check endpoint for web ([ykzts](https://github.com/tootsuite/mastodon/pull/11770), [ykzts](https://github.com/tootsuite/mastodon/pull/11947))
|
||||
- Add HTTP signature keyId to request log ([Gargron](https://github.com/tootsuite/mastodon/pull/11591))
|
||||
- Add `SMTP_REPLY_TO` environment variable ([hugogameiro](https://github.com/tootsuite/mastodon/pull/11718))
|
||||
- Add `tootctl preview_cards remove` command ([mayaeh](https://github.com/tootsuite/mastodon/pull/11320))
|
||||
- Add `tootctl media refresh` command ([Gargron](https://github.com/tootsuite/mastodon/pull/11775))
|
||||
- Add `tootctl cache recount` command ([Gargron](https://github.com/tootsuite/mastodon/pull/11597))
|
||||
- Add option to exclude suspended domains from `tootctl domains crawl` ([dariusk](https://github.com/tootsuite/mastodon/pull/11454))
|
||||
- Add parallelization to `tootctl search deploy` ([noellabo](https://github.com/tootsuite/mastodon/pull/12051))
|
||||
- Add soft delete for statuses for instant deletes through API ([Gargron](https://github.com/tootsuite/mastodon/pull/11623), [Gargron](https://github.com/tootsuite/mastodon/pull/11648))
|
||||
- Add rails-level JSON caching ([Gargron](https://github.com/tootsuite/mastodon/pull/11333), [Gargron](https://github.com/tootsuite/mastodon/pull/11271))
|
||||
- **Add request pool to improve delivery performance** ([Gargron](https://github.com/tootsuite/mastodon/pull/10353), [ykzts](https://github.com/tootsuite/mastodon/pull/11756))
|
||||
- Add concurrent connection attempts to resolved IP addresses ([ThibG](https://github.com/tootsuite/mastodon/pull/11757))
|
||||
- Add index for remember_token to improve login performance ([abcang](https://github.com/tootsuite/mastodon/pull/11881))
|
||||
- **Add more accurate hashtag search** ([Gargron](https://github.com/tootsuite/mastodon/pull/11579), [Gargron](https://github.com/tootsuite/mastodon/pull/11427), [Gargron](https://github.com/tootsuite/mastodon/pull/11448))
|
||||
- **Add more accurate account search** ([Gargron](https://github.com/tootsuite/mastodon/pull/11537), [Gargron](https://github.com/tootsuite/mastodon/pull/11580))
|
||||
- **Add a spam check** ([Gargron](https://github.com/tootsuite/mastodon/pull/11217), [Gargron](https://github.com/tootsuite/mastodon/pull/11806), [ThibG](https://github.com/tootsuite/mastodon/pull/11296))
|
||||
- Add new languages ([Gargron](https://github.com/tootsuite/mastodon/pull/12062))
|
||||
- Breton
|
||||
- Spanish (Argentina)
|
||||
- Estonian
|
||||
- Macedonian
|
||||
- New Norwegian
|
||||
- Add NodeInfo endpoint ([Gargron](https://github.com/tootsuite/mastodon/pull/12002), [Gargron](https://github.com/tootsuite/mastodon/pull/12058))
|
||||
|
||||
### Changed
|
||||
|
||||
- **Change conversations UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11896))
|
||||
- Change dashboard to short number notation ([noellabo](https://github.com/tootsuite/mastodon/pull/11847), [noellabo](https://github.com/tootsuite/mastodon/pull/11911))
|
||||
- Change REST API `GET /api/v1/timelines/public` to require authentication when public preview is off ([ThibG](https://github.com/tootsuite/mastodon/pull/11802))
|
||||
- Change REST API `POST /api/v1/follow_requests/:id/(approve|reject)` to return relationship ([ThibG](https://github.com/tootsuite/mastodon/pull/11800))
|
||||
- Change rate limit for media proxy ([ykzts](https://github.com/tootsuite/mastodon/pull/11814))
|
||||
- Change unlisted custom emoji to not appear in autosuggestions ([Gargron](https://github.com/tootsuite/mastodon/pull/11818))
|
||||
- Change max length of media descriptions from 420 to 1500 characters ([Gargron](https://github.com/tootsuite/mastodon/pull/11819), [ThibG](https://github.com/tootsuite/mastodon/pull/11836))
|
||||
- **Change deletes to preserve soft-deleted statuses in unresolved reports** ([Gargron](https://github.com/tootsuite/mastodon/pull/11805))
|
||||
- **Change tootctl to use inline parallelization instead of Sidekiq** ([Gargron](https://github.com/tootsuite/mastodon/pull/11776))
|
||||
- **Change account deletion page to have better explanations** ([Gargron](https://github.com/tootsuite/mastodon/pull/11753), [Gargron](https://github.com/tootsuite/mastodon/pull/11763))
|
||||
- Change hashtag component in web UI to show numbers for 2 last days ([Gargron](https://github.com/tootsuite/mastodon/pull/11742), [Gargron](https://github.com/tootsuite/mastodon/pull/11755), [Gargron](https://github.com/tootsuite/mastodon/pull/11754))
|
||||
- Change OpenGraph description on sign-up page to reflect invite ([Gargron](https://github.com/tootsuite/mastodon/pull/11744))
|
||||
- Change layout of public profile directory to be the same as in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11705))
|
||||
- Change detailed status child ordering to sort self-replies on top ([ThibG](https://github.com/tootsuite/mastodon/pull/11686))
|
||||
- Change window resize handler to switch to/from mobile layout as soon as needed ([ThibG](https://github.com/tootsuite/mastodon/pull/11656))
|
||||
- Change icon button styles to make hover/focus states more obvious ([ThibG](https://github.com/tootsuite/mastodon/pull/11474))
|
||||
- Change contrast of status links that are not mentions or hashtags ([ThibG](https://github.com/tootsuite/mastodon/pull/11406))
|
||||
- **Change hashtags to preserve first-used casing** ([Gargron](https://github.com/tootsuite/mastodon/pull/11416), [Gargron](https://github.com/tootsuite/mastodon/pull/11508), [Gargron](https://github.com/tootsuite/mastodon/pull/11504), [Gargron](https://github.com/tootsuite/mastodon/pull/11507), [Gargron](https://github.com/tootsuite/mastodon/pull/11441))
|
||||
- **Change unconfirmed user login behaviour** ([Gargron](https://github.com/tootsuite/mastodon/pull/11375), [ThibG](https://github.com/tootsuite/mastodon/pull/11394), [Gargron](https://github.com/tootsuite/mastodon/pull/11860))
|
||||
- **Change single-column mode to scroll the whole page** ([Gargron](https://github.com/tootsuite/mastodon/pull/11359), [Gargron](https://github.com/tootsuite/mastodon/pull/11894), [Gargron](https://github.com/tootsuite/mastodon/pull/11891), [ThibG](https://github.com/tootsuite/mastodon/pull/11655), [Gargron](https://github.com/tootsuite/mastodon/pull/11463), [Gargron](https://github.com/tootsuite/mastodon/pull/11458), [ThibG](https://github.com/tootsuite/mastodon/pull/11395), [Gargron](https://github.com/tootsuite/mastodon/pull/11418))
|
||||
- Change `tootctl accounts follow` to only work with local accounts ([angristan](https://github.com/tootsuite/mastodon/pull/11592))
|
||||
- Change Dockerfile ([Shleeble](https://github.com/tootsuite/mastodon/pull/11710), [ykzts](https://github.com/tootsuite/mastodon/pull/11768), [Shleeble](https://github.com/tootsuite/mastodon/pull/11707))
|
||||
- Change supported Node versions to include v12 ([abcang](https://github.com/tootsuite/mastodon/pull/11706))
|
||||
- Change Portuguese language from `pt` to `pt-PT` ([Gargron](https://github.com/tootsuite/mastodon/pull/11820))
|
||||
- Change domain block silence to always require approval on follow ([ThibG](https://github.com/tootsuite/mastodon/pull/11975))
|
||||
- Change link preview fetcher to not perform a HEAD request first ([Gargron](https://github.com/tootsuite/mastodon/pull/12028))
|
||||
- Change `tootctl domains purge` to accept multiple domains at once ([Gargron](https://github.com/tootsuite/mastodon/pull/12046))
|
||||
|
||||
### Removed
|
||||
|
||||
- **Remove OStatus support** ([Gargron](https://github.com/tootsuite/mastodon/pull/11205), [Gargron](https://github.com/tootsuite/mastodon/pull/11303), [Gargron](https://github.com/tootsuite/mastodon/pull/11460), [ThibG](https://github.com/tootsuite/mastodon/pull/11280), [ThibG](https://github.com/tootsuite/mastodon/pull/11278))
|
||||
- Remove Atom feeds and old URLs in the form of `GET /:username/updates/:id` ([Gargron](https://github.com/tootsuite/mastodon/pull/11247))
|
||||
- Remove WebP support ([angristan](https://github.com/tootsuite/mastodon/pull/11589))
|
||||
- Remove deprecated config options from Heroku and Scalingo ([ykzts](https://github.com/tootsuite/mastodon/pull/11925))
|
||||
- Remove deprecated REST API `GET /api/v1/search` API ([Gargron](https://github.com/tootsuite/mastodon/pull/11823))
|
||||
- Remove deprecated REST API `GET /api/v1/statuses/:id/card` ([Gargron](https://github.com/tootsuite/mastodon/pull/11213))
|
||||
- Remove deprecated REST API `POST /api/v1/notifications/dismiss?id=:id` ([Gargron](https://github.com/tootsuite/mastodon/pull/11214))
|
||||
- Remove deprecated REST API `GET /api/v1/timelines/direct` ([Gargron](https://github.com/tootsuite/mastodon/pull/11212))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix manifest warning ([ykzts](https://github.com/tootsuite/mastodon/pull/11767))
|
||||
- Fix admin UI for custom emoji not respecting GIF autoplay preference ([ThibG](https://github.com/tootsuite/mastodon/pull/11801))
|
||||
- Fix page body not being scrollable in admin/settings layout ([Gargron](https://github.com/tootsuite/mastodon/pull/11893))
|
||||
- Fix placeholder colors for inputs not being explicitly defined ([Gargron](https://github.com/tootsuite/mastodon/pull/11890))
|
||||
- Fix incorrect enclosure length in RSS ([tsia](https://github.com/tootsuite/mastodon/pull/11889))
|
||||
- Fix TOTP codes not being filtered from logs during enabling/disabling ([Gargron](https://github.com/tootsuite/mastodon/pull/11877))
|
||||
- Fix webfinger response not returning 410 when account is suspended ([Gargron](https://github.com/tootsuite/mastodon/pull/11869))
|
||||
- Fix ActivityPub Move handler queuing jobs that will fail if account is suspended ([Gargron](https://github.com/tootsuite/mastodon/pull/11864))
|
||||
- Fix SSO login not using existing account when e-mail is verified ([Gargron](https://github.com/tootsuite/mastodon/pull/11862))
|
||||
- Fix web UI allowing uploads past status limit via drag & drop ([Gargron](https://github.com/tootsuite/mastodon/pull/11863))
|
||||
- Fix expiring polls not being displayed as such in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11835))
|
||||
- Fix 2FA challenge and password challenge for non-database users ([Gargron](https://github.com/tootsuite/mastodon/pull/11831), [Gargron](https://github.com/tootsuite/mastodon/pull/11943))
|
||||
- Fix profile fields overflowing page width in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11828))
|
||||
- Fix web push subscriptions being deleted on rate limit or timeout ([Gargron](https://github.com/tootsuite/mastodon/pull/11826))
|
||||
- Fix display of long poll options in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11717), [ThibG](https://github.com/tootsuite/mastodon/pull/11833))
|
||||
- Fix search API not resolving URL when `type` is given ([Gargron](https://github.com/tootsuite/mastodon/pull/11822))
|
||||
- Fix hashtags being split by ZWNJ character ([Gargron](https://github.com/tootsuite/mastodon/pull/11821))
|
||||
- Fix scroll position resetting when opening media modals in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11815))
|
||||
- Fix duplicate HTML IDs on about page ([ThibG](https://github.com/tootsuite/mastodon/pull/11803))
|
||||
- Fix admin UI showing superfluous reject media/reports on suspended domain blocks ([ThibG](https://github.com/tootsuite/mastodon/pull/11749))
|
||||
- Fix ActivityPub context not being dynamically computed ([ThibG](https://github.com/tootsuite/mastodon/pull/11746))
|
||||
- Fix Mastodon logo style on hover on public pages' footer ([ThibG](https://github.com/tootsuite/mastodon/pull/11735))
|
||||
- Fix height of dashboard counters ([ThibG](https://github.com/tootsuite/mastodon/pull/11736))
|
||||
- Fix custom emoji animation on hover in web UI directory bios ([ThibG](https://github.com/tootsuite/mastodon/pull/11716))
|
||||
- Fix non-numbers being passed to Redis and causing an error ([Gargron](https://github.com/tootsuite/mastodon/pull/11697))
|
||||
- Fix error in REST API for an account's statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/11700))
|
||||
- Fix uncaught error when resource param is missing in Webfinger request ([Gargron](https://github.com/tootsuite/mastodon/pull/11701))
|
||||
- Fix uncaught domain normalization error in remote follow ([Gargron](https://github.com/tootsuite/mastodon/pull/11703))
|
||||
- Fix uncaught 422 and 500 errors ([Gargron](https://github.com/tootsuite/mastodon/pull/11590), [Gargron](https://github.com/tootsuite/mastodon/pull/11811))
|
||||
- Fix uncaught parameter missing exceptions and missing error templates ([Gargron](https://github.com/tootsuite/mastodon/pull/11702))
|
||||
- Fix encoding error when checking e-mail MX records ([Gargron](https://github.com/tootsuite/mastodon/pull/11696))
|
||||
- Fix items in StatusContent render list not all having a key ([ThibG](https://github.com/tootsuite/mastodon/pull/11645))
|
||||
- Fix remote and staff-removed statuses leaving media behind for a day ([Gargron](https://github.com/tootsuite/mastodon/pull/11638))
|
||||
- Fix CSP needlessly allowing blob URLs in script-src ([ThibG](https://github.com/tootsuite/mastodon/pull/11620))
|
||||
- Fix ignoring whole status because of one invalid hashtag ([Gargron](https://github.com/tootsuite/mastodon/pull/11621))
|
||||
- Fix hidden statuses losing focus ([ThibG](https://github.com/tootsuite/mastodon/pull/11208))
|
||||
- Fix loading bar being obscured by other elements in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11598))
|
||||
- Fix multiple issues with replies collection for pages further than self-replies ([ThibG](https://github.com/tootsuite/mastodon/pull/11582))
|
||||
- Fix blurhash and autoplay not working on public pages ([Gargron](https://github.com/tootsuite/mastodon/pull/11585))
|
||||
- Fix 422 being returned instead of 404 when POSTing to unmatched routes ([Gargron](https://github.com/tootsuite/mastodon/pull/11574), [Gargron](https://github.com/tootsuite/mastodon/pull/11704))
|
||||
- Fix client-side resizing of image uploads ([ThibG](https://github.com/tootsuite/mastodon/pull/11570))
|
||||
- Fix short number formatting for numbers above million in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11559))
|
||||
- Fix ActivityPub and REST API queries setting cookies and preventing caching ([ThibG](https://github.com/tootsuite/mastodon/pull/11539), [ThibG](https://github.com/tootsuite/mastodon/pull/11557), [ThibG](https://github.com/tootsuite/mastodon/pull/11336), [ThibG](https://github.com/tootsuite/mastodon/pull/11331))
|
||||
- Fix some emojis in profile metadata labels are not emojified. ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/11534))
|
||||
- Fix account search always returning exact match on paginated results ([Gargron](https://github.com/tootsuite/mastodon/pull/11525))
|
||||
- Fix acct URIs with IDN domains not being resolved ([Gargron](https://github.com/tootsuite/mastodon/pull/11520))
|
||||
- Fix admin dashboard missing latest features ([Gargron](https://github.com/tootsuite/mastodon/pull/11505))
|
||||
- Fix jumping of toot date when clicking spoiler button ([ariasuni](https://github.com/tootsuite/mastodon/pull/11449))
|
||||
- Fix boost to original audience not working on mobile in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11371))
|
||||
- Fix handling of webfinger redirects in ResolveAccountService ([ThibG](https://github.com/tootsuite/mastodon/pull/11279))
|
||||
- Fix URLs appearing twice in errors of ActivityPub::DeliveryWorker ([Gargron](https://github.com/tootsuite/mastodon/pull/11231))
|
||||
- Fix support for HTTP proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/11245))
|
||||
- Fix HTTP requests to IPv6 hosts ([ThibG](https://github.com/tootsuite/mastodon/pull/11240))
|
||||
- Fix error in ElasticSearch index import ([mayaeh](https://github.com/tootsuite/mastodon/pull/11192))
|
||||
- Fix duplicate account error when seeding development database ([ysksn](https://github.com/tootsuite/mastodon/pull/11366))
|
||||
- Fix performance of session clean-up scheduler ([abcang](https://github.com/tootsuite/mastodon/pull/11871))
|
||||
- Fix older migrations not running ([zunda](https://github.com/tootsuite/mastodon/pull/11377))
|
||||
- Fix URLs counting towards RTL detection ([ahangarha](https://github.com/tootsuite/mastodon/pull/11759))
|
||||
- Fix unnecessary status re-rendering in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11211))
|
||||
- Fix http_parser.rb gem not being compiled when no network available ([petabyteboy](https://github.com/tootsuite/mastodon/pull/11444))
|
||||
- Fix muted text color not applying to all text ([trwnh](https://github.com/tootsuite/mastodon/pull/11996))
|
||||
- Fix follower/following lists resetting on back-navigation in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11986))
|
||||
- Fix n+1 query when approving multiple follow requests ([abcang](https://github.com/tootsuite/mastodon/pull/12004))
|
||||
- Fix records not being indexed into ElasticSearch sometimes ([Gargron](https://github.com/tootsuite/mastodon/pull/12024))
|
||||
- Fix needlessly indexing unsearchable statuses into ElasticSearch ([Gargron](https://github.com/tootsuite/mastodon/pull/12041))
|
||||
- Fix new user bootstrapping crashing when to-be-followed accounts are invalid ([ThibG](https://github.com/tootsuite/mastodon/pull/12037))
|
||||
- Fix featured hashtag URL being interpreted as media or replies tab ([Gargron](https://github.com/tootsuite/mastodon/pull/12048))
|
||||
- Fix account counters being overwritten by parallel writes ([Gargron](https://github.com/tootsuite/mastodon/pull/12045))
|
||||
|
||||
### Security
|
||||
|
||||
- Fix performance of GIF re-encoding and always strip EXIF data from videos ([Gargron](https://github.com/tootsuite/mastodon/pull/12057))
|
||||
|
||||
## [2.9.3] - 2019-08-10
|
||||
### Added
|
||||
|
||||
|
20
Dockerfile
20
Dockerfile
@ -4,22 +4,20 @@ FROM ubuntu:18.04 as build-dep
|
||||
SHELL ["bash", "-c"]
|
||||
|
||||
# Install Node
|
||||
ENV NODE_VER="8.15.0"
|
||||
ENV NODE_VER="12.11.1"
|
||||
RUN echo "Etc/UTC" > /etc/localtime && \
|
||||
apt update && \
|
||||
apt -y install wget make gcc g++ python && \
|
||||
apt -y install wget python && \
|
||||
cd ~ && \
|
||||
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \
|
||||
tar xf node-v$NODE_VER.tar.gz && \
|
||||
cd node-v$NODE_VER && \
|
||||
./configure --prefix=/opt/node && \
|
||||
make -j$(nproc) > /dev/null && \
|
||||
make install
|
||||
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-x64.tar.gz && \
|
||||
tar xf node-v$NODE_VER-linux-x64.tar.gz && \
|
||||
rm node-v$NODE_VER-linux-x64.tar.gz && \
|
||||
mv node-v$NODE_VER-linux-x64 /opt/node
|
||||
|
||||
# Install jemalloc
|
||||
ENV JE_VER="5.1.0"
|
||||
ENV JE_VER="5.2.1"
|
||||
RUN apt update && \
|
||||
apt -y install autoconf && \
|
||||
apt -y install make autoconf gcc g++ && \
|
||||
cd ~ && \
|
||||
wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
|
||||
tar xf $JE_VER.tar.gz && \
|
||||
@ -30,7 +28,7 @@ RUN apt update && \
|
||||
make install_bin install_include install_lib
|
||||
|
||||
# Install ruby
|
||||
ENV RUBY_VER="2.6.1"
|
||||
ENV RUBY_VER="2.6.5"
|
||||
ENV CPPFLAGS="-I/opt/jemalloc/include"
|
||||
ENV LDFLAGS="-L/opt/jemalloc/lib/"
|
||||
RUN apt update && \
|
||||
|
53
Gemfile
53
Gemfile
@ -5,17 +5,17 @@ ruby '>= 2.4.0', '< 2.7.0'
|
||||
|
||||
gem 'pkg-config', '~> 1.3'
|
||||
|
||||
gem 'puma', '~> 3.12'
|
||||
gem 'puma', '~> 4.2'
|
||||
gem 'rails', '~> 5.2.3'
|
||||
gem 'thor', '~> 0.20'
|
||||
|
||||
gem 'hamlit-rails', '~> 0.2'
|
||||
gem 'pg', '~> 1.1'
|
||||
gem 'makara', '~> 0.4'
|
||||
gem 'pghero', '~> 2.2'
|
||||
gem 'pghero', '~> 2.3'
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.42', require: false
|
||||
gem 'aws-sdk-s3', '~> 1.48', require: false
|
||||
gem 'fog-core', '<= 2.1.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'paperclip', '~> 6.0'
|
||||
@ -24,15 +24,15 @@ gem 'streamio-ffmpeg', '~> 3.0'
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.6'
|
||||
gem 'addressable', '~> 2.7'
|
||||
gem 'bootsnap', '~> 1.4', require: false
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.6'
|
||||
gem 'iso-639'
|
||||
gem 'chewy', '~> 5.0'
|
||||
gem 'chewy', '~> 5.1'
|
||||
gem 'cld3', '~> 3.2.4'
|
||||
gem 'devise', '~> 4.6'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
gem 'devise', '~> 4.7'
|
||||
gem 'devise-two-factor', '~> 3.1'
|
||||
|
||||
group :pam_authentication, optional: true do
|
||||
gem 'devise_pam_authenticatable2', '~> 9.2'
|
||||
@ -43,42 +43,48 @@ gem 'omniauth-cas', '~> 1.1'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
gem 'omniauth', '~> 1.9'
|
||||
|
||||
gem 'doorkeeper', '~> 5.1'
|
||||
gem 'discard', '~> 1.1'
|
||||
gem 'doorkeeper', '~> 5.2'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
gem 'goldfinger', '~> 2.1'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
gem 'redis-namespace', '~> 1.5'
|
||||
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 3.3'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
|
||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
|
||||
gem 'httplog', '~> 1.3'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'kaminari', '~> 1.1'
|
||||
gem 'link_header', '~> 0.0'
|
||||
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
|
||||
gem 'mime-types', '~> 3.3', require: 'mime/types/columnar'
|
||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||
gem 'nokogiri', '~> 1.10'
|
||||
gem 'nsa', '~> 0.2'
|
||||
gem 'oj', '~> 3.7'
|
||||
gem 'oj', '~> 3.9'
|
||||
gem 'ostatus2', '~> 2.0'
|
||||
gem 'ox', '~> 2.11'
|
||||
gem 'parslet'
|
||||
gem 'parallel', '~> 1.17'
|
||||
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
||||
gem 'pundit', '~> 2.0'
|
||||
gem 'pundit', '~> 2.1'
|
||||
gem 'premailer-rails'
|
||||
gem 'rack-attack', '~> 6.0'
|
||||
gem 'rack-attack', '~> 6.1'
|
||||
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
|
||||
gem 'rails-i18n', '~> 5.1'
|
||||
gem 'rails-settings-cached', '~> 0.6'
|
||||
gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']
|
||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||
gem 'rqrcode', '~> 0.10'
|
||||
gem 'sanitize', '~> 5.0'
|
||||
gem 'ruby-progressbar', '~> 1.10'
|
||||
gem 'sanitize', '~> 5.1'
|
||||
gem 'sidekiq', '~> 5.2'
|
||||
gem 'sidekiq-scheduler', '~> 3.0'
|
||||
gem 'sidekiq-unique-jobs', '~> 6.0'
|
||||
gem 'sidekiq-bulk', '~>0.2.0'
|
||||
gem 'simple-navigation', '~> 4.0'
|
||||
gem 'simple-navigation', '~> 4.1'
|
||||
gem 'simple_form', '~> 4.1'
|
||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||
gem 'stoplight', '~> 2.1.3'
|
||||
@ -90,7 +96,7 @@ gem 'tzinfo-data', '~> 1.2019'
|
||||
gem 'webpacker', '~> 4.0'
|
||||
gem 'webpush'
|
||||
|
||||
gem 'json-ld', '~> 3.0'
|
||||
gem 'json-ld', git: 'https://github.com/ruby-rdf/json-ld.git', ref: 'e742697a0906e74e8bb777ef98137bc3955d981d'
|
||||
gem 'json-ld-preloaded', '~> 3.0'
|
||||
gem 'rdf-normalize', '~> 0.3'
|
||||
|
||||
@ -108,14 +114,14 @@ group :production, :test do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.24'
|
||||
gem 'capybara', '~> 3.29'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 1.9'
|
||||
gem 'faker', '~> 2.4'
|
||||
gem 'microformats', '~> 4.1'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.0'
|
||||
gem 'simplecov', '~> 0.16', require: false
|
||||
gem 'webmock', '~> 3.6'
|
||||
gem 'simplecov', '~> 0.17', require: false
|
||||
gem 'webmock', '~> 3.7'
|
||||
gem 'parallel_tests', '~> 2.29'
|
||||
end
|
||||
|
||||
@ -128,9 +134,9 @@ group :development do
|
||||
gem 'letter_opener', '~> 1.7'
|
||||
gem 'letter_opener_web', '~> 1.3'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 0.71', require: false
|
||||
gem 'rubocop-rails', '~> 2.0', require: false
|
||||
gem 'brakeman', '~> 4.5', require: false
|
||||
gem 'rubocop', '~> 0.74', require: false
|
||||
gem 'rubocop-rails', '~> 2.3', require: false
|
||||
gem 'brakeman', '~> 4.6', require: false
|
||||
gem 'bundler-audit', '~> 0.6', require: false
|
||||
|
||||
gem 'capistrano', '~> 3.11'
|
||||
@ -148,3 +154,4 @@ group :production do
|
||||
end
|
||||
|
||||
gem 'concurrent-ruby', require: false
|
||||
gem 'connection_pool', require: false
|
||||
|
270
Gemfile.lock
270
Gemfile.lock
@ -1,3 +1,11 @@
|
||||
GIT
|
||||
remote: https://github.com/ianheggie/health_check
|
||||
revision: 0b799ead604f900ed50685e9b2d469cd2befba5b
|
||||
ref: 0b799ead604f900ed50685e9b2d469cd2befba5b
|
||||
specs:
|
||||
health_check (4.0.0.pre)
|
||||
rails (>= 4.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rtomayko/posix-spawn
|
||||
revision: 58465d2e213991f8afb13b984854a49fcdcc980c
|
||||
@ -5,13 +13,34 @@ GIT
|
||||
specs:
|
||||
posix-spawn (0.3.13)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/ruby-rdf/json-ld.git
|
||||
revision: e742697a0906e74e8bb777ef98137bc3955d981d
|
||||
ref: e742697a0906e74e8bb777ef98137bc3955d981d
|
||||
specs:
|
||||
json-ld (3.0.2)
|
||||
htmlentities (~> 4.3)
|
||||
json-canonicalization (~> 0.1)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
multi_json (~> 1.13)
|
||||
rack (>= 1.6, < 3.0)
|
||||
rdf (~> 3.0, >= 3.0.8)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/tmm1/http_parser.rb
|
||||
revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
|
||||
ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
|
||||
submodules: true
|
||||
specs:
|
||||
http_parser.rb (0.6.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/witgo/nilsimsa
|
||||
revision: fd184883048b922b176939f851338d0a4971a532
|
||||
ref: fd184883048b922b176939f851338d0a4971a532
|
||||
specs:
|
||||
nilsimsa (1.1.2)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@ -38,9 +67,9 @@ GEM
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.10.9)
|
||||
actionpack (>= 4.1, < 6)
|
||||
activemodel (>= 4.1, < 6)
|
||||
active_model_serializers (0.10.10)
|
||||
actionpack (>= 4.1, < 6.1)
|
||||
activemodel (>= 4.1, < 6.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.6.2)
|
||||
@ -62,9 +91,9 @@ GEM
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.6.0)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
airbrussh (1.3.0)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
airbrussh (1.3.3)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
annotate (2.7.5)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
@ -76,17 +105,17 @@ GEM
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.175.0)
|
||||
aws-sdk-core (3.55.0)
|
||||
aws-partitions (1.207.0)
|
||||
aws-sdk-core (3.65.1)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-partitions (~> 1.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.21.0)
|
||||
aws-sdk-core (~> 3, >= 3.53.0)
|
||||
aws-sdk-kms (1.24.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.42.0)
|
||||
aws-sdk-core (~> 3, >= 3.53.0)
|
||||
aws-sdk-s3 (1.48.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.0)
|
||||
@ -101,19 +130,19 @@ GEM
|
||||
debug_inspector (>= 0.0.1)
|
||||
blurhash (0.1.3)
|
||||
ffi (~> 1.10.0)
|
||||
bootsnap (1.4.4)
|
||||
bootsnap (1.4.5)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.5.1)
|
||||
browser (2.5.3)
|
||||
brakeman (4.6.1)
|
||||
browser (2.6.1)
|
||||
builder (3.2.3)
|
||||
bullet (6.0.0)
|
||||
bullet (6.0.2)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundler-audit (0.6.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 0.18)
|
||||
byebug (11.0.0)
|
||||
capistrano (3.11.0)
|
||||
capistrano (3.11.1)
|
||||
airbrussh (>= 1.0.0)
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
@ -129,7 +158,7 @@ GEM
|
||||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.24.0)
|
||||
capybara (3.29.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
@ -140,7 +169,7 @@ GEM
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
charlock_holmes (0.7.6)
|
||||
chewy (5.0.0)
|
||||
chewy (5.1.0)
|
||||
activesupport (>= 4.0)
|
||||
elasticsearch (>= 2.0.0)
|
||||
elasticsearch-dsl
|
||||
@ -156,10 +185,10 @@ GEM
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.4)
|
||||
css_parser (1.6.0)
|
||||
css_parser (1.7.0)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
derailed_benchmarks (1.3.5)
|
||||
derailed_benchmarks (1.3.6)
|
||||
benchmark-ips (~> 2)
|
||||
get_process_mem (~> 0)
|
||||
heapy (~> 0)
|
||||
@ -167,38 +196,40 @@ GEM
|
||||
rack (>= 1)
|
||||
rake (> 10, < 13)
|
||||
thor (~> 0.19)
|
||||
devise (4.6.2)
|
||||
devise (4.7.1)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 6.0)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (3.0.3)
|
||||
activesupport (< 5.3)
|
||||
devise-two-factor (3.1.0)
|
||||
activesupport (< 6.1)
|
||||
attr_encrypted (>= 1.3, < 4, != 2)
|
||||
devise (~> 4.0)
|
||||
railties (< 5.3)
|
||||
railties (< 6.1)
|
||||
rotp (~> 2.0)
|
||||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.3)
|
||||
docile (1.3.0)
|
||||
discard (1.1.0)
|
||||
activerecord (>= 4.2, < 7)
|
||||
docile (1.3.2)
|
||||
domain_name (0.5.20180417)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (5.1.0)
|
||||
doorkeeper (5.2.1)
|
||||
railties (>= 5)
|
||||
dotenv (2.7.2)
|
||||
dotenv-rails (2.7.2)
|
||||
dotenv (= 2.7.2)
|
||||
dotenv (2.7.5)
|
||||
dotenv-rails (2.7.5)
|
||||
dotenv (= 2.7.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
elasticsearch (6.0.2)
|
||||
elasticsearch-api (= 6.0.2)
|
||||
elasticsearch-transport (= 6.0.2)
|
||||
elasticsearch-api (6.0.2)
|
||||
elasticsearch (7.3.0)
|
||||
elasticsearch-api (= 7.3.0)
|
||||
elasticsearch-transport (= 7.3.0)
|
||||
elasticsearch-api (7.3.0)
|
||||
multi_json
|
||||
elasticsearch-dsl (0.1.5)
|
||||
elasticsearch-transport (6.0.2)
|
||||
elasticsearch-dsl (0.1.8)
|
||||
elasticsearch-transport (7.3.0)
|
||||
faraday
|
||||
multi_json
|
||||
encryptor (3.0.0)
|
||||
@ -208,12 +239,12 @@ GEM
|
||||
tzinfo
|
||||
excon (0.62.0)
|
||||
fabrication (2.20.2)
|
||||
faker (1.9.3)
|
||||
i18n (>= 0.7)
|
||||
faraday (0.15.0)
|
||||
faker (2.4.0)
|
||||
i18n (~> 1.6.0)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
fast_blank (1.0.0)
|
||||
fastimage (2.1.5)
|
||||
fastimage (2.1.7)
|
||||
ffi (1.10.0)
|
||||
fog-core (2.1.0)
|
||||
builder
|
||||
@ -253,7 +284,7 @@ GEM
|
||||
railties (>= 4.0.1)
|
||||
hamster (3.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
hashdiff (0.4.0)
|
||||
hashdiff (1.0.0)
|
||||
hashie (3.6.0)
|
||||
heapy (0.1.4)
|
||||
highline (2.0.1)
|
||||
@ -269,7 +300,7 @@ GEM
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.1.1)
|
||||
http_accept_language (2.1.1)
|
||||
httplog (1.3.1)
|
||||
httplog (1.3.2)
|
||||
rack (>= 1.0)
|
||||
rainbow (>= 2.0.0)
|
||||
i18n (1.6.0)
|
||||
@ -287,17 +318,15 @@ GEM
|
||||
idn-ruby (0.1.0)
|
||||
ipaddress (0.8.3)
|
||||
iso-639 (0.2.8)
|
||||
jaro_winkler (1.5.2)
|
||||
jaro_winkler (1.5.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.1.0)
|
||||
json-ld (3.0.2)
|
||||
multi_json (~> 1.12)
|
||||
rdf (>= 2.2.8, < 4.0)
|
||||
json-ld-preloaded (3.0.2)
|
||||
json (2.2.0)
|
||||
json-canonicalization (0.1.0)
|
||||
json-ld-preloaded (3.0.4)
|
||||
json-ld (~> 3.0)
|
||||
multi_json (~> 1.12)
|
||||
rdf (~> 3.0)
|
||||
jsonapi-renderer (0.2.0)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.1.0)
|
||||
kaminari (1.1.1)
|
||||
activesupport (>= 4.1.0)
|
||||
@ -336,37 +365,37 @@ GEM
|
||||
mimemagic (~> 0.3.2)
|
||||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
memory_profiler (0.9.13)
|
||||
memory_profiler (0.9.14)
|
||||
method_source (0.9.2)
|
||||
microformats (4.1.0)
|
||||
json (~> 2.1)
|
||||
nokogiri (~> 1.8, >= 1.8.3)
|
||||
mime-types (3.2.2)
|
||||
mime-types (3.3)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2018.0812)
|
||||
mime-types-data (3.2019.0904)
|
||||
mimemagic (0.3.3)
|
||||
mini_mime (1.0.1)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
msgpack (1.2.10)
|
||||
minitest (5.12.0)
|
||||
msgpack (1.3.1)
|
||||
multi_json (1.13.1)
|
||||
multipart-post (2.0.0)
|
||||
multipart-post (2.1.1)
|
||||
necromancer (0.5.0)
|
||||
net-ldap (0.16.1)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (5.0.2)
|
||||
nio4r (2.3.1)
|
||||
nokogiri (1.10.3)
|
||||
net-scp (2.0.0)
|
||||
net-ssh (>= 2.6.5, < 6.0.0)
|
||||
net-ssh (5.2.0)
|
||||
nio4r (2.5.1)
|
||||
nokogiri (1.10.4)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
nokogumbo (2.0.0)
|
||||
nokogumbo (2.0.1)
|
||||
nokogiri (~> 1.8, >= 1.8.4)
|
||||
nsa (0.2.7)
|
||||
activesupport (>= 4.2, < 6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.7.12)
|
||||
oj (3.9.1)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
@ -393,23 +422,24 @@ GEM
|
||||
av (~> 0.9.0)
|
||||
paperclip (>= 2.5.2)
|
||||
parallel (1.17.0)
|
||||
parallel_tests (2.29.0)
|
||||
parallel_tests (2.29.2)
|
||||
parallel
|
||||
parser (2.6.3.0)
|
||||
parser (2.6.4.0)
|
||||
ast (~> 2.4.0)
|
||||
parslet (1.8.2)
|
||||
pastel (0.7.2)
|
||||
equatable (~> 0.5.0)
|
||||
tty-color (~> 0.4.0)
|
||||
pg (1.1.4)
|
||||
pghero (2.2.1)
|
||||
activerecord
|
||||
pkg-config (1.3.7)
|
||||
pghero (2.3.0)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.3.8)
|
||||
premailer (1.11.1)
|
||||
addressable
|
||||
css_parser (>= 1.6.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.10.2)
|
||||
actionmailer (>= 3, < 6)
|
||||
premailer-rails (1.10.3)
|
||||
actionmailer (>= 3)
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
pry (0.12.2)
|
||||
@ -420,13 +450,14 @@ GEM
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.1.0)
|
||||
puma (3.12.1)
|
||||
pundit (2.0.1)
|
||||
public_suffix (4.0.1)
|
||||
puma (4.2.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.1.6)
|
||||
rack (2.0.7)
|
||||
rack-attack (6.0.0)
|
||||
rack-attack (6.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.0.3)
|
||||
rack-protection (2.0.5)
|
||||
@ -455,7 +486,7 @@ GEM
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.4)
|
||||
rails-html-sanitizer (1.2.0)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-i18n (5.1.3)
|
||||
i18n (>= 0.7, < 2)
|
||||
@ -469,13 +500,13 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (12.3.2)
|
||||
rdf (3.0.9)
|
||||
rake (12.3.3)
|
||||
rdf (3.0.12)
|
||||
hamster (~> 3.0)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.3.3)
|
||||
rdf (>= 2.2, < 4.0)
|
||||
redis (4.1.2)
|
||||
redis (4.1.3)
|
||||
redis-actionpack (5.0.2)
|
||||
actionpack (>= 4.0, < 6)
|
||||
redis-rack (>= 1, < 3)
|
||||
@ -494,12 +525,12 @@ GEM
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.5.0)
|
||||
redis (>= 2.2, < 5)
|
||||
regexp_parser (1.5.1)
|
||||
regexp_parser (1.6.0)
|
||||
request_store (1.4.1)
|
||||
rack (>= 1.4)
|
||||
responders (2.4.1)
|
||||
actionpack (>= 4.2.0, < 6.0)
|
||||
railties (>= 4.2.0, < 6.0)
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rotp (2.1.2)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (0.10.1)
|
||||
@ -524,23 +555,23 @@ GEM
|
||||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.8.0)
|
||||
rubocop (0.71.0)
|
||||
rubocop (0.74.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-rails (2.0.1)
|
||||
rubocop-rails (2.3.2)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.70.0)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-saml (1.9.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
rufus-scheduler (3.5.2)
|
||||
fugit (~> 1.1, >= 1.1.5)
|
||||
safe_yaml (1.0.5)
|
||||
sanitize (5.0.0)
|
||||
sanitize (5.1.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.8.0)
|
||||
nokogumbo (~> 2.0)
|
||||
@ -560,12 +591,12 @@ GEM
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
sidekiq (>= 4.0, < 7.0)
|
||||
thor (~> 0)
|
||||
simple-navigation (4.0.5)
|
||||
simple-navigation (4.1.0)
|
||||
activesupport (>= 2.3.2)
|
||||
simple_form (4.1.0)
|
||||
actionpack (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
simplecov (0.16.1)
|
||||
simplecov (0.17.1)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
@ -577,7 +608,7 @@ GEM
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkit (1.17.0)
|
||||
sshkit (1.20.0)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.12)
|
||||
@ -585,7 +616,7 @@ GEM
|
||||
stoplight (2.1.3)
|
||||
streamio-ffmpeg (3.0.2)
|
||||
multi_json (~> 1.8)
|
||||
strong_migrations (0.4.0)
|
||||
strong_migrations (0.4.1)
|
||||
activerecord (>= 5)
|
||||
temple (0.8.1)
|
||||
terminal-table (1.8.0)
|
||||
@ -612,7 +643,7 @@ GEM
|
||||
unf (~> 0.1.0)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2019.1)
|
||||
tzinfo-data (1.2019.3)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
@ -621,7 +652,7 @@ GEM
|
||||
uniform_notifier (1.12.1)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
webmock (3.6.0)
|
||||
webmock (3.7.5)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
@ -645,14 +676,14 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.10)
|
||||
active_record_query_trace (~> 1.6)
|
||||
addressable (~> 2.6)
|
||||
addressable (~> 2.7)
|
||||
annotate (~> 2.7)
|
||||
aws-sdk-s3 (~> 1.42)
|
||||
aws-sdk-s3 (~> 1.48)
|
||||
better_errors (~> 2.5)
|
||||
binding_of_caller (~> 0.7)
|
||||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.4)
|
||||
brakeman (~> 4.5)
|
||||
brakeman (~> 4.6)
|
||||
browser
|
||||
bullet (~> 6.0)
|
||||
bundler-audit (~> 0.6)
|
||||
@ -660,20 +691,22 @@ DEPENDENCIES
|
||||
capistrano-rails (~> 1.4)
|
||||
capistrano-rbenv (~> 2.1)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.24)
|
||||
capybara (~> 3.29)
|
||||
charlock_holmes (~> 0.7.6)
|
||||
chewy (~> 5.0)
|
||||
chewy (~> 5.1)
|
||||
cld3 (~> 3.2.4)
|
||||
climate_control (~> 0.2)
|
||||
concurrent-ruby
|
||||
connection_pool
|
||||
derailed_benchmarks
|
||||
devise (~> 4.6)
|
||||
devise-two-factor (~> 3.0)
|
||||
devise (~> 4.7)
|
||||
devise-two-factor (~> 3.1)
|
||||
devise_pam_authenticatable2 (~> 9.2)
|
||||
doorkeeper (~> 5.1)
|
||||
discard (~> 1.1)
|
||||
doorkeeper (~> 5.2)
|
||||
dotenv-rails (~> 2.7)
|
||||
fabrication (~> 2.20)
|
||||
faker (~> 1.9)
|
||||
faker (~> 2.4)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
@ -681,6 +714,7 @@ DEPENDENCIES
|
||||
fuubar (~> 2.4)
|
||||
goldfinger (~> 2.1)
|
||||
hamlit-rails (~> 0.2)
|
||||
health_check!
|
||||
hiredis (~> 0.6)
|
||||
htmlentities (~> 4.3)
|
||||
http (~> 3.3)
|
||||
@ -690,7 +724,7 @@ DEPENDENCIES
|
||||
i18n-tasks (~> 0.9)
|
||||
idn-ruby
|
||||
iso-639
|
||||
json-ld (~> 3.0)
|
||||
json-ld!
|
||||
json-ld-preloaded (~> 3.0)
|
||||
kaminari (~> 1.1)
|
||||
letter_opener (~> 1.7)
|
||||
@ -701,11 +735,12 @@ DEPENDENCIES
|
||||
mario-redis-lock (~> 1.2)
|
||||
memory_profiler
|
||||
microformats (~> 4.1)
|
||||
mime-types (~> 3.2)
|
||||
mime-types (~> 3.3)
|
||||
net-ldap (~> 0.10)
|
||||
nilsimsa!
|
||||
nokogiri (~> 1.10)
|
||||
nsa (~> 0.2)
|
||||
oj (~> 3.7)
|
||||
oj (~> 3.9)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-cas (~> 1.1)
|
||||
omniauth-saml (~> 1.10)
|
||||
@ -713,18 +748,20 @@ DEPENDENCIES
|
||||
ox (~> 2.11)
|
||||
paperclip (~> 6.0)
|
||||
paperclip-av-transcoder (~> 0.6)
|
||||
parallel (~> 1.17)
|
||||
parallel_tests (~> 2.29)
|
||||
parslet
|
||||
pg (~> 1.1)
|
||||
pghero (~> 2.2)
|
||||
pghero (~> 2.3)
|
||||
pkg-config (~> 1.3)
|
||||
posix-spawn!
|
||||
premailer-rails
|
||||
private_address_check (~> 0.5)
|
||||
pry-byebug (~> 3.7)
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 3.12)
|
||||
pundit (~> 2.0)
|
||||
rack-attack (~> 6.0)
|
||||
puma (~> 4.2)
|
||||
pundit (~> 2.1)
|
||||
rack-attack (~> 6.1)
|
||||
rack-cors (~> 1.0)
|
||||
rails (~> 5.2.3)
|
||||
rails-controller-testing (~> 1.0)
|
||||
@ -737,16 +774,17 @@ DEPENDENCIES
|
||||
rqrcode (~> 0.10)
|
||||
rspec-rails (~> 3.8)
|
||||
rspec-sidekiq (~> 3.0)
|
||||
rubocop (~> 0.71)
|
||||
rubocop-rails (~> 2.0)
|
||||
sanitize (~> 5.0)
|
||||
rubocop (~> 0.74)
|
||||
rubocop-rails (~> 2.3)
|
||||
ruby-progressbar (~> 1.10)
|
||||
sanitize (~> 5.1)
|
||||
sidekiq (~> 5.2)
|
||||
sidekiq-bulk (~> 0.2.0)
|
||||
sidekiq-scheduler (~> 3.0)
|
||||
sidekiq-unique-jobs (~> 6.0)
|
||||
simple-navigation (~> 4.0)
|
||||
simple-navigation (~> 4.1)
|
||||
simple_form (~> 4.1)
|
||||
simplecov (~> 0.16)
|
||||
simplecov (~> 0.17)
|
||||
sprockets-rails (~> 3.2)
|
||||
stackprof
|
||||
stoplight (~> 2.1.3)
|
||||
@ -757,12 +795,12 @@ DEPENDENCIES
|
||||
tty-prompt (~> 0.19)
|
||||
twitter-text (~> 1.14)
|
||||
tzinfo-data (~> 1.2019)
|
||||
webmock (~> 3.6)
|
||||
webmock (~> 3.7)
|
||||
webpacker (~> 4.0)
|
||||
webpush
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.1p33
|
||||
ruby 2.6.5p114
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
|
9
app.json
9
app.json
@ -13,15 +13,6 @@
|
||||
"description": "The domain that your Mastodon instance will run on (this can be appname.herokuapp.com or a custom domain)",
|
||||
"required": true
|
||||
},
|
||||
"LOCAL_HTTPS": {
|
||||
"description": "Will your domain support HTTPS? (Automatic for herokuapp, requires manual configuration for custom domains)",
|
||||
"value": "false",
|
||||
"required": true
|
||||
},
|
||||
"PAPERCLIP_SECRET": {
|
||||
"description": "The secret key for storing media files",
|
||||
"generator": "secret"
|
||||
},
|
||||
"SECRET_KEY_BASE": {
|
||||
"description": "The secret key base",
|
||||
"generator": "secret"
|
||||
|
43
app/chewy/accounts_index.rb
Normal file
43
app/chewy/accounts_index.rb
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountsIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '5m' }, analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
tokenizer: 'whitespace',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
|
||||
edge_ngram: {
|
||||
tokenizer: 'edge_ngram',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
},
|
||||
|
||||
tokenizer: {
|
||||
edge_ngram: {
|
||||
type: 'edge_ngram',
|
||||
min_gram: 1,
|
||||
max_gram: 15,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
define_type ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? } do
|
||||
root date_detection: false do
|
||||
field :id, type: 'long'
|
||||
|
||||
field :display_name, type: 'text', analyzer: 'content' do
|
||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :acct, type: 'text', analyzer: 'content', value: ->(account) { [account.username, account.domain].compact.join('@') } do
|
||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :following_count, type: 'long', value: ->(account) { account.following.local.count }
|
||||
field :followers_count, type: 'long', value: ->(account) { account.followers.local.count }
|
||||
field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
|
||||
end
|
||||
end
|
||||
end
|
@ -31,19 +31,19 @@ class StatusesIndex < Chewy::Index
|
||||
},
|
||||
}
|
||||
|
||||
define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
|
||||
define_type ::Status.unscoped.kept.without_reblogs.includes(:media_attachments), delete_if: ->(status) { status.searchable_by.empty? } do
|
||||
crutch :mentions do |collection|
|
||||
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
||||
data = ::Mention.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
|
||||
|
||||
crutch :favourites do |collection|
|
||||
data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
||||
data = ::Favourite.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
|
||||
|
||||
crutch :reblogs do |collection|
|
||||
data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
|
||||
data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
@ -51,7 +51,7 @@ class StatusesIndex < Chewy::Index
|
||||
field :id, type: 'long'
|
||||
field :account_id, type: 'long'
|
||||
|
||||
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).concat(status.preloadable_poll ? status_preloadable_poll.options : []).join("\n\n") } do
|
||||
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
|
||||
field :stemmed, type: 'text', analyzer: 'content'
|
||||
end
|
||||
|
||||
|
37
app/chewy/tags_index.rb
Normal file
37
app/chewy/tags_index.rb
Normal file
@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TagsIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '15m' }, analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
tokenizer: 'keyword',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
|
||||
edge_ngram: {
|
||||
tokenizer: 'edge_ngram',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
},
|
||||
|
||||
tokenizer: {
|
||||
edge_ngram: {
|
||||
type: 'edge_ngram',
|
||||
min_gram: 2,
|
||||
max_gram: 15,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
define_type ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? } do
|
||||
root date_detection: false do
|
||||
field :name, type: 'text', analyzer: 'content' do
|
||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
|
||||
field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } }
|
||||
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
|
||||
end
|
||||
end
|
||||
end
|
@ -3,20 +3,46 @@
|
||||
class AboutController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :set_instance_presenter, only: [:show, :more, :terms]
|
||||
before_action :require_open_federation!, only: [:show, :more]
|
||||
before_action :set_body_classes, only: :show
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in, only: [:show, :more, :terms]
|
||||
|
||||
skip_before_action :check_user_permissions, only: [:more, :terms]
|
||||
skip_before_action :require_functional!, only: [:more, :terms]
|
||||
|
||||
def show
|
||||
@hide_navbar = true
|
||||
def show; end
|
||||
|
||||
def more
|
||||
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
|
||||
|
||||
toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
|
||||
|
||||
@contents = toc_generator.html
|
||||
@table_of_contents = toc_generator.toc
|
||||
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
|
||||
end
|
||||
|
||||
def more; end
|
||||
|
||||
def terms; end
|
||||
|
||||
helper_method :display_blocks?
|
||||
helper_method :display_blocks_rationale?
|
||||
helper_method :public_fetch_mode?
|
||||
helper_method :new_user
|
||||
|
||||
private
|
||||
|
||||
def require_open_federation!
|
||||
not_found if whitelist_mode?
|
||||
end
|
||||
|
||||
def display_blocks?
|
||||
Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def display_blocks_rationale?
|
||||
Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def new_user
|
||||
User.new.tap do |user|
|
||||
user.build_account
|
||||
@ -24,9 +50,15 @@ class AboutController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :new_user
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@hide_navbar = true
|
||||
end
|
||||
|
||||
def set_expires_in
|
||||
expires_in 0, public: true
|
||||
end
|
||||
end
|
||||
|
@ -4,17 +4,22 @@ class AccountsController < ApplicationController
|
||||
PAGE_SIZE = 20
|
||||
|
||||
include AccountControllerConcern
|
||||
include SignatureAuthentication
|
||||
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format) }
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
mark_cacheable! unless user_signed_in?
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
@body_classes = 'with-modals'
|
||||
@pinned_statuses = []
|
||||
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
|
||||
@featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
|
||||
|
||||
if current_account && @account.blocking?(current_account)
|
||||
@statuses = []
|
||||
@ -24,6 +29,7 @@ class AccountsController < ApplicationController
|
||||
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
|
||||
@statuses = filtered_status_page(params)
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
@rss_url = rss_url
|
||||
|
||||
unless @statuses.empty?
|
||||
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
|
||||
@ -31,30 +37,27 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
format.atom do
|
||||
mark_cacheable!
|
||||
|
||||
@entries = @account.stream_entries.where(hidden: false).with_includes.without_local_only.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
|
||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
|
||||
end
|
||||
|
||||
format.rss do
|
||||
mark_cacheable!
|
||||
expires_in 1.minute, public: true
|
||||
|
||||
@statuses = cache_collection(default_statuses.without_local_only.without_reblogs.without_replies.limit(PAGE_SIZE), Status)
|
||||
render xml: RSS::AccountSerializer.render(@account, @statuses)
|
||||
@statuses = filtered_statuses.without_local_only.without_reblogs.without_replies.limit(PAGE_SIZE)
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
|
||||
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def show_pinned_statuses?
|
||||
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
|
||||
end
|
||||
@ -101,6 +104,14 @@ class AccountsController < ApplicationController
|
||||
params[:username]
|
||||
end
|
||||
|
||||
def rss_url
|
||||
if tag_requested?
|
||||
short_account_tag_url(@account, params[:tag], format: 'rss')
|
||||
else
|
||||
short_account_url(@account, format: 'rss')
|
||||
end
|
||||
end
|
||||
|
||||
def older_url
|
||||
pagination_url(max_id: @statuses.last.id)
|
||||
end
|
||||
@ -122,15 +133,15 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def media_requested?
|
||||
request.path.ends_with?('/media')
|
||||
request.path.ends_with?('/media') && !tag_requested?
|
||||
end
|
||||
|
||||
def replies_requested?
|
||||
request.path.ends_with?('/with_replies')
|
||||
request.path.ends_with?('/with_replies') && !tag_requested?
|
||||
end
|
||||
|
||||
def tag_requested?
|
||||
request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
end
|
||||
|
||||
def filtered_status_page(params)
|
||||
@ -140,4 +151,12 @@ class AccountsController < ApplicationController
|
||||
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
|
||||
end
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
if signed_request_account.present? || public_fetch_mode?
|
||||
# Return all fields
|
||||
else
|
||||
%i(id type preferred_username inbox public_key endpoints)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
11
app/controllers/activitypub/base_controller.rb
Normal file
11
app/controllers/activitypub/base_controller.rb
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::BaseController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
private
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
|
||||
end
|
||||
end
|
@ -1,30 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::CollectionsController < Api::BaseController
|
||||
class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_account
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_size
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
render_cached_json(['activitypub', 'collection', @account, params[:id]], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(
|
||||
collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
skip_activities: true
|
||||
)
|
||||
end
|
||||
expires_in 3.minutes, public: public_fetch_mode?
|
||||
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = scope_for_collection
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
@ -42,9 +33,9 @@ class ActivityPub::CollectionsController < Api::BaseController
|
||||
def scope_for_collection
|
||||
case params[:id]
|
||||
when 'featured'
|
||||
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
|
||||
scope.merge!(@account.pinned_statuses)
|
||||
end
|
||||
return Status.none if @account.blocking?(signed_request_account)
|
||||
|
||||
@account.pinned_statuses
|
||||
else
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
@ -1,40 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::InboxesController < Api::BaseController
|
||||
class ActivityPub::InboxesController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include JsonLdHelper
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_account
|
||||
before_action :skip_unknown_actor_delete
|
||||
before_action :require_signature!
|
||||
|
||||
def create
|
||||
if unknown_deleted_account?
|
||||
head 202
|
||||
elsif signed_request_account
|
||||
upgrade_account
|
||||
process_payload
|
||||
head 202
|
||||
else
|
||||
render plain: signature_verification_failure_reason, status: 401
|
||||
end
|
||||
upgrade_account
|
||||
process_payload
|
||||
head 202
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def skip_unknown_actor_delete
|
||||
head 202 if unknown_deleted_account?
|
||||
end
|
||||
|
||||
def unknown_deleted_account?
|
||||
json = Oj.load(body, mode: :strict)
|
||||
json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
|
||||
json.is_a?(Hash) && json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
|
||||
rescue Oj::ParseError
|
||||
false
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username]) if params[:account_username]
|
||||
def account_required?
|
||||
params[:account_username].present?
|
||||
end
|
||||
|
||||
def body
|
||||
return @body if defined?(@body)
|
||||
@body = request.body.read.force_encoding('UTF-8')
|
||||
|
||||
@body = request.body.read
|
||||
@body.force_encoding('UTF-8') if @body.present?
|
||||
|
||||
request.body.rewind if request.body.respond_to?(:rewind)
|
||||
|
||||
@body
|
||||
end
|
||||
|
||||
@ -44,7 +48,6 @@ class ActivityPub::InboxesController < Api::BaseController
|
||||
ResolveAccountWorker.perform_async(signed_request_account.acct)
|
||||
end
|
||||
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
|
||||
DeliveryFailureTracker.track_inverse_success!(signed_request_account)
|
||||
end
|
||||
|
||||
|
@ -1,26 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::OutboxesController < Api::BaseController
|
||||
class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||
LIMIT = 20
|
||||
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_account
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in 1.minute, public: true unless page_requested?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def outbox_presenter
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
|
71
app/controllers/activitypub/replies_controller.rb
Normal file
71
app/controllers/activitypub/replies_controller.rb
Normal file
@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||
include SignatureAuthentication
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
DESCENDANTS_LIMIT = 60
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_status
|
||||
before_action :set_cache_headers
|
||||
before_action :set_replies
|
||||
|
||||
def index
|
||||
expires_in 0, public: public_fetch_mode?
|
||||
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def set_replies
|
||||
@replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
|
||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
|
||||
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
||||
end
|
||||
|
||||
def replies_collection_presenter
|
||||
page = ActivityPub::CollectionPresenter.new(
|
||||
id: account_status_replies_url(@account, @status, page_params),
|
||||
type: :unordered,
|
||||
part_of: account_status_replies_url(@account, @status),
|
||||
next: next_page,
|
||||
items: @replies.map { |status| status.local ? status : status.uri }
|
||||
)
|
||||
|
||||
return page if page_requested?
|
||||
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_status_replies_url(@account, @status),
|
||||
type: :unordered,
|
||||
first: page
|
||||
)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page] == 'true'
|
||||
end
|
||||
|
||||
def next_page
|
||||
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
|
||||
account_status_replies_url(
|
||||
@account,
|
||||
@status,
|
||||
page: true,
|
||||
min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id,
|
||||
only_other_accounts: only_other_accounts
|
||||
)
|
||||
end
|
||||
|
||||
def page_params
|
||||
params_slice(:only_other_accounts, :min_id).merge(page: true)
|
||||
end
|
||||
end
|
@ -5,7 +5,7 @@ module Admin
|
||||
before_action :set_account
|
||||
|
||||
def new
|
||||
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true)
|
||||
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true, include_statuses: true)
|
||||
@warning_presets = AccountWarningPreset.all
|
||||
end
|
||||
|
||||
@ -30,7 +30,7 @@ module Admin
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification)
|
||||
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
module Admin
|
||||
class AccountsController < BaseController
|
||||
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
|
||||
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
|
||||
before_action :require_remote_account!, only: [:redownload]
|
||||
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
||||
|
||||
def index
|
||||
@ -19,18 +19,6 @@ module Admin
|
||||
@warnings = @account.targeted_account_warnings.latest.custom
|
||||
end
|
||||
|
||||
def subscribe
|
||||
authorize @account, :subscribe?
|
||||
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
authorize @account, :unsubscribe?
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def memorialize
|
||||
authorize @account, :memorialize?
|
||||
@account.memorialize!
|
||||
@ -53,7 +41,7 @@ module Admin
|
||||
|
||||
def reject
|
||||
authorize @account.user, :reject?
|
||||
SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
|
||||
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||
redirect_to admin_pending_accounts_path
|
||||
end
|
||||
|
||||
|
@ -2,19 +2,20 @@
|
||||
|
||||
module Admin
|
||||
class CustomEmojisController < BaseController
|
||||
before_action :set_custom_emoji, except: [:index, :new, :create]
|
||||
before_action :set_filter_params
|
||||
|
||||
include ObfuscateFilename
|
||||
|
||||
obfuscate_filename [:custom_emoji, :image]
|
||||
|
||||
def index
|
||||
authorize :custom_emoji, :index?
|
||||
|
||||
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
|
||||
@form = Form::CustomEmojiBatch.new
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :custom_emoji, :create?
|
||||
|
||||
@custom_emoji = CustomEmoji.new
|
||||
end
|
||||
|
||||
@ -31,69 +32,17 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @custom_emoji, :update?
|
||||
|
||||
if @custom_emoji.update(resource_params)
|
||||
log_action :update, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
|
||||
else
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.update_failed_msg')
|
||||
end
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @custom_emoji, :destroy?
|
||||
@custom_emoji.destroy!
|
||||
log_action :destroy, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def copy
|
||||
authorize @custom_emoji, :copy?
|
||||
|
||||
emoji = CustomEmoji.find_or_initialize_by(domain: nil,
|
||||
shortcode: @custom_emoji.shortcode)
|
||||
emoji.image = @custom_emoji.image
|
||||
|
||||
if emoji.save
|
||||
log_action :create, emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
|
||||
else
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
|
||||
end
|
||||
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def enable
|
||||
authorize @custom_emoji, :enable?
|
||||
@custom_emoji.update!(disabled: false)
|
||||
log_action :enable, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def disable
|
||||
authorize @custom_emoji, :disable?
|
||||
@custom_emoji.update!(disabled: true)
|
||||
log_action :disable, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
def batch
|
||||
@form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
ensure
|
||||
redirect_to admin_custom_emojis_path(filter_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_custom_emoji
|
||||
@custom_emoji = CustomEmoji.find(params[:id])
|
||||
end
|
||||
|
||||
def set_filter_params
|
||||
@filter_params = filter_params.to_hash.symbolize_keys
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
|
||||
end
|
||||
@ -103,12 +52,29 @@ module Admin
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(
|
||||
:local,
|
||||
:remote,
|
||||
:by_domain,
|
||||
:shortcode
|
||||
)
|
||||
params.slice(:local, :remote, :by_domain, :shortcode, :page).permit(:local, :remote, :by_domain, :shortcode, :page)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:update]
|
||||
'update'
|
||||
elsif params[:list]
|
||||
'list'
|
||||
elsif params[:unlist]
|
||||
'unlist'
|
||||
elsif params[:enable]
|
||||
'enable'
|
||||
elsif params[:disable]
|
||||
'disable'
|
||||
elsif params[:copy]
|
||||
'copy'
|
||||
elsif params[:delete]
|
||||
'delete'
|
||||
end
|
||||
end
|
||||
|
||||
def form_custom_emoji_batch_params
|
||||
params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,6 +5,7 @@ module Admin
|
||||
class DashboardController < BaseController
|
||||
def index
|
||||
@users_count = User.count
|
||||
@pending_users_count = User.pending.count
|
||||
@registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0
|
||||
@logins_week = Redis.current.pfcount("activity:logins:#{current_week}")
|
||||
@interactions_week = Redis.current.get("activity:interactions:#{current_week}") || 0
|
||||
@ -19,7 +20,7 @@ module Admin
|
||||
@redis_version = redis_info['redis_version']
|
||||
@reports_count = Report.unresolved.count
|
||||
@queue_backlog = Sidekiq::Stats.new.enqueued
|
||||
@recent_users = User.confirmed.recent.includes(:account).limit(4)
|
||||
@recent_users = User.confirmed.recent.includes(:account).limit(8)
|
||||
@database_size = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
|
||||
@redis_size = redis_info['used_memory']
|
||||
@ldap_enabled = ENV['LDAP_ENABLED'] == 'true'
|
||||
@ -27,9 +28,14 @@ module Admin
|
||||
@saml_enabled = ENV['SAML_ENABLED'] == 'true'
|
||||
@pam_enabled = ENV['PAM_ENABLED'] == 'true'
|
||||
@hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
|
||||
@trending_hashtags = TrendingTags.get(7)
|
||||
@trending_hashtags = TrendingTags.get(10, filtered: false)
|
||||
@pending_tags_count = Tag.pending_review.count
|
||||
@authorized_fetch = authorized_fetch_mode?
|
||||
@whitelist_enabled = whitelist_mode?
|
||||
@profile_directory = Setting.profile_directory
|
||||
@timeline_preview = Setting.timeline_preview
|
||||
@spam_check_enabled = Setting.spam_check_enabled
|
||||
@trends_enabled = Setting.trends
|
||||
end
|
||||
|
||||
private
|
||||
@ -39,7 +45,13 @@ module Admin
|
||||
end
|
||||
|
||||
def redis_info
|
||||
@redis_info ||= Redis.current.info
|
||||
@redis_info ||= begin
|
||||
if Redis.current.is_a?(Redis::Namespace)
|
||||
Redis.current.redis.info
|
||||
else
|
||||
Redis.current.info
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
40
app/controllers/admin/domain_allows_controller.rb
Normal file
40
app/controllers/admin/domain_allows_controller.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::DomainAllowsController < Admin::BaseController
|
||||
before_action :set_domain_allow, only: [:destroy]
|
||||
|
||||
def new
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
@domain_allow = DomainAllow.new(domain: params[:_domain])
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
@domain_allow = DomainAllow.new(resource_params)
|
||||
|
||||
if @domain_allow.save
|
||||
log_action :create, @domain_allow
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @domain_allow, :destroy?
|
||||
UnallowDomainService.new.call(@domain_allow)
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_domain_allow
|
||||
@domain_allow = DomainAllow.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_allow).permit(:domain)
|
||||
end
|
||||
end
|
@ -2,13 +2,17 @@
|
||||
|
||||
module Admin
|
||||
class DomainBlocksController < BaseController
|
||||
before_action :set_domain_block, only: [:show, :destroy]
|
||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
|
||||
|
||||
def new
|
||||
authorize :domain_block, :create?
|
||||
@domain_block = DomainBlock.new(domain: params[:_domain])
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :domain_block, :create?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_block, :create?
|
||||
|
||||
@ -35,6 +39,22 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :domain_block, :create?
|
||||
|
||||
@domain_block.update(update_params)
|
||||
|
||||
severity_changed = @domain_block.severity_changed?
|
||||
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
|
||||
log_action :create, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_block, :show?
|
||||
end
|
||||
@ -52,8 +72,12 @@ module Admin
|
||||
@domain_block = DomainBlock.find(params[:id])
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports)
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
module Admin
|
||||
class InstancesController < BaseController
|
||||
before_action :set_domain_block, only: :show
|
||||
before_action :set_domain_allow, only: :show
|
||||
before_action :set_instance, only: :show
|
||||
|
||||
def index
|
||||
authorize :instance, :index?
|
||||
|
||||
@ -11,20 +15,40 @@ module Admin
|
||||
def show
|
||||
authorize :instance, :show?
|
||||
|
||||
@instance = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
|
||||
@following_count = Follow.where(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
|
||||
@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)
|
||||
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
|
||||
@domain_block = DomainBlock.rule_for(params[:id])
|
||||
@private_comment = @domain_block&.private_comment
|
||||
@public_comment = @domain_block&.public_comment
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_domain_block
|
||||
@domain_block = DomainBlock.rule_for(params[:id])
|
||||
end
|
||||
|
||||
def set_domain_allow
|
||||
@domain_allow = DomainAllow.rule_for(params[:id])
|
||||
end
|
||||
|
||||
def set_instance
|
||||
resource = Account.by_domain_accounts.find_by(domain: params[:id])
|
||||
resource ||= @domain_block
|
||||
resource ||= @domain_allow
|
||||
|
||||
if resource
|
||||
@instance = Instance.new(resource)
|
||||
else
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_instances
|
||||
InstanceFilter.new(filter_params).results
|
||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
||||
end
|
||||
|
||||
def paginated_instances
|
||||
|
@ -3,6 +3,7 @@
|
||||
module Admin
|
||||
class RelaysController < BaseController
|
||||
before_action :set_relay, except: [:index, :new, :create]
|
||||
before_action :require_signatures_enabled!, only: [:new, :create, :enable]
|
||||
|
||||
def index
|
||||
authorize :relay, :update?
|
||||
@ -11,7 +12,7 @@ module Admin
|
||||
|
||||
def new
|
||||
authorize :relay, :update?
|
||||
@relay = Relay.new(inbox_url: Relay::PRESET_RELAY)
|
||||
@relay = Relay.new
|
||||
end
|
||||
|
||||
def create
|
||||
@ -54,5 +55,9 @@ module Admin
|
||||
def resource_params
|
||||
params.require(:relay).permit(:inbox_url)
|
||||
end
|
||||
|
||||
def require_signatures_enabled!
|
||||
redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,10 +5,10 @@ module Admin
|
||||
before_action :set_report_note, only: [:destroy]
|
||||
|
||||
def create
|
||||
authorize ReportNote, :create?
|
||||
authorize :report_note, :create?
|
||||
|
||||
@report_note = current_account.report_notes.new(resource_params)
|
||||
@report = @report_note.report
|
||||
@report = @report_note.report
|
||||
|
||||
if @report_note.save
|
||||
if params[:create_and_resolve]
|
||||
@ -26,9 +26,8 @@ module Admin
|
||||
|
||||
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
|
||||
else
|
||||
@report_notes = @report.notes.latest
|
||||
@report_history = @report.history
|
||||
@form = Form::StatusBatch.new
|
||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
||||
@form = Form::StatusBatch.new
|
||||
|
||||
render template: 'admin/reports/show'
|
||||
end
|
||||
|
@ -2,43 +2,102 @@
|
||||
|
||||
module Admin
|
||||
class TagsController < BaseController
|
||||
before_action :set_tags, only: :index
|
||||
before_action :set_tag, except: :index
|
||||
before_action :set_filter_params
|
||||
before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all]
|
||||
before_action :set_usage_by_domain, except: [:index, :batch, :approve_all, :reject_all]
|
||||
before_action :set_counters, except: [:index, :batch, :approve_all, :reject_all]
|
||||
|
||||
def index
|
||||
authorize :tag, :index?
|
||||
|
||||
@tags = filtered_tags.page(params[:page])
|
||||
@form = Form::TagBatch.new
|
||||
end
|
||||
|
||||
def hide
|
||||
authorize @tag, :hide?
|
||||
@tag.account_tag_stat.update!(hidden: true)
|
||||
redirect_to admin_tags_path(@filter_params)
|
||||
def batch
|
||||
@form = Form::TagBatch.new(form_tag_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
ensure
|
||||
redirect_to admin_tags_path(filter_params)
|
||||
end
|
||||
|
||||
def unhide
|
||||
authorize @tag, :unhide?
|
||||
@tag.account_tag_stat.update!(hidden: false)
|
||||
redirect_to admin_tags_path(@filter_params)
|
||||
def approve_all
|
||||
Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'approve').save
|
||||
redirect_to admin_tags_path(filter_params)
|
||||
end
|
||||
|
||||
def reject_all
|
||||
Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'reject').save
|
||||
redirect_to admin_tags_path(filter_params)
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @tag, :show?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @tag, :update?
|
||||
|
||||
if @tag.update(tag_params.merge(reviewed_at: Time.now.utc))
|
||||
redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = Tag.discoverable
|
||||
@tags.merge!(Tag.hidden) if filter_params[:hidden]
|
||||
end
|
||||
|
||||
def set_tag
|
||||
@tag = Tag.find(params[:id])
|
||||
end
|
||||
|
||||
def set_filter_params
|
||||
@filter_params = filter_params.to_hash.symbolize_keys
|
||||
def set_usage_by_domain
|
||||
@usage_by_domain = @tag.statuses
|
||||
.with_public_visibility
|
||||
.excluding_silenced_accounts
|
||||
.where(Status.arel_table[:id].gteq(Mastodon::Snowflake.id_at(Time.now.utc.beginning_of_day)))
|
||||
.joins(:account)
|
||||
.group('accounts.domain')
|
||||
.reorder('statuses_count desc')
|
||||
.pluck('accounts.domain, count(*) AS statuses_count')
|
||||
end
|
||||
|
||||
def set_counters
|
||||
@accounts_today = @tag.history.first[:accounts]
|
||||
@accounts_week = Redis.current.pfcount(*current_week_days.map { |day| "activity:tags:#{@tag.id}:#{day}:accounts" })
|
||||
end
|
||||
|
||||
def filtered_tags
|
||||
TagFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(:hidden)
|
||||
params.slice(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name).permit(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name)
|
||||
end
|
||||
|
||||
def tag_params
|
||||
params.require(:tag).permit(:name, :trendable, :usable, :listable)
|
||||
end
|
||||
|
||||
def current_week_days
|
||||
now = Time.now.utc.beginning_of_day.to_date
|
||||
|
||||
(Date.commercial(now.cwyear, now.cweek)..now).map do |date|
|
||||
date.to_time(:utc).beginning_of_day.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def form_tag_batch_params
|
||||
params.require(:form_tag_batch).permit(:action, tag_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:approve]
|
||||
'approve'
|
||||
elsif params[:reject]
|
||||
'reject'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -8,6 +8,7 @@ module Admin
|
||||
authorize @user, :disable_2fa?
|
||||
@user.disable_two_factor!
|
||||
log_action :disable_2fa, @user
|
||||
UserMailer.two_factor_disabled(@user).deliver_later!
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
|
@ -7,12 +7,15 @@ class Api::BaseController < ApplicationController
|
||||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :check_user_permissions
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :set_cache_headers
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
skip_around_action :set_locale
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
@ -33,6 +36,14 @@ class Api::BaseController < ApplicationController
|
||||
render json: { error: 'This action is not allowed' }, status: 403
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RaceConditionError do
|
||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from ActionController::ParameterMissing do |e|
|
||||
render json: { error: e.to_s }, status: 400
|
||||
end
|
||||
|
||||
def doorkeeper_unauthorized_render_options(error: nil)
|
||||
{ json: { error: (error.try(:description) || 'Not authorized') } }
|
||||
end
|
||||
@ -69,6 +80,10 @@ class Api::BaseController < ApplicationController
|
||||
nil
|
||||
end
|
||||
|
||||
def require_authenticated_user!
|
||||
render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
|
||||
end
|
||||
|
||||
def require_user!
|
||||
if !current_user
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
@ -94,4 +109,8 @@ class Api::BaseController < ApplicationController
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::ProofsController < Api::BaseController
|
||||
before_action :set_account
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_provider
|
||||
before_action :check_account_approval
|
||||
before_action :check_account_suspension
|
||||
|
||||
def index
|
||||
render json: @account, serializer: @provider.serializer_class
|
||||
@ -16,15 +15,7 @@ class Api::ProofsController < Api::BaseController
|
||||
@provider = ProofProvider.find(params[:provider]) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:username])
|
||||
end
|
||||
|
||||
def check_account_approval
|
||||
not_found if @account.user_pending?
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
gone if @account.suspended?
|
||||
def username_param
|
||||
params[:username]
|
||||
end
|
||||
end
|
||||
|
@ -1,73 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::PushController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
def update
|
||||
response, status = process_push_request
|
||||
render plain: response, status: status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_push_request
|
||||
case hub_mode
|
||||
when 'subscribe'
|
||||
Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds, verified_domain)
|
||||
when 'unsubscribe'
|
||||
Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
|
||||
else
|
||||
["Unknown mode: #{hub_mode}", 422]
|
||||
end
|
||||
end
|
||||
|
||||
def hub_mode
|
||||
params['hub.mode']
|
||||
end
|
||||
|
||||
def hub_topic
|
||||
params['hub.topic']
|
||||
end
|
||||
|
||||
def hub_callback
|
||||
params['hub.callback']
|
||||
end
|
||||
|
||||
def hub_lease_seconds
|
||||
params['hub.lease_seconds']
|
||||
end
|
||||
|
||||
def hub_secret
|
||||
params['hub.secret']
|
||||
end
|
||||
|
||||
def account_from_topic
|
||||
if hub_topic.present? && local_domain? && account_feed_path?
|
||||
Account.find_local(hub_topic_params[:username])
|
||||
end
|
||||
end
|
||||
|
||||
def hub_topic_params
|
||||
@_hub_topic_params ||= Rails.application.routes.recognize_path(hub_topic_uri.path)
|
||||
end
|
||||
|
||||
def hub_topic_uri
|
||||
@_hub_topic_uri ||= Addressable::URI.parse(hub_topic).normalize
|
||||
end
|
||||
|
||||
def local_domain?
|
||||
TagManager.instance.web_domain?(hub_topic_domain)
|
||||
end
|
||||
|
||||
def verified_domain
|
||||
return signed_request_account.domain if signed_request_account
|
||||
end
|
||||
|
||||
def hub_topic_domain
|
||||
hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
|
||||
end
|
||||
|
||||
def account_feed_path?
|
||||
hub_topic_params[:controller] == 'accounts' && hub_topic_params[:action] == 'show' && hub_topic_params[:format] == 'atom'
|
||||
end
|
||||
end
|
@ -1,37 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SalmonController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def update
|
||||
if verify_payload?
|
||||
process_salmon
|
||||
head 202
|
||||
elsif payload.present?
|
||||
render plain: signature_verification_failure_reason, status: 401
|
||||
else
|
||||
head 400
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def payload
|
||||
@_payload ||= request.body.read
|
||||
end
|
||||
|
||||
def verify_payload?
|
||||
payload.present? && VerifySalmonService.new.call(payload)
|
||||
end
|
||||
|
||||
def process_salmon
|
||||
SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
|
||||
end
|
||||
end
|
@ -1,51 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SubscriptionsController < Api::BaseController
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def show
|
||||
if subscription.valid?(params['hub.topic'])
|
||||
@account.update(subscription_expires_at: future_expires)
|
||||
render plain: encoded_challenge, status: 200
|
||||
else
|
||||
head 404
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
|
||||
ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
|
||||
end
|
||||
|
||||
head 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscription
|
||||
@_subscription ||= @account.subscription(
|
||||
api_subscription_url(@account.id)
|
||||
)
|
||||
end
|
||||
|
||||
def body
|
||||
@_body ||= request.body.read
|
||||
end
|
||||
|
||||
def encoded_challenge
|
||||
HTMLEntities.new.encode(params['hub.challenge'])
|
||||
end
|
||||
|
||||
def future_expires
|
||||
Time.now.utc + lease_seconds_or_default
|
||||
end
|
||||
|
||||
def lease_seconds_or_default
|
||||
(params['hub.lease_seconds'] || 1.day).to_i.seconds
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
end
|
@ -29,14 +29,13 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
|
||||
def account_statuses
|
||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
||||
statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
|
||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||
|
||||
statuses
|
||||
statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def permitted_account_statuses
|
||||
@ -58,6 +57,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
end
|
||||
|
||||
def pinned_scope
|
||||
return Status.none if @account.blocking?(current_account)
|
||||
|
||||
@account.pinned_statuses
|
||||
end
|
||||
|
||||
|
@ -12,6 +12,8 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
before_action :check_account_suspension, only: [:show]
|
||||
before_action :check_enabled_registrations, only: [:create]
|
||||
|
||||
skip_before_action :require_authenticated_user!, only: :create
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@ -31,7 +33,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
def follow
|
||||
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
|
||||
|
||||
options = @account.locked? ? {} : { 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 } }
|
||||
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
|
||||
end
|
||||
@ -76,7 +78,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.permit(:username, :email, :password, :agreement, :locale)
|
||||
params.permit(:username, :email, :password, :agreement, :locale, :reason)
|
||||
end
|
||||
|
||||
def check_enabled_registrations
|
||||
|
@ -58,7 +58,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||
|
||||
def reject
|
||||
authorize @account.user, :reject?
|
||||
SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
|
||||
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::AppsController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
def create
|
||||
@app = Doorkeeper::Application.create!(application_options)
|
||||
render json: @app, serializer: REST::ApplicationSerializer
|
||||
|
@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
|
||||
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
|
||||
end
|
||||
end
|
||||
|
30
app/controllers/api/v1/directories_controller.rb
Normal file
30
app/controllers/api/v1/directories_controller.rb
Normal file
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::DirectoriesController < Api::BaseController
|
||||
before_action :require_enabled!
|
||||
before_action :set_accounts
|
||||
|
||||
def show
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled!
|
||||
return not_found unless Setting.profile_directory
|
||||
end
|
||||
|
||||
def set_accounts
|
||||
@accounts = accounts_scope.offset(params[:offset]).limit(limit_param(DEFAULT_ACCOUNTS_LIMIT))
|
||||
end
|
||||
|
||||
def accounts_scope
|
||||
Account.discoverable.tap do |scope|
|
||||
scope.merge!(Account.local) if truthy_param?(:local)
|
||||
scope.merge!(Account.by_recent_status) if params[:order].blank? || params[:order] == 'active'
|
||||
scope.merge!(Account.order(id: :desc)) if params[:order] == 'new'
|
||||
scope.merge!(Account.not_excluded_by_account(current_account)) if current_account
|
||||
scope.merge!(Account.not_domain_blocked_by_account(current_account)) if current_account && !truthy_param?(:local)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
|
||||
|
||||
before_action :require_user!
|
||||
before_action :set_most_used_tags, only: :index
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: @most_used_tags, each_serializer: REST::TagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_most_used_tags
|
||||
@most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10)
|
||||
end
|
||||
end
|
40
app/controllers/api/v1/featured_tags_controller.rb
Normal file
40
app/controllers/api/v1/featured_tags_controller.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FeaturedTagsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
|
||||
|
||||
before_action :require_user!
|
||||
before_action :set_featured_tags, only: :index
|
||||
before_action :set_featured_tag, except: [:index, :create]
|
||||
|
||||
def index
|
||||
render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
@featured_tag = current_account.featured_tags.new(featured_tag_params)
|
||||
@featured_tag.reset_data
|
||||
@featured_tag.save!
|
||||
render json: @featured_tag, serializer: REST::FeaturedTagSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
@featured_tag.destroy!
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_featured_tag
|
||||
@featured_tag = current_account.featured_tags.find(params[:id])
|
||||
end
|
||||
|
||||
def set_featured_tags
|
||||
@featured_tags = current_account.featured_tags.order(statuses_count: :desc)
|
||||
end
|
||||
|
||||
def featured_tag_params
|
||||
params.permit(:name)
|
||||
end
|
||||
end
|
@ -14,12 +14,12 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
||||
def authorize
|
||||
AuthorizeFollowService.new.call(account, current_account)
|
||||
NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
|
||||
render_empty
|
||||
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
def reject
|
||||
RejectFollowService.new.call(account, current_account)
|
||||
render_empty
|
||||
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
private
|
||||
@ -28,6 +28,10 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
||||
Account.find(params[:id])
|
||||
end
|
||||
|
||||
def relationships(**options)
|
||||
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, options)
|
||||
end
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_follow_requests).to_a
|
||||
end
|
||||
|
@ -1,31 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FollowsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
|
||||
|
||||
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
|
||||
|
||||
if @account.nil?
|
||||
username, domain = target_uri.split('@')
|
||||
@account = Account.find_remote!(username, domain)
|
||||
end
|
||||
|
||||
render json: @account, serializer: REST::AccountSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def target_uri
|
||||
follow_params[:uri].strip.gsub(/\A@/, '')
|
||||
end
|
||||
|
||||
def follow_params
|
||||
params.permit(:uri)
|
||||
end
|
||||
end
|
@ -2,12 +2,14 @@
|
||||
|
||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
|
||||
expires_in 1.day, public: true
|
||||
render_with_cache json: :activity, expires_in: 1.day
|
||||
end
|
||||
|
||||
private
|
||||
@ -32,6 +34,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
end
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.activity_api_enabled
|
||||
head 404 unless Setting.activity_api_enabled && !whitelist_mode?
|
||||
end
|
||||
end
|
||||
|
@ -2,17 +2,19 @@
|
||||
|
||||
class Api::V1::Instances::PeersController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
|
||||
expires_in 1.day, public: true
|
||||
render_with_cache(expires_in: 1.day) { Account.remote.domains }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.peers_api_enabled
|
||||
head 404 unless Setting.peers_api_enabled && !whitelist_mode?
|
||||
end
|
||||
end
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
class Api::V1::InstancesController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
|
||||
ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: {}, serializer: REST::InstanceSerializer, root: 'instance'
|
||||
end
|
||||
end
|
||||
|
44
app/controllers/api/v1/markers_controller.rb
Normal file
44
app/controllers/api/v1/markers_controller.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::MarkersController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:index]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, except: [:index]
|
||||
|
||||
before_action :require_user!
|
||||
|
||||
def index
|
||||
@markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker }
|
||||
render json: serialize_map(@markers)
|
||||
end
|
||||
|
||||
def create
|
||||
Marker.transaction do
|
||||
@markers = {}
|
||||
|
||||
resource_params.each_pair do |timeline, timeline_params|
|
||||
@markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline)
|
||||
@markers[timeline].update!(timeline_params)
|
||||
end
|
||||
end
|
||||
|
||||
render json: serialize_map(@markers)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
render json: { error: 'Conflict during update, please try again' }, status: 409
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialize_map(map)
|
||||
serialized = {}
|
||||
|
||||
map.each_pair do |key, value|
|
||||
serialized[key] = ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer).as_json
|
||||
end
|
||||
|
||||
Oj.dump(serialized)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.slice(*Marker::TIMELINES).permit(*Marker::TIMELINES.map { |timeline| { timeline.to_sym => [:last_read_id] } })
|
||||
end
|
||||
end
|
@ -21,7 +21,7 @@ class Api::V1::ReportsController < Api::BaseController
|
||||
private
|
||||
|
||||
def reported_status_ids
|
||||
reported_account.statuses.find(status_ids).pluck(:id)
|
||||
reported_account.statuses.with_discarded.find(status_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def status_ids
|
||||
|
@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::SearchController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
RESULTS_LIMIT = 20
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@search = Search.new(search_results)
|
||||
render json: @search, serializer: REST::SearchSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_results
|
||||
SearchService.new.call(
|
||||
params[:q],
|
||||
current_account,
|
||||
limit_param(RESULTS_LIMIT),
|
||||
search_params.merge(resolve: truthy_param?(:resolve))
|
||||
)
|
||||
end
|
||||
|
||||
def search_params
|
||||
params.permit(:type, :offset, :min_id, :max_id, :account_id)
|
||||
end
|
||||
end
|
@ -18,6 +18,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
@reblogs_map = { @status.id => false }
|
||||
|
||||
authorize status_for_destroy, :unreblog?
|
||||
status_for_destroy.discard
|
||||
RemovalWorker.perform_async(status_for_destroy.id)
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map)
|
||||
@ -30,7 +31,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
end
|
||||
|
||||
def status_for_destroy
|
||||
current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
|
||||
@status_for_destroy ||= current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
|
||||
end
|
||||
|
||||
def reblog_params
|
||||
|
@ -5,8 +5,8 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
|
||||
before_action :require_user!, except: [:show, :context, :card]
|
||||
before_action :set_status, only: [:show, :context, :card]
|
||||
before_action :require_user!, except: [:show, :context]
|
||||
before_action :set_status, only: [:show, :context]
|
||||
|
||||
respond_to :json
|
||||
|
||||
@ -33,16 +33,6 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
def card
|
||||
@card = @status.preview_cards.first
|
||||
|
||||
if @card.nil?
|
||||
render_empty
|
||||
else
|
||||
render json: @card, serializer: REST::PreviewCardSerializer
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@status = PostStatusService.new.call(current_user.account,
|
||||
text: status_params[:status],
|
||||
@ -64,7 +54,8 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
@status = Status.where(account_id: current_user.account).find(params[:id])
|
||||
authorize @status, :destroy?
|
||||
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
@status.discard
|
||||
RemovalWorker.perform_async(@status.id, redraft: true)
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, source_requested: true
|
||||
end
|
||||
|
@ -1,63 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::DirectController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
|
||||
before_action :require_user!, only: [:show]
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_direct_statuses
|
||||
end
|
||||
|
||||
def cached_direct_statuses
|
||||
cache_collection direct_statuses, Status
|
||||
end
|
||||
|
||||
def direct_statuses
|
||||
direct_timeline_statuses
|
||||
end
|
||||
|
||||
def direct_timeline_statuses
|
||||
# this query requires built in pagination.
|
||||
Status.as_direct_timeline(
|
||||
current_account,
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id],
|
||||
true # returns array of cache_ids object
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::PublicController < Api::BaseController
|
||||
before_action :require_user!, only: [:show], if: :require_auth?
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
@ -12,6 +13,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def require_auth?
|
||||
!Setting.timeline_preview
|
||||
end
|
||||
|
||||
def load_statuses
|
||||
cached_public_statuses
|
||||
end
|
||||
|
17
app/controllers/api/v1/trends_controller.rb
Normal file
17
app/controllers/api/v1/trends_controller.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::TrendsController < Api::BaseController
|
||||
before_action :set_tags
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: @tags, each_serializer: REST::TagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = TrendingTags.get(limit_param(10))
|
||||
end
|
||||
end
|
@ -1,8 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V2::SearchController < Api::V1::SearchController
|
||||
class Api::V2::SearchController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
RESULTS_LIMIT = 20
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@search = Search.new(search_results)
|
||||
render json: @search, serializer: REST::V2::SearchSerializer
|
||||
render json: @search, serializer: REST::SearchSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_results
|
||||
SearchService.new.call(
|
||||
params[:q],
|
||||
current_account,
|
||||
limit_param(RESULTS_LIMIT),
|
||||
search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed))
|
||||
)
|
||||
end
|
||||
|
||||
def search_params
|
||||
params.permit(:type, :offset, :min_id, :max_id, :account_id)
|
||||
end
|
||||
end
|
||||
|
@ -10,21 +10,29 @@ class ApplicationController < ActionController::Base
|
||||
include Localized
|
||||
include UserTrackingConcern
|
||||
include SessionTrackingConcern
|
||||
include CacheConcern
|
||||
include DomainControlHelper
|
||||
|
||||
helper_method :current_account
|
||||
helper_method :current_session
|
||||
helper_method :current_theme
|
||||
helper_method :single_user_mode?
|
||||
helper_method :use_seamless_external_login?
|
||||
helper_method :whitelist_mode?
|
||||
|
||||
rescue_from ActionController::RoutingError, with: :not_found
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
|
||||
rescue_from ActionController::UnknownFormat, with: :not_acceptable
|
||||
rescue_from ActionController::ParameterMissing, with: :bad_request
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
|
||||
rescue_from Mastodon::RaceConditionError, with: :service_unavailable
|
||||
|
||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
||||
before_action :check_user_permissions, if: :user_signed_in?
|
||||
before_action :require_functional!, if: :user_signed_in?
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: :raise_not_found
|
||||
|
||||
def raise_not_found
|
||||
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
|
||||
@ -33,7 +41,15 @@ class ApplicationController < ActionController::Base
|
||||
private
|
||||
|
||||
def https_enabled?
|
||||
Rails.env.production?
|
||||
Rails.env.production? && !request.path.start_with?('/health')
|
||||
end
|
||||
|
||||
def authorized_fetch_mode?
|
||||
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||
end
|
||||
|
||||
def public_fetch_mode?
|
||||
!authorized_fetch_mode?
|
||||
end
|
||||
|
||||
def store_current_location
|
||||
@ -48,8 +64,8 @@ class ApplicationController < ActionController::Base
|
||||
forbidden unless current_user&.staff?
|
||||
end
|
||||
|
||||
def check_user_permissions
|
||||
forbidden if current_user.disabled? || current_user.account.suspended?
|
||||
def require_functional!
|
||||
redirect_to edit_user_registration_path unless current_user.functional?
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
@ -82,8 +98,20 @@ class ApplicationController < ActionController::Base
|
||||
respond_with_error(406)
|
||||
end
|
||||
|
||||
def bad_request
|
||||
respond_with_error(400)
|
||||
end
|
||||
|
||||
def internal_server_error
|
||||
respond_with_error(500)
|
||||
end
|
||||
|
||||
def service_unavailable
|
||||
respond_with_error(503)
|
||||
end
|
||||
|
||||
def single_user_mode?
|
||||
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
|
||||
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
|
||||
end
|
||||
|
||||
def use_seamless_external_login?
|
||||
@ -107,51 +135,10 @@ class ApplicationController < ActionController::Base
|
||||
current_user.setting_theme
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any { head code }
|
||||
format.html { render "errors/#{code}", layout: 'error', status: code }
|
||||
end
|
||||
end
|
||||
|
||||
def render_cached_json(cache_key, **options)
|
||||
options[:expires_in] ||= 3.minutes
|
||||
cache_public = options.key?(:public) ? options.delete(:public) : true
|
||||
content_type = options.delete(:content_type) || 'application/json'
|
||||
|
||||
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
|
||||
yield.to_json
|
||||
end
|
||||
|
||||
expires_in options[:expires_in], public: cache_public
|
||||
render json: data, content_type: content_type
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Accept'
|
||||
end
|
||||
|
||||
def mark_cacheable!
|
||||
expires_in 0, public: true
|
||||
end
|
||||
end
|
||||
|
22
app/controllers/auth/challenges_controller.rb
Normal file
22
app/controllers/auth/challenges_controller.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Auth::ChallengesController < ApplicationController
|
||||
include ChallengableConcern
|
||||
|
||||
layout 'auth'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def create
|
||||
if challenge_passed?
|
||||
session[:challenge_passed_at] = Time.now.utc
|
||||
redirect_to challenge_params[:return_to]
|
||||
else
|
||||
@challenge = Form::Challenge.new(return_to: challenge_params[:return_to])
|
||||
flash.now[:alert] = I18n.t('challenge.invalid_password')
|
||||
render_challenge
|
||||
end
|
||||
end
|
||||
end
|
@ -4,32 +4,36 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||
layout 'auth'
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :set_user, only: [:finish_signup]
|
||||
before_action :require_unconfirmed!
|
||||
|
||||
def finish_signup
|
||||
return unless request.patch? && params[:user]
|
||||
skip_before_action :require_functional!
|
||||
|
||||
if @user.update(user_params)
|
||||
@user.skip_reconfirmation!
|
||||
bypass_sign_in(@user)
|
||||
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
@show_errors = true
|
||||
end
|
||||
def new
|
||||
super
|
||||
|
||||
resource.email = current_user.unconfirmed_email || current_user.email if user_signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
def require_unconfirmed!
|
||||
redirect_to edit_user_registration_path if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank?
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
def after_resending_confirmation_instructions_path_for(_resource_name)
|
||||
if user_signed_in?
|
||||
if current_user.confirmed? && current_user.approved?
|
||||
edit_user_registration_path
|
||||
else
|
||||
auth_setup_path
|
||||
end
|
||||
else
|
||||
new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
def after_confirmation_path_for(_resource_name, user)
|
||||
|
@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
if resource.email_verified?
|
||||
root_path
|
||||
else
|
||||
finish_signup_path
|
||||
auth_setup_path(missing_email: '1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,6 +9,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
super(&:build_invite_request)
|
||||
@ -43,7 +46,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
new_user_session_path
|
||||
auth_setup_path
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(_resource)
|
||||
@ -102,4 +105,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
def set_sessions
|
||||
@sessions = current_user.session_activations
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
@ -6,8 +6,10 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
layout 'auth'
|
||||
|
||||
skip_before_action :require_no_authentication, only: [:create]
|
||||
skip_before_action :check_user_permissions, only: [:destroy]
|
||||
skip_before_action :require_functional!
|
||||
|
||||
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
|
||||
|
||||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
||||
@ -29,6 +31,7 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
def destroy
|
||||
tmp_stored_location = stored_location_for(:user)
|
||||
super
|
||||
session.delete(:challenge_passed_at)
|
||||
flash.delete(:notice)
|
||||
store_location_for(:user, tmp_stored_location) if continue_after?
|
||||
end
|
||||
@ -38,12 +41,10 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
def find_user
|
||||
if session[:otp_user_id]
|
||||
User.find(session[:otp_user_id])
|
||||
elsif user_params[:email]
|
||||
if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
|
||||
User.joins(:account).find_by(accounts: { username: user_params[:email] })
|
||||
else
|
||||
User.find_for_authentication(email: user_params[:email])
|
||||
end
|
||||
else
|
||||
user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
|
||||
user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
|
||||
user ||= User.find_for_authentication(email: user_params[:email])
|
||||
end
|
||||
end
|
||||
|
||||
@ -70,13 +71,13 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
end
|
||||
|
||||
def two_factor_enabled?
|
||||
find_user.try(:otp_required_for_login?)
|
||||
find_user&.otp_required_for_login?
|
||||
end
|
||||
|
||||
def valid_otp_attempt?(user)
|
||||
user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
|
||||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
|
||||
rescue OpenSSL::Cipher::CipherError => _error
|
||||
rescue OpenSSL::Cipher::CipherError
|
||||
false
|
||||
end
|
||||
|
||||
@ -85,7 +86,10 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
|
||||
if user_params[:otp_attempt].present? && session[:otp_user_id]
|
||||
authenticate_with_two_factor_via_otp(user)
|
||||
elsif user&.valid_password?(user_params[:password])
|
||||
elsif user.present? && (user.encrypted_password.blank? || user.valid_password?(user_params[:password]))
|
||||
# If encrypted_password is blank, we got the user from LDAP or PAM,
|
||||
# so credentials are already valid
|
||||
|
||||
prompt_for_two_factor(user)
|
||||
end
|
||||
end
|
||||
@ -103,6 +107,7 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
|
||||
def prompt_for_two_factor(user)
|
||||
session[:otp_user_id] = user.id
|
||||
@body_classes = 'lighter'
|
||||
render :two_factor
|
||||
end
|
||||
|
||||
|
58
app/controllers/auth/setup_controller.rb
Normal file
58
app/controllers/auth/setup_controller.rb
Normal file
@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Auth::SetupController < ApplicationController
|
||||
layout 'auth'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_unconfirmed_or_pending!
|
||||
before_action :set_body_classes
|
||||
before_action :set_user
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
flash.now[:notice] = begin
|
||||
if @user.pending?
|
||||
I18n.t('devise.registrations.signed_up_but_pending')
|
||||
else
|
||||
I18n.t('devise.registrations.signed_up_but_unconfirmed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
# This allows updating the e-mail without entering a password as is required
|
||||
# on the account settings page; however, we only allow this for accounts
|
||||
# that were not confirmed yet
|
||||
|
||||
if @user.update(user_params)
|
||||
redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :missing_email?
|
||||
|
||||
private
|
||||
|
||||
def require_unconfirmed_or_pending!
|
||||
redirect_to root_path if current_user.confirmed? && current_user.approved?
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def missing_email?
|
||||
truthy_param?(:missing_email)
|
||||
end
|
||||
end
|
@ -3,24 +3,19 @@
|
||||
module AccountControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AccountOwnedConcern
|
||||
|
||||
FOLLOW_PER_PAGE = 12
|
||||
|
||||
included do
|
||||
layout 'public'
|
||||
|
||||
before_action :set_account
|
||||
before_action :check_account_approval
|
||||
before_action :check_account_suspension
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(username_param)
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
@ -29,27 +24,15 @@ module AccountControllerConcern
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
webfinger_account_link,
|
||||
atom_account_url_link,
|
||||
actor_url_link,
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def username_param
|
||||
params[:account_username]
|
||||
end
|
||||
|
||||
def webfinger_account_link
|
||||
[
|
||||
webfinger_account_url,
|
||||
[%w(rel lrdd), %w(type application/xrd+xml)],
|
||||
]
|
||||
end
|
||||
|
||||
def atom_account_url_link
|
||||
[
|
||||
account_url(@account, format: 'atom'),
|
||||
[%w(rel alternate), %w(type application/atom+xml)],
|
||||
[%w(rel lrdd), %w(type application/jrd+json)],
|
||||
]
|
||||
end
|
||||
|
||||
@ -63,15 +46,4 @@ module AccountControllerConcern
|
||||
def webfinger_account_url
|
||||
webfinger_url(resource: @account.to_webfinger_s)
|
||||
end
|
||||
|
||||
def check_account_approval
|
||||
not_found if @account.user_pending?
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
if @account.suspended?
|
||||
expires_in(3.minutes, public: true)
|
||||
gone
|
||||
end
|
||||
end
|
||||
end
|
||||
|
34
app/controllers/concerns/account_owned_concern.rb
Normal file
34
app/controllers/concerns/account_owned_concern.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AccountOwnedConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
|
||||
before_action :set_account, if: :account_required?
|
||||
before_action :check_account_approval, if: :account_required?
|
||||
before_action :check_account_suspension, if: :account_required?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_required?
|
||||
true
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(username_param)
|
||||
end
|
||||
|
||||
def username_param
|
||||
params[:account_username]
|
||||
end
|
||||
|
||||
def check_account_approval
|
||||
not_found if @account.local? && @account.user_pending?
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
expires_in(3.minutes, public: true) && gone if @account.suspended?
|
||||
end
|
||||
end
|
50
app/controllers/concerns/cache_concern.rb
Normal file
50
app/controllers/concerns/cache_concern.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module CacheConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def render_with_cache(**options)
|
||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
|
||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||
expires_in = options.delete(:expires_in) || 3.minutes
|
||||
body = Rails.cache.read(key, raw: true)
|
||||
|
||||
if body
|
||||
render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body))
|
||||
else
|
||||
if block_given?
|
||||
options[:json] = yield
|
||||
elsif options[:json].is_a?(Symbol)
|
||||
options[:json] = send(options[:json])
|
||||
end
|
||||
|
||||
render(options)
|
||||
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
|
||||
end
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
end
|
65
app/controllers/concerns/challengable_concern.rb
Normal file
65
app/controllers/concerns/challengable_concern.rb
Normal file
@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This concern is inspired by "sudo mode" on GitHub. It
|
||||
# is a way to re-authenticate a user before allowing them
|
||||
# to see or perform an action.
|
||||
#
|
||||
# Add `before_action :require_challenge!` to actions you
|
||||
# want to protect.
|
||||
#
|
||||
# The user will be shown a page to enter the challenge (which
|
||||
# is either the password, or just the username when no
|
||||
# password exists). Upon passing, there is a grace period
|
||||
# during which no challenge will be asked from the user.
|
||||
#
|
||||
# Accessing challenge-protected resources during the grace
|
||||
# period will refresh the grace period.
|
||||
module ChallengableConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
CHALLENGE_TIMEOUT = 1.hour.freeze
|
||||
|
||||
def require_challenge!
|
||||
return if skip_challenge?
|
||||
|
||||
if challenge_passed_recently?
|
||||
session[:challenge_passed_at] = Time.now.utc
|
||||
return
|
||||
end
|
||||
|
||||
@challenge = Form::Challenge.new(return_to: request.url)
|
||||
|
||||
if params.key?(:form_challenge)
|
||||
if challenge_passed?
|
||||
session[:challenge_passed_at] = Time.now.utc
|
||||
return
|
||||
else
|
||||
flash.now[:alert] = I18n.t('challenge.invalid_password')
|
||||
render_challenge
|
||||
end
|
||||
else
|
||||
render_challenge
|
||||
end
|
||||
end
|
||||
|
||||
def render_challenge
|
||||
@body_classes = 'lighter'
|
||||
render template: 'auth/challenges/new', layout: 'auth'
|
||||
end
|
||||
|
||||
def challenge_passed?
|
||||
current_user.valid_password?(challenge_params[:current_password])
|
||||
end
|
||||
|
||||
def skip_challenge?
|
||||
current_user.encrypted_password.blank?
|
||||
end
|
||||
|
||||
def challenge_passed_recently?
|
||||
session[:challenge_passed_at].present? && session[:challenge_passed_at] >= CHALLENGE_TIMEOUT.ago
|
||||
end
|
||||
|
||||
def challenge_params
|
||||
params.require(:form_challenge).permit(:current_password, :return_to)
|
||||
end
|
||||
end
|
@ -5,7 +5,10 @@ module ExportControllerConcern
|
||||
|
||||
included do
|
||||
before_action :authenticate_user!
|
||||
before_action :require_not_suspended!
|
||||
before_action :load_export
|
||||
|
||||
skip_before_action :require_functional!
|
||||
end
|
||||
|
||||
private
|
||||
@ -27,4 +30,8 @@ module ExportControllerConcern
|
||||
def export_filename
|
||||
"#{controller_name}.csv"
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
@ -5,12 +5,35 @@
|
||||
module SignatureVerification
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include DomainControlHelper
|
||||
|
||||
def require_signature!
|
||||
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
|
||||
end
|
||||
|
||||
def signed_request?
|
||||
request.headers['Signature'].present?
|
||||
end
|
||||
|
||||
def signature_verification_failure_reason
|
||||
return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
|
||||
@signature_verification_failure_reason
|
||||
end
|
||||
|
||||
def signature_verification_failure_code
|
||||
@signature_verification_failure_code || 401
|
||||
end
|
||||
|
||||
def signature_key_id
|
||||
raw_signature = request.headers['Signature']
|
||||
signature_params = {}
|
||||
|
||||
raw_signature.split(',').each do |part|
|
||||
parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
|
||||
next if parsed_parts.nil? || parsed_parts.size != 3
|
||||
signature_params[parsed_parts[1]] = parsed_parts[2]
|
||||
end
|
||||
|
||||
signature_params['keyId']
|
||||
end
|
||||
|
||||
def signed_request_account
|
||||
@ -123,6 +146,13 @@ module SignatureVerification
|
||||
end
|
||||
|
||||
def account_from_key_id(key_id)
|
||||
domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
|
||||
|
||||
if domain_not_allowed?(domain)
|
||||
@signature_verification_failure_code = 403
|
||||
return
|
||||
end
|
||||
|
||||
if key_id.start_with?('acct:')
|
||||
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) }
|
||||
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||
@ -137,7 +167,7 @@ module SignatureVerification
|
||||
.with_fallback { nil }
|
||||
.with_threshold(1)
|
||||
.with_cool_off_time(5.minutes.seconds)
|
||||
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
|
||||
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) }
|
||||
.run
|
||||
end
|
||||
|
||||
|
87
app/controllers/concerns/status_controller_concern.rb
Normal file
87
app/controllers/concerns/status_controller_concern.rb
Normal file
@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StatusControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ANCESTORS_LIMIT = 40
|
||||
DESCENDANTS_LIMIT = 60
|
||||
DESCENDANTS_DEPTH_LIMIT = 20
|
||||
|
||||
def create_descendant_thread(starting_depth, statuses)
|
||||
depth = starting_depth + statuses.size
|
||||
|
||||
if depth < DESCENDANTS_DEPTH_LIMIT
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
}
|
||||
else
|
||||
next_status = statuses.pop
|
||||
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
next_status: next_status,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def set_ancestors
|
||||
@ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : []
|
||||
@next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift
|
||||
end
|
||||
|
||||
def set_descendants
|
||||
@max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i
|
||||
@since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i
|
||||
|
||||
descendants = cache_collection(
|
||||
@status.descendants(
|
||||
DESCENDANTS_LIMIT,
|
||||
current_account,
|
||||
@max_descendant_thread_id,
|
||||
@since_descendant_thread_id,
|
||||
DESCENDANTS_DEPTH_LIMIT
|
||||
),
|
||||
Status
|
||||
)
|
||||
|
||||
@descendant_threads = []
|
||||
|
||||
if descendants.present?
|
||||
statuses = [descendants.first]
|
||||
starting_depth = 0
|
||||
|
||||
descendants.drop(1).each_with_index do |descendant, index|
|
||||
if descendants[index].id == descendant.in_reply_to_id
|
||||
statuses << descendant
|
||||
else
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
|
||||
# The thread is broken, assume it's a reply to the root status
|
||||
starting_depth = 0
|
||||
|
||||
# ... unless we can find its ancestor in one of the already-processed threads
|
||||
@descendant_threads.reverse_each do |descendant_thread|
|
||||
statuses = descendant_thread[:statuses]
|
||||
|
||||
index = statuses.find_index do |thread_status|
|
||||
thread_status.id == descendant.in_reply_to_id
|
||||
end
|
||||
|
||||
if index.present?
|
||||
starting_depth = descendant_thread[:starting_depth] + index + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
statuses = [descendant]
|
||||
end
|
||||
end
|
||||
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
end
|
||||
|
||||
@max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT
|
||||
end
|
||||
end
|
@ -2,10 +2,12 @@
|
||||
|
||||
class CustomCssController < ApplicationController
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in 3.minutes, public: true
|
||||
render plain: Setting.custom_css || '', content_type: 'text/css'
|
||||
end
|
||||
end
|
||||
|
@ -3,12 +3,14 @@
|
||||
class DirectoriesController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :check_enabled
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_tag, only: :show
|
||||
before_action :set_tags
|
||||
before_action :set_accounts
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def index
|
||||
render :index
|
||||
end
|
||||
@ -19,21 +21,18 @@ class DirectoriesController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def check_enabled
|
||||
def require_enabled!
|
||||
return not_found unless Setting.profile_directory
|
||||
end
|
||||
|
||||
def set_tag
|
||||
@tag = Tag.discoverable.find_by!(name: params[:id].downcase)
|
||||
end
|
||||
|
||||
def set_tags
|
||||
@tags = Tag.discoverable.limit(30).reject { |tag| tag.cached_sample_accounts.empty? }
|
||||
@tag = Tag.discoverable.find_normalized!(params[:id])
|
||||
end
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.discoverable.by_recent_status.page(params[:page]).per(40).tap do |query|
|
||||
@accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query|
|
||||
query.merge!(Account.tagged_with(@tag.id)) if @tag
|
||||
query.merge!(Account.not_excluded_by_account(current_account)) if current_account
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -7,9 +7,8 @@ class EmojisController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render_cached_json(['activitypub', 'emoji', @emoji], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,13 +2,18 @@
|
||||
|
||||
class FollowerAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
mark_cacheable! unless user_signed_in?
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
|
||||
@ -17,9 +22,9 @@ class FollowerAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
|
||||
expires_in 3.minutes, public: true if params[:page].blank?
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
@ -35,12 +40,16 @@ class FollowerAccountsController < ApplicationController
|
||||
@follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page].present?
|
||||
end
|
||||
|
||||
def page_url(page)
|
||||
account_followers_url(@account, page: page) unless page.nil?
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
if params[:page].present?
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_followers_url(@account, page: params.fetch(:page, 1)),
|
||||
type: :ordered,
|
||||
|
@ -2,13 +2,18 @@
|
||||
|
||||
class FollowingAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
mark_cacheable! unless user_signed_in?
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
|
||||
@ -17,9 +22,9 @@ class FollowingAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
|
||||
expires_in 3.minutes, public: true if params[:page].blank?
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
@ -35,12 +40,16 @@ class FollowingAccountsController < ApplicationController
|
||||
@follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page].present?
|
||||
end
|
||||
|
||||
def page_url(page)
|
||||
account_following_index_url(@account, page: page) unless page.nil?
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
if params[:page].present?
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_following_index_url(@account, page: params.fetch(:page, 1)),
|
||||
type: :ordered,
|
||||
|
@ -3,7 +3,6 @@
|
||||
class HomeController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_referrer_policy_header
|
||||
before_action :set_initial_state_json
|
||||
|
||||
def index
|
||||
@body_classes = 'app-body'
|
||||
@ -21,7 +20,7 @@ class HomeController < ApplicationController
|
||||
when 'statuses'
|
||||
status = Status.find_by(id: matches[2])
|
||||
|
||||
if status && (status.public_visibility? || status.unlisted_visibility?)
|
||||
if status&.distributable?
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(status))
|
||||
return
|
||||
end
|
||||
@ -39,26 +38,11 @@ class HomeController < ApplicationController
|
||||
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
|
||||
end
|
||||
|
||||
def set_initial_state_json
|
||||
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
||||
@initial_state_json = serializable_resource.to_json
|
||||
end
|
||||
|
||||
def initial_state_params
|
||||
{
|
||||
settings: Web::Setting.find_by(user: current_user)&.data || {},
|
||||
push_subscription: current_account.user.web_push_subscription(current_session),
|
||||
current_account: current_account,
|
||||
token: current_session.token,
|
||||
admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
|
||||
}
|
||||
end
|
||||
|
||||
def default_redirect_path
|
||||
if request.path.start_with?('/web')
|
||||
if request.path.start_with?('/web') || whitelist_mode?
|
||||
new_user_session_path
|
||||
elsif single_user_mode?
|
||||
short_account_path(Account.local.without_suspended.first)
|
||||
short_account_path(Account.local.without_suspended.where('id > 0').first)
|
||||
else
|
||||
about_path
|
||||
end
|
||||
|
22
app/controllers/instance_actors_controller.rb
Normal file
22
app/controllers/instance_actors_controller.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class InstanceActorsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
skip_around_action :set_locale
|
||||
|
||||
def show
|
||||
expires_in 10.minutes, public: true
|
||||
render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(-99)
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
%i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
|
||||
end
|
||||
end
|
@ -2,6 +2,7 @@
|
||||
|
||||
class IntentsController < ApplicationController
|
||||
before_action :check_uri
|
||||
|
||||
rescue_from Addressable::URI::InvalidURIError, with: :handle_invalid_uri
|
||||
|
||||
def show
|
||||
|
@ -43,7 +43,7 @@ class InvitesController < ApplicationController
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:invite).permit(:max_uses, :expires_in, :autofollow)
|
||||
params.require(:invite).permit(:max_uses, :expires_in, :autofollow, :comment)
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
class ManifestsController < ApplicationController
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
render json: InstancePresenter.new, serializer: ManifestSerializer
|
||||
expires_in 3.minutes, public: true
|
||||
render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance'
|
||||
end
|
||||
end
|
||||
|
@ -4,7 +4,9 @@ class MediaController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_media_attachment
|
||||
before_action :verify_permitted_status!
|
||||
before_action :check_playable, only: :player
|
||||
@ -31,7 +33,6 @@ class MediaController < ApplicationController
|
||||
def verify_permitted_status!
|
||||
authorize @media_attachment.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
|
@ -4,6 +4,13 @@ class MediaProxyController < ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :not_found
|
||||
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
|
||||
rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
|
||||
|
||||
def show
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
|
@ -7,6 +7,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||
before_action :authenticate_resource_owner!
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
include Localized
|
||||
|
||||
def destroy
|
||||
|
@ -3,25 +3,17 @@
|
||||
class PublicTimelinesController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :check_enabled
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@initial_state_json = ActiveModelSerializers::SerializableResource.new(
|
||||
InitialStatePresenter.new(settings: { known_fediverse: Setting.show_known_fediverse_at_about_page }, token: current_session&.token),
|
||||
serializer: InitialStateSerializer
|
||||
).to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
def show; end
|
||||
|
||||
private
|
||||
|
||||
def check_enabled
|
||||
raise ActiveRecord::RecordNotFound unless Setting.timeline_preview
|
||||
def require_enabled!
|
||||
not_found unless Setting.timeline_preview
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
|
@ -1,12 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoteFollowController < ApplicationController
|
||||
include AccountOwnedConcern
|
||||
|
||||
layout 'modal'
|
||||
|
||||
before_action :set_account
|
||||
before_action :gone, if: :suspended_account?
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
@remote_follow = RemoteFollow.new(session_params)
|
||||
end
|
||||
@ -29,15 +31,7 @@ class RemoteFollowController < ApplicationController
|
||||
end
|
||||
|
||||
def session_params
|
||||
{ acct: session[:remote_follow] }
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def suspended_account?
|
||||
@account.suspended?
|
||||
{ acct: session[:remote_follow] || current_account&.username }
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
|
@ -5,10 +5,13 @@ class RemoteInteractionController < ApplicationController
|
||||
|
||||
layout 'modal'
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_interaction_type
|
||||
before_action :set_status
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
@remote_follow = RemoteFollow.new(session_params)
|
||||
end
|
||||
@ -31,14 +34,13 @@ class RemoteInteractionController < ApplicationController
|
||||
end
|
||||
|
||||
def session_params
|
||||
{ acct: session[:remote_follow] }
|
||||
{ acct: session[:remote_follow] || current_account&.username }
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoteUnfollowsController < ApplicationController
|
||||
layout 'modal'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_body_classes
|
||||
|
||||
def create
|
||||
@account = unfollow_attempt.try(:target_account)
|
||||
|
||||
if @account.nil?
|
||||
render :error
|
||||
else
|
||||
render :success
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
render :error
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unfollow_attempt
|
||||
username, domain = acct_without_prefix.split('@')
|
||||
UnfollowService.new.call(current_account, Account.find_remote!(username, domain))
|
||||
end
|
||||
|
||||
def acct_without_prefix
|
||||
acct_params.gsub(/\Aacct:/, '')
|
||||
end
|
||||
|
||||
def acct_params
|
||||
params.fetch(:acct, '')
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'modal-layout'
|
||||
end
|
||||
end
|
43
app/controllers/settings/aliases_controller.rb
Normal file
43
app/controllers/settings/aliases_controller.rb
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Settings::AliasesController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_aliases, except: :destroy
|
||||
before_action :set_alias, only: :destroy
|
||||
|
||||
def index
|
||||
@alias = current_account.aliases.build
|
||||
end
|
||||
|
||||
def create
|
||||
@alias = current_account.aliases.build(resource_params)
|
||||
|
||||
if @alias.save
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
|
||||
redirect_to settings_aliases_path, notice: I18n.t('aliases.created_msg')
|
||||
else
|
||||
render :index
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@alias.destroy!
|
||||
redirect_to settings_aliases_path, notice: I18n.t('aliases.deleted_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:account_alias).permit(:acct)
|
||||
end
|
||||
|
||||
def set_alias
|
||||
@alias = current_account.aliases.find(params[:id])
|
||||
end
|
||||
|
||||
def set_aliases
|
||||
@aliases = current_account.aliases.order(id: :desc).reject(&:new_record?)
|
||||
end
|
||||
end
|
@ -5,18 +5,20 @@ class Settings::DeletesController < Settings::BaseController
|
||||
|
||||
before_action :check_enabled_deletion
|
||||
before_action :authenticate_user!
|
||||
before_action :require_not_suspended!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@confirmation = Form::DeleteConfirmation.new
|
||||
end
|
||||
|
||||
def destroy
|
||||
if current_user.valid_password?(delete_params[:password])
|
||||
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
|
||||
sign_out
|
||||
if challenge_passed?
|
||||
destroy_account!
|
||||
redirect_to new_user_session_path, notice: I18n.t('deletes.success_msg')
|
||||
else
|
||||
redirect_to settings_delete_path, alert: I18n.t('deletes.bad_password_msg')
|
||||
redirect_to settings_delete_path, alert: I18n.t('deletes.challenge_not_passed')
|
||||
end
|
||||
end
|
||||
|
||||
@ -26,7 +28,25 @@ class Settings::DeletesController < Settings::BaseController
|
||||
redirect_to root_path unless Setting.open_deletion
|
||||
end
|
||||
|
||||
def delete_params
|
||||
params.require(:form_delete_confirmation).permit(:password)
|
||||
def resource_params
|
||||
params.require(:form_delete_confirmation).permit(:password, :username)
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
|
||||
def challenge_passed?
|
||||
if current_user.encrypted_password.blank?
|
||||
current_account.username == resource_params[:username]
|
||||
else
|
||||
current_user.valid_password?(resource_params[:password])
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_account!
|
||||
current_account.suspend!
|
||||
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
|
||||
sign_out
|
||||
end
|
||||
end
|
||||
|
@ -6,6 +6,9 @@ class Settings::ExportsController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_not_suspended!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@export = Export.new(current_account)
|
||||
@ -34,4 +37,8 @@ class Settings::ExportsController < Settings::BaseController
|
||||
def lock_options
|
||||
{ redis: Redis.current, key: "backup:#{current_user.id}" }
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
45
app/controllers/settings/migration/redirects_controller.rb
Normal file
45
app/controllers/settings/migration/redirects_controller.rb
Normal file
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Settings::Migration::RedirectsController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_not_suspended!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
@redirect = Form::Redirect.new
|
||||
end
|
||||
|
||||
def create
|
||||
@redirect = Form::Redirect.new(resource_params.merge(account: current_account))
|
||||
|
||||
if @redirect.valid_with_challenge?(current_user)
|
||||
current_account.update!(moved_to_account: @redirect.target_account)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
|
||||
redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if current_account.moved_to_account_id.present?
|
||||
current_account.update!(moved_to_account: nil)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
|
||||
end
|
||||
|
||||
redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:form_redirect).permit(:acct, :current_password, :current_username)
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
@ -4,31 +4,48 @@ class Settings::MigrationsController < Settings::BaseController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_not_suspended!
|
||||
before_action :set_migrations
|
||||
before_action :set_cooldown
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@migration = Form::Migration.new(account: current_account.moved_to_account)
|
||||
@migration = current_account.migrations.build
|
||||
end
|
||||
|
||||
def update
|
||||
@migration = Form::Migration.new(resource_params)
|
||||
def create
|
||||
@migration = current_account.migrations.build(resource_params)
|
||||
|
||||
if @migration.valid? && migration_account_changed?
|
||||
current_account.update!(moved_to_account: @migration.account)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
|
||||
redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg')
|
||||
if @migration.save_with_challenge(current_user)
|
||||
MoveService.new.call(@migration)
|
||||
redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :on_cooldown?
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:migration).permit(:acct)
|
||||
params.require(:account_migration).permit(:acct, :current_password, :current_username)
|
||||
end
|
||||
|
||||
def migration_account_changed?
|
||||
current_account.moved_to_account_id != @migration.account&.id &&
|
||||
current_account.id != @migration.account&.id
|
||||
def set_migrations
|
||||
@migrations = current_account.migrations.includes(:target_account).order(id: :desc).reject(&:new_record?)
|
||||
end
|
||||
|
||||
def set_cooldown
|
||||
@cooldown = current_account.migrations.within_cooldown.first
|
||||
end
|
||||
|
||||
def on_cooldown?
|
||||
@cooldown.present?
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
@ -55,7 +55,10 @@ class Settings::PreferencesController < Settings::BaseController
|
||||
:setting_aggregate_reblogs,
|
||||
:setting_show_application,
|
||||
:setting_advanced_layout,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account),
|
||||
:setting_use_blurhash,
|
||||
:setting_use_pending_items,
|
||||
:setting_trends,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
|
||||
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||
)
|
||||
end
|
||||
|
@ -4,6 +4,8 @@ class Settings::SessionsController < Settings::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_session, only: :destroy
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def destroy
|
||||
@session.destroy!
|
||||
flash[:notice] = I18n.t('sessions.revoke_success')
|
||||
|
@ -3,23 +3,30 @@
|
||||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class ConfirmationsController < BaseController
|
||||
include ChallengableConcern
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_challenge!
|
||||
before_action :ensure_otp_secret
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
prepare_two_factor_form
|
||||
end
|
||||
|
||||
def create
|
||||
if current_user.validate_and_consume_otp!(confirmation_params[:code])
|
||||
if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt])
|
||||
flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success')
|
||||
|
||||
current_user.otp_required_for_login = true
|
||||
@recovery_codes = current_user.generate_otp_backup_codes!
|
||||
current_user.save!
|
||||
|
||||
UserMailer.two_factor_enabled(current_user).deliver_later!
|
||||
|
||||
render 'settings/two_factor_authentication/recovery_codes/index'
|
||||
else
|
||||
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')
|
||||
@ -31,7 +38,7 @@ module Settings
|
||||
private
|
||||
|
||||
def confirmation_params
|
||||
params.require(:form_two_factor_confirmation).permit(:code)
|
||||
params.require(:form_two_factor_confirmation).permit(:otp_attempt)
|
||||
end
|
||||
|
||||
def prepare_two_factor_form
|
||||
|
@ -3,14 +3,22 @@
|
||||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class RecoveryCodesController < BaseController
|
||||
include ChallengableConcern
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_challenge!, on: :create
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def create
|
||||
@recovery_codes = current_user.generate_otp_backup_codes!
|
||||
current_user.save!
|
||||
|
||||
UserMailer.two_factor_recovery_codes_changed(current_user).deliver_later!
|
||||
flash.now[:notice] = I18n.t('two_factor_authentication.recovery_codes_regenerated')
|
||||
|
||||
render :index
|
||||
end
|
||||
end
|
||||
|
@ -2,10 +2,15 @@
|
||||
|
||||
module Settings
|
||||
class TwoFactorAuthenticationsController < BaseController
|
||||
include ChallengableConcern
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :verify_otp_required, only: [:create]
|
||||
before_action :require_challenge!, only: [:create]
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@confirmation = Form::TwoFactorConfirmation.new
|
||||
@ -21,6 +26,7 @@ module Settings
|
||||
if acceptable_code?
|
||||
current_user.otp_required_for_login = false
|
||||
current_user.save!
|
||||
UserMailer.two_factor_disabled(current_user).deliver_later!
|
||||
redirect_to settings_two_factor_authentication_path
|
||||
else
|
||||
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')
|
||||
@ -32,7 +38,7 @@ module Settings
|
||||
private
|
||||
|
||||
def confirmation_params
|
||||
params.require(:form_two_factor_confirmation).permit(:code)
|
||||
params.require(:form_two_factor_confirmation).permit(:otp_attempt)
|
||||
end
|
||||
|
||||
def verify_otp_required
|
||||
@ -40,8 +46,8 @@ module Settings
|
||||
end
|
||||
|
||||
def acceptable_code?
|
||||
current_user.validate_and_consume_otp!(confirmation_params[:code]) ||
|
||||
current_user.invalidate_otp_backup_code!(confirmation_params[:code])
|
||||
current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt]) ||
|
||||
current_user.invalidate_otp_backup_code!(confirmation_params[:otp_attempt])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -6,26 +6,10 @@ class SharesController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_body_classes
|
||||
|
||||
def show
|
||||
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
||||
@initial_state_json = serializable_resource.to_json
|
||||
end
|
||||
def show; end
|
||||
|
||||
private
|
||||
|
||||
def initial_state_params
|
||||
text = [params[:title], params[:text], params[:url]].compact.join(' ')
|
||||
|
||||
{
|
||||
settings: Web::Setting.find_by(user: current_user)&.data || {},
|
||||
push_subscription: current_account.user.web_push_subscription(current_session),
|
||||
current_account: current_account,
|
||||
token: current_session.token,
|
||||
admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
|
||||
text: text,
|
||||
}
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'modal-layout compose-standalone'
|
||||
end
|
||||
|
@ -1,24 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StatusesController < ApplicationController
|
||||
include StatusControllerConcern
|
||||
include SignatureAuthentication
|
||||
include Authorization
|
||||
|
||||
ANCESTORS_LIMIT = 40
|
||||
DESCENDANTS_LIMIT = 60
|
||||
DESCENDANTS_DEPTH_LIMIT = 20
|
||||
include AccountOwnedConcern
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :set_account
|
||||
before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
before_action :check_account_suspension
|
||||
before_action :redirect_to_original, only: [:show]
|
||||
before_action :set_referrer_policy_header, only: [:show]
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_referrer_policy_header, only: :show
|
||||
before_action :set_cache_headers
|
||||
before_action :set_replies, only: [:replies]
|
||||
before_action :set_body_classes
|
||||
before_action :set_autoplay, only: :embed
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, only: [:show, :embed]
|
||||
|
||||
content_security_policy only: :embed do |p|
|
||||
p.frame_ancestors(false)
|
||||
@ -28,27 +29,20 @@ class StatusesController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 10.seconds, public: true if current_account.nil?
|
||||
|
||||
@body_classes = 'with-modals'
|
||||
|
||||
set_ancestors
|
||||
set_descendants
|
||||
|
||||
render 'stream_entries/show'
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def activity
|
||||
render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
|
||||
def embed
|
||||
@ -56,130 +50,24 @@ class StatusesController < ApplicationController
|
||||
|
||||
expires_in 180, public: true
|
||||
response.headers['X-Frame-Options'] = 'ALLOWALL'
|
||||
@autoplay = ActiveModel::Type::Boolean.new.cast(params[:autoplay])
|
||||
|
||||
render 'stream_entries/embed', layout: 'embedded'
|
||||
end
|
||||
|
||||
def replies
|
||||
render json: replies_collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json',
|
||||
skip_activities: true
|
||||
render layout: 'embedded'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def replies_collection_presenter
|
||||
page = ActivityPub::CollectionPresenter.new(
|
||||
id: replies_account_status_url(@account, @status, page_params),
|
||||
type: :unordered,
|
||||
part_of: replies_account_status_url(@account, @status),
|
||||
next: next_page,
|
||||
items: @replies.map { |status| status.local ? status : status.id }
|
||||
)
|
||||
if page_requested?
|
||||
page
|
||||
else
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: replies_account_status_url(@account, @status),
|
||||
type: :unordered,
|
||||
first: page
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def create_descendant_thread(starting_depth, statuses)
|
||||
depth = starting_depth + statuses.size
|
||||
if depth < DESCENDANTS_DEPTH_LIMIT
|
||||
{ statuses: statuses, starting_depth: starting_depth }
|
||||
else
|
||||
next_status = statuses.pop
|
||||
{ statuses: statuses, starting_depth: starting_depth, next_status: next_status }
|
||||
end
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_ancestors
|
||||
@ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : []
|
||||
@next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift
|
||||
end
|
||||
|
||||
def set_descendants
|
||||
@max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i
|
||||
@since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i
|
||||
|
||||
descendants = cache_collection(
|
||||
@status.descendants(
|
||||
DESCENDANTS_LIMIT,
|
||||
current_account,
|
||||
@max_descendant_thread_id,
|
||||
@since_descendant_thread_id,
|
||||
DESCENDANTS_DEPTH_LIMIT
|
||||
),
|
||||
Status
|
||||
)
|
||||
|
||||
@descendant_threads = []
|
||||
|
||||
if descendants.present?
|
||||
statuses = [descendants.first]
|
||||
starting_depth = 0
|
||||
|
||||
descendants.drop(1).each_with_index do |descendant, index|
|
||||
if descendants[index].id == descendant.in_reply_to_id
|
||||
statuses << descendant
|
||||
else
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
|
||||
# The thread is broken, assume it's a reply to the root status
|
||||
starting_depth = 0
|
||||
|
||||
# ... unless we can find its ancestor in one of the already-processed threads
|
||||
@descendant_threads.reverse_each do |descendant_thread|
|
||||
statuses = descendant_thread[:statuses]
|
||||
|
||||
index = statuses.find_index do |thread_status|
|
||||
thread_status.id == descendant.in_reply_to_id
|
||||
end
|
||||
|
||||
if index.present?
|
||||
starting_depth = descendant_thread[:starting_depth] + index + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
statuses = [descendant]
|
||||
end
|
||||
end
|
||||
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
end
|
||||
|
||||
@max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
|
||||
[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]],
|
||||
]
|
||||
)
|
||||
response.headers['Link'] = LinkHeader.new([[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:id])
|
||||
@stream_entry = @status.stream_entry
|
||||
@type = @stream_entry.activity_type.downcase
|
||||
|
||||
@status = @account.statuses.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
@ -187,39 +75,15 @@ class StatusesController < ApplicationController
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
gone if @account.suspended?
|
||||
end
|
||||
|
||||
def redirect_to_original
|
||||
redirect_to ::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
return if @status.public_visibility? || @status.unlisted_visibility?
|
||||
response.headers['Referrer-Policy'] = 'origin'
|
||||
response.headers['Referrer-Policy'] = 'origin' unless @status.distributable?
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page] == 'true'
|
||||
end
|
||||
|
||||
def set_replies
|
||||
@replies = page_params[:other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
|
||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
|
||||
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
||||
end
|
||||
|
||||
def next_page
|
||||
last_reply = @replies.last
|
||||
return if last_reply.nil?
|
||||
same_account = last_reply.account_id == @account.id
|
||||
return unless same_account || @replies.size == DESCENDANTS_LIMIT
|
||||
same_account = false unless @replies.size == DESCENDANTS_LIMIT
|
||||
replies_account_status_url(@account, @status, page: true, min_id: last_reply.id, other_accounts: !same_account)
|
||||
end
|
||||
|
||||
def page_params
|
||||
{ page: true, other_accounts: params[:other_accounts], min_id: params[:min_id] }.compact
|
||||
def set_autoplay
|
||||
@autoplay = truthy_param?(:autoplay)
|
||||
end
|
||||
end
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user