From 7585f51e01e6fb9c6f5c254eab05bf14e3c8b9a1 Mon Sep 17 00:00:00 2001 From: Codex Night Worker Date: Thu, 12 Nov 2026 23:28:48 +0900 Subject: [PATCH 1/2] AI: address issue #438 --- backend/app/controllers/material_versions_controller.rb | 1 + backend/app/controllers/materials_controller.rb | 1 + backend/app/controllers/nico_tags_controller.rb | 1 + backend/app/controllers/post_versions_controller.rb | 1 + backend/app/controllers/posts_controller.rb | 1 + backend/app/controllers/tag_versions_controller.rb | 1 + backend/app/controllers/tags_controller.rb | 1 + backend/app/controllers/theatre_comments_controller.rb | 2 ++ backend/app/controllers/theatre_programmes_controller.rb | 3 ++- backend/app/controllers/theatre_skip_events_controller.rb | 1 + 10 files changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/app/controllers/material_versions_controller.rb b/backend/app/controllers/material_versions_controller.rb index 9ee5ff1..5f68b48 100644 --- a/backend/app/controllers/material_versions_controller.rb +++ b/backend/app/controllers/material_versions_controller.rb @@ -5,6 +5,7 @@ class MaterialVersionsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 offset = (page - 1) * limit diff --git a/backend/app/controllers/materials_controller.rb b/backend/app/controllers/materials_controller.rb index 93b2fbe..46df777 100644 --- a/backend/app/controllers/materials_controller.rb +++ b/backend/app/controllers/materials_controller.rb @@ -14,6 +14,7 @@ class MaterialsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 offset = (page - 1) * limit diff --git a/backend/app/controllers/nico_tags_controller.rb b/backend/app/controllers/nico_tags_controller.rb index 902358d..08a3877 100644 --- a/backend/app/controllers/nico_tags_controller.rb +++ b/backend/app/controllers/nico_tags_controller.rb @@ -15,6 +15,7 @@ class NicoTagsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 post_tag_max_sql = PostExternalTag diff --git a/backend/app/controllers/post_versions_controller.rb b/backend/app/controllers/post_versions_controller.rb index cf07119..9a3b641 100644 --- a/backend/app/controllers/post_versions_controller.rb +++ b/backend/app/controllers/post_versions_controller.rb @@ -11,6 +11,7 @@ class PostVersionsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 offset = (page - 1) * limit diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index ba15e99..34c71b0 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -34,6 +34,7 @@ class PostsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 offset = (page - 1) * limit diff --git a/backend/app/controllers/tag_versions_controller.rb b/backend/app/controllers/tag_versions_controller.rb index 95c71d0..4668cd9 100644 --- a/backend/app/controllers/tag_versions_controller.rb +++ b/backend/app/controllers/tag_versions_controller.rb @@ -7,6 +7,7 @@ class TagVersionsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 offset = (page - 1) * limit diff --git a/backend/app/controllers/tags_controller.rb b/backend/app/controllers/tags_controller.rb index 7c6feac..3162f14 100644 --- a/backend/app/controllers/tags_controller.rb +++ b/backend/app/controllers/tags_controller.rb @@ -33,6 +33,7 @@ class TagsController < ApplicationController page = 1 if page < 1 limit = 1 if limit < 1 + limit = 100 if limit > 100 offset = (page - 1) * limit diff --git a/backend/app/controllers/theatre_comments_controller.rb b/backend/app/controllers/theatre_comments_controller.rb index 599a57e..4342983 100644 --- a/backend/app/controllers/theatre_comments_controller.rb +++ b/backend/app/controllers/theatre_comments_controller.rb @@ -2,6 +2,7 @@ class TheatreCommentsController < ApplicationController def index limit = params[:limit].to_i limit = 20 if limit <= 0 + limit = 100 if limit > 100 no_gt = params[:no_gt].to_i no_gt = 0 if no_gt < 0 @@ -9,6 +10,7 @@ class TheatreCommentsController < ApplicationController comments = TheatreComment .where(theatre_id: params[:theatre_id]) .where('no > ?', no_gt) + .includes(:user) .order(no: :desc) .limit(limit) diff --git a/backend/app/controllers/theatre_programmes_controller.rb b/backend/app/controllers/theatre_programmes_controller.rb index b386f63..bf6c917 100644 --- a/backend/app/controllers/theatre_programmes_controller.rb +++ b/backend/app/controllers/theatre_programmes_controller.rb @@ -2,6 +2,7 @@ class TheatreProgrammesController < ApplicationController def index limit = params[:limit].to_i limit = 100 if limit <= 0 + limit = 100 if limit > 100 position_gt = params[:position_gt].to_i position_gt = 0 if position_gt < 0 @@ -10,7 +11,7 @@ class TheatreProgrammesController < ApplicationController .where(theatre_id: params[:theatre_id]) .where('position > ?', position_gt) .includes(:post) - .order(position: :desc).limit(100) + .order(position: :desc) .limit(limit) render json: programmes.map { |programme| diff --git a/backend/app/controllers/theatre_skip_events_controller.rb b/backend/app/controllers/theatre_skip_events_controller.rb index c5047c5..4cb05de 100644 --- a/backend/app/controllers/theatre_skip_events_controller.rb +++ b/backend/app/controllers/theatre_skip_events_controller.rb @@ -2,6 +2,7 @@ class TheatreSkipEventsController < ApplicationController def index limit = params[:limit].to_i limit = 50 if limit <= 0 + limit = 100 if limit > 100 events = TheatreSkipEvent -- 2.53.0 From d830ac0311bee1450e953a22cb5bde7cb9e27420 Mon Sep 17 00:00:00 2001 From: Codex Night Worker Date: Thu, 12 Nov 2026 23:33:15 +0900 Subject: [PATCH 2/2] AI: address review on PR #466 --- backend/spec/requests/list_limits_spec.rb | 174 ++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 backend/spec/requests/list_limits_spec.rb diff --git a/backend/spec/requests/list_limits_spec.rb b/backend/spec/requests/list_limits_spec.rb new file mode 100644 index 0000000..ec0a0ef --- /dev/null +++ b/backend/spec/requests/list_limits_spec.rb @@ -0,0 +1,174 @@ +require 'rails_helper' + +RSpec.describe 'List API limits', type: :request do + shared_examples 'a capped list' do |default_count, minimum_count, paginated| + def response_rows + body = json + body.is_a?(Array) ? body : body.fetch(collection_key) + end + + it 'caps oversized limits at 100 and preserves the 100 boundary' do + get path, params: { locale: 'ja', limit: 100 } + expect(response).to have_http_status(:ok) + expect(response_rows.size).to eq(100) + boundary_rows = response_rows + + [101, 10_000].each do |limit| + get path, params: { locale: 'ja', limit: } + expect(response).to have_http_status(:ok) + expect(response_rows).to eq(boundary_rows) + end + end + + it 'preserves the default, nonpositive and small positive limits' do + get path, params: { locale: 'ja' } + expect(response).to have_http_status(:ok) + expect(response_rows.size).to eq(default_count) + + [0, -1].each do |limit| + get path, params: { locale: 'ja', limit: } + expect(response).to have_http_status(:ok) + expect(response_rows.size).to eq(minimum_count) + end + + get path, params: { locale: 'ja', limit: 2 } + expect(response).to have_http_status(:ok) + expect(response_rows.size).to eq(2) + end + + if paginated + it 'uses the capped limit for page offsets and preserves total count' do + get path, params: { locale: 'ja', limit: 100 } + first_page = response_rows + expect(response).to have_http_status(:ok) + expect(json.fetch('count')).to eq(101) + + get path, params: { locale: 'ja', limit: 100, page: 2 } + expect(response).to have_http_status(:ok) + expect(response_rows.size).to eq(1) + second_page = response_rows + expect(first_page & second_page).to be_empty + + get path, params: { locale: 'ja', limit: 101, page: 2 } + expect(response).to have_http_status(:ok) + expect(response_rows).to eq(second_page) + expect(json.fetch('count')).to eq(101) + end + end + end + + context 'posts' do + let(:path) { '/posts' } + let(:collection_key) { 'posts' } + before { create_list(:post, 101) } + + include_examples 'a capped list', 20, 1, true + end + + context 'tags' do + let(:path) { '/tags' } + let(:collection_key) { 'tags' } + before { create_list(:tag, 101) } + + include_examples 'a capped list', 20, 1, true + end + + context 'nico tags' do + let(:path) { '/tags/nico' } + let(:collection_key) { 'tags' } + before { create_list(:external_tag, 101) } + + include_examples 'a capped list', 20, 1, true + end + + context 'materials' do + let(:path) { '/materials' } + let(:collection_key) { 'materials' } + before do + 101.times { |index| Material.create!(url: "https://example.com/material-#{ index }") } + end + + include_examples 'a capped list', 20, 1, true + end + + context 'post versions' do + let(:path) { '/posts/versions' } + let(:collection_key) { 'versions' } + let(:post) { create(:post) } + before do + 101.times do |index| + PostVersion.create!(post:, version_no: index + 1, event_type: 'update', + url: post.url, tags: '', tags_json: [], parent_post_ids: '') + end + end + + include_examples 'a capped list', 20, 1, true + end + + context 'tag versions' do + let(:path) { '/tags/versions' } + let(:collection_key) { 'versions' } + let(:tag) { create(:tag) } + before do + 101.times do |index| + TagVersion.create!(tag:, version_no: index + 1, event_type: 'update', + name: 'limit tag', category: 'general', aliases: '', + parent_tag_ids: '', names: { + ja: { name: 'limit tag', script_code: 'Jpan', + auto_generated: false, aliases: [] } }) + end + end + + include_examples 'a capped list', 20, 1, true + end + + context 'material versions' do + let(:path) { '/materials/versions' } + let(:collection_key) { 'versions' } + let(:material) { Material.create!(url: 'https://example.com/material') } + before do + 101.times do |index| + MaterialVersion.create!(material:, version_no: index + 1, + event_type: 'update', url: material.url) + end + end + + include_examples 'a capped list', 20, 1, true + end + + context 'theatre lists' do + let(:theatre) { create(:theatre) } + let(:post) { create(:post) } + + context 'comments' do + let(:path) { "/theatres/#{ theatre.id }/comments" } + before { create_list(:theatre_comment, 101, theatre:, user: theatre.created_by_user) } + + include_examples 'a capped list', 20, 20, false + end + + context 'programmes' do + let(:path) { "/theatres/#{ theatre.id }/programmes" } + before do + 101.times do |index| + TheatreProgramme.create!(theatre:, post:, position: index + 1) + end + end + + include_examples 'a capped list', 100, 100, false + end + + context 'skip events' do + let(:path) { "/theatres/#{ theatre.id }/skip_events" } + before do + 101.times do |index| + TheatreSkipEvent.create!(theatre:, post:, + skipped_by_user: theatre.created_by_user, + created_at: Time.utc(2026, 1, 1) + index.seconds) + end + end + + include_examples 'a capped list', 50, 50, false + end + end +end -- 2.53.0