forked from e621ng/e621ng
408 lines
9.9 KiB
Ruby
408 lines
9.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class Ticket < ApplicationRecord
|
|
belongs_to_creator
|
|
user_status_counter :ticket_count
|
|
belongs_to :claimant, class_name: "User", optional: true
|
|
belongs_to :handler, class_name: "User", optional: true
|
|
belongs_to :accused, class_name: "User", optional: true
|
|
belongs_to :post_report_reason, foreign_key: "report_reason", optional: true
|
|
before_validation :initialize_fields, on: :create
|
|
after_initialize :validate_type
|
|
after_initialize :classify
|
|
normalizes :reason, with: ->(reason) { reason.gsub("\r\n", "\n") }
|
|
validates :qtype, presence: true
|
|
validates :reason, presence: true
|
|
validates :reason, length: { minimum: 2, maximum: Danbooru.config.ticket_max_size }
|
|
validates :response, length: { minimum: 2 }, on: :update
|
|
enum :status, %i[pending partial approved].index_with(&:to_s)
|
|
after_update :log_update
|
|
after_update :create_dmail
|
|
validate :validate_content_exists, on: :create
|
|
validate :validate_creator_is_not_limited, on: :create
|
|
|
|
scope :for_creator, ->(uid) {where('creator_id = ?', uid)}
|
|
|
|
attr_accessor :record_type, :send_update_dmail
|
|
|
|
# Permissions Table
|
|
#
|
|
# | Type | Can Create | Visible |
|
|
# |:----------:|:-------------------:|:--------------------:|
|
|
# | Blip | Visible | Janitor+ / Creator |
|
|
# | Comment | Visible | Janitor+ / Creator |
|
|
# | Dmail | Visible & Recipient | Moderator+ / Creator |
|
|
# | Forum Post | Visible | Janitor+ / Creator |
|
|
# | Pool | Any | Janitor+ / Creator |
|
|
# | Post | Any | Janitor+ / Creator |
|
|
# | Post Set | Visible | Janitor+ / Creator |
|
|
# | User | Any | Moderator+ / Creator |
|
|
# | Wiki Page | Any | Janitor+ / Creator |
|
|
# | Other | None | Moderator+ / Creator |
|
|
|
|
module TicketTypes
|
|
module Blip
|
|
def can_create_for?(user)
|
|
content&.visible_to?(user)
|
|
end
|
|
|
|
def can_view?(user)
|
|
(content&.visible_to?(user) && user.is_janitor?) || (user.id == creator_id)
|
|
end
|
|
end
|
|
|
|
module Comment
|
|
def can_create_for?(user)
|
|
content&.visible_to?(user)
|
|
end
|
|
|
|
def can_view?(user)
|
|
(content&.visible_to?(user) && user.is_janitor?) || (user.id == creator_id)
|
|
end
|
|
end
|
|
|
|
module Dmail
|
|
def can_create_for?(user)
|
|
content&.visible_to?(user) && content.to_id == user.id
|
|
end
|
|
|
|
def can_view?(user)
|
|
user.is_moderator? || (user.id == creator_id)
|
|
end
|
|
|
|
def bot_target_name
|
|
content&.from&.name
|
|
end
|
|
end
|
|
|
|
module Forum
|
|
# FIXME: Remove this by renaming the qtype value to the correct one
|
|
def model
|
|
::ForumPost
|
|
end
|
|
|
|
def can_create_for?(user)
|
|
content.visible?(user)
|
|
end
|
|
|
|
def can_view?(user)
|
|
(content&.visible?(user) && user.is_janitor?) || (user.id == creator_id)
|
|
end
|
|
end
|
|
|
|
module Pool
|
|
def can_create_for?(user)
|
|
true
|
|
end
|
|
|
|
def bot_target_name
|
|
content&.name
|
|
end
|
|
|
|
def can_view?(user)
|
|
user.is_janitor? || (user.id == creator_id)
|
|
end
|
|
end
|
|
|
|
module Post
|
|
def self.extended(m)
|
|
m.class_eval do
|
|
validates :report_reason, presence: true
|
|
end
|
|
end
|
|
|
|
def subject
|
|
reason.split("\n")[0] || "Unknown Report Type"
|
|
end
|
|
|
|
def can_create_for?(user)
|
|
true
|
|
end
|
|
|
|
def bot_target_name
|
|
content&.uploader&.name
|
|
end
|
|
|
|
def can_view?(user)
|
|
user.is_janitor? || (user.id == creator_id)
|
|
end
|
|
end
|
|
|
|
module Set
|
|
def model
|
|
::PostSet
|
|
end
|
|
|
|
def can_create_for?(user)
|
|
content&.can_view?(user)
|
|
end
|
|
|
|
def can_view?(user)
|
|
(content&.can_view?(user) && user.is_janitor?) || (user.id == creator_id)
|
|
end
|
|
end
|
|
|
|
module User
|
|
def can_create_for?(user)
|
|
true
|
|
end
|
|
|
|
def can_view?(user)
|
|
user.is_moderator? || user.id == creator_id
|
|
end
|
|
|
|
def bot_target_name
|
|
content&.name
|
|
end
|
|
end
|
|
|
|
module Wiki
|
|
def model
|
|
::WikiPage
|
|
end
|
|
|
|
def can_create_for?(user)
|
|
true
|
|
end
|
|
|
|
def bot_target_name
|
|
content&.title
|
|
end
|
|
|
|
def can_view?(user)
|
|
user.is_janitor? || (user.id == creator_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
module APIMethods
|
|
def hidden_attributes
|
|
hidden = []
|
|
hidden += %i[claimant_id] unless CurrentUser.is_moderator?
|
|
hidden += %i[creator_id] unless can_see_reporter?(CurrentUser)
|
|
super + hidden
|
|
end
|
|
end
|
|
|
|
module ValidationMethods
|
|
def validate_type
|
|
valid_types = TicketTypes.constants.map { |v| v.to_s.downcase }
|
|
errors.add(:qtype, "is not valid") if valid_types.exclude?(qtype)
|
|
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))
|
|
return false
|
|
end
|
|
true
|
|
end
|
|
|
|
def validate_content_exists
|
|
errors.add model.name.underscore.to_sym, "does not exist" if content.nil?
|
|
end
|
|
|
|
def initialize_fields
|
|
self.status = "pending"
|
|
case qtype
|
|
when "blip"
|
|
self.accused_id = Blip.find(disp_id).creator_id
|
|
when "forum"
|
|
self.accused_id = ForumPost.find(disp_id).creator_id
|
|
when "comment"
|
|
self.accused_id = Comment.find(disp_id).creator_id
|
|
when "dmail"
|
|
self.accused_id = Dmail.find(disp_id).from_id
|
|
when "user"
|
|
self.accused_id = disp_id
|
|
end
|
|
end
|
|
end
|
|
|
|
module SearchMethods
|
|
def for_accused(user_id)
|
|
where(accused_id: user_id)
|
|
end
|
|
|
|
def active
|
|
where(status: %w[pending partial])
|
|
end
|
|
|
|
def visible(user)
|
|
if user.is_moderator?
|
|
all
|
|
elsif user.is_janitor?
|
|
for_creator(user.id).or(where.not(qtype: %w[Dmail User]))
|
|
else
|
|
for_creator(user.id)
|
|
end
|
|
end
|
|
|
|
def search(params)
|
|
q = super.includes(:creator).includes(:claimant)
|
|
|
|
q = q.where_user(:creator_id, :creator, params)
|
|
q = q.where_user(:claimant_id, :claimant, params)
|
|
q = q.where_user(:accused_id, :accused, params)
|
|
|
|
if params[:qtype].present?
|
|
q = q.where('qtype = ?', params[:qtype])
|
|
end
|
|
|
|
if params[:reason].present?
|
|
q = q.attribute_matches(:reason, params[:reason])
|
|
end
|
|
|
|
if params[:status].present?
|
|
case params[:status]
|
|
when "pending_claimed"
|
|
q = q.where('status = ? and claimant_id is not null', 'pending')
|
|
when "pending_unclaimed"
|
|
q = q.where('status = ? and claimant_id is null', 'pending')
|
|
else
|
|
q = q.where('status = ?', params[:status])
|
|
end
|
|
end
|
|
|
|
if params[:order].present?
|
|
q.apply_basic_order(params)
|
|
else
|
|
q.order(Arel.sql("CASE status WHEN 'pending' THEN 0 WHEN 'partial' THEN 1 ELSE 2 END ASC, id DESC"))
|
|
end
|
|
end
|
|
end
|
|
|
|
module ClassifyMethods
|
|
def classify
|
|
extend(TicketTypes.const_get(qtype.camelize)) if TicketTypes.constants.map(&:to_s).include?(qtype&.camelize)
|
|
end
|
|
end
|
|
|
|
def content=(new_content)
|
|
@content = new_content
|
|
self.disp_id = content&.id
|
|
end
|
|
|
|
def content
|
|
@content ||= model.find_by(id: disp_id)
|
|
end
|
|
|
|
def bot_target_name
|
|
content&.creator&.name
|
|
end
|
|
|
|
def can_view?(user)
|
|
user.is_janitor?
|
|
end
|
|
|
|
def can_see_reporter?(user)
|
|
user.is_moderator? || (user.id == creator_id)
|
|
end
|
|
|
|
def can_create_for?(user)
|
|
false
|
|
end
|
|
|
|
def model
|
|
qtype.classify.constantize
|
|
end
|
|
|
|
def type_title
|
|
"#{model.name.titlecase} Complaint"
|
|
end
|
|
|
|
def subject
|
|
if reason.length > 40
|
|
"#{reason[0, 38]}..."
|
|
else
|
|
reason
|
|
end
|
|
end
|
|
|
|
def open_duplicates
|
|
Ticket.where('qtype = ? and disp_id = ? and status = ?', qtype, disp_id, 'pending')
|
|
end
|
|
|
|
def warnable?
|
|
content.respond_to?(:user_warned!) && !content.was_warned? && pending?
|
|
end
|
|
|
|
module ClaimMethods
|
|
def claim!(user = CurrentUser)
|
|
transaction do
|
|
ModAction.log(:ticket_claim, {ticket_id: id})
|
|
update_attribute(:claimant_id, user.id)
|
|
push_pubsub('claim')
|
|
end
|
|
end
|
|
|
|
def unclaim!(user = CurrentUser)
|
|
transaction do
|
|
ModAction.log(:ticket_unclaim, {ticket_id: id})
|
|
update_attribute(:claimant_id, nil)
|
|
push_pubsub('unclaim')
|
|
end
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
msg = <<~MSG.chomp
|
|
"Your ticket":#{Rails.application.routes.url_helpers.ticket_path(self)} has been updated by #{handler.pretty_name}.
|
|
Ticket Status: #{status}
|
|
|
|
Response: #{response}
|
|
MSG
|
|
Dmail.create_split(
|
|
from_id: CurrentUser.id,
|
|
to_id: creator.id,
|
|
title: "Your ticket has been updated#{" to #{status}" if saved_change_to_status?}",
|
|
body: msg,
|
|
bypass_limits: true,
|
|
)
|
|
end
|
|
|
|
def log_update
|
|
return unless saved_change_to_response? || saved_change_to_status?
|
|
|
|
ModAction.log(:ticket_update, { ticket_id: id })
|
|
end
|
|
end
|
|
|
|
module PubSubMethods
|
|
def pubsub_hash(action)
|
|
{
|
|
action: action,
|
|
ticket: {
|
|
id: id,
|
|
user_id: creator_id,
|
|
user: creator_id ? User.id_to_name(creator_id) : nil,
|
|
claimant: claimant_id ? User.id_to_name(claimant_id) : nil,
|
|
target: bot_target_name,
|
|
status: status,
|
|
category: qtype,
|
|
reason: reason,
|
|
}
|
|
}
|
|
end
|
|
|
|
def push_pubsub(action)
|
|
Cache.redis.publish("ticket_updates", pubsub_hash(action).to_json)
|
|
end
|
|
end
|
|
|
|
include ClassifyMethods
|
|
include ValidationMethods
|
|
include APIMethods
|
|
include ClaimMethods
|
|
include NotificationMethods
|
|
include PubSubMethods
|
|
extend SearchMethods
|
|
end
|