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