2019-12-11 12:04:02 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class Bookmark < ActiveRecord::Base
|
2022-01-07 10:16:43 +08:00
|
|
|
# these columns were here for a very short amount of time,
|
|
|
|
# hence the very short ignore time
|
|
|
|
self.ignored_columns = [
|
2022-01-10 15:50:53 +08:00
|
|
|
"topic_id", # TODO 2022-04-01: remove
|
|
|
|
"reminder_type" # TODO 2021-04-01: remove
|
2022-01-07 10:16:43 +08:00
|
|
|
]
|
|
|
|
|
2019-12-11 12:04:02 +08:00
|
|
|
belongs_to :user
|
|
|
|
belongs_to :post
|
2021-09-15 08:16:54 +08:00
|
|
|
has_one :topic, through: :post
|
2022-01-06 06:56:05 +08:00
|
|
|
belongs_to :bookmarkable, polymorphic: true
|
2021-09-15 08:16:54 +08:00
|
|
|
|
|
|
|
delegate :topic_id, to: :post
|
2019-12-11 12:04:02 +08:00
|
|
|
|
2021-09-16 07:56:54 +08:00
|
|
|
def self.auto_delete_preferences
|
|
|
|
@auto_delete_preferences ||= Enum.new(
|
|
|
|
never: 0,
|
|
|
|
when_reminder_sent: 1,
|
2022-03-09 01:44:18 +08:00
|
|
|
on_owner_reply: 2,
|
|
|
|
clear_reminder: 3,
|
2021-09-16 07:56:54 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-09-09 08:50:26 +08:00
|
|
|
validate :unique_per_post_for_user,
|
|
|
|
on: [:create, :update],
|
|
|
|
if: Proc.new { |b| b.will_save_change_to_post_id? || b.will_save_change_to_user_id? }
|
|
|
|
|
2021-09-21 06:45:47 +08:00
|
|
|
validate :for_topic_must_use_first_post,
|
|
|
|
on: [:create, :update],
|
|
|
|
if: Proc.new { |b| b.will_save_change_to_post_id? || b.will_save_change_to_for_topic? }
|
|
|
|
|
2022-03-09 01:44:18 +08:00
|
|
|
validate :ensure_sane_reminder_at_time, if: :will_save_change_to_reminder_at?
|
2021-01-19 06:53:49 +08:00
|
|
|
validate :bookmark_limit_not_reached
|
2020-07-08 15:19:01 +08:00
|
|
|
validates :name, length: { maximum: 100 }
|
2020-03-12 08:16:00 +08:00
|
|
|
|
|
|
|
def unique_per_post_for_user
|
2021-09-21 06:45:47 +08:00
|
|
|
exists = if is_for_first_post?
|
2021-09-15 09:29:22 +08:00
|
|
|
Bookmark.exists?(user_id: user_id, post_id: post_id, for_topic: for_topic)
|
|
|
|
else
|
|
|
|
Bookmark.exists?(user_id: user_id, post_id: post_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
if exists
|
2021-09-09 08:50:26 +08:00
|
|
|
self.errors.add(:base, I18n.t("bookmarks.errors.already_bookmarked_post"))
|
|
|
|
end
|
2020-03-12 08:16:00 +08:00
|
|
|
end
|
|
|
|
|
2021-09-21 06:45:47 +08:00
|
|
|
def for_topic_must_use_first_post
|
|
|
|
if !is_for_first_post? && self.for_topic
|
|
|
|
self.errors.add(:base, I18n.t("bookmarks.errors.for_topic_must_use_first_post"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-03-12 08:16:00 +08:00
|
|
|
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
|
|
|
|
|
2021-01-19 06:53:49 +08:00
|
|
|
def bookmark_limit_not_reached
|
2021-04-09 11:06:35 +08:00
|
|
|
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
|
|
|
|
)
|
|
|
|
)
|
2021-01-19 06:53:49 +08:00
|
|
|
end
|
|
|
|
|
2021-09-21 06:45:47 +08:00
|
|
|
def is_for_first_post?
|
|
|
|
@is_for_first_post ||= new_record? ? Post.exists?(id: post_id, post_number: 1) : post.post_number == 1
|
|
|
|
end
|
|
|
|
|
2020-07-29 15:02:36 +08:00
|
|
|
def auto_delete_when_reminder_sent?
|
2020-07-21 08:00:39 +08:00
|
|
|
self.auto_delete_preference == Bookmark.auto_delete_preferences[:when_reminder_sent]
|
|
|
|
end
|
|
|
|
|
2020-07-29 15:02:36 +08:00
|
|
|
def auto_delete_on_owner_reply?
|
2020-07-21 08:00:39 +08:00
|
|
|
self.auto_delete_preference == Bookmark.auto_delete_preferences[:on_owner_reply]
|
|
|
|
end
|
|
|
|
|
2022-03-09 01:44:18 +08:00
|
|
|
def auto_clear_reminder_when_reminder_sent?
|
|
|
|
self.auto_delete_preference == Bookmark.auto_delete_preferences[:clear_reminder]
|
|
|
|
end
|
|
|
|
|
2021-09-15 08:16:54 +08:00
|
|
|
def reminder_at_ics(offset: 0)
|
|
|
|
(reminder_at + offset).strftime(I18n.t("datetime_formats.formats.calendar_ics"))
|
|
|
|
end
|
|
|
|
|
2020-07-29 07:43:32 +08:00
|
|
|
def clear_reminder!
|
2021-03-15 13:10:40 +08:00
|
|
|
update!(
|
2020-07-29 07:43:32 +08:00
|
|
|
reminder_last_sent_at: Time.zone.now,
|
2022-03-09 01:44:18 +08:00
|
|
|
reminder_set_at: nil,
|
2020-07-29 07:43:32 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-09-15 08:16:54 +08:00
|
|
|
scope :with_reminders, -> do
|
|
|
|
where("reminder_at IS NOT NULL")
|
|
|
|
end
|
|
|
|
|
2020-03-12 08:16:00 +08:00
|
|
|
scope :pending_reminders, ->(before_time = Time.now.utc) do
|
2022-03-18 22:31:35 +08:00
|
|
|
with_reminders.where("reminder_at <= ?", before_time).where(reminder_last_sent_at: nil)
|
2020-03-12 08:16:00 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
scope :pending_reminders_for_user, ->(user) do
|
|
|
|
pending_reminders.where(user: user)
|
|
|
|
end
|
|
|
|
|
2021-09-15 08:16:54 +08:00
|
|
|
scope :for_user_in_topic, ->(user_id, topic_id) {
|
|
|
|
joins(:post).where(user_id: user_id, posts: { topic_id: topic_id })
|
|
|
|
}
|
|
|
|
|
2021-09-15 09:29:22 +08:00
|
|
|
def self.find_for_topic_by_user(topic_id, user_id)
|
|
|
|
for_user_in_topic(user_id, topic_id).where(for_topic: true).first
|
|
|
|
end
|
|
|
|
|
2020-04-22 11:44:19 +08:00
|
|
|
def self.count_per_day(opts = nil)
|
|
|
|
opts ||= {}
|
|
|
|
result = where('bookmarks.created_at >= ?', opts[:start_date] || (opts[:since_days_ago] || 30).days.ago)
|
2021-09-15 08:16:54 +08:00
|
|
|
|
|
|
|
if opts[:end_date]
|
|
|
|
result = result.where('bookmarks.created_at <= ?', opts[:end_date])
|
|
|
|
end
|
|
|
|
|
|
|
|
if opts[:category_id]
|
|
|
|
result = result.joins(:topic).merge(Topic.in_category_and_subcategories(opts[:category_id]))
|
|
|
|
end
|
|
|
|
|
2020-04-22 11:44:19 +08:00
|
|
|
result.group('date(bookmarks.created_at)')
|
|
|
|
.order('date(bookmarks.created_at)')
|
|
|
|
.count
|
|
|
|
end
|
2020-10-14 07:38:57 +08:00
|
|
|
|
|
|
|
##
|
|
|
|
# Deletes bookmarks that are attached to posts/topics that were deleted
|
|
|
|
# more than X days ago. We don't delete bookmarks instantly when a post/topic
|
|
|
|
# is deleted so that there is a grace period to un-delete.
|
|
|
|
def self.cleanup!
|
|
|
|
grace_time = 3.days.ago
|
2021-06-16 06:30:40 +08:00
|
|
|
topics_deleted = DB.query(<<~SQL, grace_time: grace_time)
|
2020-10-14 07:38:57 +08:00
|
|
|
DELETE FROM bookmarks b
|
|
|
|
USING topics t, posts p
|
2021-09-15 08:16:54 +08:00
|
|
|
WHERE (t.id = p.topic_id AND b.post_id = p.id)
|
2020-10-14 07:38:57 +08:00
|
|
|
AND (t.deleted_at < :grace_time OR p.deleted_at < :grace_time)
|
2021-09-15 08:16:54 +08:00
|
|
|
RETURNING t.id AS topic_id
|
2020-10-14 07:38:57 +08:00
|
|
|
SQL
|
2021-06-16 06:30:40 +08:00
|
|
|
|
|
|
|
topics_deleted_ids = topics_deleted.map(&:topic_id).uniq
|
|
|
|
topics_deleted_ids.each do |topic_id|
|
|
|
|
Jobs.enqueue(:sync_topic_user_bookmarked, topic_id: topic_id)
|
|
|
|
end
|
2020-10-14 07:38:57 +08:00
|
|
|
end
|
2019-12-11 12:04:02 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: bookmarks
|
|
|
|
#
|
2020-07-22 18:32:31 +08:00
|
|
|
# id :bigint not null, primary key
|
|
|
|
# user_id :bigint not null
|
|
|
|
# post_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
|
2021-03-22 07:50:22 +08:00
|
|
|
# pinned :boolean default(FALSE)
|
2021-09-15 09:29:22 +08:00
|
|
|
# for_topic :boolean default(FALSE), not null
|
DEV: Re-add polymorphic bookmark columns (#16261)
This commit is a redo of2f1ddadff7dd47f824070c8a3f633f00a27aacde
which we reverted because it blew up an internal CI check. I looked
into it, and it happened because the old migration to add the bookmark
columns still existed, and those columns were dropped in a post migrate,
so the two migrations to add the columns were conflicting before
the post migrate was run.
------
This commit only includes the creation of the new columns and index,
and does not add any triggers, backfilling, or new data.
A backfill will be done in the final PR when we switch this over.
Intermediate PRs will look something like this:
Add an experimental site setting for using polymorphic bookmarks,
and make sure in the places where bookmarks are created or updated
we fill in the columns. This setting will be used in subsequent
PRs as well.
Listing and searching bookmarks based on polymorphic associations
Creating post and topic bookmarks using polymorphic associations,
and changing special for_topic logic to just rely on the Topic
bookmarkable_type
Querying bookmark reminders based on polymorphic associations
Make sure various other areas like importers, bookmark guardian,
and others all rely on the associations
Prepare plugins that rely on the Bookmark model to use polymorphic
associations
The final core PR will remove all the setting gates and switch over
to using the polymorphic associations, backfill the bookmarks
table columns, and ignore the old post_id and for_topic colummns.
Then it will just be a matter of dropping the old columns down the
line.
2022-03-23 10:43:08 +08:00
|
|
|
# bookmarkable_id :integer
|
|
|
|
# bookmarkable_type :string
|
2019-12-11 12:04:02 +08:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
DEV: Re-add polymorphic bookmark columns (#16261)
This commit is a redo of2f1ddadff7dd47f824070c8a3f633f00a27aacde
which we reverted because it blew up an internal CI check. I looked
into it, and it happened because the old migration to add the bookmark
columns still existed, and those columns were dropped in a post migrate,
so the two migrations to add the columns were conflicting before
the post migrate was run.
------
This commit only includes the creation of the new columns and index,
and does not add any triggers, backfilling, or new data.
A backfill will be done in the final PR when we switch this over.
Intermediate PRs will look something like this:
Add an experimental site setting for using polymorphic bookmarks,
and make sure in the places where bookmarks are created or updated
we fill in the columns. This setting will be used in subsequent
PRs as well.
Listing and searching bookmarks based on polymorphic associations
Creating post and topic bookmarks using polymorphic associations,
and changing special for_topic logic to just rely on the Topic
bookmarkable_type
Querying bookmark reminders based on polymorphic associations
Make sure various other areas like importers, bookmark guardian,
and others all rely on the associations
Prepare plugins that rely on the Bookmark model to use polymorphic
associations
The final core PR will remove all the setting gates and switch over
to using the polymorphic associations, backfill the bookmarks
table columns, and ignore the old post_id and for_topic colummns.
Then it will just be a matter of dropping the old columns down the
line.
2022-03-23 10:43:08 +08:00
|
|
|
# idx_bookmarks_user_polymorphic_unique (user_id,bookmarkable_type,bookmarkable_id) UNIQUE
|
2021-09-15 09:29:22 +08:00
|
|
|
# index_bookmarks_on_post_id (post_id)
|
|
|
|
# index_bookmarks_on_reminder_at (reminder_at)
|
|
|
|
# index_bookmarks_on_reminder_set_at (reminder_set_at)
|
|
|
|
# index_bookmarks_on_user_id (user_id)
|
|
|
|
# index_bookmarks_on_user_id_and_post_id_and_for_topic (user_id,post_id,for_topic) UNIQUE
|
2019-12-11 12:04:02 +08:00
|
|
|
#
|