diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index ae32f3a101dd6534ca05146a5c90e229a696f25f..cbe13600ab04f92097eb38e9734ea8064a43234e 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -2,7 +2,7 @@
 class PostsController < ApplicationController
   before_action :authenticate_user!, except: [:document, :share_q, :share_a, :help_center, :show]
   before_action :set_post, only: [:toggle_comments, :feature, :lock, :unlock]
-  before_action :set_scoped_post, only: [:change_category, :show, :edit, :update]
+  before_action :set_scoped_post, only: [:change_category, :show, :edit, :update, :close, :reopen, :delete, :restore]
   before_action :verify_moderator, only: [:toggle_comments]
   before_action :edit_checks, only: [:edit, :update]
 
@@ -173,6 +173,46 @@ class PostsController < ApplicationController
     end
   end
 
+  def close
+    unless check_your_privilege('flag_close', nil, false)
+      render json: { status: 'failed', message: helpers.ability_err_msg(:flag_close, 'close this post') },
+             status: :forbidden
+      return
+    end
+
+    if @post.closed
+      render json: { status: 'failed', message: 'Cannot close a closed post.' }, status: :bad_request
+      return
+    end
+
+    reason = CloseReason.find_by id: params[:reason_id]
+    if reason.nil?
+      render json: { status: 'failed', message: 'Close reason not found.' }, status: :not_found
+      return
+    end
+
+    if reason.requires_other_post
+      other = Post.find_by(id: params[:other_post])
+      if other.nil? || !top_level_post_types.include?(other.post_type_id)
+        render json: { status: 'failed', message: 'Invalid input for other post.' }, status: :bad_request
+        return
+      end
+
+      duplicate_of = Question.find(params[:other_post])
+    else
+      duplicate_of = nil
+    end
+
+    if @post.update(closed: true, closed_by: current_user, closed_at: DateTime.now, last_activity: DateTime.now,
+                    last_activity_by: current_user, close_reason: reason, duplicate_post: duplicate_of)
+      PostHistory.question_closed(@post, current_user)
+      render json: { status: 'success' }
+    else
+      render json: { status: 'failed', message: "Can't close this question right now. Try again later.",
+                     errors: @post.errors.full_messages }
+    end
+  end
+
   # TODO: delete, undelete, close, reopen
 
   def document
diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb
index 12d40b523784445f90ec23c36d24f9f8ef990880..3d1296e7b740f6ef033e9ded7bd97903f1e5ab4d 100644
--- a/app/controllers/questions_controller.rb
+++ b/app/controllers/questions_controller.rb
@@ -82,45 +82,6 @@ class QuestionsController < ApplicationController
     end
   end
 
-  def close
-    unless check_your_privilege('flag_close', nil, false)
-      render(json: { status: 'failed', message: helpers.ability_err_msg(:flag_close, 'close this question') },
-             status: :forbidden)
-      return
-    end
-
-    if @question.closed
-      render(json: { status: 'failed', message: 'Cannot close a closed question.' }, status: :bad_request)
-      return
-    end
-
-    reason = CloseReason.find_by id: params[:reason_id]
-    if reason.nil?
-      render(json: { status: 'failed', message: 'Close reason not found.' }, status: :not_found)
-      return
-    end
-
-    if reason.requires_other_post
-      unless Question.exists? params[:other_post]
-        render(json: { status: 'failed', message: 'Invalid input for other post.' }, status: :bad_request)
-        return
-      end
-
-      duplicate_of = Question.find(params[:other_post])
-    else
-      duplicate_of = nil
-    end
-
-    if @question.update(closed: true, closed_by: current_user, closed_at: DateTime.now, last_activity: DateTime.now,
-                        last_activity_by: current_user, close_reason: reason, duplicate_post: duplicate_of)
-      PostHistory.question_closed(@question, current_user)
-      render json: { status: 'success' }
-    else
-      render json: { status: 'failed', message: "Can't close this question right now. Try again later.",
-                     errors: @question.errors.full_messages }
-    end
-  end
-
   def reopen
     unless check_your_privilege('flag_close', nil, false)
       flash[:danger] = helpers.ability_err_msg(:flag_close, 'reopen this question')
diff --git a/app/models/post.rb b/app/models/post.rb
index 0b9b6e1e86bee448e50f4cd53f74501777433e85..98e9edbbcc9d84c676b775f0f93b8407d1cc6df7 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -11,6 +11,8 @@ class Post < ApplicationRecord
   belongs_to :last_edited_by, class_name: 'User', optional: true
   belongs_to :category, optional: true
   belongs_to :license, optional: true
+  belongs_to :close_reason, optional: true
+  belongs_to :duplicate_post, class_name: 'Question', optional: true
   has_and_belongs_to_many :tags, dependent: :destroy
   has_many :votes, dependent: :destroy
   has_many :comments, dependent: :destroy
diff --git a/app/models/question.rb b/app/models/question.rb
index b0a347e7e0a0e11e29982e6d6f56a1ecb1cec626..85d63310c74ff41449674029f02bad3f8df25607 100644
--- a/app/models/question.rb
+++ b/app/models/question.rb
@@ -4,9 +4,6 @@ class Question < Post
   scope :meta, -> { joins(:category).where(categories: { name: 'Meta' }) }
   scope :main, -> { joins(:category).where(categories: { name: 'Main' }) }
 
-  belongs_to :close_reason, optional: true
-  belongs_to :duplicate_post, class_name: 'Question', optional: true
-
   def self.post_type_id
     PostType.mapping['Question']
   end
diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb
index fcc735141fb6b06deb889cc4e2208294a72e1494..4f468979b38510b319130977cc29ca1cf31074a3 100644
--- a/test/controllers/posts_controller_test.rb
+++ b/test/controllers/posts_controller_test.rb
@@ -302,4 +302,18 @@ class PostsControllerTest < ActionController::TestCase
     assert_not_nil flash[:danger]
     assert_equal before_history, after_history, 'PostHistory event incorrectly created on no-change update'
   end
+
+  test 'can close question' do
+    sign_in users(:closer)
+    before_history = PostHistory.where(post: posts(:question_one)).count
+    post :close, params: { id: posts(:question_one).id, reason_id: close_reasons(:not_good).id }
+    after_history = PostHistory.where(post: posts(:question_one)).count
+    assert_response 200
+    assert_not_nil assigns(:post)
+    assert_equal before_history + 1, after_history, 'PostHistory event not created on closure'
+    assert_nothing_raised do
+      JSON.parse(response.body)
+    end
+    assert_equal 'success', JSON.parse(response.body)['status']
+  end
 end