Users will still see their own trades in the list and in the counts, and support staff will still see them too!
217 lines
6.9 KiB
Ruby
217 lines
6.9 KiB
Ruby
class User < ApplicationRecord
|
|
include PrettyParam
|
|
|
|
PreviewTopContributorsCount = 3
|
|
|
|
belongs_to :auth_user, foreign_key: :remote_id, inverse_of: :user
|
|
delegate :disconnect_neopass, :uses_neopass?, to: :auth_user
|
|
|
|
has_many :closet_hangers
|
|
has_many :closet_lists
|
|
has_many :closeted_items, through: :closet_hangers, source: :item
|
|
has_many :contributions
|
|
has_many :neopets_connections
|
|
has_many :outfits
|
|
|
|
# TODO: When `owned_items` and `wanted_items` are merged, they override one
|
|
# another instead of correctly returning an empty set. Is this a Rails bug
|
|
# that gets fixed down the line once we finish upgrading, or...?
|
|
has_many :owned_items, -> { where(ClosetHanger.arel_table[:owned].eq(true)) },
|
|
through: :closet_hangers, source: :item
|
|
has_many :wanted_items, -> { where(ClosetHanger.arel_table[:owned].eq(false)) },
|
|
through: :closet_hangers, source: :item
|
|
|
|
belongs_to :contact_neopets_connection, class_name: 'NeopetsConnection', optional: true
|
|
|
|
scope :top_contributors, -> { order('points DESC').where('points > 0') }
|
|
|
|
after_update :sync_name_with_auth_user!, if: :saved_change_to_name?
|
|
after_update :log_trade_activity, if: -> user {
|
|
(user.saved_change_to_owned_closet_hangers_visibility? &&
|
|
user.owned_closet_hangers_visibility >= ClosetVisibility[:trading].id) ||
|
|
(user.saved_change_to_wanted_closet_hangers_visibility? &&
|
|
user.wanted_closet_hangers_visibility >= ClosetVisibility[:trading].id)
|
|
}
|
|
|
|
def sync_name_with_auth_user!
|
|
auth_user.name = name
|
|
auth_user.save!
|
|
end
|
|
|
|
def admin?
|
|
name == 'matchu' # you know that's right.
|
|
end
|
|
|
|
def as_json
|
|
serializable_hash only: [:id, :name]
|
|
end
|
|
|
|
# Given info about a request, return whether that request is likely to be
|
|
# coming from the same person who owns this account.
|
|
def likely_is?(current_user, remote_ip)
|
|
current_user == self || auth_user.current_sign_in_ip == remote_ip
|
|
end
|
|
|
|
def unowned_items
|
|
# Join all items against our owned closet hangers, group by item ID, then
|
|
# only return those with zero matching hangers.
|
|
#
|
|
# TODO: It'd be nice to replace this with a `left_outer_joins` call in
|
|
# Rails 5+, but these conditions really do need to be part of the join:
|
|
# if we do them as a `where`, they prevent unmatching items from being
|
|
# returned in the first place.
|
|
#
|
|
# TODO: This crashes the query when combined with `unwanted_items`.
|
|
ch = ClosetHanger.arel_table.alias("owned_hangers")
|
|
Item.
|
|
joins(
|
|
"LEFT JOIN closet_hangers owned_hangers ON owned_hangers.item_id = items.id " +
|
|
"AND #{ch[:user_id].eq(self.id).to_sql} AND owned_hangers.owned = true"
|
|
).
|
|
group("items.id").having("COUNT(owned_hangers.id) = 0")
|
|
end
|
|
|
|
def unwanted_items
|
|
# See `unowned_items` above! We just change the `true` to `false`.
|
|
# TODO: This crashes the query when combined with `unowned_items`.
|
|
ch = ClosetHanger.arel_table.alias("wanted_hangers")
|
|
Item.
|
|
joins(
|
|
"LEFT JOIN closet_hangers wanted_hangers ON wanted_hangers.item_id = items.id " +
|
|
"AND #{ch[:user_id].eq(self.id).to_sql} AND wanted_hangers.owned = false"
|
|
).
|
|
group("items.id").having("COUNT(wanted_hangers.id) = 0")
|
|
end
|
|
|
|
def contribute!(pet)
|
|
new_contributions = []
|
|
pet.contributables.each do |contributable|
|
|
if contributable.new_record?
|
|
contribution = Contribution.new
|
|
contribution.contributed = contributable
|
|
contribution.user = self
|
|
new_contributions << contribution
|
|
end
|
|
end
|
|
new_points = 0 # temp assignment for scoping
|
|
Pet.transaction do
|
|
pet.save!
|
|
new_contributions.each do |contribution|
|
|
Rails.logger.debug("Saving contribution of #{contribution.contributed.inspect}: #{contribution.contributed_type.inspect}, #{contribution.contributed_id.inspect}")
|
|
begin
|
|
contribution.save!
|
|
rescue ActiveRecord::RecordNotSaved => e
|
|
raise ActiveRecord::RecordNotSaved, "#{e.message}, #{contribution.inspect}, #{contribution.valid?.inspect}, #{contribution.errors.inspect}"
|
|
end
|
|
end
|
|
new_points = new_contributions.map(&:point_value).inject(0, &:+)
|
|
self.points += new_points
|
|
begin
|
|
save!
|
|
rescue ActiveRecord::RecordNotSaved => e
|
|
raise ActiveRecord::RecordNotSaved, "#{e.message}, #{self.inspect}, #{self.valid?.inspect}, #{self.errors.inspect}"
|
|
end
|
|
end
|
|
new_points
|
|
end
|
|
|
|
def assign_closeted_to_items!(items)
|
|
# Assigning these items to a hash by ID means that we don't have to go
|
|
# N^2 searching the items list for items that match the given IDs or vice
|
|
# versa, and everything stays a lovely O(n)
|
|
items_by_id = items.group_by(&:id)
|
|
closet_hangers.where(:item_id => items_by_id.keys).each do |hanger|
|
|
items = items_by_id[hanger.item_id]
|
|
items.each do |item|
|
|
if hanger.owned?
|
|
item.owned = true
|
|
else
|
|
item.wanted = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def closet_hangers_groups_visible_to(user)
|
|
if user == self
|
|
[true, false]
|
|
else
|
|
public_closet_hangers_groups
|
|
end
|
|
end
|
|
|
|
def public_closet_hangers_groups
|
|
[].tap do |groups|
|
|
groups << true if owned_closet_hangers_visibility >= ClosetVisibility[:public].id
|
|
groups << false if wanted_closet_hangers_visibility >= ClosetVisibility[:public].id
|
|
end
|
|
end
|
|
|
|
def null_closet_list(owned)
|
|
owned ? null_owned_list : null_wanted_list
|
|
end
|
|
|
|
def null_owned_list
|
|
ClosetList::NullOwned.new(self)
|
|
end
|
|
|
|
def null_wanted_list
|
|
ClosetList::NullWanted.new(self)
|
|
end
|
|
|
|
def find_closet_list_by_id_or_null_owned(id_or_owned)
|
|
id_or_owned_str = id_or_owned.to_s
|
|
if id_or_owned_str == 'true'
|
|
null_owned_list
|
|
elsif id_or_owned_str == 'false'
|
|
null_wanted_list
|
|
else
|
|
self.closet_lists.find id_or_owned
|
|
end
|
|
end
|
|
|
|
def neopets_usernames
|
|
neopets_connections.map(&:neopets_username)
|
|
end
|
|
|
|
def contact_neopets_username?
|
|
contact_neopets_connection.present?
|
|
end
|
|
|
|
def contact_neopets_username
|
|
contact_neopets_connection.try(:neopets_username)
|
|
end
|
|
|
|
def item_quantities_for(item_id)
|
|
quantities = Hash.new(0)
|
|
|
|
hangers = closet_hangers.where(item_id: item_id).
|
|
select([:owned, :list_id, :quantity])
|
|
hangers.each do |hanger|
|
|
quantities[hanger.list_id || hanger.owned?] = hanger.quantity
|
|
end
|
|
|
|
quantities
|
|
end
|
|
|
|
def log_trade_activity
|
|
touch(:last_trade_activity_at)
|
|
end
|
|
|
|
def visible_to?(current_user, remote_ip)
|
|
# Everyone is visible to support staff.
|
|
return true if current_user&.support_staff?
|
|
|
|
# Shadowbanned users are only visible to themselves.
|
|
return false if shadowbanned? && !likely_is?(current_user, remote_ip)
|
|
|
|
# Other than that, users are visible to everyone by default.
|
|
return true
|
|
end
|
|
|
|
def self.points_required_to_pass_top_contributor(offset)
|
|
user = User.top_contributors.select(:points).limit(1).offset(offset).first
|
|
user ? user.points : 0
|
|
end
|
|
end
|
|
|