discourse/app/models/topic_timer.rb
Martin Brennan 5268568d23
FEATURE: Remove user topic timers and migrate to bookmarks with reminders (#10474)
This PR removes the user reminder topic timers, because that system has been supplanted and improved by bookmark reminders. The option is removed from the UI and all existing user reminder topic timers are migrated to bookmark reminders.

Migration does this:

* Get all topic_timers with status_type 5 (reminders)
* Gets all bookmarks where the user ID and topic ID match
* Loops through the found topic timers
  * If there is no bookmark for the OP of the topic, then we just create a bookmark with a reminder
  * If there is a bookmark for the OP of the topic and it does **not** have a reminder set, then just 
update it with the topic timer reminder
  * If there is a bookmark for the OP of the topic with a reminder then just discard the topic timer
* Cancels all outstanding user reminder topic timers
* **Trashes (not deletes) all user reminder topic timers**

Notes:

* For now I have left the user reminder topic timer job class in place; this is so the jobs can be cancelled in the migration. It and the specs will be deleted in the next PR.
* At a later date I will write a migration to delete all trashed user topic timers. They are not deleted here in case there are data issues and they need to be recovered.
* A future PR will change the UI of the topic timer modal to make it look more like the bookmark modal.
2020-09-14 11:11:55 +10:00

186 lines
5.0 KiB
Ruby

# frozen_string_literal: true
class TopicTimer < ActiveRecord::Base
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 :ensure_update_will_happen
scope :scheduled_bump_topics, -> { where(status_type: TopicTimer.types[:bump], deleted_at: nil).pluck(:topic_id) }
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?
# private implementation detail have to use send
self.send("cancel_auto_#{self.class.types[status_type]}_job")
end
end
after_save do
if (saved_change_to_execute_at? || saved_change_to_user_id?)
now = Time.zone.now
time = execute_at < now ? now : execute_at
# private implementation detail have to use send
self.send("schedule_auto_#{self.class.types[status_type]}_job", time)
end
end
def self.types
@types ||= Enum.new(
close: 1,
open: 2,
publish_to_category: 3,
delete: 4,
reminder: 5,
bump: 6,
delete_replies: 7
)
end
def self.public_types
@_public_types ||= types.except(:reminder)
end
def self.private_types
@_private_types ||= types.only(:reminder)
end
def self.ensure_consistency!
TopicTimer.where("topic_timers.execute_at < ?", Time.zone.now)
.find_each do |topic_timer|
# private implementation detail scoped to class
topic_timer.send(
"schedule_auto_#{self.types[topic_timer.status_type]}_job",
topic_timer.execute_at
)
end
end
def public_type?
!!self.class.public_types[self.status_type]
end
def private_type?
!!self.class.private_types[self.status_type]
end
private
def ensure_update_will_happen
if created_at && (execute_at < created_at)
errors.add(:execute_at, I18n.t(
'activerecord.errors.models.topic_timer.attributes.execute_at.in_the_past'
))
end
end
def cancel_auto_close_job
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
end
alias_method :cancel_auto_open_job, :cancel_auto_close_job
def cancel_auto_publish_to_category_job
Jobs.cancel_scheduled_job(:publish_topic_to_category, topic_timer_id: id)
end
def cancel_auto_delete_job
Jobs.cancel_scheduled_job(:delete_topic, topic_timer_id: id)
end
def cancel_auto_reminder_job
Jobs.cancel_scheduled_job(:topic_reminder, topic_timer_id: id)
end
def cancel_auto_bump_job
Jobs.cancel_scheduled_job(:bump_topic, topic_timer_id: id)
end
def cancel_auto_delete_replies_job
Jobs.cancel_scheduled_job(:delete_replies, topic_timer_id: id)
end
def schedule_auto_delete_replies_job(time)
Jobs.enqueue_at(time, :delete_replies, topic_timer_id: id)
end
def schedule_auto_bump_job(time)
Jobs.enqueue_at(time, :bump_topic, topic_timer_id: id)
end
def schedule_auto_open_job(time)
topic.update_status('closed', true, user) if topic && !topic.closed
Jobs.enqueue_at(time, :toggle_topic_closed,
topic_timer_id: id,
state: false
)
end
def schedule_auto_close_job(time)
topic.update_status('closed', false, user) if topic&.closed
Jobs.enqueue_at(time, :toggle_topic_closed,
topic_timer_id: id,
state: true
)
end
def schedule_auto_publish_to_category_job(time)
Jobs.enqueue_at(time, :publish_topic_to_category, topic_timer_id: id)
end
def publishing_to_category?
self.status_type.to_i == TopicTimer.types[:publish_to_category]
end
def schedule_auto_delete_job(time)
Jobs.enqueue_at(time, :delete_topic, topic_timer_id: id)
end
def schedule_auto_reminder_job(time)
# noop, TODO(martin 2021-03-11): Remove this after timers migrated and outstanding jobs cancelled
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 :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)
#