このコミットが含まれているのは:
@@ -1,18 +1,20 @@
|
||||
class TagDescriptionOperation
|
||||
LANGUAGE_CODE = 'ja'
|
||||
|
||||
class Conflict < StandardError
|
||||
;
|
||||
end
|
||||
|
||||
def self.create!(tag:, body:, created_by_user:, message: nil)
|
||||
ApplicationRecord.transaction do
|
||||
tag.lock!
|
||||
raise ActiveRecord::RecordNotUnique if TagDescription.exists?(
|
||||
tag_id: tag.id, language_code: LANGUAGE_CODE)
|
||||
|
||||
page = Wiki::CompatibilityWriter.create!(
|
||||
tag_name: compatibility_tag_name(tag), body:, created_by_user:, message:)
|
||||
revision = page.wiki_revisions.order(id: :desc).first
|
||||
record_version!(tag:, revision:, body: revision.body, user: created_by_user,
|
||||
description: nil)
|
||||
page
|
||||
description = TagDescription.new(tag:, language_code: LANGUAGE_CODE)
|
||||
body = normalise_body!(description, body)
|
||||
record_version!(description:, body:, user: created_by_user)
|
||||
description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,51 +24,47 @@ class TagDescriptionOperation
|
||||
tag.lock!
|
||||
description = TagDescription.lock.find_by!(
|
||||
tag_id: tag.id, language_code: LANGUAGE_CODE)
|
||||
tag_name = compatibility_tag_name(tag)
|
||||
page = WikiPage.lock.find_by(tag_name_id: tag_name.id)
|
||||
unless page
|
||||
versions = TagDescriptionVersion.where(tag_id: tag.id, language_code: LANGUAGE_CODE)
|
||||
if base_revision_id.present? && versions.maximum(:id) != base_revision_id.to_i
|
||||
raise Wiki::Commit::Conflict, '競合が発生してゐます.'
|
||||
end
|
||||
page = Wiki::CompatibilityWriter.create!(
|
||||
tag_name:, body:, created_by_user: updated_by_user, message:)
|
||||
revision = page.current_revision
|
||||
record_version!(tag:, revision:, body: revision.body,
|
||||
user: updated_by_user, description:)
|
||||
next revision
|
||||
body = normalise_body!(description, body)
|
||||
latest = TagDescriptionVersion.find_by(
|
||||
tag_id: tag.id, language_code: LANGUAGE_CODE,
|
||||
version_no: description.version_no)
|
||||
if base_revision_id.present? && latest&.id != base_revision_id.to_i
|
||||
raise Conflict, '競合が発生してゐます.'
|
||||
end
|
||||
revision = Wiki::CompatibilityWriter.update!(
|
||||
page:, body:, updated_by_user:, message:,
|
||||
base_revision_id:)
|
||||
next unless revision
|
||||
next if latest && normalise_body(description.body) == body
|
||||
|
||||
record_version!(tag:, revision:, body: revision.body,
|
||||
user: updated_by_user, description:)
|
||||
revision
|
||||
record_version!(description:, body:, user: updated_by_user)
|
||||
end
|
||||
end
|
||||
|
||||
def self.compatibility_tag_name tag
|
||||
tag.tag_name(LANGUAGE_CODE) || TagName.find_by!(id: tag.tag_name_id,
|
||||
language_code: LANGUAGE_CODE)
|
||||
def self.record_version! description:, body:, user:
|
||||
creating = description.new_record?
|
||||
version_no = creating ? 1 : description.version_no + 1
|
||||
version = TagDescriptionVersion.create!(
|
||||
tag_id: description.tag_id, language_code: description.language_code,
|
||||
version_no:, event_type: creating ? 'create' : 'update', body:,
|
||||
source_language_code: description.source_language_code,
|
||||
source_version_no: description.source_version_no,
|
||||
created_by_user_id: user.id, created_at: Time.current)
|
||||
description.created_by_user_id = user.id if creating
|
||||
description.update!(body:, version_no:, updated_by_user_id: user.id)
|
||||
version
|
||||
end
|
||||
|
||||
def self.record_version! tag:, revision:, body:, user:, description:
|
||||
version_no = description ? description.version_no + 1 : 1
|
||||
TagDescriptionVersion.create!(
|
||||
id: revision.id, tag_id: tag.id, language_code: LANGUAGE_CODE,
|
||||
version_no:, event_type: description ? 'update' : 'create', body:,
|
||||
created_by_user_id: user.id, created_at: revision.created_at)
|
||||
if description
|
||||
description.update!(body:, version_no:, updated_by_user_id: user.id)
|
||||
else
|
||||
TagDescription.create!(
|
||||
tag_id: tag.id,
|
||||
language_code: LANGUAGE_CODE, version_no:, body:,
|
||||
created_by_user_id: user.id, updated_by_user_id: user.id)
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method :record_version!
|
||||
|
||||
def self.normalise_body body
|
||||
body.to_s.gsub(/\r\n?/, "\n")
|
||||
.encode('UTF-8', invalid: :replace, undef: :replace, replace: '🖕')
|
||||
.sub(/\n+\z/, '')
|
||||
end
|
||||
private_class_method :normalise_body
|
||||
|
||||
def self.normalise_body! description, body
|
||||
normalised = normalise_body(body)
|
||||
return normalised if normalised.present?
|
||||
|
||||
description.errors.add(:body, :blank)
|
||||
raise ActiveRecord::RecordInvalid, description
|
||||
end
|
||||
private_class_method :normalise_body!
|
||||
end
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
class TagGroupOperation
|
||||
MAX_PLACEMENT_DEPTH = 5
|
||||
PREFIX = TagGroupRepr::PREFIX
|
||||
|
||||
def self.create!(name:, description:, members:, placements:, actor:,
|
||||
require_compatibility: false, compatibility_body: nil)
|
||||
group, sync_issue = ApplicationRecord.transaction do
|
||||
def self.create!(name:, description:, members:, placements:, actor:)
|
||||
ApplicationRecord.transaction do
|
||||
group = TagGroup.new(version_no: 1, created_by_user: actor,
|
||||
updated_by_user: actor)
|
||||
attributes = prepare!(group:, name:, description:, members:, placements:)
|
||||
@@ -13,21 +11,15 @@ class TagGroupOperation
|
||||
group.save!
|
||||
persist_structure!(group:, attributes:, actor:)
|
||||
persist_version!(group:, attributes:, actor:, event_type: 'create')
|
||||
issue = sync_compatibility!(group:, actor:, old_name: nil,
|
||||
require_compatibility:, compatibility_body:)
|
||||
[group, issue]
|
||||
group
|
||||
end
|
||||
report_sync_issue(group, actor, sync_issue)
|
||||
group
|
||||
end
|
||||
|
||||
def self.update!(group:, name:, description:, members:, placements:, actor:,
|
||||
require_compatibility: false, compatibility_body: nil)
|
||||
group, sync_issue = ApplicationRecord.transaction do
|
||||
def self.update!(group:, name:, description:, members:, placements:, actor:)
|
||||
ApplicationRecord.transaction do
|
||||
group.lock!
|
||||
old_name = group.name
|
||||
attributes = prepare!(group:, name:, description:, members:, placements:)
|
||||
next [group, nil] if unchanged?(group, attributes)
|
||||
next group if unchanged?(group, attributes)
|
||||
|
||||
group.update!(name: attributes.fetch(:name),
|
||||
description: attributes.fetch(:description),
|
||||
@@ -35,12 +27,8 @@ class TagGroupOperation
|
||||
updated_by_user: actor)
|
||||
persist_structure!(group:, attributes:, actor:)
|
||||
persist_version!(group:, attributes:, actor:, event_type: 'update')
|
||||
issue = sync_compatibility!(group:, actor:, old_name:,
|
||||
require_compatibility:, compatibility_body:)
|
||||
[group, issue]
|
||||
group
|
||||
end
|
||||
report_sync_issue(group, actor, sync_issue)
|
||||
group
|
||||
end
|
||||
|
||||
def self.prepare!(group:, name:, description:, members:, placements:)
|
||||
@@ -313,57 +301,6 @@ class TagGroupOperation
|
||||
end
|
||||
private_class_method :persist_version!
|
||||
|
||||
def self.sync_compatibility!(group:, actor:, old_name:,
|
||||
require_compatibility:, compatibility_body:)
|
||||
representation = TagGroupRepr.one(group)
|
||||
body = begin
|
||||
TagGroupMarkdownRenderer.render(representation)
|
||||
rescue TagGroupMarkdownRenderer::UnsupportedStructure => error
|
||||
if compatibility_body.present?
|
||||
compatibility_body
|
||||
elsif require_compatibility
|
||||
invalid!(group, :base, error.message)
|
||||
else
|
||||
return error.message
|
||||
end
|
||||
end
|
||||
invalid!(group, :base, '本文は必須です.') if body.blank?
|
||||
|
||||
title = "#{ PREFIX }#{ group.name }"
|
||||
old_title = old_name && "#{ PREFIX }#{ old_name }"
|
||||
page = if old_title
|
||||
WikiPage.joins(:tag_name).find_by(tag_names: { name: old_title })
|
||||
else
|
||||
nil
|
||||
end
|
||||
page ||= WikiPage.joins(:tag_name).find_by(tag_names: { name: title })
|
||||
|
||||
if page
|
||||
tag_name = page.tag_name
|
||||
tag_name.update!(name: title) if tag_name.name != title
|
||||
Wiki::Commit.legacy_content!(page:, body:, created_user: actor, message: nil,
|
||||
base_revision_id: nil)
|
||||
else
|
||||
tag_name = TagName.find_or_initialize_by(language_code: 'ja', name: title)
|
||||
tag_name.script_code = 'Jpan'
|
||||
tag_name.primary_flg = true
|
||||
tag_name.auto_generated = false
|
||||
tag_name.save!
|
||||
Wiki::CompatibilityWriter.create!(tag_name:, body:, created_by_user: actor)
|
||||
end
|
||||
nil
|
||||
end
|
||||
private_class_method :sync_compatibility!
|
||||
|
||||
def self.report_sync_issue(group, actor, reason)
|
||||
return unless reason
|
||||
|
||||
payload = { status: 'unsupported_structure', tag_group_id: group.id,
|
||||
version_no: group.version_no, user_id: actor&.id, reason: }
|
||||
Rails.logger.warn("tag_group_wiki_sync_skipped #{ payload.to_json }")
|
||||
end
|
||||
private_class_method :report_sync_issue
|
||||
|
||||
def self.placement_references(nodes)
|
||||
nodes.flat_map do |node|
|
||||
own = if node.key?('tag_id')
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
require 'digest'
|
||||
|
||||
|
||||
module Wiki
|
||||
class Commit
|
||||
class Conflict < StandardError
|
||||
;
|
||||
end
|
||||
|
||||
def self.create_content! tag_name:, body:, created_by_user:, message: nil
|
||||
if tag_name.tag && tag_name.language_code == 'ja' && !tag_name.name.include?(':')
|
||||
return TagDescriptionOperation.create!(
|
||||
tag: tag_name.tag, body:, created_by_user:, message:)
|
||||
end
|
||||
|
||||
tag_name.errors.add(:base, :invalid)
|
||||
raise ActiveRecord::RecordInvalid, tag_name
|
||||
end
|
||||
|
||||
def self.create_legacy_content! tag_name:, body:, created_by_user:, message: nil
|
||||
normalised = normalise_body(body)
|
||||
|
||||
page = WikiPage.new(tag_name:,
|
||||
body: normalised,
|
||||
created_user: created_by_user,
|
||||
updated_user: created_by_user)
|
||||
|
||||
if normalised.blank?
|
||||
page.errors.add(:body, :blank)
|
||||
raise ActiveRecord::RecordInvalid, page
|
||||
end
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
page.save!
|
||||
|
||||
new(page:, created_user: created_by_user).content!(
|
||||
body: normalised,
|
||||
message:,
|
||||
base_revision_id: nil)
|
||||
|
||||
page
|
||||
end
|
||||
end
|
||||
|
||||
def self.content! page:, body:, created_user:, message: nil, base_revision_id: nil
|
||||
description = TagDescription.find_by(tag_id: page.tag_name.tag_id, language_code: 'ja')
|
||||
description ||= TagDescription.joins(:tag).find_by(
|
||||
tags: { tag_name_id: page.tag_name_id }, language_code: 'ja')
|
||||
if description && !page.title.include?(':')
|
||||
return TagDescriptionOperation.update!(
|
||||
tag: description.tag, body:, updated_by_user: created_user,
|
||||
message:, base_revision_id:)
|
||||
end
|
||||
|
||||
page.errors.add(:base, :invalid)
|
||||
raise ActiveRecord::RecordInvalid, page
|
||||
end
|
||||
|
||||
def self.legacy_content! page:, body:, created_user:, message: nil,
|
||||
base_revision_id: nil
|
||||
new(page:, created_user:).content!(body:, message:, base_revision_id:)
|
||||
end
|
||||
|
||||
def self.redirect! page:, redirect_page:, created_user:, message: nil, base_revision_id: nil
|
||||
new(page:, created_user:).redirect!(redirect_page:, message:, base_revision_id:)
|
||||
end
|
||||
|
||||
def initialize page:, created_user:
|
||||
@page = page
|
||||
@created_user = created_user
|
||||
end
|
||||
|
||||
def content! body:, message:, base_revision_id:
|
||||
normalised = self.class.normalise_body(body)
|
||||
if normalised.blank?
|
||||
@page.errors.add(:body, :blank)
|
||||
raise ActiveRecord::RecordInvalid, @page
|
||||
end
|
||||
|
||||
lines = split_lines(normalised)
|
||||
|
||||
line_shas = lines.map { |line| Digest::SHA256.hexdigest(line) }
|
||||
tree_sha = Digest::SHA256.hexdigest(line_shas.join(','))
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@page.lock!
|
||||
|
||||
if base_revision_id.present?
|
||||
current_id = @page.wiki_revisions.maximum(:id)
|
||||
if current_id && current_id != base_revision_id.to_i
|
||||
raise Conflict,
|
||||
"競合が発生してゐます" +
|
||||
"(現在の Id.:#{ current_id },ベース Id.:#{ base_revision_id })."
|
||||
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!(
|
||||
page: @page,
|
||||
event_type: @page.wiki_versions.exists? ? :update : :create,
|
||||
reason: message,
|
||||
created_by_user: @created_user)
|
||||
|
||||
rev = WikiRevision.create!(
|
||||
wiki_page: @page,
|
||||
base_revision_id:,
|
||||
created_user: @created_user,
|
||||
kind: :content,
|
||||
redirect_page_id: nil,
|
||||
message:,
|
||||
lines_count: lines.length,
|
||||
tree_sha256: tree_sha)
|
||||
|
||||
rows = line_ids.each_with_index.map do |line_id, pos|
|
||||
{ wiki_revision_id: rev.id, wiki_line_id: line_id, position: pos }
|
||||
end
|
||||
WikiRevisionLine.insert_all!(rows) if rows.any?
|
||||
rev
|
||||
end
|
||||
end
|
||||
|
||||
def redirect!(redirect_page:, message:, base_revision_id:) = raise '廃止しました.'
|
||||
|
||||
def self.normalise_body body
|
||||
s = body.to_s
|
||||
s.gsub!(/\r\n?/, "\n")
|
||||
s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '🖕')
|
||||
s.gsub(/\n+$/, '')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def split_lines(body) = body.split("\n")
|
||||
|
||||
def upsert_lines! lines, line_shas
|
||||
now = Time.current
|
||||
|
||||
id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h
|
||||
|
||||
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_by_sha[sha] = {
|
||||
sha256: sha,
|
||||
body: lines[i],
|
||||
created_at: now,
|
||||
updated_at: now }
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
id_by_sha
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
module Wiki
|
||||
class CompatibilityWriter
|
||||
def self.create! tag_name:, body:, created_by_user:, message: nil
|
||||
ApplicationRecord.transaction do
|
||||
tag_name.lock!
|
||||
page = WikiPage.lock.find_by(tag_name_id: tag_name.id)
|
||||
if page
|
||||
update!(page:, body:, updated_by_user: created_by_user, message:)
|
||||
page
|
||||
else
|
||||
Commit.create_legacy_content!(tag_name:, body:, created_by_user:, message:)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.update! page:, body:, updated_by_user:, message: nil,
|
||||
base_revision_id: nil
|
||||
normalised = Commit.normalise_body(body)
|
||||
if normalised.blank?
|
||||
return Commit.legacy_content!(
|
||||
page:, body: normalised, created_user: updated_by_user, message:,
|
||||
base_revision_id:)
|
||||
end
|
||||
|
||||
if base_revision_id.present?
|
||||
current_id = page.wiki_revisions.maximum(:id)
|
||||
if current_id && current_id != base_revision_id.to_i
|
||||
raise Commit::Conflict,
|
||||
"競合が発生してゐます" +
|
||||
"(現在の Id.:#{ current_id },ベース Id.:#{ base_revision_id })."
|
||||
end
|
||||
end
|
||||
|
||||
if Commit.normalise_body(page.body) == normalised && page.wiki_revisions.exists?
|
||||
return
|
||||
end
|
||||
|
||||
Commit.legacy_content!(
|
||||
page:, body: normalised, created_user: updated_by_user, message:,
|
||||
base_revision_id:)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
class WikiVersionRecorder < VersionRecorder
|
||||
def self.record! page:, event_type:, reason: nil, created_by_user:
|
||||
new(page:, event_type:, reason:, created_by_user:).record!
|
||||
end
|
||||
|
||||
def initialize page:, event_type:, reason: nil, created_by_user:
|
||||
@reason = reason
|
||||
super(record: page, event_type:, created_by_user:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def version_class = WikiVersion
|
||||
def version_association = :wiki_versions
|
||||
def record_key = :wiki_page
|
||||
|
||||
def snapshot_attributes = {
|
||||
title: @record.title,
|
||||
body: @record.body,
|
||||
reason: @reason }
|
||||
end
|
||||
新しいイシューから参照
ユーザーをブロックする