diff options
41 files changed, 337 insertions, 159 deletions
diff --git a/Gemfile b/Gemfile index 3ed4989c8..b9a9159de 100644 --- a/Gemfile +++ b/Gemfile @@ -109,7 +109,7 @@ group :production, :test do end group :test do - gem 'capybara', '~> 3.18' + gem 'capybara', '~> 3.19' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.9' gem 'microformats', '~> 4.1' @@ -129,7 +129,7 @@ group :development do gem 'letter_opener', '~> 1.7' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' - gem 'rubocop', '~> 0.68', require: false + gem 'rubocop', '~> 0.69', require: false gem 'brakeman', '~> 4.5', require: false gem 'bundler-audit', '~> 0.6', require: false gem 'scss_lint', '~> 0.58', require: false diff --git a/Gemfile.lock b/Gemfile.lock index b170b9aa3..98cd2103d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,7 +103,7 @@ GEM ffi (~> 1.10.0) bootsnap (1.4.4) msgpack (~> 1.0) - brakeman (4.5.0) + brakeman (4.5.1) browser (2.5.3) builder (3.2.3) bullet (6.0.0) @@ -129,7 +129,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.18.0) + capybara (3.19.1) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -529,13 +529,13 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.8.0) - rubocop (0.68.1) + rubocop (0.69.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) + parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.6) + unicode-display_width (>= 1.4.0, < 1.7) ruby-progressbar (1.10.0) ruby-saml (1.9.0) nokogiri (>= 1.5.10) @@ -630,7 +630,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.5.0) + unicode-display_width (1.6.0) uniform_notifier (1.12.1) warden (1.2.8) rack (>= 2.0.6) @@ -673,7 +673,7 @@ DEPENDENCIES capistrano-rails (~> 1.4) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 3.18) + capybara (~> 3.19) charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.4) @@ -751,7 +751,7 @@ DEPENDENCIES rqrcode (~> 0.10) rspec-rails (~> 3.8) rspec-sidekiq (~> 3.0) - rubocop (~> 0.68) + rubocop (~> 0.69) sanitize (~> 5.0) scss_lint (~> 0.58) sidekiq (~> 5.2) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index dd3f83389..71597763b 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -41,7 +41,7 @@ module Admin def destroy authorize @domain_block, :destroy? - UnblockDomainService.new.call(@domain_block, retroactive_unblock?) + UnblockDomainService.new.call(@domain_block) log_action :destroy, @domain_block redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.destroyed_msg') end @@ -53,11 +53,7 @@ module Admin end def resource_params - params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :retroactive) - end - - def retroactive_unblock? - ActiveRecord::Type.lookup(:boolean).cast(resource_params[:retroactive]) + params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports) end end end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 06ca03e34..17cf9e07b 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -64,7 +64,7 @@ class HomeController < ApplicationController if request.path.start_with?('/web') new_user_session_path elsif single_user_mode? - short_account_path(Account.local.where(suspended: false).first) + short_account_path(Account.local.without_suspended.first) else about_path end diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss index bb333d35f..ba517a2ab 100644 --- a/app/javascript/flavours/glitch/styles/components/composer.scss +++ b/app/javascript/flavours/glitch/styles/components/composer.scss @@ -124,10 +124,71 @@ white-space: pre-wrap; padding-top: 5px; - p { + p, pre, blockquote { margin-bottom: 20px; + white-space: pre-wrap; - &:last-child { margin-bottom: 0 } + &:last-child { + margin-bottom: 0; + } + } + + h1, h2, h3, h4, h5 { + margin-top: 20px; + margin-bottom: 20px; + } + + h1, h2 { + font-weight: 700; + font-size: 18px; + } + + h2 { + font-size: 16px; + } + + h3, h4, h5 { + font-weight: 500; + } + + blockquote { + padding-left: 10px; + border-left: 3px solid $inverted-text-color; + color: $inverted-text-color; + white-space: normal; + + p:last-child { + margin-bottom: 0; + } + } + + b, strong { + font-weight: 700; + } + + em, i { + font-style: italic; + } + + sub { + font-size: smaller; + text-align: sub; + } + + ul, ol { + margin-left: 1em; + + p { + margin: 0; + } + } + + ul { + list-style-type: disc; + } + + ol { + list-style-type: decimal; } a { diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index fb031258f..62f0a815f 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -31,7 +31,8 @@ } } - .status__content__text { + .status__content__text, + .e-content { h1, h2, h3, h4, h5 { margin-top: 20px; margin-bottom: 20px; @@ -653,7 +654,7 @@ .muted { .status__content p, .status__content a, - .status__content_text { + .status__content__text { color: $dark-text-color; } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 95ca4a548..42535ea68 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -356,6 +356,7 @@ class Status extends ImmutablePureComponent { {prepend} <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}> + <div className='status__expand' onClick={this.handleClick} role='presentation' /> <div className='status__info'> <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a> diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 85cbdfb17..708272591 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -331,7 +331,7 @@ export default function compose(state = initialState, action) { })); case REDRAFT: return state.withMutations(map => { - map.set('text', action.raw_content || unescapeHTML(expandMentions(action.status))); + map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status))); map.set('in_reply_to', action.status.get('in_reply_to_id')); map.set('privacy', action.status.get('visibility')); map.set('media_attachments', action.status.get('media_attachments')); diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index de03cf1a6..48236a286 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -162,7 +162,7 @@ .actions-modal ul li:not(:empty) a:focus button, .actions-modal ul li:not(:empty) a:hover, .actions-modal ul li:not(:empty) a:hover button, -.admin-wrapper .sidebar ul ul a.selected, +.admin-wrapper .sidebar ul li a.selected, .simple_form .block-button, .simple_form .button, .simple_form button { @@ -230,6 +230,7 @@ .empty-column-indicator, .error-column { color: $primary-text-color; + background: $white; } // Change the default colors used on some parts of the profile pages diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f8f64bdd6..19e7ac41b 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1412,6 +1412,15 @@ a.account__display-name { width: 48px; } +.status__expand { + width: 68px; + position: absolute; + left: 0; + top: 0; + height: 100%; + cursor: pointer; +} + .muted { .status__content p, .status__content a { diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 59dfc9004..8a1aad41a 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -187,7 +187,7 @@ class Formatter end def rewrite(text, entities) - chars = text.to_s.to_char_a + text = text.to_s # Sort by start index entities = entities.sort_by do |entity| @@ -199,12 +199,12 @@ class Formatter last_index = entities.reduce(0) do |index, entity| indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices] - result << encode(chars[index...indices.first].join) + result << encode(text[index...indices.first]) result << yield(entity) indices.last end - result << encode(chars[last_index..-1].join) + result << encode(text[last_index..-1]) result.flatten.join end @@ -231,23 +231,14 @@ class Formatter # Note: I couldn't obtain list_slug with @user/list-name format # for mention so this requires additional check special = Extractor.extract_urls_with_indices(escaped, options).map do |extract| - # exactly one of :url, :hashtag, :screen_name, :cashtag keys is present - key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first - new_indices = [ old_to_new_index.find_index(extract[:indices].first), old_to_new_index.find_index(extract[:indices].last), ] - has_prefix_char = [:hashtag, :screen_name, :cashtag].include?(key) - value_indices = [ - new_indices.first + (has_prefix_char ? 1 : 0), # account for #, @ or $ - new_indices.last - 1, - ] - next extract.merge( - :indices => new_indices, - key => text[value_indices.first..value_indices.last] + indices: new_indices, + url: text[new_indices.first..new_indices.last - 1] ) end diff --git a/app/models/account.rb b/app/models/account.rb index a82251d2e..70697db30 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -28,8 +28,6 @@ # header_updated_at :datetime # avatar_remote_url :string # subscription_expires_at :datetime -# silenced :boolean default(FALSE), not null -# suspended :boolean default(FALSE), not null # locked :boolean default(FALSE), not null # header_remote_url :string default(""), not null # last_webfingered_at :datetime @@ -45,6 +43,8 @@ # actor_type :string # discoverable :boolean # also_known_as :string is an Array +# silenced_at :datetime +# suspended_at :datetime # class Account < ApplicationRecord @@ -86,10 +86,10 @@ class Account < ApplicationRecord scope :local, -> { where(domain: nil) } scope :expiring, ->(time) { remote.where.not(subscription_expires_at: nil).where('subscription_expires_at < ?', time) } scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } - scope :silenced, -> { where(silenced: true) } - scope :suspended, -> { where(suspended: true) } - scope :without_suspended, -> { where(suspended: false) } - scope :without_silenced, -> { where(silenced: false) } + scope :silenced, -> { where.not(silenced_at: nil) } + scope :suspended, -> { where.not(suspended_at: nil) } + scope :without_suspended, -> { where(suspended_at: nil) } + scope :without_silenced, -> { where(silenced_at: nil) } scope :recent, -> { reorder(id: :desc) } scope :bots, -> { where(actor_type: %w(Application Service)) } scope :alphabetic, -> { order(domain: :asc, username: :asc) } @@ -169,25 +169,35 @@ class Account < ApplicationRecord ResolveAccountService.new.call(acct) end - def silence! - update!(silenced: true) + def silenced? + silenced_at.present? + end + + def silence!(date = nil) + date ||= Time.now.utc + update!(silenced_at: date) end def unsilence! - update!(silenced: false) + update!(silenced_at: nil) + end + + def suspended? + suspended_at.present? end - def suspend! + def suspend!(date = nil) + date ||= Time.now.utc transaction do user&.disable! if local? - update!(suspended: true) + update!(suspended_at: date) end end def unsuspend! transaction do user&.enable! if local? - update!(suspended: false) + update!(suspended_at: nil) end end @@ -401,7 +411,7 @@ class Account < ApplicationRecord ts_rank_cd(#{textsearch}, #{query}, 32) AS rank FROM accounts WHERE #{query} @@ #{textsearch} - AND accounts.suspended = false + AND accounts.suspended_at IS NULL AND accounts.moved_to_account_id IS NULL ORDER BY rank DESC LIMIT ? OFFSET ? @@ -429,7 +439,7 @@ class Account < ApplicationRecord LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?) WHERE accounts.id IN (SELECT * FROM first_degree) AND #{query} @@ #{textsearch} - AND accounts.suspended = false + AND accounts.suspended_at IS NULL AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC @@ -445,7 +455,7 @@ class Account < ApplicationRecord FROM accounts LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?) WHERE #{query} @@ #{textsearch} - AND accounts.suspended = false + AND accounts.suspended_at IS NULL AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC diff --git a/app/models/concerns/account_finder_concern.rb b/app/models/concerns/account_finder_concern.rb index 0ac49cc12..ccd7bfa12 100644 --- a/app/models/concerns/account_finder_concern.rb +++ b/app/models/concerns/account_finder_concern.rb @@ -13,7 +13,7 @@ module AccountFinderConcern end def representative - find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) || Account.local.find_by(suspended: false) + find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) || Account.local.without_suspended.first end def find_local(username) diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 0b12617c6..84c08c158 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -17,8 +17,6 @@ class DomainBlock < ApplicationRecord enum severity: [:silence, :suspend, :noop] - attr_accessor :retroactive - validates :domain, presence: true, uniqueness: true has_many :accounts, foreign_key: :domain, primary_key: :domain @@ -36,4 +34,9 @@ class DomainBlock < ApplicationRecord return false if other_block.silence? && noop? (reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports) end + + def affected_accounts_count + scope = suspend? ? accounts.where(suspended_at: created_at) : accounts.where(silenced_at: created_at) + scope.count + end end diff --git a/app/models/status.rb b/app/models/status.rb index 1b905d5b8..8736e65e3 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -87,8 +87,8 @@ class Status < ApplicationRecord scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } scope :with_public_visibility, -> { where(visibility: :public) } scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) } - scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) } - scope :including_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: true }) } + scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) } + scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) } scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) } scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) } scope :tagged_with_all, ->(tags) { diff --git a/app/models/user.rb b/app/models/user.rb index 77e6a33b5..8985ebf53 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -88,7 +88,7 @@ class User < ApplicationRecord scope :confirmed, -> { where.not(confirmed_at: nil) } scope :enabled, -> { where(disabled: false) } scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } - scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) } + scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where.not(accounts: { suspended_at: nil }) } scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) } scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) } diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 6d0609ca0..ad22d37fe 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -50,12 +50,12 @@ class ActivityPub::ProcessAccountService < BaseService def create_account @account = Account.new - @account.protocol = :activitypub - @account.username = @username - @account.domain = @domain - @account.suspended = true if auto_suspend? - @account.silenced = true if auto_silence? - @account.private_key = nil + @account.protocol = :activitypub + @account.username = @username + @account.domain = @domain + @account.private_key = nil + @account.suspended_at = domain_block.created_at if auto_suspend? + @account.silenced_at = domain_block.created_at if auto_silence? end def update_account diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb index a1fe93665..497f0394b 100644 --- a/app/services/block_domain_service.rb +++ b/app/services/block_domain_service.rb @@ -29,7 +29,7 @@ class BlockDomainService < BaseService end def silence_accounts! - blocked_domain_accounts.in_batches.update_all(silenced: true) + blocked_domain_accounts.without_silenced.in_batches.update_all(silenced_at: @domain_block.created_at) end def clear_media! @@ -43,9 +43,9 @@ class BlockDomainService < BaseService end def suspend_accounts! - blocked_domain_accounts.where(suspended: false).reorder(nil).find_each do |account| + blocked_domain_accounts.without_suspended.reorder(nil).find_each do |account| UnsubscribeService.new.call(account) if account.subscribed? - SuspendAccountService.new.call(account) + SuspendAccountService.new.call(account, suspended_at: @domain_block.created_at) end end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 333bf88d8..d2cca145b 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -52,7 +52,7 @@ class PostStatusService < BaseService @text = @media.find(&:video?) ? '📹' : '🖼' if @media.size > 0 end @visibility = @options[:visibility] || @account.user&.setting_default_privacy - @visibility = :unlisted if @visibility == :public && @account.silenced + @visibility = :unlisted if @visibility == :public && @account.silenced? @scheduled_at = @options[:scheduled_at]&.to_datetime @scheduled_at = nil if scheduled_in_the_past? rescue ArgumentError diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 1d9448e21..989cc19a6 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -25,7 +25,7 @@ class ProcessMentionsService < BaseService end end - next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended + next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended? mentions << mentioned_account.mentions.where(status: status).first_or_create(status: status) diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 4ff351c5f..11e33a83a 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -119,9 +119,9 @@ class ResolveAccountService < BaseService Rails.logger.debug "Creating new remote account for #{@username}@#{@domain}" @account = Account.new(username: @username, domain: @domain) - @account.suspended = true if auto_suspend? - @account.silenced = true if auto_silence? - @account.private_key = nil + @account.suspended_at = domain_block.created_at if auto_suspend? + @account.silenced_at = domain_block.created_at if auto_silence? + @account.private_key = nil end def update_account diff --git a/app/services/subscribe_service.rb b/app/services/subscribe_service.rb index 2893b5410..83fd64396 100644 --- a/app/services/subscribe_service.rb +++ b/app/services/subscribe_service.rb @@ -43,7 +43,7 @@ class SubscribeService < BaseService end def some_local_account - @some_local_account ||= Account.local.where(suspended: false).first + @some_local_account ||= Account.local.without_suspended.first end # Any response in the 3xx or 4xx range, except for 429 (rate limit) diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 6c2ecad30..412873f84 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -88,8 +88,8 @@ class SuspendAccountService < BaseService return if @options[:destroy] - @account.silenced = false - @account.suspended = true + @account.silenced_at = nil + @account.suspended_at = @options[:suspended_at] || Time.now.utc @account.locked = false @account.display_name = '' @account.note = '' diff --git a/app/services/unblock_domain_service.rb b/app/services/unblock_domain_service.rb index 946b6d465..9b8526fbe 100644 --- a/app/services/unblock_domain_service.rb +++ b/app/services/unblock_domain_service.rb @@ -3,9 +3,9 @@ class UnblockDomainService < BaseService attr_accessor :domain_block - def call(domain_block, retroactive) + def call(domain_block) @domain_block = domain_block - process_retroactive_updates if retroactive + process_retroactive_updates domain_block.destroy end @@ -14,14 +14,19 @@ class UnblockDomainService < BaseService end def blocked_accounts - Account.where(domain: domain_block.domain) + scope = Account.where(domain: domain_block.domain) + if domain_block.silence? + scope.where(silenced_at: @domain_block.created_at) + else + scope.where(suspended_at: @domain_block.created_at) + end end def update_options - { domain_block_impact => false } + { domain_block_impact => nil } end def domain_block_impact - domain_block.silence? ? :silenced : :suspended + domain_block.silence? ? :silenced_at : :suspended_at end end diff --git a/app/views/admin/domain_blocks/show.html.haml b/app/views/admin/domain_blocks/show.html.haml index ea1929d44..dca4dbac7 100644 --- a/app/views/admin/domain_blocks/show.html.haml +++ b/app/views/admin/domain_blocks/show.html.haml @@ -3,18 +3,11 @@ = simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f| - - if (@domain_block.noop?) - = f.input :retroactive, - as: :hidden, - input_html: { :value => "0" } - - else - = f.input :retroactive, - as: :boolean, - wrapper: :with_label, - label: t(".retroactive.#{@domain_block.severity}"), - hint: t(:affected_accounts, - scope: [:admin, :domain_blocks, :show], - count: @domain_block.accounts_count) + - unless (@domain_block.noop?) + %p= t(".retroactive.#{@domain_block.severity}") + %p.hint= t(:affected_accounts, + scope: [:admin, :domain_blocks, :show], + count: @domain_block.affected_accounts_count) .actions = f.button :button, t('.undo'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f87b34d6..2a5751060 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -294,8 +294,8 @@ en: one: One account in the database affected other: "%{count} accounts in the database affected" retroactive: - silence: Unsilence all existing accounts from this domain - suspend: Unsuspend all existing accounts from this domain + silence: Unsilence existing affected accounts from this domain + suspend: Unsuspend existing affected accounts from this domain title: Undo domain block for %{domain} undo: Undo undo: Undo domain block diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 1ce97639c..e2121098a 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -128,7 +128,7 @@ fr: follow: Envoyer un courriel lorsque quelqu’un me suit follow_request: Envoyer un courriel lorsque quelqu’un demande à me suivre mention: Envoyer un courriel lorsque quelqu’un me mentionne - pending_account: Envoyer un courriel lorsqu'un nouveau compte est en attente d'approbation + pending_account: Envoyer un courriel lorsqu’un nouveau compte est en attente d’approbation reblog: Envoyer un courriel lorsque quelqu’un partage mes statuts report: Envoyer un courriel lorsqu’un nouveau rapport est soumis 'no': Non diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index 17be44e67..28e8629d2 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -28,12 +28,12 @@ sk: scopes: Ktoré API budú povolené aplikácii pre prístup. Ak vyberieš vrcholný stupeň, nemusíš už potom vyberať po jednom. setting_aggregate_reblogs: Nezobrazuj nové vyzdvihnutia pre príspevky, ktoré už boli len nedávno povýšené (týka sa iba nanovo získaných povýšení) setting_default_language: Jazyk tvojích príspevkov môže byť zistený automaticky, ale nieje to vždy presné - setting_display_media_default: Skryť médiá označené ako citlivé - setting_display_media_hide_all: Vždy ukryť všetky médiá - setting_display_media_show_all: Stále ukazuj médiá označené ako citlivé - setting_hide_network: Koho následuješ, a kto následuje teba nebude zobrazené na tvojom profile + setting_display_media_default: Skry médiá označené ako citlivé + setting_display_media_hide_all: Vždy ukry všetky médiá + setting_display_media_show_all: Stále zobrazuj médiá označené ako citlivé + setting_hide_network: Koho následuješ, a kto následuje teba, nebude zobrazené na tvojom profile setting_noindex: Ovplyvňuje verejný profil a stránky s príspevkami - setting_show_application: Aplikácia, ktorú používaš na písanie príspevkov, bude zobrazená v detailnom náhľade jednotlivých tvojích príspevkov + setting_show_application: Aplikácia, ktorú používaš na písanie príspevkov, bude zobrazená v podrobnom náhľade jednotlivých tvojích príspevkov setting_theme: Ovplyvňuje ako Mastodon vyzerá pri prihlásení z hociakého zariadenia. username: Tvoja prezývka bude unikátna pre server %{domain} whole_word: Ak je kľúčové slovo, alebo fráza poskladaná iba s písmen a čísel, bude použité iba ak sa zhoduje s celým výrazom @@ -41,6 +41,8 @@ sk: name: 'Možno by si chcel/a použiť niektoré z týchto:' imports: data: CSV súbor vyexportovaný z iného Mastodon serveru + invite_request: + text: Toto pomôže s vyhodnocovaním tvojej žiadosti sessions: otp: 'Napíš sem dvoj-faktorový kód z telefónu, alebo použi jeden z tvojích obnovovacích kódov:' user: @@ -59,7 +61,7 @@ sk: types: disable: Deaktivuj none: Neurob nič - silence: Utíšenie + silence: Utíš suspend: Vylúč a nenávratne vymaž dáta na účte warning_preset_id: Použi varovnú predlohu defaults: @@ -119,13 +121,14 @@ sk: must_be_following: Blokuj oboznámenia od ľudí, ktorých nesledujem must_be_following_dm: Blokuj súkromné správy od ľudí ktorých nesledujem notification_emails: - digest: Posielaj súhrnné emaily - favourite: Poslať email ak si niekto obľúbi tvoj príspevok - follow: Poslať email, ak ťa niekto začne následovať - follow_request: Zaslať email ak ti niekto pošle žiadosť o sledovanie - mention: Poslať email ak ťa niekto spomenie v svojom príspevku - reblog: Poslať email ak niekto re-tootne tvoj príspevok - report: Poslať e-mail ak niekto dodá nové hlásenie + digest: Zasielať súhrnné emaily + favourite: Zaslať email, ak si niekto obľúbi tvoj príspevok + follow: Zaslať email, ak ťa niekto začne následovať + follow_request: Zaslať email, ak ti niekto pošle žiadosť o sledovanie + mention: Zaslať email, ak ťa niekto spomenie vo svojom príspevku + pending_account: Zaslať email, ak treba prehodnotiť nový účet + reblog: Zaslať email, ak niekto re-tootne tvoj príspevok + report: Zaslať email, ak niekto podá nové nahlásenie 'no': Nie required: mark: "*" diff --git a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb new file mode 100644 index 000000000..1e5cd669c --- /dev/null +++ b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb @@ -0,0 +1,41 @@ +class AddSilencedAtSuspendedAtToAccounts < ActiveRecord::Migration[5.2] + class Account < ApplicationRecord + # Dummy class, to make migration possible across version changes + end + + class DomainBlock < ApplicationRecord + # Dummy class, to make migration possible across version changes + enum severity: [:silence, :suspend, :noop] + + has_many :accounts, foreign_key: :domain, primary_key: :domain + end + + def up + add_column :accounts, :silenced_at, :datetime + add_column :accounts, :suspended_at, :datetime + + # Record suspend date of blocks and silences for users whose limitations match + # a domain block + DomainBlock.where(severity: [:silence, :suspend]).find_each do |block| + scope = block.accounts + if block.suspend? + block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at) + else + block.accounts.where(silenced: true).in_batches.update_all(silenced_at: block.created_at) + end + end + + # Set dates for accounts which have limitations not related to a domain block + Account.where(suspended: true, suspended_at: nil).in_batches.update_all(suspended_at: Time.now.utc) + Account.where(silenced: true, silenced_at: nil).in_batches.update_all(silenced_at: Time.now.utc) + end + + def down + # Block or silence accounts that have a date set + Account.where(suspended: false).where.not(suspended_at: nil).in_batches.update_all(suspended: true) + Account.where(silenced: false).where.not(silenced_at: nil).in_batches.update_all(silenced: true) + + remove_column :accounts, :silenced_at + remove_column :accounts, :suspended_at + end +end diff --git a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb new file mode 100644 index 000000000..a46349cb7 --- /dev/null +++ b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class RemoveSuspendedSilencedAccountFields < ActiveRecord::Migration[5.2] + class Account < ApplicationRecord + # Dummy class, to make migration possible across version changes + end + + class DomainBlock < ApplicationRecord + # Dummy class, to make migration possible across version changes + enum severity: [:silence, :suspend, :noop] + + has_many :accounts, foreign_key: :domain, primary_key: :domain + end + + disable_ddl_transaction! + + def up + # Record suspend date of blocks and silences for users whose limitations match + # a domain block + DomainBlock.where(severity: [:silence, :suspend]).find_each do |block| + scope = block.accounts + if block.suspend? + block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at) + else + block.accounts.where(silenced: true).in_batches.update_all(silenced_at: block.created_at) + end + end + + # Set dates for accounts which have limitations not related to a domain block + Account.where(suspended: true, suspended_at: nil).in_batches.update_all(suspended_at: Time.now.utc) + Account.where(silenced: true, silenced_at: nil).in_batches.update_all(silenced_at: Time.now.utc) + + safety_assured do + remove_column :accounts, :suspended, :boolean, null: false, default: false + remove_column :accounts, :silenced, :boolean, null: false, default: false + end + end + + def down + safety_assured do + add_column :accounts, :suspended, :boolean, null: false, default: false + add_column :accounts, :silenced, :boolean, null: false, default: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 43d589f3a..75a67c6c3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_05_09_164208) do +ActiveRecord::Schema.define(version: 2019_05_11_152737) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -131,8 +131,6 @@ ActiveRecord::Schema.define(version: 2019_05_09_164208) do t.datetime "header_updated_at" t.string "avatar_remote_url" t.datetime "subscription_expires_at" - t.boolean "silenced", default: false, null: false - t.boolean "suspended", default: false, null: false t.boolean "locked", default: false, null: false t.string "header_remote_url", default: "", null: false t.datetime "last_webfingered_at" @@ -148,6 +146,8 @@ ActiveRecord::Schema.define(version: 2019_05_09_164208) do t.string "actor_type" t.boolean "discoverable" t.string "also_known_as", array: true + t.datetime "silenced_at" + t.datetime "suspended_at" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" diff --git a/lib/cli.rb b/lib/cli.rb index 5780e3e87..be276583d 100644 --- a/lib/cli.rb +++ b/lib/cli.rb @@ -106,7 +106,7 @@ module Mastodon [json, account.id, inbox_url] end - account.update_column(:suspended, true) + account.suspend! end processed += 1 diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 3131647f3..7d0215313 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -87,8 +87,8 @@ module Mastodon end end - account.suspended = false - user.account = account + account.suspended_at = nil + user.account = account if user.save if options[:confirmed] diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb index 2a8675c21..fb23658c0 100644 --- a/spec/controllers/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/domain_blocks_controller_spec.rb @@ -63,9 +63,9 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do service = double(call: true) allow(UnblockDomainService).to receive(:new).and_return(service) domain_block = Fabricate(:domain_block) - delete :destroy, params: { id: domain_block.id, domain_block: { retroactive: '1' } } + delete :destroy, params: { id: domain_block.id } - expect(service).to have_received(:call).with(domain_block, true) + expect(service).to have_received(:call).with(domain_block) expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.destroyed_msg') expect(response).to redirect_to(admin_instances_path(limited: '1')) end diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb index e092e6c09..f12464ef3 100644 --- a/spec/fabricators/account_fabricator.rb +++ b/spec/fabricators/account_fabricator.rb @@ -3,8 +3,11 @@ public_key = keypair.public_key.to_pem private_key = keypair.to_pem Fabricator(:account) do + transient :suspended, :silenced username { sequence(:username) { |i| "#{Faker::Internet.user_name(nil, %w(_))}#{i}" } } last_webfingered_at { Time.now.utc } public_key { public_key } private_key { private_key } + suspended_at { |attrs| attrs[:suspended] ? Time.now.utc : nil } + silenced_at { |attrs| attrs[:silenced] ? Time.now.utc : nil } end diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index df92094d1..13850f807 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -175,13 +175,13 @@ RSpec.describe FeedManager do it 'returns true for status by silenced account who recipient is not following' do status = Fabricate(:status, text: 'Hello world', account: alice) - alice.update(silenced: true) + alice.silence! expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be true end it 'returns false for status by followed silenced account' do status = Fabricate(:status, text: 'Hello world', account: alice) - alice.update(silenced: true) + alice.silence! bob.follow!(alice) expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be false end diff --git a/spec/lib/status_filter_spec.rb b/spec/lib/status_filter_spec.rb index db2d87de2..a851014d9 100644 --- a/spec/lib/status_filter_spec.rb +++ b/spec/lib/status_filter_spec.rb @@ -15,7 +15,7 @@ describe StatusFilter do context 'when status account is silenced' do before do - status.account.update(silenced: true) + status.account.silence! end it { is_expected.to be_filtered } @@ -65,7 +65,7 @@ describe StatusFilter do context 'when status account is silenced' do before do - status.account.update(silenced: true) + status.account.silence! end it { is_expected.to be_filtered } diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb index 94c2d5fc2..50286ef77 100644 --- a/spec/models/concerns/status_threading_concern_spec.rb +++ b/spec/models/concerns/status_threading_concern_spec.rb @@ -35,7 +35,7 @@ describe StatusThreadingConcern do end it 'does not return conversation history from silenced and not followed users' do - jeff.update(silenced: true) + jeff.silence! expect(reply3.ancestors(4, viewer)).to_not include(reply1) end @@ -110,7 +110,7 @@ describe StatusThreadingConcern do end it 'does not return replies from silenced and not followed users' do - jeff.update(silenced: true) + jeff.silence! expect(status.descendants(4, viewer)).to_not include(reply3) end diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb index 7ef9e2770..c689b57e3 100644 --- a/spec/services/block_domain_service_spec.rb +++ b/spec/services/block_domain_service_spec.rb @@ -1,20 +1,14 @@ require 'rails_helper' RSpec.describe BlockDomainService, type: :service do - let(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } - let(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') } - let(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') } - let(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) } + let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } + let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') } + let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') } + let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) } + let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) } subject { BlockDomainService.new } - before do - bad_account - bad_status1 - bad_status2 - bad_attachment - end - describe 'for a suspension' do before do subject.call(DomainBlock.create!(domain: 'evil.org', severity: :suspend)) @@ -28,6 +22,18 @@ RSpec.describe BlockDomainService, type: :service do expect(Account.find_remote('badguy666', 'evil.org').suspended?).to be true end + it 'records suspension date appropriately' do + expect(Account.find_remote('badguy666', 'evil.org').suspended_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at + end + + it 'keeps already-banned accounts banned' do + expect(Account.find_remote('badguy', 'evil.org').suspended?).to be true + end + + it 'does not overwrite suspension date of already-banned accounts' do + expect(Account.find_remote('badguy', 'evil.org').suspended_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at + end + it 'removes the remote accounts\'s statuses and media attachments' do expect { bad_status1.reload }.to raise_exception ActiveRecord::RecordNotFound expect { bad_status2.reload }.to raise_exception ActiveRecord::RecordNotFound @@ -48,6 +54,18 @@ RSpec.describe BlockDomainService, type: :service do expect(Account.find_remote('badguy666', 'evil.org').silenced?).to be true end + it 'records suspension date appropriately' do + expect(Account.find_remote('badguy666', 'evil.org').silenced_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at + end + + it 'keeps already-banned accounts banned' do + expect(Account.find_remote('badguy', 'evil.org').silenced?).to be true + end + + it 'does not overwrite suspension date of already-banned accounts' do + expect(Account.find_remote('badguy', 'evil.org').silenced_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at + end + it 'leaves the domains status and attachements, but clears media' do expect { bad_status1.reload }.not_to raise_error expect { bad_status2.reload }.not_to raise_error diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 7bfca7fb5..a68fdd8df 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -39,12 +39,12 @@ RSpec.describe NotifyService, type: :service do end it 'does not notify when sender is silenced and not followed' do - sender.update(silenced: true) + sender.silence! is_expected.to_not change(Notification, :count) end it 'does not notify when recipient is suspended' do - recipient.update(suspended: true) + recipient.suspend! is_expected.to_not change(Notification, :count) end diff --git a/spec/services/unblock_domain_service_spec.rb b/spec/services/unblock_domain_service_spec.rb index 8e8893d63..619aefb5c 100644 --- a/spec/services/unblock_domain_service_spec.rb +++ b/spec/services/unblock_domain_service_spec.rb @@ -7,36 +7,33 @@ describe UnblockDomainService, type: :service do describe 'call' do before do - @silenced = Fabricate(:account, domain: 'example.com', silenced: true) - @suspended = Fabricate(:account, domain: 'example.com', suspended: true) + @independently_suspended = Fabricate(:account, domain: 'example.com', suspended_at: 1.hour.ago) + @independently_silenced = Fabricate(:account, domain: 'example.com', silenced_at: 1.hour.ago) @domain_block = Fabricate(:domain_block, domain: 'example.com') + @silenced = Fabricate(:account, domain: 'example.com', silenced_at: @domain_block.created_at) + @suspended = Fabricate(:account, domain: 'example.com', suspended_at: @domain_block.created_at) end - context 'without retroactive' do - it 'removes the domain block' do - subject.call(@domain_block, false) - expect_deleted_domain_block - end - end - - context 'with retroactive' do - it 'unsilences accounts and removes block' do - @domain_block.update(severity: :silence) + it 'unsilences accounts and removes block' do + @domain_block.update(severity: :silence) - subject.call(@domain_block, true) - expect_deleted_domain_block - expect(@silenced.reload.silenced).to be false - expect(@suspended.reload.suspended).to be true - end + subject.call(@domain_block) + expect_deleted_domain_block + expect(@silenced.reload.silenced?).to be false + expect(@suspended.reload.suspended?).to be true + expect(@independently_suspended.reload.suspended?).to be true + expect(@independently_silenced.reload.silenced?).to be true + end - it 'unsuspends accounts and removes block' do - @domain_block.update(severity: :suspend) + it 'unsuspends accounts and removes block' do + @domain_block.update(severity: :suspend) - subject.call(@domain_block, true) - expect_deleted_domain_block - expect(@suspended.reload.suspended).to be false - expect(@silenced.reload.silenced).to be true - end + subject.call(@domain_block) + expect_deleted_domain_block + expect(@suspended.reload.suspended?).to be false + expect(@silenced.reload.silenced?).to be true + expect(@independently_suspended.reload.suspended?).to be true + expect(@independently_silenced.reload.silenced?).to be true end end |