From ff62911a89d721ebec1716990c5e407e572d8960 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 6 Mar 2020 12:23:22 +0000 Subject: [PATCH] FEATURE: New route for loading multiple user cards simultaneously (#9078) Introduces `/user-cards.json` Also allows the client-side user model to be passed an existing promise when loading, so that multiple models can share the same AJAX request --- .../javascripts/discourse/models/user.js.es6 | 6 +++- app/controllers/users_controller.rb | 24 ++++++++++++++++ config/routes.rb | 2 ++ spec/requests/users_controller_spec.rb | 28 +++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 69276768055..84c2765f6f5 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -536,8 +536,12 @@ const User = RestModel.extend({ const user = this; return PreloadStore.getAndRemove(`user_${user.get("username")}`, () => { - const useCardRoute = options && options.forCard; + if (options && options.existingRequest) { + // Existing ajax request has been passed, use it + return options.existingRequest; + } + const useCardRoute = options && options.forCard; if (options) delete options.forCard; const path = useCardRoute diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 33e7bdce6c2..5b7bbc70222 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -99,6 +99,30 @@ class UsersController < ApplicationController show(for_card: true) end + def cards + return redirect_to path('/login') if SiteSetting.hide_user_profiles_from_public && !current_user + + user_ids = params.require(:user_ids).split(",").map(&:to_i) + raise Discourse::InvalidParameters.new(:user_ids) if user_ids.length > 50 + + users = User.where(id: user_ids).includes(:user_option, + :user_stat, + :default_featured_user_badges, + :user_profile, + :card_background_upload, + :primary_group, + :primary_email + ) + + users = users.filter { |u| guardian.can_see_profile?(u) } + + preload_fields = User.whitelisted_user_custom_fields(guardian) + UserField.all.pluck(:id).map { |fid| "#{User::USER_FIELD_PREFIX}#{fid}" } + User.preload_custom_fields(users, preload_fields) + User.preload_recent_time_read(users) + + render json: users, each_serializer: UserCardSerializer + end + def badges raise Discourse::NotFound unless SiteSetting.enable_badges? show diff --git a/config/routes.rb b/config/routes.rb index 8fd2d19727d..38047b91eb7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -367,6 +367,8 @@ Discourse::Application.routes.draw do get "user_preferences" => "users#user_preferences_redirect" get ".well-known/change-password", to: redirect(relative_url_root + 'my/preferences/account', status: 302) + get "user-cards" => "users#cards", format: :json + %w{users u}.each_with_index do |root_path, index| get "#{root_path}" => "users#index", constraints: { format: 'html' } diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 13aebdfd2f4..7a15170e38f 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -3052,6 +3052,34 @@ describe UsersController do end end + describe "#cards" do + fab!(:user) { Discourse.system_user } + fab!(:user2) { Fabricate(:user) } + + it "returns success" do + get "/user-cards.json?user_ids=#{user.id},#{user2.id}" + expect(response.status).to eq(200) + parsed = JSON.parse(response.body)["users"] + + expect(parsed.map { |u| u["username"] }).to contain_exactly(user.username, user2.username) + end + + it "should redirect to login page for anonymous user when profiles are hidden" do + SiteSetting.hide_user_profiles_from_public = true + get "/user-cards.json?user_ids=#{user.id},#{user2.id}" + expect(response).to redirect_to '/login' + end + + it "does not include hidden profiles" do + user2.user_option.update(hide_profile_and_presence: true) + get "/user-cards.json?user_ids=#{user.id},#{user2.id}" + expect(response.status).to eq(200) + parsed = JSON.parse(response.body)["users"] + + expect(parsed.map { |u| u["username"] }).to contain_exactly(user.username) + end + end + describe '#badges' do it "renders fine by default" do get "/u/#{user.username}/badges"