diff --git a/.gitignore b/.gitignore index cfa756a62..eb0227188 100644 --- a/.gitignore +++ b/.gitignore @@ -69,4 +69,8 @@ CLAUDE.md /app/assets/builds/* !/app/assets/builds/.keep .cursorindexingignore -.specstory \ No newline at end of file +.specstory + +# Local agent/scratch notes, not part of the repo +/docs/ +/style-guide/ \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 07a651bf5..4c9a8ca49 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -180,6 +180,7 @@ GEM multipart-post (~> 2.0) faraday-net_http (3.4.4) net-http (~> 0.5) + ffi (1.17.4) ffi (1.17.4-aarch64-linux-gnu) ffi (1.17.4-aarch64-linux-musl) ffi (1.17.4-arm-linux-gnu) @@ -305,6 +306,7 @@ GEM nokogiri (~> 1) rake mini_mime (1.1.5) + mini_portile2 (2.8.9) minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) @@ -326,6 +328,9 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) + nokogiri (1.19.4) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) nokogiri (1.19.4-aarch64-linux-gnu) racc (~> 1.4) nokogiri (1.19.4-aarch64-linux-musl) @@ -518,7 +523,10 @@ GEM faraday (>= 1) faraday-multipart (>= 1) ruby-progressbar (1.13.0) - rubyzip (3.1.0) + rubyzip (3.6.0) + sass-embedded (1.93.2) + google-protobuf (~> 4.31) + rake (>= 13) sass-embedded (1.93.2-aarch64-linux-gnu) google-protobuf (~> 4.31) sass-embedded (1.93.2-aarch64-linux-musl) @@ -620,6 +628,7 @@ PLATFORMS arm-linux-gnu arm-linux-musl arm64-darwin + ruby x86_64-darwin x86_64-linux-gnu x86_64-linux-musl diff --git a/app/controllers/auctions_controller.rb b/app/controllers/auctions_controller.rb index 519da0e98..c6c10a5d1 100644 --- a/app/controllers/auctions_controller.rb +++ b/app/controllers/auctions_controller.rb @@ -4,6 +4,11 @@ class AuctionsController < ApplicationController DEFAULT_PAGE_LIMIT = 15 + # Hard ceiling for one rendered page. A single auction row costs ~270KB of transient + # objects to render, so an unbounded page size (show_all, or a hand-crafted per_page) + # lets one request blow the container memory limit and take the process down. + MAX_PAGE_LIMIT = 100 + # GET /auctions def index set_cors_header @@ -37,12 +42,13 @@ def cors_preflight_check def fetch_auctions_list = Auction.active.search(params, current_user) def per_page_count - count = params[:show_all] == 'true' ? @auctions_list.count : per_page - count = nil if count.zero? - count - end + return MAX_PAGE_LIMIT if params[:show_all] == 'true' - def per_page = params[:per_page] || DEFAULT_PAGE_LIMIT + requested = params[:per_page].to_i + return DEFAULT_PAGE_LIMIT unless requested.positive? + + [requested, MAX_PAGE_LIMIT].min + end def set_cors_header response.headers['Access-Control-Allow-Origin'] = request.headers['Origin'] diff --git a/test/controllers/auctions_controller_test.rb b/test/controllers/auctions_controller_test.rb index 20effe5e9..3ffab90df 100644 --- a/test/controllers/auctions_controller_test.rb +++ b/test/controllers/auctions_controller_test.rb @@ -165,6 +165,29 @@ def test_show_all_with_sorting_returns_every_auction_in_sort_order assert_equal expected_domains, auction_domains end + def test_page_size_is_capped_regardless_of_requested_size + create_extra_active_auctions(AuctionsController::MAX_PAGE_LIMIT + 5) + + get auctions_path, params: { show_all: 'true', sort_by: 'domain_name', sort_direction: 'asc' } + + assert_response :success + assert_select 'tbody#bids tr.contents', AuctionsController::MAX_PAGE_LIMIT + + get auctions_path, params: { per_page: '5000' } + + assert_response :success + assert_select 'tbody#bids tr.contents', AuctionsController::MAX_PAGE_LIMIT + end + + def test_non_numeric_per_page_falls_back_to_the_default_page_size + create_extra_active_auctions(20) + + get auctions_path, params: { per_page: 'all' } + + assert_response :success + assert_select 'tbody#bids tr.contents', AuctionsController::DEFAULT_PAGE_LIMIT + end + private def query_from_link(title)