ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

111 lines
3.2 KiB

  1. require 'digest'
  2. module Wiki
  3. class Commit
  4. class Conflict < StandardError
  5. ;
  6. end
  7. def self.content! page:, body:, created_user:, message: nil, base_revision_id: nil
  8. new(page:, created_user:).content!(body:, message:, base_revision_id:)
  9. end
  10. def self.redirect! page:, redirect_page:, created_user:, message: nil, base_revision_id: nil
  11. new(page:, created_user:).redirect!(redirect_page:, message:, base_revision_id:)
  12. end
  13. def initialize page:, created_user:
  14. @page = page
  15. @created_user = created_user
  16. end
  17. def content! body:, message:, base_revision_id:
  18. normalised = normalise_body(body).gsub(/\n+$/, '')
  19. lines = split_lines(normalised)
  20. line_shas = lines.map { |line| Digest::SHA256.hexdigest(line) }
  21. tree_sha = Digest::SHA256.hexdigest(line_shas.join(','))
  22. line_id_by_sha = upsert_lines!(lines, line_shas)
  23. line_ids = line_shas.map { |sha| line_id_by_sha.fetch(sha) }
  24. ActiveRecord::Base.transaction do
  25. @page.lock!
  26. if base_revision_id.present?
  27. current_id = @page.wiki_revisions.maximum(:id)
  28. if current_id && current_id != base_revision_id.to_i
  29. raise Conflict,
  30. "競合が発生してゐます" +
  31. "(現在の Id.:#{ current_id },ベース Id.:#{ base_revision_id })."
  32. end
  33. end
  34. @page.update!(body: normalised)
  35. WikiVersionRecorder.record!(
  36. page: { title: @page.title, body: normalised },
  37. event_type: @page.wiki_versions.exists? ? :update : :create,
  38. created_by_user: @created_user)
  39. tag = @page.tag_name.tag
  40. if tag
  41. TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: @created_user)
  42. end
  43. rev = WikiRevision.create!(
  44. wiki_page: @page,
  45. base_revision_id:,
  46. created_user: @created_user,
  47. kind: :content,
  48. redirect_page_id: nil,
  49. message:,
  50. lines_count: lines.length,
  51. tree_sha256: tree_sha)
  52. rows = line_ids.each_with_index.map do |line_id, pos|
  53. { wiki_revision_id: rev.id, wiki_line_id: line_id, position: pos }
  54. end
  55. WikiRevisionLine.insert_all!(rows) if rows.any?
  56. rev
  57. end
  58. end
  59. def redirect!(redirect_page:, message:, base_revision_id:) = raise '廃止しました.'
  60. private
  61. def normalise_body body
  62. s = body.to_s
  63. s.gsub!(/\r\n?/, "\n")
  64. s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '🖕')
  65. end
  66. def split_lines(body) = body.split("\n")
  67. def upsert_lines! lines, line_shas
  68. now = Time.current
  69. id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h
  70. missing_rows = []
  71. line_shas.each_with_index do |sha, i|
  72. next if id_by_sha.key?(sha)
  73. missing_rows << { sha256: sha,
  74. body: lines[i],
  75. created_at: now,
  76. updated_at: now }
  77. end
  78. if missing_rows.any?
  79. WikiLine.upsert_all(missing_rows)
  80. id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h
  81. end
  82. id_by_sha
  83. end
  84. end
  85. end