mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 05:21:50 +08:00
FEATURE: Allow admins to permanently delete revisions (#19913)
# Context This PR introduces the ability to permanently delete revisions from a post while maintaining the changes implemented by the revisions. Additional Context: /t/90301 # Functionality In the case a staff member wants to _remove the visual cue_ that a post has been edited eg. <img width="86" alt="Screenshot 2023-01-18 at 2 59 12 PM" src="https://user-images.githubusercontent.com/50783505/213293333-9c881229-ab18-4591-b39b-e3419a67907d.png"> while maintaining the changes made in the edits, they can enable the (hidden) site setting of `can_permanently_delete`. When this is enabled, after _hiding_ the revisions <img width="149" alt="Screenshot 2023-01-19 at 1 53 35 PM" src="https://user-images.githubusercontent.com/50783505/213546080-2a9e9c55-b3ef-428e-a93d-1b6ba287dfae.png"> there will be an additional button in the history modal to <kbd>Delete revisions</kbd> on a post. <img width="997" alt="Screenshot 2023-01-19 at 1 49 51 PM" src="https://user-images.githubusercontent.com/50783505/213546333-49042558-50ab-4724-9da7-08bacc68d38d.png"> Since this action is permanent, we display a confirmation dialog prior to triggering the destroy call <img width="722" alt="Screenshot 2023-01-19 at 1 55 59 PM" src="https://user-images.githubusercontent.com/50783505/213546487-96ea6e89-ac49-4892-b4b0-28996e3c867f.png"> Once confirmed the history modal will close and the post will `rebake` to display an _unedited_ post. <img width="868" alt="Screenshot 2023-01-19 at 1 56 35 PM" src="https://user-images.githubusercontent.com/50783505/213546608-d6436717-8484-4132-a1a8-b7a348d92728.png"> see that there is not a visual que for _revision have been made on this post_ for a post that **HAS** been edited. In addition to this, a user history log for `purge_post_revisions` will be added for each action completed. # Limits - Admins are rate limited to 20 posts per minute
This commit is contained in:
parent
2fb2b0a538
commit
292d3677e9
|
@ -114,6 +114,17 @@ export default Controller.extend(ModalFunctionality, {
|
|||
);
|
||||
},
|
||||
|
||||
permanentlyDeleteRevisions(postId) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("post.revisions.controls.destroy_confirm"),
|
||||
didConfirm: () => {
|
||||
Post.permanentlyDeleteRevisions(postId).then(() => {
|
||||
this.send("closeModal");
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
show(postId, postVersion) {
|
||||
Post.showRevision(postId, postVersion).then(() =>
|
||||
this.refresh(postId, postVersion)
|
||||
|
@ -162,6 +173,7 @@ export default Controller.extend(ModalFunctionality, {
|
|||
},
|
||||
|
||||
displayRevisions: gt("model.version_count", 2),
|
||||
|
||||
displayGoToFirst: propertyGreaterThan(
|
||||
"model.current_revision",
|
||||
"model.first_revision"
|
||||
|
@ -215,6 +227,15 @@ export default Controller.extend(ModalFunctionality, {
|
|||
return this.currentUser && this.currentUser.get("staff");
|
||||
},
|
||||
|
||||
@discourseComputed("model.previous_hidden")
|
||||
displayPermanentlyDeleteButton(previousHidden) {
|
||||
return (
|
||||
this.siteSettings.can_permanently_delete &&
|
||||
this.currentUser?.staff &&
|
||||
previousHidden
|
||||
);
|
||||
},
|
||||
|
||||
isEitherRevisionHidden: or("model.previous_hidden", "model.current_hidden"),
|
||||
|
||||
@discourseComputed(
|
||||
|
@ -352,6 +373,9 @@ export default Controller.extend(ModalFunctionality, {
|
|||
hideVersion() {
|
||||
this.hide(this.get("model.post_id"), this.get("model.current_revision"));
|
||||
},
|
||||
permanentlyDeleteVersions() {
|
||||
this.permanentlyDeleteRevisions(this.get("model.post_id"));
|
||||
},
|
||||
showVersion() {
|
||||
this.show(this.get("model.post_id"), this.get("model.current_revision"));
|
||||
},
|
||||
|
|
|
@ -461,6 +461,12 @@ Post.reopenClass({
|
|||
});
|
||||
},
|
||||
|
||||
permanentlyDeleteRevisions(postId) {
|
||||
return ajax(`/posts/${postId}/revisions/permanently_delete`, {
|
||||
type: "DELETE",
|
||||
});
|
||||
},
|
||||
|
||||
showRevision(postId, version) {
|
||||
return ajax(`/posts/${postId}/revisions/${version}/show`, {
|
||||
type: "PUT",
|
||||
|
|
|
@ -250,6 +250,16 @@
|
|||
@disabled={{this.loading}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.displayPermanentlyDeleteButton}}
|
||||
<DButton
|
||||
@action={{action "permanentlyDeleteVersions"}}
|
||||
@icon="far-trash-alt"
|
||||
@label="post.revisions.controls.destroy"
|
||||
@class="btn-danger"
|
||||
@disabled={{this.loading}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
@ -466,6 +466,33 @@ class PostsController < ApplicationController
|
|||
render body: nil
|
||||
end
|
||||
|
||||
def permanently_delete_revisions
|
||||
guardian.ensure_can_permanently_delete_post_revisions!
|
||||
|
||||
post = find_post_from_params
|
||||
raise Discourse::InvalidParameters.new(:post) if post.blank?
|
||||
raise Discourse::NotFound unless post.revisions.present?
|
||||
|
||||
RateLimiter.new(
|
||||
current_user,
|
||||
"admin_permanently_delete_post_revisions",
|
||||
20,
|
||||
1.minute,
|
||||
apply_limit_to_staff: true,
|
||||
).performed!
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
updated_at = Time.zone.now
|
||||
post.revisions.destroy_all
|
||||
post.update(version: 1, public_version: 1, last_version_at: updated_at)
|
||||
StaffActionLogger.new(current_user).log_permanently_delete_post_revisions(post)
|
||||
end
|
||||
|
||||
post.rebake!
|
||||
|
||||
render body: nil
|
||||
end
|
||||
|
||||
def show_revision
|
||||
post_revision = find_post_revision_from_params
|
||||
guardian.ensure_can_show_post_revision!(post_revision)
|
||||
|
|
|
@ -119,6 +119,7 @@ class UserHistory < ActiveRecord::Base
|
|||
watched_word_create: 97,
|
||||
watched_word_destroy: 98,
|
||||
delete_group: 99,
|
||||
permanently_delete_post_revisions: 100,
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -213,6 +214,7 @@ class UserHistory < ActiveRecord::Base
|
|||
watched_word_create
|
||||
watched_word_destroy
|
||||
delete_group
|
||||
permanently_delete_post_revisions
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -954,6 +954,16 @@ class StaffActionLogger
|
|||
)
|
||||
end
|
||||
|
||||
def log_permanently_delete_post_revisions(post)
|
||||
raise Discourse::InvalidParameters.new(:post) if post.nil?
|
||||
|
||||
UserHistory.create!(
|
||||
action: UserHistory.actions[:permanently_delete_post_revisions],
|
||||
acting_user_id: @admin.id,
|
||||
post_id: post.id,
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_changes(changes)
|
||||
|
|
|
@ -1121,7 +1121,7 @@ en:
|
|||
perm_denied_expl: "You denied permission for notifications. Allow notifications via your browser settings."
|
||||
disable: "Disable Notifications"
|
||||
enable: "Enable Notifications"
|
||||
each_browser_note: 'Note: You have to change this setting on every browser you use. All notifications will be disabled if you pause notifications from user menu, regardless of this setting.'
|
||||
each_browser_note: "Note: You have to change this setting on every browser you use. All notifications will be disabled if you pause notifications from user menu, regardless of this setting."
|
||||
consent_prompt: "Do you want live notifications when people reply to your posts?"
|
||||
dismiss: "Dismiss"
|
||||
dismiss_notifications: "Dismiss All"
|
||||
|
@ -3548,6 +3548,8 @@ en:
|
|||
last: "Last revision"
|
||||
hide: "Hide revision"
|
||||
show: "Show revision"
|
||||
destroy: "Delete revisions"
|
||||
destroy_confirm: "Are you sure you want to delete all of the revisions on this post? This action is permanent."
|
||||
revert: "Revert to revision %{revision}"
|
||||
edit_wiki: "Edit Wiki"
|
||||
edit_post: "Edit Post"
|
||||
|
|
|
@ -1082,6 +1082,7 @@ Discourse::Application.routes.draw do
|
|||
put "revisions/:revision/hide" => "posts#hide_revision", :constraints => { revision: /\d+/ }
|
||||
put "revisions/:revision/show" => "posts#show_revision", :constraints => { revision: /\d+/ }
|
||||
put "revisions/:revision/revert" => "posts#revert", :constraints => { revision: /\d+/ }
|
||||
delete "revisions/permanently_delete" => "posts#permanently_delete_revisions"
|
||||
put "recover"
|
||||
collection do
|
||||
delete "destroy_many"
|
||||
|
|
|
@ -13,6 +13,10 @@ module PostRevisionGuardian
|
|||
is_staff?
|
||||
end
|
||||
|
||||
def can_permanently_delete_post_revisions?
|
||||
is_staff? && SiteSetting.can_permanently_delete
|
||||
end
|
||||
|
||||
def can_show_post_revision?(post_revision)
|
||||
is_staff?
|
||||
end
|
||||
|
|
|
@ -2049,6 +2049,76 @@ RSpec.describe PostsController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#permanently_delete_revisions" do
|
||||
before { SiteSetting.can_permanently_delete = true }
|
||||
|
||||
fab!(:post) do
|
||||
Fabricate(
|
||||
:post,
|
||||
user: Fabricate(:user),
|
||||
raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex",
|
||||
)
|
||||
end
|
||||
|
||||
fab!(:post_with_no_revisions) do
|
||||
Fabricate(
|
||||
:post,
|
||||
user: Fabricate(:user),
|
||||
raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex",
|
||||
)
|
||||
end
|
||||
|
||||
fab!(:post_revision) { Fabricate(:post_revision, post: post) }
|
||||
fab!(:post_revision_2) { Fabricate(:post_revision, post: post) }
|
||||
|
||||
let(:post_id) { post.id }
|
||||
|
||||
describe "when logged in as a regular user" do
|
||||
it "does not delete revisions" do
|
||||
sign_in(user)
|
||||
delete "/posts/#{post_id}/revisions/permanently_delete.json"
|
||||
expect(response).to_not be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "when logged in as staff" do
|
||||
before { sign_in(admin) }
|
||||
|
||||
it "fails when post record is not found" do
|
||||
delete "/posts/#{post_id + 1}/revisions/permanently_delete.json"
|
||||
expect(response).to_not be_successful
|
||||
end
|
||||
|
||||
it "fails when no post revisions are found" do
|
||||
delete "/posts/#{post_with_no_revisions.id}/revisions/permanently_delete.json"
|
||||
expect(response).to_not be_successful
|
||||
end
|
||||
|
||||
it "fails when 'can_permanently_delete' setting is false" do
|
||||
SiteSetting.can_permanently_delete = false
|
||||
delete "/posts/#{post_id}/revisions/permanently_delete.json"
|
||||
expect(response).to_not be_successful
|
||||
end
|
||||
|
||||
it "permanently deletes revisions from post and adds a staff log" do
|
||||
delete "/posts/#{post_id}/revisions/permanently_delete.json"
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
# It creates a staff log
|
||||
logs =
|
||||
UserHistory.find_by(
|
||||
action: UserHistory.actions[:permanently_delete_post_revisions],
|
||||
acting_user_id: admin.id,
|
||||
post_id: post_id,
|
||||
)
|
||||
expect(logs).to be_present
|
||||
|
||||
# ensure post revisions are deleted
|
||||
expect(PostRevision.where(post: post)).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#revert" do
|
||||
include_examples "action requires login", :put, "/posts/123/revisions/2/revert.json"
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user