Add simple versioning replacement

This replaces the old simple versioning helper that existed on
e621. The implementaiton is roughly the same but has been updated
to use newer rails methods.

No, it's not very flexible, but it didn't need to be.
This commit is contained in:
Kira 2019-04-27 11:28:02 -07:00
parent 0006ded1b9
commit 06db14aceb
17 changed files with 335 additions and 4 deletions

View File

@ -0,0 +1,14 @@
class EditHistoriesController < ApplicationController
respond_to :html
before_action :moderator_only
def index
@edit_history = EditHistory.includes(:user).paginate(params[:page], limit: params[:limit])
respond_with(@edit_history)
end
def show
@edits = EditHistoryDecorator.decorate_collection(EditHistory.includes(:user).where('versionable_id = ? AND versionable_type = ?', params[:id], params[:type]).order(:id))
respond_with(@edits)
end
end

View File

@ -0,0 +1,58 @@
class EditHistoryDecorator < ApplicationDecorator
def self.collection_decorator_class
PaginatedDecorator
end
delegate_all
def diff(other)
pattern = Regexp.new('(?:<.+?>)|(?:\w+)|(?:[ \t]+)|(?:\r?\n)|(?:.+?)')
thisarr = other.body.scan(pattern)
otharr = object.body.scan(pattern)
cbo = Diff::LCS::ContextDiffCallbacks.new
diffs = thisarr.diff(otharr, cbo)
escape_html = ->(str) {str.gsub(/&/, '&amp;').gsub(/</, '&lt;').gsub(/>/, '&gt;')}
output = thisarr
output.each {|q| q.replace(escape_html[q])}
diffs.reverse_each do |hunk|
newchange = hunk.max {|a, b| a.old_position <=> b.old_position}
newstart = newchange.old_position
oldstart = hunk.min {|a, b| a.old_position <=> b.old_position}.old_position
if newchange.action == '+'
output.insert(newstart, '</ins>')
end
hunk.reverse_each do |chg|
case chg.action
when '-'
oldstart = chg.old_position
output[chg.old_position] = '<br>' if chg.old_element.match(/^\r?\n$/)
when '+'
if chg.new_element.match(/^\r?\n$/)
output.insert(chg.old_position, '<br>')
else
output.insert(chg.old_position, "#{escape_html[chg.new_element]}")
end
end
end
if newchange.action == '+'
output.insert(newstart, '<ins>')
end
if hunk[0].action == '-'
output.insert((newstart == oldstart || newchange.action != '+') ? newstart + 1 : newstart, '</del>')
output.insert(oldstart, '<del>')
end
end
output.join.gsub(/\r?\n/, '<br>').html_safe
end
end

View File

@ -0,0 +1,20 @@
@import "../base/000_vars.scss";
#c-edit-history {
.edit-item {
display: flex;
&>div {
margin-right: 1em;
display: inline-block;
}
}
ins {
background-color: $success_color;
font-weight: bold;
}
del {
background-color: $error_color;
font-weight: bold;
}
}

View File

@ -241,6 +241,60 @@ class ApplicationRecord < ActiveRecord::Base
end
end
concerning :SimpleVersioningMethods do
class_methods do
def simple_versioning(options = {})
cattr_accessor :versioning_body_column, :versioning_ip_column, :versioning_user_column, :versioning_subject_column
self.versioning_body_column = options[:body_column] || "body"
self.versioning_subject_column = options[:subject_column]
self.versioning_ip_column = options[:ip_column] || "creator_ip_addr"
self.versioning_user_column = options[:user_column] || "creator_id"
class_eval do
has_many :versions, class_name: 'EditHistory', as: :versionable
after_update :save_version, if: :should_version_change
define_method :should_version_change do
if self.versioning_subject_column
return true if send "saved_change_to_#{self.versioning_subject_column}?"
end
send "saved_change_to_#{self.versioning_body_column}?"
end
define_method :save_version do
EditHistory.transaction do
our_next_version = next_version
if our_next_version == 0
our_next_version += 1
new = EditHistory.new
new.versionable = self
new.version = 1
new.ip_addr = self.send self.versioning_ip_column
new.body = self.send "#{self.versioning_body_column}_before_last_save"
new.user_id = self.send self.versioning_user_column
new.subject = self.send "#{self.versioning_subject_column}_before_last_save" if self.versioning_subject_column
new.created_at = self.created_at
new.save
end
version = EditHistory.new
version.version = our_next_version + 1
version.versionable = self
version.ip_addr = CurrentUser.ip_addr
version.body = self.send self.versioning_body_column
version.user_id = CurrentUser.id
version.save
end
end
define_method :next_version do
versions.count
end
end
end
end
end
concerning :UserMethods do
class_methods do
def belongs_to_creator(options = {})

View File

@ -1,4 +1,5 @@
class Blip < ApplicationRecord
simple_versioning
belongs_to_creator
validates_presence_of :body
belongs_to :parent, class_name: "Blip", foreign_key: "response_to", optional: true

View File

@ -1,6 +1,7 @@
class Comment < ApplicationRecord
include Mentionable
simple_versioning
validate :validate_post_exists, :on => :create
validate :validate_creator_is_not_limited, :on => :create
validates_presence_of :body, :message => "has no content"

View File

@ -0,0 +1,14 @@
class EditHistory < ApplicationRecord
self.table_name = 'edit_histories'
belongs_to :versionable, polymorphic: true
belongs_to :user
attr_accessor :difference
TYPE_MAP = {
comment: 'Comment',
forum: 'ForumPost',
blip: 'Blip'
}
end

View File

@ -1,6 +1,7 @@
class ForumPost < ApplicationRecord
include Mentionable
simple_versioning
attr_readonly :topic_id
belongs_to_creator
belongs_to_updater
@ -160,7 +161,7 @@ class ForumPost < ApplicationRecord
end
def category_allows_replies
if topic && !topic.can_rely?(creator)
if topic && !topic.can_reply?(creator)
errors[:topic] << "does not allow replies"
return false
end

View File

@ -53,6 +53,7 @@
<% if CurrentUser.is_moderator? %>
<li>|</li>
<li><%= link_to "Show Edits", edit_history_path(id: blip.id, type: 'Blip') %></li>
<li>
<strong>IP</strong>
<span><%= link_to_ip blip.creator_ip_addr %></span>

View File

@ -40,6 +40,7 @@
<li><%= link_to "Report", new_ticket_path(disp_id: comment.id, type: 'comment') %></li>
<% if CurrentUser.is_moderator? %>
<li>|</li>
<li><%= link_to "Show Edits", edit_history_path(id: comment.id, type: 'Comment') %></li>
<li>
<strong>IP</strong>
<span><%= link_to_ip comment.creator_ip_addr %></span>

View File

@ -0,0 +1,37 @@
<div id="c-edit-history">
<div id="a-index">
<h1>Recent Edits</h1>
<table class='striped' style='width:100%;'>
<thead>
<tr>
<th></th>
<th>Type</th>
<th>Date</th>
<th>IP Address</th>
<th>Editor</th>
<th>Body</th>
<th>Subject</th>
</tr>
</thead>
<tbody>
<% @edit_history.each do |edit| %>
<tr id="edit-<%= edit.id %>">
<td><%= link_to "Show", action: "show", id: edit.versionable_id, type: edit.versionable_type %></td>
<td><%= edit.versionable_type %></td>
<td><%= edit.updated_at.strftime("%b %d, %Y %I:%M %p") %></td>
<td><%= link_to_ip edit.ip_addr %></td>
<td><%= link_to_user edit.user %></td>
<td><%= edit.body[0..30] %></td>
<td><%= edit.subject&[0..30] %></td>
</tr>
<% end %>
</tbody>
</table>
<div id="paginator">
<%= numbered_paginator(@edit_history) %>
</div>
</div>
</div>

View File

@ -0,0 +1,26 @@
<div id="c-edit-history">
<div id="a-show">
<h1>Edits for <%= h params[:type] %> #<%= h params[:id] %></h1>
<div class="response-list" id="edit-history">
<% @edits.each_with_index do |edit, idx| %>
<div class="edit-item box-section">
<div class="author">
<h6><%= link_to_user edit.user %></h6>
<span class="date" title="<%= time_ago_in_words(edit.created_at) + " ago" %>"><%= edit.created_at.strftime("%b %d, %Y %I:%M %p") %></span>
<div><%= link_to_ip edit.ip_addr %></div>
</div>
<div class="content">
<div class="body">
<% if edit.version > 1 %>
<%= edit.diff(@edits[idx-1]) %>
<% else %>
<%= edit.body %>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>

View File

@ -40,6 +40,11 @@
<% if CurrentUser.is_member? %>
<li><%= link_to "Report", new_ticket_path(disp_id: forum_post.id, type: 'forum') %></li>
<% end %>
<% if CurrentUser.is_moderator? %>
<li>|</li>
<li><%= link_to "Show Edits", edit_history_path(id: forum_post.id, type: 'ForumPost') %></li>
<li>|</li>
<% end %>
<% if params[:controller] == "forum_posts" %>
<li><%= link_to "Parent", forum_topic_path(forum_post.topic, :page => forum_post.forum_topic_page, :anchor => "forum_post_#{forum_post.id}") %></li>
<% else %>

View File

@ -10,6 +10,7 @@ Rails.application.routes.draw do
resource :alias_and_implication_import, :only => [:new, :create]
resource :dashboard, :only => [:show]
end
resources :edit_histories
namespace :moderator do
resource :bulk_revert, :only => [:new, :create]
resource :dashboard, :only => [:show]

View File

@ -0,0 +1,19 @@
class CreateEditHistories < ActiveRecord::Migration[5.2]
def self.up
create_table :edit_histories do |t|
t.timestamps
t.text :body, null: false
t.text :subject, null: true
t.string :versionable_type, limit: 100, null: false
t.integer :versionable_id, null: false
t.integer :version, null: false
t.column :ip_addr, "inet", null: false
t.integer :user_id, index: true, null: false
t.index [:versionable_id, :versionable_type]
end
end
def self.down
drop_table :edit_histories
end
end

View File

@ -0,0 +1,8 @@
class AddIpToForums < ActiveRecord::Migration[5.2]
def change
ForumPost.without_timeout do
add_column :forum_posts, :creator_ip_addr, :inet
add_column :forum_topics, :creator_ip_addr, :inet
end
end
end

View File

@ -779,6 +779,43 @@ CREATE SEQUENCE public.dmails_id_seq
ALTER SEQUENCE public.dmails_id_seq OWNED BY public.dmails.id;
--
-- Name: edit_histories; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.edit_histories (
id bigint NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
body text NOT NULL,
subject text,
versionable_type character varying(100) NOT NULL,
versionable_id integer NOT NULL,
version integer NOT NULL,
ip_addr inet NOT NULL,
user_id integer NOT NULL
);
--
-- Name: edit_histories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.edit_histories_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: edit_histories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.edit_histories_id_seq OWNED BY public.edit_histories.id;
--
-- Name: email_blacklists; Type: TABLE; Schema: public; Owner: -
--
@ -958,7 +995,8 @@ CREATE TABLE public.forum_posts (
text_index tsvector NOT NULL,
is_deleted boolean DEFAULT false NOT NULL,
created_at timestamp without time zone,
updated_at timestamp without time zone
updated_at timestamp without time zone,
creator_ip_addr inet
);
@ -1066,7 +1104,8 @@ CREATE TABLE public.forum_topics (
created_at timestamp without time zone,
updated_at timestamp without time zone,
category_id integer DEFAULT 0 NOT NULL,
min_level integer DEFAULT 0 NOT NULL
min_level integer DEFAULT 0 NOT NULL,
creator_ip_addr inet
);
@ -2649,6 +2688,13 @@ ALTER TABLE ONLY public.dmail_filters ALTER COLUMN id SET DEFAULT nextval('publi
ALTER TABLE ONLY public.dmails ALTER COLUMN id SET DEFAULT nextval('public.dmails_id_seq'::regclass);
--
-- Name: edit_histories id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.edit_histories ALTER COLUMN id SET DEFAULT nextval('public.edit_histories_id_seq'::regclass);
--
-- Name: email_blacklists id; Type: DEFAULT; Schema: public; Owner: -
--
@ -3116,6 +3162,14 @@ ALTER TABLE ONLY public.dmails
ADD CONSTRAINT dmails_pkey PRIMARY KEY (id);
--
-- Name: edit_histories edit_histories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.edit_histories
ADD CONSTRAINT edit_histories_pkey PRIMARY KEY (id);
--
-- Name: email_blacklists email_blacklists_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -3778,6 +3832,20 @@ CREATE INDEX index_dmails_on_message_index ON public.dmails USING gin (message_i
CREATE INDEX index_dmails_on_owner_id ON public.dmails USING btree (owner_id);
--
-- Name: index_edit_histories_on_user_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_edit_histories_on_user_id ON public.edit_histories USING btree (user_id);
--
-- Name: index_edit_histories_on_versionable_id_and_versionable_type; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_edit_histories_on_versionable_id_and_versionable_type ON public.edit_histories USING btree (versionable_id, versionable_type);
--
-- Name: index_favorite_groups_on_creator_id; Type: INDEX; Schema: public; Owner: -
--
@ -4810,6 +4878,8 @@ INSERT INTO "schema_migrations" (version) VALUES
('20190409195837'),
('20190410022203'),
('20190413055451'),
('20190418093745');
('20190418093745'),
('20190427163107'),
('20190427181805');