about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2018-08-21 18:24:48 +0200
committerThibaut Girka <thib@sitedethib.com>2018-08-21 18:24:48 +0200
commit8b4abaa90d8dff64525d92c3ba1c750356608240 (patch)
tree51f194442d2ec7ab760b443b98c7685f64481ca7 /app
parentc78918162642649ce294d89effbf2e815c2aa327 (diff)
parentf06fa099625e928e5858ea81a20be1eddf6c6fbb (diff)
Merge branch 'master' into glitch-soc/master
Conflicts:
	config/routes.rb

Added the “endorsements” route from upstream.
Diffstat (limited to 'app')
-rw-r--r--app/controllers/accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/endorsements_controller.rb72
-rw-r--r--app/controllers/api/v1/statuses_controller.rb3
-rw-r--r--app/controllers/application_controller.rb13
-rw-r--r--app/controllers/emojis_controller.rb2
-rw-r--r--app/controllers/statuses_controller.rb4
-rw-r--r--app/javascript/mastodon/actions/timelines.js2
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js8
-rw-r--r--app/javascript/mastodon/features/ui/index.js6
-rw-r--r--app/javascript/mastodon/selectors/index.js3
-rw-r--r--app/javascript/styles/mastodon/components.scss34
-rw-r--r--app/javascript/styles/mastodon/forms.scss5
-rw-r--r--app/models/account_pin.rb1
-rw-r--r--app/models/form/status_batch.rb4
-rw-r--r--app/models/notification.rb6
-rw-r--r--app/models/status.rb2
-rw-r--r--app/services/after_block_domain_from_account_service.rb4
-rw-r--r--app/services/backup_service.rb4
-rw-r--r--app/services/block_domain_service.rb6
-rw-r--r--app/services/remove_status_service.rb4
-rw-r--r--app/services/suspend_account_service.rb8
-rw-r--r--app/workers/maintenance/uncache_media_worker.rb2
-rw-r--r--app/workers/refollow_worker.rb2
-rw-r--r--app/workers/scheduler/backup_cleanup_scheduler.rb4
-rw-r--r--app/workers/scheduler/doorkeeper_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/email_scheduler.rb4
-rw-r--r--app/workers/scheduler/feed_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/ip_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/media_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/subscriptions_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/subscriptions_scheduler.rb2
-rw-r--r--app/workers/scheduler/user_cleanup_scheduler.rb4
33 files changed, 174 insertions, 49 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 3d20f0e88..85d9e784a 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -43,7 +43,7 @@ class AccountsController < ApplicationController
       format.json do
         skip_session!
 
-        render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
+        render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
           ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
         end
       end
diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb
new file mode 100644
index 000000000..0f04b488f
--- /dev/null
+++ b/app/controllers/api/v1/endorsements_controller.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+class Api::V1::EndorsementsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
+  before_action :require_user!
+  after_action :insert_pagination_headers
+
+  respond_to :json
+
+  def index
+    @accounts = load_accounts
+    render json: @accounts, each_serializer: REST::AccountSerializer
+  end
+
+  private
+
+  def load_accounts
+    if unlimited?
+      endorsed_accounts.all
+    else
+      endorsed_accounts.paginate_by_max_id(
+        limit_param(DEFAULT_ACCOUNTS_LIMIT),
+        params[:max_id],
+        params[:since_id]
+      )
+    end
+  end
+
+  def endorsed_accounts
+    current_account.endorsed_accounts
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    return if unlimited?
+
+    if records_continue?
+      api_v1_endorsements_url pagination_params(max_id: pagination_max_id)
+    end
+  end
+
+  def prev_path
+    return if unlimited?
+
+    unless @accounts.empty?
+      api_v1_endorsements_url pagination_params(since_id: pagination_since_id)
+    end
+  end
+
+  def pagination_max_id
+    @accounts.last.id
+  end
+
+  def pagination_since_id
+    @accounts.first.id
+  end
+
+  def records_continue?
+    @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+
+  def unlimited?
+    params[:limit] == '0'
+  end
+end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index c6925d462..49a52f7a6 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -17,8 +17,7 @@ class Api::V1::StatusesController < Api::BaseController
   CONTEXT_LIMIT = 4_096
 
   def show
-    cached  = Rails.cache.read(@status.cache_key)
-    @status = cached unless cached.nil?
+    @status = cache_collection([@status], Status).first
     render json: @status, serializer: REST::StatusSerializer
   end
 
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index cc92894a5..27cd0f4f9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -178,12 +178,8 @@ class ApplicationController < ActionController::Base
     return raw unless klass.respond_to?(:with_includes)
 
     raw                    = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
-    uncached_ids           = []
-    cached_keys_with_value = Rails.cache.read_multi(*raw.map(&:cache_key))
-
-    raw.each do |item|
-      uncached_ids << item.id unless cached_keys_with_value.key?(item.cache_key)
-    end
+    cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
+    uncached_ids           = raw.map(&:id) - cached_keys_with_value.keys
 
     klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
 
@@ -191,11 +187,11 @@ class ApplicationController < ActionController::Base
       uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h
 
       uncached.each_value do |item|
-        Rails.cache.write(item.cache_key, item)
+        Rails.cache.write(item, item)
       end
     end
 
-    raw.map { |item| cached_keys_with_value[item.cache_key] || uncached[item.id] }.compact
+    raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
   end
 
   def respond_with_error(code)
@@ -211,7 +207,6 @@ class ApplicationController < ActionController::Base
 
   def render_cached_json(cache_key, **options)
     options[:expires_in] ||= 3.minutes
-    cache_key              = cache_key.join(':') if cache_key.is_a?(Enumerable)
     cache_public           = options.key?(:public) ? options.delete(:public) : true
     content_type           = options.delete(:content_type) || 'application/json'
 
diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb
index c9725ccc0..5d306e600 100644
--- a/app/controllers/emojis_controller.rb
+++ b/app/controllers/emojis_controller.rb
@@ -9,7 +9,7 @@ class EmojisController < ApplicationController
       format.json do
         skip_session!
 
-        render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do
+        render_cached_json(['activitypub', 'emoji', @emoji], content_type: 'application/activity+json') do
           ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
         end
       end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 1940aaa1b..bc711a8e4 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -34,7 +34,7 @@ class StatusesController < ApplicationController
       format.json do
         skip_session! unless @stream_entry.hidden?
 
-        render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
+        render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
           ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
         end
       end
@@ -44,7 +44,7 @@ class StatusesController < ApplicationController
   def activity
     skip_session!
 
-    render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
+    render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
       ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
     end
   end
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index 11a199db6..e8fd441e1 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -55,7 +55,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
       return;
     }
 
-    if (!params.max_id && timeline.get('items', ImmutableList()).size > 0) {
+    if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
       params.since_id = timeline.getIn(['items', 0]);
     }
 
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 0a6e7c627..e83f724e9 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -137,7 +137,7 @@ class DropdownMenu extends React.PureComponent {
           // It should not be transformed when mounting because the resulting
           // size will be used to determine the coordinate of the menu by
           // react-overlays
-          <div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
+          <div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
             <div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
 
             <ul>
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index a772c1c95..e19778fd2 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -28,6 +28,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
     style: PropTypes.object,
     items: PropTypes.array.isRequired,
     value: PropTypes.string.isRequired,
+    placement: PropTypes.string.isRequired,
     onClose: PropTypes.func.isRequired,
     onChange: PropTypes.func.isRequired,
   };
@@ -119,7 +120,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
 
   render () {
     const { mounted } = this.state;
-    const { style, items, value } = this.props;
+    const { style, items, placement, value } = this.props;
 
     return (
       <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
@@ -127,7 +128,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
           // It should not be transformed when mounting because the resulting
           // size will be used to determine the coordinate of the menu by
           // react-overlays
-          <div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
+          <div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
             {items.map(item => (
               <div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
                 <div className='privacy-dropdown__option__icon'>
@@ -226,7 +227,7 @@ export default class PrivacyDropdown extends React.PureComponent {
     const valueOption = this.options.find(item => item.value === value);
 
     return (
-      <div className={classNames('privacy-dropdown', { active: open })} onKeyDown={this.handleKeyDown}>
+      <div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
         <div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}>
           <IconButton
             className='privacy-dropdown__value-icon'
@@ -247,6 +248,7 @@ export default class PrivacyDropdown extends React.PureComponent {
             value={value}
             onClose={this.handleClose}
             onChange={this.handleChange}
+            placement={placement}
           />
         </Overlay>
       </div>
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 67484fc63..34d52a7d2 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -89,6 +89,7 @@ const keyMap = {
   goToProfile: 'g u',
   goToBlocked: 'g b',
   goToMuted: 'g m',
+  goToRequests: 'g r',
   toggleHidden: 'x',
 };
 
@@ -427,6 +428,10 @@ export default class UI extends React.PureComponent {
     this.context.router.history.push('/mutes');
   }
 
+  handleHotkeyGoToRequests = () => {
+    this.context.router.history.push('/follow_requests');
+  }
+
   render () {
     const { draggingOver } = this.state;
     const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
@@ -449,6 +454,7 @@ export default class UI extends React.PureComponent {
       goToProfile: this.handleHotkeyGoToProfile,
       goToBlocked: this.handleHotkeyGoToBlocked,
       goToMuted: this.handleHotkeyGoToMuted,
+      goToRequests: this.handleHotkeyGoToRequests,
     };
 
     return (
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index 106198f74..70f08a8eb 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -1,5 +1,6 @@
 import { createSelector } from 'reselect';
 import { List as ImmutableList } from 'immutable';
+import { me } from '../initial_state';
 
 const getAccountBase         = (state, id) => state.getIn(['accounts', id], null);
 const getAccountCounters     = (state, id) => state.getIn(['accounts_counters', id], null);
@@ -83,7 +84,7 @@ export const makeGetStatus = () => {
         statusReblog = null;
       }
 
-      const regex    = regexFromFilters(filters);
+      const regex    = (accountReblog || accountBase).get('id') !== me && regexFromFilters(filters);
       const filtered = regex && regex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'));
 
       return statusBase.withMutations(map => {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 64a00c2c3..6073f9c0e 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -230,7 +230,6 @@
 
 .dropdown-menu {
   position: absolute;
-  transform-origin: 50% 0;
 }
 
 .invisible {
@@ -1634,6 +1633,22 @@ a.account__display-name {
   ul {
     list-style: none;
   }
+
+  &.left {
+    transform-origin: 100% 50%;
+  }
+
+  &.top {
+    transform-origin: 50% 100%;
+  }
+
+  &.bottom {
+    transform-origin: 50% 0;
+  }
+
+  &.right {
+    transform-origin: 0 50%;
+  }
 }
 
 .dropdown-menu__arrow {
@@ -3300,7 +3315,14 @@ a.status-card {
   border-radius: 4px;
   margin-left: 40px;
   overflow: hidden;
-  transform-origin: 50% 0;
+
+  &.top {
+    transform-origin: 50% 100%;
+  }
+
+  &.bottom {
+    transform-origin: 50% 0;
+  }
 }
 
 .privacy-dropdown__option {
@@ -3372,6 +3394,10 @@ a.status-card {
     }
   }
 
+  &.top .privacy-dropdown__value {
+    border-radius: 0 0 4px 4px;
+  }
+
   .privacy-dropdown__dropdown {
     display: block;
     box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1);
@@ -4176,6 +4202,10 @@ a.status-card {
     color: $highlight-text-color;
   }
 
+  .status__content p {
+    color: $inverted-text-color;
+  }
+
   @media screen and (max-width: 480px) {
     max-height: 10vh;
   }
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 375c7b64b..22dbfa8cf 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -154,9 +154,8 @@ code {
       margin-bottom: 15px;
     }
 
-    li {
-      float: left;
-      width: 50%;
+    ul {
+      columns: 2;
     }
   }
 
diff --git a/app/models/account_pin.rb b/app/models/account_pin.rb
index 9a21c3405..b51d3d4cd 100644
--- a/app/models/account_pin.rb
+++ b/app/models/account_pin.rb
@@ -11,6 +11,7 @@
 #
 
 class AccountPin < ApplicationRecord
+  include Paginable
   include RelationshipCacheable
 
   belongs_to :account
diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb
index 4f08a3049..8f5fd1fa2 100644
--- a/app/models/form/status_batch.rb
+++ b/app/models/form/status_batch.rb
@@ -23,7 +23,7 @@ class Form::StatusBatch
     media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id)
 
     ApplicationRecord.transaction do
-      Status.where(id: media_attached_status_ids).find_each do |status|
+      Status.where(id: media_attached_status_ids).reorder(nil).find_each do |status|
         status.update!(sensitive: sensitive)
         log_action :update, status
       end
@@ -35,7 +35,7 @@ class Form::StatusBatch
   end
 
   def delete_statuses
-    Status.where(id: status_ids).find_each do |status|
+    Status.where(id: status_ids).reorder(nil).find_each do |status|
       RemovalWorker.perform_async(status.id)
       log_action :destroy, status
     end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 4f6ec8e8e..b9bec0808 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -39,8 +39,6 @@ class Notification < ApplicationRecord
   validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
   validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values }
 
-  scope :cache_ids, -> { select(:id, :updated_at, :activity_type, :activity_id) }
-
   scope :browserable, ->(exclude_types = []) {
     types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types + [:follow_request])
     where(activity_type: types)
@@ -68,6 +66,10 @@ class Notification < ApplicationRecord
   end
 
   class << self
+    def cache_ids
+      select(:id, :updated_at, :activity_type, :activity_id)
+    end
+
     def reload_stale_associations!(cached_items)
       account_ids = (cached_items.map(&:from_account_id) + cached_items.map { |item| item.target_status&.account_id }.compact).uniq
 
diff --git a/app/models/status.rb b/app/models/status.rb
index 8cd6d3862..f38227b85 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -26,8 +26,6 @@
 #
 
 class Status < ApplicationRecord
-  self.cache_versioning = false
-
   include Paginable
   include Streamable
   include Cacheable
diff --git a/app/services/after_block_domain_from_account_service.rb b/app/services/after_block_domain_from_account_service.rb
index 0f1a8505d..56cc819fb 100644
--- a/app/services/after_block_domain_from_account_service.rb
+++ b/app/services/after_block_domain_from_account_service.rb
@@ -15,13 +15,13 @@ class AfterBlockDomainFromAccountService < BaseService
   private
 
   def reject_existing_followers!
-    @account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).find_each do |follow|
+    @account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).find_each do |follow|
       reject_follow!(follow)
     end
   end
 
   def reject_pending_follow_requests!
-    FollowRequest.where(target_account: @account).where(account: Account.where(domain: @domain)).includes(:account).find_each do |follow_request|
+    FollowRequest.where(target_account: @account).where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).find_each do |follow_request|
       reject_follow!(follow_request)
     end
   end
diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb
index 8492c1117..da7db6462 100644
--- a/app/services/backup_service.rb
+++ b/app/services/backup_service.rb
@@ -18,7 +18,7 @@ class BackupService < BaseService
   def build_json!
     @collection = serialize(collection_presenter, ActivityPub::CollectionSerializer)
 
-    account.statuses.with_includes.find_in_batches do |statuses|
+    account.statuses.with_includes.reorder(nil).find_in_batches do |statuses|
       statuses.each do |status|
         item = serialize(status, ActivityPub::ActivitySerializer)
         item.delete(:'@context')
@@ -60,7 +60,7 @@ class BackupService < BaseService
   end
 
   def dump_media_attachments!(tar)
-    MediaAttachment.attached.where(account: account).find_in_batches do |media_attachments|
+    MediaAttachment.attached.where(account: account).reorder(nil).find_in_batches do |media_attachments|
       media_attachments.each do |m|
         download_to_tar(tar, m.file, m.file.path)
       end
diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb
index d082de40b..a1fe93665 100644
--- a/app/services/block_domain_service.rb
+++ b/app/services/block_domain_service.rb
@@ -43,14 +43,14 @@ class BlockDomainService < BaseService
   end
 
   def suspend_accounts!
-    blocked_domain_accounts.where(suspended: false).find_each do |account|
+    blocked_domain_accounts.where(suspended: false).reorder(nil).find_each do |account|
       UnsubscribeService.new.call(account) if account.subscribed?
       SuspendAccountService.new.call(account)
     end
   end
 
   def clear_account_images!
-    blocked_domain_accounts.find_each do |account|
+    blocked_domain_accounts.reorder(nil).find_each do |account|
       account.avatar.destroy if account.avatar.exists?
       account.header.destroy if account.header.exists?
       account.save
@@ -58,7 +58,7 @@ class BlockDomainService < BaseService
   end
 
   def clear_account_attachments!
-    media_from_blocked_domain.find_each do |attachment|
+    media_from_blocked_domain.reorder(nil).find_each do |attachment|
       @affected_status_ids << attachment.status_id if attachment.status_id.present?
 
       attachment.file.destroy if attachment.file.exists?
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index fb889140b..1a53093b8 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -43,13 +43,13 @@ class RemoveStatusService < BaseService
   end
 
   def remove_from_followers
-    @account.followers_for_local_distribution.find_each do |follower|
+    @account.followers_for_local_distribution.reorder(nil).find_each do |follower|
       FeedManager.instance.unpush_from_home(follower, @status)
     end
   end
 
   def remove_from_lists
-    @account.lists_for_local_distribution.select(:id, :account_id).find_each do |list|
+    @account.lists_for_local_distribution.select(:id, :account_id).reorder(nil).find_each do |list|
       FeedManager.instance.unpush_from_list(list, @status)
     end
   end
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index 0a98f5fb9..8fc79b8ad 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -23,9 +23,7 @@ class SuspendAccountService < BaseService
 
   def purge_content!
     if @account.local?
-      ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id)
-
-      ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
+      ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url|
         [delete_actor_json, @account.id, inbox_url]
       end
     end
@@ -75,4 +73,8 @@ class SuspendAccountService < BaseService
 
     @delete_actor_json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
   end
+
+  def delivery_inboxes
+    Account.inboxes + Relay.enabled.pluck(:inbox_url)
+  end
 end
diff --git a/app/workers/maintenance/uncache_media_worker.rb b/app/workers/maintenance/uncache_media_worker.rb
index f6a51a1b8..2d1a670a7 100644
--- a/app/workers/maintenance/uncache_media_worker.rb
+++ b/app/workers/maintenance/uncache_media_worker.rb
@@ -8,7 +8,7 @@ class Maintenance::UncacheMediaWorker
   def perform(media_attachment_id)
     media = MediaAttachment.find(media_attachment_id)
 
-    return unless media.file.exists?
+    return if media.file.blank?
 
     media.file.destroy
     media.save
diff --git a/app/workers/refollow_worker.rb b/app/workers/refollow_worker.rb
index 66bcd27c3..12f2bf671 100644
--- a/app/workers/refollow_worker.rb
+++ b/app/workers/refollow_worker.rb
@@ -9,7 +9,7 @@ class RefollowWorker
     target_account = Account.find(target_account_id)
     return unless target_account.protocol == :activitypub
 
-    target_account.followers.where(domain: nil).find_each do |follower|
+    target_account.followers.where(domain: nil).reorder(nil).find_each do |follower|
       # Locally unfollow remote account
       follower.unfollow!(target_account)
 
diff --git a/app/workers/scheduler/backup_cleanup_scheduler.rb b/app/workers/scheduler/backup_cleanup_scheduler.rb
index 5ab16c057..023a77307 100644
--- a/app/workers/scheduler/backup_cleanup_scheduler.rb
+++ b/app/workers/scheduler/backup_cleanup_scheduler.rb
@@ -3,8 +3,10 @@
 class Scheduler::BackupCleanupScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
-    old_backups.find_each(&:destroy!)
+    old_backups.reorder(nil).find_each(&:destroy!)
   end
 
   private
diff --git a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
index bab4ae886..fec08c6bc 100644
--- a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
+++ b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
@@ -3,6 +3,8 @@
 class Scheduler::DoorkeeperCleanupScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
     Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
     Doorkeeper::AccessGrant.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
diff --git a/app/workers/scheduler/email_scheduler.rb b/app/workers/scheduler/email_scheduler.rb
index 36866061b..24117e424 100644
--- a/app/workers/scheduler/email_scheduler.rb
+++ b/app/workers/scheduler/email_scheduler.rb
@@ -3,8 +3,10 @@
 class Scheduler::EmailScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
-    eligible_users.find_each do |user|
+    eligible_users.reorder(nil).find_each do |user|
       next unless user.allows_digest_emails?
       DigestMailerWorker.perform_async(user.id)
     end
diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb
index 42cf14128..b02bac883 100644
--- a/app/workers/scheduler/feed_cleanup_scheduler.rb
+++ b/app/workers/scheduler/feed_cleanup_scheduler.rb
@@ -3,6 +3,8 @@
 class Scheduler::FeedCleanupScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
     clean_home_feeds!
     clean_list_feeds!
diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb
index 613a5e336..6bb93df7d 100644
--- a/app/workers/scheduler/ip_cleanup_scheduler.rb
+++ b/app/workers/scheduler/ip_cleanup_scheduler.rb
@@ -5,6 +5,8 @@ class Scheduler::IpCleanupScheduler
 
   RETENTION_PERIOD = 1.year
 
+  sidekiq_options unique: :until_executed
+
   def perform
     time_ago = RETENTION_PERIOD.ago
     SessionActivation.where('updated_at < ?', time_ago).destroy_all
diff --git a/app/workers/scheduler/media_cleanup_scheduler.rb b/app/workers/scheduler/media_cleanup_scheduler.rb
index c35686fcb..a27e02953 100644
--- a/app/workers/scheduler/media_cleanup_scheduler.rb
+++ b/app/workers/scheduler/media_cleanup_scheduler.rb
@@ -3,6 +3,8 @@
 class Scheduler::MediaCleanupScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
     unattached_media.find_each(&:destroy)
   end
diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
index af2ae3120..06ba66205 100644
--- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
@@ -3,6 +3,8 @@
 class Scheduler::SubscriptionsCleanupScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
     Subscription.expired.in_batches.delete_all
   end
diff --git a/app/workers/scheduler/subscriptions_scheduler.rb b/app/workers/scheduler/subscriptions_scheduler.rb
index dc16e85c2..4b0959af2 100644
--- a/app/workers/scheduler/subscriptions_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_scheduler.rb
@@ -3,6 +3,8 @@
 class Scheduler::SubscriptionsScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
     Pubsubhubbub::SubscribeWorker.push_bulk(expiring_accounts.pluck(:id))
   end
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index 245536cea..626fb1652 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -3,8 +3,10 @@
 class Scheduler::UserCleanupScheduler
   include Sidekiq::Worker
 
+  sidekiq_options unique: :until_executed
+
   def perform
-    User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).find_in_batches do |batch|
+    User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch|
       Account.where(id: batch.map(&:account_id)).delete_all
       User.where(id: batch.map(&:id)).delete_all
     end