require_dependency "discourse_diff" class PostRevision < ActiveRecord::Base belongs_to :post belongs_to :user serialize :modifications, Hash def self.ensure_consistency! # 1 - fix the numbers sql = <<-SQL UPDATE post_revisions SET number = pr.rank FROM (SELECT id, ROW_NUMBER() OVER (PARTITION BY post_id ORDER BY number, created_at, updated_at) AS rank FROM post_revisions) AS pr WHERE post_revisions.id = pr.id AND post_revisions.number <> pr.rank SQL PostRevision.exec_sql(sql) # 2 - fix the versions on the posts sql = <<-SQL UPDATE posts SET version = pv.version FROM (SELECT post_id, MAX(number) AS version FROM post_revisions GROUP BY post_id) AS pv WHERE posts.id = pv.post_id AND posts.version <> pv.version SQL PostRevision.exec_sql(sql) end def body_changes cooked_diff = DiscourseDiff.new(previous("cooked"), current("cooked")) raw_diff = DiscourseDiff.new(previous("raw"), current("raw")) { inline: cooked_diff.inline_html, side_by_side: cooked_diff.side_by_side_html, side_by_side_markdown: raw_diff.side_by_side_markdown } end def category_changes prev = previous("category_id") cur = current("category_id") return if prev == cur { previous_category_id: prev, current_category_id: cur, } end def wiki_changes prev = previous("wiki") cur = current("wiki") return if prev == cur { previous_wiki: prev, current_wiki: cur, } end def post_type_changes prev = previous("post_type") cur = current("post_type") return if prev == cur { previous_post_type: prev, current_post_type: cur, } end def title_changes prev = "
#{CGI::escapeHTML(previous("title"))}
" cur = "
#{CGI::escapeHTML(current("title"))}
" return if prev == cur diff = DiscourseDiff.new(prev, cur) { inline: diff.inline_html, side_by_side: diff.side_by_side_html } end def user_changes prev = previous("user_id") cur = current("user_id") return if prev == cur { previous_user: User.find_by(id: prev), current_user: User.find_by(id: cur) } end def previous(field) val = lookup(field) if val.nil? val = lookup_in_previous_revisions(field) end if val.nil? val = lookup_in_post(field) end val end def current(field) val = lookup_in_next_revision(field) if val.nil? val = lookup_in_post(field) end if val.nil? val = lookup(field) end if val.nil? val = lookup_in_previous_revisions(field) end return val end def previous_revisions @previous_revs ||= PostRevision.where("post_id = ? AND number < ? AND hidden = ?", post_id, number, false) .order("number desc") .to_a end def next_revision @next_revision ||= PostRevision.where("post_id = ? AND number > ? AND hidden = ?", post_id, number, false) .order("number asc") .to_a.first end def has_topic_data? post && post.post_number == 1 end def lookup_in_previous_revisions(field) previous_revisions.each do |v| val = v.lookup(field) return val unless val.nil? end nil end def lookup_in_next_revision(field) if next_revision return next_revision.lookup(field) end end def lookup_in_post(field) if !post return elsif ["cooked", "raw"].include?(field) val = post.send(field) elsif ["title", "category_id"].include?(field) val = post.topic.send(field) end val end def lookup(field) return nil if hidden mod = modifications[field] unless mod.nil? mod[0] end end def hide! self.hidden = true self.save! end def show! self.hidden = false self.save! end end # == Schema Information # # Table name: post_revisions # # id :integer not null, primary key # user_id :integer # post_id :integer # modifications :text # number :integer # created_at :datetime not null # updated_at :datetime not null # # Indexes # # index_post_revisions_on_post_id (post_id) # index_post_revisions_on_post_id_and_number (post_id,number) #