impress/app/controllers/items_controller.rb
Emi Matchu 1cfb5129fa Hide shadowbanned users from trade lists for most viewers
Users will still see their own trades in the list and in the counts,
and support staff will still see them too!
2025-06-22 11:35:27 -07:00

273 lines
8.5 KiB
Ruby

class ItemsController < ApplicationController
before_action :set_query
before_action :support_staff_only, except: [:index, :show, :sources]
rescue_from Item::Search::Error, :with => :search_error
def index
if @query
if params[:per_page]
per_page = params[:per_page].to_i
per_page = 50 if per_page && per_page > 50
else
per_page = 30
end
@items = @query.results.paginate(
page: params[:page], per_page: per_page)
assign_closeted!(@items)
respond_to do |format|
format.html {
@campaign = Fundraising::Campaign.current rescue nil
if @items.count == 1
redirect_to @items.first
else
render
end
}
format.json {
render json: {
items: @items.as_json(
methods: [:nc?, :pb?, :owned?, :wanted?],
include: {
restricted_zones: {
only: [:id, :depth, :label],
methods: [:is_commonly_used_by_items],
},
},
),
appearances: load_appearances.as_json(
include: {
swf_assets: {
only: [:id, :remote_id, :body_id],
include: {
zone: {
only: [:id, :depth, :label],
methods: [:is_commonly_used_by_items],
},
restricted_zones: {
only: [:id, :depth, :label],
methods: [:is_commonly_used_by_items],
},
},
methods: [:urls, :known_glitches],
},
}
),
total_pages: @items.total_pages,
query: @query.to_s,
}
}
end
elsif params.has_key?(:ids) && params[:ids].is_a?(Array)
@items = Item.find(params[:ids])
assign_closeted!(@items)
respond_to do |format|
format.json { render json: @items }
end
else
respond_to do |format|
format.html {
@campaign = Fundraising::Campaign.current rescue nil
@newest_items = Item.newest.limit(18)
}
end
end
end
def show
@item = Item.find params[:id]
respond_to do |format|
format.html do
@trades = @item.closet_hangers.trading.user_is_active.
to_trades(current_user, request.remote_ip)
@contributors_with_counts = @item.contributors_with_counts
if user_signed_in?
@current_user_lists = current_user.closet_lists.alphabetical.
group_by_owned
@current_user_quantities = current_user.item_quantities_for(@item)
end
@selected_preview_pet_type = load_selected_preview_pet_type
@preview_outfit = Outfit.new(
pet_state: load_preview_pet_type.canonical_pet_state,
worn_items: [@item],
)
@preview_error = validate_preview
@all_appearances = @item.appearances
@appearances_by_occupied_zone_label =
@item.appearances_by_occupied_zone_label.sort_by { |l, a| l }
@selected_item_appearance = @preview_outfit.item_appearances.first
@preview_pet_type_options = PetType.where(color: @preview_outfit.color).
includes(:species).merge(Species.alphabetical)
end
format.gif do
expires_in 1.month
redirect_to @item.thumbnail_url, allow_other_host: true
end
end
end
def edit
@item = Item.find params[:id]
render layout: "application"
end
def update
@item = Item.find params[:id]
if @item.update(item_params)
flash[:notice] = "\"#{@item.name}\" successfully saved!"
redirect_to @item
else
render action: "edit", layout: "application", status: :bad_request
end
end
def sources
# Load all the items, then group them by source.
item_ids = params[:ids].split(",")
@all_items = Item.where(id: item_ids).includes(:nc_mall_record).
includes(:dyeworks_base_item).order(:name).limit(50)
@items = @all_items.group_by(&:source).tap { |i| i.default = [] }
assign_closeted!(@all_items)
if @all_items.empty?
render file: "public/404.html", status: :not_found, layout: nil
return
end
# For Dyeworks items whose base is currently in the NC Mall, preload their
# trade values. We'll use this to determine which ones are fully buyable rn
# (because Owls tracks this data and we don't).
Item.preload_nc_trade_values(@items[:dyeworks])
# Start loading the NC trade values for the non-Mall NC items.
trade_values_task = Async { Item.preload_nc_trade_values(@items[:other_nc]) }
# Also, PB items have some special handling: we group them by color, then
# load example pet types for the colors that don't have paint brushes.
@pb_items_by_color = @items[:pb].group_by(&:pb_color).
sort_by { |color, items| color&.name }.to_h
colors_without_thumbnails = @pb_items_by_color.keys.
select(&:present?).reject(&:pb_item_thumbnail_url?)
@pb_color_pet_types = colors_without_thumbnails.map do |color|
# Infer the ideal species from the first item we can, then try to find a
# matching pet type to use as the thumbnail, if needed.
species = @pb_items_by_color[color].map(&:pb_species).select(&:present?)
.first
[color, color.example_pet_type(preferred_species: species)]
end.to_h
# Create a second value that only include the items the user *needs*: that
# is, that they don't already own.
@items_needed = @items.transform_values { |items| items.reject(&:owned?) }
@pb_items_needed_by_color =
@pb_items_by_color.transform_values { |items| items.reject(&:owned?) }
# Finish loading the NC trade values.
trade_values_task.wait
render layout: "application"
end
protected
def item_params
params.require(:item).permit(
:name, :thumbnail_url, :description, :modeling_status_hint,
:is_manually_nc, :explicitly_body_specific,
).tap do |p|
p[:modeling_status_hint] = nil if p[:modeling_status_hint] == ""
end
end
def assign_closeted!(items)
current_user.assign_closeted_to_items!(items) if user_signed_in?
end
def load_appearances
appearance_params = params[:with_appearances_for]
return {} if appearance_params.blank?
if appearance_params[:alt_style_id].present?
target = Item::Search::Query.load_alt_style_by_id(
appearance_params[:alt_style_id])
else
target = Item::Search::Query.load_pet_type_by_color_and_species(
appearance_params[:color_id], appearance_params[:species_id])
end
target.appearances_for(@items, swf_asset_includes: [:zone]).
tap do |appearances|
# Preload the manifests for these SWF assets concurrently, rather than
# loading them in sequence when we generate the JSON.
swf_assets = appearances.values.map(&:swf_assets).flatten
SwfAsset.preload_manifests(swf_assets)
end
end
def load_selected_preview_pet_type
color_id = params.dig(:preview, :color_id)
species_id = params.dig(:preview, :species_id)
return load_default_preview_pet_type if color_id.nil? || species_id.nil?
PetType.find_or_initialize_by(color_id:, species_id:).tap do |pet_type|
if pet_type.persisted?
cookies["preferred-preview-color-id"] = color_id
cookies["preferred-preview-species-id"] = species_id
end
end
end
def load_preview_pet_type
if @selected_preview_pet_type.persisted?
@selected_preview_pet_type
else
load_default_preview_pet_type
end
end
def load_default_preview_pet_type
@item.compatible_pet_types.
preferring_species(cookies["preferred-preview-species-id"] || "<ignore>").
preferring_color(cookies["preferred-preview-color-id"] || "<ignore>").
preferring_simple.first ||
PetType.matching_name("Blue", "Acara").first!
end
def validate_preview
if @selected_preview_pet_type.new_record?
:pet_type_does_not_exist
elsif @preview_outfit.item_appearances.any?(&:empty?)
:no_item_data
end
end
def search_error(e)
@items = []
@query = params[:q]
respond_to do |format|
format.html { flash.now[:alert] = e.message; render }
format.json { render :json => {error: e.message} }
end
end
def set_query
q = params[:q]
if q.is_a?(String)
@query = Item::Search::Query.from_text(q, current_user)
elsif q.is_a?(ActionController::Parameters)
@query = Item::Search::Query.from_params(q, current_user)
end
end
end