-
Notifications
You must be signed in to change notification settings - Fork 4k
/
notification.rb
182 lines (138 loc) · 7.4 KB
/
notification.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# @note When we destroy the related user, it's using dependent:
# :delete for the relationship. That means no before/after
# destroy callbacks will be called on this object.
class Notification < ApplicationRecord
belongs_to :notifiable, polymorphic: true
belongs_to :user, optional: true
belongs_to :organization, optional: true
validates :user_id, presence: true, if: proc { |notification| notification.organization_id.nil? }
validates :organization_id, presence: true, if: proc { |notification| notification.user_id.nil? }
validates :user_id, uniqueness: { scope: %i[organization_id notifiable_id notifiable_type action] }
before_create :mark_notified_at_time
scope :for_published_articles, -> { where(notifiable_type: "Article", action: "Published") }
scope :for_comments, -> { where(notifiable_type: "Comment", action: nil) } # nil action means "not a reaction"
scope :for_mentions, -> { where(notifiable_type: "Mention") }
scope :for_organization, ->(org_id) { where(organization_id: org_id, user_id: nil) }
scope :for_organization_comments, lambda { |org_id|
# nil action means "not a reaction"
where(organization_id: org_id, notifiable_type: "Comment", action: nil, user_id: nil)
}
scope :for_organization_mentions, lambda { |org_id|
where(organization_id: org_id, notifiable_type: "Mention", user_id: nil)
}
scope :unread, -> { where(read: false) }
class << self
def send_new_follower_notification(follow, is_read: false)
return unless follow && Follow.need_new_follower_notification_for?(follow.followable_type)
return if follow.followable_type == "User" && UserBlock.blocking?(follow.followable_id, follow.follower_id)
follow_data = Notifications::NewFollower::FollowData.coerce(follow).to_h
follower = User.find_by(id: follow.follower_id)
if follower.registered_at > 48.hours.ago # Delay the job 60 minutes to check for spam users if new user
Notifications::NewFollowerWorker.perform_in(1.hour, follow_data, is_read)
else
Notifications::NewFollowerWorker.perform_async(follow_data, is_read)
end
end
def send_new_follower_notification_without_delay(follow, is_read: false)
return unless follow && Follow.need_new_follower_notification_for?(follow.followable_type)
return if follow.followable_type == "User" && UserBlock.blocking?(follow.followable_id, follow.follower_id)
follow_data = Notifications::NewFollower::FollowData.coerce(follow).to_h
Notifications::NewFollowerWorker.new.perform(follow_data, is_read)
end
def send_to_mentioned_users_and_followers(notifiable, _action = nil)
return unless notifiable.is_a?(Article) && notifiable.published?
# We need to create associated mentions inline because they need to exist _before_ creating any
# other Article-related notifications. This ensures that users will not receive a second notification for the
# post being published if they have already received an initial notification about being @-mentioned in the post.
Mentions::CreateAll.call(notifiable)
# Kicks off a worker to send any notifications about the post being published, if necessary.
Notification.send_to_followers(notifiable, "Published")
end
def send_to_followers(notifiable, action = nil)
Notifications::NotifiableActionWorker.perform_async(notifiable.id, notifiable.class.name, action)
end
def send_new_comment_notifications_without_delay(comment)
return if comment.commentable_type == "PodcastEpisode"
return if UserBlock.blocking?(comment.commentable.user_id, comment.user_id)
Notifications::NewComment::Send.call(comment)
end
def send_new_badge_achievement_notification(badge_achievement)
Notifications::NewBadgeAchievementWorker.perform_async(badge_achievement.id)
end
def send_reaction_notification(reaction, receiver)
return if reaction.skip_notification_for?(receiver)
return if UserBlock.blocking?(receiver, reaction.user_id)
Notifications::NewReactionWorker.perform_async(*reaction_notification_attributes(reaction, receiver))
end
def send_reaction_notification_without_delay(reaction, receiver)
return if reaction.skip_notification_for?(receiver)
return if UserBlock.blocking?(receiver, reaction.user_id)
Notifications::NewReactionWorker.new.perform(*reaction_notification_attributes(reaction, receiver))
end
def send_mention_notification(mention)
return if MentionDecorator.new(mention).mentioned_by_blocked_user?
Notifications::MentionWorker.perform_async(mention.id)
end
def send_mention_notification_without_delay(mention)
return if MentionDecorator.new(mention).mentioned_by_blocked_user?
Notifications::NewMention::Send.call(mention) if mention
end
def send_welcome_notification(receiver_id, broadcast_id)
Notifications::WelcomeNotificationWorker.perform_async(receiver_id, broadcast_id)
end
def send_moderation_notification(notifiable)
return unless [Comment, Article].include?(notifiable.class)
if notifiable.instance_of?(Comment) && UserBlock.blocking?(notifiable.commentable.user_id, notifiable.user_id)
return
end
Notifications::CreateRoundRobinModerationNotificationsWorker.perform_async(notifiable.id, notifiable.class.to_s)
end
def send_tag_adjustment_notification(tag_adjustment)
Notifications::TagAdjustmentNotificationWorker.perform_async(tag_adjustment.id)
end
def send_milestone_notification(type:, article_id:)
Notifications::MilestoneWorker.perform_async(type, article_id)
end
def remove_all_by_action_without_delay(notifiable_ids:, notifiable_type:, action: nil)
return unless %w[Article Comment Mention].include?(notifiable_type) && notifiable_ids.present?
Notifications::RemoveAllByAction.call(Array.wrap(notifiable_ids), notifiable_type, action)
end
def remove_all(notifiable_ids:, notifiable_type:)
return unless %w[Article Comment Mention].include?(notifiable_type) && notifiable_ids.present?
Notifications::RemoveAllWorker.perform_async(notifiable_ids, notifiable_type)
end
def remove_all_without_delay(notifiable_ids:, notifiable_type:)
return unless %w[Article Comment Mention].include?(notifiable_type) && notifiable_ids.present?
Notifications::RemoveAllWorker.new.perform(notifiable_ids, notifiable_type)
end
def update_notifications(notifiable, action = nil)
Notifications::UpdateWorker.perform_async(notifiable.id, notifiable.class.name, action)
end
def fast_destroy_old_notifications(destroy_before_timestamp = 3.months.ago)
sql = <<-SQL.squish
DELETE FROM notifications
WHERE notifications.id IN (
SELECT notifications.id
FROM notifications
WHERE created_at < ?
LIMIT 50000
)
SQL
notification_sql = Notification.sanitize_sql([sql, destroy_before_timestamp])
BulkSqlDelete.delete_in_batches(notification_sql)
end
private
def reaction_notification_attributes(reaction, receiver)
reactable_data = Notifications::Reactions::ReactionData.coerce(reaction).to_h
receiver_data = { "klass" => receiver.class.name, "id" => receiver.id }
[reactable_data, receiver_data]
end
end
def aggregated?
action == "Reaction" || action == "Follow"
end
private
def mark_notified_at_time
self.notified_at = Time.current
end
end