| @@ -249,7 +249,10 @@ class TagsController < ApplicationController | |||||
| update_parent_tags!(tag, parent_names) | update_parent_tags!(tag, parent_names) | ||||
| tag.reload | 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 | end | ||||
| render json: TagRepr.base(tag.reload) | render json: TagRepr.base(tag.reload) | ||||
| @@ -272,10 +275,19 @@ class TagsController < ApplicationController | |||||
| ApplicationRecord.transaction do | ApplicationRecord.transaction do | ||||
| TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) | 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.tag_name.update!(name:) if name.present? | ||||
| tag.update!(category:) if category.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 | end | ||||
| render json: TagRepr.base(tag.reload) | render json: TagRepr.base(tag.reload) | ||||
| @@ -297,16 +309,20 @@ class TagsController < ApplicationController | |||||
| material: material.as_json&.merge(file:, content_type:)) | material: material.as_json&.merge(file:, content_type:)) | ||||
| end | 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? | if tag.nico? | ||||
| NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:) | 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 | 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 | end | ||||
| def update_aliases! tag, alias_names | def update_aliases! tag, alias_names | ||||
| @@ -85,22 +85,24 @@ class WikiPagesController < ApplicationController | |||||
| return head :unauthorized unless current_user | return head :unauthorized unless current_user | ||||
| return head :forbidden unless current_user.gte_member? | return head :forbidden unless current_user.gte_member? | ||||
| name = params[:title]&.strip | |||||
| title = params[:title].to_s.strip | |||||
| body = params[:body].to_s | 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 | end | ||||
| def update | def update | ||||
| @@ -113,28 +115,32 @@ class WikiPagesController < ApplicationController | |||||
| return head :unprocessable_entity if title.blank? || body.blank? | return head :unprocessable_entity if title.blank? || body.blank? | ||||
| page = WikiPage.find(params[:id]) | 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 | end | ||||
| head :ok | head :ok | ||||
| @@ -117,18 +117,21 @@ module Wiki | |||||
| id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h | 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| | line_shas.each_with_index do |sha, i| | ||||
| next if id_by_sha.key?(sha) | 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 | 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 | id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h | ||||
| end | end | ||||
| @@ -1,6 +1,6 @@ | |||||
| class WikiVersionRecorder < VersionRecorder | class WikiVersionRecorder < VersionRecorder | ||||
| def self.record! page:, event_type:, reason: nil, created_by_user: | 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 | end | ||||
| def initialize page:, event_type:, reason: nil, created_by_user: | def initialize page:, event_type:, reason: nil, created_by_user: | ||||
| @@ -471,54 +471,54 @@ RSpec.describe 'Tags API', type: :request do | |||||
| expect(response).to have_http_status(:unprocessable_entity) | expect(response).to have_http_status(:unprocessable_entity) | ||||
| expect(nico_tag.reload.category).to eq('nico') | expect(nico_tag.reload.category).to eq('nico') | ||||
| end | 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 | ||||
| end | end | ||||
| @@ -1023,37 +1023,37 @@ RSpec.describe 'Tags API', type: :request do | |||||
| expect(TagImplication.where(tag:, parent_tag: child)).not_to exist | expect(TagImplication.where(tag:, parent_tag: child)).not_to exist | ||||
| end | 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 | end | ||||
| end | end | ||||
| @@ -301,23 +301,22 @@ RSpec.describe 'Wiki API', type: :request do | |||||
| end | end | ||||
| 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 | context 'when page not found' do | ||||
| it 'returns 404' do | it 'returns 404' do | ||||
| @@ -520,76 +519,6 @@ RSpec.describe 'Wiki API', type: :request do | |||||
| end | end | ||||
| 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 | it 'wiki title を変更すると対応する tag の version を作成する' do | ||||
| linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') | linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') | ||||
| linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) | linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) | ||||