Only allow endorsement for categories that the user has access to (#27)

Only allow endorsement for categories that the user has access to (#27)

diff --git a/app/controllers/category_experts_controller.rb b/app/controllers/category_experts_controller.rb
index 6de3102..dd76a12 100644
--- a/app/controllers/category_experts_controller.rb
+++ b/app/controllers/category_experts_controller.rb
@@ -7,11 +7,9 @@ class CategoryExpertsController < ApplicationController
     :retroactive_approval?,
   ]
   before_action :ensure_needs_approval_enabled, only: [:approve_post, :unapprove_post]
+  before_action :authenticate_and_find_user, only: [:endorse, :endorsable_categories]
 
   def endorse
-    raise Discourse::NotFound unless current_user
-    user = fetch_user_from_params
-
     category_ids = params[:categoryIds]&.reject(&:blank?)
 
     raise Discourse::InvalidParameters if category_ids.blank?
@@ -20,14 +18,40 @@ class CategoryExpertsController < ApplicationController
     categories.each do |category|
       raise Discourse::InvalidParameters unless category.accepting_category_expert_endorsements?
 
-      CategoryExpertEndorsement.find_or_create_by(user: current_user, endorsed_user: user, category: category)
+      CategoryExpertEndorsement.find_or_create_by(user: current_user, endorsed_user: @user, category: category)
     end
 
     render json: {
-      category_expert_endorsements: current_user.given_category_expert_endorsements_for(user)
+      category_expert_endorsements: current_user.given_category_expert_endorsements_for(@user)
     }.to_json
   end
 
+  def endorsable_categories
+    endorsee_guardian = Guardian.new(@user)
+
+    categories = Category.where(<<~SQL)
+      id IN (
+        SELECT cf.category_id
+        FROM category_custom_fields cf, category_custom_fields ocf
+        WHERE cf.name = '#{CategoryExperts::CATEGORY_EXPERT_GROUP_IDS}' AND
+              cf.value <> '' AND
+              ocf.name = '#{CategoryExperts::CATEGORY_ACCEPTING_ENDORSEMENTS}' AND
+              ocf.value <> ''
+      )
+    SQL
+
+    categories = categories.select do |category|
+      guardian.can_see_category?(category) && endorsee_guardian.can_see_category?(category)
+    end
+
+    render json: ActiveModel::ArraySerializer.new(
+      categories,
+      each_serializer: BasicCategorySerializer,
+      scope: guardian,
+      root: :categories,
+    ).as_json
+  end
+
   def approve_post
     post_handler = CategoryExperts::PostHandler.new(post: @post)
     group_name = post_handler.mark_post_as_approved
@@ -85,4 +109,10 @@ class CategoryExpertsController < ApplicationController
 
     raise Discourse::NotFound unless @post
   end
+
+  def authenticate_and_find_user
+    raise Discourse::NotFound unless current_user
+
+    @user = fetch_user_from_params
+  end
 end
diff --git a/assets/javascripts/discourse/components/endorsement-button.js b/assets/javascripts/discourse/components/endorsement-button.js
index 6b90475..8f28947 100644
--- a/assets/javascripts/discourse/components/endorsement-button.js
+++ b/assets/javascripts/discourse/components/endorsement-button.js
@@ -57,7 +57,6 @@ export default Component.extend({
 
     showModal("endorse-user", {
       model: {
-        categories: this.categoriesAllowingEndorsements,
         user: this.user,
         endorsements: this.endorsements,
         location: this.location,
diff --git a/assets/javascripts/discourse/components/endorsement-checkboxes.js b/assets/javascripts/discourse/components/endorsement-checkboxes.js
index 3fdfa4e..3235e40 100644
--- a/assets/javascripts/discourse/components/endorsement-checkboxes.js
+++ b/assets/javascripts/discourse/components/endorsement-checkboxes.js
@@ -13,25 +13,39 @@ export default Component.extend({
   selectedCategoryIds: null,
   startingCategoryIds: null,
   showingSuccess: false,
+  loading: true,
 
   didInsertElement() {
     this._super(...arguments);
+
     if (!this.endorsements) {
       this.set("endorsements", []);
     }
-
     this.set(
       "startingCategoryIds",
       this.endorsements.map((e) => e.category_id)
     );
-    this.set("selectedCategoryIds", [...this.startingCategoryIds]);
-    this.endorsements.forEach((endorsement) => {
-      const checkbox = this.element.querySelector(
-        `#category-endorsement-${endorsement.category_id}`
-      );
-      checkbox.checked = true;
-      checkbox.disabled = true;
-    });
+
+    ajax(`/category-experts/endorsable-categories/${this.user.username}.json`)
+      .then((response) => {
+        this.setProperties({
+          categories: response.categories,
+          selectedCategoryIds: [...this.startingCategoryIds],
+          loading: false,
+        });
+        next(() => {
+          this.endorsements.forEach((endorsement) => {
+            const checkbox = this.element.querySelector(
+              `#category-endorsement-${endorsement.category_id}`
+            );
+            if (checkbox) {
+              checkbox.checked = true;
+              checkbox.disabled = true;
+            }
+          });
+        });
+      })
+      .catch(popupAjaxError);
   },
 
   @discourseComputed("saving", "selectedCategoryIds", "startingCategoryIds")
diff --git a/assets/javascripts/discourse/templates/components/endorsement-checkboxes.hbs b/assets/javascripts/discourse/templates/components/endorsement-checkboxes.hbs
index 31a2548..a188d12 100644
--- a/assets/javascripts/discourse/templates/components/endorsement-checkboxes.hbs
+++ b/assets/javascripts/discourse/templates/components/endorsement-checkboxes.hbs
@@ -1,24 +1,28 @@
 {{#d-modal-body}}
   <h3>{{i18n "category_experts.manage_endorsements.subtitle" username=user.username}}</h3>
 
-  {{#if showingSuccess}}
-    <div class="endorsement-successful">
-      {{d-icon "check"}}
-    </div>
+  {{#if loading}}
+    {{loading-spinner size="large"}}
   {{else}}
-    {{#each categories as |category|}}
-      <label class="category-experts-endorsement-row">
-        <input
-          type="checkbox"
-          name="category"
-          class="category-endorsement-checkbox"
-          value={{category.id}}
-          id="category-endorsement-{{category.id}}"
-          {{action "checkboxChanged" category.id}}
-        >
-        {{category.name}}
-      </label>
-    {{/each}}
+    {{#if showingSuccess}}
+      <div class="endorsement-successful">
+        {{d-icon "check"}}
+      </div>
+    {{else}}
+      {{#each categories as |category|}}
+        <label class="category-experts-endorsement-row">
+          <input
+            type="checkbox"
+            name="category"
+            class="category-endorsement-checkbox"
+            value={{category.id}}
+            id="category-endorsement-{{category.id}}"
+            {{action "checkboxChanged" category.id}}
+          >
+          {{category.name}}
+        </label>
+      {{/each}}
+    {{/if}}
   {{/if}}
 {{/d-modal-body}}
 
diff --git a/assets/javascripts/discourse/templates/modal/endorse-user.hbs b/assets/javascripts/discourse/templates/modal/endorse-user.hbs
index 64580fe..e5ec16c 100644
--- a/assets/javascripts/discourse/templates/modal/endorse-user.hbs
+++ b/assets/javascripts/discourse/templates/modal/endorse-user.hbs
@@ -1,5 +1,4 @@
 {{endorsement-checkboxes
-  categories=model.categories
   user=model.user
   endorsements=model.endorsements
   location=model.location
diff --git a/plugin.rb b/plugin.rb
index fb5335a..a665974 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -256,5 +256,6 @@ after_initialize do
     post "category-experts/approve" => "category_experts#approve_post"
     post "category-experts/unapprove" => "category_experts#unapprove_post"
     get "category-experts/retroactive-approval/:post_id" => "category_experts#retroactive_approval?"
+    get "category-experts/endorsable-categories/:username" => "category_experts#endorsable_categories", constraints: { username: ::RouteFormat.username }
   end
 end
diff --git a/spec/requests/category_experts_controller_spec.rb b/spec/requests/category_experts_controller_spec.rb
index fbcf598..0fc0468 100644
--- a/spec/requests/category_experts_controller_spec.rb

[... diff too long, it was truncated ...]

GitHub sha: 88d53ce0

This commit appears in #27 which was approved by pmusaraj. It was merged by markvanlan.