forked from e621ng/e621ng
344 lines
10 KiB
Ruby
344 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class PostSet < ApplicationRecord
|
|
array_attribute :post_ids, parse: %r{(?:https://(?:e621|e926)\.net/posts/)?(\d+)}i, cast: :to_i
|
|
|
|
has_many :post_set_maintainers, dependent: :destroy do
|
|
def in_cooldown(user)
|
|
where(creator_id: user.id, status: 'cooldown').where('created_at < ?', 24.hours.ago)
|
|
end
|
|
def active
|
|
where(status: 'approved')
|
|
end
|
|
def pending
|
|
where(status: 'pending')
|
|
end
|
|
def banned
|
|
where(status: 'banned')
|
|
end
|
|
end
|
|
has_many :maintainers, class_name: "User", through: :post_set_maintainers, source: :user
|
|
belongs_to_creator
|
|
user_status_counter :set_count
|
|
|
|
before_validation :normalize_shortname
|
|
validates :name, length: { in: 3..100, message: "must be between three and one hundred characters long" }
|
|
validates :name, :shortname, uniqueness: { case_sensitive: false, message: "is already taken" }, if: :if_names_changed?
|
|
validates :shortname, length: { in: 3..50, message: 'must be between three and fifty characters long' }
|
|
validates :shortname, format: { with: /\A[\w]+\z/, message: "must only contain numbers, lowercase letters, and underscores" }
|
|
validates :shortname, format: { with: /\A\d*[a-z_][\w]*\z/, message: "must contain at least one lowercase letter or underscore" }
|
|
validates :description, length: { maximum: Danbooru.config.pool_descr_max_size }
|
|
validate :validate_number_of_posts
|
|
validate :can_make_public, if: :is_public_changed?
|
|
validate :set_per_hour_limit, on: :create
|
|
validate :can_create_new_set_limit, on: :create
|
|
|
|
after_update :send_maintainer_public_dmails
|
|
before_destroy :send_maintainer_destroy_dmails
|
|
before_save :update_post_count
|
|
after_save :synchronize, if: :saved_change_to_post_ids?
|
|
|
|
attr_accessor :skip_sync
|
|
|
|
def self.name_to_id(name)
|
|
if name =~ /\A\d+\z/
|
|
name.to_i
|
|
else
|
|
PostSet.where("lower(shortname) = ?", name.downcase.tr(" ", "_")).pick(:id).to_i
|
|
end
|
|
end
|
|
|
|
def self.visible(user = CurrentUser.user)
|
|
return where('is_public = true') if user.nil?
|
|
return all if user.is_moderator?
|
|
where('is_public = true OR creator_id = ?', user.id)
|
|
end
|
|
|
|
def self.owned(user = CurrentUser.user)
|
|
where('creator_id = ?', user.id)
|
|
end
|
|
|
|
def self.active_maintainer(user = CurrentUser.user)
|
|
joins(:post_set_maintainers).where(post_set_maintainers: {status: 'approved', user_id: user.id})
|
|
end
|
|
|
|
def if_names_changed?
|
|
name_changed? || shortname_changed?
|
|
end
|
|
|
|
def saved_change_to_watched_attributes?
|
|
saved_change_to_name? || saved_change_to_shortname? || saved_change_to_description? || saved_change_to_transfer_on_delete?
|
|
end
|
|
|
|
module ValidationMethods
|
|
def normalize_shortname
|
|
if shortname_changed?
|
|
self.shortname.downcase!
|
|
end
|
|
end
|
|
|
|
def send_maintainer_public_dmails
|
|
if RateLimiter.check_limit("set.public.#{id}", 1, 24.hours)
|
|
return
|
|
end
|
|
if is_public_changed? && !is_public # If set was made private
|
|
RateLimiter.hit("set.public.#{id}", 24.hours)
|
|
PostSetMaintainer.active.where(post_set_id: id).each do |maintainer|
|
|
Dmail.create_automated(to_id: maintainer.user_id, title: "A set you maintain was made private",
|
|
body: "The set \"#{name}\":#{post_set_path(self)} by \"#{creator.name}\":#{user_path(creator)} that you maintain was set to private. You will not be able to view, add posts, or remove posts from the set until the owner makes it public again.")
|
|
end
|
|
|
|
PostSetMaintainer.pending.where(post_set_id: id).delete
|
|
elsif is_public_changed? && is_public # If set was made public
|
|
RateLimiter.hit("set.public.#{id}", 24.hours)
|
|
PostSetMaintainer.active.where(post_set_id: id).each do |maintainer|
|
|
Dmail.create_automated(to_id: maintainer.user_id, titlet: "A private set you had maintained was made public again",
|
|
body: "The set \"#{name}\":#{post_set_path(self)} by \"#{creaator.name}\":#{user_path(creator)} that you previously maintained was made public again. You are now able to view the set and add/remove posts.")
|
|
end
|
|
end
|
|
end
|
|
|
|
def send_maintainer_destroy_dmails
|
|
PostSetMaintainer.active.where(post_set_id: id).each do |maintainer|
|
|
Dmail.create_automated(to_id: maintainer.user_id,
|
|
title: "A set you maintain was deleted",
|
|
body: "The set #{name} by \"#{creator.name}\":#{user_path(creator)} that you maintain was deleted.")
|
|
end
|
|
end
|
|
|
|
def can_make_public
|
|
if is_public && creator.younger_than(3.days) && !creator.is_janitor?
|
|
errors.add(:base, "Can't make a set public until your account is at least three days old")
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
def can_create_new_set_limit
|
|
if PostSet.where(creator_id: creator.id).count() >= 75
|
|
errors.add(:base, "You can only create 75 sets.")
|
|
return false
|
|
end
|
|
true
|
|
end
|
|
|
|
def set_per_hour_limit
|
|
if PostSet.where("created_at > ? AND creator_id = ?", 1.hour.ago, creator.id).count() > 6 && !creator.is_janitor?
|
|
errors.add(:base, "You have already created 6 sets in the last hour.")
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
def validate_number_of_posts
|
|
post_ids_before = post_ids_before_last_save || post_ids_was
|
|
added = post_ids - post_ids_before
|
|
return unless added.size > 0
|
|
max = Danbooru.config.set_post_limit(CurrentUser.user)
|
|
if post_ids.size > max
|
|
errors.add(:base, "Sets can only have up to #{ActiveSupport::NumberHelper.number_to_delimited(max)} posts each")
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
end
|
|
|
|
module AccessMethods
|
|
def can_view?(user)
|
|
is_public || is_owner?(user) || user.is_moderator?
|
|
end
|
|
|
|
def can_edit_settings?(user)
|
|
is_owner?(user) || user.is_admin?
|
|
end
|
|
|
|
def can_edit_posts?(user)
|
|
can_edit_settings?(user) || (is_maintainer?(user) && is_public)
|
|
end
|
|
|
|
def is_maintainer?(user)
|
|
return false if user.is_blocked?
|
|
post_set_maintainers.where(user_id: user.id, status: 'approved').count() > 0
|
|
end
|
|
|
|
def is_invited?(user)
|
|
post_set_maintainers.where(user_id: user.id, status: 'pending').count() > 0
|
|
end
|
|
|
|
def is_blocked?(user)
|
|
post_set_maintainers.where(user_id: user.id, status: 'blocked').count() > 0
|
|
end
|
|
|
|
def is_owner?(user)
|
|
return false if user.is_blocked?
|
|
creator_id == user.id
|
|
end
|
|
end
|
|
|
|
module PostMethods
|
|
def contains?(post_id)
|
|
post_ids.include?(post_id)
|
|
end
|
|
|
|
def page_number(post_id)
|
|
post_ids.find_index(post_id).to_i + 1
|
|
end
|
|
|
|
def add(ids)
|
|
real_ids = Post.select(:id).where(id: ids)
|
|
real_ids.each do |post|
|
|
next if contains?(post.id)
|
|
self.post_ids = post_ids + [post.id]
|
|
end
|
|
end
|
|
|
|
def add!(post)
|
|
return if post.nil?
|
|
return if post.id.nil?
|
|
return if contains?(post.id)
|
|
|
|
with_lock do
|
|
reload
|
|
self.skip_sync = true
|
|
update(post_ids: post_ids + [post.id])
|
|
raise(ActiveRecord::Rollback) unless valid?
|
|
post.add_set!(self, true)
|
|
post.save
|
|
end
|
|
end
|
|
|
|
def remove(ids)
|
|
self.post_ids = post_ids - ids
|
|
end
|
|
|
|
def remove!(post)
|
|
return unless contains?(post.id)
|
|
|
|
with_lock do
|
|
reload
|
|
self.skip_sync = true
|
|
update(post_ids: post_ids - [post.id])
|
|
raise(ActiveRecord::Rollback) unless valid?
|
|
post.remove_set!(self)
|
|
post.save
|
|
end
|
|
end
|
|
|
|
def post_count
|
|
post_ids.size
|
|
end
|
|
|
|
def first_post?(post_id)
|
|
post_id == post_ids.first
|
|
end
|
|
|
|
def last_post?(post_id)
|
|
post_id == post_ids.last
|
|
end
|
|
|
|
def previous_post_id(post_id)
|
|
return nil if first_post?(post_id) || !contains?(post_id)
|
|
|
|
n = post_ids.index(post_id) - 1
|
|
post_ids[n]
|
|
end
|
|
|
|
def next_post_id(post_id)
|
|
return nil if last_post?(post_id) || !contains?(post_id)
|
|
|
|
n = post_ids.index(post_id) + 1
|
|
post_ids[n]
|
|
end
|
|
|
|
def synchronize
|
|
return if skip_sync == true
|
|
post_ids_before = post_ids_before_last_save || post_ids_was
|
|
added = post_ids - post_ids_before
|
|
removed = post_ids_before - post_ids
|
|
|
|
added_posts = Post.where(id: added)
|
|
added_posts.find_each do |post|
|
|
post.add_set!(self, true)
|
|
post.save
|
|
end
|
|
|
|
removed_posts = Post.where(id: removed)
|
|
removed_posts.find_each do |post|
|
|
post.remove_set!(self)
|
|
post.save
|
|
end
|
|
end
|
|
|
|
def synchronize!
|
|
synchronize
|
|
save if will_save_change_to_post_ids?
|
|
end
|
|
|
|
def normalize_post_ids
|
|
self.post_ids = post_ids.uniq
|
|
end
|
|
|
|
def update_post_count
|
|
normalize_post_ids
|
|
self.post_count = post_ids.size
|
|
end
|
|
end
|
|
|
|
module SearchMethods
|
|
def selected_first(current_set_id)
|
|
return where("true") if current_set_id.blank?
|
|
current_set_id = current_set_id.to_i
|
|
reorder(Arel.sql("(case post_sets.id when #{current_set_id} then 0 else 1 end), post_sets.name"))
|
|
end
|
|
|
|
def where_has_post(post_id)
|
|
where('post_ids @> ARRAY[?]::integer[]', post_id)
|
|
end
|
|
|
|
def where_has_maintainer(user_id)
|
|
joins(:maintainers).where('(post_set_maintainers.user_id = ? AND post_set_maintainers.status = ?) OR creator_id = ?', user_id, 'approved', user_id)
|
|
end
|
|
|
|
def search(params)
|
|
q = super
|
|
|
|
q = q.where_user(:creator_id, :creator, params)
|
|
|
|
if params[:name].present?
|
|
q = q.attribute_matches(:name, params[:name], convert_to_wildcard: true)
|
|
end
|
|
if params[:shortname].present?
|
|
q = q.where_ilike(:shortname, params[:shortname])
|
|
end
|
|
if params[:is_public].present?
|
|
q = q.attribute_matches(:is_public, params[:is_public])
|
|
end
|
|
|
|
case params[:order]
|
|
when 'name'
|
|
q = q.order(:name, id: :desc)
|
|
when 'shortname'
|
|
q = q.order(:shortname, id: :desc)
|
|
when 'postcount', 'post_count'
|
|
q = q.order(post_count: :desc, id: :desc)
|
|
when 'created_at'
|
|
q = q.order(:id)
|
|
when 'update', 'updated_at'
|
|
q = q.order(updated_at: :desc)
|
|
else
|
|
q = q.order(id: :desc)
|
|
end
|
|
|
|
q
|
|
end
|
|
end
|
|
|
|
extend SearchMethods
|
|
include ValidationMethods
|
|
include AccessMethods
|
|
include PostMethods
|
|
end
|