Allow category expert posts to be retroactively approved (#24)

Allow category expert posts to be retroactively approved (#24)

diff --git a/app/controllers/category_experts_controller.rb b/app/controllers/category_experts_controller.rb
index 83c0926..0950f94 100644
--- a/app/controllers/category_experts_controller.rb
+++ b/app/controllers/category_experts_controller.rb
@@ -1,7 +1,12 @@
 # frozen_string_literal: true
 
 class CategoryExpertsController < ApplicationController
-  before_action :find_post, :ensure_staff_and_enabled, only: [:approve_post, :unapprove_post]
+  before_action :find_post, :ensure_staff, :ensure_needs_approval_enabled, only: [
+    :approve_post,
+    :unapprove_post,
+    :retroactive_approval?,
+  ]
+  before_action :ensure_needs_approval_enabled, only: [:approve_post, :unapprove_post]
 
   def endorse
     raise Discourse::NotFound unless current_user
@@ -39,8 +44,24 @@ class CategoryExpertsController < ApplicationController
     render json: topic_custom_fields
   end
 
+  def retroactive_approval?
+    render json: { can_be_approved: post_could_be_expert_answer(@post) }
+  end
+
   private
 
+  def post_could_be_expert_answer(post)
+    return false if post.custom_fields[CategoryExperts::POST_APPROVED_GROUP_NAME]
+
+    category = post.topic.category
+    return false unless category
+
+    expert_group_ids = category.custom_fields[CategoryExperts::CATEGORY_EXPERT_GROUP_IDS].split("|").map(&:to_i)
+    return false unless expert_group_ids.count
+
+    (expert_group_ids & (post.user.group_ids || [])).count > 0
+  end
+
   def topic_custom_fields
     {
       topic_expert_post_group_names: @post.topic.custom_fields[CategoryExperts::TOPIC_EXPERT_POST_GROUP_NAMES],
@@ -49,11 +70,15 @@ class CategoryExpertsController < ApplicationController
   end
 
   def ensure_staff_and_enabled
-    unless current_user && current_user.staff? && SiteSetting.category_experts_posts_require_approval
+    unless current_user && current_user.staff?
       raise Discourse::InvalidAccess
     end
   end
 
+  def ensure_needs_approval_enabled
+    raise Discourse::InvalidAccess unless SiteSetting.category_experts_posts_require_approval
+  end
+
   def find_post
     post_id = params.require(:post_id)
     @post = Post.find_by(id: post_id)
diff --git a/assets/javascripts/discourse/initializers/category-experts-post-admin-menu.js b/assets/javascripts/discourse/initializers/category-experts-post-admin-menu.js
new file mode 100644
index 0000000..733aa50
--- /dev/null
+++ b/assets/javascripts/discourse/initializers/category-experts-post-admin-menu.js
@@ -0,0 +1,67 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+import { createWidget } from "discourse/widgets/widget";
+import { ajax } from "discourse/lib/ajax";
+import { h } from "virtual-dom";
+import { next } from "@ember/runloop";
+
+export default {
+  name: "category-experts-post-admin-menu",
+
+  initialize(container) {
+    createWidget("category-experts-post-admin-menu-btn", {
+      tagName: "ul",
+      buildClasses: () => "category-experts-post-admin-menu-btn",
+      buildKey: () => "category-experts-post-admin-menu-btn",
+
+      defaultState() {
+        return { show: false, loading: false, loaded: false };
+      },
+
+      load(attrs, state) {
+        return ajax(`/category-experts/retroactive-approval/${attrs.id}.json`)
+          .then((response) => {
+            state.show = response.can_be_approved;
+          })
+          .catch(() => {
+            state.show = false;
+          })
+          .finally(() => {
+            state.loaded = true;
+            this.scheduleRerender();
+          });
+      },
+
+      html(attrs, state) {
+        if (
+          attrs.category_expert_approved_group ||
+          attrs.needs_category_expert_approval
+        ) {
+          return;
+        }
+        if (!state.loaded) {
+          this.load(attrs, state);
+        } else if (state.show) {
+          return this.attach("post-admin-menu-button", {
+            action: "approveCategoryExpertPost",
+            title: "category_experts.approve",
+            label: "category_experts.approve",
+            icon: "thumbs-up",
+          });
+        }
+      },
+    });
+
+    withPluginApi("0.8.36", (api) => {
+      api.decorateWidget("post-admin-menu:after", (helper) => {
+        return helper.attach(
+          "category-experts-post-admin-menu-btn",
+          helper.attrs
+        );
+      });
+      api.attachWidgetAction("post", "approveCategoryExpertPost", () => {
+        console.log("yeppers");
+        // setPostCategoryExpertAttributes(this.model, { approved: true });
+      });
+    });
+  },
+};
diff --git a/assets/stylesheets/common.scss b/assets/stylesheets/common.scss
index a8b8e58..c7f1927 100644
--- a/assets/stylesheets/common.scss
+++ b/assets/stylesheets/common.scss
@@ -12,14 +12,12 @@
     text-decoration: underline;
   }
 }
-.user-card .first-row .usercard-controls .category-expert-endorse-edit, .group-card .first-row .usercard-controls .category-expert-endorse-edit {
-
+.user-card .first-row .usercard-controls .category-expert-endorse-edit,
+.group-card .first-row .usercard-controls .category-expert-endorse-edit {
   width: unset;
   min-width: unset;
 }
 
-
-
 .reviewable-category-expert-suggestion {
   .reviewable-category-experts-header {
     width: 100%;
@@ -45,14 +43,15 @@
   line-height: 1em;
 }
 
-
-.time-gap+.topic-post article.category-expert-post {
-  .topic-body, .topic-avatar {
+.time-gap + .topic-post article.category-expert-post {
+  .topic-body,
+  .topic-avatar {
     border-top: 4px solid $success;
   }
 }
 article.category-expert-post {
-  .topic-body, .topic-avatar {
+  .topic-body,
+  .topic-avatar {
     border-top: 4px solid $success;
   }
 }
@@ -82,3 +81,7 @@ article.category-expert-post {
 .topic-list-category-expert-question {
   background: $tertiary-low;
 }
+
+ul.category-experts-post-admin-menu-btn li {
+  border-top: 1px solid $primary-low !important;
+}
diff --git a/plugin.rb b/plugin.rb
index 7635b1a..fb5335a 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -255,5 +255,6 @@ after_initialize do
     put "category-experts/endorse/:username" => "category_experts#endorse", constraints: { username: ::RouteFormat.username }
     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?"
   end
 end
diff --git a/spec/requests/category_experts_controller_spec.rb b/spec/requests/category_experts_controller_spec.rb
index 10e24e4..b62553a 100644
--- a/spec/requests/category_experts_controller_spec.rb
+++ b/spec/requests/category_experts_controller_spec.rb
@@ -3,6 +3,7 @@
 require "rails_helper"
 
 describe CategoryExpertsController do
+  fab!(:admin) { Fabricate(:admin) }
   fab!(:user) { Fabricate(:user) }
   fab!(:other_user) { Fabricate(:user) }
   fab!(:endorsee) { Fabricate(:user) }
@@ -71,7 +72,6 @@ describe CategoryExpertsController do
 
   describe "#approve_post" do
     fab!(:topic) { Fabricate(:topic, category: category1) }
-    fab!(:admin) { Fabricate(:admin) }
 
     before do
       create_post(topic_id: topic.id, user: user)
@@ -131,7 +131,6 @@ describe CategoryExpertsController do
 
   describe "#unapprove_post" do
     fab!(:topic) { Fabricate(:topic, category: category1) }
-    fab!(:admin) { Fabricate(:admin) }
 
     before do
       create_post(topic_id: topic.id, user: user)
@@ -187,4 +186,56 @@ describe CategoryExpertsController do
       end
     end
   end
+
+  describe "#retroactive_approval?" do
+    fab!(:topic) { Fabricate(:topic, category: category1) }
+    fab!(:random_user) { Fabricate(:user) }
+
+    describe "non-staff user" do
+      before do
+        sign_in(user)
+      end
+
+      it "returns a 403" do
+        post = create_post(topic_id: topic.id, user: user)
+        get "/category-experts/retroactive-approval/#{post.id}.json"
+
+        expect(response.status).to eq(403)
+      end
+    end
+

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

GitHub sha: 5bd614c0

This commit appears in #24 which was approved by eviltrout. It was merged by markvanlan.