From 422b59808b4818171e057e1a459de153816bf505 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Aug 2019 21:55:56 +0200 Subject: port tootsuite#11623 to monsterfork: Add soft delete for statuses for instant deletes through API * Add soft delete for statuses to allow them to appear instant * Allow reporting soft-deleted statuses and show them in the admin UI * Change index for getting an account's statuses --- app/controllers/api/v1/reports_controller.rb | 2 +- app/controllers/api/v1/statuses_controller.rb | 1 + app/models/form/status_batch.rb | 1 + app/models/report.rb | 2 +- app/models/status.rb | 5 ++++- app/views/admin/reports/_status.html.haml | 5 ++++- app/workers/removal_worker.rb | 2 +- config/locales/en.yml | 1 + db/migrate/20190819134503_add_deleted_at_to_statuses.rb | 5 +++++ db/migrate/20190820003045_update_statuses_index.rb | 13 +++++++++++++ 10 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20190819134503_add_deleted_at_to_statuses.rb create mode 100644 db/migrate/20190820003045_update_statuses_index.rb diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index f266f5828..734a0d89e 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -21,7 +21,7 @@ class Api::V1::ReportsController < Api::BaseController private def reported_status_ids - reported_account.statuses.find(status_ids).pluck(:id) + reported_account.statuses.with_discarded.find(status_ids).pluck(:id) end def status_ids diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 41fec1ad5..69d3cdbd3 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -71,6 +71,7 @@ class Api::V1::StatusesController < Api::BaseController @status = Status.where(account_id: current_user.account).find(params[:id]) authorize @status, :destroy? + @status.discard RemovalWorker.perform_async(@status.id, redraft: true) render json: @status, serializer: REST::StatusSerializer, source_requested: true, monsterfork_api: monsterfork_api diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb index 3b82b5551..91710a5d7 100644 --- a/app/models/form/status_batch.rb +++ b/app/models/form/status_batch.rb @@ -33,6 +33,7 @@ class Form::StatusBatch def delete_statuses Status.where(id: status_ids).reorder(nil).find_each do |status| + status.discard RemovalWorker.perform_async(status.id, redraft: false) Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) log_action :destroy, status diff --git a/app/models/report.rb b/app/models/report.rb index fe825759a..07389ef63 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -40,7 +40,7 @@ class Report < ApplicationRecord end def statuses - Status.where(id: status_ids).includes(:account, :media_attachments, :mentions) + Status.with_discarded.where(id: status_ids).includes(:account, :media_attachments, :mentions) end def media_attachments diff --git a/app/models/status.rb b/app/models/status.rb index 5caed1b20..2d2b0aafe 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -32,11 +32,13 @@ # reject_replies :boolean # tsv :tsvector # hidden :boolean +# deleted_at :datetime # class Status < ApplicationRecord before_destroy :unlink_from_conversations + include Discard::Model include Paginable include Cacheable include StatusThreadingConcern @@ -45,6 +47,7 @@ class Status < ApplicationRecord LOCAL_ONLY_TOKENS = /(?:#!|\u{1f441}\ufe0f?)\u200b?\z/ REJECT_REPLIES_TOKENS = /^(?:please )?(?:ms_dont_at_me|no (?:replie|mention)s|(?:dont|do not) (?:@|at|reply|mention|interact)(?: (?:me|us))?)\b/i SIMPLIFIED = /[^\w\s@_\"\'\-]+/ + self.discard_column = :deleted_at # If `override_timestamps` is set at creation time, Snowflake ID creation # will be based on current time instead of `created_at` @@ -95,7 +98,7 @@ class Status < ApplicationRecord accepts_nested_attributes_for :poll - default_scope { recent } + default_scope { recent.kept } scope :recent, -> { reorder(id: :desc) } scope :remote, -> { where(local: false).or(where.not(uri: nil)) } diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index e1068b3e9..0effadffb 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -16,11 +16,14 @@ - video = status.proper.media_attachments.first = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.proper.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description - else - = react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } + = react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.proper.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } .detailed-status__meta = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) + - if status.discarded? + · + %span.negative-hint= t('admin.statuses.deleted') · - if status.reblog? = fa_icon('repeat fw') diff --git a/app/workers/removal_worker.rb b/app/workers/removal_worker.rb index 14423a4fb..2a1eaa89b 100644 --- a/app/workers/removal_worker.rb +++ b/app/workers/removal_worker.rb @@ -4,7 +4,7 @@ class RemovalWorker include Sidekiq::Worker def perform(status_id, options = {}) - RemoveStatusService.new.call(Status.find(status_id), **options.symbolize_keys) + RemoveStatusService.new.call(Status.with_discarded.find(status_id), **options.symbolize_keys) rescue ActiveRecord::RecordNotFound true end diff --git a/config/locales/en.yml b/config/locales/en.yml index 3278007ea..abf6da7b7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -570,6 +570,7 @@ en: delete: Delete nsfw_off: Mark as not sensitive nsfw_on: Mark as sensitive + deleted: Deleted failed_to_execute: Failed to execute media: title: Media diff --git a/db/migrate/20190819134503_add_deleted_at_to_statuses.rb b/db/migrate/20190819134503_add_deleted_at_to_statuses.rb new file mode 100644 index 000000000..5af109097 --- /dev/null +++ b/db/migrate/20190819134503_add_deleted_at_to_statuses.rb @@ -0,0 +1,5 @@ +class AddDeletedAtToStatuses < ActiveRecord::Migration[5.2] + def change + add_column :statuses, :deleted_at, :datetime + end +end diff --git a/db/migrate/20190820003045_update_statuses_index.rb b/db/migrate/20190820003045_update_statuses_index.rb new file mode 100644 index 000000000..5c2ea1f6a --- /dev/null +++ b/db/migrate/20190820003045_update_statuses_index.rb @@ -0,0 +1,13 @@ +class UpdateStatusesIndex < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def up + safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], where: 'deleted_at IS NULL', order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20190820 } + remove_index :statuses, name: :index_statuses_20180106 + end + + def down + safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 } + remove_index :statuses, name: :index_statuses_20190820 + end +end -- cgit