diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb
index fcd60c6391278897330d8d34c398671cd82cc965..a30ba3f64b51dff519403da0fb387232fd879511 100644
--- a/app/controllers/answers_controller.rb
+++ b/app/controllers/answers_controller.rb
@@ -1,166 +1,10 @@
 # Web controller. Provides actions that relate to answers. Pretty much the standard set of resources, really - it's
 # questions that have a few more actions.
 class AnswersController < ApplicationController
-  before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy, :undelete, :convert_to_comment]
-  before_action :set_answer, only: [:edit, :update, :destroy, :undelete, :convert_to_comment]
+  before_action :authenticate_user!, only: [:convert_to_comment]
+  before_action :set_answer, only: [:convert_to_comment]
   before_action :verify_moderator, only: [:convert_to_comment]
-  before_action :check_if_answer_locked, only: [:edit, :update, :destroy, :undelete, :convert_to_comment]
-
-  def new
-    @answer = Answer.new
-    @question = Question.find params[:id]
-  end
-
-  def create
-    @question = Question.find params[:id]
-
-    @answer = Answer.new(answer_params.merge(parent: @question, user: current_user, score: 0,
-                                             body: helpers.post_markdown(:answer, :body_markdown),
-                                             last_activity: DateTime.now, last_activity_by: current_user,
-                                             category: @question.category))
-
-    recent_second_level_posts = Post.where(created_at: 24.hours.ago..Time.zone.now, user: current_user)
-                                    .where(post_type_id: second_level_post_types).count
-
-    max_slps = SiteSetting[if current_user.privilege?('unrestricted')
-                             'RL_SecondLevelPosts'
-                           else
-                             'RL_NewUserSecondLevelPosts'
-                           end]
-
-    post_limit_msg = if current_user.privilege? 'unrestricted'
-                       "You may only post #{max_slps} answers per day."
-                     else
-                       "You may only post #{max_slps} answers per day. " \
-                       'Once you have some well-received posts, that limit will increase.'
-                     end
-
-    if recent_second_level_posts >= max_slps
-      @answer.errors.add :base, post_limit_msg
-      AuditLog.rate_limit_log(event_type: 'second_level_post', related: @question, user: current_user,
-                              comment: "limit: #{max_slps}\n\npost:\n#{@answer.attributes_print}")
-      render :new, status: :bad_request
-      return
-    end
-
-    unless current_user.id == @question.user.id
-      @question.user.create_notification("New answer to your question '#{@question.title.truncate(50)}'",
-                                         share_question_url(@question))
-    end
-    if @answer.save
-      @question.update(last_activity: DateTime.now, last_activity_by: current_user)
-      unless current_user.id == @question.user.id
-        @question.user.create_notification("New answer to your question '#{@question.title.truncate(50)}'",
-                                           share_question_url(@question))
-      end
-      redirect_to url_for(controller: :questions, action: :show, id: params[:id])
-    else
-      render :new, status: :unprocessable_entity
-    end
-  end
-
-  def edit; end
-
-  def update
-    can_post_in_category = @answer.parent.category.present? &&
-                           (@answer.parent.category.min_trust_level || -1) <= current_user&.trust_level
-    unless current_user&.has_post_privilege?('edit_posts', @answer) && can_post_in_category
-      return update_as_suggested_edit
-    end
-
-    if params[:answer][:body_markdown] == @answer.body_markdown
-      flash[:danger] = "No changes were saved because you didn't edit the post."
-      return redirect_to question_path(@answer.parent)
-    end
-
-    before = @answer.body_markdown
-    if @answer.update(answer_params.merge(body: helpers.post_markdown(:answer, :body_markdown),
-                                          last_activity: DateTime.now, last_activity_by: current_user,
-                                          last_edited_at: DateTime.now, last_edited_by: current_user,
-                                          license_id: @answer.license_id))
-      PostHistory.post_edited(@answer, current_user, before: before,
-                              after: params[:answer][:body_markdown], comment: params[:edit_comment])
-      redirect_to share_answer_path(qid: @answer.parent_id, id: @answer.id)
-    else
-      render :edit
-    end
-  end
-
-  def update_as_suggested_edit
-    return if check_edits_limit! @answer
-
-    if params[:answer][:body_markdown] == @answer.body_markdown
-      flash[:danger] = "No changes were saved because you didn't edit the post."
-      return redirect_to question_path(@answer.parent)
-    end
-
-    updates = {
-      post: @answer,
-      user: current_user,
-      community: @answer.community,
-      body: helpers.post_markdown(:answer, :body_markdown),
-      body_markdown: params[:answer][:body_markdown] == @answer.body_markdown ? nil : params[:answer][:body_markdown],
-      comment: params[:edit_comment],
-      active: true, accepted: false,
-      decided_at: nil, decided_by: nil,
-      rejected_comment: nil
-    }
-
-    @edit = SuggestedEdit.new(updates)
-    if @edit.save
-      @answer.user.create_notification("Edit suggested on your answer to #{@answer.parent.title.truncate(50)}",
-                                       share_answer_url(qid: @answer.parent_id, id: @answer.id))
-      redirect_to share_answer_path(qid: @answer.parent_id, id: @answer.id)
-    else
-      @answer.errors = @edit.errors
-      render :edit
-    end
-  end
-
-  def destroy
-    unless check_your_privilege('flag_curate', @answer, false)
-      flash[:danger] = helpers.ability_err_msg(:flag_curate, 'delete this answer')
-      redirect_to(question_path(@answer.parent)) && return
-    end
-
-    if @answer.deleted
-      flash[:danger] = "Can't delete a deleted answer."
-      redirect_to(question_path(@answer.parent)) && return
-    end
-
-    if @answer.update(deleted: true, deleted_at: DateTime.now, deleted_by: current_user,
-                      last_activity: DateTime.now, last_activity_by: current_user)
-      PostHistory.post_deleted(@answer, current_user)
-    else
-      flash[:danger] = "Can't delete this answer right now. Try again later."
-    end
-    redirect_to question_path(@answer.parent)
-  end
-
-  def undelete
-    unless check_your_privilege('flag_curate', @answer, false)
-      flash[:danger] = flash[:danger] = helpers.ability_err_msg(:flag_curate, 'undelete this answer')
-      redirect_to(question_path(@answer.parent)) && return
-    end
-
-    unless @answer.deleted
-      flash[:danger] = "Can't undelete an undeleted answer."
-      redirect_to(question_path(@answer.parent)) && return
-    end
-
-    if @answer.deleted_by.is_moderator && !current_user.is_moderator
-      flash[:danger] = 'You cannot undelete this post deleted by a moderator.'
-      redirect_to(question_path(@answer.parent)) && return
-    end
-
-    if @answer.update(deleted: false, deleted_at: nil, deleted_by: nil,
-                      last_activity: DateTime.now, last_activity_by: current_user)
-      PostHistory.post_undeleted(@answer, current_user)
-    else
-      flash[:danger] = "Can't undelete this answer right now. Try again later."
-    end
-    redirect_to question_path(@answer.parent)
-  end
+  before_action :check_if_answer_locked, only: [:convert_to_comment]
 
   def convert_to_comment
     text = @answer.body_markdown
@@ -176,10 +20,6 @@ class AnswersController < ApplicationController
 
   private
 
-  def answer_params
-    params.require(:answer).permit(:body_markdown, :license_id)
-  end
-
   def set_answer
     @answer = Answer.find params[:id]
   end
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index 21debf8bbf969c23acfe8368eef4b02c14ea59ad..7f9d4ba3537b138736060c424a47128486047b76 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -1,137 +1,11 @@
 class ArticlesController < ApplicationController
   before_action :set_article
   before_action :check_article
-  before_action :check_if_article_locked, only: [:edit, :update, :destroy, :undelete, :close, :reopen]
-
-  def show
-    if @article.deleted?
-      check_your_privilege('flag_curate', @article) # || return
-    end
-  end
 
   def share
     redirect_to article_path(params[:id])
   end
 
-  def edit; end
-
-  def update
-    can_post_in_category = @article.category.present? &&
-                           (@article.category.min_trust_level || -1) <= current_user&.trust_level
-    unless current_user&.has_post_privilege?('edit_posts', @article) && can_post_in_category
-      return update_as_suggested_edit
-    end
-
-    tags_cache = params[:article][:tags_cache]&.reject { |e| e.to_s.empty? }
-    after_tags = Tag.where(tag_set_id: @article.category.tag_set_id, name: tags_cache)
-
-    if @article.tags == after_tags && @article.body_markdown == params[:article][:body_markdown] &&
-       @article.title == params[:article][:title]
-      flash[:danger] = "No changes were saved because you didn't edit the post."
-      return redirect_to article_path(@article)
-    end
-
-    body_rendered = helpers.post_markdown(:article, :body_markdown)
-    before = { body: @article.body_markdown, title: @article.title, tags: @article.tags }
-    if @article.update(article_params.merge(tags_cache: tags_cache, body: body_rendered,
-                                            last_activity: DateTime.now, last_activity_by: current_user,
-                                            last_edited_at: DateTime.now, last_edited_by: current_user))
-      PostHistory.post_edited(@article, current_user, before: before[:body],
-                              after: params[:article][:body_markdown], comment: params[:edit_comment],
-                              before_title: before[:title], after_title: params[:article][:title],
-                              before_tags: before[:tags], after_tags: after_tags)
-      redirect_to share_article_path(@article)
-    else
-      render :edit
-    end
-  end
-
-  def update_as_suggested_edit
-    return if check_edits_limit! @article
-
-    body_rendered = helpers.post_markdown(:article, :body_markdown)
-    new_tags_cache = params[:article][:tags_cache]&.reject(&:empty?)
-
-    body_markdown = if params[:article][:body_markdown] != @article.body_markdown
-                      params[:article][:body_markdown]
-                    end
-
-    if @article.tags_cache == new_tags_cache && @article.body_markdown == params[:article][:body_markdown] &&
-       @article.title == params[:article][:title]
-      flash[:danger] = "No changes were saved because you didn't edit the post."
-      return redirect_to article_path(@article)
-    end
-
-    updates = {
-      post: @article,
-      user: current_user,
-      community: @article.community,
-      body: body_rendered,
-      title: params[:article][:title] == @article.title ? nil : params[:article][:title],
-      tags_cache: new_tags_cache == @article.tags_cache ? @article.tags_cache : new_tags_cache,
-      body_markdown: body_markdown,
-      comment: params[:edit_comment],
-      active: true, accepted: false,
-      decided_at: nil, decided_by: nil,
-      rejected_comment: nil
-    }
-
-    @edit = SuggestedEdit.new(updates)
-    if @edit.save
-      @article.user.create_notification("Edit suggested on your post #{@article.title.truncate(50)}",
-                                        article_url(@article))
-      redirect_to share_article_path(@article)
-    else
-      @article.errors = @edit.errors
-      render :edit
-    end
-  end
-
-  def destroy
-    unless check_your_privilege('flag_curate', @article, false)
-      flash[:danger] = helpers.ability_err_msg(:flag_curate, 'delete this article')
-      redirect_to article_path(@article) && return
-    end
-
-    if @article.deleted
-      flash[:danger] = "Can't delete a deleted post."
-      redirect_to article_path(@article) && return
-    end
-
-    if @article.update(deleted: true, deleted_at: DateTime.now, deleted_by: current_user,
-                       last_activity: DateTime.now, last_activity_by: current_user)
-      PostHistory.post_deleted(@article, current_user)
-    else
-      flash[:danger] = "Can't delete this post right now. Try again later."
-    end
-    redirect_to article_path(@article)
-  end
-
-  def undelete
-    unless check_your_privilege('flag_curate', @article, false)
-      flash[:danger] = helpers.ability_err_msg(:flag_curate, 'undelete this article')
-      redirect_to article_path(@article) && return
-    end
-
-    unless @article.deleted
-      flash[:danger] = "Can't undelete an undeleted post."
-      redirect_to article_path(@article) && return
-    end
-
-    if @article.deleted_by.is_moderator && !current_user.is_moderator
-      flash[:danger] = 'You cannot undelete this post deleted by a moderator.'
-      redirect_to(article_path(@article)) && return
-    end
-
-    if @article.update(deleted: false, deleted_at: nil, deleted_by: nil,
-                       last_activity: DateTime.now, last_activity_by: current_user)
-      PostHistory.post_undeleted(@article, current_user)
-    else
-      flash[:danger] = "Can't undelete this article right now. Try again later."
-    end
-    redirect_to article_path(@article)
-  end
-
   private
 
   def set_article
@@ -146,12 +20,4 @@ class ArticlesController < ApplicationController
       not_found
     end
   end
-
-  def article_params
-    params.require(:article).permit(:body_markdown, :title, :tags_cache)
-  end
-
-  def check_if_article_locked
-    check_if_locked(@article)
-  end
 end
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index e12ef42471a635d8120b3d2782f00a9261287039..e4f7a9e50dc6341b78f40ffe1d849152ed2e0ab5 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -13,13 +13,13 @@ class PostsController < ApplicationController
     @parent = Post.where(id: params[:parent]).first
     @post = Post.new(category: @category, post_type: @post_type, parent: @parent)
 
-    if @post_type.has_category? && @category.nil? && @parent.nil?
-      flash[:danger] = helpers.i18ns('posts.type_requires_category', type: @post_type.name)
+    if @post_type.has_parent? && @parent.nil?
+      flash[:danger] = helpers.i18ns('posts.type_requires_parent', type: @post_type.name)
       redirect_back fallback_location: root_path
     end
 
-    if @post_type.has_parent? && @parent.nil?
-      flash[:danger] = helpers.i18ns('posts.type_requires_parent', type: @post_type.name)
+    if @post_type.has_category? && @category.nil? && @parent.nil?
+      flash[:danger] = helpers.i18ns('posts.type_requires_category', type: @post_type.name)
       redirect_back fallback_location: root_path
     end
 
@@ -41,14 +41,14 @@ class PostsController < ApplicationController
     @post = Post.new(post_params.merge(user: current_user, body: helpers.post_markdown(:post, :body_markdown),
                                        category: @category, post_type: @post_type, parent: @parent))
 
-    if @post_type.has_category? && @category.nil? && @parent.nil?
-      flash[:danger] = helpers.i18ns('posts.type_requires_category', type: @post_type.name)
+    if @post_type.has_parent? && @parent.nil?
+      flash[:danger] = helpers.i18ns('posts.type_requires_parent', type: @post_type.name)
       redirect_back fallback_location: root_path
       return
     end
 
-    if @post_type.has_parent? && @parent.nil?
-      flash[:danger] = helpers.i18ns('posts.type_requires_parent', type: @post_type.name)
+    if @post_type.has_category? && @category.nil? && @parent.nil?
+      flash[:danger] = helpers.i18ns('posts.type_requires_category', type: @post_type.name)
       redirect_back fallback_location: root_path
       return
     end
diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb
index 0efe22b97833eb1dc41ae27004df2c4ddaa8dd5d..1b57bce8494fefe93112ac946b66ba7069e37e18 100644
--- a/app/controllers/questions_controller.rb
+++ b/app/controllers/questions_controller.rb
@@ -1,10 +1,6 @@
 # Web controller. Provides actions that relate to questions - this is essentially the standard set of resources, plus a
 # couple for the extra question lists (such as listing by tag).
 class QuestionsController < ApplicationController
-  before_action :authenticate_user!, only: [:destroy, :undelete, :close, :reopen]
-  before_action :set_question, only: [:destroy, :undelete, :close, :reopen]
-  before_action :check_if_question_locked, only: [:destroy, :undelete, :close, :reopen]
-
   def lottery
     ids = Rails.cache.fetch 'lottery_questions', expires_in: 24.hours do
       # noinspection RailsParamDefResolve
@@ -23,29 +19,4 @@ class QuestionsController < ApplicationController
       format.rss { render layout: false }
     end
   end
-
-  private
-
-  def question_params
-    params.require(:question).permit(:body_markdown, :title, :tags_cache)
-  end
-
-  def set_question
-    @question = Question.find params[:id]
-  rescue
-    if current_user&.privilege?('flag_curate')
-      @question ||= Question.unscoped.find params[:id]
-    end
-    if @question.nil?
-      not_found
-      return
-    end
-    unless @question.post_type_id == Question.post_type_id
-      not_found
-    end
-  end
-
-  def check_if_question_locked
-    check_if_locked(@question)
-  end
 end
diff --git a/config/routes.rb b/config/routes.rb
index 7cfa5fa1576e803b511b7b6dc377888a674d7615..044e3a95c28758ecbdf3471a3962fcfa5f3b13f6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -64,7 +64,6 @@ Rails.application.routes.draw do
 
   get    'questions/lottery',              to: 'questions#lottery', as: :questions_lottery
   get    'questions/feed',                 to: 'questions#feed', as: :question_feed
-  get    'questions/tagged/:tag_set/:tag', to: 'questions#tagged', as: :questions_tagged
   get    'questions/:id',                  to: 'posts#redirect', as: :question
 
   scope 'posts' do
diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb
index 3405e8668bdc52f21a69f91b21f63b35b8d3680a..ce99f5d5ae7eca98323ed9380c8ad32847368167 100644
--- a/test/controllers/posts_controller_test.rb
+++ b/test/controllers/posts_controller_test.rb
@@ -80,6 +80,22 @@ class PostsControllerTest < ActionController::TestCase
     assert_redirected_to new_user_session_path
   end
 
+  test 'new rejects category post type without category' do
+    sign_in users(:standard_user)
+    get :new, params: { post_type: post_types(:question).id }
+    assert_response 302
+    assert_redirected_to root_path
+    assert_not_nil flash[:danger]
+  end
+
+  test 'new rejects parented post type without parent' do
+    sign_in users(:standard_user)
+    get :new, params: { post_type: post_types(:answer).id }
+    assert_response 302
+    assert_redirected_to root_path
+    assert_not_nil flash[:danger]
+  end
+
   # Create
 
   test 'can create help post' do
diff --git a/test/controllers/questions_controller_test.rb b/test/controllers/questions_controller_test.rb
index 8515e933d29e394d254977cfd29c6af388919000..5164c708ef072ffa37b48bdd24dfc1f81e27968a 100644
--- a/test/controllers/questions_controller_test.rb
+++ b/test/controllers/questions_controller_test.rb
@@ -3,10 +3,4 @@ require 'test_helper'
 class QuestionsControllerTest < ActionController::TestCase
   include Devise::Test::ControllerHelpers
   include ApplicationTestHelper
-
-  test 'should get tagged page' do
-    get :tagged, params: { tag: 'discussion', tag_set: tag_sets(:main).id }
-    assert_not_nil assigns(:questions)
-    assert_response(200)
-  end
 end