217 lines
7.6 KiB
Ruby
217 lines
7.6 KiB
Ruby
|
|
class Bookmark < ApplicationRecord
|
||
|
|
include Collectible
|
||
|
|
include Searchable
|
||
|
|
include Responder
|
||
|
|
include Taggable
|
||
|
|
|
||
|
|
belongs_to :bookmarkable, polymorphic: true, inverse_of: :bookmarks
|
||
|
|
belongs_to :pseud, optional: false
|
||
|
|
|
||
|
|
validates_length_of :bookmarker_notes,
|
||
|
|
maximum: ArchiveConfig.NOTES_MAX, too_long: ts("must be less than %{max} letters long.", max: ArchiveConfig.NOTES_MAX)
|
||
|
|
|
||
|
|
validate :not_already_bookmarked_by_user, on: :create
|
||
|
|
def not_already_bookmarked_by_user
|
||
|
|
return unless self.pseud && self.bookmarkable
|
||
|
|
|
||
|
|
return if self.pseud.user.bookmarks.where(bookmarkable: self.bookmarkable).empty?
|
||
|
|
|
||
|
|
errors.add(:base, ts("You have already bookmarked that."))
|
||
|
|
end
|
||
|
|
|
||
|
|
validate :check_new_external_work
|
||
|
|
def check_new_external_work
|
||
|
|
return unless bookmarkable.is_a?(ExternalWork) && bookmarkable.new_record?
|
||
|
|
|
||
|
|
errors.add(:base, "Fandom tag is required") if bookmarkable.fandom_string.blank?
|
||
|
|
|
||
|
|
return if bookmarkable.valid?
|
||
|
|
|
||
|
|
bookmarkable.errors.full_messages.each do |message|
|
||
|
|
errors.add(:base, message)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
# renaming scope :public -> :is_public because otherwise it overlaps with the "public" keyword
|
||
|
|
scope :is_public, -> { where(private: false, hidden_by_admin: false) }
|
||
|
|
scope :not_public, -> { where(private: true) }
|
||
|
|
scope :not_private, -> { where(private: false) }
|
||
|
|
scope :since, lambda { |*args| where("bookmarks.created_at > ?", (args.first || 1.week.ago)) }
|
||
|
|
scope :recs, -> { where(rec: true) }
|
||
|
|
scope :order_by_created_at, -> { order("created_at DESC") }
|
||
|
|
|
||
|
|
scope :join_work, -> {
|
||
|
|
joins("LEFT JOIN works ON (bookmarks.bookmarkable_id = works.id AND bookmarks.bookmarkable_type = 'Work')").
|
||
|
|
merge(Work.visible_to_all)
|
||
|
|
}
|
||
|
|
|
||
|
|
scope :join_series, -> {
|
||
|
|
joins("LEFT JOIN series ON (bookmarks.bookmarkable_id = series.id AND bookmarks.bookmarkable_type = 'Series')").
|
||
|
|
merge(Series.visible_to_all)
|
||
|
|
}
|
||
|
|
|
||
|
|
scope :join_external_works, -> {
|
||
|
|
joins("LEFT JOIN external_works ON (bookmarks.bookmarkable_id = external_works.id AND bookmarks.bookmarkable_type = 'ExternalWork')").
|
||
|
|
merge(ExternalWork.visible_to_all)
|
||
|
|
}
|
||
|
|
|
||
|
|
scope :join_bookmarkable, -> {
|
||
|
|
joins("LEFT JOIN works ON (bookmarks.bookmarkable_id = works.id AND bookmarks.bookmarkable_type = 'Work')
|
||
|
|
LEFT JOIN series ON (bookmarks.bookmarkable_id = series.id AND bookmarks.bookmarkable_type = 'Series')
|
||
|
|
LEFT JOIN external_works ON (bookmarks.bookmarkable_id = external_works.id AND bookmarks.bookmarkable_type = 'ExternalWork')")
|
||
|
|
}
|
||
|
|
|
||
|
|
scope :visible_to_all, -> {
|
||
|
|
is_public.with_bookmarkable_visible_to_all
|
||
|
|
}
|
||
|
|
|
||
|
|
scope :visible_to_registered_user, -> {
|
||
|
|
is_public.with_bookmarkable_visible_to_registered_user
|
||
|
|
}
|
||
|
|
|
||
|
|
# Scope for retrieving bookmarks with a bookmarkable visible to registered
|
||
|
|
# users (regardless of the bookmark's hidden_by_admin/private status).
|
||
|
|
scope :with_bookmarkable_visible_to_registered_user, -> {
|
||
|
|
join_bookmarkable.where(
|
||
|
|
"(works.posted = 1 AND works.hidden_by_admin = 0) OR
|
||
|
|
(series.hidden_by_admin = 0) OR
|
||
|
|
(external_works.hidden_by_admin = 0)"
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Scope for retrieving bookmarks with a bookmarkable visible to logged-out
|
||
|
|
# users (regardless of the bookmark's hidden_by_admin/private status).
|
||
|
|
scope :with_bookmarkable_visible_to_all, -> {
|
||
|
|
join_bookmarkable.where(
|
||
|
|
"(works.posted = 1 AND works.restricted = 0 AND works.hidden_by_admin = 0) OR
|
||
|
|
(series.restricted = 0 AND series.hidden_by_admin = 0) OR
|
||
|
|
(external_works.hidden_by_admin = 0)"
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Scope for retrieving bookmarks with a missing bookmarkable (regardless of
|
||
|
|
# the bookmark's hidden_by_admin/private status).
|
||
|
|
scope :with_missing_bookmarkable, -> {
|
||
|
|
join_bookmarkable.where(
|
||
|
|
"works.id IS NULL AND series.id IS NULL AND external_works.id IS NULL"
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
scope :visible_to_admin, -> { not_private }
|
||
|
|
|
||
|
|
scope :latest, -> { is_public.order_by_created_at.limit(ArchiveConfig.ITEMS_PER_PAGE).join_work }
|
||
|
|
|
||
|
|
scope :for_blurb, -> { includes(:bookmarkable, :tags, :collections, pseud: [:user]) }
|
||
|
|
|
||
|
|
# a complicated dynamic scope here:
|
||
|
|
# if the user is an Admin, we use the "visible_to_admin" scope
|
||
|
|
# if the user is not a logged-in User, we use the "visible_to_all" scope
|
||
|
|
# otherwise, we use a join to get userids and then get all posted works that are either unhidden OR belong to this user.
|
||
|
|
# Note: in that last case we have to use select("DISTINCT works.") because of cases where the same user appears twice
|
||
|
|
# on a work.
|
||
|
|
scope :visible_to_user, lambda {|user|
|
||
|
|
if user.is_a?(Admin)
|
||
|
|
visible_to_admin
|
||
|
|
elsif !user.is_a?(User)
|
||
|
|
visible_to_all
|
||
|
|
else
|
||
|
|
select("DISTINCT bookmarks.*").
|
||
|
|
visible_to_registered_user.
|
||
|
|
joins("JOIN pseuds as p1 ON p1.id = bookmarks.pseud_id JOIN users ON users.id = p1.user_id").
|
||
|
|
where("bookmarks.hidden_by_admin = 0 OR users.id = ?", user.id)
|
||
|
|
end
|
||
|
|
}
|
||
|
|
|
||
|
|
# Use the current user to determine what works are visible
|
||
|
|
scope :visible, -> { visible_to_user(User.current_user) }
|
||
|
|
|
||
|
|
before_destroy :invalidate_bookmark_count
|
||
|
|
after_save :invalidate_bookmark_count, :update_pseud_index
|
||
|
|
|
||
|
|
after_create :update_work_stats
|
||
|
|
after_destroy :update_work_stats, :update_pseud_index
|
||
|
|
|
||
|
|
def invalidate_bookmark_count
|
||
|
|
work = Work.where(id: self.bookmarkable_id)
|
||
|
|
if work.present? && self.bookmarkable_type == 'Work'
|
||
|
|
work.first.invalidate_public_bookmarks_count
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
# We index the bookmark count, so if it should change, update the pseud
|
||
|
|
def update_pseud_index
|
||
|
|
return unless destroyed? || saved_change_to_id? || saved_change_to_private? || saved_change_to_hidden_by_admin?
|
||
|
|
IndexQueue.enqueue_id(Pseud, pseud_id, :background)
|
||
|
|
end
|
||
|
|
|
||
|
|
def visible?(current_user=User.current_user)
|
||
|
|
return true if current_user == self.pseud.user
|
||
|
|
unless current_user == :false || !current_user
|
||
|
|
# Admins should not see private bookmarks
|
||
|
|
return true if current_user.is_a?(Admin) && self.private == false
|
||
|
|
end
|
||
|
|
if !(self.private? || self.hidden_by_admin?)
|
||
|
|
if self.bookmarkable.nil?
|
||
|
|
# only show bookmarks for deleted works to the user who
|
||
|
|
# created the bookmark
|
||
|
|
return true if pseud.user == current_user
|
||
|
|
else
|
||
|
|
if self.bookmarkable_type == 'Work' || self.bookmarkable_type == 'Series' || self.bookmarkable_type == 'ExternalWork'
|
||
|
|
return true if self.bookmarkable.visible?(current_user)
|
||
|
|
else
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
# Returns the number of bookmarks on an item visible to the current user
|
||
|
|
def self.count_visible_bookmarks(bookmarkable, current_user=:false)
|
||
|
|
bookmarkable.bookmarks.visible.size
|
||
|
|
end
|
||
|
|
|
||
|
|
# TODO: Is this necessary anymore?
|
||
|
|
before_destroy :save_parent_info
|
||
|
|
|
||
|
|
# Because of the way the elasticsearch parent/child index is set up, we need
|
||
|
|
# to know what the bookmarkable type and id was in order to delete the
|
||
|
|
# bookmark from the index after it's been deleted from the database
|
||
|
|
def save_parent_info
|
||
|
|
expire_time = (Time.now + 2.weeks).to_i
|
||
|
|
REDIS_GENERAL.setex(
|
||
|
|
"deleted_bookmark_parent_#{self.id}",
|
||
|
|
expire_time,
|
||
|
|
"#{bookmarkable_id}-#{bookmarkable_type.underscore}"
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
#################################
|
||
|
|
## SEARCH #######################
|
||
|
|
#################################
|
||
|
|
|
||
|
|
def document_json
|
||
|
|
BookmarkIndexer.new({}).document(self)
|
||
|
|
end
|
||
|
|
|
||
|
|
def bookmarker
|
||
|
|
pseud.try(:byline)
|
||
|
|
end
|
||
|
|
|
||
|
|
def with_notes
|
||
|
|
bookmarker_notes.present?
|
||
|
|
end
|
||
|
|
|
||
|
|
def collection_ids
|
||
|
|
approved_collections.pluck(:id, :parent_id).flatten.uniq.compact
|
||
|
|
end
|
||
|
|
|
||
|
|
def bookmarkable_date
|
||
|
|
if bookmarkable.respond_to?(:revised_at)
|
||
|
|
bookmarkable.revised_at
|
||
|
|
elsif bookmarkable.respond_to?(:updated_at)
|
||
|
|
bookmarkable.updated_at
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|