mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 09:44:22 +08:00
723dc1fa55
The primary key is usually a bigint column, but the foreign key columns are usually of integer type. This can lead to issues when joining these columns due to mismatched types and different value ranges. This was using a temporary plugin / test API to make tests pass. After more careful consideration, we concluded that it is safe to alter the tables directly.
219 lines
7.1 KiB
Ruby
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 :bigint
|
|
# 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)
|
|
#
|