discourse/app/models/bookmark.rb
Martin Brennan e58e7a49f5
FIX: Bookmark reminder was clearing incorrectly (#28506)
Followup 76c56c8284

The change introduced above made it so the expired
bookmark reminders were cleared when using the bulk
action menu for bookmarks. However this also affected
clearing reminders for bookmarks when sending notifications.

When clearing bookmark reminders after sending notifications,
we take into account the auto delete preference:

* never          - The bookmark `reminder_at` date should not be cleared,
                   and the bookmark is kept.
* clear_reminder - The bookmark `reminder_at` date is cleared and
                   the bookmark is kept

The `never` option made it so "expired" bookmark reminder show
on the user's bookmark list.

This commit fixes the change from the other commit and only
forces clearing of `reminder_at` if using the bookmark bulk
action service.
2024-08-26 09:17:39 +10:00

219 lines
7.1 KiB
Ruby

# frozen_string_literal: true
class Bookmark < ActiveRecord::Base
DEFAULT_BOOKMARKABLES = [
RegisteredBookmarkable.new(PostBookmarkable),
RegisteredBookmarkable.new(TopicBookmarkable),
]
def self.registered_bookmarkables
Set.new(DEFAULT_BOOKMARKABLES | DiscoursePluginRegistry.bookmarkables)
end
def self.registered_bookmarkable_from_type(type)
begin
resolved_type = Bookmark.polymorphic_class_for(type).name
Bookmark.registered_bookmarkables.find { |bm| bm.model.name == resolved_type }
# If the class cannot be found from the provided type using polymorphic_class_for,
# then the type is not valid and thus there will not be any registered bookmarkable.
rescue NameError
end
end
def self.valid_bookmarkable_types
Bookmark.registered_bookmarkables.map { |bm| bm.model.polymorphic_name }
end
belongs_to :user
belongs_to :bookmarkable, polymorphic: true
def self.auto_delete_preferences
@auto_delete_preferences ||=
Enum.new(never: 0, when_reminder_sent: 1, on_owner_reply: 2, clear_reminder: 3)
end
def self.select_type(bookmarks_relation, type)
bookmarks_relation.select { |bm| bm.bookmarkable_type == type }
end
validate :polymorphic_columns_present, on: %i[create update]
validate :valid_bookmarkable_type, on: %i[create update]
validate :unique_per_bookmarkable,
on: %i[create update],
if:
Proc.new { |b|
b.will_save_change_to_bookmarkable_id? || b.will_save_change_to_bookmarkable_type? ||
b.will_save_change_to_user_id?
}
validate :ensure_sane_reminder_at_time, if: :will_save_change_to_reminder_at?
validate :bookmark_limit_not_reached
validates :name, length: { maximum: 100 }
def registered_bookmarkable
Bookmark.registered_bookmarkable_from_type(self.bookmarkable_type)
end
def polymorphic_columns_present
return if self.bookmarkable_id.present? && self.bookmarkable_type.present?
self.errors.add(:base, I18n.t("bookmarks.errors.bookmarkable_id_type_required"))
end
def unique_per_bookmarkable
if !Bookmark.exists?(
user_id: user_id,
bookmarkable_id: bookmarkable_id,
bookmarkable_type: bookmarkable_type,
)
return
end
self.errors.add(:base, I18n.t("bookmarks.errors.already_bookmarked", type: bookmarkable_type))
end
def ensure_sane_reminder_at_time
return if reminder_at.blank?
if reminder_at < Time.zone.now
self.errors.add(:base, I18n.t("bookmarks.errors.cannot_set_past_reminder"))
end
if reminder_at > 10.years.from_now.utc
self.errors.add(:base, I18n.t("bookmarks.errors.cannot_set_reminder_in_distant_future"))
end
end
def bookmark_limit_not_reached
return if user.bookmarks.count < SiteSetting.max_bookmarks_per_user
return if !new_record?
self.errors.add(
:base,
I18n.t(
"bookmarks.errors.too_many",
user_bookmarks_url: "#{Discourse.base_url}/my/activity/bookmarks",
limit: SiteSetting.max_bookmarks_per_user,
),
)
end
def valid_bookmarkable_type
return if Bookmark.valid_bookmarkable_types.include?(self.bookmarkable_type)
self.errors.add(
:base,
I18n.t("bookmarks.errors.invalid_bookmarkable", type: self.bookmarkable_type),
)
end
def auto_delete_when_reminder_sent?
self.auto_delete_preference == Bookmark.auto_delete_preferences[:when_reminder_sent]
end
def auto_clear_reminder_when_reminder_sent?
self.auto_delete_preference == Bookmark.auto_delete_preferences[:clear_reminder]
end
def reminder_at_ics(offset: 0)
(reminder_at + offset).strftime(I18n.t("datetime_formats.formats.calendar_ics"))
end
def clear_reminder!(force_clear_reminder_at: false)
reminder_update_attrs = { reminder_last_sent_at: Time.zone.now, reminder_set_at: nil }
if self.auto_clear_reminder_when_reminder_sent? || force_clear_reminder_at
reminder_update_attrs[:reminder_at] = nil
end
update!(reminder_update_attrs)
end
def reminder_at_in_zone(timezone)
self.reminder_at.in_time_zone(timezone)
end
scope :with_reminders, -> { where("reminder_at IS NOT NULL") }
scope :pending_reminders,
->(before_time = Time.now.utc) do
with_reminders.where("reminder_at <= ?", before_time).where(reminder_last_sent_at: nil)
end
scope :pending_reminders_for_user, ->(user) { pending_reminders.where(user: user) }
scope :for_user_in_topic,
->(user_id, topic_id) do
joins(
"LEFT JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'",
).joins(
"LEFT JOIN topics ON (topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic') OR
(topics.id = posts.topic_id)",
).where(
"bookmarks.user_id = :user_id AND (topics.id = :topic_id OR posts.topic_id = :topic_id)
AND posts.deleted_at IS NULL AND topics.deleted_at IS NULL",
user_id: user_id,
topic_id: topic_id,
)
end
def self.count_per_day(opts = nil)
opts ||= {}
result =
where(
"bookmarks.created_at >= ?",
opts[:start_date] || (opts[:since_days_ago] || 30).days.ago,
)
result = result.where("bookmarks.created_at <= ?", opts[:end_date]) if opts[:end_date]
if opts[:category_id]
result =
result
.joins(
"LEFT JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'",
)
.joins(
"LEFT JOIN topics ON (topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic') OR (topics.id = posts.topic_id)",
)
.where("topics.deleted_at IS NULL AND posts.deleted_at IS NULL")
.merge(Topic.in_category_and_subcategories(opts[:category_id]))
end
result.group("date(bookmarks.created_at)").order("date(bookmarks.created_at)").count
end
##
# Deletes bookmarks that are attached to the bookmarkable records that were deleted
# more than X days ago. We don't delete bookmarks instantly when trashable bookmarkables
# are deleted so that there is a grace period to un-delete.
def self.cleanup!
Bookmark.registered_bookmarkables.each(&:cleanup_deleted)
end
end
# == Schema Information
#
# Table name: bookmarks
#
# id :bigint not null, primary key
# user_id :bigint not null
# name :string(100)
# reminder_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# reminder_last_sent_at :datetime
# reminder_set_at :datetime
# auto_delete_preference :integer default(0), not null
# pinned :boolean default(FALSE)
# bookmarkable_id :integer
# bookmarkable_type :string
#
# Indexes
#
# idx_bookmarks_user_polymorphic_unique (user_id,bookmarkable_type,bookmarkable_id) UNIQUE
# index_bookmarks_on_reminder_at (reminder_at)
# index_bookmarks_on_reminder_set_at (reminder_set_at)
# index_bookmarks_on_user_id (user_id)
#