diff --git a/app/controllers/drafts_controller.rb b/app/controllers/drafts_controller.rb index ba767b8a5d8..c9ec8bdab87 100644 --- a/app/controllers/drafts_controller.rb +++ b/app/controllers/drafts_controller.rb @@ -40,6 +40,19 @@ class DraftsController < ApplicationController raise Discourse::InvalidParameters.new(:data) end + if reached_max_drafts_per_user?(params) + render_json_error I18n.t("draft.too_many_drafts.title"), + status: 403, + extras: { + description: + I18n.t( + "draft.too_many_drafts.description", + base_url: Discourse.base_url, + ), + } + return + end + sequence = begin Draft.set( @@ -115,4 +128,13 @@ class DraftsController < ApplicationController render json: success_json end + + private + + def reached_max_drafts_per_user?(params) + user_id = current_user.id + + Draft.where(user_id: user_id).count >= SiteSetting.max_drafts_per_user && + !Draft.exists?(user_id: user_id, draft_key: params[:draft_key]) + end end diff --git a/app/models/draft.rb b/app/models/draft.rb index 5e1ae358747..1f759d4e2cf 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -9,6 +9,8 @@ class Draft < ActiveRecord::Base has_many :upload_references, as: :target, dependent: :delete_all + validates :draft_key, length: { maximum: 25 } + after_commit :update_draft_count, on: %i[create destroy] class OutOfSequence < StandardError diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 28cb1aca532..4e1d89668b8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -978,6 +978,9 @@ en: sequence_conflict_error: title: "draft error" description: "Draft is being edited in another window. Please reload this page." + too_many_drafts: + title: "Too many drafts" + description: "You have reached the maximum number of allowed drafts. Please delete some of [your drafts](%{base_url}/my/activity/drafts) and try again." draft_backup: pm_title: "Backup Drafts from ongoing topics" pm_body: "Topic containing backup drafts" diff --git a/config/site_settings.yml b/config/site_settings.yml index 3196e8a10bd..034901389b5 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1118,6 +1118,9 @@ posting: max_draft_length: default: 400_000 hidden: true + max_drafts_per_user: + default: 10_000 + hidden: true max_form_template_title_length: default: 100 min: 5 diff --git a/spec/models/draft_spec.rb b/spec/models/draft_spec.rb index c584a9606c1..22b6b9b99a4 100644 --- a/spec/models/draft_spec.rb +++ b/spec/models/draft_spec.rb @@ -298,4 +298,6 @@ RSpec.describe Draft do expect(drafts[0].post.id).to eq(post.id) end end + + it { is_expected.to validate_length_of(:draft_key).is_at_most(25) } end diff --git a/spec/requests/drafts_controller_spec.rb b/spec/requests/drafts_controller_spec.rb index 1d88db0b4a5..e1629992fab 100644 --- a/spec/requests/drafts_controller_spec.rb +++ b/spec/requests/drafts_controller_spec.rb @@ -234,6 +234,45 @@ RSpec.describe DraftsController do end end end + + it "returns 403 when the maximum amount of drafts per users is reached" do + SiteSetting.max_drafts_per_user = 2 + + user1 = Fabricate(:user) + sign_in(user1) + + data = { my: "data" }.to_json + + # creating the first draft should work + post "/drafts.json", params: { draft_key: "TOPIC_1", data: data } + expect(response.status).to eq(200) + + # same draft key, so shouldn't count against the limit + post "/drafts.json", params: { draft_key: "TOPIC_1", data: data, sequence: 0 } + expect(response.status).to eq(200) + + # different draft key, so should count against the limit + post "/drafts.json", params: { draft_key: "TOPIC_2", data: data } + expect(response.status).to eq(200) + + # limit should be reached now + post "/drafts.json", params: { draft_key: "TOPIC_3", data: data } + expect(response.status).to eq(403) + + # updating existing draft should still work + post "/drafts.json", params: { draft_key: "TOPIC_1", data: data, sequence: 1 } + expect(response.status).to eq(200) + + # creating a new draft as a different user should still work + user2 = Fabricate(:user) + sign_in(user2) + post "/drafts.json", params: { draft_key: "TOPIC_3", data: data } + expect(response.status).to eq(200) + + # check the draft counts just to be safe + expect(Draft.where(user_id: user1.id).count).to eq(2) + expect(Draft.where(user_id: user2.id).count).to eq(1) + end end describe "#destroy" do