2019-05-08 10:20:51 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
class ReviewablesController < ApplicationController
|
|
|
|
requires_login
|
|
|
|
|
|
|
|
PER_PAGE = 10
|
|
|
|
|
|
|
|
before_action :version_required, only: [:update, :perform]
|
2020-07-20 16:21:20 -04:00
|
|
|
before_action :ensure_can_see, except: [:destroy]
|
2019-01-03 12:03:01 -05:00
|
|
|
|
|
|
|
def index
|
|
|
|
offset = params[:offset].to_i
|
|
|
|
|
|
|
|
if params[:type].present?
|
2021-05-25 19:47:35 -04:00
|
|
|
raise Discourse::InvalidParameters.new(:type) unless Reviewable.valid_type?(params[:type])
|
2019-01-03 12:03:01 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
status = (params[:status] || 'pending').to_sym
|
2021-05-25 19:47:35 -04:00
|
|
|
raise Discourse::InvalidParameters.new(:status) unless allowed_statuses.include?(status)
|
2019-01-03 12:03:01 -05:00
|
|
|
|
|
|
|
topic_id = params[:topic_id] ? params[:topic_id].to_i : nil
|
|
|
|
category_id = params[:category_id] ? params[:category_id].to_i : nil
|
|
|
|
|
2019-11-22 14:33:10 -05:00
|
|
|
custom_keys = Reviewable.custom_filters.map(&:first)
|
|
|
|
additional_filters = JSON.parse(params.fetch(:additional_filters, {}), symbolize_names: true).slice(*custom_keys)
|
2019-01-03 12:03:01 -05:00
|
|
|
filters = {
|
2021-02-26 05:56:14 -05:00
|
|
|
ids: params[:ids],
|
2019-01-03 12:03:01 -05:00
|
|
|
status: status,
|
|
|
|
category_id: category_id,
|
|
|
|
topic_id: topic_id,
|
2019-11-22 14:33:10 -05:00
|
|
|
additional_filters: additional_filters.reject { |_, v| v.blank? }
|
2019-01-03 12:03:01 -05:00
|
|
|
}
|
|
|
|
|
2020-12-04 12:09:05 -05:00
|
|
|
%i[priority username reviewed_by from_date to_date type sort_order].each do |filter_key|
|
2019-11-22 14:33:10 -05:00
|
|
|
filters[filter_key] = params[filter_key]
|
|
|
|
end
|
|
|
|
|
2019-11-27 21:13:13 -05:00
|
|
|
total_rows = Reviewable.list_for(current_user, **filters).count
|
|
|
|
reviewables = Reviewable.list_for(current_user, **filters.merge(limit: PER_PAGE, offset: offset)).to_a
|
2019-01-03 12:03:01 -05:00
|
|
|
|
2019-05-08 10:20:51 -04:00
|
|
|
claimed_topics = ReviewableClaimedTopic.claimed_hash(reviewables.map { |r| r.topic_id }.uniq)
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
# This is a bit awkward, but ActiveModel serializers doesn't seem to serialize STI. Note `hash`
|
|
|
|
# is mutated by the serializer and contains the side loaded records which must be merged in the end.
|
|
|
|
hash = {}
|
|
|
|
json = {
|
|
|
|
reviewables: reviewables.map! do |r|
|
2019-05-08 10:20:51 -04:00
|
|
|
result = r.serializer.new(
|
|
|
|
r,
|
|
|
|
root: nil,
|
|
|
|
hash: hash,
|
|
|
|
scope: guardian,
|
|
|
|
claimed_topics: claimed_topics
|
|
|
|
).as_json
|
2019-01-03 12:03:01 -05:00
|
|
|
hash[:bundled_actions].uniq!
|
|
|
|
(hash['actions'] || []).uniq!
|
|
|
|
result
|
|
|
|
end,
|
|
|
|
meta: filters.merge(
|
2019-04-05 10:34:02 -04:00
|
|
|
total_rows_reviewables: total_rows, types: meta_types, reviewable_types: Reviewable.types,
|
2022-07-28 04:16:33 -04:00
|
|
|
reviewable_count: current_user.reviewable_count
|
2019-01-03 12:03:01 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
if (offset + PER_PAGE) < total_rows
|
|
|
|
json[:meta][:load_more_reviewables] = review_path(filters.merge(offset: offset + PER_PAGE))
|
|
|
|
end
|
|
|
|
json.merge!(hash)
|
|
|
|
|
|
|
|
render_json_dump(json, rest_serializer: true)
|
|
|
|
end
|
|
|
|
|
2022-07-28 04:16:33 -04:00
|
|
|
def user_menu_list
|
|
|
|
reviewables = Reviewable.recent_list_with_pending_first(current_user).to_a
|
|
|
|
json = {
|
|
|
|
reviewables: reviewables.map! { |r| r.basic_serializer.new(r, scope: guardian, root: nil).as_json }
|
|
|
|
}
|
|
|
|
render_json_dump(json, rest_serializer: true)
|
|
|
|
end
|
|
|
|
|
2020-08-07 12:13:02 -04:00
|
|
|
def count
|
|
|
|
render_json_dump(count: Reviewable.pending_count(current_user))
|
|
|
|
end
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
def topics
|
|
|
|
topic_ids = Set.new
|
|
|
|
|
|
|
|
stats = {}
|
|
|
|
unique_users = {}
|
|
|
|
|
|
|
|
# topics isn't indexed on `reviewable_score` and doesn't know what the current user can see,
|
|
|
|
# so let's query from the inside out.
|
2019-03-29 15:34:43 -04:00
|
|
|
pending = Reviewable.viewable_by(current_user).pending
|
2019-05-07 13:25:11 -04:00
|
|
|
pending = pending.where("score >= ?", Reviewable.min_score_for_priority)
|
2019-03-29 15:34:43 -04:00
|
|
|
|
|
|
|
pending.each do |r|
|
2019-01-03 12:03:01 -05:00
|
|
|
topic_ids << r.topic_id
|
|
|
|
|
|
|
|
meta = stats[r.topic_id] ||= { count: 0, unique_users: 0 }
|
|
|
|
users = unique_users[r.topic_id] ||= Set.new
|
|
|
|
|
|
|
|
r.reviewable_scores.each do |rs|
|
|
|
|
users << rs.user_id
|
|
|
|
meta[:count] += 1
|
|
|
|
end
|
|
|
|
meta[:unique_users] = users.size
|
|
|
|
end
|
|
|
|
|
|
|
|
topics = Topic.where(id: topic_ids).order('reviewable_score DESC')
|
2019-05-08 10:20:51 -04:00
|
|
|
render_serialized(
|
|
|
|
topics,
|
|
|
|
ReviewableTopicSerializer,
|
|
|
|
root: 'reviewable_topics',
|
|
|
|
stats: stats,
|
|
|
|
claimed_topics: ReviewableClaimedTopic.claimed_hash(topic_ids),
|
|
|
|
rest_serializer: true,
|
|
|
|
meta: {
|
|
|
|
types: meta_types
|
|
|
|
}
|
|
|
|
)
|
2019-01-03 12:03:01 -05:00
|
|
|
end
|
|
|
|
|
2019-09-04 11:56:25 -04:00
|
|
|
def explain
|
|
|
|
reviewable = find_reviewable
|
|
|
|
|
|
|
|
render_serialized(
|
|
|
|
{ reviewable: reviewable, scores: reviewable.explain_score },
|
|
|
|
ReviewableExplanationSerializer,
|
|
|
|
rest_serializer: true,
|
|
|
|
root: 'reviewable_explanation'
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
def show
|
|
|
|
reviewable = find_reviewable
|
|
|
|
|
|
|
|
render_serialized(
|
|
|
|
reviewable,
|
|
|
|
reviewable.serializer,
|
|
|
|
rest_serializer: true,
|
2019-05-08 10:20:51 -04:00
|
|
|
claimed_topics: ReviewableClaimedTopic.claimed_hash([reviewable.topic_id]),
|
2019-01-03 12:03:01 -05:00
|
|
|
root: 'reviewable',
|
|
|
|
meta: {
|
|
|
|
types: meta_types
|
|
|
|
}
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2019-04-12 09:55:27 -04:00
|
|
|
def destroy
|
|
|
|
reviewable = Reviewable.find_by(id: params[:reviewable_id], created_by: current_user)
|
|
|
|
raise Discourse::NotFound.new if reviewable.blank?
|
|
|
|
|
|
|
|
reviewable.perform(current_user, :delete)
|
|
|
|
|
|
|
|
render json: success_json
|
|
|
|
end
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
def update
|
|
|
|
reviewable = find_reviewable
|
2019-05-08 10:20:51 -04:00
|
|
|
if error = claim_error?(reviewable)
|
|
|
|
return render_json_error(error)
|
|
|
|
end
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
editable = reviewable.editable_for(guardian)
|
|
|
|
raise Discourse::InvalidAccess.new unless editable.present?
|
|
|
|
|
|
|
|
# Validate parameters are all editable
|
|
|
|
edit_params = params[:reviewable] || {}
|
|
|
|
edit_params.each do |name, value|
|
|
|
|
if value.is_a?(ActionController::Parameters)
|
|
|
|
value.each do |pay_name, pay_value|
|
|
|
|
raise Discourse::InvalidAccess.new unless editable.has?("#{name}.#{pay_name}")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
raise Discourse::InvalidAccess.new unless editable.has?(name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
if reviewable.update_fields(edit_params, current_user, version: params[:version].to_i)
|
|
|
|
result = edit_params.merge(version: reviewable.version)
|
|
|
|
render json: result
|
|
|
|
else
|
|
|
|
render_json_error(reviewable.errors)
|
|
|
|
end
|
|
|
|
rescue Reviewable::UpdateConflict
|
2019-12-09 19:48:27 -05:00
|
|
|
render_json_error(I18n.t('reviewables.conflict'), status: 409)
|
2019-01-03 12:03:01 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform
|
|
|
|
args = { version: params[:version].to_i }
|
|
|
|
|
2019-05-08 10:20:51 -04:00
|
|
|
result = nil
|
2019-01-03 12:03:01 -05:00
|
|
|
begin
|
2019-05-08 10:20:51 -04:00
|
|
|
reviewable = find_reviewable
|
|
|
|
|
|
|
|
if error = claim_error?(reviewable)
|
|
|
|
return render_json_error(error)
|
|
|
|
end
|
|
|
|
|
2021-01-28 12:41:30 -05:00
|
|
|
args.merge!(reject_reason: params[:reject_reason], send_email: params[:send_email] != "false") if reviewable.type == 'ReviewableUser'
|
2021-01-14 17:43:26 -05:00
|
|
|
|
2021-03-02 11:28:27 -05:00
|
|
|
plugin_params = DiscoursePluginRegistry.reviewable_params.select do |reviewable_param|
|
|
|
|
reviewable.type == reviewable_param[:type].to_s.classify
|
|
|
|
end
|
|
|
|
args.merge!(params.slice(*plugin_params.map { |pp| pp[:param] }).permit!)
|
|
|
|
|
2019-05-08 10:20:51 -04:00
|
|
|
result = reviewable.perform(current_user, params[:action_id].to_sym, args)
|
2019-01-03 12:03:01 -05:00
|
|
|
rescue Reviewable::InvalidAction => e
|
2021-05-07 12:30:04 -04:00
|
|
|
if reviewable.type == 'ReviewableUser' && !reviewable.pending? && reviewable.target.blank?
|
|
|
|
raise Discourse::NotFound.new(e.message, custom_message: "reviewables.already_handled_and_user_not_exist")
|
|
|
|
else
|
|
|
|
# Consider InvalidAction an InvalidAccess
|
|
|
|
raise Discourse::InvalidAccess.new(e.message)
|
|
|
|
end
|
2019-01-03 12:03:01 -05:00
|
|
|
rescue Reviewable::UpdateConflict
|
|
|
|
return render_json_error(I18n.t('reviewables.conflict'), status: 409)
|
|
|
|
end
|
|
|
|
|
|
|
|
if result.success?
|
|
|
|
render_serialized(result, ReviewablePerformResultSerializer)
|
|
|
|
else
|
|
|
|
render_json_error(result)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-02 17:00:15 -04:00
|
|
|
def settings
|
|
|
|
raise Discourse::InvalidAccess.new unless current_user.admin?
|
|
|
|
|
|
|
|
post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order('id')
|
|
|
|
|
|
|
|
if request.put?
|
2019-05-22 17:23:45 -04:00
|
|
|
params[:reviewable_priorities].each do |id, priority|
|
|
|
|
if !priority.nil? && Reviewable.priorities.has_value?(priority.to_i)
|
|
|
|
# For now, the score bonus is equal to the priority. In the future we might want
|
|
|
|
# to calculate it a different way.
|
|
|
|
PostActionType.where(id: id).update_all(
|
|
|
|
reviewable_priority: priority.to_i,
|
|
|
|
score_bonus: priority.to_f
|
|
|
|
)
|
|
|
|
end
|
2019-04-02 17:00:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-05-22 17:23:45 -04:00
|
|
|
data = { reviewable_score_types: post_action_types }
|
2019-04-02 17:00:15 -04:00
|
|
|
render_serialized(data, ReviewableSettingsSerializer, rest_serializer: true)
|
|
|
|
end
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
protected
|
|
|
|
|
2019-05-08 10:20:51 -04:00
|
|
|
def claim_error?(reviewable)
|
|
|
|
return if SiteSetting.reviewable_claiming == "disabled" || reviewable.topic_id.blank?
|
|
|
|
|
|
|
|
claimed_by_id = ReviewableClaimedTopic.where(topic_id: reviewable.topic_id).pluck(:user_id)[0]
|
2020-04-20 21:50:20 -04:00
|
|
|
|
2019-05-08 10:20:51 -04:00
|
|
|
if SiteSetting.reviewable_claiming == "required" && claimed_by_id.blank?
|
2020-04-20 21:50:20 -04:00
|
|
|
I18n.t('reviewables.must_claim')
|
|
|
|
elsif claimed_by_id.present? && claimed_by_id != current_user.id
|
|
|
|
I18n.t('reviewables.user_claimed')
|
2019-05-08 10:20:51 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-03 12:03:01 -05:00
|
|
|
def find_reviewable
|
|
|
|
reviewable = Reviewable.viewable_by(current_user).where(id: params[:reviewable_id]).first
|
|
|
|
raise Discourse::NotFound.new if reviewable.blank?
|
|
|
|
reviewable
|
|
|
|
end
|
|
|
|
|
|
|
|
def allowed_statuses
|
|
|
|
@allowed_statuses ||= (%i[reviewed all] + Reviewable.statuses.keys)
|
|
|
|
end
|
|
|
|
|
|
|
|
def version_required
|
|
|
|
if params[:version].blank?
|
|
|
|
render_json_error(I18n.t('reviewables.missing_version'), status: 422)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def meta_types
|
|
|
|
{
|
|
|
|
created_by: 'user',
|
2019-04-09 16:43:08 -04:00
|
|
|
target_created_by: 'user',
|
2019-05-08 10:20:51 -04:00
|
|
|
reviewed_by: 'user',
|
|
|
|
claimed_by: 'user'
|
2019-01-03 12:03:01 -05:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-04-27 13:51:25 -04:00
|
|
|
def ensure_can_see
|
|
|
|
Guardian.new(current_user).ensure_can_see_review_queue!
|
|
|
|
end
|
2019-01-03 12:03:01 -05:00
|
|
|
end
|