* Add "Welcome" type to Broadcasts, allow dynamic "authoring" of Broadcasts This adds a new `type_of` to the Broadcast model: "Welcome". As we begin to create a new "welcome notification" workflow, we'll categorize them by making them all of the same type. This also adds a concept of a "welcoming user", which can be set via an ENV var. The WELCOMING_USER_ID will allow us to explicitly set (and change) which user in the database is the one that "sends" welcoming notification. In production, we plan for this to be dev.to/sloan. * Allow WelcomeNotificationWorker to accept + send any kind of broadcast This abstracts out the logic of deciding which broadcast to send from the woker into the calling method. This will help us send many different kinds of welcome notifications using one, resuable worker class. * Raise if a Notification can't be created * Add STAFF_USER_ID as an ENV var This is in preparation for moving away from SiteConfig.staff_user_id, and replacing it with an ENV var. We will need to set this in production first, and then make a separate PR to replace all instances of staff_user_id with the newly-set ENV var.
156 lines
6.2 KiB
Ruby
156 lines
6.2 KiB
Ruby
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)
|
|
}
|
|
|
|
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 = follow.attributes.slice("follower_id", "followable_id", "followable_type").symbolize_keys
|
|
Notifications::NewFollowerWorker.perform_async(follow_data, is_read)
|
|
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 = follow.attributes.slice("follower_id", "followable_id", "followable_type").symbolize_keys
|
|
Notifications::NewFollowerWorker.new.perform(follow_data, is_read)
|
|
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 mention.mentionable_type == "User" && UserBlock.blocking?(mention.mentionable_id, mention.user_id)
|
|
|
|
Notifications::MentionWorker.perform_async(mention.id)
|
|
end
|
|
|
|
def send_welcome_notification(receiver_id, broadcast_id)
|
|
Notifications::WelcomeNotificationWorker.perform_async(receiver_id, broadcast_id)
|
|
end
|
|
|
|
def send_moderation_notification(notifiable)
|
|
# TODO: make this work for articles in the future. only works for comments right now
|
|
return if UserBlock.blocking?(notifiable.commentable.user_id, notifiable.user_id)
|
|
|
|
Notifications::ModerationNotificationWorker.perform_async(notifiable.id)
|
|
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 = 4.months.ago)
|
|
sql = <<-SQL
|
|
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 = {
|
|
reactable_id: reaction.reactable_id,
|
|
reactable_type: reaction.reactable_type,
|
|
reactable_user_id: reaction.reactable.user_id
|
|
}
|
|
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
|