diff --git a/app/controllers/admin/destroyed_posts_controller.rb b/app/controllers/admin/destroyed_posts_controller.rb new file mode 100644 index 000000000..508775710 --- /dev/null +++ b/app/controllers/admin/destroyed_posts_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class DestroyedPostsController < ApplicationController + before_action :admin_only + before_action :is_bd_staff_only, only: %i[update] + respond_to :html + + def index + @destroyed_posts = DestroyedPost.search(search_params).paginate(params[:page], limit: params[:limit]) + end + + def show + redirect_to(admin_destroyed_posts_path(search: { post_id: params[:id] })) + end + + def update + @destroyed_post = DestroyedPost.find_by!(post_id: params[:id]) + @destroyed_post.update(dp_params) + flash[:notice] = dp_params[:notify] == "true" ? "Re-uploads of that post will now notify admins" : "Re-uploads of that post will no longer notify admins" + redirect_to(admin_destroyed_posts_path) + end + + private + + def search_params + permit_search_params(%i[destroyer_id destroyer_name destroyer_ip_addr uploader_id uploader_name uploader_ip_addr post_id md5]) + end + + def dp_params + params.require(:destroyed_post).permit(:notify) + end + end +end diff --git a/app/controllers/moderator/post/posts_controller.rb b/app/controllers/moderator/post/posts_controller.rb index c4f59f695..6f8579c9c 100644 --- a/app/controllers/moderator/post/posts_controller.rb +++ b/app/controllers/moderator/post/posts_controller.rb @@ -48,7 +48,7 @@ module Moderator def expunge @post = ::Post.find(params[:id]) - @post.expunge! + @post.expunge!(reason: params[:reason]) respond_with(@post) end diff --git a/app/controllers/post_replacements_controller.rb b/app/controllers/post_replacements_controller.rb index b73179179..c0c602459 100644 --- a/app/controllers/post_replacements_controller.rb +++ b/app/controllers/post_replacements_controller.rb @@ -23,6 +23,7 @@ class PostReplacementsController < ApplicationController check_allow_create @post = Post.find(params[:post_id]) @post_replacement = @post.replacements.create(create_params.merge(creator_id: CurrentUser.id, creator_ip_addr: CurrentUser.ip_addr)) + @post_replacement.notify_reupload if @post_replacement.errors.none? flash[:notice] = "Post replacement submitted" end diff --git a/app/javascript/src/javascripts/posts.js b/app/javascript/src/javascripts/posts.js index 5dafad4b2..a69f2d937 100644 --- a/app/javascript/src/javascripts/posts.js +++ b/app/javascript/src/javascripts/posts.js @@ -354,9 +354,9 @@ Post.initialize_links = function() { }); $("#destroy-post-link").on('click', e => { e.preventDefault(); - if(!confirm("This will permanently delete this post (meaning the file will be deleted). Are you sure you want to delete this post?")) - return; - Post.destroy($(e.target).data('pid')); + const reason = prompt("This will permanently delete this post (meaning the file will be deleted). What is the reason for destroying the post?") + if(reason === null) return; + Post.destroy($(e.target).data('pid'), reason); }); $("#regenerate-image-samples-link").on('click', e => { e.preventDefault(); @@ -841,13 +841,13 @@ Post.unapprove = function(post_id) { }); } -Post.destroy = function(post_id) { - $.post(`/moderator/post/posts/${post_id}/expunge.json`, {} +Post.destroy = function(post_id, reason) { + $.post(`/moderator/post/posts/${post_id}/expunge.json`, { reason } ).fail(data => { var message = $.map(data.responseJSON.errors, function(msg, attr) { return msg; }).join("; "); $(window).trigger("danbooru:error", "Error: " + message); }).done(data => { - location.reload(); + location.href = `/admin/destroyed_posts/${post_id}`; }); }; diff --git a/app/javascript/src/styles/base.scss b/app/javascript/src/styles/base.scss index 0862396f8..3d3901be0 100644 --- a/app/javascript/src/styles/base.scss +++ b/app/javascript/src/styles/base.scss @@ -39,6 +39,7 @@ @import "specific/bans.scss"; @import "specific/blips.scss"; @import "specific/comments.scss"; +@import "specific/destroyed_posts.scss"; @import "specific/dmails.scss"; @import "specific/edit_history.scss"; @import "specific/error.scss"; diff --git a/app/javascript/src/styles/specific/destroyed_posts.scss b/app/javascript/src/styles/specific/destroyed_posts.scss new file mode 100644 index 000000000..70aae06e3 --- /dev/null +++ b/app/javascript/src/styles/specific/destroyed_posts.scss @@ -0,0 +1,5 @@ +#c-admin-destroyed-posts { + tr[data-notify=false] { + background-color: $negative-record-background; + } +} diff --git a/app/logical/dummy_ticket.rb b/app/logical/dummy_ticket.rb deleted file mode 100644 index ad9b3fc77..000000000 --- a/app/logical/dummy_ticket.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class DummyTicket - def initialize(accused, post_id) - @ticket = Ticket.new( - id: 0, - created_at: Time.now, - updated_at: Time.now, - creator_id: User.system.id, - disp_id: 0, - status: "pending", - qtype: "user", - reason: "User ##{accused.id} (#{accused.name}) tried to reupload destroyed post ##{post_id}", - ) - end - - def notify - @ticket.push_pubsub("create") - end -end diff --git a/app/models/destroyed_post.rb b/app/models/destroyed_post.rb index a84b4786e..73aa8897c 100644 --- a/app/models/destroyed_post.rb +++ b/app/models/destroyed_post.rb @@ -1,5 +1,55 @@ # frozen_string_literal: true class DestroyedPost < ApplicationRecord - + belongs_to :destroyer, class_name: "User" + belongs_to :uploader, class_name: "User", optional: true + after_update :log_notify_change, if: :saved_change_to_notify? + + def log_notify_change + action = notify? ? :enable_post_notifications : :disable_post_notifications + StaffAuditLog.log(action, CurrentUser.user, { destroyed_post_id: id, post_id: post_id }) + end + + module SearchMethods + def search(params) + q = super + + q = q.where_user(:destroyer_id, :destroyer, params) + q = q.where_user(:uploader_id, :uploader, params) + + if params[:destroyer_ip_addr].present? + q = q.where("destroyer_ip_addr <<= ?", params[:destroyer_ip_addr]) + end + + if params[:uploader_ip_addr].present? + q = q.where("uploader_ip_addr <<= ?", params[:uploader_ip_addr]) + end + + if params[:post_id].present? + q = q.attribute_matches(:post_id, params[:post_id]) + end + + if params[:md5].present? + q = q.attribute_matches(:md5, params[:md5]) + end + + q.apply_basic_order(params) + end + end + + extend SearchMethods + + def notify_reupload(uploader, replacement_post_id: nil) + return if notify == false + reason = "User tried to re-upload \"previously destroyed post ##{post_id}\":/admin/destroyed_posts/#{post_id}" + reason += " as a replacement for post ##{replacement_post_id}" if replacement_post_id.present? + Ticket.create!( + creator_id: User.system.id, + creator_ip_addr: "127.0.0.1", + disp_id: uploader.id, + status: "pending", + qtype: "user", + reason: reason, + ).push_pubsub("create") + end end diff --git a/app/models/post.rb b/app/models/post.rb index 4193264e5..10af8a718 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1133,7 +1133,7 @@ class Post < ApplicationRecord end module DeletionMethods - def backup_post_data_destroy + def backup_post_data_destroy(reason: "") post_data = { id: id, description: description, @@ -1157,17 +1157,17 @@ class Post < ApplicationRecord DestroyedPost.create!(post_id: id, post_data: post_data, md5: md5, uploader_ip_addr: uploader_ip_addr, uploader_id: uploader_id, destroyer_id: CurrentUser.id, destroyer_ip_addr: CurrentUser.ip_addr, - upload_date: created_at) + upload_date: created_at, reason: reason || "") end - def expunge! + def expunge!(reason: "") if is_status_locked? self.errors.add(:is_status_locked, "; cannot delete post") return false end transaction do - backup_post_data_destroy + backup_post_data_destroy(reason: reason) end transaction do diff --git a/app/models/post_replacement.rb b/app/models/post_replacement.rb index fef1a325d..2a32be1ef 100644 --- a/app/models/post_replacement.rb +++ b/app/models/post_replacement.rb @@ -6,7 +6,7 @@ class PostReplacement < ApplicationRecord belongs_to :creator, class_name: "User" belongs_to :approver, class_name: "User", optional: true belongs_to :uploader_on_approve, class_name: "User", foreign_key: :uploader_id_on_approve, optional: true - attr_accessor :replacement_file, :replacement_url, :tags, :is_backup + attr_accessor :replacement_file, :replacement_url, :tags, :is_backup, :is_destroyed_reupload validate :user_is_not_limited, on: :create validate :post_is_valid, on: :create @@ -33,6 +33,13 @@ class PostReplacement < ApplicationRecord Addressable::URI.heuristic_parse(replacement_url) rescue nil end + def notify_reupload + return unless is_destroyed_reupload + if (destroyed_post = DestroyedPost.find_by(md5: md5)) + destroyed_post.notify_reupload(creator, replacement_post_id: post_id) + end + end + module PostMethods def post_is_valid if post.is_deleted? @@ -45,9 +52,9 @@ class PostReplacement < ApplicationRecord def no_pending_duplicates return true if is_backup - if (destroyed_post = DestroyedPost.find_by(md5: md5)) - errors.add(:base, "An unexpected errror occured") - DummyTicket.new(creator, destroyed_post.post_id).notify + if DestroyedPost.find_by(md5: md5) + errors.add(:base, "That image had been deleted from our site, and cannot be re-uploaded") + self.is_destroyed_reupload = true return end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 2fd4daa49..60c796219 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -187,6 +187,7 @@ class Ticket < ApplicationRecord end def validate_creator_is_not_limited + return if creator == User.system allowed = creator.can_ticket_with_reason if allowed != true errors.add(:creator, User.throttle_reason(allowed)) @@ -330,6 +331,7 @@ class Ticket < ApplicationRecord module NotificationMethods def create_dmail + return if creator == User.system should_send = saved_change_to_status? || (send_update_dmail.to_s.truthy? && saved_change_to_response?) return unless should_send diff --git a/app/models/upload.rb b/app/models/upload.rb index 2f78653ff..4445f2903 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -178,8 +178,8 @@ class Upload < ApplicationRecord end if (destroyed_post = DestroyedPost.find_by(md5: md5)) - errors.add(:base, "An unexpected errror occured") - DummyTicket.new(uploader, destroyed_post.post_id).notify + errors.add(:base, "That image had been deleted from our site, and cannot be re-uploaded") + destroyed_post.notify_reupload(uploader) return end diff --git a/app/views/admin/destroyed_posts/index.html.erb b/app/views/admin/destroyed_posts/index.html.erb new file mode 100644 index 000000000..07af7e79d --- /dev/null +++ b/app/views/admin/destroyed_posts/index.html.erb @@ -0,0 +1,70 @@ +
ID | +Destroyed At | +Destroyer | +Uploader | +MD5 | +Reason | + <% if CurrentUser.user.is_bd_staff? %> ++ <% end %> + |
---|---|---|---|---|---|---|
<%= destroyed_post.post_id %> | +<%= compact_time(destroyed_post.created_at) %> | +
+ <%= link_to_user(destroyed_post.destroyer) %> + <%= link_to_ip(destroyed_post.destroyer_ip_addr) %> + |
+
+ <% if destroyed_post.uploader_id.present? %>
+ <%= link_to_user(destroyed_post.uploader) %>
+ <% end %> + <% if destroyed_post.uploader_ip_addr.present? %> + <%= link_to_ip(destroyed_post.uploader_ip_addr) %> + <% end %> + |
+ + <%= destroyed_post.md5 %> + | ++ <%= destroyed_post.reason %> + | + <% if CurrentUser.user.is_bd_staff? %> ++ <% if destroyed_post.notify %> + <%= link_to("Disable Notifications", admin_destroyed_post_path(id: destroyed_post.post_id, destroyed_post: { notify: "false" }), method: :put) %> + <% else %> + <%= link_to("Enable Notifications", admin_destroyed_post_path(id: destroyed_post.post_id, destroyed_post: { notify: "true" }), method: :put) %> + <% end %> + | + <% end %> +