about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock16
-rw-r--r--app/controllers/admin/domain_blocks_controller.rb8
-rw-r--r--app/controllers/home_controller.rb2
-rw-r--r--app/javascript/flavours/glitch/styles/components/composer.scss65
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss5
-rw-r--r--app/javascript/mastodon/components/status.js1
-rw-r--r--app/javascript/mastodon/reducers/compose.js2
-rw-r--r--app/javascript/styles/mastodon-light/diff.scss3
-rw-r--r--app/javascript/styles/mastodon/components.scss9
-rw-r--r--app/lib/formatter.rb19
-rw-r--r--app/models/account.rb40
-rw-r--r--app/models/concerns/account_finder_concern.rb2
-rw-r--r--app/models/domain_block.rb7
-rw-r--r--app/models/status.rb4
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/activitypub/process_account_service.rb12
-rw-r--r--app/services/block_domain_service.rb6
-rw-r--r--app/services/post_status_service.rb2
-rw-r--r--app/services/process_mentions_service.rb2
-rw-r--r--app/services/resolve_account_service.rb6
-rw-r--r--app/services/subscribe_service.rb2
-rw-r--r--app/services/suspend_account_service.rb4
-rw-r--r--app/services/unblock_domain_service.rb15
-rw-r--r--app/views/admin/domain_blocks/show.html.haml17
-rw-r--r--config/locales/en.yml4
-rw-r--r--config/locales/simple_form.fr.yml2
-rw-r--r--config/locales/simple_form.sk.yml29
-rw-r--r--db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb41
-rw-r--r--db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb45
-rw-r--r--db/schema.rb6
-rw-r--r--lib/cli.rb2
-rw-r--r--lib/mastodon/accounts_cli.rb4
-rw-r--r--spec/controllers/admin/domain_blocks_controller_spec.rb4
-rw-r--r--spec/fabricators/account_fabricator.rb3
-rw-r--r--spec/lib/feed_manager_spec.rb4
-rw-r--r--spec/lib/status_filter_spec.rb4
-rw-r--r--spec/models/concerns/status_threading_concern_spec.rb4
-rw-r--r--spec/services/block_domain_service_spec.rb40
-rw-r--r--spec/services/notify_service_spec.rb4
-rw-r--r--spec/services/unblock_domain_service_spec.rb45
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