Browse Source

Merge remote-tracking branch 'origin/master' into closed-social-v3

pull/4/head
欧醚 2 months ago
parent
commit
04bfe40736
94 changed files with 1097 additions and 626 deletions
  1. +7
    -7
      Gemfile
  2. +66
    -68
      Gemfile.lock
  3. +2
    -9
      app/controllers/accounts_controller.rb
  4. +20
    -11
      app/controllers/admin/accounts_controller.rb
  5. +2
    -2
      app/controllers/api/base_controller.rb
  6. +2
    -3
      app/controllers/api/v1/accounts_controller.rb
  7. +8
    -1
      app/controllers/api/v1/admin/accounts_controller.rb
  8. +1
    -1
      app/controllers/api/v1/follow_requests_controller.rb
  9. +1
    -1
      app/controllers/api/v1/push/subscriptions_controller.rb
  10. +2
    -1
      app/controllers/api/web/push_subscriptions_controller.rb
  11. +1
    -1
      app/controllers/settings/deletes_controller.rb
  12. +2
    -2
      app/javascript/mastodon/actions/accounts.js
  13. +1
    -1
      app/javascript/mastodon/actions/markers.js
  14. +1
    -1
      app/javascript/mastodon/actions/notifications.js
  15. +17
    -3
      app/javascript/mastodon/components/error_boundary.js
  16. +11
    -1
      app/javascript/mastodon/features/account/components/header.js
  17. +5
    -0
      app/javascript/mastodon/features/account_timeline/components/header.js
  18. +10
    -2
      app/javascript/mastodon/features/account_timeline/containers/header_container.js
  19. +8
    -0
      app/javascript/mastodon/features/notifications/components/filter_bar.js
  20. +35
    -0
      app/javascript/mastodon/features/notifications/components/notification.js
  21. +1
    -1
      app/javascript/mastodon/features/notifications/index.js
  22. +5
    -0
      app/javascript/styles/contrast/diff.scss
  23. +4
    -0
      app/javascript/styles/mastodon/components.scss
  24. +2
    -2
      app/lib/activitypub/activity.rb
  25. +1
    -1
      app/lib/activitypub/activity/delete.rb
  26. +2
    -2
      app/lib/activitypub/activity/follow.rb
  27. +1
    -1
      app/lib/activitypub/activity/like.rb
  28. +9
    -7
      app/mailers/notification_mailer.rb
  29. +14
    -14
      app/mailers/user_mailer.rb
  30. +3
    -6
      app/models/account.rb
  31. +20
    -0
      app/models/account_deletion_request.rb
  32. +1
    -1
      app/models/admin/account_action.rb
  33. +3
    -0
      app/models/concerns/account_associations.rb
  34. +16
    -10
      app/models/concerns/account_interactions.rb
  35. +2
    -1
      app/models/follow.rb
  36. +2
    -1
      app/models/follow_request.rb
  37. +1
    -1
      app/models/form/account_batch.rb
  38. +1
    -1
      app/models/invite.rb
  39. +27
    -17
      app/models/notification.rb
  40. +2
    -2
      app/models/user.rb
  41. +1
    -1
      app/models/webauthn_credential.rb
  42. +4
    -0
      app/policies/account_policy.rb
  43. +1
    -1
      app/serializers/rest/notification_serializer.rb
  44. +9
    -3
      app/serializers/rest/relationship_serializer.rb
  45. +1
    -1
      app/services/after_unallow_domain_service.rb
  46. +1
    -1
      app/services/block_domain_service.rb
  47. +180
    -0
      app/services/delete_account_service.rb
  48. +1
    -1
      app/services/favourite_service.rb
  49. +8
    -7
      app/services/follow_service.rb
  50. +3
    -3
      app/services/import_service.rb
  51. +6
    -2
      app/services/notify_service.rb
  52. +1
    -1
      app/services/process_mentions_service.rb
  53. +1
    -1
      app/services/reblog_service.rb
  54. +30
    -153
      app/services/suspend_account_service.rb
  55. +52
    -0
      app/services/unsuspend_account_service.rb
  56. +58
    -56
      app/views/admin/accounts/show.html.haml
  57. +13
    -0
      app/workers/account_deletion_worker.rb
  58. +13
    -0
      app/workers/admin/account_deletion_worker.rb
  59. +4
    -2
      app/workers/admin/suspension_worker.rb
  60. +13
    -0
      app/workers/admin/unsuspension_worker.rb
  61. +14
    -1
      app/workers/feed_insert_worker.rb
  62. +2
    -2
      app/workers/local_notification_worker.rb
  63. +2
    -2
      app/workers/poll_expiration_notify_worker.rb
  64. +2
    -1
      app/workers/refollow_worker.rb
  65. +13
    -0
      app/workers/scheduler/user_cleanup_scheduler.rb
  66. +3
    -2
      app/workers/unfollow_follow_worker.rb
  67. +23
    -8
      config/locales/en.yml
  68. +4
    -4
      config/locales/simple_form.en.yml
  69. +2
    -2
      config/routes.rb
  70. +8
    -0
      db/migrate/20200908193330_create_account_deletion_requests.rb
  71. +19
    -0
      db/migrate/20200917192924_add_notify_to_follows.rb
  72. +5
    -0
      db/migrate/20200917193034_add_type_to_notifications.rb
  73. +7
    -0
      db/migrate/20200917222316_add_index_notifications_on_type.rb
  74. +22
    -0
      db/post_migrate/20200917193528_migrate_notifications_type.rb
  75. +15
    -0
      db/post_migrate/20200917222734_remove_index_notifications_on_account_activity.rb
  76. +13
    -3
      db/schema.rb
  77. +2
    -2
      lib/mastodon/accounts_cli.rb
  78. +1
    -1
      lib/mastodon/domains_cli.rb
  79. +8
    -8
      package.json
  80. +2
    -18
      spec/controllers/accounts_controller_spec.rb
  81. +57
    -27
      spec/controllers/api/v1/accounts_controller_spec.rb
  82. +2
    -1
      spec/controllers/auth/registrations_controller_spec.rb
  83. +1
    -0
      spec/controllers/concerns/export_controller_concern_spec.rb
  84. +3
    -0
      spec/fabricators/account_deletion_request_fabricator.rb
  85. +4
    -0
      spec/models/account_deletion_request_spec.rb
  86. +1
    -1
      spec/models/concerns/account_interactions_spec.rb
  87. +1
    -1
      spec/models/follow_request_spec.rb
  88. +1
    -1
      spec/models/invite_spec.rb
  89. +2
    -2
      spec/models/webauthn_credentials_spec.rb
  90. +1
    -1
      spec/services/delete_account_service_spec.rb
  91. +1
    -0
      spec/services/import_service_spec.rb
  92. +5
    -1
      spec/services/notify_service_spec.rb
  93. +2
    -2
      spec/workers/refollow_worker_spec.rb
  94. +130
    -119
      yarn.lock

+ 7
- 7
Gemfile View File

@ -6,9 +6,9 @@ ruby '>= 2.5.0', '< 3.0.0'
gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 4.3'
gem 'rails', '~> 5.2.4.3'
gem 'rails', '~> 5.2.4.4'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 0.20'
gem 'thor', '~> 1.0'
gem 'rack', '~> 2.2.3'
gem 'thwait', '~> 0.2.0'
@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.7'
gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.79', require: false
gem 'aws-sdk-s3', '~> 1.81', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
@ -121,12 +121,12 @@ end
group :test do
gem 'capybara', '~> 3.33'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.13'
gem 'faker', '~> 2.14'
gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.19', require: false
gem 'webmock', '~> 3.8'
gem 'webmock', '~> 3.9'
gem 'parallel_tests', '~> 3.2'
gem 'rspec_junit_formatter', '~> 0.4'
end
@ -134,13 +134,13 @@ end
group :development do
gem 'active_record_query_trace', '~> 1.7'
gem 'annotate', '~> 3.1'
gem 'better_errors', '~> 2.7'
gem 'better_errors', '~> 2.8'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
gem 'rubocop', '~> 0.90', require: false
gem 'rubocop', '~> 0.91', require: false
gem 'rubocop-rails', '~> 2.8', require: false
gem 'brakeman', '~> 4.9', require: false
gem 'bundler-audit', '~> 0.7', require: false

+ 66
- 68
Gemfile.lock View File

@ -16,25 +16,25 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (5.2.4.3)
actionpack (= 5.2.4.3)
actioncable (5.2.4.4)
actionpack (= 5.2.4.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.4.3)
actionpack (= 5.2.4.3)
actionview (= 5.2.4.3)
activejob (= 5.2.4.3)
actionmailer (5.2.4.4)
actionpack (= 5.2.4.4)
actionview (= 5.2.4.4)
activejob (= 5.2.4.4)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.4.3)
actionview (= 5.2.4.3)
activesupport (= 5.2.4.3)
actionpack (5.2.4.4)
actionview (= 5.2.4.4)
activesupport (= 5.2.4.4)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.4.3)
activesupport (= 5.2.4.3)
actionview (5.2.4.4)
activesupport (= 5.2.4.4)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -45,20 +45,20 @@ GEM
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.7)
activejob (5.2.4.3)
activesupport (= 5.2.4.3)
activejob (5.2.4.4)
activesupport (= 5.2.4.4)
globalid (>= 0.3.6)
activemodel (5.2.4.3)
activesupport (= 5.2.4.3)
activerecord (5.2.4.3)
activemodel (= 5.2.4.3)
activesupport (= 5.2.4.3)
activemodel (5.2.4.4)
activesupport (= 5.2.4.4)
activerecord (5.2.4.4)
activemodel (= 5.2.4.4)
activesupport (= 5.2.4.4)
arel (>= 9.0)
activestorage (5.2.4.3)
actionpack (= 5.2.4.3)
activerecord (= 5.2.4.3)
activestorage (5.2.4.4)
actionpack (= 5.2.4.4)
activerecord (= 5.2.4.4)
marcel (~> 0.3.1)
activesupport (5.2.4.3)
activesupport (5.2.4.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@ -79,23 +79,23 @@ GEM
cocaine (~> 0.5.3)
awrence (1.1.1)
aws-eventstream (1.1.0)
aws-partitions (1.365.0)
aws-sdk-core (3.105.0)
aws-partitions (1.373.0)
aws-sdk-core (3.107.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.37.0)
aws-sdk-kms (1.38.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.79.1)
aws-sdk-s3 (1.81.0)
aws-sdk-core (~> 3, >= 3.104.3)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.16)
better_errors (2.7.1)
better_errors (2.8.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@ -160,13 +160,12 @@ GEM
cose (1.0.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 0.4.0)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crack (0.4.4)
crass (1.0.6)
css_parser (1.7.1)
addressable
debug_inspector (0.0.3)
devise (4.7.2)
devise (4.7.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@ -210,7 +209,7 @@ GEM
tzinfo
excon (0.76.0)
fabrication (2.21.1)
faker (2.13.0)
faker (2.14.0)
i18n (>= 1.6, < 2)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
@ -233,7 +232,7 @@ GEM
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.2.5)
fugit (1.3.8)
fugit (1.3.9)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.3)
fuubar (2.5.0)
@ -363,7 +362,7 @@ GEM
net-scp (3.0.0)
net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.1.0)
nio4r (2.5.3)
nio4r (2.5.4)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.2)
@ -387,7 +386,7 @@ GEM
openssl (2.2.0)
openssl-signature_algorithm (0.4.0)
orm_adapter (0.5.0)
ox (2.13.3)
ox (2.13.4)
paperclip (6.0.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
@ -406,9 +405,9 @@ GEM
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.2.3)
pghero (2.7.0)
pghero (2.7.2)
activerecord (>= 5)
pkg-config (1.4.2)
pkg-config (1.4.3)
posix-spawn (0.3.15)
premailer (1.13.1)
addressable
@ -441,18 +440,18 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.4.3)
actioncable (= 5.2.4.3)
actionmailer (= 5.2.4.3)
actionpack (= 5.2.4.3)
actionview (= 5.2.4.3)
activejob (= 5.2.4.3)
activemodel (= 5.2.4.3)
activerecord (= 5.2.4.3)
activestorage (= 5.2.4.3)
activesupport (= 5.2.4.3)
rails (5.2.4.4)
actioncable (= 5.2.4.4)
actionmailer (= 5.2.4.4)
actionpack (= 5.2.4.4)
actionview (= 5.2.4.4)
activejob (= 5.2.4.4)
activemodel (= 5.2.4.4)
activerecord (= 5.2.4.4)
activestorage (= 5.2.4.4)
activesupport (= 5.2.4.4)
bundler (>= 1.3.0)
railties (= 5.2.4.3)
railties (= 5.2.4.4)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -468,9 +467,9 @@ GEM
railties (>= 5.0, < 6)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (5.2.4.3)
actionpack (= 5.2.4.3)
activesupport (= 5.2.4.3)
railties (5.2.4.4)
actionpack (= 5.2.4.4)
activesupport (= 5.2.4.4)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
@ -481,7 +480,7 @@ GEM
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.4.0)
rdf (~> 3.1)
redis (4.2.1)
redis (4.2.2)
redis-actionpack (5.2.0)
actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3)
@ -500,7 +499,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.9.0)
redis (>= 4, < 5)
regexp_parser (1.7.1)
regexp_parser (1.8.0)
request_store (1.5.0)
rack (>= 1.4)
responders (3.0.1)
@ -535,18 +534,18 @@ GEM
rspec-support (3.9.3)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (0.90.0)
rubocop (0.91.0)
parallel (~> 1.10)
parser (>= 2.7.1.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
rexml
rubocop-ast (>= 0.3.0, < 1.0)
rubocop-ast (>= 0.4.0, < 1.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.3.0)
rubocop-ast (0.4.2)
parser (>= 2.7.1.4)
rubocop-rails (2.8.0)
rubocop-rails (2.8.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.87.0)
@ -555,7 +554,6 @@ GEM
nokogiri (>= 1.5.10)
rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.5)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
sanitize (5.2.1)
@ -564,7 +562,7 @@ GEM
nokogumbo (~> 2.0)
securecompare (1.0.0)
semantic_range (2.3.0)
sidekiq (6.1.1)
sidekiq (6.1.2)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@ -593,7 +591,7 @@ GEM
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.1)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@ -612,7 +610,7 @@ GEM
unicode-display_width (~> 1.1, >= 1.1.1)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (0.20.3)
thor (1.0.1)
thread_safe (0.3.6)
thwait (0.2.0)
e2mmap
@ -653,7 +651,7 @@ GEM
safety_net_attestation (~> 0.4.0)
securecompare (~> 1.0)
tpm-key_attestation (~> 0.9.0)
webmock (3.8.3)
webmock (3.9.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@ -680,8 +678,8 @@ DEPENDENCIES
active_record_query_trace (~> 1.7)
addressable (~> 2.7)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.79)
better_errors (~> 2.7)
aws-sdk-s3 (~> 1.81)
better_errors (~> 2.8)
binding_of_caller (~> 0.7)
blurhash (~> 0.1)
bootsnap (~> 1.4)
@ -710,7 +708,7 @@ DEPENDENCIES
e2mmap (~> 0.1.0)
ed25519 (~> 1.2)
fabrication (~> 2.21)
faker (~> 2.13)
faker (~> 2.14)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
@ -766,7 +764,7 @@ DEPENDENCIES
rack (~> 2.2.3)
rack-attack (~> 6.3)
rack-cors (~> 1.1)
rails (~> 5.2.4.3)
rails (~> 5.2.4.4)
rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6)
@ -778,7 +776,7 @@ DEPENDENCIES
rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4)
rubocop (~> 0.90)
rubocop (~> 0.91)
rubocop-rails (~> 2.8)
ruby-progressbar (~> 1.10)
sanitize (~> 5.2)
@ -795,12 +793,12 @@ DEPENDENCIES
stoplight (~> 2.2.1)
streamio-ffmpeg (~> 3.0)
strong_migrations (~> 0.7)
thor (~> 0.20)
thor (~> 1.0)
thwait (~> 0.2.0)
tty-prompt (~> 0.22)
twitter-text (~> 1.14)
tzinfo-data (~> 1.2020)
webauthn (~> 3.0.0.alpha1)
webmock (~> 3.8)
webmock (~> 3.9)
webpacker (~> 5.2)
webpush

+ 2
- 9
app/controllers/accounts_controller.rb View File

@ -7,6 +7,7 @@ class AccountsController < ApplicationController
include AccountControllerConcern
include SignatureAuthentication
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
before_action :set_body_classes
@ -48,7 +49,7 @@ class AccountsController < ApplicationController
format.json do
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
end
end
end
@ -153,12 +154,4 @@ class AccountsController < ApplicationController
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end
def restrict_fields_to
if signed_request_account.present? || public_fetch_mode?
# Return all fields
else
%i(id type preferred_username inbox public_key endpoints)
end
end
end

+ 20
- 11
app/controllers/admin/accounts_controller.rb View File

@ -2,7 +2,7 @@
module Admin
class AccountsController < BaseController
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
before_action :set_account, except: [:index]
before_action :require_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
@ -14,49 +14,58 @@ module Admin
def show
authorize @account, :show?
@deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
@domain_block = DomainBlock.rule_for(@account.domain)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
end
def approve
authorize @account.user, :approve?
@account.user.approve!
redirect_to admin_pending_accounts_path
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
end
def reject
authorize @account.user, :reject?
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
redirect_to admin_pending_accounts_path
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end
def destroy
authorize @account, :destroy?
Admin::AccountDeletionWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
end
def redownload
@ -65,7 +74,7 @@ module Admin
@account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account)
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
end
def remove_avatar
@ -76,7 +85,7 @@ module Admin
log_action :remove_avatar, @account.user
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
end
def remove_header
@ -87,7 +96,7 @@ module Admin
log_action :remove_header, @account.user
redirect_to admin_account_path(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
end
private

+ 2
- 2
app/controllers/api/base_controller.rb View File

@ -96,12 +96,12 @@ class Api::BaseController < ApplicationController
def require_user!
if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422
elsif current_user.disabled?
render json: { error: 'Your login is currently disabled' }, status: 403
elsif !current_user.confirmed?
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403
elsif !current_user.functional?
render json: { error: 'Your login is currently disabled' }, status: 403
else
set_user_activity
end

+ 2
- 3
app/controllers/api/v1/accounts_controller.rb View File

@ -30,9 +30,8 @@ class Api::V1::AccountsController < Api::BaseController
end
def follow
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end

+ 8
- 1
app/controllers/api/v1/admin/accounts_controller.rb View File

@ -58,7 +58,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def reject
authorize @account.user, :reject?
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
render json: @account, serializer: REST::Admin::AccountSerializer
end
def destroy
authorize @account, :destroy?
Admin::AccountDeletionWorker.perform_async(@account.id)
render json: @account, serializer: REST::Admin::AccountSerializer
end
@ -72,6 +78,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end

+ 1
- 1
app/controllers/api/v1/follow_requests_controller.rb View File

@ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
def authorize
AuthorizeFollowService.new.call(account, current_account)
NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
NotifyService.new.call(current_account, :follow, Follow.find_by(account: account, target_account: current_account))
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
end

+ 1
- 1
app/controllers/api/v1/push/subscriptions_controller.rb View File

@ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
def data_params
return {} if params[:data].blank?
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
end
end

+ 2
- 1
app/controllers/api/web/push_subscriptions_controller.rb View File

@ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
reblog: alerts_enabled,
mention: alerts_enabled,
poll: alerts_enabled,
status: alerts_enabled,
},
}
@ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
@data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
@data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
end
end

+ 1
- 1
app/controllers/settings/deletes_controller.rb View File

@ -43,7 +43,7 @@ class Settings::DeletesController < Settings::BaseController
def destroy_account!
current_account.suspend!
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
AccountDeletionWorker.perform_async(current_user.account_id)
sign_out
end
end

+ 2
- 2
app/javascript/mastodon/actions/accounts.js View File

@ -109,14 +109,14 @@ export function fetchAccountFail(id, error) {
};
};
export function followAccount(id, reblogs = true) {
export function followAccount(id, options = { reblogs: true }) {
return (dispatch, getState) => {
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
const locked = getState().getIn(['accounts', id, 'locked'], false);
dispatch(followAccountRequest(id, locked));
api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => {
api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
dispatch(followAccountSuccess(response.data, alreadyFollowing));
}).catch(error => {
dispatch(followAccountFail(error, locked));

+ 1
- 1
app/javascript/mastodon/actions/markers.js View File

@ -57,7 +57,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
const _buildParams = (state) => {
const params = {};
const lastHomeId = state.getIn(['timelines', 'home', 'items', 0]);
const lastHomeId = state.getIn(['timelines', 'home', 'items']).find(item => item !== null);
const lastNotificationId = state.getIn(['notifications', 'items', 0, 'id']);
if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {

+ 1
- 1
app/javascript/mastodon/actions/notifications.js View File

@ -59,7 +59,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
let filtered = false;
if (notification.type === 'mention') {
if (['mention', 'status'].includes(notification.type)) {
const dropRegex = filters[0];
const regex = filters[1];
const searchIndex = searchTextFromRawStatus(notification.status);

+ 17
- 3
app/javascript/mastodon/components/error_boundary.js View File

@ -66,17 +66,31 @@ export default class ErrorBoundary extends React.PureComponent {
}
render() {
const { hasError, copied } = this.state;
const { hasError, copied, errorMessage } = this.state;
if (!hasError) {
return this.props.children;
}
const likelyBrowserAddonIssue = errorMessage && errorMessage.includes('NotFoundError');
return (
<div className='error-boundary'>
<div>
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
<p className='error-boundary__error'>
{ likelyBrowserAddonIssue ? (
<FormattedMessage id='error.unexpected_crash.explanation_addons' defaultMessage='This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.' />
) : (
<FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' />
)}
</p>
<p>
{ likelyBrowserAddonIssue ? (
<FormattedMessage id='error.unexpected_crash.next_steps_addons' defaultMessage='Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' />
) : (
<FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' />
)}
</p>
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
</div>
</div>

+ 11
- 1
app/javascript/mastodon/features/account/components/header.js View File

@ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif, me, isStaff } from 'mastodon/initial_state';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
import IconButton from 'mastodon/components/icon_button';
import Avatar from 'mastodon/components/avatar';
import { counterRenderer } from 'mastodon/components/common_counter';
import ShortNumber from 'mastodon/components/short_number';
@ -35,6 +36,8 @@ const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
@ -68,8 +71,9 @@ class Header extends ImmutablePureComponent {
onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired,
onReblogToggle: PropTypes.func.isRequired,
onNotifyToggle: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired,
onUnblockDomain: PropTypes.func.isRequired,
@ -144,6 +148,7 @@ class Header extends ImmutablePureComponent {
let info = [];
let actionBtn = '';
let bellBtn = '';
let lockedIcon = '';
let menu = [];
@ -173,6 +178,10 @@ class Header extends ImmutablePureComponent {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
}
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
}
if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
actionBtn = '';
}
@ -287,6 +296,7 @@ class Header extends ImmutablePureComponent {
{!suspended && (
<div className='account__header__tabs__buttons'>
{actionBtn}
{bellBtn}
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
</div>

+ 5
- 0
app/javascript/mastodon/features/account_timeline/components/header.js View File

@ -55,6 +55,10 @@ export default class Header extends ImmutablePureComponent {
this.props.onReblogToggle(this.props.account);
}
handleNotifyToggle = () => {
this.props.onNotifyToggle(this.props.account);
}
handleMute = () => {
this.props.onMute(this.props.account);
}
@ -106,6 +110,7 @@ export default class Header extends ImmutablePureComponent {
onMention={this.handleMention}
onDirect={this.handleDirect}
onReblogToggle={this.handleReblogToggle}
onNotifyToggle={this.handleNotifyToggle}
onReport={this.handleReport}
onMute={this.handleMute}
onBlockDomain={this.handleBlockDomain}

+ 10
- 2
app/javascript/mastodon/features/account_timeline/containers/header_container.js View File

@ -76,9 +76,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onReblogToggle (account) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
dispatch(followAccount(account.get('id'), false));
dispatch(followAccount(account.get('id'), { reblogs: false }));
} else {
dispatch(followAccount(account.get('id'), true));
dispatch(followAccount(account.get('id'), { reblogs: true }));
}
},
@ -90,6 +90,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
onNotifyToggle (account) {
if (account.getIn(['relationship', 'notifying'])) {
dispatch(followAccount(account.get('id'), { notify: false }));
} else {
dispatch(followAccount(account.get('id'), { notify: true }));
}
},
onReport (account) {
dispatch(initReport(account));
},

+ 8
- 0
app/javascript/mastodon/features/notifications/components/filter_bar.js View File

@ -9,6 +9,7 @@ const tooltips = defineMessages({
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
});
export default @injectIntl
@ -87,6 +88,13 @@ class FilterBar extends React.PureComponent {
>
<Icon id='tasks' fixedWidth />
</button>
<button
className={selectedFilter === 'status' ? 'active' : ''}
onClick={this.onClick('status')}
title={intl.formatMessage(tooltips.statuses)}
>
<Icon id='home' fixedWidth />
</button>
<button
className={selectedFilter === 'follow' ? 'active' : ''}
onClick={this.onClick('follow')}

+ 35
- 0
app/javascript/mastodon/features/notifications/components/notification.js View File

@ -17,6 +17,7 @@ const messages = defineMessages({
ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
});
const notificationForScreenReader = (intl, message, timestamp) => {
@ -237,6 +238,38 @@ class Notification extends ImmutablePureComponent {
);
}
renderStatus (notification, link) {
const { intl } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification-status focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='home' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.status' defaultMessage='{name} just posted' values={{ name: link }} />
</span>
</div>
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
);
}
renderPoll (notification, account) {
const { intl } = this.props;
const ownPoll = me === account.get('id');
@ -292,6 +325,8 @@ class Notification extends ImmutablePureComponent {
return this.renderFavourite(notification, link);
case 'reblog':
return this.renderReblog(notification, link);
case 'status':
return this.renderStatus(notification, link);
case 'poll':
return this.renderPoll(notification, account);
}

+ 1
- 1
app/javascript/mastodon/features/notifications/index.js View File

@ -32,7 +32,7 @@ const getNotifications = createSelector([
// we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
}
return notifications.filter(item => item !== null && allowedType === item.get('type'));
return notifications.filter(item => item === null || allowedType === item.get('type'));
});
const mapStateToProps = state => ({

+ 5
- 0
app/javascript/styles/contrast/diff.scss View File

@ -75,3 +75,8 @@
.public-layout .public-account-header__tabs__tabs .counter.active::after {
border-bottom: 4px solid $ui-highlight-color;
}
.compose-form .autosuggest-textarea__textarea::placeholder,
.compose-form .spoiler-input__input::placeholder {
color: $inverted-text-color;
}

+ 4
- 0
app/javascript/styles/mastodon/components.scss View File

@ -6502,6 +6502,10 @@ noscript {
padding: 2px;
}
& > .icon-button {
margin-right: 8px;
}
.button {
margin: 0 8px;
}

+ 2
- 2
app/lib/activitypub/activity.rb View File

@ -118,13 +118,13 @@ class ActivityPub::Activity
end
def notify_about_reblog(status)
NotifyService.new.call(status.reblog.account, status)
NotifyService.new.call(status.reblog.account, :reblog, status)
end
def notify_about_mentions(status)
status.active_mentions.includes(:account).each do |mention|
next unless mention.account.local? && audience_includes?(mention.account)
NotifyService.new.call(mention.account, mention)
NotifyService.new.call(mention.account, :mention, mention)
end
end

+ 1
- 1
app/lib/activitypub/activity/delete.rb View File

@ -13,7 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
def delete_person
lock_or_return("delete_in_progress:#{@account.id}") do
SuspendAccountService.new.call(@account, reserve_username: false)
DeleteAccountService.new.call(@account, reserve_username: false)
end
end

+ 2
- 2
app/lib/activitypub/activity/follow.rb View File

@ -22,10 +22,10 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
if target_account.locked? || @account.silenced?
NotifyService.new.call(target_account, follow_request)
NotifyService.new.call(target_account, :follow_request, follow_request)
else
AuthorizeFollowService.new.call(@account, target_account)
NotifyService.new.call(target_account, ::Follow.find_by(account: @account, target_account: target_account))
NotifyService.new.call(target_account, :follow, ::Follow.find_by(account: @account, target_account: target_account))
end
end

+ 1
- 1
app/lib/activitypub/activity/like.rb View File

@ -7,6 +7,6 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
return if original_status.nil? || !original_status.account.local? || delete_arrived_first?(@json['id']) || @account.favourited?(original_status)
favourite = original_status.favourites.create!(account: @account)
NotifyService.new.call(original_status.account, favourite)
NotifyService.new.call(original_status.account, :favourite, favourite)
end
end

+ 9
- 7
app/mailers/notification_mailer.rb View File

@ -10,7 +10,7 @@ class NotificationMailer < ApplicationMailer
@me = recipient
@status = notification.target_status
return if @me.user.disabled? || @status.nil?
return unless @me.user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
@ -22,7 +22,7 @@ class NotificationMailer < ApplicationMailer
@me = recipient
@account = notification.from_account
return if @me.user.disabled?
return unless @me.user.functional?
locale_for_account(@me) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
@ -34,7 +34,7 @@ class NotificationMailer < ApplicationMailer
@account = notification.from_account
@status = notification.target_status
return if @me.user.disabled? || @status.nil?
return unless @me.user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
@ -47,7 +47,7 @@ class NotificationMailer < ApplicationMailer
@account = notification.from_account
@status = notification.target_status
return if @me.user.disabled? || @status.nil?
return unless @me.user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
@ -59,7 +59,7 @@ class NotificationMailer < ApplicationMailer
@me = recipient
@account = notification.from_account
return if @me.user.disabled?
return unless @me.user.functional?
locale_for_account(@me) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
@ -67,7 +67,7 @@ class NotificationMailer < ApplicationMailer
end
def digest(recipient, **opts)
return if recipient.user.disabled?
return unless recipient.user.functional?
@me = recipient
@since = opts[:since] || [@me.user.last_emailed_at, (@me.user.current_sign_in_at + 1.day)].compact.max
@ -88,8 +88,10 @@ class NotificationMailer < ApplicationMailer
def thread_by_conversation(conversation)
return if conversation.nil?
msg_id = "<conversation-#{conversation.id}.#{conversation.created_at.strftime('%Y-%m-%d')}@#{Rails.configuration.x.local_domain}>"
headers['In-Reply-To'] = msg_id
headers['References'] = msg_id
headers['References'] = msg_id
end
end

+ 14
- 14
app/mailers/user_mailer.rb View File

@ -15,7 +15,7 @@ class UserMailer < Devise::Mailer
@token = token
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.unconfirmed_email.presence || @resource.email,
@ -29,7 +29,7 @@ class UserMailer < Devise::Mailer
@token = token
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
@ -40,7 +40,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
@ -51,7 +51,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
@ -62,7 +62,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject')
@ -73,7 +73,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject')
@ -84,7 +84,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
@ -95,7 +95,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_enabled.subject')
@ -106,7 +106,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_disabled.subject')
@ -118,7 +118,7 @@ class UserMailer < Devise::Mailer
@instance = Rails.configuration.x.local_domain
@webauthn_credential = webauthn_credential
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.added.subject')
@ -130,7 +130,7 @@ class UserMailer < Devise::Mailer
@instance = Rails.configuration.x.local_domain
@webauthn_credential = webauthn_credential
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject')
@ -141,7 +141,7 @@ class UserMailer < Devise::Mailer
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
@ -153,7 +153,7 @@ class UserMailer < Devise::Mailer
@instance = Rails.configuration.x.local_domain
@backup = backup
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
@ -181,7 +181,7 @@ class UserMailer < Devise::Mailer
@detection = Browser.new(user_agent)
@timestamp = timestamp.to_time.utc
return if @resource.disabled?
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email,

+ 3
- 6
app/models/account.rb View File

@ -222,23 +222,20 @@ class Account < ApplicationRecord
def suspend!(date = Time.now.utc)
transaction do
user&.disable! if local?
create_deletion_request!
update!(suspended_at: date)
end
end
def unsuspend!
transaction do
user&.enable! if local?
deletion_request&.destroy!
update!(suspended_at: nil)
end
end
def memorialize!
transaction do
user&.disable! if local?
update!(memorial: true)
end
update!(memorial: true)
end
def sign?

+ 20
- 0
app/models/account_deletion_request.rb View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_deletion_requests
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountDeletionRequest < ApplicationRecord
DELAY_TO_DELETION = 30.days.freeze
belongs_to :account
def due_at
created_at + DELAY_TO_DELETION
end
end

+ 1
- 1
app/models/admin/account_action.rb View File

@ -134,7 +134,7 @@ class Admin::AccountAction
end
def process_email!
UserMailer.warning(target_account.user, warning, status_ids).deliver_now! if warnable?
UserMailer.warning(target_account.user, warning, status_ids).deliver_later! if warnable?
end
def warnable?

+ 3
- 0
app/models/concerns/account_associations.rb View File

@ -60,5 +60,8 @@ module AccountAssociations
# Hashtags
has_and_belongs_to_many :tags
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
# Account deletion requests
has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
end
end

+ 16
- 10
app/models/concerns/account_interactions.rb View File

@ -8,6 +8,7 @@ module AccountInteractions
Follow.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow, mapping|
mapping[follow.target_account_id] = {
reblogs: follow.show_reblogs?,
notify: follow.notify?,
}
end
end
@ -36,6 +37,7 @@ module AccountInteractions
FollowRequest.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow_request, mapping|
mapping[follow_request.target_account_id] = {
reblogs: follow_request.show_reblogs?,
notify: follow_request.notify?,
}
end
end
@ -95,25 +97,29 @@ module AccountInteractions
has_many :announcement_mutes, dependent: :destroy
end
def follow!(other_account, reblogs: nil, uri: nil, rate_limit: false)
reblogs = true if reblogs.nil?
rel = active_relationships.create_with(show_reblogs: reblogs, uri: uri, rate_limit: rate_limit)
def follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false)
rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit)
.find_or_create_by!(target_account: other_account)
rel.update!(show_reblogs: reblogs)
rel.show_reblogs = reblogs unless reblogs.nil?
rel.notify = notify unless notify.nil?
rel.save! if rel.changed?
remove_potential_friendship(other_account)
rel
end
def request_follow!(other_account, reblogs: nil, uri: nil, rate_limit: false)
reblogs = true if reblogs.nil?
rel = follow_requests.create_with(show_reblogs: reblogs, uri: uri, rate_limit: rate_limit)