feature/125 into main 2 weeks ago
| @@ -81,8 +81,6 @@ class PostsController < ApplicationController | |||||
| return head :unauthorized unless current_user | return head :unauthorized unless current_user | ||||
| return head :forbidden unless current_user.member? | return head :forbidden unless current_user.member? | ||||
| # TODO: URL が正規のものがチェック,不正ならエラー | |||||
| # TODO: URL は必須にする(タイトルは省略可). | |||||
| # TODO: サイトに応じて thumbnail_base 設定 | # TODO: サイトに応じて thumbnail_base 設定 | ||||
| title = params[:title].presence | title = params[:title].presence | ||||
| url = params[:url] | url = params[:url] | ||||
| @@ -105,6 +103,8 @@ class PostsController < ApplicationController | |||||
| else | else | ||||
| render json: { errors: post.errors.full_messages }, status: :unprocessable_entity | render json: { errors: post.errors.full_messages }, status: :unprocessable_entity | ||||
| end | end | ||||
| rescue Tag::NicoTagNormalisationError | |||||
| head :bad_request | |||||
| end | end | ||||
| def viewed | def viewed | ||||
| @@ -142,6 +142,8 @@ class PostsController < ApplicationController | |||||
| else | else | ||||
| render json: post.errors, status: :unprocessable_entity | render json: post.errors, status: :unprocessable_entity | ||||
| end | end | ||||
| rescue Tag::NicoTagNormalisationError | |||||
| head :bad_request | |||||
| end | end | ||||
| def changes | def changes | ||||
| @@ -1,4 +1,8 @@ | |||||
| class Tag < ApplicationRecord | class Tag < ApplicationRecord | ||||
| class NicoTagNormalisationError < ArgumentError | |||||
| ; | |||||
| end | |||||
| has_many :post_tags, dependent: :delete_all, inverse_of: :tag | has_many :post_tags, dependent: :delete_all, inverse_of: :tag | ||||
| has_many :active_post_tags, -> { kept }, class_name: 'PostTag', 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' | 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') | @bot ||= find_or_create_by_tag_name!('bot操作', category: 'meta') | ||||
| end | 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| | tags = tag_names.map do |name| | ||||
| pf, cat = CATEGORY_PREFIXES.find { |p, _| name.start_with?(p) } || ['', nil] | 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| | find_or_create_by_tag_name!(name, category: (cat || 'general')).tap do |tag| | ||||
| if cat && tag.category != cat | if cat && tag.category != cat | ||||
| tag.update!(category: cat) | tag.update!(category: cat) | ||||
| @@ -101,11 +109,13 @@ class Tag < ApplicationRecord | |||||
| (result + tags).uniq { |t| t.id } | (result + tags).uniq { |t| t.id } | ||||
| end | 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) | tn = TagName.find_or_create_by!(name: name.to_s.strip) | ||||
| Tag.find_or_create_by!(tag_name_id: tn.id) do |t| | Tag.find_or_create_by!(tag_name_id: tn.id) do |t| | ||||
| t.category = category | t.category = category | ||||
| end | end | ||||
| rescue ActiveRecord::RecordNotUnique | |||||
| retry | |||||
| end | end | ||||
| private | private | ||||
| @@ -167,6 +167,25 @@ RSpec.describe 'Posts API', type: :request do | |||||
| expect(json['tags'][0]).to have_key('name') | expect(json['tags'][0]).to have_key('name') | ||||
| end | 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 | context 'when url is blank' do | ||||
| it 'returns 422' do | it 'returns 422' do | ||||
| sign_in_as(member) | sign_in_as(member) | ||||
| @@ -175,8 +194,7 @@ RSpec.describe 'Posts API', type: :request do | |||||
| title: 'new post', | title: 'new post', | ||||
| url: ' ', | url: ' ', | ||||
| tags: 'spec_tag', # 既存タグ名を投げる | tags: 'spec_tag', # 既存タグ名を投げる | ||||
| thumbnail: dummy_upload | |||||
| } | |||||
| thumbnail: dummy_upload } | |||||
| expect(response).to have_http_status(:unprocessable_entity) | expect(response).to have_http_status(:unprocessable_entity) | ||||
| end | end | ||||
| @@ -233,6 +251,23 @@ RSpec.describe 'Posts API', type: :request do | |||||
| names = json['tags'].map { |n| n['name'] } | names = json['tags'].map { |n| n['name'] } | ||||
| expect(names).to include('spec_tag_2') | expect(names).to include('spec_tag_2') | ||||
| end | 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 | end | ||||
| describe 'GET /posts/random' do | describe 'GET /posts/random' do | ||||