Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - `Gemfile.lock`: Not a real conflict, just a glitch-soc-only dependency too close to a dependency that got updated upstream. Updated as well. - `app/models/status.rb`: Not a real conflict, just a change too close to glitch-soc-changed code for optionally showing boosts in public timelines. Applied upstream changes. - `app/views/layouts/application.html.haml`: Upstream a new, static CSS file, conflict due to glitch-soc's theming system, include the file regardless of the theme. - `config/initializers/content_security_policy.rb`: Upstream dropped 'unsafe-inline' from the 'style-src' directive, but both files are very different. Removed 'unsafe-inline' as well.remotes/1727458204337373841/tmp_refs/heads/signup-info-prompt
commit
4a70792b4a
|
@ -30,7 +30,7 @@ plugins:
|
||||||
channel: eslint-6
|
channel: eslint-6
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: rubocop-0-76
|
channel: rubocop-0-82
|
||||||
sass-lint:
|
sass-lint:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude_patterns:
|
exclude_patterns:
|
||||||
|
|
|
@ -33,7 +33,7 @@ LOCAL_DOMAIN=example.com
|
||||||
# ALTERNATE_DOMAINS=example1.com,example2.com
|
# ALTERNATE_DOMAINS=example1.com,example2.com
|
||||||
|
|
||||||
# Application secrets
|
# Application secrets
|
||||||
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
|
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web bundle exec rake secret` if you use docker compose)
|
||||||
SECRET_KEY_BASE=
|
SECRET_KEY_BASE=
|
||||||
OTP_SECRET=
|
OTP_SECRET=
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ OTP_SECRET=
|
||||||
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
||||||
# be invalidated, requiring the users to access the website again to resubscribe.
|
# be invalidated, requiring the users to access the website again to resubscribe.
|
||||||
#
|
#
|
||||||
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
||||||
#
|
#
|
||||||
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
||||||
VAPID_PRIVATE_KEY=
|
VAPID_PRIVATE_KEY=
|
||||||
|
|
22
Gemfile
22
Gemfile
|
@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
|
||||||
gem 'pghero', '~> 2.4'
|
gem 'pghero', '~> 2.4'
|
||||||
gem 'dotenv-rails', '~> 2.7'
|
gem 'dotenv-rails', '~> 2.7'
|
||||||
|
|
||||||
gem 'aws-sdk-s3', '~> 1.63', require: false
|
gem 'aws-sdk-s3', '~> 1.64', require: false
|
||||||
gem 'fog-core', '<= 2.1.0'
|
gem 'fog-core', '<= 2.1.0'
|
||||||
gem 'fog-openstack', '~> 0.3', require: false
|
gem 'fog-openstack', '~> 0.3', require: false
|
||||||
gem 'paperclip', '~> 6.0'
|
gem 'paperclip', '~> 6.0'
|
||||||
|
@ -57,12 +57,12 @@ gem 'hiredis', '~> 0.6'
|
||||||
gem 'redis-namespace', '~> 1.7'
|
gem 'redis-namespace', '~> 1.7'
|
||||||
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
||||||
gem 'htmlentities', '~> 4.3'
|
gem 'htmlentities', '~> 4.3'
|
||||||
gem 'http', '~> 4.3'
|
gem 'http', '~> 4.4'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
|
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
|
||||||
gem 'httplog', '~> 1.4.2'
|
gem 'httplog', '~> 1.4.2'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'kaminari', '~> 1.1'
|
gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
||||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||||
|
@ -75,7 +75,7 @@ gem 'parallel', '~> 1.19'
|
||||||
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
||||||
gem 'pundit', '~> 2.1'
|
gem 'pundit', '~> 2.1'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'rack-attack', '~> 6.2'
|
gem 'rack-attack', '~> 6.3'
|
||||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||||
gem 'rails-i18n', '~> 5.1'
|
gem 'rails-i18n', '~> 5.1'
|
||||||
gem 'rails-settings-cached', '~> 0.6'
|
gem 'rails-settings-cached', '~> 0.6'
|
||||||
|
@ -96,8 +96,8 @@ gem 'strong_migrations', '~> 0.6'
|
||||||
gem 'tty-command', '~> 0.9', require: false
|
gem 'tty-command', '~> 0.9', require: false
|
||||||
gem 'tty-prompt', '~> 0.21', require: false
|
gem 'tty-prompt', '~> 0.21', require: false
|
||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 1.14'
|
||||||
gem 'tzinfo-data', '~> 1.2019'
|
gem 'tzinfo-data', '~> 1.2020'
|
||||||
gem 'webpacker', '~> 4.2'
|
gem 'webpacker', '~> 5.1'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
|
|
||||||
gem 'json-ld'
|
gem 'json-ld'
|
||||||
|
@ -110,7 +110,7 @@ group :development, :test do
|
||||||
gem 'fabrication', '~> 2.21'
|
gem 'fabrication', '~> 2.21'
|
||||||
gem 'fuubar', '~> 2.5'
|
gem 'fuubar', '~> 2.5'
|
||||||
gem 'i18n-tasks', '~> 0.9', require: false
|
gem 'i18n-tasks', '~> 0.9', require: false
|
||||||
gem 'pry-byebug', '~> 3.8'
|
gem 'pry-byebug', '~> 3.9'
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 4.0'
|
gem 'rspec-rails', '~> 4.0'
|
||||||
end
|
end
|
||||||
|
@ -120,7 +120,7 @@ group :production, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.31'
|
gem 'capybara', '~> 3.32'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 2.11'
|
gem 'faker', '~> 2.11'
|
||||||
gem 'microformats', '~> 4.2'
|
gem 'microformats', '~> 4.2'
|
||||||
|
@ -135,18 +135,18 @@ end
|
||||||
group :development do
|
group :development do
|
||||||
gem 'active_record_query_trace', '~> 1.7'
|
gem 'active_record_query_trace', '~> 1.7'
|
||||||
gem 'annotate', '~> 3.1'
|
gem 'annotate', '~> 3.1'
|
||||||
gem 'better_errors', '~> 2.6'
|
gem 'better_errors', '~> 2.7'
|
||||||
gem 'binding_of_caller', '~> 0.7'
|
gem 'binding_of_caller', '~> 0.7'
|
||||||
gem 'bullet', '~> 6.1'
|
gem 'bullet', '~> 6.1'
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.4'
|
gem 'letter_opener_web', '~> 1.4'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 0.79', require: false
|
gem 'rubocop', '~> 0.82', require: false
|
||||||
gem 'rubocop-rails', '~> 2.5', require: false
|
gem 'rubocop-rails', '~> 2.5', require: false
|
||||||
gem 'brakeman', '~> 4.8', require: false
|
gem 'brakeman', '~> 4.8', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
|
|
||||||
gem 'capistrano', '~> 3.13'
|
gem 'capistrano', '~> 3.14'
|
||||||
gem 'capistrano-rails', '~> 1.4'
|
gem 'capistrano-rails', '~> 1.4'
|
||||||
gem 'capistrano-rbenv', '~> 2.1'
|
gem 'capistrano-rbenv', '~> 2.1'
|
||||||
gem 'capistrano-yarn', '~> 2.0'
|
gem 'capistrano-yarn', '~> 2.0'
|
||||||
|
|
142
Gemfile.lock
142
Gemfile.lock
|
@ -92,23 +92,23 @@ GEM
|
||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.303.0)
|
aws-partitions (1.311.0)
|
||||||
aws-sdk-core (3.94.0)
|
aws-sdk-core (3.95.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.30.0)
|
aws-sdk-kms (1.31.0)
|
||||||
aws-sdk-core (~> 3, >= 3.71.0)
|
aws-sdk-core (~> 3, >= 3.71.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.63.0)
|
aws-sdk-s3 (1.64.0)
|
||||||
aws-sdk-core (~> 3, >= 3.83.0)
|
aws-sdk-core (~> 3, >= 3.83.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.1.2)
|
aws-sigv4 (1.1.3)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
bcrypt (3.1.13)
|
bcrypt (3.1.13)
|
||||||
better_errors (2.6.0)
|
better_errors (2.7.0)
|
||||||
coderay (>= 1.0.0)
|
coderay (>= 1.0.0)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
|
@ -118,7 +118,7 @@ GEM
|
||||||
ffi (~> 1.10.0)
|
ffi (~> 1.10.0)
|
||||||
bootsnap (1.4.6)
|
bootsnap (1.4.6)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.8.0)
|
brakeman (4.8.1)
|
||||||
browser (4.0.0)
|
browser (4.0.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (6.1.0)
|
bullet (6.1.0)
|
||||||
|
@ -127,8 +127,8 @@ GEM
|
||||||
bundler-audit (0.6.1)
|
bundler-audit (0.6.1)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
byebug (11.1.1)
|
byebug (11.1.3)
|
||||||
capistrano (3.13.0)
|
capistrano (3.14.0)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
rake (>= 10.0.0)
|
rake (>= 10.0.0)
|
||||||
|
@ -143,7 +143,7 @@ GEM
|
||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.31.0)
|
capybara (3.32.1)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
|
@ -194,7 +194,7 @@ GEM
|
||||||
docile (1.3.2)
|
docile (1.3.2)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (5.3.1)
|
doorkeeper (5.3.3)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.5)
|
||||||
dotenv-rails (2.7.5)
|
dotenv-rails (2.7.5)
|
||||||
|
@ -213,7 +213,7 @@ GEM
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
equatable (0.6.1)
|
equatable (0.6.1)
|
||||||
erubi (1.9.0)
|
erubi (1.9.0)
|
||||||
et-orbi (1.2.3)
|
et-orbi (1.2.4)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.73.0)
|
excon (0.73.0)
|
||||||
fabrication (2.21.1)
|
fabrication (2.21.1)
|
||||||
|
@ -240,7 +240,7 @@ GEM
|
||||||
fog-json (>= 1.0)
|
fog-json (>= 1.0)
|
||||||
ipaddress (>= 0.8)
|
ipaddress (>= 0.8)
|
||||||
formatador (0.2.5)
|
formatador (0.2.5)
|
||||||
fugit (1.3.3)
|
fugit (1.3.5)
|
||||||
et-orbi (~> 1.1, >= 1.1.8)
|
et-orbi (~> 1.1, >= 1.1.8)
|
||||||
raabro (~> 1.1)
|
raabro (~> 1.1)
|
||||||
fuubar (2.5.0)
|
fuubar (2.5.0)
|
||||||
|
@ -270,7 +270,7 @@ GEM
|
||||||
hiredis (0.6.3)
|
hiredis (0.6.3)
|
||||||
hkdf (0.3.0)
|
hkdf (0.3.0)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http (4.3.0)
|
http (4.4.1)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
http-cookie (~> 1.0)
|
http-cookie (~> 1.0)
|
||||||
http-form_data (~> 2.2)
|
http-form_data (~> 2.2)
|
||||||
|
@ -303,7 +303,7 @@ GEM
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
json-canonicalization (0.2.0)
|
json-canonicalization (0.2.0)
|
||||||
json-ld (3.1.3)
|
json-ld (3.1.4)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
json-canonicalization (~> 0.2)
|
json-canonicalization (~> 0.2)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
|
@ -314,21 +314,21 @@ GEM
|
||||||
json-ld (~> 3.1)
|
json-ld (~> 3.1)
|
||||||
rdf (~> 3.1)
|
rdf (~> 3.1)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.1.0)
|
jwt (2.2.1)
|
||||||
kaminari (1.1.1)
|
kaminari (1.2.0)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.1.1)
|
kaminari-actionview (= 1.2.0)
|
||||||
kaminari-activerecord (= 1.1.1)
|
kaminari-activerecord (= 1.2.0)
|
||||||
kaminari-core (= 1.1.1)
|
kaminari-core (= 1.2.0)
|
||||||
kaminari-actionview (1.1.1)
|
kaminari-actionview (1.2.0)
|
||||||
actionview
|
actionview
|
||||||
kaminari-core (= 1.1.1)
|
kaminari-core (= 1.2.0)
|
||||||
kaminari-activerecord (1.1.1)
|
kaminari-activerecord (1.2.0)
|
||||||
activerecord
|
activerecord
|
||||||
kaminari-core (= 1.1.1)
|
kaminari-core (= 1.2.0)
|
||||||
kaminari-core (1.1.1)
|
kaminari-core (1.2.0)
|
||||||
launchy (2.4.3)
|
launchy (2.5.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.7)
|
||||||
letter_opener (1.7.0)
|
letter_opener (1.7.0)
|
||||||
launchy (~> 2.2)
|
launchy (~> 2.2)
|
||||||
letter_opener_web (1.4.0)
|
letter_opener_web (1.4.0)
|
||||||
|
@ -353,14 +353,14 @@ GEM
|
||||||
mario-redis-lock (1.2.1)
|
mario-redis-lock (1.2.1)
|
||||||
redis (>= 3.0.5)
|
redis (>= 3.0.5)
|
||||||
memory_profiler (0.9.14)
|
memory_profiler (0.9.14)
|
||||||
method_source (0.9.2)
|
method_source (1.0.0)
|
||||||
microformats (4.2.0)
|
microformats (4.2.0)
|
||||||
json (~> 2.2)
|
json (~> 2.2)
|
||||||
nokogiri (~> 1.10)
|
nokogiri (~> 1.10)
|
||||||
mime-types (3.3.1)
|
mime-types (3.3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2020.0425)
|
mime-types-data (3.2020.0425)
|
||||||
mimemagic (0.3.4)
|
mimemagic (0.3.5)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.14.0)
|
minitest (5.14.0)
|
||||||
|
@ -369,9 +369,9 @@ GEM
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
necromancer (0.5.1)
|
necromancer (0.5.1)
|
||||||
net-ldap (0.16.2)
|
net-ldap (0.16.2)
|
||||||
net-scp (2.0.0)
|
net-scp (3.0.0)
|
||||||
net-ssh (>= 2.6.5, < 6.0.0)
|
net-ssh (>= 2.6.5, < 7.0.0)
|
||||||
net-ssh (5.2.0)
|
net-ssh (6.0.2)
|
||||||
nio4r (2.5.2)
|
nio4r (2.5.2)
|
||||||
nokogiri (1.10.9)
|
nokogiri (1.10.9)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
|
@ -407,30 +407,30 @@ GEM
|
||||||
parallel (1.19.1)
|
parallel (1.19.1)
|
||||||
parallel_tests (2.32.0)
|
parallel_tests (2.32.0)
|
||||||
parallel
|
parallel
|
||||||
parser (2.7.1.1)
|
parser (2.7.1.2)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
pastel (0.7.3)
|
pastel (0.7.3)
|
||||||
equatable (~> 0.6)
|
equatable (~> 0.6)
|
||||||
tty-color (~> 0.5)
|
tty-color (~> 0.5)
|
||||||
pg (1.2.3)
|
pg (1.2.3)
|
||||||
pghero (2.4.1)
|
pghero (2.4.2)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
pkg-config (1.4.1)
|
pkg-config (1.4.1)
|
||||||
premailer (1.11.1)
|
premailer (1.11.1)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.6.0)
|
css_parser (>= 1.6.0)
|
||||||
htmlentities (>= 4.0.0)
|
htmlentities (>= 4.0.0)
|
||||||
premailer-rails (1.10.3)
|
premailer-rails (1.11.1)
|
||||||
actionmailer (>= 3)
|
actionmailer (>= 3)
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
pry (0.12.2)
|
pry (0.13.1)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1)
|
||||||
method_source (~> 0.9.0)
|
method_source (~> 1.0)
|
||||||
pry-byebug (3.8.0)
|
pry-byebug (3.9.0)
|
||||||
byebug (~> 11.0)
|
byebug (~> 11.0)
|
||||||
pry (~> 0.10)
|
pry (~> 0.13.0)
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (4.0.4)
|
public_suffix (4.0.4)
|
||||||
|
@ -440,7 +440,7 @@ GEM
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.1.6)
|
raabro (1.1.6)
|
||||||
rack (2.2.2)
|
rack (2.2.2)
|
||||||
rack-attack (6.2.2)
|
rack-attack (6.3.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
|
@ -491,13 +491,13 @@ GEM
|
||||||
rdf-normalize (0.4.0)
|
rdf-normalize (0.4.0)
|
||||||
rdf (~> 3.1)
|
rdf (~> 3.1)
|
||||||
redcarpet (3.5.0)
|
redcarpet (3.5.0)
|
||||||
redis (4.1.3)
|
redis (4.1.4)
|
||||||
redis-actionpack (5.2.0)
|
redis-actionpack (5.2.0)
|
||||||
actionpack (>= 5, < 7)
|
actionpack (>= 5, < 7)
|
||||||
redis-rack (>= 2.1.0, < 3)
|
redis-rack (>= 2.1.0, < 3)
|
||||||
redis-store (>= 1.1.0, < 2)
|
redis-store (>= 1.1.0, < 2)
|
||||||
redis-activesupport (5.0.4)
|
redis-activesupport (5.2.0)
|
||||||
activesupport (>= 3, < 6)
|
activesupport (>= 3, < 7)
|
||||||
redis-store (>= 1.3, < 2)
|
redis-store (>= 1.3, < 2)
|
||||||
redis-namespace (1.7.0)
|
redis-namespace (1.7.0)
|
||||||
redis (>= 3.0.4)
|
redis (>= 3.0.4)
|
||||||
|
@ -516,14 +516,15 @@ GEM
|
||||||
responders (3.0.0)
|
responders (3.0.0)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
|
rexml (3.2.4)
|
||||||
rotp (2.1.2)
|
rotp (2.1.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (1.1.2)
|
rqrcode (1.1.2)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 0.1)
|
rqrcode_core (~> 0.1)
|
||||||
rqrcode_core (0.1.2)
|
rqrcode_core (0.1.2)
|
||||||
rspec-core (3.9.1)
|
rspec-core (3.9.2)
|
||||||
rspec-support (~> 3.9.1)
|
rspec-support (~> 3.9.3)
|
||||||
rspec-expectations (3.9.1)
|
rspec-expectations (3.9.1)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.9.0)
|
rspec-support (~> 3.9.0)
|
||||||
|
@ -541,16 +542,17 @@ GEM
|
||||||
rspec-sidekiq (3.0.3)
|
rspec-sidekiq (3.0.3)
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.9.2)
|
rspec-support (3.9.3)
|
||||||
rspec_junit_formatter (0.4.1)
|
rspec_junit_formatter (0.4.1)
|
||||||
rspec-core (>= 2, < 4, != 2.12.0)
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rubocop (0.79.0)
|
rubocop (0.82.0)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.7.0.1)
|
parser (>= 2.7.0.1)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
rexml
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 1.7)
|
unicode-display_width (>= 1.4.0, < 2.0)
|
||||||
rubocop-rails (2.5.2)
|
rubocop-rails (2.5.2)
|
||||||
activesupport
|
activesupport
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
|
@ -565,9 +567,10 @@ GEM
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.8.0)
|
nokogiri (>= 1.8.0)
|
||||||
nokogumbo (~> 2.0)
|
nokogumbo (~> 2.0)
|
||||||
sidekiq (6.0.4)
|
semantic_range (2.3.0)
|
||||||
|
sidekiq (6.0.7)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (>= 2.0.0)
|
rack (~> 2.0)
|
||||||
rack-protection (>= 2.0.0)
|
rack-protection (>= 2.0.0)
|
||||||
redis (>= 4.1.0)
|
redis (>= 4.1.0)
|
||||||
sidekiq-bulk (0.2.0)
|
sidekiq-bulk (0.2.0)
|
||||||
|
@ -607,7 +610,7 @@ GEM
|
||||||
stoplight (2.2.0)
|
stoplight (2.2.0)
|
||||||
streamio-ffmpeg (3.0.2)
|
streamio-ffmpeg (3.0.2)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
strong_migrations (0.6.2)
|
strong_migrations (0.6.5)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
temple (0.8.2)
|
temple (0.8.2)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
|
@ -635,12 +638,12 @@ GEM
|
||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.7)
|
tzinfo (1.2.7)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2019.3)
|
tzinfo-data (1.2020.1)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.6)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.6.1)
|
unicode-display_width (1.7.0)
|
||||||
uniform_notifier (1.13.0)
|
uniform_notifier (1.13.0)
|
||||||
warden (1.2.8)
|
warden (1.2.8)
|
||||||
rack (>= 2.0.6)
|
rack (>= 2.0.6)
|
||||||
|
@ -648,10 +651,11 @@ GEM
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webpacker (4.2.2)
|
webpacker (5.1.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 5.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 4.2)
|
railties (>= 5.2)
|
||||||
|
semantic_range (>= 2.3.0)
|
||||||
webpush (0.3.8)
|
webpush (0.3.8)
|
||||||
hkdf (~> 0.2)
|
hkdf (~> 0.2)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
|
@ -670,8 +674,8 @@ DEPENDENCIES
|
||||||
active_record_query_trace (~> 1.7)
|
active_record_query_trace (~> 1.7)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.7)
|
||||||
annotate (~> 3.1)
|
annotate (~> 3.1)
|
||||||
aws-sdk-s3 (~> 1.63)
|
aws-sdk-s3 (~> 1.64)
|
||||||
better_errors (~> 2.6)
|
better_errors (~> 2.7)
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
blurhash (~> 0.1)
|
blurhash (~> 0.1)
|
||||||
bootsnap (~> 1.4)
|
bootsnap (~> 1.4)
|
||||||
|
@ -679,11 +683,11 @@ DEPENDENCIES
|
||||||
browser
|
browser
|
||||||
bullet (~> 6.1)
|
bullet (~> 6.1)
|
||||||
bundler-audit (~> 0.6)
|
bundler-audit (~> 0.6)
|
||||||
capistrano (~> 3.13)
|
capistrano (~> 3.14)
|
||||||
capistrano-rails (~> 1.4)
|
capistrano-rails (~> 1.4)
|
||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 3.31)
|
capybara (~> 3.32)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
chewy (~> 5.1)
|
chewy (~> 5.1)
|
||||||
cld3 (~> 3.3.0)
|
cld3 (~> 3.3.0)
|
||||||
|
@ -709,7 +713,7 @@ DEPENDENCIES
|
||||||
health_check!
|
health_check!
|
||||||
hiredis (~> 0.6)
|
hiredis (~> 0.6)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
http (~> 4.3)
|
http (~> 4.4)
|
||||||
http_accept_language (~> 2.1)
|
http_accept_language (~> 2.1)
|
||||||
http_parser.rb (~> 0.6)!
|
http_parser.rb (~> 0.6)!
|
||||||
httplog (~> 1.4.2)
|
httplog (~> 1.4.2)
|
||||||
|
@ -718,7 +722,7 @@ DEPENDENCIES
|
||||||
iso-639
|
iso-639
|
||||||
json-ld
|
json-ld
|
||||||
json-ld-preloaded (~> 3.1)
|
json-ld-preloaded (~> 3.1)
|
||||||
kaminari (~> 1.1)
|
kaminari (~> 1.2)
|
||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
letter_opener_web (~> 1.4)
|
letter_opener_web (~> 1.4)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
|
@ -748,12 +752,12 @@ DEPENDENCIES
|
||||||
posix-spawn!
|
posix-spawn!
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
pry-byebug (~> 3.8)
|
pry-byebug (~> 3.9)
|
||||||
pry-rails (~> 0.3)
|
pry-rails (~> 0.3)
|
||||||
puma (~> 4.3)
|
puma (~> 4.3)
|
||||||
pundit (~> 2.1)
|
pundit (~> 2.1)
|
||||||
rack (~> 2.2.2)
|
rack (~> 2.2.2)
|
||||||
rack-attack (~> 6.2)
|
rack-attack (~> 6.3)
|
||||||
rack-cors (~> 1.1)
|
rack-cors (~> 1.1)
|
||||||
rails (~> 5.2.4.2)
|
rails (~> 5.2.4.2)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
|
@ -768,7 +772,7 @@ DEPENDENCIES
|
||||||
rspec-rails (~> 4.0)
|
rspec-rails (~> 4.0)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rspec_junit_formatter (~> 0.4)
|
rspec_junit_formatter (~> 0.4)
|
||||||
rubocop (~> 0.79)
|
rubocop (~> 0.82)
|
||||||
rubocop-rails (~> 2.5)
|
rubocop-rails (~> 2.5)
|
||||||
ruby-progressbar (~> 1.10)
|
ruby-progressbar (~> 1.10)
|
||||||
sanitize (~> 5.1)
|
sanitize (~> 5.1)
|
||||||
|
@ -790,7 +794,7 @@ DEPENDENCIES
|
||||||
tty-command (~> 0.9)
|
tty-command (~> 0.9)
|
||||||
tty-prompt (~> 0.21)
|
tty-prompt (~> 0.21)
|
||||||
twitter-text (~> 1.14)
|
twitter-text (~> 1.14)
|
||||||
tzinfo-data (~> 1.2019)
|
tzinfo-data (~> 1.2020)
|
||||||
webmock (~> 3.8)
|
webmock (~> 3.8)
|
||||||
webpacker (~> 4.2)
|
webpacker (~> 5.1)
|
||||||
webpush
|
webpush
|
||||||
|
|
|
@ -41,7 +41,7 @@ class AccountsController < ApplicationController
|
||||||
format.rss do
|
format.rss do
|
||||||
expires_in 1.minute, public: true
|
expires_in 1.minute, public: true
|
||||||
|
|
||||||
@statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE)
|
@statuses = filtered_statuses.without_reblogs.limit(PAGE_SIZE)
|
||||||
@statuses = cache_collection(@statuses, Status)
|
@statuses = cache_collection(@statuses, Status)
|
||||||
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
||||||
end
|
end
|
||||||
|
@ -130,11 +130,11 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_requested?
|
def media_requested?
|
||||||
request.path.ends_with?('/media') && !tag_requested?
|
request.path.split('.').first.ends_with?('/media') && !tag_requested?
|
||||||
end
|
end
|
||||||
|
|
||||||
def replies_requested?
|
def replies_requested?
|
||||||
request.path.ends_with?('/with_replies') && !tag_requested?
|
request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_requested?
|
def tag_requested?
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||||
return [] if hide_results?
|
return [] if hide_results?
|
||||||
|
|
||||||
scope = default_accounts
|
scope = default_accounts
|
||||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
|
||||||
scope.merge(paginated_follows).to_a
|
scope.merge(paginated_follows).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||||
return [] if hide_results?
|
return [] if hide_results?
|
||||||
|
|
||||||
scope = default_accounts
|
scope = default_accounts
|
||||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
|
||||||
scope.merge(paginated_follows).to_a
|
scope.merge(paginated_follows).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_timeline_statuses
|
def public_timeline_statuses
|
||||||
Status.as_public_timeline(current_account, truthy_param?(:local))
|
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local))
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_pagination_headers
|
def insert_pagination_headers
|
||||||
|
@ -47,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
|
params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_path
|
def next_path
|
||||||
|
|
|
@ -113,6 +113,13 @@ class Auth::SessionsController < Devise::SessionsController
|
||||||
render :two_factor
|
render :two_factor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def require_no_authentication
|
||||||
|
super
|
||||||
|
# Delete flash message that isn't entirely useful and may be confusing in
|
||||||
|
# most cases because /web doesn't display/clear flash messages.
|
||||||
|
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_pack
|
def set_pack
|
||||||
|
|
|
@ -22,8 +22,7 @@ class Settings::IdentityProofsController < Settings::BaseController
|
||||||
if current_account.username.casecmp(params[:username]).zero?
|
if current_account.username.casecmp(params[:username]).zero?
|
||||||
render layout: 'auth'
|
render layout: 'auth'
|
||||||
else
|
else
|
||||||
flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
|
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username)
|
||||||
redirect_to settings_identity_proofs_path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,11 +34,16 @@ class Settings::IdentityProofsController < Settings::BaseController
|
||||||
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
|
PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof?
|
||||||
redirect_to @proof.on_success_path(params[:user_agent])
|
redirect_to @proof.on_success_path(params[:user_agent])
|
||||||
else
|
else
|
||||||
flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
|
redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize)
|
||||||
redirect_to settings_identity_proofs_path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@proof = current_account.identity_proofs.find(params[:id])
|
||||||
|
@proof.destroy!
|
||||||
|
redirect_to settings_identity_proofs_path, success: I18n.t('identity_proofs.removed')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_enabled
|
def check_enabled
|
||||||
|
|
|
@ -7,13 +7,13 @@ module HomeHelper
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_link_to(account, button = '', size: 36, path: nil)
|
def account_link_to(account, button = '', path: nil)
|
||||||
content_tag(:div, class: 'account') do
|
content_tag(:div, class: 'account') do
|
||||||
content_tag(:div, class: 'account__wrapper') do
|
content_tag(:div, class: 'account__wrapper') do
|
||||||
section = if account.nil?
|
section = if account.nil?
|
||||||
content_tag(:div, class: 'account__display-name') do
|
content_tag(:div, class: 'account__display-name') do
|
||||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||||
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})")
|
image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'account__avatar')
|
||||||
end +
|
end +
|
||||||
content_tag(:span, class: 'display-name') do
|
content_tag(:span, class: 'display-name') do
|
||||||
content_tag(:strong, t('about.contact_missing')) +
|
content_tag(:strong, t('about.contact_missing')) +
|
||||||
|
@ -23,7 +23,7 @@ module HomeHelper
|
||||||
else
|
else
|
||||||
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
|
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
|
||||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||||
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})")
|
image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar')
|
||||||
end +
|
end +
|
||||||
content_tag(:span, class: 'display-name') do
|
content_tag(:span, class: 'display-name') do
|
||||||
content_tag(:bdi) do
|
content_tag(:bdi) do
|
||||||
|
|
|
@ -68,6 +68,7 @@ module SettingsHelper
|
||||||
tr: 'Türkçe',
|
tr: 'Türkçe',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
ur: 'اُردُو',
|
ur: 'اُردُو',
|
||||||
|
vi: 'Tiếng Việt',
|
||||||
'zh-CN': '简体中文',
|
'zh-CN': '简体中文',
|
||||||
'zh-HK': '繁體中文(香港)',
|
'zh-HK': '繁體中文(香港)',
|
||||||
'zh-TW': '繁體中文(臺灣)',
|
'zh-TW': '繁體中文(臺灣)',
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WebfingerHelper
|
||||||
|
def webfinger!(uri)
|
||||||
|
hidden_service_uri = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
||||||
|
|
||||||
|
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && hidden_service_uri
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
ssl: !hidden_service_uri,
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
'User-Agent': Mastodon::Version.user_agent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Goldfinger::Client.new(uri, opts.merge(Rails.configuration.x.http_client_proxy)).finger
|
||||||
|
end
|
||||||
|
end
|
|
@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
||||||
|
|
||||||
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
||||||
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
|
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
|
||||||
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
|
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
|
||||||
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
|
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
|
||||||
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
||||||
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
||||||
|
|
|
@ -107,7 +107,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||||
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class ColumnSettings extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
columnId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { settings, onChange } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='column-settings__row'>
|
||||||
|
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
||||||
|
<SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ColumnSettings from '../../community_timeline/components/column_settings';
|
import ColumnSettings from '../components/column_settings';
|
||||||
import { changeSetting } from '../../../actions/settings';
|
import { changeSetting } from '../../../actions/settings';
|
||||||
import { changeColumnParams } from '../../../actions/columns';
|
import { changeColumnParams } from '../../../actions/columns';
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => {
|
||||||
const columns = state.getIn(['settings', 'columns']);
|
const columns = state.getIn(['settings', 'columns']);
|
||||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
||||||
|
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
||||||
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
||||||
onlyMedia,
|
onlyMedia,
|
||||||
|
onlyRemote,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,15 +49,16 @@ class PublicTimeline extends React.PureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
onlyMedia: PropTypes.bool,
|
onlyMedia: PropTypes.bool,
|
||||||
|
onlyRemote: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePin = () => {
|
handlePin = () => {
|
||||||
const { columnId, dispatch, onlyMedia } = this.props;
|
const { columnId, dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
if (columnId) {
|
if (columnId) {
|
||||||
dispatch(removeColumn(columnId));
|
dispatch(removeColumn(columnId));
|
||||||
} else {
|
} else {
|
||||||
dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
|
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,19 +72,19 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.onlyMedia !== this.props.onlyMedia) {
|
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
|
||||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,13 +100,13 @@ class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = maxId => {
|
||||||
const { dispatch, onlyMedia } = this.props;
|
const { dispatch, onlyMedia, onlyRemote } = this.props;
|
||||||
|
|
||||||
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
|
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
|
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -122,7 +125,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
timelineId={`public${onlyMedia ? ':media' : ''}`}
|
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`public_timeline-${columnId}`}
|
scrollKey={`public_timeline-${columnId}`}
|
||||||
|
|
|
@ -5,30 +5,21 @@ import ColumnHeader from '../column_header';
|
||||||
|
|
||||||
describe('<Column />', () => {
|
describe('<Column />', () => {
|
||||||
describe('<ColumnHeader /> click handler', () => {
|
describe('<ColumnHeader /> click handler', () => {
|
||||||
const originalRaf = global.requestAnimationFrame;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
global.requestAnimationFrame = jest.fn();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
global.requestAnimationFrame = originalRaf;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('runs the scroll animation if the column contains scrollable content', () => {
|
it('runs the scroll animation if the column contains scrollable content', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Column heading='notifications'>
|
<Column heading='notifications'>
|
||||||
<div className='scrollable' />
|
<div className='scrollable' />
|
||||||
</Column>,
|
</Column>,
|
||||||
);
|
);
|
||||||
|
const scrollToMock = jest.fn();
|
||||||
|
wrapper.find(Column).find('.scrollable').getDOMNode().scrollTo = scrollToMock;
|
||||||
wrapper.find(ColumnHeader).find('button').simulate('click');
|
wrapper.find(ColumnHeader).find('button').simulate('click');
|
||||||
expect(global.requestAnimationFrame.mock.calls.length).toEqual(1);
|
expect(scrollToMock).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not try to scroll if there is no scrollable content', () => {
|
it('does not try to scroll if there is no scrollable content', () => {
|
||||||
const wrapper = mount(<Column heading='notifications' />);
|
const wrapper = mount(<Column heading='notifications' />);
|
||||||
wrapper.find(ColumnHeader).find('button').simulate('click');
|
wrapper.find(ColumnHeader).find('button').simulate('click');
|
||||||
expect(global.requestAnimationFrame.mock.calls.length).toEqual(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ const componentMap = {
|
||||||
'HOME': HomeTimeline,
|
'HOME': HomeTimeline,
|
||||||
'NOTIFICATIONS': Notifications,
|
'NOTIFICATIONS': Notifications,
|
||||||
'PUBLIC': PublicTimeline,
|
'PUBLIC': PublicTimeline,
|
||||||
|
'REMOTE': PublicTimeline,
|
||||||
'COMMUNITY': CommunityTimeline,
|
'COMMUNITY': CommunityTimeline,
|
||||||
'HASHTAG': HashtagTimeline,
|
'HASHTAG': HashtagTimeline,
|
||||||
'DIRECT': DirectTimeline,
|
'DIRECT': DirectTimeline,
|
||||||
|
|
|
@ -543,12 +543,6 @@ $small-breakpoint: 960px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-name {
|
.display-name {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|
||||||
|
@ -749,12 +743,6 @@ $small-breakpoint: 960px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__counters__wrapper {
|
&__counters__wrapper {
|
||||||
|
|
|
@ -1318,8 +1318,13 @@
|
||||||
|
|
||||||
.account__avatar {
|
.account__avatar {
|
||||||
@include avatar-radius;
|
@include avatar-radius;
|
||||||
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background-size: 36px 36px;
|
||||||
|
|
||||||
&-inline {
|
&-inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -93,12 +93,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background-size: 44px 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.trends__item {
|
.trends__item {
|
||||||
|
|
|
@ -73,8 +73,6 @@ class Request
|
||||||
response.body_with_limit if http_client.persistent?
|
response.body_with_limit if http_client.persistent?
|
||||||
|
|
||||||
yield response if block_given?
|
yield response if block_given?
|
||||||
rescue => e
|
|
||||||
raise e.class, e.message, e.backtrace[0]
|
|
||||||
ensure
|
ensure
|
||||||
http_client.close unless http_client.persistent?
|
http_client.close unless http_client.persistent?
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RSS::Serializer
|
||||||
|
private
|
||||||
|
|
||||||
|
def render_statuses(builder, statuses)
|
||||||
|
statuses.each do |status|
|
||||||
|
builder.item do |item|
|
||||||
|
item.title(status_title(status))
|
||||||
|
.link(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
.pub_date(status.created_at)
|
||||||
|
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
|
||||||
|
|
||||||
|
status.media_attachments.each do |media|
|
||||||
|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_title(status)
|
||||||
|
return "#{status.account.acct} deleted status" if status.destroyed?
|
||||||
|
|
||||||
|
preview = status.proper.spoiler_text.presence || status.proper.text
|
||||||
|
if preview.length > 30 || preview[0, 30].include?("\n")
|
||||||
|
preview = preview[0, 30]
|
||||||
|
preview = preview[0, preview.index("\n").presence || 30] + '…'
|
||||||
|
end
|
||||||
|
|
||||||
|
preview = "#{status.proper.spoiler_text.present? ? 'CW ' : ''}“#{preview}”#{status.proper.sensitive? ? ' (sensitive)' : ''}"
|
||||||
|
|
||||||
|
if status.reblog?
|
||||||
|
"#{status.account.acct} boosted #{status.reblog.account.acct}: #{preview}"
|
||||||
|
else
|
||||||
|
"#{status.account.acct}: #{preview}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,13 +1,24 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SidekiqErrorHandler
|
class SidekiqErrorHandler
|
||||||
|
BACKTRACE_LIMIT = 3
|
||||||
|
|
||||||
def call(*)
|
def call(*)
|
||||||
yield
|
yield
|
||||||
rescue Mastodon::HostValidationError
|
rescue Mastodon::HostValidationError
|
||||||
# Do not retry
|
# Do not retry
|
||||||
|
rescue => e
|
||||||
|
limit_backtrace_and_raise(e)
|
||||||
ensure
|
ensure
|
||||||
socket = Thread.current[:statsd_socket]
|
socket = Thread.current[:statsd_socket]
|
||||||
socket&.close
|
socket&.close
|
||||||
Thread.current[:statsd_socket] = nil
|
Thread.current[:statsd_socket] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def limit_backtrace_and_raise(e)
|
||||||
|
e.set_backtrace(e.backtrace.first(BACKTRACE_LIMIT))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ class RelationshipFilter
|
||||||
scope = scope_for('relationship', params['relationship'].to_s.strip)
|
scope = scope_for('relationship', params['relationship'].to_s.strip)
|
||||||
|
|
||||||
params.each do |key, value|
|
params.each do |key, value|
|
||||||
next if key.to_s == 'page'
|
next if %w(relationship page).include?(key)
|
||||||
|
|
||||||
scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
|
scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class RemoteFollow
|
class RemoteFollow
|
||||||
include ActiveModel::Validations
|
include ActiveModel::Validations
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
include WebfingerHelper
|
||||||
|
|
||||||
attr_accessor :acct, :addressable_template
|
attr_accessor :acct, :addressable_template
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ class RemoteFollow
|
||||||
end
|
end
|
||||||
|
|
||||||
def acct_resource
|
def acct_resource
|
||||||
@acct_resource ||= Goldfinger.finger("acct:#{acct}")
|
@acct_resource ||= webfinger!("acct:#{acct}")
|
||||||
rescue Goldfinger::Error, HTTP::ConnectionError
|
rescue Goldfinger::Error, HTTP::ConnectionError
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -203,14 +203,6 @@ class Status < ApplicationRecord
|
||||||
preview_cards.first
|
preview_cards.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def title
|
|
||||||
if destroyed?
|
|
||||||
"#{account.acct} deleted status"
|
|
||||||
else
|
|
||||||
reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hidden?
|
def hidden?
|
||||||
!distributable?
|
!distributable?
|
||||||
end
|
end
|
||||||
|
@ -342,7 +334,7 @@ class Status < ApplicationRecord
|
||||||
query = timeline_scope(local_only)
|
query = timeline_scope(local_only)
|
||||||
query = query.without_replies unless Setting.show_replies_in_public_timelines
|
query = query.without_replies unless Setting.show_replies_in_public_timelines
|
||||||
|
|
||||||
apply_timeline_filters(query, account, local_only)
|
apply_timeline_filters(query, account, [:local, true].include?(local_only))
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_tag_timeline(tag, account = nil, local_only = false)
|
def as_tag_timeline(tag, account = nil, local_only = false)
|
||||||
|
@ -434,8 +426,15 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def timeline_scope(local_only = false)
|
def timeline_scope(scope = false)
|
||||||
starting_scope = local_only ? Status.local : Status
|
starting_scope = case scope
|
||||||
|
when :local, true
|
||||||
|
Status.local
|
||||||
|
when :remote
|
||||||
|
Status.remote
|
||||||
|
else
|
||||||
|
Status
|
||||||
|
end
|
||||||
starting_scope = starting_scope.with_public_visibility
|
starting_scope = starting_scope.with_public_visibility
|
||||||
if Setting.show_reblogs_in_public_timelines
|
if Setting.show_reblogs_in_public_timelines
|
||||||
starting_scope
|
starting_scope
|
||||||
|
|
|
@ -4,7 +4,7 @@ class OEmbedSerializer < ActiveModel::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
include ActionView::Helpers::TagHelper
|
include ActionView::Helpers::TagHelper
|
||||||
|
|
||||||
attributes :type, :version, :title, :author_name,
|
attributes :type, :version, :author_name,
|
||||||
:author_url, :provider_name, :provider_url,
|
:author_url, :provider_name, :provider_url,
|
||||||
:cache_age, :html, :width, :height
|
:cache_age, :html, :width, :height
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RSS::AccountSerializer
|
class RSS::AccountSerializer < RSS::Serializer
|
||||||
include ActionView::Helpers::NumberHelper
|
include ActionView::Helpers::NumberHelper
|
||||||
include AccountsHelper
|
include AccountsHelper
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
@ -17,18 +17,7 @@ class RSS::AccountSerializer
|
||||||
builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?
|
builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?
|
||||||
builder.cover(full_asset_url(account.header.url(:original))) if account.header?
|
builder.cover(full_asset_url(account.header.url(:original))) if account.header?
|
||||||
|
|
||||||
statuses.each do |status|
|
render_statuses(builder, statuses)
|
||||||
builder.item do |item|
|
|
||||||
item.title(status.title)
|
|
||||||
.link(ActivityPub::TagManager.instance.url_for(status))
|
|
||||||
.pub_date(status.created_at)
|
|
||||||
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
|
|
||||||
|
|
||||||
status.media_attachments.each do |media|
|
|
||||||
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.to_xml
|
builder.to_xml
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RSS::TagSerializer
|
class RSS::TagSerializer < RSS::Serializer
|
||||||
include ActionView::Helpers::NumberHelper
|
include ActionView::Helpers::NumberHelper
|
||||||
include ActionView::Helpers::SanitizeHelper
|
include ActionView::Helpers::SanitizeHelper
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
@ -14,18 +14,7 @@ class RSS::TagSerializer
|
||||||
.logo(full_pack_url('media/images/logo.svg'))
|
.logo(full_pack_url('media/images/logo.svg'))
|
||||||
.accent_color('2b90d9')
|
.accent_color('2b90d9')
|
||||||
|
|
||||||
statuses.each do |status|
|
render_statuses(builder, statuses)
|
||||||
builder.item do |item|
|
|
||||||
item.title(status.title)
|
|
||||||
.link(ActivityPub::TagManager.instance.url_for(status))
|
|
||||||
.pub_date(status.created_at)
|
|
||||||
.description(status.spoiler_text.presence || Formatter.instance.format(status).to_str)
|
|
||||||
|
|
||||||
status.media_attachments.each do |media|
|
|
||||||
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.to_xml
|
builder.to_xml
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class ActivityPub::FetchRemoteAccountService < BaseService
|
class ActivityPub::FetchRemoteAccountService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
include DomainControlHelper
|
include DomainControlHelper
|
||||||
|
include WebfingerHelper
|
||||||
|
|
||||||
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
||||||
|
|
||||||
|
@ -35,12 +36,12 @@ class ActivityPub::FetchRemoteAccountService < BaseService
|
||||||
private
|
private
|
||||||
|
|
||||||
def verified_webfinger?
|
def verified_webfinger?
|
||||||
webfinger = Goldfinger.finger("acct:#{@username}@#{@domain}")
|
webfinger = webfinger!("acct:#{@username}@#{@domain}")
|
||||||
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
|
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
|
||||||
|
|
||||||
return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
|
return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
|
||||||
|
|
||||||
webfinger = Goldfinger.finger("acct:#{confirmed_username}@#{confirmed_domain}")
|
webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
|
||||||
@username, @domain = split_acct(webfinger.subject)
|
@username, @domain = split_acct(webfinger.subject)
|
||||||
self_reference = webfinger.link('self')
|
self_reference = webfinger.link('self')
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class ResolveAccountService < BaseService
|
class ResolveAccountService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
include DomainControlHelper
|
include DomainControlHelper
|
||||||
|
include WebfingerHelper
|
||||||
|
|
||||||
class WebfingerRedirectError < StandardError; end
|
class WebfingerRedirectError < StandardError; end
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ class ResolveAccountService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_webfinger!(uri, redirected = false)
|
def process_webfinger!(uri, redirected = false)
|
||||||
@webfinger = Goldfinger.finger("acct:#{uri}")
|
@webfinger = webfinger!("acct:#{uri}")
|
||||||
confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
|
confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
|
||||||
|
|
||||||
if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
|
if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
- target_account = reports.first.target_account
|
- target_account = reports.first.target_account
|
||||||
.report-card
|
.report-card
|
||||||
.report-card__profile
|
.report-card__profile
|
||||||
= account_link_to target_account, '', size: 36, path: admin_account_path(target_account.id)
|
= account_link_to target_account, '', path: admin_account_path(target_account.id)
|
||||||
.report-card__profile__stats
|
.report-card__profile__stats
|
||||||
= link_to t('admin.reports.account.notes', count: target_account.targeted_moderation_notes.count), admin_account_path(target_account.id)
|
= link_to t('admin.reports.account.notes', count: target_account.targeted_moderation_notes.count), admin_account_path(target_account.id)
|
||||||
%br/
|
%br/
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
|
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous'
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
|
|
||||||
|
= stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style'
|
||||||
|
|
||||||
= yield :header_tags
|
= yield :header_tags
|
||||||
|
|
||||||
-# These must come after :header_tags to ensure our initial state has been defined.
|
-# These must come after :header_tags to ensure our initial state has been defined.
|
||||||
|
|
|
@ -18,3 +18,4 @@
|
||||||
|
|
||||||
%td
|
%td
|
||||||
= table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url
|
= table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url
|
||||||
|
= table_link_to 'trash', t('identity_proofs.remove'), settings_identity_proof_path(proof), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||||
|
|
|
@ -52,13 +52,9 @@ class ActivityPub::DeliveryWorker
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
|
||||||
light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
|
light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD)
|
||||||
.with_cool_off_time(STOPLIGHT_COOLDOWN)
|
.with_cool_off_time(STOPLIGHT_COOLDOWN)
|
||||||
.run
|
.run
|
||||||
rescue Stoplight::Error::RedLight => e
|
|
||||||
raise e.class, e.message, e.backtrace.first(3)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def failure_tracker
|
def failure_tracker
|
||||||
|
|
|
@ -106,6 +106,7 @@ module Mastodon
|
||||||
:tr,
|
:tr,
|
||||||
:uk,
|
:uk,
|
||||||
:ur,
|
:ur,
|
||||||
|
:vi,
|
||||||
:'zh-CN',
|
:'zh-CN',
|
||||||
:'zh-HK',
|
:'zh-HK',
|
||||||
:'zh-TW',
|
:'zh-TW',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
lock '3.12.1'
|
lock '3.14.0'
|
||||||
|
|
||||||
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
||||||
set :branch, ENV.fetch('BRANCH', 'master')
|
set :branch, ENV.fetch('BRANCH', 'master')
|
||||||
|
@ -12,3 +12,21 @@ set :migration_role, :app
|
||||||
|
|
||||||
append :linked_files, '.env.production', 'public/robots.txt'
|
append :linked_files, '.env.production', 'public/robots.txt'
|
||||||
append :linked_dirs, 'vendor/bundle', 'node_modules', 'public/system'
|
append :linked_dirs, 'vendor/bundle', 'node_modules', 'public/system'
|
||||||
|
|
||||||
|
namespace :systemd do
|
||||||
|
%i[sidekiq streaming web].each do |service|
|
||||||
|
%i[reload restart status].each do |action|
|
||||||
|
desc "Perform a #{action} on #{service} service"
|
||||||
|
task "#{service}:#{action}".to_sym do
|
||||||
|
on roles(:app) do
|
||||||
|
# runs e.g. "sudo restart mastodon-sidekiq.service"
|
||||||
|
sudo :systemctl, action, "#{fetch(:application)}-#{service}.service"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
after 'deploy:publishing', 'systemd:web:reload'
|
||||||
|
after 'deploy:publishing', 'systemd:sidekiq:restart'
|
||||||
|
after 'deploy:publishing', 'systemd:streaming:restart'
|
||||||
|
|
|
@ -34,7 +34,7 @@ if Rails.env.production?
|
||||||
p.script_src :self, assets_host
|
p.script_src :self, assets_host
|
||||||
p.font_src :self, assets_host
|
p.font_src :self, assets_host
|
||||||
p.img_src :self, :data, :blob, *data_hosts
|
p.img_src :self, :data, :blob, *data_hosts
|
||||||
p.style_src :self, :unsafe_inline, assets_host
|
p.style_src :self, assets_host
|
||||||
p.media_src :self, :data, *data_hosts
|
p.media_src :self, :data, *data_hosts
|
||||||
p.frame_src :self, :https
|
p.frame_src :self, :https
|
||||||
p.child_src :self, :blob, assets_host
|
p.child_src :self, :blob, assets_host
|
||||||
|
@ -48,3 +48,8 @@ end
|
||||||
# For further information see the following documentation:
|
# For further information see the following documentation:
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
||||||
# Rails.application.config.content_security_policy_report_only = true
|
# Rails.application.config.content_security_policy_report_only = true
|
||||||
|
|
||||||
|
PgHero::HomeController.content_security_policy do |p|
|
||||||
|
p.script_src :self, :unsafe_inline, assets_host
|
||||||
|
p.style_src :self, :unsafe_inline, assets_host
|
||||||
|
end
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
config.x.http_client_proxy = {}
|
config.x.http_client_proxy = {}
|
||||||
|
|
||||||
if ENV['http_proxy'].present?
|
if ENV['http_proxy'].present?
|
||||||
proxy = URI.parse(ENV['http_proxy'])
|
proxy = URI.parse(ENV['http_proxy'])
|
||||||
|
|
||||||
raise "Unsupported proxy type: #{proxy.scheme}" unless %w(http https).include? proxy.scheme
|
raise "Unsupported proxy type: #{proxy.scheme}" unless %w(http https).include? proxy.scheme
|
||||||
raise "No proxy host" unless proxy.host
|
raise "No proxy host" unless proxy.host
|
||||||
|
|
||||||
host = proxy.host
|
host = proxy.host
|
||||||
host = host[1...-1] if host[0] == '[' # for IPv6 address
|
host = host[1...-1] if host[0] == '[' # for IPv6 address
|
||||||
config.x.http_client_proxy[:proxy] = { proxy_address: host, proxy_port: proxy.port, proxy_username: proxy.user, proxy_password: proxy.password }.compact
|
|
||||||
|
config.x.http_client_proxy[:proxy] = {
|
||||||
|
proxy_address: host,
|
||||||
|
proxy_port: proxy.port,
|
||||||
|
proxy_username: proxy.user,
|
||||||
|
proxy_password: proxy.password,
|
||||||
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
config.x.access_to_hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
|
config.x.access_to_hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
|
||||||
end
|
end
|
||||||
|
|
||||||
module Goldfinger
|
|
||||||
def self.finger(uri, opts = {})
|
|
||||||
to_hidden = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
|
||||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && to_hidden
|
|
||||||
opts = { ssl: !to_hidden, headers: {} }.merge(Rails.configuration.x.http_client_proxy).merge(opts)
|
|
||||||
opts[:headers]['User-Agent'] ||= Mastodon::Version.user_agent
|
|
||||||
Goldfinger::Client.new(uri, opts).finger
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -19,4 +19,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
inflect.acronym 'ActivityStreams'
|
inflect.acronym 'ActivityStreams'
|
||||||
inflect.acronym 'JsonLd'
|
inflect.acronym 'JsonLd'
|
||||||
inflect.acronym 'NodeInfo'
|
inflect.acronym 'NodeInfo'
|
||||||
|
|
||||||
|
inflect.singular 'data', 'data'
|
||||||
end
|
end
|
||||||
|
|
|
@ -858,12 +858,14 @@ en:
|
||||||
invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters
|
invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters
|
||||||
verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase.
|
verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase.
|
||||||
wrong_user: Cannot create a proof for %{proving} while logged in as %{current}. Log in as %{proving} and try again.
|
wrong_user: Cannot create a proof for %{proving} while logged in as %{current}. Log in as %{proving} and try again.
|
||||||
explanation_html: Here you can cryptographically connect your other identities, such as a Keybase profile. This lets other people send you encrypted messages and trust content you send them.
|
explanation_html: Here you can cryptographically connect your other identities from other platforms, such as Keybase. This lets other people send you encrypted messages on those platforms and allows them to trust that the content you send them comes from you.
|
||||||
i_am_html: I am %{username} on %{service}.
|
i_am_html: I am %{username} on %{service}.
|
||||||
identity: Identity
|
identity: Identity
|
||||||
inactive: Inactive
|
inactive: Inactive
|
||||||
publicize_checkbox: 'And toot this:'
|
publicize_checkbox: 'And toot this:'
|
||||||
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
|
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
|
||||||
|
remove: Remove proof from account
|
||||||
|
removed: Successfully removed proof from account
|
||||||
status: Verification status
|
status: Verification status
|
||||||
view_proof: View proof
|
view_proof: View proof
|
||||||
imports:
|
imports:
|
||||||
|
|
|
@ -38,4 +38,4 @@ databases:
|
||||||
# aws_secret_access_key: ...
|
# aws_secret_access_key: ...
|
||||||
# aws_region: us-east-1
|
# aws_region: us-east-1
|
||||||
|
|
||||||
override_csp: true
|
override_csp: false
|
||||||
|
|
|
@ -130,7 +130,7 @@ Rails.application.routes.draw do
|
||||||
resource :confirmation, only: [:new, :create]
|
resource :confirmation, only: [:new, :create]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :identity_proofs, only: [:index, :show, :new, :create, :update]
|
resources :identity_proofs, only: [:index, :new, :create, :destroy]
|
||||||
|
|
||||||
resources :applications, except: [:edit] do
|
resources :applications, except: [:edit] do
|
||||||
member do
|
member do
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class AddInviteIdToUsers < ActiveRecord::Migration[5.1]
|
class AddInviteIdToUsers < ActiveRecord::Migration[5.1]
|
||||||
def change
|
def change
|
||||||
add_reference :users, :invite, null: true, default: nil, foreign_key: { on_delete: :nullify }, index: false
|
safety_assured { add_reference :users, :invite, null: true, default: nil, foreign_key: { on_delete: :nullify }, index: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class AddAssignedAccountIdToReports < ActiveRecord::Migration[5.1]
|
class AddAssignedAccountIdToReports < ActiveRecord::Migration[5.1]
|
||||||
def change
|
def change
|
||||||
add_reference :reports, :assigned_account, null: true, default: nil, foreign_key: { on_delete: :nullify, to_table: :accounts }, index: false
|
safety_assured { add_reference :reports, :assigned_account, null: true, default: nil, foreign_key: { on_delete: :nullify, to_table: :accounts }, index: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
class AddAccessTokenIdToWebPushSubscriptions < ActiveRecord::Migration[5.2]
|
class AddAccessTokenIdToWebPushSubscriptions < ActiveRecord::Migration[5.2]
|
||||||
def change
|
def change
|
||||||
|
safety_assured do
|
||||||
add_reference :web_push_subscriptions, :access_token, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :oauth_access_tokens }, index: false
|
add_reference :web_push_subscriptions, :access_token, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :oauth_access_tokens }, index: false
|
||||||
add_reference :web_push_subscriptions, :user, null: true, default: nil, foreign_key: { on_delete: :cascade }, index: false
|
add_reference :web_push_subscriptions, :user, null: true, default: nil, foreign_key: { on_delete: :cascade }, index: false
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ class AddCreatedByApplicationIdToUsers < ActiveRecord::Migration[5.2]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def change
|
def change
|
||||||
add_reference :users, :created_by_application, foreign_key: { to_table: 'oauth_applications', on_delete: :nullify }, index: false
|
safety_assured { add_reference :users, :created_by_application, foreign_key: { to_table: 'oauth_applications', on_delete: :nullify }, index: false }
|
||||||
add_index :users, :created_by_application_id, algorithm: :concurrently
|
add_index :users, :created_by_application_id, algorithm: :concurrently
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ class AddScheduledStatusIdToMediaAttachments < ActiveRecord::Migration[5.2]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def change
|
def change
|
||||||
add_reference :media_attachments, :scheduled_status, foreign_key: { on_delete: :nullify }, index: false
|
safety_assured { add_reference :media_attachments, :scheduled_status, foreign_key: { on_delete: :nullify }, index: false }
|
||||||
add_index :media_attachments, :scheduled_status_id, algorithm: :concurrently
|
add_index :media_attachments, :scheduled_status_id, algorithm: :concurrently
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class AddParentIdToEmailDomainBlocks < ActiveRecord::Migration[5.2]
|
class AddParentIdToEmailDomainBlocks < ActiveRecord::Migration[5.2]
|
||||||
def change
|
def change
|
||||||
add_reference :email_domain_blocks, :parent, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :email_domain_blocks }, index: false
|
safety_assured { add_reference :email_domain_blocks, :parent, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :email_domain_blocks }, index: false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
class ResetUniqueJobsLocks < ActiveRecord::Migration[5.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
# We do this to clean up unique job digests that were not properly
|
||||||
|
# disposed of prior to https://github.com/tootsuite/mastodon/pull/13361
|
||||||
|
|
||||||
|
SidekiqUniqueJobs::Digests.delete_by_pattern('*', count: SidekiqUniqueJobs::Digests.count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down; end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2020_04_17_125749) do
|
ActiveRecord::Schema.define(version: 2020_05_08_212852) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
|
@ -144,7 +144,14 @@ module Mastodon
|
||||||
begin
|
begin
|
||||||
size = File.size(path)
|
size = File.size(path)
|
||||||
|
|
||||||
File.delete(path) unless options[:dry_run]
|
unless options[:dry_run]
|
||||||
|
File.delete(path)
|
||||||
|
begin
|
||||||
|
FileUtils.rmdir(File.dirname(path), parents: true)
|
||||||
|
rescue Errno::ENOTEMPTY
|
||||||
|
# OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
reclaimed_bytes += size
|
reclaimed_bytes += size
|
||||||
removed += 1
|
removed += 1
|
||||||
|
|
|
@ -121,7 +121,7 @@ module Mastodon
|
||||||
FileUtils.mv(previous_path, upgraded_path)
|
FileUtils.mv(previous_path, upgraded_path)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
FileUtils.rmdir(previous_path, parents: true)
|
FileUtils.rmdir(File.dirname(previous_path), parents: true)
|
||||||
rescue Errno::ENOTEMPTY
|
rescue Errno::ENOTEMPTY
|
||||||
# OK
|
# OK
|
||||||
end
|
end
|
||||||
|
@ -131,7 +131,7 @@ module Mastodon
|
||||||
|
|
||||||
unless dry_run?
|
unless dry_run?
|
||||||
begin
|
begin
|
||||||
FileUtils.rmdir(upgraded_path, parents: true)
|
FileUtils.rmdir(File.dirname(upgraded_path), parents: true)
|
||||||
rescue Errno::ENOTEMPTY
|
rescue Errno::ENOTEMPTY
|
||||||
# OK
|
# OK
|
||||||
end
|
end
|
||||||
|
|
14
package.json
14
package.json
|
@ -60,12 +60,12 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.9.6",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@babel/plugin-proposal-decorators": "^7.8.3",
|
"@babel/plugin-proposal-decorators": "^7.8.3",
|
||||||
"@babel/plugin-transform-react-inline-elements": "^7.9.0",
|
"@babel/plugin-transform-react-inline-elements": "^7.9.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.9.0",
|
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||||
"@babel/preset-env": "^7.9.0",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"@babel/preset-react": "^7.9.4",
|
"@babel/preset-react": "^7.9.4",
|
||||||
"@babel/runtime": "^7.8.4",
|
"@babel/runtime": "^7.8.4",
|
||||||
"@clusterws/cws": "^0.17.3",
|
"@clusterws/cws": "^0.17.3",
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"babel-plugin-preval": "^5.0.0",
|
"babel-plugin-preval": "^5.0.0",
|
||||||
"babel-plugin-react-intl": "^3.4.1",
|
"babel-plugin-react-intl": "^6.2.0",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"blurhash": "^1.1.3",
|
"blurhash": "^1.1.3",
|
||||||
|
@ -163,11 +163,11 @@
|
||||||
"tesseract.js": "^2.0.0-alpha.16",
|
"tesseract.js": "^2.0.0-alpha.16",
|
||||||
"throng": "^4.0.0",
|
"throng": "^4.0.0",
|
||||||
"tiny-queue": "^0.2.1",
|
"tiny-queue": "^0.2.1",
|
||||||
"uuid": "^7.0.3",
|
"uuid": "^8.0.0",
|
||||||
"wavesurfer.js": "^3.3.1",
|
"wavesurfer.js": "^3.3.3",
|
||||||
"webpack": "^4.42.1",
|
"webpack": "^4.42.1",
|
||||||
"webpack-assets-manifest": "^3.1.1",
|
"webpack-assets-manifest": "^3.1.1",
|
||||||
"webpack-bundle-analyzer": "^3.6.1",
|
"webpack-bundle-analyzer": "^3.7.0",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^4.2.1",
|
||||||
"wicg-inert": "^3.0.2"
|
"wicg-inert": "^3.0.2"
|
||||||
|
@ -182,7 +182,7 @@
|
||||||
"eslint-plugin-jsx-a11y": "~6.2.3",
|
"eslint-plugin-jsx-a11y": "~6.2.3",
|
||||||
"eslint-plugin-promise": "~4.2.1",
|
"eslint-plugin-promise": "~4.2.1",
|
||||||
"eslint-plugin-react": "~7.19.0",
|
"eslint-plugin-react": "~7.19.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^25.4.0",
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"react-intl-translations-manager": "^5.0.3",
|
"react-intl-translations-manager": "^5.0.3",
|
||||||
"react-test-renderer": "^16.13.0",
|
"react-test-renderer": "^16.13.0",
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
[inert] {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[inert], [inert] * {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
|
@ -3,108 +3,608 @@ require 'rails_helper'
|
||||||
RSpec.describe AccountsController, type: :controller do
|
RSpec.describe AccountsController, type: :controller do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:alice) { Fabricate(:account, username: 'alice', user: Fabricate(:user)) }
|
let(:account) { Fabricate(:user).account }
|
||||||
let(:eve) { Fabricate(:user) }
|
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
let!(:status1) { Status.create!(account: alice, text: 'Hello world') }
|
let(:format) { 'html' }
|
||||||
let!(:status2) { Status.create!(account: alice, text: 'Boop', thread: status1) }
|
|
||||||
let!(:status3) { Status.create!(account: alice, text: 'Picture!') }
|
|
||||||
let!(:status4) { Status.create!(account: alice, text: 'Mentioning @alice') }
|
|
||||||
let!(:status5) { Status.create!(account: alice, text: 'Kitsune') }
|
|
||||||
let!(:status6) { Status.create!(account: alice, text: 'Neko') }
|
|
||||||
let!(:status7) { Status.create!(account: alice, text: 'Tanuki') }
|
|
||||||
|
|
||||||
let!(:status_pin1) { StatusPin.create!(account: alice, status: status5, created_at: 5.days.ago) }
|
let!(:status) { Fabricate(:status, account: account) }
|
||||||
let!(:status_pin2) { StatusPin.create!(account: alice, status: status6, created_at: 2.years.ago) }
|
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
||||||
let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) }
|
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
|
||||||
|
let!(:status_media) { Fabricate(:status, account: account) }
|
||||||
|
let!(:status_pinned) { Fabricate(:status, account: account) }
|
||||||
|
let!(:status_private) { Fabricate(:status, account: account, visibility: :private) }
|
||||||
|
let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) }
|
||||||
|
let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
alice.block!(eve.account)
|
status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
|
||||||
status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg'))
|
account.pinned_statuses << status_pinned
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'responses' do
|
shared_examples 'preliminary checks' do
|
||||||
|
context 'when account is not approved' do
|
||||||
before do
|
before do
|
||||||
sign_in(current_user) if defined? current_user
|
account.user.update(approved: false)
|
||||||
get :show, params: {
|
|
||||||
username: alice.username,
|
|
||||||
max_id: (max_id if defined? max_id),
|
|
||||||
since_id: (since_id if defined? since_id),
|
|
||||||
current_user: (current_user if defined? current_user),
|
|
||||||
}, format: format
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns @account' do
|
it 'returns http not found' do
|
||||||
expect(assigns(:account)).to eq alice
|
get :show, params: { username: account.username, format: format }
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when account is suspended' do
|
||||||
|
before do
|
||||||
|
account.suspend!
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http gone' do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
expect(response).to have_http_status(410)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as HTML' do
|
||||||
|
let(:format) { 'html' }
|
||||||
|
|
||||||
|
it_behaves_like 'preliminary checks'
|
||||||
|
|
||||||
|
shared_examples 'common response characteristics' do
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns Link header' do
|
||||||
|
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders show template' do
|
||||||
|
expect(response).to render_template(:show)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context do
|
||||||
|
before do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reblog' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders pinned status' do
|
||||||
|
expect(response.body).to include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when signed-in' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user follows account' do
|
||||||
|
before do
|
||||||
|
user.account.follow!(account)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is blocked' do
|
||||||
|
before do
|
||||||
|
account.block!(user.account)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders unavailable message' do
|
||||||
|
expect(response.body).to include(I18n.t('accounts.unavailable'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render status with media' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with replies' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reblog' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reply to someone else' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with media' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:media_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with tag' do
|
||||||
|
let(:tag) { Fabricate(:tag) }
|
||||||
|
|
||||||
|
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||||
|
status_tag.tags << tag
|
||||||
|
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render status with media' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render pinned status' do
|
||||||
|
expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with tag' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as JSON' do
|
||||||
|
let(:authorized_fetch_mode) { false }
|
||||||
|
let(:format) { 'json' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'preliminary checks'
|
||||||
|
|
||||||
|
context do
|
||||||
|
before do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns correct format' do
|
it 'returns application/activity+json' do
|
||||||
expect(response.content_type).to eq content_type
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'activitystreams2' do
|
it 'returns public Cache-Control header' do
|
||||||
let(:format) { 'json' }
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
let(:content_type) { 'application/activity+json' }
|
|
||||||
|
|
||||||
include_examples 'responses'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'html' do
|
it 'renders account' do
|
||||||
let(:format) { nil }
|
json = body_as_json
|
||||||
let(:content_type) { 'text/html' }
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
|
||||||
shared_examples 'responsed statuses' do
|
|
||||||
it 'assigns @pinned_statuses' do
|
|
||||||
pinned_statuses = assigns(:pinned_statuses).to_a
|
|
||||||
expect(pinned_statuses.size).to eq expected_pinned_statuses.size
|
|
||||||
pinned_statuses.each.zip(expected_pinned_statuses.each) do |pinned_status, expected_pinned_status|
|
|
||||||
expect(pinned_status).to eq expected_pinned_status
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns @statuses' do
|
context 'in authorized fetch mode' do
|
||||||
statuses = assigns(:statuses).to_a
|
let(:authorized_fetch_mode) { true }
|
||||||
expect(statuses.size).to eq expected_statuses.size
|
|
||||||
statuses.each.zip(expected_statuses.each) do |status, expected_status|
|
it 'returns http success' do
|
||||||
expect(status).to eq expected_status
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns Vary header with Signature' do
|
||||||
|
expect(response.headers['Vary']).to include 'Signature'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders bare minimum account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey)
|
||||||
|
expect(json).to_not include(:name, :summary)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
include_examples 'responses'
|
context 'when signed in' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
context 'with anonymous visitor' do
|
before do
|
||||||
context 'without since_id nor max_id' do
|
sign_in(user)
|
||||||
let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
|
get :show, params: { username: account.username, format: format }
|
||||||
let(:expected_pinned_statuses) { [status7, status5, status6] }
|
|
||||||
|
|
||||||
include_examples 'responsed statuses'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with since_id nor max_id' do
|
it 'returns http success' do
|
||||||
let(:max_id) { status4.id }
|
expect(response).to have_http_status(200)
|
||||||
let(:since_id) { status1.id }
|
end
|
||||||
let(:expected_statuses) { [status3, status2] }
|
|
||||||
let(:expected_pinned_statuses) { [] }
|
|
||||||
|
|
||||||
include_examples 'responsed statuses'
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with blocked visitor' do
|
context 'with signature' do
|
||||||
let(:current_user) { eve }
|
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||||
|
|
||||||
context 'without since_id nor max_id' do
|
before do
|
||||||
let(:expected_statuses) { [] }
|
allow(controller).to receive(:signed_request_account).and_return(remote_account)
|
||||||
let(:expected_pinned_statuses) { [] }
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
include_examples 'responsed statuses'
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in authorized fetch mode' do
|
||||||
|
let(:authorized_fetch_mode) { true }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns application/activity+json' do
|
||||||
|
expect(response.content_type).to eq 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns private Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'private'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns Vary header with Signature' do
|
||||||
|
expect(response.headers['Vary']).to include 'Signature'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders account' do
|
||||||
|
json = body_as_json
|
||||||
|
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as RSS' do
|
||||||
|
let(:format) { 'rss' }
|
||||||
|
|
||||||
|
it_behaves_like 'preliminary checks'
|
||||||
|
|
||||||
|
shared_examples 'common response characteristics' do
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns public Cache-Control header' do
|
||||||
|
expect(response.headers['Cache-Control']).to include 'public'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context do
|
||||||
|
before do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with replies' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'renders public status' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders self-reply' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders reply to someone else' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with media' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:media_requested?).and_return(true)
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with media' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with tag' do
|
||||||
|
let(:tag) { Fabricate(:tag) }
|
||||||
|
|
||||||
|
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||||
|
status_tag.tags << tag
|
||||||
|
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'common response characteristics'
|
||||||
|
|
||||||
|
it 'does not render public status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render self-reply' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render status with media' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reblog' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render private status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render direct status' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render reply to someone else' do
|
||||||
|
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders status with tag' do
|
||||||
|
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,5 +36,28 @@ describe Api::V1::Accounts::FollowerAccountsController do
|
||||||
expect(body_as_json.size).to eq 1
|
expect(body_as_json.size).to eq 1
|
||||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is blocked' do
|
||||||
|
before do
|
||||||
|
account.block!(user.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hides results' do
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
expect(body_as_json.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is the account owner' do
|
||||||
|
let(:user) { Fabricate(:user, account: account) }
|
||||||
|
|
||||||
|
it 'returns all accounts, including muted accounts' do
|
||||||
|
user.account.mute!(bob)
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
|
||||||
|
expect(body_as_json.size).to eq 2
|
||||||
|
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,5 +36,28 @@ describe Api::V1::Accounts::FollowingAccountsController do
|
||||||
expect(body_as_json.size).to eq 1
|
expect(body_as_json.size).to eq 1
|
||||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is blocked' do
|
||||||
|
before do
|
||||||
|
account.block!(user.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hides results' do
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
expect(body_as_json.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when requesting user is the account owner' do
|
||||||
|
let(:user) { Fabricate(:user, account: account) }
|
||||||
|
|
||||||
|
it 'returns all accounts, including muted accounts' do
|
||||||
|
user.account.mute!(bob)
|
||||||
|
get :index, params: { account_id: account.id, limit: 2 }
|
||||||
|
|
||||||
|
expect(body_as_json.size).to eq 2
|
||||||
|
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe RemoteFollowController do
|
||||||
context 'when webfinger values are wrong' do
|
context 'when webfinger values are wrong' do
|
||||||
it 'renders new when redirect url is nil' do
|
it 'renders new when redirect url is nil' do
|
||||||
resource_with_nil_link = double(link: nil)
|
resource_with_nil_link = double(link: nil)
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_nil_link)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_nil_link)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
@ -45,7 +45,7 @@ describe RemoteFollowController do
|
||||||
it 'renders new when template is nil' do
|
it 'renders new when template is nil' do
|
||||||
link_with_nil_template = double(template: nil)
|
link_with_nil_template = double(template: nil)
|
||||||
resource_with_link = double(link: link_with_nil_template)
|
resource_with_link = double(link: link_with_nil_template)
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_link)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
@ -57,7 +57,7 @@ describe RemoteFollowController do
|
||||||
before do
|
before do
|
||||||
link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
|
link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
|
||||||
resource_with_link = double(link: link_with_template)
|
resource_with_link = double(link: link_with_template)
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_link)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ describe RemoteFollowController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders new with error when goldfinger fails' do
|
it 'renders new with error when goldfinger fails' do
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_raise(Goldfinger::Error)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Goldfinger::Error)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
@ -87,7 +87,7 @@ describe RemoteFollowController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders new when occur HTTP::ConnectionError' do
|
it 'renders new when occur HTTP::ConnectionError' do
|
||||||
allow(Goldfinger).to receive(:finger).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
|
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
|
||||||
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
|
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
|
||||||
|
|
||||||
expect(response).to render_template(:new)
|
expect(response).to render_template(:new)
|
||||||
|
|
|
@ -151,7 +151,7 @@ describe Settings::IdentityProofsController do
|
||||||
@proof1 = Fabricate(:account_identity_proof, account: user.account)
|
@proof1 = Fabricate(:account_identity_proof, account: user.account)
|
||||||
@proof2 = Fabricate(:account_identity_proof, account: user.account)
|
@proof2 = Fabricate(:account_identity_proof, account: user.account)
|
||||||
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
|
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
|
||||||
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) { }
|
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the first proof username on the page' do
|
it 'has the first proof username on the page' do
|
||||||
|
@ -165,4 +165,22 @@ describe Settings::IdentityProofsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'DELETE #destroy' do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
|
||||||
|
@proof1 = Fabricate(:account_identity_proof, account: user.account)
|
||||||
|
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
|
||||||
|
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
|
||||||
|
delete :destroy, params: { id: @proof1.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to :index' do
|
||||||
|
expect(response).to redirect_to settings_identity_proofs_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the proof' do
|
||||||
|
expect(AccountIdentityProof.where(id: @proof1.id).count).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe RSS::Serializer do
|
||||||
|
describe '#status_title' do
|
||||||
|
let(:text) { 'This is a toot' }
|
||||||
|
let(:spoiler) { '' }
|
||||||
|
let(:sensitive) { false }
|
||||||
|
let(:reblog) { nil }
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:status) { Fabricate(:status, account: account, text: text, spoiler_text: spoiler, sensitive: sensitive, reblog: reblog) }
|
||||||
|
|
||||||
|
subject { RSS::Serializer.new.send(:status_title, status) }
|
||||||
|
|
||||||
|
context 'if destroyed?' do
|
||||||
|
it 'returns "#{account.acct} deleted status"' do
|
||||||
|
status.destroy!
|
||||||
|
expect(subject).to eq "#{account.acct} deleted status"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with long text' do
|
||||||
|
let(:text) { "This toot's text is longer than the allowed number of characters" }
|
||||||
|
|
||||||
|
it 'truncates toot text appropriately' do
|
||||||
|
expect(subject).to eq "#{account.acct}: “This toot's text is longer tha…”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with long text with a newline' do
|
||||||
|
let(:text) { "This toot's text is longer\nthan the allowed number of characters" }
|
||||||
|
|
||||||
|
it 'truncates toot text appropriately' do
|
||||||
|
expect(subject).to eq "#{account.acct}: “This toot's text is longer…”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with a content warning' do
|
||||||
|
let(:spoiler) { 'long toot' }
|
||||||
|
|
||||||
|
it 'displays spoiler text instead of toot content' do
|
||||||
|
expect(subject).to eq "#{account.acct}: CW “long toot”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a toot with sensitive media' do
|
||||||
|
let(:sensitive) { true }
|
||||||
|
|
||||||
|
it 'displays that the media is sensitive' do
|
||||||
|
expect(subject).to eq "#{account.acct}: “This is a toot” (sensitive)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on a reblog' do
|
||||||
|
let(:reblog) { Fabricate(:status, text: 'This is a toot') }
|
||||||
|
|
||||||
|
it 'display that the toot is a reblog' do
|
||||||
|
expect(subject).to eq "#{account.acct} boosted #{reblog.account.acct}: “This is a toot”"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe RelationshipFilter do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
describe '#results' do
|
||||||
|
context 'when default params are used' do
|
||||||
|
let(:subject) do
|
||||||
|
RelationshipFilter.new(account, 'order' => 'active').results
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
add_following_account_with(last_status_at: 7.days.ago)
|
||||||
|
add_following_account_with(last_status_at: 1.day.ago)
|
||||||
|
add_following_account_with(last_status_at: 3.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns followings ordered by last activity' do
|
||||||
|
expected_result = account.following.eager_load(:account_stat).reorder(nil).by_recent_status
|
||||||
|
|
||||||
|
expect(subject).to eq expected_result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_following_account_with(last_status_at:)
|
||||||
|
following_account = Fabricate(:account)
|
||||||
|
Fabricate(:account_stat, account: following_account,
|
||||||
|
last_status_at: last_status_at,
|
||||||
|
statuses_count: 1,
|
||||||
|
following_count: 0,
|
||||||
|
followers_count: 0)
|
||||||
|
Fabricate(:follow, account: account, target_account: following_account).account
|
||||||
|
end
|
||||||
|
end
|
|
@ -82,35 +82,6 @@ RSpec.describe Status, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#title' do
|
|
||||||
# rubocop:disable Style/InterpolationCheck
|
|
||||||
|
|
||||||
let(:account) { subject.account }
|
|
||||||
|
|
||||||
context 'if destroyed?' do
|
|
||||||
it 'returns "#{account.acct} deleted status"' do
|
|
||||||
subject.destroy!
|
|
||||||
expect(subject.title).to eq "#{account.acct} deleted status"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'unless destroyed?' do
|
|
||||||
context 'if reblog?' do
|
|
||||||
it 'returns "#{account.acct} shared a status by #{reblog.account.acct}"' do
|
|
||||||
reblog = subject.reblog = other
|
|
||||||
expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'unless reblog?' do
|
|
||||||
it 'returns "New status by #{account.acct}"' do
|
|
||||||
subject.reblog = nil
|
|
||||||
expect(subject.title).to eq "New status by #{account.acct}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#hidden?' do
|
describe '#hidden?' do
|
||||||
context 'if private_visibility?' do
|
context 'if private_visibility?' do
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
|
@ -490,6 +461,33 @@ RSpec.describe Status, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a remote_only option set' do
|
||||||
|
let!(:local_account) { Fabricate(:account, domain: nil) }
|
||||||
|
let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
|
||||||
|
let!(:local_status) { Fabricate(:status, account: local_account) }
|
||||||
|
let!(:remote_status) { Fabricate(:status, account: remote_account) }
|
||||||
|
|
||||||
|
subject { Status.as_public_timeline(viewer, :remote) }
|
||||||
|
|
||||||
|
context 'without a viewer' do
|
||||||
|
let(:viewer) { nil }
|
||||||
|
|
||||||
|
it 'does not include local instances statuses' do
|
||||||
|
expect(subject).not_to include(local_status)
|
||||||
|
expect(subject).to include(remote_status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a viewer' do
|
||||||
|
let(:viewer) { Fabricate(:account, username: 'viewer') }
|
||||||
|
|
||||||
|
it 'does not include local instances statuses' do
|
||||||
|
expect(subject).not_to include(local_status)
|
||||||
|
expect(subject).to include(remote_status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'with an account passed in' do
|
describe 'with an account passed in' do
|
||||||
before do
|
before do
|
||||||
@account = Fabricate(:account)
|
@account = Fabricate(:account)
|
||||||
|
|
|
@ -266,6 +266,8 @@ const startWorker = (workerId) => {
|
||||||
'public:media',
|
'public:media',
|
||||||
'public:local',
|
'public:local',
|
||||||
'public:local:media',
|
'public:local:media',
|
||||||
|
'public:remote',
|
||||||
|
'public:remote:media',
|
||||||
'hashtag',
|
'hashtag',
|
||||||
'hashtag:local',
|
'hashtag:local',
|
||||||
];
|
];
|
||||||
|
@ -297,6 +299,7 @@ const startWorker = (workerId) => {
|
||||||
const PUBLIC_ENDPOINTS = [
|
const PUBLIC_ENDPOINTS = [
|
||||||
'/api/v1/streaming/public',
|
'/api/v1/streaming/public',
|
||||||
'/api/v1/streaming/public/local',
|
'/api/v1/streaming/public/local',
|
||||||
|
'/api/v1/streaming/public/remote',
|
||||||
'/api/v1/streaming/hashtag',
|
'/api/v1/streaming/hashtag',
|
||||||
'/api/v1/streaming/hashtag/local',
|
'/api/v1/streaming/hashtag/local',
|
||||||
];
|
];
|
||||||
|
@ -541,6 +544,13 @@ const startWorker = (workerId) => {
|
||||||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true);
|
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/api/v1/streaming/public/remote', (req, res) => {
|
||||||
|
const onlyMedia = req.query.only_media === '1' || req.query.only_media === 'true';
|
||||||
|
const channel = onlyMedia ? 'timeline:public:remote:media' : 'timeline:public:remote';
|
||||||
|
|
||||||
|
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true);
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/v1/streaming/direct', (req, res) => {
|
app.get('/api/v1/streaming/direct', (req, res) => {
|
||||||
const channel = `timeline:direct:${req.accountId}`;
|
const channel = `timeline:direct:${req.accountId}`;
|
||||||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)), true);
|
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)), true);
|
||||||
|
@ -605,12 +615,18 @@ const startWorker = (workerId) => {
|
||||||
case 'public:local':
|
case 'public:local':
|
||||||
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
break;
|
break;
|
||||||
|
case 'public:remote':
|
||||||
|
streamFrom('timeline:public:remote', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
|
break;
|
||||||
case 'public:media':
|
case 'public:media':
|
||||||
streamFrom('timeline:public:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
streamFrom('timeline:public:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
break;
|
break;
|
||||||
case 'public:local:media':
|
case 'public:local:media':
|
||||||
streamFrom('timeline:public:local:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
streamFrom('timeline:public:local:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
break;
|
break;
|
||||||
|
case 'public:remote:media':
|
||||||
|
streamFrom('timeline:public:remote:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||||
|
break;
|
||||||
case 'direct':
|
case 'direct':
|
||||||
channel = `timeline:direct:${req.accountId}`;
|
channel = `timeline:direct:${req.accountId}`;
|
||||||
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)), true);
|
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)), true);
|
||||||
|
|
Loading…
Reference in New Issue