diff --git a/backend/app/controllers/tags_controller.rb b/backend/app/controllers/tags_controller.rb index 8613bd0..c0d54d1 100644 --- a/backend/app/controllers/tags_controller.rb +++ b/backend/app/controllers/tags_controller.rb @@ -249,7 +249,10 @@ class TagsController < ApplicationController update_parent_tags!(tag, parent_names) tag.reload - record_tag_version!(tag, event_type: :update, created_by_user: current_user) + record_tag_version!(tag, + event_type: :update, + created_by_user: current_user, + name_changed: name.present? && name != old_name) end render json: TagRepr.base(tag.reload) @@ -272,10 +275,19 @@ class TagsController < ApplicationController ApplicationRecord.transaction do TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) + old_name = tag.name + name_changed = name.present? && name != old_name + tag.tag_name.update!(name:) if name.present? tag.update!(category:) if category.present? - record_tag_version!(tag, event_type: :update, created_by_user: current_user) + tag.reload + + record_tag_version!( + tag, + event_type: :update, + created_by_user: current_user, + name_changed:) end render json: TagRepr.base(tag.reload) @@ -297,16 +309,20 @@ class TagsController < ApplicationController material: material.as_json&.merge(file:, content_type:)) end - def record_tag_version! tag, event_type:, created_by_user: + def record_tag_version! tag, event_type:, created_by_user:, name_changed: false if tag.nico? NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:) - else - TagVersionRecorder.record!(tag:, event_type:, created_by_user:) - wiki_page = tag.tag_name.wiki_page - if wiki_page&.wiki_versions&.exists? - WikiVersionRecorder.record!(page: wiki_page, event_type: :update, created_by_user:) - end + return end + + TagVersionRecorder.record!(tag:, event_type:, created_by_user:) + + return unless name_changed + + wiki_page = tag.tag_name.wiki_page + return unless wiki_page&.wiki_versions&.exists? + + WikiVersionRecorder.record!(page: wiki_page, event_type: :update, created_by_user:) end def update_aliases! tag, alias_names diff --git a/backend/app/controllers/wiki_pages_controller.rb b/backend/app/controllers/wiki_pages_controller.rb index 60941b8..de42c52 100644 --- a/backend/app/controllers/wiki_pages_controller.rb +++ b/backend/app/controllers/wiki_pages_controller.rb @@ -85,22 +85,24 @@ class WikiPagesController < ApplicationController return head :unauthorized unless current_user return head :forbidden unless current_user.gte_member? - name = params[:title]&.strip + title = params[:title].to_s.strip body = params[:body].to_s + message = params[:message].presence - return head :unprocessable_entity if name.blank? || body.blank? + return head :unprocessable_entity if title.blank? || body.blank? - tag_name = TagName.find_undiscard_or_create_by!(name:) - page = WikiPage.new(tag_name:, body:, created_user: current_user, updated_user: current_user) - if page.save - message = params[:message].presence - Wiki::Commit.content!(page:, body:, created_user: current_user, message:) + tag_name = TagName.find_undiscard_or_create_by!(name: title) - render json: WikiPageRepr.base(page), status: :created - else - render json: { errors: page.errors.full_messages }, - status: :unprocessable_entity - end + page = + Wiki::Commit.create_content!( + tag_name:, + body:, + created_by_user: current_user, + message:) + + render json: WikiPageRepr.base(page), status: :created + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique + head :unprocessable_entity end def update @@ -113,28 +115,32 @@ class WikiPagesController < ApplicationController return head :unprocessable_entity if title.blank? || body.blank? page = WikiPage.find(params[:id]) - base_revision_id = page.current_revision.id + base_revision_id = params[:base_revision_id].presence - old_title = page.title + ApplicationRecord.transaction do + page.lock! - tag = Tag.find_by(tag_name_id: page.tag_name_id) + old_title = page.title - if tag && title != old_title - TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) - end + tag = Tag.find_by(tag_name_id: page.tag_name_id) - page.tag_name.update!(name: title) if title != old_title + if tag && title != old_title + TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) + end - message = params[:message].presence - Wiki::Commit.content!(page:, - body:, - created_user: current_user, - message:, - base_revision_id:) - - if tag && title != old_title - tag.reload - TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user) + page.tag_name.update!(name: title) if title != old_title + + message = params[:message].presence + Wiki::Commit.content!(page:, + body:, + created_user: current_user, + message:, + base_revision_id:) + + if tag && title != old_title + tag.reload + TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user) + end end head :ok diff --git a/backend/app/services/wiki/commit.rb b/backend/app/services/wiki/commit.rb index dde51fd..bd6fb7b 100644 --- a/backend/app/services/wiki/commit.rb +++ b/backend/app/services/wiki/commit.rb @@ -117,18 +117,21 @@ module Wiki id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h - missing_rows = [] + missing_by_sha = { } + line_shas.each_with_index do |sha, i| next if id_by_sha.key?(sha) + next if missing_by_sha.key?(sha) - missing_rows << { sha256: sha, - body: lines[i], - created_at: now, - updated_at: now } + missing_by_sha[sha] = { + sha256: sha, + body: lines[i], + created_at: now, + updated_at: now } end - if missing_rows.any? - WikiLine.upsert_all(missing_rows) + if missing_by_sha.any? + WikiLine.upsert_all(missing_by_sha.values) id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h end diff --git a/backend/app/services/wiki_version_recorder.rb b/backend/app/services/wiki_version_recorder.rb index f6aa09e..41e1db9 100644 --- a/backend/app/services/wiki_version_recorder.rb +++ b/backend/app/services/wiki_version_recorder.rb @@ -1,6 +1,6 @@ class WikiVersionRecorder < VersionRecorder def self.record! page:, event_type:, reason: nil, created_by_user: - new(page:, event_type:, created_by_user:).record! + new(page:, event_type:, reason:, created_by_user:).record! end def initialize page:, event_type:, reason: nil, created_by_user: diff --git a/backend/spec/requests/tags_spec.rb b/backend/spec/requests/tags_spec.rb index fa442fe..864cdae 100644 --- a/backend/spec/requests/tags_spec.rb +++ b/backend/spec/requests/tags_spec.rb @@ -471,54 +471,54 @@ RSpec.describe 'Tags API', type: :request do expect(response).to have_http_status(:unprocessable_entity) expect(nico_tag.reload.category).to eq('nico') end - end - it 'PATCH で tag の name を変更すると対応する wiki version を作成する' do - wiki_page = - Wiki::Commit.create_content!( - tag_name: tag.tag_name, - body: 'wiki body before', - created_by_user: member_user, - message: 'init') - - expect { - patch "/tags/#{ tag.id }", params: { - name: 'patch_wiki_renamed_tag', + it 'PATCH で tag の name を変更すると対応する wiki version を作成する' do + wiki_page = + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body: 'wiki body before', + created_by_user: member_user, + message: 'init') + + expect { + patch "/tags/#{ tag.id }", params: { + name: 'patch_wiki_renamed_tag', + } } - } - .to change(TagVersion, :count).by(2) - .and change(WikiVersion, :count).by(1) + .to change(TagVersion, :count).by(2) + .and change(WikiVersion, :count).by(1) - expect(response).to have_http_status(:ok) + expect(response).to have_http_status(:ok) - version = wiki_page.reload.wiki_versions.order(:version_no).last + version = wiki_page.reload.wiki_versions.order(:version_no).last - expect(version).to have_attributes( - event_type: 'update', - title: 'patch_wiki_renamed_tag', - body: 'wiki body before', - created_by_user_id: member_user.id - ) - end + expect(version).to have_attributes( + event_type: 'update', + title: 'patch_wiki_renamed_tag', + body: 'wiki body before', + created_by_user_id: member_user.id + ) + end - it 'tag の category だけを変更しても wiki version は作成しない' do - wiki_page = - Wiki::Commit.create_content!( - tag_name: tag.tag_name, - body: 'wiki body before', - created_by_user: member_user, - message: 'init') + it 'tag の category だけを変更しても wiki version は作成しない' do + wiki_page = + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body: 'wiki body before', + created_by_user: member_user, + message: 'init') - before_count = wiki_page.reload.wiki_versions.count + before_wiki_version_count = wiki_page.reload.wiki_versions.count - expect { - patch "/tags/#{ tag.id }", params: { - category: 'meme', - } - }.to change(TagVersion, :count).by(2) + expect { + patch "/tags/#{ tag.id }", params: { + category: 'meme', + } + }.to change(TagVersion, :count).by(2) - expect(response).to have_http_status(:ok) - expect(wiki_page.reload.wiki_versions.count).to eq(before_count) + expect(response).to have_http_status(:ok) + expect(wiki_page.reload.wiki_versions.count).to eq(before_wiki_version_count) + end end end @@ -1023,37 +1023,37 @@ RSpec.describe 'Tags API', type: :request do expect(TagImplication.where(tag:, parent_tag: child)).not_to exist end - end - it 'tag の name を変更すると対応する wiki version を作成する' do - wiki_page = - Wiki::Commit.create_content!( - tag_name: tag.tag_name, - body: 'wiki body before', - created_by_user: member_user, - message: 'init') + it 'tag の name を変更すると対応する wiki version を作成する' do + wiki_page = + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body: 'wiki body before', + created_by_user: member_user, + message: 'init') - expect { - put "/tags/#{ tag.id }", params: { - name: 'put_wiki_renamed_tag', - category: 'general', - aliases: 'unko', - parent_tags: '', + expect { + put "/tags/#{ tag.id }", params: { + name: 'put_wiki_renamed_tag', + category: 'general', + aliases: 'unko', + parent_tags: '', + } } - } - .to change(TagVersion, :count).by(2) - .and change(WikiVersion, :count).by(1) + .to change(TagVersion, :count).by(2) + .and change(WikiVersion, :count).by(1) - expect(response).to have_http_status(:ok) + expect(response).to have_http_status(:ok) - version = wiki_page.reload.wiki_versions.order(:version_no).last + version = wiki_page.reload.wiki_versions.order(:version_no).last - expect(version).to have_attributes( - event_type: 'update', - title: 'put_wiki_renamed_tag', - body: 'wiki body before', - created_by_user_id: member_user.id - ) + expect(version).to have_attributes( + event_type: 'update', + title: 'put_wiki_renamed_tag', + body: 'wiki body before', + created_by_user_id: member_user.id + ) + end end end end diff --git a/backend/spec/requests/wiki_spec.rb b/backend/spec/requests/wiki_spec.rb index d6cf88f..9b6e8c1 100644 --- a/backend/spec/requests/wiki_spec.rb +++ b/backend/spec/requests/wiki_spec.rb @@ -301,23 +301,22 @@ RSpec.describe 'Wiki API', type: :request do end end - # TODO: コンフリクト未実装のため,実装したらコメント外す. - # context 'when conflict' do - # it 'returns 409 when base_revision_id mismatches' do - # # 先に別ユーザ(同じ member でもOK)が 1 回更新して先頭を進める - # Wiki::Commit.content!(page: page, body: "zzz", created_user: member, message: 'other edit') - # page.reload - - # stale_id = page.wiki_revisions.order(:id).first.id # わざと古い id - # put "/wiki/#{page.id}", - # params: { title: 'TestPage', body: 'x', base_revision_id: stale_id }, - # headers: auth_headers(member) - - # expect(response).to have_http_status(:conflict) - # json = JSON.parse(response.body) - # expect(json['error']).to eq('conflict') - # end - # end + context 'when conflict' do + it 'returns 409 when base_revision_id mismatches' do + # 先に別ユーザ(同じ member でもOK)が 1 回更新して先頭を進める + Wiki::Commit.content!(page: page, body: "zzz", created_user: member, message: 'other edit') + page.reload + + stale_id = page.wiki_revisions.order(:id).first.id # わざと古い id + put "/wiki/#{page.id}", + params: { title: 'TestPage', body: 'x', base_revision_id: stale_id }, + headers: auth_headers(member) + + expect(response).to have_http_status(:conflict) + json = JSON.parse(response.body) + expect(json['error']).to eq('conflict') + end + end context 'when page not found' do it 'returns 404' do @@ -520,76 +519,6 @@ RSpec.describe 'Wiki API', type: :request do end end - it 'wiki title を変更すると対応する tag の version を作成する' do - linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') - linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) - - linked_page = - Wiki::Commit.create_content!( - tag_name: linked_tag_name, - body: 'before', - created_by_user: user, - message: 'init') - - current_id = linked_page.current_revision.id - - expect { - put "/wiki/#{ linked_page.id }", - params: { - title: 'wiki_linked_tag_for_version_renamed', - body: 'after', - message: 'edit', - base_revision_id: current_id, - }, - headers: auth_headers(member) - } - .to change(WikiRevision, :count).by(1) - .and change(WikiVersion, :count).by(1) - .and change { linked_tag.reload.tag_versions.count }.by(2) - - expect(response).to have_http_status(:ok) - - linked_tag.reload - expect(linked_tag.name).to eq('wiki_linked_tag_for_version_renamed') - - versions = linked_tag.tag_versions.order(:version_no) - - expect(versions.first.event_type).to eq('create') - expect(versions.first.name).to eq('wiki_linked_tag_for_version') - - expect(versions.second.event_type).to eq('update') - expect(versions.second.name).to eq('wiki_linked_tag_for_version_renamed') - end - - it 'wiki body だけを変更しても tag version は作成しない' do - linked_tag_name = TagName.create!(name: 'wiki_body_only_tag') - linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) - - linked_page = - Wiki::Commit.create_content!( - tag_name: linked_tag_name, - body: 'before', - created_by_user: user, - message: 'init') - - current_id = linked_page.current_revision.id - - expect { - put "/wiki/#{ linked_page.id }", - params: { - title: 'wiki_body_only_tag', - body: 'after', - message: 'edit', - base_revision_id: current_id, - }, - headers: auth_headers(member) - } - .to change(WikiRevision, :count).by(1) - .and change(WikiVersion, :count).by(1) - - expect(linked_tag.reload.tag_versions.count).to eq(0) - end - it 'wiki title を変更すると対応する tag の version を作成する' do linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general)