From 877876b661dc4b81d1f75469dd78c4f74b273edb Mon Sep 17 00:00:00 2001 From: miteruzo Date: Tue, 30 Jun 2026 01:19:23 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=B5=E3=83=A0=E3=83=8D=E3=81=A4=E3=81=8D?= =?UTF-8?q?=E3=81=A7=E5=BA=83=E5=A0=B4=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=81=99?= =?UTF-8?q?=E3=82=8B=E9=9A=9B=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=A8?= =?UTF-8?q?=E3=81=AA=E3=82=8B=E5=95=8F=E9=A1=8C=E4=BF=AE=E6=AD=A3=20(#352)?= =?UTF-8?q?=20(#390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: http://git.miteruzo.com/miteruzo/btrc-hub/pulls/390 Co-authored-by: miteruzo Co-committed-by: miteruzo --- backend/app/controllers/posts_controller.rb | 7 +- backend/app/models/post.rb | 21 ++++-- backend/spec/requests/posts_spec.rb | 78 +++++++++++++++++++++ 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index 48cf816..26e358e 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -140,10 +140,11 @@ class PostsController < ApplicationController original_created_from = params[:original_created_from] original_created_before = params[:original_created_before] parent_post_ids = parse_parent_post_ids + resized_thumbnail = thumbnail.present? ? Post.resized_thumbnail_attachment(thumbnail) : nil post = Post.new(title:, url:, thumbnail_base: nil, uploaded_user: current_user, original_created_from:, original_created_before:) - post.thumbnail.attach(thumbnail) if thumbnail.present? + post.thumbnail.attach(resized_thumbnail) if resized_thumbnail ApplicationRecord.transaction do post.save! @@ -156,8 +157,6 @@ class PostsController < ApplicationController sync_parent_posts!(post, parent_post_ids) - post.resized_thumbnail! - PostVersionRecorder.record!(post:, event_type: :create, created_by_user: current_user) end @@ -167,6 +166,8 @@ class PostsController < ApplicationController render_validation_error fields: { tags: 'ニコニコ・タグは直接指定できません.' } rescue Tag::DeprecatedTagNormalisationError render_unprocessable_entity '廃止済みタグは付与できません.', field: :tags + rescue MiniMagick::Error + render_validation_error fields: { thumbnail: ['サムネイル画像の変換に失敗しました.'] } rescue ArgumentError => e render_validation_error fields: { parent_post_ids: [e.message] } rescue ActiveRecord::RecordInvalid => e diff --git a/backend/app/models/post.rb b/backend/app/models/post.rb index d354314..b2835b0 100644 --- a/backend/app/models/post.rb +++ b/backend/app/models/post.rb @@ -1,5 +1,19 @@ class Post < ApplicationRecord require 'mini_magick' + require 'stringio' + + def self.resized_thumbnail_attachment(upload) + upload.rewind + image = MiniMagick::Image.read(upload.read) + image.resize '180x180' + image.format 'jpg' + + { io: StringIO.new(image.to_blob), + filename: 'resized_thumbnail.jpg', + content_type: 'image/jpeg' } + ensure + upload.rewind + end belongs_to :uploaded_user, class_name: 'User', optional: true @@ -87,12 +101,7 @@ class Post < ApplicationRecord def resized_thumbnail! return unless thumbnail.attached? - image = MiniMagick::Image.read(thumbnail.download) - image.resize '180x180' - thumbnail.purge - thumbnail.attach(io: File.open(image.path), - filename: 'resized_thumbnail.jpg', - content_type: 'image/jpeg') + thumbnail.attach(self.class.resized_thumbnail_attachment(StringIO.new(thumbnail.download))) end private diff --git a/backend/spec/requests/posts_spec.rb b/backend/spec/requests/posts_spec.rb index 18d5b73..b640f1a 100644 --- a/backend/spec/requests/posts_spec.rb +++ b/backend/spec/requests/posts_spec.rb @@ -1,4 +1,5 @@ require 'rails_helper' +require 'base64' require 'set' include ActiveSupport::Testing::TimeHelpers @@ -8,6 +9,11 @@ RSpec.describe 'Posts API', type: :request do # resized_thumbnail! が MiniMagick 依存でコケやすいので request spec ではスタブしとくのが無難。 before do allow_any_instance_of(Post).to receive(:resized_thumbnail!).and_return(true) + allow(Post).to receive(:resized_thumbnail_attachment).and_return( + io: StringIO.new('dummy'), + filename: 'resized_thumbnail.jpg', + content_type: 'image/jpeg' + ) end def create_nico_tag!(name) @@ -19,6 +25,18 @@ RSpec.describe 'Posts API', type: :request do Rack::Test::UploadedFile.new(StringIO.new('dummy'), 'image/jpeg', original_filename: 'dummy.jpg') end + def real_thumbnail_upload + gif = + Base64.decode64( + 'R0lGODdhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=') + + Rack::Test::UploadedFile.new( + StringIO.new(gif), + 'image/gif', + original_filename: 'thumbnail.gif' + ) + end + def post_write_params params = { } { parent_post_ids: '' }.merge(params) end @@ -696,6 +714,66 @@ RSpec.describe 'Posts API', type: :request do expect(json['tags'][0]).to have_key('name') end + it '201 when posting manually with a thumbnail' do + sign_in_as(member) + allow(Post).to receive(:resized_thumbnail_attachment).and_call_original + + post '/posts', params: post_write_params( + title: 'thumbnail post', + url: 'https://example.com/thumbnail-post', + tags: 'spec_tag', + thumbnail: real_thumbnail_upload + ) + + expect(response).to have_http_status(:created) + post_record = Post.find(json.fetch('id')) + expect(post_record.thumbnail).to be_attached + expect(post_record.thumbnail.blob.content_type).to eq('image/jpeg') + expect { post_record.thumbnail.download }.not_to raise_error + end + + it 'resizes the thumbnail before the create transaction begins' do + sign_in_as(member) + + open_transactions = [] + baseline_open_transactions = Post.connection.open_transactions + allow(Post).to receive(:resized_thumbnail_attachment) do |_upload| + open_transactions << Post.connection.open_transactions + { io: StringIO.new('dummy'), + filename: 'resized_thumbnail.jpg', + content_type: 'image/jpeg' } + end + + post '/posts', params: post_write_params( + title: 'transaction post', + url: 'https://example.com/transaction-post', + tags: 'spec_tag', + thumbnail: dummy_upload + ) + + expect(response).to have_http_status(:created) + expect(open_transactions).to eq([baseline_open_transactions]) + end + + it 'returns 422 and does not create a post when thumbnail resize fails' do + sign_in_as(member) + allow(Post).to receive(:resized_thumbnail_attachment).and_raise(MiniMagick::Error) + + expect { + post '/posts', params: post_write_params( + title: 'broken thumbnail post', + url: 'https://example.com/broken-thumbnail-post', + tags: 'spec_tag', + thumbnail: dummy_upload + ) + }.not_to change(Post, :count) + + expect(response).to have_http_status(:unprocessable_entity) + expect(json.fetch('errors')).to include( + 'thumbnail' => ['サムネイル画像の変換に失敗しました.'] + ) + end + it '201 and creates post + tags when member and tags have aliases' do sign_in_as(member)