ファイル
btrc-hub/backend/app/controllers/wiki_pages_controller.rb
T
2026-10-04 15:24:27 +09:00

308 行
10 KiB
Ruby

class WikiPagesController < ApplicationController
rescue_from Wiki::Commit::Conflict, with: :render_wiki_conflict
def index
title = params[:title].to_s.strip
descriptions = TagDescription.where(language_code: 'ja')
.includes(tag: :tag_names)
pages = unmigrated_wiki_pages.includes(tag_name: :tag)
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
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])
return render_wiki_page_by_tag_description tag_description if tag_description
page = WikiPage.joins(:tag_name)
.includes(tag_name: :tag)
.find_by(id: params[:id])
render_wiki_page_or_404 page
end
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
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
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
id = params[:id]
return render_bad_request('id は必須です.') if id.blank?
from = params[:from].presence
to = params[:to].presence
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)
diff_json = diffs.map { |change|
case change.action
when ?=
{ type: 'context', content: change.old_element }
when ?!
[{ type: 'removed', content: change.old_element },
{ type: 'added', content: change.new_element }]
when ?+
{ type: 'added', content: change.new_element }
when ?-
{ type: 'removed', content: change.old_element }
end
}.flatten.compact
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 }
end
def create
return head :unauthorized unless current_user
return head :forbidden unless current_user.gte_member?
title = params[:title].to_s.strip
body = params[:body].to_s
message = params[:message].presence
return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank?
return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank?
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
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
render_validation_error e.record
rescue ActiveRecord::RecordNotUnique
render_record_not_unique
end
def update
return head :unauthorized unless current_user
return head :forbidden unless current_user.gte_member?
title = params[:title]&.strip
body = params[:body].to_s
return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank?
return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank?
page = WikiPage.find(params[:id])
base_revision_id = params[:base_revision_id].presence
ApplicationRecord.transaction do
page.lock!
old_title = page.title
tag = page.tag_name.tag
if tag && title != old_title
TagVersioning.ensure_snapshot!(tag, created_by_user: current_user)
end
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
end
def changes
id = params[:id].presence
description = id && TagDescription.find_by(id:)
if description
return render json: description_changes(TagDescription.where(id: description.id))
end
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
def render_wiki_page_or_404 page
return head :not_found unless page
rev = find_revision(page)
return head :not_found unless rev
if rev.redirect?
return (
redirect_to wiki_page_by_title_path(title: rev.redirect_page.title),
status: :moved_permanently)
end
body = rev.body
revision_id = rev.id
pred = page.pred_revision_id(revision_id)
succ = page.succ_revision_id(revision_id)
updated_at = rev.created_at
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])
else
page.current_revision
end
end
def render_wiki_conflict err
render json: { error: 'conflict', message: err.message }, status: :conflict
end
end