diff --git a/backend/app/controllers/wiki_pages_controller.rb b/backend/app/controllers/wiki_pages_controller.rb index 9a0c310..3973e48 100644 --- a/backend/app/controllers/wiki_pages_controller.rb +++ b/backend/app/controllers/wiki_pages_controller.rb @@ -2,124 +2,29 @@ class WikiPagesController < ApplicationController rescue_from Wiki::Commit::Conflict, with: :render_wiki_conflict def index - # 互換性のための化石リソース - # TODO: ヘルプおよびタグ・グループ追加後に, - # TODO: タグ説明とヘルプ、タグ・グループから取得する作りに変更する. - title = params[:title].to_s.strip - if title.blank? - tag_descriptions = TagDescription.where(language_code: :ja).map do |td| - { id: td.id, - tag_name_id: td.tag.tag_name(:ja).id, - body: td.body, - created_user_id: td.created_by_user_id, - updated_user_id: td.updated_by_user_id, - created_at: td.created_at, - updated_at: td.updated_at, - discarded_at: nil, - version_no: td.version_no, - title: td.tag.name(:ja), - deprecated_at: td.tag.deprecated_at } - end - # TODO: ヘルプ移行したらここに追記 - # TODO: タグ・グループ追加したらここに追記 + descriptions = TagDescription.where(language_code: 'ja') + .includes(tag: :tag_names) + pages = unmigrated_wiki_pages.includes(tag_name: :tag) - # TODO: ヘルプ、タグ・グループ移行したらそれらもタイトルから除外に含める. - # てかこれ何順? - wiki_pages = - WikiPageRepr.base(WikiPage.joins(:tag_name).includes(tag_name: :tag)) - .reject { |wp| tag_descriptions.any? { |td| td.title == wp.title } } - - json = tag_descriptions + wiki_pages - - return render json: + if title.present? + pattern = "%#{ ApplicationRecord.sanitize_sql_like(title) }%" + descriptions = descriptions.joins(tag: :tag_names) + .where(tag_names: { language_code: 'ja', primary_flg: true }) + .where('tag_names.name LIKE ?', pattern).limit(20) + pages = pages.where('tag_names.name LIKE ?', pattern).limit(20) end - q = - TagDescription.joins(tag: :tag_names) - .where(language_code: :ja) - .where(tag: { tag_names: { language_code: :ja, primary_flg: true } }) - .where('tag_names.name LIKE ?', "%#{ ApplicationRecord.sanitize_sql_like(title) }%") - tag_descriptions = q.limit(20).map do |td| - { id: td.id, - tag_name_id: td.tag.tag_name(:ja).id, - body: td.body, - created_user_id: td.created_by_user_id, - updated_user_id: td.updated_by_user_id, - created_at: td.created_at, - updated_at: td.updated_at, - discarded_at: nil, - version_no: td.version_no, - title: td.tag.name(:ja), - deprecated_at: td.tag.deprecated_at } - end - - q = - WikiPage.joins(:tag_name).includes(tag_name: :tag) - .where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%") - # TODO: ヘルプ、タグ・グループ移行したらそれらもタイトルから除外に含める. - wiki_pages = - WikiPageRepr.base(q.limit(20)) - .reject { |wp| tag_descriptions.any? { |td| td.title == wp.title } } - - json = (tag_descriptions + wiki_pages)[...20] - + json = descriptions.map { |description| WikiPageRepr.tag_description(description) } + json.concat(WikiPageRepr.base(pages)) + json = json.first(20) if title.present? render json: end def show tag_description = TagDescription.find_by(id: params[:id]) - if tag_description - rev = - if params[:version].present? - TagDescriptionVersion.find_by(id: params[:version]) - else - TagDescriptionVersion.find_by(tag_id: tag_description.tag_id, - language_code: :ja, - version_no: tag_description.version_no) - end - - if rev - body = - if rev.version_no == tag_description.version_no - tag_description.body - else - rev.body - end - revision_id = rev.id - pred = TagDescriptionVersion.find_by(tag_id: tag_description.tag_id, - language_code: :ja, - version_no: tag_description.version_no - 1)&.id - succ = TagDescriptionVersion.find_by(tag_id: tag_description.tag_id, - language_code: :ja, - version_no: tag_description.version_no + 1)&.id - updated_at = - if rev.version_no == tag_description.version_no - tag_description.updated_at - else - rev.created_at - end - - json = - { id: tag_description.id, - tag_name_id: tag_description.tag.tag_name(:ja).id, - body:, - created_user_id: tag_description.created_by_user_id, - updated_user_id: tag_description.updated_by_user_id, - created_at: tag_description.created_at, - updated_at:, - discarded_at: nil, - version_no: tag_description.version_no, - title: tag_description.tag.name(:ja), - deprecated_at: tag_description.tag.deprecated_at, - revision_id:, - pred:, - succ: } - - return render json: - end - end + return render_wiki_page_by_tag_description tag_description if tag_description page = WikiPage.joins(:tag_name) .includes(tag_name: :tag) @@ -130,27 +35,30 @@ class WikiPagesController < ApplicationController def show_by_title title = params[:title].to_s.strip + + tag_description = tag_description_by_title(title) + + return render_wiki_page_by_tag_description tag_description if tag_description + page = WikiPage.joins(:tag_name) .includes(tag_name: :tag) .find_by(tag_name: { name: title }) + render_wiki_page_or_404 page end def exists - if WikiPage.exists?(id: params[:id]) - head :no_content - else - head :not_found - end + return head :no_content if TagDescription.exists?(id: params[:id]) + + head WikiPage.exists?(id: params[:id]) ? :no_content : :not_found end def exists_by_title title = params[:title].to_s.strip - if WikiPage.joins(:tag_name).exists?(tag_names: { name: title }) - head :no_content - else - head :not_found - end + return head :no_content if tag_description_by_title(title) + + exists = WikiPage.joins(:tag_name).exists?(tag_names: { name: title }) + head exists ? :no_content : :not_found end def diff @@ -160,12 +68,20 @@ class WikiPagesController < ApplicationController from = params[:from].presence to = params[:to].presence - page = WikiPage.joins(:tag_name).includes(tag_name: :tag).find(id) - - from_rev = from && page.wiki_revisions.find(from) - to_rev = to ? page.wiki_revisions.find(to) : page.current_revision - if ((from_rev && !(from_rev.content?)) || !(to_rev&.content?)) - return render_unprocessable_entity('差分を表示できない版です.') + description = TagDescription.find_by(id:) + if description + versions = description_versions(description) + from_rev = from && versions.find(from) + to_rev = to ? versions.find(to) : versions.find_by!(version_no: description.version_no) + resource = WikiPageRepr.tag_description(description) + else + page = WikiPage.joins(:tag_name).includes(tag_name: :tag).find(id) + from_rev = from && page.wiki_revisions.find(from) + to_rev = to ? page.wiki_revisions.find(to) : page.current_revision + if ((from_rev && !from_rev.content?) || !to_rev&.content?) + return render_unprocessable_entity('差分を表示できない版です.') + end + resource = WikiPageRepr.base(page) end diffs = Diff::LCS.sdiff(from_rev&.body&.lines || [], to_rev.body.lines) @@ -183,9 +99,9 @@ class WikiPagesController < ApplicationController end }.flatten.compact - render json: { wiki_page_id: page.id, - title: page.title, - deprecated_at: page.deprecated_at, + render json: { wiki_page_id: resource.fetch('id'), + title: resource.fetch('title'), + deprecated_at: resource.fetch('deprecated_at'), older_revision_id: from_rev&.id, newer_revision_id: to_rev.id, diff: diff_json } @@ -202,18 +118,16 @@ class WikiPagesController < ApplicationController return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank? return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank? - tag_name = TagName.find_or_create_by!(language_code: 'ja', name: title) do |name| - name.script_code = 'Jpan' - name.primary_flg = true - name.auto_generated = false - end + page = ApplicationRecord.transaction do + tag_name = TagName.find_or_create_by!(language_code: 'ja', name: title) do |name| + name.script_code = 'Jpan' + name.primary_flg = true + name.auto_generated = false + end - page = - Wiki::Commit.create_content!( - tag_name:, - body:, - created_by_user: current_user, - message:) + Wiki::Commit.create_content!( + tag_name:, body:, created_by_user: current_user, message:) + end render json: WikiPageRepr.base(page), status: :created rescue ActiveRecord::RecordInvalid => e @@ -240,7 +154,7 @@ class WikiPagesController < ApplicationController old_title = page.title - tag = Tag.find_by(tag_name_id: page.tag_name_id) + tag = page.tag_name.tag if tag && title != old_title TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) @@ -266,23 +180,33 @@ class WikiPagesController < ApplicationController def changes id = params[:id].presence - q = WikiRevision.joins(wiki_page: :tag_name) - .includes(:created_user, wiki_page: { tag_name: :tag }) - .order(id: :desc) - q = q.where(wiki_page_id: id) if id + description = id && TagDescription.find_by(id:) + if description + return render json: description_changes(TagDescription.where(id: description.id)) + end - render json: q.limit(200).map { |rev| - { revision_id: rev.id, - pred: rev.base_revision_id, - succ: nil, - wiki_page: { id: rev.wiki_page_id, - title: rev.wiki_page.title, - deprecated_at: rev.wiki_page.deprecated_at }, - user: rev.created_user && { id: rev.created_user.id, name: rev.created_user.name }, - kind: rev.kind, - message: rev.message, - timestamp: rev.created_at } - }.compact + pages = id ? WikiPage.where(id:) : unmigrated_wiki_pages + revisions = WikiRevision.joins(wiki_page: :tag_name) + .where(wiki_page_id: pages.select(:id)) + .includes(:created_user, wiki_page: { tag_name: :tag }) + .order(id: :desc).limit(200) + json = revisions.map { |rev| + { revision_id: rev.id, + pred: rev.base_revision_id, + succ: nil, + wiki_page: { id: rev.wiki_page_id, + title: rev.wiki_page.title, + deprecated_at: rev.wiki_page.deprecated_at }, + user: rev.created_user && { id: rev.created_user.id, name: rev.created_user.name }, + kind: rev.kind, + message: rev.message, + timestamp: rev.created_at } + } + unless id + json.concat(description_changes(TagDescription.where(language_code: 'ja'))) + json.sort_by! { |row| -row[:revision_id] } + end + render json: json.first(200) end private @@ -308,6 +232,67 @@ class WikiPagesController < ApplicationController render json: WikiPageRepr.base(page).merge(body:, revision_id:, pred:, succ:, updated_at:) end + def render_wiki_page_by_tag_description description + versions = description_versions(description) + historical = params[:version].present? + rev = historical ? versions.find_by(id: params[:version]) : + versions.find_by(version_no: description.version_no) + return head :not_found unless rev + + pred = versions.find_by(version_no: rev.version_no - 1)&.id + succ = versions.find_by(version_no: rev.version_no + 1)&.id + body = historical ? rev.body : description.body + updated_at = historical ? rev.created_at : description.updated_at + render json: WikiPageRepr.tag_description(description).merge( + body:, revision_id: rev.id, pred:, succ:, updated_at:) + end + + def description_versions description + TagDescriptionVersion.where(tag_id: description.tag_id, + language_code: description.language_code) + end + + def tag_description_by_title title + TagDescription.joins(tag: :tag_names) + .find_by(language_code: 'ja', tag_names: { language_code: 'ja', name: title }) + end + + def unmigrated_wiki_pages + descriptions = TagDescription.where(language_code: 'ja') + WikiPage.joins(:tag_name).where.not(id: descriptions.select(:id)) + .where('tag_names.tag_id IS NULL OR tag_names.tag_id NOT IN (?)', + descriptions.select(:tag_id)) + end + + def description_changes descriptions + versions = TagDescriptionVersion.where(language_code: 'ja', + tag_id: descriptions.select(:tag_id)) + .order(id: :desc).limit(200).to_a + return [] if versions.empty? + + by_tag = descriptions.where(tag_id: versions.map(&:tag_id)) + .includes(tag: :tag_names).index_by(&:tag_id) + users = User.where(id: versions.map(&:created_by_user_id)).index_by(&:id) + predecessors = TagDescriptionVersion.where( + language_code: 'ja', tag_id: by_tag.keys, + version_no: versions.map { |version| version.version_no - 1 }) + .pluck(:tag_id, :version_no, :id) + .to_h { |tag_id, version_no, id| [[tag_id, version_no], id] } + versions.map do |version| + description = by_tag.fetch(version.tag_id) + user = users[version.created_by_user_id] + { revision_id: version.id, + pred: predecessors[[version.tag_id, version.version_no - 1]], + succ: nil, + wiki_page: { id: description.id, title: description.tag.name('ja'), + deprecated_at: description.tag.deprecated_at }, + user: user && { id: user.id, name: user.name }, + kind: 'content', + message: nil, + timestamp: version.created_at } + end + end + def find_revision page if params[:version].present? page.wiki_revisions.find_by(id: params[:version]) diff --git a/backend/app/representations/wiki_page_repr.rb b/backend/app/representations/wiki_page_repr.rb index ee09de3..a217341 100644 --- a/backend/app/representations/wiki_page_repr.rb +++ b/backend/app/representations/wiki_page_repr.rb @@ -10,6 +10,21 @@ module WikiPageRepr wiki_page.as_json(BASE) end + def tag_description description + tag = description.tag + { 'id' => description.id, + 'tag_name_id' => tag.tag_name('ja')&.id, + 'body' => description.body, + 'created_user_id' => description.created_by_user_id, + 'updated_user_id' => description.updated_by_user_id, + 'created_at' => description.created_at, + 'updated_at' => description.updated_at, + 'discarded_at' => nil, + 'version_no' => description.version_no, + 'title' => tag.name('ja'), + 'deprecated_at' => tag.deprecated_at } + end + def many wiki_pages wiki_pages.map { |p| base(p) } end diff --git a/backend/app/services/wiki/commit.rb b/backend/app/services/wiki/commit.rb index bd6fb7b..14afa7b 100644 --- a/backend/app/services/wiki/commit.rb +++ b/backend/app/services/wiki/commit.rb @@ -57,9 +57,6 @@ module Wiki line_shas = lines.map { |line| Digest::SHA256.hexdigest(line) } tree_sha = Digest::SHA256.hexdigest(line_shas.join(',')) - line_id_by_sha = upsert_lines!(lines, line_shas) - line_ids = line_shas.map { |sha| line_id_by_sha.fetch(sha) } - ActiveRecord::Base.transaction do @page.lock! @@ -72,6 +69,8 @@ module Wiki end end + line_id_by_sha = upsert_lines!(lines, line_shas) + line_ids = line_shas.map { |sha| line_id_by_sha.fetch(sha) } @page.update!(body: normalised) WikiVersionRecorder.record!( @@ -94,6 +93,7 @@ module Wiki { wiki_revision_id: rev.id, wiki_line_id: line_id, position: pos } end WikiRevisionLine.insert_all!(rows) if rows.any? + record_description!(rev, normalised) rev end @@ -110,6 +110,26 @@ module Wiki private + def record_description! revision, body + tag = @page.tag_name.tag + return unless tag && @page.tag_name.language_code == 'ja' + + tag.lock! + description = TagDescription.lock.find_by(tag_id: tag.id, language_code: 'ja') + version_no = description ? description.version_no + 1 : 1 + TagDescriptionVersion.create!( + id: revision.id, tag_id: tag.id, language_code: 'ja', version_no:, + event_type: description ? 'update' : 'create', body:, + created_by_user_id: @created_user.id, created_at: revision.created_at) + if description + description.update!(body:, version_no:, updated_by_user_id: @created_user.id) + else + TagDescription.create!( + id: @page.id, tag_id: tag.id, language_code: 'ja', version_no:, body:, + created_by_user_id: @created_user.id, updated_by_user_id: @created_user.id) + end + end + def split_lines(body) = body.split("\n") def upsert_lines! lines, line_shas diff --git a/backend/config/routes.rb b/backend/config/routes.rb index e44f5b5..e109460 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -35,7 +35,7 @@ Rails.application.routes.draw do resources :wiki_pages, path: 'wiki', only: [:index, :show, :create, :update] do collection do - get :search, to: redirect('/') + get :search, action: :index get :changes scope :title do