From ed099d8bdc5b3d9e7df7ce5358441887e6bb7e48 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 15 Sep 2020 14:37:58 +0200 Subject: Change account suspensions to be reversible by default (#14726) --- spec/services/delete_account_service_spec.rb | 84 +++++++++++++++++++++++++++ spec/services/suspend_account_service_spec.rb | 84 --------------------------- 2 files changed, 84 insertions(+), 84 deletions(-) create mode 100644 spec/services/delete_account_service_spec.rb delete mode 100644 spec/services/suspend_account_service_spec.rb (limited to 'spec/services') diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb new file mode 100644 index 000000000..d208b25b8 --- /dev/null +++ b/spec/services/delete_account_service_spec.rb @@ -0,0 +1,84 @@ +require 'rails_helper' + +RSpec.describe DeleteAccountService, type: :service do + describe '#call on local account' do + before do + stub_request(:post, "https://alice.com/inbox").to_return(status: 201) + stub_request(:post, "https://bob.com/inbox").to_return(status: 201) + end + + subject do + -> { described_class.new.call(account) } + end + + let!(:account) { Fabricate(:account) } + let!(:status) { Fabricate(:status, account: account) } + let!(:media_attachment) { Fabricate(:media_attachment, account: account) } + let!(:notification) { Fabricate(:notification, account: account) } + let!(:favourite) { Fabricate(:favourite, account: account) } + let!(:active_relationship) { Fabricate(:follow, account: account) } + let!(:passive_relationship) { Fabricate(:follow, target_account: account) } + let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } + let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } + let!(:endorsment) { Fabricate(:account_pin, account: passive_relationship.account, target_account: account) } + + it 'deletes associated records' do + is_expected.to change { + [ + account.statuses, + account.media_attachments, + account.notifications, + account.favourites, + account.active_relationships, + account.passive_relationships, + AccountPin.where(target_account: account), + ].map(&:count) + }.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0]) + end + + it 'sends a delete actor activity to all known inboxes' do + subject.call + expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once + expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once + end + end + + describe '#call on remote account' do + before do + stub_request(:post, "https://alice.com/inbox").to_return(status: 201) + stub_request(:post, "https://bob.com/inbox").to_return(status: 201) + end + + subject do + -> { described_class.new.call(remote_bob) } + end + + let!(:account) { Fabricate(:account) } + let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } + let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } + let!(:status) { Fabricate(:status, account: remote_bob) } + let!(:media_attachment) { Fabricate(:media_attachment, account: remote_bob) } + let!(:notification) { Fabricate(:notification, account: remote_bob) } + let!(:favourite) { Fabricate(:favourite, account: remote_bob) } + let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) } + let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) } + + it 'deletes associated records' do + is_expected.to change { + [ + remote_bob.statuses, + remote_bob.media_attachments, + remote_bob.notifications, + remote_bob.favourites, + remote_bob.active_relationships, + remote_bob.passive_relationships, + ].map(&:count) + }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0]) + end + + it 'sends a reject follow to follwer inboxes' do + subject.call + expect(a_request(:post, remote_bob.inbox_url)).to have_been_made.once + end + end +end diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb deleted file mode 100644 index 32726d763..000000000 --- a/spec/services/suspend_account_service_spec.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'rails_helper' - -RSpec.describe SuspendAccountService, type: :service do - describe '#call on local account' do - before do - stub_request(:post, "https://alice.com/inbox").to_return(status: 201) - stub_request(:post, "https://bob.com/inbox").to_return(status: 201) - end - - subject do - -> { described_class.new.call(account) } - end - - let!(:account) { Fabricate(:account) } - let!(:status) { Fabricate(:status, account: account) } - let!(:media_attachment) { Fabricate(:media_attachment, account: account) } - let!(:notification) { Fabricate(:notification, account: account) } - let!(:favourite) { Fabricate(:favourite, account: account) } - let!(:active_relationship) { Fabricate(:follow, account: account) } - let!(:passive_relationship) { Fabricate(:follow, target_account: account) } - let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } - let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } - let!(:endorsment) { Fabricate(:account_pin, account: passive_relationship.account, target_account: account) } - - it 'deletes associated records' do - is_expected.to change { - [ - account.statuses, - account.media_attachments, - account.notifications, - account.favourites, - account.active_relationships, - account.passive_relationships, - AccountPin.where(target_account: account), - ].map(&:count) - }.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0]) - end - - it 'sends a delete actor activity to all known inboxes' do - subject.call - expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once - expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once - end - end - - describe '#call on remote account' do - before do - stub_request(:post, "https://alice.com/inbox").to_return(status: 201) - stub_request(:post, "https://bob.com/inbox").to_return(status: 201) - end - - subject do - -> { described_class.new.call(remote_bob) } - end - - let!(:account) { Fabricate(:account) } - let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } - let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } - let!(:status) { Fabricate(:status, account: remote_bob) } - let!(:media_attachment) { Fabricate(:media_attachment, account: remote_bob) } - let!(:notification) { Fabricate(:notification, account: remote_bob) } - let!(:favourite) { Fabricate(:favourite, account: remote_bob) } - let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) } - let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) } - - it 'deletes associated records' do - is_expected.to change { - [ - remote_bob.statuses, - remote_bob.media_attachments, - remote_bob.notifications, - remote_bob.favourites, - remote_bob.active_relationships, - remote_bob.passive_relationships, - ].map(&:count) - }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0]) - end - - it 'sends a reject follow to follwer inboxes' do - subject.call - expect(a_request(:post, remote_bob.inbox_url)).to have_been_made.once - end - end -end -- cgit From 974b1b79ce58e6799e5e5bb576e630ca783150de Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Sep 2020 17:26:45 +0200 Subject: Add option to be notified when a followed user posts (#13546) * Add bell button Fix #4890 * Remove duplicate type from post-deployment migration * Fix legacy class type mappings * Improve query performance with better index * Fix validation * Remove redundant index from notifications --- app/controllers/api/v1/accounts_controller.rb | 5 +- .../api/v1/follow_requests_controller.rb | 2 +- app/javascript/mastodon/actions/accounts.js | 4 +- app/javascript/mastodon/actions/notifications.js | 2 +- .../mastodon/features/account/components/header.js | 12 +++- .../features/account_timeline/components/header.js | 5 ++ .../containers/header_container.js | 12 +++- .../notifications/components/filter_bar.js | 8 +++ .../notifications/components/notification.js | 35 +++++++++ app/javascript/styles/mastodon/components.scss | 4 ++ app/lib/activitypub/activity.rb | 4 +- app/lib/activitypub/activity/follow.rb | 4 +- app/lib/activitypub/activity/like.rb | 2 +- app/models/concerns/account_interactions.rb | 26 ++++--- app/models/follow.rb | 3 +- app/models/follow_request.rb | 3 +- app/models/notification.rb | 44 +++++++----- app/serializers/rest/notification_serializer.rb | 2 +- app/serializers/rest/relationship_serializer.rb | 12 +++- app/services/favourite_service.rb | 2 +- app/services/follow_service.rb | 15 ++-- app/services/import_service.rb | 6 +- app/services/notify_service.rb | 8 ++- app/services/process_mentions_service.rb | 2 +- app/services/reblog_service.rb | 2 +- app/workers/feed_insert_worker.rb | 15 +++- app/workers/local_notification_worker.rb | 4 +- app/workers/poll_expiration_notify_worker.rb | 4 +- app/workers/refollow_worker.rb | 3 +- app/workers/unfollow_follow_worker.rb | 5 +- db/migrate/20200917192924_add_notify_to_follows.rb | 19 +++++ .../20200917193034_add_type_to_notifications.rb | 5 ++ ...200917222316_add_index_notifications_on_type.rb | 7 ++ .../20200917193528_migrate_notifications_type.rb | 22 ++++++ ...move_index_notifications_on_account_activity.rb | 15 ++++ db/schema.rb | 8 ++- .../controllers/api/v1/accounts_controller_spec.rb | 84 +++++++++++++++------- spec/models/concerns/account_interactions_spec.rb | 2 +- spec/models/follow_request_spec.rb | 2 +- spec/services/import_service_spec.rb | 1 + spec/services/notify_service_spec.rb | 6 +- spec/workers/refollow_worker_spec.rb | 4 +- 42 files changed, 324 insertions(+), 106 deletions(-) create mode 100644 db/migrate/20200917192924_add_notify_to_follows.rb create mode 100644 db/migrate/20200917193034_add_type_to_notifications.rb create mode 100644 db/migrate/20200917222316_add_index_notifications_on_type.rb create mode 100644 db/post_migrate/20200917193528_migrate_notifications_type.rb create mode 100644 db/post_migrate/20200917222734_remove_index_notifications_on_account_activity.rb (limited to 'spec/services') diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 61dcb87c2..aef51a647 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -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 diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index 0420b7bef..b34c76f29 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -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 diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index d28f7dad8..723c04e55 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -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)); diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index a26844f84..099e42f6c 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -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); diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 02217b62c..2b97af4e6 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -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 = +