From 0f64ec00f4b516e235b3564548cde7ad801620a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BF=E3=81=A6=E3=82=8B=E3=81=9E?= Date: Sun, 25 Jan 2026 00:17:36 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8A=95=E7=A8=BF=E3=81=AB=E3=83=8B?= =?UTF-8?q?=E3=82=B3=E3=82=BF=E3=82=B0=E8=BF=BD=E5=8A=A0=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=A6=E3=81=97=E3=81=BE=E3=81=B5=E3=83=90=E3=82=B0=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=EF=BC=88#125=EF=BC=89=20(#236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #125 #125 Co-authored-by: miteruzo Reviewed-on: https://git.miteruzo.com/miteruzo/btrc-hub/pulls/236 --- backend/app/controllers/posts_controller.rb | 6 ++- backend/app/models/tag.rb | 16 ++++++-- backend/spec/requests/posts_spec.rb | 45 ++++++++++++++++++--- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index 648d42e..dde9f15 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -81,8 +81,6 @@ class PostsController < ApplicationController return head :unauthorized unless current_user return head :forbidden unless current_user.member? - # TODO: URL が正規のものがチェック,不正ならエラー - # TODO: URL は必須にする(タイトルは省略可). # TODO: サイトに応じて thumbnail_base 設定 title = params[:title].presence url = params[:url] @@ -105,6 +103,8 @@ class PostsController < ApplicationController else render json: { errors: post.errors.full_messages }, status: :unprocessable_entity end + rescue Tag::NicoTagNormalisationError + head :bad_request end def viewed @@ -142,6 +142,8 @@ class PostsController < ApplicationController else render json: post.errors, status: :unprocessable_entity end + rescue Tag::NicoTagNormalisationError + head :bad_request end def changes diff --git a/backend/app/models/tag.rb b/backend/app/models/tag.rb index 17546b5..491a0e3 100644 --- a/backend/app/models/tag.rb +++ b/backend/app/models/tag.rb @@ -1,4 +1,8 @@ class Tag < ApplicationRecord + class NicoTagNormalisationError < ArgumentError + ; + end + has_many :post_tags, dependent: :delete_all, inverse_of: :tag has_many :active_post_tags, -> { kept }, class_name: 'PostTag', inverse_of: :tag has_many :post_tags_with_discarded, -> { with_discarded }, class_name: 'PostTag' @@ -63,10 +67,14 @@ class Tag < ApplicationRecord @bot ||= find_or_create_by_tag_name!('bot操作', category: 'meta') end - def self.normalise_tags tag_names, with_tagme: true + def self.normalise_tags tag_names, with_tagme: true, deny_nico: true + if deny_nico && tag_names.any? { |n| n.start_with?('nico:') } + raise NicoTagNormalisationError + end + tags = tag_names.map do |name| pf, cat = CATEGORY_PREFIXES.find { |p, _| name.start_with?(p) } || ['', nil] - name.delete_prefix!(pf) + name = name.delete_prefix(pf) find_or_create_by_tag_name!(name, category: (cat || 'general')).tap do |tag| if cat && tag.category != cat tag.update!(category: cat) @@ -101,11 +109,13 @@ class Tag < ApplicationRecord (result + tags).uniq { |t| t.id } end - def self.find_or_create_by_tag_name!(name, category:) + def self.find_or_create_by_tag_name! name, category: tn = TagName.find_or_create_by!(name: name.to_s.strip) Tag.find_or_create_by!(tag_name_id: tn.id) do |t| t.category = category end + rescue ActiveRecord::RecordNotUnique + retry end private diff --git a/backend/spec/requests/posts_spec.rb b/backend/spec/requests/posts_spec.rb index eb7ce9f..07523b1 100644 --- a/backend/spec/requests/posts_spec.rb +++ b/backend/spec/requests/posts_spec.rb @@ -167,16 +167,34 @@ RSpec.describe 'Posts API', type: :request do expect(json['tags'][0]).to have_key('name') end + context "when nico tag already exists in tags" do + before do + Tag.find_or_create_by!(tag_name: TagName.find_or_create_by!(name: 'nico:nico_tag'), + category: 'nico') + end + + it 'return 400' do + sign_in_as(member) + + post '/posts', params: { + title: 'new post', + url: 'https://example.com/nico_tag', + tags: 'nico:nico_tag', + thumbnail: dummy_upload } + + expect(response).to have_http_status(:bad_request) + end + end + context 'when url is blank' do it 'returns 422' do sign_in_as(member) post '/posts', params: { - title: 'new post', - url: ' ', - tags: 'spec_tag', # 既存タグ名を投げる - thumbnail: dummy_upload - } + title: 'new post', + url: ' ', + tags: 'spec_tag', # 既存タグ名を投げる + thumbnail: dummy_upload } expect(response).to have_http_status(:unprocessable_entity) end @@ -233,6 +251,23 @@ RSpec.describe 'Posts API', type: :request do names = json['tags'].map { |n| n['name'] } expect(names).to include('spec_tag_2') end + + context "when nico tag already exists in tags" do + before do + Tag.find_or_create_by!(tag_name: TagName.find_or_create_by!(name: 'nico:nico_tag'), + category: 'nico') + end + + it 'return 400' do + sign_in_as(member) + + put "/posts/#{ post_record.id }", params: { + title: 'updated title', + tags: 'nico:nico_tag' } + + expect(response).to have_http_status(:bad_request) + end + end end describe 'GET /posts/random' do