uploads: add limit to prevent users from submitting too many uploads at once.

Add a limit so that users can't upload more if they already have more
than 250 images queued for upload.

For example, if you upload a Pixiv post that has 200 images, then you'll
have 200 queued images for upload. This will go down as the images are
processed. If you exceed the limit, then trying to create new uploads
will return an error.

This is to prevent single users from overwhelming the site by uploading
too many images at once, thereby preventing other users from uploading
because the job queue is backed up and can't process new uploads by
other users until existing uploads are finished.
This commit is contained in:
evazion
2022-02-28 22:19:40 -06:00
parent 7031fd13d7
commit 03560bafc6
3 changed files with 18 additions and 1 deletions

View File

@@ -6,6 +6,9 @@ class Upload < ApplicationRecord
MAX_FILES_PER_UPLOAD = 100
# The maximum number of 'pending' or 'processing' media assets a single user can have at once.
MAX_QUEUED_ASSETS = 250
attr_accessor :files
belongs_to :uploader, class_name: "User"
@@ -19,6 +22,7 @@ class Upload < ApplicationRecord
validates :source, format: { with: %r{\Ahttps?://}i, message: "is not a valid URL" }, if: -> { source.present? }
validates :referer_url, format: { with: %r{\Ahttps?://}i, message: "is not a valid URL" }, if: -> { referer_url.present? }
validate :validate_file_and_source, on: :create
validate :uploader_is_not_limited, on: :create
after_create :async_process_upload!
@@ -64,6 +68,14 @@ class Upload < ApplicationRecord
errors.add(:base, "No file or source given")
end
end
def uploader_is_not_limited
queued_asset_count = uploader.upload_media_assets.unfinished.count
if queued_asset_count > MAX_QUEUED_ASSETS
errors.add(:base, "You have too many images queued for upload (queued: #{queued_asset_count}; limit: #{MAX_QUEUED_ASSETS}). Try again later.")
end
end
end
concerning :SourceMethods do

View File

@@ -23,6 +23,9 @@ class UploadMediaAsset < ApplicationRecord
failed: 300,
}
scope :unfinished, -> { where(status: %w[pending processing]) }
scope :finished, -> { where(status: %w[active failed]) }
def self.visible(user)
if user.is_admin?
all

View File

@@ -109,6 +109,7 @@ class User < ApplicationRecord
validate :validate_custom_css, if: :custom_style_changed?
before_validation :normalize_blacklisted_tags
before_create :promote_to_owner_if_first_user
has_many :artist_versions, foreign_key: :updater_id
has_many :artist_commentary_versions, foreign_key: :updater_id
has_many :comments, foreign_key: :creator_id
@@ -131,7 +132,6 @@ class User < ApplicationRecord
has_many :purchased_upgrades, class_name: "UserUpgrade", foreign_key: :purchaser_id, dependent: :destroy
has_many :user_events, dependent: :destroy
has_one :active_ban, -> { active }, class_name: "Ban"
has_one :email_address, dependent: :destroy
has_many :api_keys, dependent: :destroy
has_many :note_versions, :foreign_key => "updater_id"
@@ -145,6 +145,8 @@ class User < ApplicationRecord
has_many :ip_bans, foreign_key: :creator_id
has_many :tag_aliases, foreign_key: :creator_id
has_many :tag_implications, foreign_key: :creator_id
has_many :uploads, foreign_key: :uploader_id, dependent: :destroy
has_many :upload_media_assets, through: :uploads, dependent: :destroy
belongs_to :inviter, class_name: "User", optional: true
accepts_nested_attributes_for :email_address, reject_if: :all_blank, allow_destroy: true