2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-03-05 20:52:20 +08:00
|
|
|
class UserBadge < ActiveRecord::Base
|
2024-08-27 11:46:48 +08:00
|
|
|
self.ignored_columns = [
|
2024-10-15 16:58:57 +08:00
|
|
|
:old_notification_id, # TODO: Remove once 20240829140226_drop_old_notification_id_columns has been promoted to pre-deploy
|
2024-08-27 11:46:48 +08:00
|
|
|
]
|
|
|
|
|
2014-03-05 20:52:20 +08:00
|
|
|
belongs_to :badge
|
|
|
|
belongs_to :user
|
|
|
|
belongs_to :granted_by, class_name: "User"
|
2014-06-17 13:59:28 +08:00
|
|
|
belongs_to :notification, dependent: :destroy
|
2014-06-28 03:08:03 +08:00
|
|
|
belongs_to :post
|
2014-03-05 20:52:20 +08:00
|
|
|
|
2021-06-09 02:37:42 +08:00
|
|
|
BOOLEAN_ATTRIBUTES = %w[is_favorite]
|
|
|
|
|
2020-01-14 22:26:49 +08:00
|
|
|
scope :grouped_with_count,
|
2023-11-29 13:38:07 +08:00
|
|
|
-> do
|
2020-01-14 22:26:49 +08:00
|
|
|
group(:badge_id, :user_id)
|
2021-06-01 16:33:40 +08:00
|
|
|
.select_for_grouping
|
2020-01-14 22:26:49 +08:00
|
|
|
.order("MAX(featured_rank) ASC")
|
|
|
|
.includes(:user, :granted_by, { badge: :badge_type }, post: :topic)
|
2023-11-29 13:38:07 +08:00
|
|
|
end
|
2020-01-14 22:26:49 +08:00
|
|
|
|
2021-06-01 16:33:40 +08:00
|
|
|
scope :select_for_grouping,
|
2023-11-29 13:38:07 +08:00
|
|
|
-> do
|
2021-06-01 16:33:40 +08:00
|
|
|
select(
|
|
|
|
UserBadge.attribute_names.map do |name|
|
2021-06-09 02:37:42 +08:00
|
|
|
operation = BOOLEAN_ATTRIBUTES.include?(name) ? "BOOL_OR" : "MAX"
|
|
|
|
"#{operation}(user_badges.#{name}) AS #{name}"
|
2021-06-01 16:33:40 +08:00
|
|
|
end,
|
|
|
|
'COUNT(*) AS "count"',
|
|
|
|
)
|
2023-11-29 13:38:07 +08:00
|
|
|
end
|
2021-06-01 16:33:40 +08:00
|
|
|
|
2020-01-14 22:26:49 +08:00
|
|
|
scope :for_enabled_badges,
|
|
|
|
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
|
|
|
|
|
2024-12-03 10:43:27 +08:00
|
|
|
scope :by_post_and_user,
|
|
|
|
->(posts) do
|
|
|
|
posts.reduce(UserBadge.none) do |scope, post|
|
|
|
|
scope.or(UserBadge.where(user_id: post.user_id, post_id: post.id))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
scope :for_post_header_badges,
|
|
|
|
->(posts) do
|
|
|
|
by_post_and_user(posts).where(
|
|
|
|
"user_badges.badge_id IN (SELECT id FROM badges WHERE show_posts AND enabled AND listable AND show_in_post_header)",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2017-08-31 12:06:56 +08:00
|
|
|
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
|
|
|
|
|
2014-03-05 20:52:20 +08:00
|
|
|
validates :user_id, presence: true
|
|
|
|
validates :granted_at, presence: true
|
|
|
|
validates :granted_by, presence: true
|
2014-06-17 23:11:52 +08:00
|
|
|
|
|
|
|
after_create do
|
|
|
|
Badge.increment_counter "grant_count", self.badge_id
|
2019-12-30 19:19:59 +08:00
|
|
|
UserStat.update_distinct_badge_count self.user_id
|
2020-01-14 22:26:49 +08:00
|
|
|
UserBadge.update_featured_ranks! self.user_id
|
2022-07-22 09:06:02 +08:00
|
|
|
self.trigger_user_badge_granted_event
|
2014-06-17 23:11:52 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
after_destroy do
|
|
|
|
Badge.decrement_counter "grant_count", self.badge_id
|
2019-12-30 19:19:59 +08:00
|
|
|
UserStat.update_distinct_badge_count self.user_id
|
2020-01-14 22:26:49 +08:00
|
|
|
UserBadge.update_featured_ranks! self.user_id
|
2023-04-25 04:36:40 +08:00
|
|
|
|
2015-02-26 08:50:58 +08:00
|
|
|
DiscourseEvent.trigger(:user_badge_removed, self.badge_id, self.user_id)
|
2023-04-25 04:36:40 +08:00
|
|
|
DiscourseEvent.trigger(:user_badge_revoked, user_badge: self)
|
2014-06-17 23:11:52 +08:00
|
|
|
end
|
2017-08-31 12:06:56 +08:00
|
|
|
|
2020-01-14 22:26:49 +08:00
|
|
|
def self.ensure_consistency!
|
|
|
|
self.update_featured_ranks!
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.update_featured_ranks!(user_id = nil)
|
|
|
|
query = <<~SQL
|
|
|
|
WITH featured_tl_badge AS -- Find the best trust level badge for each user
|
|
|
|
(
|
|
|
|
SELECT user_id, max(badge_id) as badge_id
|
|
|
|
FROM user_badges
|
|
|
|
WHERE badge_id IN (1,2,3,4)
|
|
|
|
#{"AND user_id = #{user_id.to_i}" if user_id}
|
|
|
|
GROUP BY user_id
|
|
|
|
),
|
|
|
|
ranks AS ( -- Take all user badges, group by user_id and badge_id, and calculate a rank for each one
|
|
|
|
SELECT
|
|
|
|
user_badges.user_id,
|
|
|
|
user_badges.badge_id,
|
|
|
|
RANK() OVER (
|
|
|
|
PARTITION BY user_badges.user_id -- Do a separate rank for each user
|
|
|
|
ORDER BY BOOL_OR(badges.enabled) DESC, -- Disabled badges last
|
|
|
|
MAX(featured_tl_badge.user_id) NULLS LAST, -- Best tl badge first
|
2021-06-01 16:33:40 +08:00
|
|
|
BOOL_OR(user_badges.is_favorite) DESC NULLS LAST, -- Favorite badges next
|
2020-01-14 22:26:49 +08:00
|
|
|
CASE WHEN user_badges.badge_id IN (1,2,3,4) THEN 1 ELSE 0 END ASC, -- Non-featured tl badges last
|
|
|
|
MAX(badges.badge_type_id) ASC,
|
|
|
|
MAX(badges.grant_count) ASC,
|
|
|
|
user_badges.badge_id DESC
|
|
|
|
) rank_number
|
|
|
|
FROM user_badges
|
|
|
|
INNER JOIN badges ON badges.id = user_badges.badge_id
|
|
|
|
LEFT JOIN featured_tl_badge ON featured_tl_badge.user_id = user_badges.user_id AND featured_tl_badge.badge_id = user_badges.badge_id
|
|
|
|
#{"WHERE user_badges.user_id = #{user_id.to_i}" if user_id}
|
|
|
|
GROUP BY user_badges.user_id, user_badges.badge_id
|
|
|
|
)
|
|
|
|
-- Now use that data to update the featured_rank column
|
|
|
|
UPDATE user_badges SET featured_rank = rank_number
|
|
|
|
FROM ranks WHERE ranks.badge_id = user_badges.badge_id AND ranks.user_id = user_badges.user_id AND featured_rank IS DISTINCT FROM rank_number
|
|
|
|
SQL
|
|
|
|
|
|
|
|
DB.exec query
|
|
|
|
end
|
|
|
|
|
2022-07-22 09:06:02 +08:00
|
|
|
def self.trigger_user_badge_granted_event(badge_id, user_id)
|
|
|
|
DiscourseEvent.trigger(:user_badge_granted, badge_id, user_id)
|
|
|
|
end
|
|
|
|
|
2017-08-31 12:06:56 +08:00
|
|
|
private
|
|
|
|
|
2022-07-22 09:06:02 +08:00
|
|
|
def trigger_user_badge_granted_event
|
|
|
|
self.class.trigger_user_badge_granted_event(self.badge_id, self.user_id)
|
|
|
|
end
|
|
|
|
|
2017-08-31 12:06:56 +08:00
|
|
|
def single_grant_badge?
|
2019-04-23 18:25:21 +08:00
|
|
|
self.badge ? self.badge.single_grant? : true
|
2017-08-31 12:06:56 +08:00
|
|
|
end
|
2014-03-05 20:52:20 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: user_badges
|
|
|
|
#
|
2014-06-17 13:59:28 +08:00
|
|
|
# id :integer not null, primary key
|
|
|
|
# badge_id :integer not null
|
|
|
|
# user_id :integer not null
|
|
|
|
# granted_at :datetime not null
|
|
|
|
# granted_by_id :integer not null
|
|
|
|
# post_id :integer
|
2014-08-11 07:21:06 +08:00
|
|
|
# seq :integer default(0), not null
|
2020-01-14 22:26:49 +08:00
|
|
|
# featured_rank :integer
|
2020-04-28 18:29:39 +08:00
|
|
|
# created_at :datetime not null
|
2021-06-01 16:33:40 +08:00
|
|
|
# is_favorite :boolean
|
2024-08-27 11:46:48 +08:00
|
|
|
# notification_id :bigint
|
2014-03-05 20:52:20 +08:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2014-07-15 09:29:44 +08:00
|
|
|
# index_user_badges_on_badge_id_and_user_id (badge_id,user_id)
|
2018-07-16 14:18:07 +08:00
|
|
|
# index_user_badges_on_badge_id_and_user_id_and_post_id (badge_id,user_id,post_id) UNIQUE WHERE (post_id IS NOT NULL)
|
|
|
|
# index_user_badges_on_badge_id_and_user_id_and_seq (badge_id,user_id,seq) UNIQUE WHERE (post_id IS NULL)
|
2019-01-12 03:29:56 +08:00
|
|
|
# index_user_badges_on_user_id (user_id)
|
2014-03-05 20:52:20 +08:00
|
|
|
#
|