mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
Merge pull request #3678 from tgxworld/allow_admin_to_change_timestamp
FEATURE: Allow admin to change timestamp of topic.
This commit is contained in:
commit
2b9ca0de8b
11 changed files with 257 additions and 0 deletions
|
@ -0,0 +1,58 @@
|
|||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
// Modal related to changing the timestamp of posts
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
needs: ['topic'],
|
||||
|
||||
topicController: Em.computed.alias('controllers.topic'),
|
||||
saving: false,
|
||||
date: '',
|
||||
time: '',
|
||||
|
||||
@computed('saving')
|
||||
buttonTitle(saving) {
|
||||
return saving ? I18n.t('saving') : I18n.t('topic.change_timestamp.action');
|
||||
},
|
||||
|
||||
@computed('date', 'time')
|
||||
createdAt(date, time) {
|
||||
return moment(date + ' ' + time, 'YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
|
||||
@computed('createdAt')
|
||||
validTimestamp(createdAt) {
|
||||
return moment().diff(createdAt, 'minutes') < 0;
|
||||
},
|
||||
|
||||
@computed('saving', 'date', 'validTimestamp')
|
||||
buttonDisabled() {
|
||||
if (this.get('saving') || this.get('validTimestamp')) return true;
|
||||
return Ember.isEmpty(this.get('date'));
|
||||
},
|
||||
|
||||
onShow: function() {
|
||||
this.setProperties({
|
||||
date: moment().format('YYYY-MM-DD')
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeTimestamp: function() {
|
||||
this.set('saving', true);
|
||||
const self = this;
|
||||
|
||||
Discourse.Topic.changeTimestamp(
|
||||
this.get('topicController.model.id'),
|
||||
this.get('createdAt').unix()
|
||||
).then(function() {
|
||||
self.send('closeModal');
|
||||
self.setProperties({ date: '', time: '', saving: false });
|
||||
}).catch(function() {
|
||||
self.flash(I18n.t('topic.change_timestamp.error'), 'alert-error');
|
||||
self.set('saving', false);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -480,6 +480,17 @@ Topic.reopenClass({
|
|||
return promise;
|
||||
},
|
||||
|
||||
changeTimestamp(topicId, timestamp) {
|
||||
const promise = Discourse.ajax("/t/" + topicId + '/change-timestamp', {
|
||||
type: 'PUT',
|
||||
data: { timestamp: timestamp },
|
||||
}).then(function(result) {
|
||||
if (result.success) return result;
|
||||
promise.reject(new Error("error updating timestamp of topic"));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
bulkOperation(topics, operation) {
|
||||
return Discourse.ajax("/topics/bulk", {
|
||||
type: 'PUT',
|
||||
|
|
|
@ -60,6 +60,10 @@ const TopicRoute = Discourse.Route.extend({
|
|||
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
|
||||
},
|
||||
|
||||
showChangeTimestamp() {
|
||||
showModal('change-timestamp', { model: this.modelFor('topic'), title: 'topic.change_timestamp.title' });
|
||||
},
|
||||
|
||||
showFeatureTopic() {
|
||||
showModal('featureTopic', { model: this.modelFor('topic'), title: 'topic.feature_topic.title' });
|
||||
this.controllerFor('modal').set('modalClass', 'feature-topic-modal');
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<div class="modal-body">
|
||||
<p>
|
||||
{{i18n 'topic.change_timestamp.instructions'}}
|
||||
</p>
|
||||
|
||||
<p {{bind-attr class=":alert :alert-error validTimestamp::hidden"}}>
|
||||
{{i18n 'topic.change_timestamp.invalid_timestamp'}}
|
||||
</p>
|
||||
|
||||
<form>
|
||||
{{input type="date" value=date}}
|
||||
{{input type="time" value=time}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" {{bind-attr disabled="buttonDisabled"}} {{action "changeTimestamp"}}>{{buttonTitle}}</button>
|
||||
</div>
|
|
@ -38,6 +38,10 @@
|
|||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
<li>
|
||||
{{d-button action="showChangeTimestamp" icon="calendar" label="topic.change_timestamp.title" class="btn-admin"}}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{{#if model.archived}}
|
||||
{{d-button action="toggleArchived" icon="folder" label="topic.actions.unarchive" class="btn-admin"}}
|
||||
|
|
|
@ -24,6 +24,7 @@ class TopicsController < ApplicationController
|
|||
:bulk,
|
||||
:reset_new,
|
||||
:change_post_owners,
|
||||
:change_timestamps,
|
||||
:bookmark,
|
||||
:unsubscribe]
|
||||
|
||||
|
@ -375,6 +376,22 @@ class TopicsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def change_timestamps
|
||||
params.require(:topic_id)
|
||||
params.require(:timestamp)
|
||||
|
||||
guardian.ensure_can_change_post_owner!
|
||||
|
||||
begin
|
||||
PostTimestampChanger.new( topic_id: params[:topic_id].to_i,
|
||||
timestamp: params[:timestamp].to_i ).change!
|
||||
|
||||
render json: success_json
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render json: failed_json, status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def clear_pin
|
||||
topic = Topic.find_by(id: params[:topic_id].to_i)
|
||||
guardian.ensure_can_see!(topic)
|
||||
|
|
43
app/services/post_timestamp_changer.rb
Normal file
43
app/services/post_timestamp_changer.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
class PostTimestampChanger
|
||||
def initialize(params)
|
||||
@topic = Topic.with_deleted.find(params[:topic_id])
|
||||
@posts = @topic.posts
|
||||
@timestamp = Time.at(params[:timestamp])
|
||||
@time_difference = calculate_time_difference
|
||||
end
|
||||
|
||||
def change!
|
||||
ActiveRecord::Base.transaction do
|
||||
update_topic
|
||||
|
||||
@posts.each do |post|
|
||||
if post.is_first_post?
|
||||
update_post(post, @timestamp)
|
||||
else
|
||||
update_post(post, Time.at(post.created_at.to_f + @time_difference))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Burst the cache for stats
|
||||
[AdminDashboardData, About].each { |klass| $redis.del klass.stats_cache_key }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_time_difference
|
||||
@timestamp - @topic.created_at
|
||||
end
|
||||
|
||||
def update_topic
|
||||
@topic.update_attributes(
|
||||
created_at: @timestamp,
|
||||
updated_at: @timestamp,
|
||||
bumped_at: @timestamp
|
||||
)
|
||||
end
|
||||
|
||||
def update_post(post, timestamp)
|
||||
post.update_attributes(created_at: timestamp, updated_at: timestamp)
|
||||
end
|
||||
end
|
|
@ -1241,6 +1241,13 @@ en:
|
|||
other: "Please choose the new owner of the {{count}} posts by <b>{{old_user}}</b>."
|
||||
instructions_warn: "Note that any notifications about this post will not be transferred to the new user retroactively.<br>Warning: Currently, no post-dependent data is transferred over to the new user. Use with caution."
|
||||
|
||||
change_timestamp:
|
||||
title: "Change Timestamp"
|
||||
action: "change timestamp"
|
||||
invalid_timestamp: "Timestamp cannot be in the future."
|
||||
error: "There was an error changing the timestamp of the topic."
|
||||
instructions: "Please select the new timestamp of the topic. Posts in the topic will be updated to have the same time difference."
|
||||
|
||||
multi_select:
|
||||
select: 'select'
|
||||
selected: 'selected ({{count}})'
|
||||
|
|
|
@ -469,6 +469,7 @@ Discourse::Application.routes.draw do
|
|||
post "t/:topic_id/move-posts" => "topics#move_posts", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/merge-topic" => "topics#merge_topic", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/change-owner" => "topics#change_post_owners", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/change-timestamp" => "topics#change_timestamps", constraints: {topic_id: /\d+/}
|
||||
delete "t/:topic_id/timings" => "topics#destroy_timings", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/bookmark" => "topics#bookmark", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/remove_bookmarks" => "topics#remove_bookmarks", constraints: {topic_id: /\d+/}
|
||||
|
|
|
@ -263,6 +263,45 @@ describe TopicsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'change_timestamps' do
|
||||
let(:params) { { topic_id: 1, timestamp: Time.zone.now } }
|
||||
|
||||
it 'needs you to be logged in' do
|
||||
expect { xhr :put, :change_timestamps, params }.to raise_error(Discourse::NotLoggedIn)
|
||||
end
|
||||
|
||||
[:moderator, :trust_level_4].each do |user|
|
||||
describe "forbidden to #{user}" do
|
||||
let!(user) { log_in(user) }
|
||||
|
||||
it 'correctly denies' do
|
||||
xhr :put, :change_timestamps, params
|
||||
expect(response).to be_forbidden
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'changing timestamps' do
|
||||
let!(:admin) { log_in(:admin) }
|
||||
let(:old_timestamp) { Time.zone.now }
|
||||
let(:new_timestamp) { old_timestamp - 1.day }
|
||||
let!(:topic) { Fabricate(:topic, created_at: old_timestamp) }
|
||||
let!(:p1) { Fabricate(:post, topic_id: topic.id, created_at: old_timestamp) }
|
||||
let!(:p2) { Fabricate(:post, topic_id: topic.id, created_at: old_timestamp + 1.day) }
|
||||
|
||||
it 'raises an error with a missing parameter' do
|
||||
expect { xhr :put, :change_timestamps, topic_id: 1 }.to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
|
||||
it 'should update the timestamps of selected posts' do
|
||||
xhr :put, :change_timestamps, topic_id: topic.id, timestamp: new_timestamp.to_f
|
||||
expect(topic.reload.created_at.to_s).to eq(new_timestamp.to_s)
|
||||
expect(p1.reload.created_at.to_s).to eq(new_timestamp.to_s)
|
||||
expect(p2.reload.created_at.to_s).to eq(old_timestamp.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'clear_pin' do
|
||||
it 'needs you to be logged in' do
|
||||
expect { xhr :put, :clear_pin, topic_id: 1 }.to raise_error(Discourse::NotLoggedIn)
|
||||
|
|
55
spec/services/post_timestamp_changer_spec.rb
Normal file
55
spec/services/post_timestamp_changer_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe PostTimestampChanger do
|
||||
describe "change!" do
|
||||
let(:old_timestamp) { Time.zone.now }
|
||||
let(:new_timestamp) { old_timestamp + 1.day }
|
||||
let!(:topic) { Fabricate(:topic, created_at: old_timestamp) }
|
||||
let!(:p1) { Fabricate(:post, topic: topic, created_at: old_timestamp) }
|
||||
let!(:p2) { Fabricate(:post, topic: topic, created_at: old_timestamp + 1.day) }
|
||||
let(:params) { { topic_id: topic.id, timestamp: new_timestamp.to_f } }
|
||||
|
||||
it 'changes the timestamp of the topic and opening post' do
|
||||
PostTimestampChanger.new(params).change!
|
||||
|
||||
topic.reload
|
||||
[:created_at, :updated_at, :bumped_at].each do |column|
|
||||
expect(topic.public_send(column).to_s).to eq(new_timestamp.to_s)
|
||||
end
|
||||
|
||||
p1.reload
|
||||
[:created_at, :updated_at].each do |column|
|
||||
expect(p1.public_send(column).to_s).to eq(new_timestamp.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'predated timestamp' do
|
||||
it 'updates the timestamp of posts in the topic with the time difference applied' do
|
||||
PostTimestampChanger.new(params).change!
|
||||
|
||||
p2.reload
|
||||
[:created_at, :updated_at].each do |column|
|
||||
expect(p2.public_send(column).to_s).to eq((old_timestamp + 2.day).to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'backdated timestamp' do
|
||||
let(:new_timestamp) { old_timestamp - 1.day }
|
||||
|
||||
it 'updates the timestamp of posts in the topic with the time difference applied' do
|
||||
PostTimestampChanger.new(params).change!
|
||||
|
||||
p2.reload
|
||||
[:created_at, :updated_at].each do |column|
|
||||
expect(p2.public_send(column).to_s).to eq((old_timestamp).to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes the stats cache' do
|
||||
$redis.expects(:del).twice
|
||||
PostTimestampChanger.new(params).change!
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue