mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 04:19:05 +08:00
793f39139a
* This PR implements the scheduling and notification system for bookmark reminders. Every 5 minutes a schedule runs to check any reminders that need to be sent before now, limited to **300** reminders at a time. Any leftover reminders will be sent in the next run. This is to avoid having to deal with fickle sidekiq and reminders in the far-flung future, which would necessitate having a background job anyway to clean up any missing `enqueue_at` reminders. * If a reminder is sent its `reminder_at` time is cleared and the `reminder_last_sent_at` time is filled in. Notifications are only user-level notifications for now. * All JavaScript and frontend code related to displaying the bookmark reminder notification is contained here. The reminder functionality is now re-enabled in the bookmark modal as well. * This PR also implements the "Remind me next time I am at my desktop" bookmark reminder functionality. When the user is on a mobile device they are able to select this option. When they choose this option we set a key in Redis saying they have a pending at desktop reminder. The next time they change devices we check if the new device is desktop, and if it is we send reminders using a DistributedMutex. There is also a job to ensure consistency of these reminders in Redis (in case Redis drops the ball) and the at desktop reminders expire after 20 days. * Also in this PR is a fix to delete all Bookmarks for a user via `UserDestroyer`
111 lines
4.2 KiB
Ruby
111 lines
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Jobs
|
|
|
|
# Runs periodically to send out bookmark reminders, capped at 300 at a time.
|
|
# Any leftovers will be caught in the next run, because the reminder_at column
|
|
# is set to NULL once a reminder has been sent.
|
|
class BookmarkReminderNotifications < ::Jobs::Scheduled
|
|
JOB_RUN_NUMBER_KEY ||= 'jobs_bookmark_reminder_notifications_job_run_num'.freeze
|
|
AT_DESKTOP_CONSISTENCY_RUN_NUMBER ||= 6
|
|
|
|
every 5.minutes
|
|
|
|
def self.max_reminder_notifications_per_run
|
|
@@max_reminder_notifications_per_run ||= 3
|
|
@@max_reminder_notifications_per_run
|
|
end
|
|
|
|
def self.max_reminder_notifications_per_run=(max)
|
|
@@max_reminder_notifications_per_run = max
|
|
end
|
|
|
|
def execute(args = nil)
|
|
return if !SiteSetting.enable_bookmarks_with_reminders?
|
|
|
|
bookmarks = Bookmark.pending_reminders
|
|
.where.not(reminder_type: Bookmark.reminder_types[:at_desktop])
|
|
.includes(:user).order('reminder_at ASC')
|
|
|
|
bookmarks.limit(BookmarkReminderNotifications.max_reminder_notifications_per_run).each do |bookmark|
|
|
BookmarkReminderNotificationHandler.send_notification(bookmark)
|
|
end
|
|
|
|
# we only want to ensure the desktop consistency every X runs of this job
|
|
# (every 30 mins in this case) so we don't bother redis too much, and the
|
|
# at desktop consistency problem should not really happen unless people
|
|
# are setting the "at desktop" reminder, going out for milk, and never coming
|
|
# back
|
|
current_job_run_number = Discourse.redis.get(JOB_RUN_NUMBER_KEY).to_i
|
|
if current_job_run_number == AT_DESKTOP_CONSISTENCY_RUN_NUMBER
|
|
ensure_at_desktop_consistency
|
|
end
|
|
|
|
increment_job_run_number(current_job_run_number)
|
|
end
|
|
|
|
def increment_job_run_number(current_job_run_number)
|
|
if current_job_run_number.zero? || current_job_run_number == AT_DESKTOP_CONSISTENCY_RUN_NUMBER
|
|
new_job_run_number = 1
|
|
else
|
|
new_job_run_number = current_job_run_number + 1
|
|
end
|
|
Discourse.redis.set(JOB_RUN_NUMBER_KEY, new_job_run_number)
|
|
end
|
|
|
|
def ensure_at_desktop_consistency
|
|
pending_at_desktop_bookmark_reminders = \
|
|
Bookmark.includes(:user)
|
|
.references(:user)
|
|
.pending_at_desktop_reminders
|
|
.where('users.last_seen_at >= :one_day_ago', one_day_ago: 1.day.ago.utc)
|
|
|
|
return if pending_at_desktop_bookmark_reminders.count.zero?
|
|
|
|
unique_users = pending_at_desktop_bookmark_reminders.map(&:user).uniq.map { |u| [u.id, u] }.flatten
|
|
unique_users = Hash[*unique_users]
|
|
pending_reminders_for_redis_check = unique_users.keys.map do |user_id|
|
|
"#{BookmarkReminderNotificationHandler::PENDING_AT_DESKTOP_KEY_PREFIX}#{user_id}"
|
|
end
|
|
|
|
Discourse.redis.mget(pending_reminders_for_redis_check).each.with_index do |value, idx|
|
|
next if value.present?
|
|
user_id = pending_reminders_for_redis_check[idx][/\d+/].to_i
|
|
user = unique_users[user_id]
|
|
|
|
user_pending_bookmark_reminders = pending_at_desktop_bookmark_reminders.select do |bookmark|
|
|
bookmark.user == user
|
|
end
|
|
|
|
user_expired_bookmark_reminders = user_pending_bookmark_reminders.select do |bookmark|
|
|
bookmark.reminder_set_at <= expiry_limit_datetime
|
|
end
|
|
|
|
next if user_pending_bookmark_reminders.length == user_expired_bookmark_reminders.length
|
|
|
|
# only tell the cache-gods that this user has pending "at desktop" reminders
|
|
# if they haven't let them all expire before coming back to their desktop
|
|
#
|
|
# the next time they visit the desktop the reminders will be cleared out once
|
|
# the notifications are sent
|
|
BookmarkReminderNotificationHandler.cache_pending_at_desktop_reminder(user)
|
|
end
|
|
|
|
clear_expired_at_desktop_reminders
|
|
end
|
|
|
|
def clear_expired_at_desktop_reminders
|
|
Bookmark
|
|
.pending_at_desktop_reminders
|
|
.where('reminder_set_at <= :expiry_limit_datetime', expiry_limit_datetime: expiry_limit_datetime)
|
|
.update_all(
|
|
reminder_set_at: nil, reminder_type: nil
|
|
)
|
|
end
|
|
|
|
def expiry_limit_datetime
|
|
BookmarkReminderNotificationHandler::PENDING_AT_DESKTOP_EXPIRY_DAYS.days.ago.utc
|
|
end
|
|
end
|
|
end
|