ぼざクリタグ広場 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.
 
 
 
 
 
 

123 lines
3.5 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)
  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. "競合が発生してゐます(現在の Id.:#{ current_id },ベース Id.:#{ base_revision_id })."
  31. end
  32. end
  33. rev = WikiRevision.create!(
  34. wiki_page: @page,
  35. base_revision_id:,
  36. created_user: @created_user,
  37. kind: :content,
  38. redirect_page_id: nil,
  39. message:,
  40. lines_count: lines.length,
  41. tree_sha256: tree_sha)
  42. rows = line_ids.each_with_index.map do |line_id, pos|
  43. { wiki_revision_id: rev.id, wiki_line_id: line_id, position: pos }
  44. end
  45. WikiRevisionLine.insert_all!(rows)
  46. rev
  47. end
  48. end
  49. def redirect! redirect_page:, message:, base_revision_id:
  50. ActiveRecord::Base.transaction do
  51. @page.lock!
  52. if base_revision_id.present?
  53. current_id = @page.wiki_revisions.maximum(:id)
  54. if current_id && current_id != base_revision_id.to_i
  55. raise Conflict,
  56. "競合が発生してゐます(現在の Id.:#{ current_id },ベース Id.:#{ base_revision_id })."
  57. end
  58. end
  59. WikiRevision.create!(
  60. wiki_page: @page,
  61. base_revision_id:,
  62. created_user: @created_user,
  63. kind: :redirect,
  64. redirect_page:,
  65. message:,
  66. lines_count: 0,
  67. tree_sha256: nil)
  68. end
  69. end
  70. private
  71. def normalise_body body
  72. s = body.to_s
  73. s.gsub!("\r\n", "\n")
  74. s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '🖕')
  75. end
  76. def split_lines body
  77. body.split("\n")
  78. end
  79. def upsert_lines! lines, line_shas
  80. now = Time.current
  81. id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h
  82. missing_rows = []
  83. line_shas.each_with_index do |sha, i|
  84. next if id_by_sha.key?(sha)
  85. missing_rows << { sha256: sha,
  86. body: lines[i],
  87. created_at: now,
  88. updated_at: now }
  89. end
  90. if missing_rows.any?
  91. WikiLine.upsert_all(missing_rows)
  92. id_by_sha = WikiLine.where(sha256: line_shas).pluck(:sha256, :id).to_h
  93. end
  94. id_by_sha
  95. end
  96. end
  97. end