Implement forum topic voting and tag change pruning (#3580)
This commit is contained in:
@@ -8,6 +8,6 @@
|
||||
//= require jquery.qtip.js
|
||||
//= require ugoira_player.js
|
||||
//= require stupidtable.js
|
||||
//= require rails.js
|
||||
//= require rails-ujs
|
||||
//= require common.js
|
||||
//= require_tree .
|
||||
|
||||
2
app/assets/javascripts/forum_post_votes_controller.js
Normal file
2
app/assets/javascripts/forum_post_votes_controller.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
||||
3
app/assets/stylesheets/forum_post_votes_controller.scss
Normal file
3
app/assets/stylesheets/forum_post_votes_controller.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the ForumPostVotesController controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
@@ -5,10 +5,30 @@ div.list-of-forum-posts {
|
||||
word-wrap: break-word;
|
||||
box-shadow: 1px 1px 2px #AAA;
|
||||
|
||||
a.voted {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.desc {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
&:target {
|
||||
background-color: #FFC;
|
||||
}
|
||||
|
||||
.vote-score-up {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.vote-score-meh {
|
||||
color: goldenrod;
|
||||
}
|
||||
|
||||
.vote-score-down {
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.author {
|
||||
padding: 1em 1em 0 1em;
|
||||
width: 12em;
|
||||
@@ -26,6 +46,10 @@ div.list-of-forum-posts {
|
||||
menu {
|
||||
margin-top: 0.5em;
|
||||
|
||||
ul.votes {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class ApplicationController < ActionController::Base
|
||||
fmt.xml { render template: "static/error", status: 501 }
|
||||
end
|
||||
else
|
||||
render :template => "static/error", :status => 500
|
||||
render :template => "static/error", :status => 500, :layout => "blank"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
32
app/controllers/forum_post_votes_controller.rb
Normal file
32
app/controllers/forum_post_votes_controller.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
class ForumPostVotesController < ApplicationController
|
||||
respond_to :js
|
||||
before_action :load_forum_post
|
||||
before_action :load_vote, only: [:destroy]
|
||||
before_action :member_only
|
||||
|
||||
def create
|
||||
@forum_post_vote = @forum_post.votes.create(forum_post_vote_params)
|
||||
respond_with(@forum_post_vote)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@forum_post_vote.destroy
|
||||
respond_with(@forum_post_vote)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vote
|
||||
@forum_post_vote = @forum_post.votes.where(creator_id: CurrentUser.id).first
|
||||
raise ActiveRecord::RecordNotFound.new if @forum_post_vote.nil?
|
||||
end
|
||||
|
||||
def load_forum_post
|
||||
@forum_post = ForumPost.find(params[:forum_post_id])
|
||||
end
|
||||
|
||||
def forum_post_vote_params
|
||||
params.fetch(:forum_post_vote, {}).permit(:score)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -46,6 +46,7 @@ class ForumTopicsController < ApplicationController
|
||||
@forum_topic.mark_as_read!(CurrentUser.user)
|
||||
end
|
||||
@forum_posts = ForumPost.search(:topic_id => @forum_topic.id).reorder("forum_posts.id").paginate(params[:page])
|
||||
@original_forum_post_id = @forum_topic.original_post.id
|
||||
respond_with(@forum_topic) do |format|
|
||||
format.atom do
|
||||
@forum_posts = @forum_posts.reverse_order.includes(:creator).load
|
||||
|
||||
2
app/helpers/forum_post_votes_controller_helper.rb
Normal file
2
app/helpers/forum_post_votes_controller_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module ForumPostVotesControllerHelper
|
||||
end
|
||||
@@ -5,6 +5,7 @@ class DailyMaintenance
|
||||
TagPruner.new.prune!
|
||||
Upload.where('created_at < ?', 1.day.ago).delete_all
|
||||
Delayed::Job.where('created_at < ?', 45.days.ago).delete_all
|
||||
#ForumPostVote.where("created_at < ?", 90.days.ago).delete_all
|
||||
PostVote.prune!
|
||||
CommentVote.prune!
|
||||
ApiCacheGenerator.new.generate_tag_cache
|
||||
@@ -15,5 +16,7 @@ class DailyMaintenance
|
||||
Tag.clean_up_negative_post_counts!
|
||||
SuperVoter.init!
|
||||
TokenBucket.prune!
|
||||
TagChangeRequestPruner.warn_all
|
||||
TagChangeRequestPruner.reject_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,7 +79,8 @@ private
|
||||
end
|
||||
|
||||
def load_session_user
|
||||
CurrentUser.user = User.find_by_id(session[:user_id])
|
||||
user = User.find_by_id(session[:user_id])
|
||||
CurrentUser.user = user if user
|
||||
end
|
||||
|
||||
def load_cookie_user
|
||||
|
||||
43
app/logical/tag_change_request_pruner.rb
Normal file
43
app/logical/tag_change_request_pruner.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# Service to prune old unapproved tag change requests
|
||||
# (including tag aliases, tag implications, and bulk
|
||||
# update requests).
|
||||
|
||||
class TagChangeRequestPruner
|
||||
def self.warn_all
|
||||
[TagAlias, TagImplication, BulkUpdateRequest].each do |model|
|
||||
new.warn_old(model)
|
||||
end
|
||||
end
|
||||
|
||||
def self.reject_all
|
||||
[TagAlias, TagImplication, BulkUpdateRequest].each do |model|
|
||||
new.reject_expired(model)
|
||||
end
|
||||
end
|
||||
|
||||
def warn_old(model)
|
||||
model.old.pending.find_each do |tag_change|
|
||||
if tag_change.forum_topic
|
||||
name = model.model_name.human.downcase
|
||||
body = "This #{name} is pending automatic rejection in 5 days."
|
||||
unless tag_change.forum_topic.posts.where(creator_id: User.system.id, body: body).exists?
|
||||
tag_change.forum_updater.update(body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reject_expired(model)
|
||||
model.expired.pending.find_each do |tag_change|
|
||||
if tag_change.forum_topic
|
||||
name = model.model_name.human.downcase
|
||||
body = "This #{name} has been rejected because it was not approved within 60 days."
|
||||
tag_change.forum_updater.update(body)
|
||||
end
|
||||
|
||||
CurrentUser.as_system do
|
||||
tag_change.reject!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,9 @@ class BulkUpdateRequest < ApplicationRecord
|
||||
after_create :create_forum_topic
|
||||
|
||||
scope :pending_first, lambda { order("(case status when 'pending' then 0 when 'approved' then 1 else 2 end)") }
|
||||
scope :pending, ->{where(status: "pending")}
|
||||
scope :expired, ->{where("created_at < ?", TagRelationship::EXPIRY.days.ago)}
|
||||
scope :old, ->{where("created_at between ? and ?", TagRelationship::EXPIRY.days.ago, TagRelationship::EXPIRY_WARNING.days.ago)}
|
||||
|
||||
module SearchMethods
|
||||
def default_order
|
||||
@@ -121,7 +124,7 @@ class BulkUpdateRequest < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def reject!(rejector)
|
||||
def reject!(rejector = User.system)
|
||||
transaction do
|
||||
update(status: "rejected")
|
||||
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has been rejected by @#{rejector.name}.", "REJECTED")
|
||||
|
||||
@@ -5,6 +5,7 @@ class ForumPost < ApplicationRecord
|
||||
belongs_to_creator
|
||||
belongs_to_updater
|
||||
belongs_to :topic, :class_name => "ForumTopic"
|
||||
has_many :votes, class_name: "ForumPostVote"
|
||||
before_validation :initialize_is_deleted, :on => :create
|
||||
after_create :update_topic_updated_at_on_create
|
||||
after_update :update_topic_updated_at_on_update_for_original_posts
|
||||
@@ -128,6 +129,10 @@ class ForumPost < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def voted?(user, score)
|
||||
votes.where(creator_id: user.id, score: score).exists?
|
||||
end
|
||||
|
||||
def validate_topic_is_unlocked
|
||||
return if CurrentUser.is_moderator?
|
||||
return if topic.nil?
|
||||
@@ -228,8 +233,12 @@ class ForumPost < ApplicationRecord
|
||||
((ForumPost.where("topic_id = ? and created_at <= ?", topic_id, created_at).count) / Danbooru.config.posts_per_page.to_f).ceil
|
||||
end
|
||||
|
||||
def is_original_post?
|
||||
ForumPost.exists?(["id = ? and id = (select _.id from forum_posts _ where _.topic_id = ? order by _.id asc limit 1)", id, topic_id])
|
||||
def is_original_post?(original_post_id = nil)
|
||||
if original_post_id
|
||||
return id == original_post_id
|
||||
else
|
||||
ForumPost.exists?(["id = ? and id = (select _.id from forum_posts _ where _.topic_id = ? order by _.id asc limit 1)", id, topic_id])
|
||||
end
|
||||
end
|
||||
|
||||
def delete_topic_if_original_post
|
||||
|
||||
44
app/models/forum_post_vote.rb
Normal file
44
app/models/forum_post_vote.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
class ForumPostVote < ApplicationRecord
|
||||
belongs_to_creator
|
||||
belongs_to :forum_post
|
||||
validates :creator_id, uniqueness: {scope: :forum_post_id}
|
||||
validates :score, inclusion: {in: [-1, 0, 1]}
|
||||
scope :up, -> {where(score: 1)}
|
||||
scope :down, -> {where(score: -1)}
|
||||
scope :by, ->(user_id) {where(creator_id: user_id)}
|
||||
scope :excluding, ->(user_id) {where("creator_id <> ?", user_id)}
|
||||
|
||||
def up?
|
||||
score == 1
|
||||
end
|
||||
|
||||
def down?
|
||||
score == -1
|
||||
end
|
||||
|
||||
def meh?
|
||||
score == 0
|
||||
end
|
||||
|
||||
def fa_class
|
||||
if score == 1
|
||||
return "fa-thumbs-up"
|
||||
elsif score == -1
|
||||
return "fa-thumbs-down"
|
||||
else
|
||||
return "fa-meh"
|
||||
end
|
||||
end
|
||||
|
||||
def vote_type
|
||||
if score == 1
|
||||
return "up"
|
||||
elsif score == -1
|
||||
return "down"
|
||||
elsif score == 0
|
||||
return "meh"
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,9 @@ class TagImplication < TagRelationship
|
||||
validate :consequent_is_not_aliased
|
||||
validate :antecedent_and_consequent_are_different
|
||||
validate :wiki_pages_present, :on => :create
|
||||
scope :expired, ->{where("created_at < ?", 2.months.ago)}
|
||||
scope :old, ->{where("created_at between ? and ?", 2.months.ago, 1.month.ago)}
|
||||
scope :pending, ->{where(status: "pending")}
|
||||
|
||||
module DescendantMethods
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
class TagRelationship < ApplicationRecord
|
||||
self.abstract_class = true
|
||||
|
||||
EXPIRY = 60
|
||||
EXPIRY_WARNING = 55
|
||||
|
||||
attr_accessor :skip_secondary_validations
|
||||
|
||||
belongs_to_creator
|
||||
@@ -10,6 +13,10 @@ class TagRelationship < ApplicationRecord
|
||||
has_one :antecedent_tag, :class_name => "Tag", :foreign_key => "name", :primary_key => "antecedent_name"
|
||||
has_one :consequent_tag, :class_name => "Tag", :foreign_key => "name", :primary_key => "consequent_name"
|
||||
|
||||
scope :expired, ->{where("created_at < ?", EXPIRY.days.ago)}
|
||||
scope :old, ->{where("created_at >= ? and created_at < ?", EXPIRY.days.ago, EXPIRY_WARNING.days.ago)}
|
||||
scope :pending, ->{where(status: "pending")}
|
||||
|
||||
before_validation :initialize_creator, :on => :create
|
||||
before_validation :normalize_names
|
||||
validates_format_of :status, :with => /\A(active|deleted|pending|processing|queued|error: .*)\Z/
|
||||
|
||||
11
app/views/forum_post_votes/_add_vote.html.erb
Normal file
11
app/views/forum_post_votes/_add_vote.html.erb
Normal file
@@ -0,0 +1,11 @@
|
||||
<%-
|
||||
# forum_post
|
||||
%>
|
||||
|
||||
<li>
|
||||
<%= link_to content_tag(:i, nil, class: "far fa-thumbs-up"), forum_post_votes_path(forum_post_id: forum_post.id, format: "js"), remote: true, method: :post, data: {params: "forum_post_vote[score]=1"}, title: "Vote up" %>
|
||||
|
||||
<%= link_to content_tag(:i, nil, class: "far fa-meh"), forum_post_votes_path(forum_post_id: forum_post.id, format: "js"), remote: true, method: :post, data: {params: "forum_post_vote[score]=0"}, title: "Vote meh" %>
|
||||
|
||||
<%= link_to content_tag(:i, nil, class: "far fa-thumbs-down"), forum_post_votes_path(forum_post_id: forum_post.id, format: "js"), remote: true, method: :post, data: {params: "forum_post_vote[score]=-1"}, title: "Vote down" %>
|
||||
</li>
|
||||
16
app/views/forum_post_votes/_list.html.erb
Normal file
16
app/views/forum_post_votes/_list.html.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
<%-
|
||||
# votes
|
||||
# forum_post
|
||||
%>
|
||||
|
||||
<% votes.by(CurrentUser.user.id).each do |vote| %>
|
||||
<%= render "forum_post_votes/vote", vote: vote, forum_post: forum_post %>
|
||||
<% end %>
|
||||
|
||||
<% votes.excluding(CurrentUser.user.id).each do |vote| %>
|
||||
<%= render "forum_post_votes/vote", vote: vote, forum_post: forum_post %>
|
||||
<% end %>
|
||||
|
||||
<% if !votes.by(CurrentUser.user.id).exists? %>
|
||||
<%= render "forum_post_votes/add_vote", vote: votes.by(CurrentUser.user.id).first, forum_post: forum_post %>
|
||||
<% end %>
|
||||
14
app/views/forum_post_votes/_vote.html.erb
Normal file
14
app/views/forum_post_votes/_vote.html.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
<%-
|
||||
# vote
|
||||
# forum_post
|
||||
%>
|
||||
|
||||
<li class="vote-score-<%= vote.vote_type %>">
|
||||
<% if vote.creator_id == CurrentUser.id %>
|
||||
<%= link_to content_tag(:i, nil, class: "far #{vote.fa_class}"), forum_post_votes_path(forum_post_id: forum_post.id, format: "js"), remote: true, method: :delete %>
|
||||
<%= link_to_user vote.creator %>
|
||||
<% else %>
|
||||
<%= content_tag(:i, nil, class: "far #{vote.fa_class}") %>
|
||||
<%= link_to_user vote.creator %>
|
||||
<% end %>
|
||||
</li>
|
||||
7
app/views/forum_post_votes/create.js.erb
Normal file
7
app/views/forum_post_votes/create.js.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
<% if @forum_post_vote.invalid? %>
|
||||
Danbooru.error(<%= raw @forum_post_vote.errors.full_messages.join("; ").to_json %>);
|
||||
<% else %>
|
||||
Danbooru.notice("Voted");
|
||||
var code = <%= raw render(partial: "forum_post_votes/list", locals: {forum_post: @forum_post, votes: @forum_post.votes}).to_json %>;
|
||||
$("#forum-post-votes-for-<%= @forum_post.id %>").html(code);
|
||||
<% end %>
|
||||
3
app/views/forum_post_votes/destroy.js.erb
Normal file
3
app/views/forum_post_votes/destroy.js.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
Danbooru.notice("Unvoted");
|
||||
var code = <%= raw render(partial: "forum_post_votes/list", locals: {forum_post: @forum_post, votes: @forum_post.votes}).to_json %>;
|
||||
$("#forum-post-votes-for-<%= @forum_post.id %>").html(code);
|
||||
@@ -1,3 +1,5 @@
|
||||
<%- # original_forum_post_id: used to accelerate #is_original_post? calls %>
|
||||
|
||||
<% if forum_post.visible?(CurrentUser.user) %>
|
||||
<article class="forum-post" id="forum_post_<%= forum_post.id %>" data-forum-post-id="<%= forum_post.id %>" data-creator="<%= forum_post.creator.name %>">
|
||||
<div class="author">
|
||||
@@ -23,7 +25,7 @@
|
||||
<% if CurrentUser.is_member? && @forum_topic %>
|
||||
<li><%= link_to "Quote", new_forum_post_path(:post_id => forum_post.id), :method => :get, :remote => true %></li>
|
||||
<% end %>
|
||||
<% if CurrentUser.is_moderator? && !forum_post.is_original_post? %>
|
||||
<% if CurrentUser.is_moderator? && !forum_post.is_original_post?(original_forum_post_id) %>
|
||||
<% if forum_post.is_deleted %>
|
||||
<li><%= link_to "Undelete", undelete_forum_post_path(forum_post.id), :method => :post, :remote => true %></li>
|
||||
<% else %>
|
||||
@@ -31,7 +33,7 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if forum_post.editable_by?(CurrentUser.user) %>
|
||||
<% if forum_post.is_original_post? %>
|
||||
<% if forum_post.is_original_post?(original_forum_post_id) %>
|
||||
<li><%= link_to "Edit", edit_forum_topic_path(forum_post.topic), :id => "edit_forum_topic_link_#{forum_post.topic.id}", :class => "edit_forum_topic_link" %></li>
|
||||
<% else %>
|
||||
<li><%= link_to "Edit", edit_forum_post_path(forum_post.id), :id => "edit_forum_post_link_#{forum_post.id}", :class => "edit_forum_post_link" %></li>
|
||||
@@ -42,9 +44,14 @@
|
||||
<% else %>
|
||||
<li><%= link_to "Permalink", forum_post_path(forum_post) %></li>
|
||||
<% end %>
|
||||
<% if forum_post.is_original_post?(original_forum_post_id) %>
|
||||
<ul class="votes" id="forum-post-votes-for-<%= forum_post.id %>">
|
||||
<%= render "forum_post_votes/list", votes: forum_post.votes, forum_post: forum_post %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</menu>
|
||||
<% if forum_post.editable_by?(CurrentUser.user) %>
|
||||
<% if forum_post.is_original_post? %>
|
||||
<% if forum_post.is_original_post?(original_forum_post_id) %>
|
||||
<%= render "forum_topics/form", :forum_topic => forum_post.topic %>
|
||||
<% else %>
|
||||
<%= render "forum_posts/partials/edit/form", :forum_post => forum_post %>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<%- # forum_post %>
|
||||
<%- # original_forum_post_id %>
|
||||
|
||||
<div class="list-of-forum-posts">
|
||||
<% forum_posts.each do |forum_post| %>
|
||||
<%= render "forum_posts/forum_post", :forum_post => forum_post %>
|
||||
<%= render "forum_posts/forum_post", :forum_post => forum_post, :original_forum_post_id => original_forum_post_id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div id="c-forum-posts">
|
||||
<div id="a-show" class="single-forum-post list-of-forum-posts">
|
||||
<h1>Topic: <%= @forum_post.topic.title %></h1>
|
||||
<%= render "forum_post", :forum_post => @forum_post %>
|
||||
<%= render "forum_post", :forum_post => @forum_post, :original_forum_post_id => @forum_post.topic.original_post.id %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "forum_posts/listing", :forum_posts => @forum_posts %>
|
||||
<%= render "forum_posts/listing", :forum_posts => @forum_posts, :original_forum_post_id => @forum_topic.original_post.id %>
|
||||
|
||||
<% if CurrentUser.is_member? %>
|
||||
<% if CurrentUser.is_moderator? || !@forum_topic.is_locked? %>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<%= csrf_meta_tag %>
|
||||
<%= auto_discovery_link_tag :atom, posts_path(:format => "atom", :tags => params[:tags]) %>
|
||||
<%= stylesheet_link_tag "application", :media => "screen" %>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%= Danbooru.config.custom_html_header_content %>
|
||||
<meta name="enable-auto-complete" content="<%= CurrentUser.user.enable_auto_complete %>">
|
||||
<%= yield :html_header %>
|
||||
</head>
|
||||
<body lang="en">
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"url" : "<%= root_url %>"
|
||||
}
|
||||
</script>
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.0.10/js/all.js" integrity="sha384-slN8GvtUJGnv6ca26v8EzVaR9DC58QEwsIk9q1QXdCU8Yu8ck/tL/5szYlBbqmS+" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body lang="en" <%= body_attributes(CurrentUser.user) %>>
|
||||
<header id="top">
|
||||
|
||||
Reference in New Issue
Block a user