FEATURE: Allow moderators to change topic timestamps (#7053)

FEATURE: Allow moderators to change topic timestamps (#7053)

diff --git a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
index 38e4203..2d6e065 100644
--- a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
@@ -201,7 +201,7 @@ export default createWidget("topic-admin-menu", {
       });
     }
 
-    if (this.currentUser.admin) {
+    if (this.currentUser.get("staff")) {
       buttons.push({
         className: "topic-admin-change-timestamp",
         buttonClass: "btn-default",
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index e311798..11ddf19 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -676,17 +676,22 @@ class TopicsController < ApplicationController
   end
 
   def change_timestamps
-    params.require(:topic_id)
-    params.require(:timestamp)
+    topic_id = params.require(:topic_id).to_i
+    timestamp = params.require(:timestamp).to_f
 
     guardian.ensure_can_change_post_timestamps!
 
+    topic = Topic.with_deleted.find(topic_id)
+    previous_timestamp = topic.first_post.created_at
+
     begin
       TopicTimestampChanger.new(
-        topic_id: params[:topic_id].to_i,
-        timestamp: params[:timestamp].to_f
+        topic: topic,
+        timestamp: timestamp
       ).change!
 
+      StaffActionLogger.new(current_user).log_topic_timestamps_changed(topic, Time.zone.at(timestamp), previous_timestamp)
+
       render json: success_json
     rescue ActiveRecord::RecordInvalid, TopicTimestampChanger::InvalidTimestampError
       render json: failed_json, status: 422
diff --git a/app/models/user_history.rb b/app/models/user_history.rb
index 4eb345b..c8ec875 100644
--- a/app/models/user_history.rb
+++ b/app/models/user_history.rb
@@ -83,7 +83,8 @@ class UserHistory < ActiveRecord::Base
       post_rejected: 64,
       merge_user: 65,
       entity_export: 66,
-      change_password: 67
+      change_password: 67,
+      topic_timestamps_changed: 68
     )
   end
 
@@ -145,7 +146,8 @@ class UserHistory < ActiveRecord::Base
       :post_rejected,
       :merge_user,
       :entity_export,
-      :change_name
+      :change_name,
+      :topic_timestamps_changed
     ]
   end
 
diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb
index 6ebb5af..a270dee 100644
--- a/app/services/staff_action_logger.rb
+++ b/app/services/staff_action_logger.rb
@@ -117,6 +117,16 @@ class StaffActionLogger
     )
   end
 
+  def log_topic_timestamps_changed(topic, new_timestamp, previous_timestamp, opts = {})
+    raise Discourse::InvalidParameters.new(:topic) unless topic && topic.is_a?(Topic)
+    UserHistory.create!(params(opts).merge(
+      action: UserHistory.actions[:topic_timestamps_changed],
+      topic_id: topic.id,
+      new_value: new_timestamp,
+      previous_value: previous_timestamp)
+    )
+  end
+
   def log_post_lock(post, opts = {})
     raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post)
     UserHistory.create!(params(opts).merge(
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 5c23e09..9609ee9 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3669,6 +3669,7 @@ en:
             merge_user: "merge user"
             entity_export: "export entity"
             change_name: "change name"
+            topic_timestamps_changed: "topic timestamps changed"
         screened_emails:
           title: "Screened Emails"
           description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb
index c49d3cd..f0e63f6 100644
--- a/lib/guardian/post_guardian.rb
+++ b/lib/guardian/post_guardian.rb
@@ -226,7 +226,7 @@ module PostGuardian
   end
 
   def can_change_post_timestamps?
-    is_admin?
+    is_staff?
   end
 
   def can_wiki?(post)
diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb
index 738d17e..022f68f 100644
--- a/spec/requests/topics_controller_spec.rb
+++ b/spec/requests/topics_controller_spec.rb
@@ -572,19 +572,17 @@ RSpec.describe TopicsController do
       expect(response.status).to eq(403)
     end
 
-    [:moderator, :trust_level_4].each do |user|
-      describe "forbidden to #{user}" do
-        let!(user) { sign_in(Fabricate(user)) }
+    describe "forbidden to trust_level_4" do
+      let!(:trust_level_4) { sign_in(Fabricate(:trust_level_4)) }
 
-        it 'correctly denies' do
-          put "/t/1/change-timestamp.json", params: params
-          expect(response).to be_forbidden
-        end
+      it 'correctly denies' do
+        put "/t/1/change-timestamp.json", params: params
+        expect(response).to be_forbidden
       end
     end
 
     describe 'changing timestamps' do
-      let!(:admin) { sign_in(Fabricate(:admin)) }
+      let!(:moderator) { sign_in(Fabricate(:moderator)) }
       let(:old_timestamp) { Time.zone.now }
       let(:new_timestamp) { old_timestamp - 1.day }
       let!(:topic) { Fabricate(:topic, created_at: old_timestamp) }
@@ -605,6 +603,18 @@ RSpec.describe TopicsController do
         expect(p1.reload.created_at).to be_within_one_second_of(new_timestamp)
         expect(p2.reload.created_at).to be_within_one_second_of(old_timestamp)
       end
+
+      it 'should create a staff log entry' do
+        put "/t/#{topic.id}/change-timestamp.json", params: {
+          timestamp: new_timestamp.to_f
+        }
+
+        log = UserHistory.last
+        expect(log.acting_user_id).to eq(moderator.id)
+        expect(log.topic_id).to eq(topic.id)
+        expect(log.new_value).to eq(new_timestamp.utc.to_s)
+        expect(log.previous_value).to eq(old_timestamp.utc.to_s)
+      end
     end
   end

GitHub sha: d1bad881

1 Like

This commit has been mentioned on Discourse Meta. There might be relevant details there: