2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-03-01 07:07:44 -05:00
|
|
|
class Notification < ActiveRecord::Base
|
2013-02-05 14:16:51 -05:00
|
|
|
belongs_to :user
|
|
|
|
belongs_to :topic
|
|
|
|
|
2019-11-27 17:32:35 -05:00
|
|
|
MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS = 24
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
validates_presence_of :data
|
|
|
|
validates_presence_of :notification_type
|
|
|
|
|
2013-02-28 13:54:12 -05:00
|
|
|
scope :unread, lambda { where(read: false) }
|
2017-07-27 21:20:09 -04:00
|
|
|
scope :recent, lambda { |n = nil| n ||= 10; order('notifications.created_at desc').limit(n) }
|
2015-06-22 16:14:22 -04:00
|
|
|
scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id')
|
2017-07-27 21:20:09 -04:00
|
|
|
.where('topics.id IS NULL OR topics.deleted_at IS NULL') }
|
2013-02-28 13:54:12 -05:00
|
|
|
|
2019-12-05 04:06:06 -05:00
|
|
|
scope :filter_by_consolidation_data, ->(notification_type, data) {
|
|
|
|
notifications = where(notification_type: notification_type)
|
|
|
|
|
|
|
|
case notification_type
|
|
|
|
when types[:liked], types[:liked_consolidated]
|
|
|
|
key = "display_username"
|
|
|
|
consolidation_window = SiteSetting.likes_notification_consolidation_window_mins.minutes.ago
|
|
|
|
when types[:private_message]
|
|
|
|
key = "topic_title"
|
|
|
|
consolidation_window = MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours.ago
|
|
|
|
when types[:membership_request_consolidated]
|
|
|
|
key = "group_name"
|
|
|
|
consolidation_window = MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours.ago
|
|
|
|
end
|
|
|
|
|
|
|
|
notifications = notifications.where("created_at > ? AND data::json ->> '#{key}' = ?", consolidation_window, data[key.to_sym]) if data[key&.to_sym].present?
|
|
|
|
notifications = notifications.where("data::json ->> 'username2' IS NULL") if notification_type == types[:liked]
|
|
|
|
|
|
|
|
notifications
|
2019-01-15 21:40:16 -05:00
|
|
|
}
|
|
|
|
|
2017-06-12 03:41:39 -04:00
|
|
|
attr_accessor :skip_send_email
|
|
|
|
|
2017-08-31 00:06:56 -04:00
|
|
|
after_commit :refresh_notification_count, on: [:create, :update, :destroy]
|
2016-12-21 23:29:34 -05:00
|
|
|
|
2019-08-15 14:45:30 -04:00
|
|
|
after_commit(on: :create) do
|
2019-11-27 18:01:55 -05:00
|
|
|
DiscourseEvent.trigger(:notification_created, self)
|
2019-12-05 04:06:06 -05:00
|
|
|
send_email unless NotificationConsolidator.new(self).consolidate!
|
2019-08-15 14:45:30 -04:00
|
|
|
end
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
before_create do
|
|
|
|
self.high_priority = Notification.high_priority_types.include?(self.notification_type)
|
|
|
|
end
|
|
|
|
|
2020-02-23 19:42:50 -05:00
|
|
|
def self.purge_old!
|
|
|
|
return if SiteSetting.max_notifications_per_user == 0
|
|
|
|
|
|
|
|
DB.exec(<<~SQL, SiteSetting.max_notifications_per_user)
|
|
|
|
DELETE FROM notifications n1
|
|
|
|
USING (
|
|
|
|
SELECT * FROM (
|
|
|
|
SELECT
|
|
|
|
user_id,
|
|
|
|
id,
|
|
|
|
rank() OVER (PARTITION BY user_id ORDER BY id DESC)
|
|
|
|
FROM notifications
|
|
|
|
) AS X
|
|
|
|
WHERE rank = ?
|
|
|
|
) n2
|
|
|
|
WHERE n1.user_id = n2.user_id AND n1.id < n2.id
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2013-05-16 03:50:14 -04:00
|
|
|
def self.ensure_consistency!
|
2020-03-31 19:09:20 -04:00
|
|
|
DB.exec(<<~SQL)
|
2017-04-24 16:51:09 -04:00
|
|
|
DELETE
|
|
|
|
FROM notifications n
|
2020-03-31 19:09:20 -04:00
|
|
|
WHERE high_priority
|
2017-04-24 16:51:09 -04:00
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1
|
|
|
|
FROM posts p
|
|
|
|
JOIN topics t ON t.id = p.topic_id
|
|
|
|
WHERE p.deleted_at IS NULL
|
|
|
|
AND t.deleted_at IS NULL
|
|
|
|
AND p.post_number = n.post_number
|
|
|
|
AND t.id = n.topic_id
|
|
|
|
)
|
|
|
|
SQL
|
2013-05-16 03:50:14 -04:00
|
|
|
end
|
|
|
|
|
2013-03-01 07:07:44 -05:00
|
|
|
def self.types
|
2016-01-08 05:53:52 -05:00
|
|
|
@types ||= Enum.new(mentioned: 1,
|
|
|
|
replied: 2,
|
|
|
|
quoted: 3,
|
|
|
|
edited: 4,
|
|
|
|
liked: 5,
|
|
|
|
private_message: 6,
|
|
|
|
invited_to_private_message: 7,
|
|
|
|
invitee_accepted: 8,
|
|
|
|
posted: 9,
|
|
|
|
moved_post: 10,
|
|
|
|
linked: 11,
|
|
|
|
granted_badge: 12,
|
|
|
|
invited_to_topic: 13,
|
|
|
|
custom: 14,
|
2016-01-27 05:38:14 -05:00
|
|
|
group_mentioned: 15,
|
2016-07-06 15:56:40 -04:00
|
|
|
group_message_summary: 16,
|
2017-05-16 14:49:42 -04:00
|
|
|
watching_first_post: 17,
|
2019-01-15 21:40:16 -05:00
|
|
|
topic_reminder: 18,
|
|
|
|
liked_consolidated: 19,
|
2019-06-10 21:17:23 -04:00
|
|
|
post_approved: 20,
|
2019-08-06 06:29:46 -04:00
|
|
|
code_review_commit_approved: 21,
|
2019-11-27 17:32:35 -05:00
|
|
|
membership_request_accepted: 22,
|
2020-03-11 20:16:00 -04:00
|
|
|
membership_request_consolidated: 23,
|
|
|
|
bookmark_reminder: 24
|
2016-01-27 05:38:14 -05:00
|
|
|
)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
def self.high_priority_types
|
|
|
|
@high_priority_types ||= [
|
|
|
|
types[:private_message],
|
|
|
|
types[:bookmark_reminder]
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.normal_priority_types
|
|
|
|
@normal_priority_types ||= types.reject { |_k, v| high_priority_types.include?(v) }.values
|
|
|
|
end
|
|
|
|
|
2013-02-25 02:42:42 -05:00
|
|
|
def self.mark_posts_read(user, topic_id, post_numbers)
|
2018-05-25 21:11:10 -04:00
|
|
|
Notification
|
2018-05-28 04:41:38 -04:00
|
|
|
.where(
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: topic_id,
|
|
|
|
post_number: post_numbers,
|
|
|
|
read: false
|
|
|
|
)
|
2018-05-25 20:09:48 -04:00
|
|
|
.update_all(read: true)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2016-09-16 02:14:00 -04:00
|
|
|
def self.read(user, notification_ids)
|
2018-05-25 21:11:10 -04:00
|
|
|
Notification
|
2018-05-28 04:41:38 -04:00
|
|
|
.where(
|
|
|
|
id: notification_ids,
|
|
|
|
user_id: user.id,
|
|
|
|
read: false
|
|
|
|
)
|
2017-07-27 21:20:09 -04:00
|
|
|
.update_all(read: true)
|
2016-09-16 02:14:00 -04:00
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def self.interesting_after(min_date)
|
2017-07-27 21:20:09 -04:00
|
|
|
result = where("created_at > ?", min_date)
|
|
|
|
.includes(:topic)
|
|
|
|
.visible
|
|
|
|
.unread
|
|
|
|
.limit(20)
|
|
|
|
.order("CASE WHEN notification_type = #{Notification.types[:replied]} THEN 1
|
2013-03-01 07:07:44 -05:00
|
|
|
WHEN notification_type = #{Notification.types[:mentioned]} THEN 2
|
2013-02-05 14:16:51 -05:00
|
|
|
ELSE 3
|
|
|
|
END, created_at DESC").to_a
|
|
|
|
|
|
|
|
# Remove any duplicates by type and topic
|
|
|
|
if result.present?
|
2013-02-07 10:45:24 -05:00
|
|
|
seen = {}
|
2013-02-05 14:16:51 -05:00
|
|
|
to_remove = Set.new
|
|
|
|
|
|
|
|
result.each do |r|
|
|
|
|
seen[r.notification_type] ||= Set.new
|
|
|
|
if seen[r.notification_type].include?(r.topic_id)
|
2013-02-07 10:45:24 -05:00
|
|
|
to_remove << r.id
|
2013-02-05 14:16:51 -05:00
|
|
|
else
|
|
|
|
seen[r.notification_type] << r.topic_id
|
|
|
|
end
|
|
|
|
end
|
2017-07-27 21:20:09 -04:00
|
|
|
result.reject! { |r| to_remove.include?(r.id) }
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2014-05-26 13:26:28 -04:00
|
|
|
# Clean up any notifications the user can no longer see. For example, if a topic was previously
|
|
|
|
# public then turns private.
|
|
|
|
def self.remove_for(user_id, topic_id)
|
|
|
|
Notification.where(user_id: user_id, topic_id: topic_id).delete_all
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Be wary of calling this frequently. O(n) JSON parsing can suck.
|
|
|
|
def data_hash
|
2019-12-05 10:05:39 -05:00
|
|
|
@data_hash ||= begin
|
|
|
|
return {} if data.blank?
|
2016-02-22 19:34:16 -05:00
|
|
|
|
2019-12-05 10:05:39 -05:00
|
|
|
parsed = JSON.parse(data)
|
|
|
|
return {} if parsed.blank?
|
2015-09-03 23:34:21 -04:00
|
|
|
|
2019-12-05 10:05:39 -05:00
|
|
|
parsed.with_indifferent_access
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def url
|
2016-02-01 13:12:10 -05:00
|
|
|
topic.relative_url(post_number) if topic.present?
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def post
|
2013-02-28 13:54:12 -05:00
|
|
|
return if topic_id.blank? || post_number.blank?
|
2014-05-06 09:41:59 -04:00
|
|
|
Post.find_by(topic_id: topic_id, post_number: post_number)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
2013-05-16 01:03:03 -04:00
|
|
|
|
2014-02-13 01:12:17 -05:00
|
|
|
def self.recent_report(user, count = nil)
|
2016-03-05 17:21:38 -05:00
|
|
|
return unless user && user.user_option
|
|
|
|
|
2014-02-13 15:20:56 -05:00
|
|
|
count ||= 10
|
2015-02-18 20:40:00 -05:00
|
|
|
notifications = user.notifications
|
2017-07-27 21:20:09 -04:00
|
|
|
.visible
|
|
|
|
.recent(count)
|
|
|
|
.includes(:topic)
|
2016-03-05 17:21:38 -05:00
|
|
|
|
|
|
|
if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:never]
|
2019-01-16 04:08:59 -05:00
|
|
|
[
|
|
|
|
Notification.types[:liked],
|
|
|
|
Notification.types[:liked_consolidated]
|
|
|
|
].each do |notification_type|
|
|
|
|
notifications = notifications.where(
|
|
|
|
'notification_type <> ?', notification_type
|
|
|
|
)
|
|
|
|
end
|
2016-03-05 17:21:38 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
notifications = notifications.to_a
|
2014-02-13 01:12:17 -05:00
|
|
|
|
|
|
|
if notifications.present?
|
2016-02-15 03:29:35 -05:00
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
ids = DB.query_single(<<~SQL, limit: count.to_i)
|
2016-02-15 03:29:35 -05:00
|
|
|
SELECT n.id FROM notifications n
|
|
|
|
WHERE
|
2020-03-31 19:09:20 -04:00
|
|
|
n.high_priority = TRUE AND
|
2016-02-15 03:29:35 -05:00
|
|
|
n.user_id = #{user.id.to_i} AND
|
|
|
|
NOT read
|
|
|
|
ORDER BY n.id ASC
|
2020-03-31 19:09:20 -04:00
|
|
|
LIMIT :limit
|
2018-06-19 02:13:14 -04:00
|
|
|
SQL
|
2016-02-15 03:29:35 -05:00
|
|
|
|
|
|
|
if ids.length > 0
|
|
|
|
notifications += user
|
|
|
|
.notifications
|
|
|
|
.order('notifications.created_at DESC')
|
|
|
|
.where(id: ids)
|
|
|
|
.joins(:topic)
|
|
|
|
.limit(count)
|
|
|
|
end
|
|
|
|
|
2017-07-27 21:20:09 -04:00
|
|
|
notifications.uniq(&:id).sort do |x, y|
|
2020-03-31 19:09:20 -04:00
|
|
|
if x.unread_high_priority? && !y.unread_high_priority?
|
2014-02-13 01:27:35 -05:00
|
|
|
-1
|
2020-03-31 19:09:20 -04:00
|
|
|
elsif y.unread_high_priority? && !x.unread_high_priority?
|
2014-02-13 01:27:35 -05:00
|
|
|
1
|
|
|
|
else
|
|
|
|
y.created_at <=> x.created_at
|
|
|
|
end
|
|
|
|
end.take(count)
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
2014-02-13 01:12:17 -05:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
def unread_high_priority?
|
|
|
|
self.high_priority? && !read
|
2014-02-13 01:12:17 -05:00
|
|
|
end
|
2013-05-16 01:03:03 -04:00
|
|
|
|
2016-01-26 20:19:49 -05:00
|
|
|
def post_id
|
2019-10-21 06:32:27 -04:00
|
|
|
Post.where(topic: topic_id, post_number: post_number).pluck_first(:id)
|
2016-01-26 20:19:49 -05:00
|
|
|
end
|
|
|
|
|
2013-05-16 01:03:03 -04:00
|
|
|
protected
|
|
|
|
|
|
|
|
def refresh_notification_count
|
2019-10-23 01:09:55 -04:00
|
|
|
if user_id
|
|
|
|
User.find_by(id: user_id)&.publish_notifications_state
|
2018-05-25 20:27:54 -04:00
|
|
|
end
|
2013-05-16 01:03:03 -04:00
|
|
|
end
|
|
|
|
|
2016-12-21 23:29:34 -05:00
|
|
|
def send_email
|
2017-06-12 03:41:39 -04:00
|
|
|
NotificationEmailer.process_notification(self) if !skip_send_email
|
2016-12-21 23:29:34 -05:00
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2013-05-23 22:48:32 -04:00
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: notifications
|
|
|
|
#
|
|
|
|
# id :integer not null, primary key
|
|
|
|
# notification_type :integer not null
|
|
|
|
# user_id :integer not null
|
|
|
|
# data :string(1000) not null
|
|
|
|
# read :boolean default(FALSE), not null
|
2014-08-27 01:19:25 -04:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
2013-05-23 22:48:32 -04:00
|
|
|
# topic_id :integer
|
|
|
|
# post_number :integer
|
|
|
|
# post_action_id :integer
|
2020-03-31 19:09:20 -04:00
|
|
|
# high_priority :boolean default(FALSE), not null
|
2013-05-23 22:48:32 -04:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2018-07-16 02:18:07 -04:00
|
|
|
# idx_notifications_speedup_unread_count (user_id,notification_type) WHERE (NOT read)
|
2015-09-17 20:41:10 -04:00
|
|
|
# index_notifications_on_post_action_id (post_action_id)
|
2019-10-30 07:59:59 -04:00
|
|
|
# index_notifications_on_topic_id_and_post_number (topic_id,post_number)
|
2015-09-17 20:41:10 -04:00
|
|
|
# index_notifications_on_user_id_and_created_at (user_id,created_at)
|
|
|
|
# index_notifications_on_user_id_and_topic_id_and_post_number (user_id,topic_id,post_number)
|
2020-03-31 19:09:20 -04:00
|
|
|
# index_notifications_read_or_not_high_priority (user_id,id DESC,read,topic_id) WHERE (read OR (high_priority = false))
|
|
|
|
# index_notifications_unique_unread_high_priority (user_id,id) UNIQUE WHERE ((NOT read) AND (high_priority = true))
|
2013-05-23 22:48:32 -04:00
|
|
|
#
|