mirror of
https://github.com/discourse/discourse.git
synced 2024-11-28 22:16:17 +08:00
4af77f1e38
This PR allows entering a float value for topic timers e.g. 0.5 for 30 minutes when entering hours, 0.5 for 12 hours when entering days. This is achieved by adding a new column to store the duration of a topic timer in minutes instead of the ambiguous both hours and days that it could be before. This PR has ommitted the post migration to delete the duration column in topic timers; it will be done in a subsequent PR to ensure that no data is lost if the UPDATE query to set duration_mintues fails. I have to keep the old keyword of duration in set_or_create_topic_timer for backwards compat, will remove at a later date after plugins are updated.
251 lines
7.8 KiB
Ruby
251 lines
7.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class TopicTimer < ActiveRecord::Base
|
|
self.ignored_columns = [
|
|
"duration" # TODO(2021-06-01): remove
|
|
]
|
|
|
|
include Trashable
|
|
|
|
belongs_to :user
|
|
belongs_to :topic
|
|
belongs_to :category
|
|
|
|
validates :user_id, presence: true
|
|
validates :topic_id, presence: true
|
|
validates :execute_at, presence: true
|
|
validates :status_type, presence: true
|
|
validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at] }, if: :public_type?
|
|
validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at, :user_id] }, if: :private_type?
|
|
validates :category_id, presence: true, if: :publishing_to_category?
|
|
|
|
validate :executed_at_in_future?
|
|
|
|
scope :scheduled_bump_topics, -> { where(status_type: TopicTimer.types[:bump], deleted_at: nil).pluck(:topic_id) }
|
|
scope :pending_timers, ->(before_time = Time.now.utc) do
|
|
where("execute_at <= :before_time AND deleted_at IS NULL", before_time: before_time)
|
|
end
|
|
|
|
before_save do
|
|
self.created_at ||= Time.zone.now if execute_at
|
|
self.public_type = self.public_type?
|
|
|
|
if (will_save_change_to_execute_at? &&
|
|
!attribute_in_database(:execute_at).nil?) ||
|
|
will_save_change_to_user_id?
|
|
|
|
# TODO(martin - 2021-05-01) - Remove this backwards compatability for outstanding
|
|
# jobs once they have all been run and after Jobs::TopicTimerEnqueuer is in place
|
|
self.send("cancel_auto_#{self.class.types[status_type]}_job")
|
|
end
|
|
end
|
|
|
|
# These actions are in place to make sure the topic is in the correct
|
|
# state at the point in time where the timer is saved. It does not
|
|
# guarantee that the topic will be in the correct state when the timer
|
|
# job is executed, but each timer job handles deleted topics etc. gracefully.
|
|
#
|
|
# This is also important for the Open Temporarily and Close Temporarily timers,
|
|
# which change the topic's status straight away and set a timer to do the
|
|
# opposite action in the future.
|
|
after_save do
|
|
if (saved_change_to_execute_at? || saved_change_to_user_id?)
|
|
if status_type == TopicTimer.types[:silent_close] || status_type == TopicTimer.types[:close]
|
|
topic.update_status('closed', false, user) if topic.closed?
|
|
end
|
|
if status_type == TopicTimer.types[:open]
|
|
topic.update_status('closed', true, user) if topic.open?
|
|
end
|
|
end
|
|
end
|
|
|
|
def status_type_name
|
|
self.class.types[status_type]
|
|
end
|
|
|
|
def enqueue_typed_job(time: nil)
|
|
return if typed_job_scheduled?
|
|
self.send("schedule_auto_#{status_type_name}_job")
|
|
end
|
|
|
|
# TODO(martin - 2021-05-01) - Remove this backwards compatability for outstanding
|
|
# jobs once they have all been run and after Jobs::TopicTimerEnqueuer is in place
|
|
def typed_job_scheduled?
|
|
scheduled = Jobs.scheduled_for(
|
|
TopicTimer.type_job_map[status_type_name], topic_timer_id: id
|
|
).any?
|
|
|
|
if [:close, :silent_close, :open].include?(status_type_name)
|
|
return scheduled || Jobs.scheduled_for(:toggle_topic_closed, topic_timer_id: id).any?
|
|
end
|
|
|
|
scheduled
|
|
end
|
|
|
|
def self.type_job_map
|
|
{
|
|
close: :close_topic,
|
|
open: :open_topic,
|
|
publish_to_category: :publish_topic_to_category,
|
|
delete: :delete_topic,
|
|
reminder: :topic_reminder,
|
|
bump: :bump_topic,
|
|
delete_replies: :delete_replies,
|
|
silent_close: :close_topic,
|
|
clear_slow_mode: :clear_slow_mode
|
|
}
|
|
end
|
|
|
|
def self.types
|
|
@types ||= Enum.new(
|
|
close: 1,
|
|
open: 2,
|
|
publish_to_category: 3,
|
|
delete: 4,
|
|
reminder: 5,
|
|
bump: 6,
|
|
delete_replies: 7,
|
|
silent_close: 8,
|
|
clear_slow_mode: 9
|
|
)
|
|
end
|
|
|
|
def self.public_types
|
|
@_public_types ||= types.except(:reminder, :clear_slow_mode)
|
|
end
|
|
|
|
def self.private_types
|
|
@_private_types ||= types.only(:reminder, :clear_slow_mode)
|
|
end
|
|
|
|
def public_type?
|
|
!!self.class.public_types[self.status_type]
|
|
end
|
|
|
|
def private_type?
|
|
!!self.class.private_types[self.status_type]
|
|
end
|
|
|
|
def runnable?
|
|
return false if deleted_at.present?
|
|
return false if execute_at > Time.zone.now
|
|
true
|
|
end
|
|
|
|
private
|
|
|
|
def executed_at_in_future?
|
|
return if created_at.blank? || (execute_at > created_at)
|
|
|
|
errors.add(:execute_at, I18n.t(
|
|
'activerecord.errors.models.topic_timer.attributes.execute_at.in_the_past'
|
|
))
|
|
end
|
|
|
|
def publishing_to_category?
|
|
self.status_type.to_i == TopicTimer.types[:publish_to_category]
|
|
end
|
|
|
|
# TODO(martin - 2021-05-01) - Remove cancels for toggle_topic_closed once topic timer revamp completed.
|
|
def cancel_auto_close_job
|
|
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
|
|
Jobs.cancel_scheduled_job(:close_topic, topic_timer_id: id)
|
|
end
|
|
|
|
# TODO(martin - 2021-05-01) - Remove cancels for toggle_topic_closed once topic timer revamp completed.
|
|
def cancel_auto_open_job
|
|
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
|
|
Jobs.cancel_scheduled_job(:open_topic, topic_timer_id: id)
|
|
end
|
|
|
|
# TODO(martin - 2021-05-01) - Remove cancels for toggle_topic_closed once topic timer revamp completed.
|
|
def cancel_auto_silent_close_job
|
|
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
|
|
Jobs.cancel_scheduled_job(:close_topic, topic_timer_id: id)
|
|
end
|
|
|
|
def cancel_auto_publish_to_category_job
|
|
Jobs.cancel_scheduled_job(TopicTimer.type_job_map[:publish_to_category], topic_timer_id: id)
|
|
end
|
|
|
|
def cancel_auto_delete_job
|
|
Jobs.cancel_scheduled_job(TopicTimer.type_job_map[:delete], topic_timer_id: id)
|
|
end
|
|
|
|
def cancel_auto_reminder_job
|
|
Jobs.cancel_scheduled_job(TopicTimer.type_job_map[:reminder], topic_timer_id: id)
|
|
end
|
|
|
|
def cancel_auto_bump_job
|
|
Jobs.cancel_scheduled_job(TopicTimer.type_job_map[:bump], topic_timer_id: id)
|
|
end
|
|
|
|
def cancel_auto_delete_replies_job
|
|
Jobs.cancel_scheduled_job(TopicTimer.type_job_map[:delete_replies], topic_timer_id: id)
|
|
end
|
|
|
|
def cancel_auto_clear_slow_mode_job
|
|
Jobs.cancel_scheduled_job(TopicTimer.type_job_map[:clear_slow_mode], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_delete_replies_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:delete_replies], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_bump_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:bump], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_open_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:open], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_close_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:close], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_silent_close_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:close], topic_timer_id: id, silent: true)
|
|
end
|
|
|
|
def schedule_auto_publish_to_category_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:publish_to_category], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_delete_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:delete], topic_timer_id: id)
|
|
end
|
|
|
|
def schedule_auto_reminder_job
|
|
# noop, TODO(martin 2021-03-11): Remove this after timers migrated and outstanding jobs cancelled
|
|
end
|
|
|
|
def schedule_auto_clear_slow_mode_job
|
|
Jobs.enqueue(TopicTimer.type_job_map[:clear_slow_mode], topic_timer_id: id)
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: topic_timers
|
|
#
|
|
# id :integer not null, primary key
|
|
# execute_at :datetime not null
|
|
# status_type :integer not null
|
|
# user_id :integer not null
|
|
# topic_id :integer not null
|
|
# based_on_last_post :boolean default(FALSE), not null
|
|
# deleted_at :datetime
|
|
# deleted_by_id :integer
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# category_id :integer
|
|
# public_type :boolean default(TRUE)
|
|
# duration_minutes :integer
|
|
#
|
|
# Indexes
|
|
#
|
|
# idx_topic_id_public_type_deleted_at (topic_id) UNIQUE WHERE ((public_type = true) AND (deleted_at IS NULL))
|
|
# index_topic_timers_on_user_id (user_id)
|
|
#
|