mirror of
https://github.com/discourse/discourse.git
synced 2025-02-23 21:04:59 +08:00

This commit updates the `Jobs::BadgeGrant` scheduled job to enqueue on `Job::BackfillBadge` regular job for each enabled badge on the site. The rationale for this change is that we started seeing the `Jobs::BadgeGrant` job taking hours on sites with lots of enabled badges as well as users because the job was backfilling all enabled badges serially within the job. This is bad as it means that a `mini_scheduler` thread is tied up by this job thus reducing the overall capacity of `mini_scheduler` for hours.
186 lines
5.4 KiB
Ruby
186 lines
5.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class UserBadgesController < ApplicationController
|
|
MAX_BADGES = 96 # This was limited in PR#2360 to make it divisible by 8
|
|
|
|
before_action :ensure_badges_enabled
|
|
|
|
def index
|
|
params.permit %i[granted_before offset username]
|
|
|
|
badge = fetch_badge_from_params
|
|
user_badges = badge.user_badges.order("granted_at DESC, id DESC").limit(MAX_BADGES)
|
|
user_badges =
|
|
user_badges.includes(
|
|
:user,
|
|
:granted_by,
|
|
badge: :badge_type,
|
|
post: :topic,
|
|
user: %i[primary_group flair_group],
|
|
)
|
|
|
|
grant_count = nil
|
|
|
|
if params[:username]
|
|
user_id = User.where(username_lower: params[:username].downcase).pick(:id)
|
|
user_badges = user_badges.where(user_id: user_id) if user_id
|
|
grant_count = badge.user_badges.where(user_id: user_id).count
|
|
end
|
|
|
|
offset = fetch_int_from_params(:offset, default: 0)
|
|
user_badges = user_badges.offset(offset) if offset > 0
|
|
|
|
user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact
|
|
|
|
user_badges =
|
|
UserBadges.new(
|
|
user_badges: user_badges,
|
|
username: params[:username],
|
|
grant_count: grant_count,
|
|
)
|
|
|
|
render_serialized(
|
|
user_badges,
|
|
UserBadgesSerializer,
|
|
root: :user_badge_info,
|
|
include_long_description: true,
|
|
allowed_user_badge_topic_ids: guardian.can_see_topic_ids(topic_ids: user_badges_topic_ids),
|
|
)
|
|
end
|
|
|
|
def username
|
|
params.permit [:grouped]
|
|
|
|
user =
|
|
fetch_user_from_params(
|
|
include_inactive:
|
|
current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts),
|
|
)
|
|
raise Discourse::NotFound unless guardian.can_see_profile?(user)
|
|
user_badges = user.user_badges
|
|
|
|
user_badges = user_badges.group(:badge_id).select_for_grouping if params[:grouped]
|
|
|
|
user_badges =
|
|
user_badges
|
|
.includes(badge: %i[badge_grouping badge_type image_upload])
|
|
.includes(post: :topic)
|
|
.includes(:granted_by)
|
|
|
|
user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact
|
|
|
|
render_serialized(
|
|
user_badges,
|
|
DetailedUserBadgeSerializer,
|
|
allowed_user_badge_topic_ids: guardian.can_see_topic_ids(topic_ids: user_badges_topic_ids),
|
|
root: :user_badges,
|
|
)
|
|
end
|
|
|
|
def create
|
|
params.require(:username)
|
|
user = fetch_user_from_params
|
|
|
|
return render json: failed_json, status: 403 unless can_assign_badge_to_user?(user)
|
|
|
|
badge = fetch_badge_from_params
|
|
post_id = nil
|
|
|
|
if params[:reason].present?
|
|
unless is_badge_reason_valid? params[:reason]
|
|
return(
|
|
render json: failed_json.merge(message: I18n.t("invalid_grant_badge_reason_link")),
|
|
status: 400
|
|
)
|
|
end
|
|
|
|
if route = Discourse.route_for(params[:reason])
|
|
if route[:controller] == "topics" && route[:action] == "show"
|
|
topic_id = (route[:id] || route[:topic_id]).to_i
|
|
post_number = route[:post_number] || 1
|
|
post_id = Post.find_by(topic_id: topic_id, post_number: post_number)&.id if topic_id > 0
|
|
end
|
|
end
|
|
end
|
|
|
|
grant_opts_from_params =
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:user_badges_badge_grant_opts,
|
|
{ granted_by: current_user, post_id: post_id },
|
|
{ param: params },
|
|
)
|
|
|
|
user_badge = BadgeGranter.grant(badge, user, grant_opts_from_params)
|
|
|
|
render_serialized(user_badge, DetailedUserBadgeSerializer, root: "user_badge")
|
|
end
|
|
|
|
def destroy
|
|
params.require(:id)
|
|
user_badge = UserBadge.find(params[:id])
|
|
|
|
unless can_assign_badge_to_user?(user_badge.user)
|
|
render json: failed_json, status: 403
|
|
return
|
|
end
|
|
|
|
BadgeGranter.revoke(user_badge, revoked_by: current_user)
|
|
render json: success_json
|
|
end
|
|
|
|
def toggle_favorite
|
|
params.require(:user_badge_id)
|
|
user_badge = UserBadge.find(params[:user_badge_id])
|
|
user_badges = user_badge.user.user_badges
|
|
|
|
return render json: failed_json, status: 403 unless can_favorite_badge?(user_badge)
|
|
|
|
if !user_badge.is_favorite &&
|
|
user_badges.select(:badge_id).distinct.where(is_favorite: true).count >=
|
|
SiteSetting.max_favorite_badges
|
|
return render json: failed_json, status: 400
|
|
end
|
|
|
|
UserBadge.where(user_id: user_badge.user_id, badge_id: user_badge.badge_id).update_all(
|
|
is_favorite: !user_badge.is_favorite,
|
|
)
|
|
UserBadge.update_featured_ranks!([user_badge.user_id])
|
|
end
|
|
|
|
private
|
|
|
|
# Get the badge from either the badge name or id specified in the params.
|
|
def fetch_badge_from_params
|
|
badge = nil
|
|
|
|
params.permit(:badge_name)
|
|
if params[:badge_name].nil?
|
|
params.require(:badge_id)
|
|
badge = Badge.find_by(id: params[:badge_id], enabled: true)
|
|
else
|
|
badge = Badge.find_by(name: params[:badge_name], enabled: true)
|
|
end
|
|
raise Discourse::NotFound if badge.blank?
|
|
|
|
badge
|
|
end
|
|
|
|
def can_assign_badge_to_user?(user)
|
|
master_api_call = current_user.nil? && is_api?
|
|
master_api_call || guardian.can_grant_badges?(user)
|
|
end
|
|
|
|
def can_favorite_badge?(user_badge)
|
|
current_user == user_badge.user && !(1..4).include?(user_badge.badge_id)
|
|
end
|
|
|
|
def ensure_badges_enabled
|
|
raise Discourse::NotFound unless SiteSetting.enable_badges?
|
|
end
|
|
|
|
def is_badge_reason_valid?(reason)
|
|
route = Discourse.route_for(reason)
|
|
route && (route[:controller] == "posts" || route[:controller] == "topics")
|
|
end
|
|
end
|