diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 index bde5384560e..f56b55dc93f 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 @@ -6,6 +6,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; export const CLOSE_STATUS_TYPE = 'close'; const OPEN_STATUS_TYPE = 'open'; const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category'; +const DELETE_STATUS_TYPE = 'delete'; export default Ember.Controller.extend(ModalFunctionality, { loading: false, @@ -14,14 +15,18 @@ export default Ember.Controller.extend(ModalFunctionality, { selection: Ember.computed.alias('model.topic_status_update.status_type'), autoOpen: Ember.computed.equal('selection', OPEN_STATUS_TYPE), autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE), + autoDelete: Ember.computed.equal('selection', DELETE_STATUS_TYPE), publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE), + showTimeOnly: Ember.computed.or('autoOpen', 'autoDelete'), + @computed("model.closed") statusUpdates(closed) { return [ { id: CLOSE_STATUS_TYPE, name: I18n.t(closed ? 'topic.temp_open.title' : 'topic.auto_close.title'), }, { id: OPEN_STATUS_TYPE, name: I18n.t(closed ? 'topic.auto_reopen.title' : 'topic.temp_close.title') }, - { id: PUBLISH_TO_CATEGORY_STATUS_TYPE, name: I18n.t('topic.publish_to_category.title') } + { id: PUBLISH_TO_CATEGORY_STATUS_TYPE, name: I18n.t('topic.publish_to_category.title') }, + { id: DELETE_STATUS_TYPE, name: I18n.t('topic.auto_delete.title') } ]; }, diff --git a/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs b/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs index c1039fd9b42..6b40e09f039 100644 --- a/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs +++ b/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs @@ -5,7 +5,7 @@
- {{#if autoOpen}} + {{#if showTimeOnly}} {{auto-update-input input=updateTime statusType=selection diff --git a/app/jobs/regular/delete_topic.rb b/app/jobs/regular/delete_topic.rb new file mode 100644 index 00000000000..83a37517f38 --- /dev/null +++ b/app/jobs/regular/delete_topic.rb @@ -0,0 +1,21 @@ +module Jobs + class DeleteTopic < Jobs::Base + + def execute(args) + topic_status_update = TopicStatusUpdate.find_by(id: args[:topic_status_update_id]) + + topic = topic_status_update&.topic + + if topic_status_update.blank? || topic.blank? || + topic_status_update.execute_at > Time.zone.now + return + end + + if Guardian.new(topic_status_update.user).can_delete?(topic) + first_post = topic.ordered_posts.first + PostDestroyer.new(topic_status_update.user, first_post, { context: I18n.t("topic_statuses.auto_deleted_by_timer") }).destroy + end + end + + end +end \ No newline at end of file diff --git a/app/models/topic_status_update.rb b/app/models/topic_status_update.rb index d5e59c67373..fae22a90181 100644 --- a/app/models/topic_status_update.rb +++ b/app/models/topic_status_update.rb @@ -35,7 +35,8 @@ class TopicStatusUpdate < ActiveRecord::Base @types ||= Enum.new( close: 1, open: 2, - publish_to_category: 3 + publish_to_category: 3, + delete: 4 ) end @@ -77,6 +78,10 @@ class TopicStatusUpdate < ActiveRecord::Base Jobs.cancel_scheduled_job(:publish_topic_to_category, topic_status_update_id: id) end + def cancel_auto_delete_job + Jobs.cancel_scheduled_job(:delete_topic, topic_status_update_id: id) + end + def schedule_auto_open_job(time) return unless topic topic.update_status('closed', true, user) if !topic.closed @@ -104,6 +109,10 @@ class TopicStatusUpdate < ActiveRecord::Base def publishing_to_category? self.status_type.to_i == TopicStatusUpdate.types[:publish_to_category] end + + def schedule_auto_delete_job(time) + Jobs.enqueue_at(time, :delete_topic, topic_status_update_id: id) + end end # == Schema Information diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 68853da6288..18f4ef79adb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1524,12 +1524,15 @@ en: label: "Auto-close topic hours:" error: "Please enter a valid value." based_on_last_post: "Don't close until the last post in the topic is at least this old." + auto_delete: + title: "Auto-Delete Topic" status_update_notice: auto_open: "This topic will automatically open %{timeLeft}." auto_close: "This topic will automatically close %{timeLeft}." auto_publish_to_category: "This topic will be published to #%{categoryName} %{timeLeft}." auto_close_based_on_last_post: "This topic will close %{duration} after the last reply." + auto_delete: "This topic will be automatically deleted %{timeLeft}." auto_close_title: 'Auto-Close Settings' auto_close_immediate: one: "The last post in the topic is already 1 hour old, so the topic will be closed immediately." diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4fa51fa6e77..8363974f390 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1656,6 +1656,7 @@ en: pinned_globally_disabled: "This topic is now unpinned. It will no longer appear at the top of its category." visible_enabled: "This topic is now listed. It will be displayed in topic lists." visible_disabled: "This topic is now unlisted. It will no longer be displayed in any topic lists. The only way to access this topic is via direct link." + auto_deleted_by_timer: "Automatically deleted by timer." login: not_approved: "Your account hasn't been approved yet. You will be notified by email when you are ready to log in." diff --git a/spec/jobs/delete_topic_spec.rb b/spec/jobs/delete_topic_spec.rb new file mode 100644 index 00000000000..aa820a44b1f --- /dev/null +++ b/spec/jobs/delete_topic_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +describe Jobs::DeleteTopic do + let(:admin) { Fabricate(:admin) } + + let(:topic) do + Fabricate(:topic, + topic_status_updates: [Fabricate(:topic_status_update, user: admin)] + ) + end + + let(:first_post) { create_post(topic: topic) } + + before do + SiteSetting.queue_jobs = true + end + + it "can close a topic" do + first_post + Timecop.freeze(2.hours.from_now) do + described_class.new.execute(topic_status_update_id: topic.topic_status_update.id) + expect(topic.reload).to be_trashed + expect(first_post.reload).to be_trashed + end + end + + it "should do nothing if topic is already deleted" do + first_post + topic.trash! + Timecop.freeze(2.hours.from_now) do + Topic.any_instance.expects(:trash!).never + described_class.new.execute(topic_status_update_id: topic.topic_status_update.id) + end + end + + it "should do nothing if it's too early" do + t = Fabricate(:topic, + topic_status_updates: [Fabricate(:topic_status_update, user: admin, execute_at: 5.hours.from_now)] + ) + create_post(topic: t) + Timecop.freeze(4.hours.from_now) do + described_class.new.execute(topic_status_update_id: t.topic_status_update.id) + expect(t.reload).to_not be_trashed + end + end + + describe "user isn't authorized to delete topics" do + let(:topic) { + Fabricate(:topic, + topic_status_updates: [Fabricate(:topic_status_update, user: Fabricate(:user))] + ) + } + + it "shouldn't delete the topic" do + create_post(topic: topic) + Timecop.freeze(2.hours.from_now) do + described_class.new.execute(topic_status_update_id: topic.topic_status_update.id) + expect(topic.reload).to_not be_trashed + end + end + end + +end