diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2cc78054b..ecf8b913e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -103,15 +103,11 @@ def check_if_locked(post) end def top_level_post_types - Rails.cache.fetch 'top_level_post_types' do - PostType.where(is_top_level: true).select(:id).map(&:id) - end + helpers.post_type_ids(is_top_level: true) end def second_level_post_types - Rails.cache.fetch 'second_level_post_types' do - PostType.where(is_top_level: false, has_parent: true).select(:id).map(&:id) - end + helpers.post_type_ids(is_top_level: false, has_parent: true) end def check_edits_limit!(post) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 0486ab394..e2e203795 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -129,7 +129,8 @@ def update return redirect_to post_path(@post) end - if current_user.privilege?('edit_posts') || current_user.is_moderator || current_user == @post.user + if current_user.privilege?('edit_posts') || current_user.is_moderator || current_user == @post.user || \ + (@post_type.is_freely_editable && current_user.privilege?('unrestricted')) if @post.update(edit_post_params.merge(body: body_rendered, last_edited_at: DateTime.now, last_edited_by: current_user, last_activity: DateTime.now, last_activity_by: current_user)) diff --git a/app/helpers/post_types_helper.rb b/app/helpers/post_types_helper.rb index 222e2844b..073e8ade0 100644 --- a/app/helpers/post_types_helper.rb +++ b/app/helpers/post_types_helper.rb @@ -8,4 +8,15 @@ def post_type_badge(type) tag.i(class: icon_class) + ' ' + tag.span(type) # rubocop:disable Style/StringConcatenation end end + + def post_type_criteria + PostType.new.attributes.keys.select { |k| k.start_with?('has_') || k.start_with?('is_') }.map(&:to_sym) + end + + def post_type_ids(**opts) + key = post_type_criteria.map { |a| opts[a] ? '1' : '0' }.join + Rails.cache.fetch "post_type_ids/#{key}" do + PostType.where(**opts).select(:id).map(&:id) + end + end end diff --git a/app/models/community_user.rb b/app/models/community_user.rb index 897672e95..a2c685ed4 100644 --- a/app/models/community_user.rb +++ b/app/models/community_user.rb @@ -25,8 +25,9 @@ def suspended? # These are quite expensive, so we'll cache them for a while def post_score Rails.cache.fetch("privileges/#{id}/post_score", expires_in: 3.hours) do - good_posts = Post.where(user: user).where('score > 0.5').count - bad_posts = Post.where(user: user).where('score < 0.5').count + exclude_types = ApplicationController.helpers.post_type_ids(is_freely_editable: true) + good_posts = Post.where(user: user).where('score > 0.5').where.not(post_type_id: exclude_types).count + bad_posts = Post.where(user: user).where('score < 0.5').where.not(post_type_id: exclude_types).count (good_posts + 2.0) / (good_posts + bad_posts + 4.0) end diff --git a/db/migrate/20201216225353_add_is_freely_editable_to_post_types.rb b/db/migrate/20201216225353_add_is_freely_editable_to_post_types.rb new file mode 100644 index 000000000..1d5180118 --- /dev/null +++ b/db/migrate/20201216225353_add_is_freely_editable_to_post_types.rb @@ -0,0 +1,5 @@ +class AddIsFreelyEditableToPostTypes < ActiveRecord::Migration[5.2] + def change + add_column :post_types, :is_freely_editable, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 8c4c79644..a701c093b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_12_12_235514) do +ActiveRecord::Schema.define(version: 2020_12_16_225353) do create_table "abilities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "community_id" @@ -309,6 +309,7 @@ t.boolean "is_public_editable", default: false, null: false t.boolean "is_closeable", default: false, null: false t.boolean "is_top_level", default: false, null: false + t.boolean "is_freely_editable", default: false, null: false t.index ["name"], name: "index_post_types_on_name" end @@ -545,6 +546,7 @@ t.integer "failed_attempts", default: 0, null: false t.string "unlock_token" t.datetime "locked_at" + t.integer "trust_level" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb index f59de979b..16e50a1da 100644 --- a/test/controllers/posts_controller_test.rb +++ b/test/controllers/posts_controller_test.rb @@ -393,6 +393,20 @@ class PostsControllerTest < ActionController::TestCase assert_equal before_history, after_history, 'PostHistory event incorrectly created on update' end + test 'anyone with unrestricted can update free-edit post' do + sign_in users(:standard_user) + before_history = PostHistory.where(post: posts(:free_edit)).count + patch :update, params: { id: posts(:free_edit).id, + post: { title: sample.edit.title, body_markdown: sample.edit.body_markdown, + tags_cache: sample.edit.tags_cache } } + after_history = PostHistory.where(post: posts(:free_edit)).count + assert_response 302 + assert_redirected_to post_path(posts(:free_edit)) + assert_not_nil assigns(:post) + assert_equal sample.edit.body_markdown, assigns(:post).body_markdown + assert_equal before_history + 1, after_history, 'No PostHistory event created on free-edit update' + end + # Close test 'can close question' do diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml index 5669e36b4..f922ef750 100644 --- a/test/fixtures/categories.yml +++ b/test/fixtures/categories.yml @@ -11,6 +11,7 @@ main: - question - answer - article + - free_edit tag_set: main license: cc_by_sa diff --git a/test/fixtures/post_types.yml b/test/fixtures/post_types.yml index dea8f1923..7cb6bd2b4 100644 --- a/test/fixtures/post_types.yml +++ b/test/fixtures/post_types.yml @@ -10,6 +10,7 @@ question: is_public_editable: true is_closeable: true is_top_level: true + is_freely_editable: false answer: name: Answer @@ -23,6 +24,7 @@ answer: is_public_editable: true is_closeable: false is_top_level: false + is_freely_editable: false article: name: Article @@ -36,6 +38,7 @@ article: is_public_editable: true is_closeable: false is_top_level: true + is_freely_editable: false policy_doc: name: PolicyDoc @@ -49,6 +52,7 @@ policy_doc: is_public_editable: false is_closeable: false is_top_level: false + is_freely_editable: false help_doc: name: HelpDoc @@ -62,6 +66,7 @@ help_doc: is_public_editable: false is_closeable: false is_top_level: false + is_freely_editable: false blog_post: name: BlogPost @@ -74,4 +79,19 @@ blog_post: has_license: true is_public_editable: false is_closeable: false - is_top_level: true \ No newline at end of file + is_top_level: true + is_freely_editable: false + +free_edit: + name: FreeEdit + description: ~ + has_answers: true + has_votes: true + has_tags: true + has_parent: false + has_category: true + has_license: true + is_public_editable: true + is_closeable: true + is_top_level: true + is_freely_editable: true \ No newline at end of file diff --git a/test/fixtures/posts.yml b/test/fixtures/posts.yml index 54e50a21a..98444323b 100644 --- a/test/fixtures/posts.yml +++ b/test/fixtures/posts.yml @@ -183,6 +183,27 @@ locked_mod: upvote_count: 0 downvote_count: 0 +free_edit: + post_type: free_edit + title: FE ABCDEF GHIJKL MNOPQR STUVWX YZ + body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ + body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ + tags_cache: + - discussion + - support + - bug + tags: + - discussion + - support + - bug + score: 0.5 + user: moderator + community: sample + category: main + license: cc_by_sa + upvote_count: 0 + downvote_count: 0 + answer_one: post_type: answer body: A1 ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ