# frozen_string_literal: true module Api module V1 class PartiesController < Api::V1::ApiController before_action :set_from_slug, except: %w[create destroy update index favorites] before_action :set, only: %w[update destroy] before_action :authorize, only: %w[update destroy] MAX_CHARACTERS = 5 MAX_SUMMONS = 8 MAX_WEAPONS = 13 DEFAULT_MIN_CHARACTERS = 3 DEFAULT_MIN_SUMMONS = 2 DEFAULT_MIN_WEAPONS = 5 DEFAULT_MAX_CLEAR_TIME = 5400 def create party = Party.new party.user = current_user if current_user party.attributes = party_params if party_params if party.save! return render json: PartyBlueprint.render(party, view: :created, root: :party), status: :created end render_validation_error_response(@party) end def show return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party render_not_found_response('project') end def update @party.attributes = party_params.except(:skill1_id, :skill2_id, :skill3_id) # TODO: Validate accessory with job return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party.save render_validation_error_response(@party) end def destroy return render json: PartyBlueprint.render(@party, view: :destroyed, root: :checkin) if @party.destroy end def remix new_party = @party.amoeba_dup new_party.attributes = { user: current_user, name: remixed_name(@party.name), source_party: @party, remix: true } new_party.local_id = party_params[:local_id] unless party_params.nil? if new_party.save render json: PartyBlueprint.render(new_party, view: :created, root: :party), status: :created else render_validation_error_response(new_party) end end def index conditions = build_filters query = build_query(conditions) query = apply_includes(query, params[:includes]) if params[:includes].present? query = apply_excludes(query, params[:excludes]) if params[:excludes].present? @parties = fetch_parties(query) count = calculate_count(query) total_pages = calculate_total_pages(count) render_party_json(@parties, count, total_pages) end def favorites raise Api::V1::UnauthorizedError unless current_user conditions = build_filters conditions[:favorites] = { user_id: current_user.id } query = build_query(conditions, true) query = apply_includes(query, params[:includes]) if params[:includes].present? query = apply_excludes(query, params[:excludes]) if params[:excludes].present? @parties = fetch_parties(query) count = calculate_count(query) total_pages = calculate_total_pages(count) render_party_json(@parties, count, total_pages) end private def authorize render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key end def build_filters params = request.params start_time = build_start_time(params['recency']) min_characters_count = build_count(params['characters_count'], DEFAULT_MIN_CHARACTERS) min_summons_count = build_count(params['summons_count'], DEFAULT_MIN_SUMMONS) min_weapons_count = build_count(params['weapons_count'], DEFAULT_MIN_WEAPONS) max_clear_time = build_max_clear_time(params['max_clear_time']) { element: build_element(params['element']), raid: params['raid'], created_at: params['recency'].present? ? start_time..DateTime.current : nil, full_auto: build_option(params['full_auto']), auto_guard: build_option(params['auto_guard']), charge_attack: build_option(params['charge_attack']), characters_count: min_characters_count..MAX_CHARACTERS, summons_count: min_summons_count..MAX_SUMMONS, weapons_count: min_weapons_count..MAX_WEAPONS }.delete_if { |_k, v| v.nil? } end def build_start_time(recency) return unless recency.present? (DateTime.current - recency.to_i.seconds).to_datetime.beginning_of_day end def build_count(value, default) value.blank? ? default : value.to_i end def build_max_clear_time(value) value.blank? ? DEFAULT_MAX_CLEAR_TIME : value.to_i end def build_element(element) element.to_i unless element.blank? end def build_option(value) value.to_i unless value.blank? || value.to_i == -1 end def build_query(conditions, favorites = false) query = Party.distinct .joins(weapons: [:object], summons: [:object], characters: [:object]) .group('parties.id') .where(conditions) .where(name_quality) .where(user_quality) .where(original) query = query.joins(:favorites) if favorites query end def includes(id) "(\"#{id_to_table(id)}\".\"granblue_id\" = '#{id}')" end def excludes(id) "(\"#{id_to_table(id)}\".\"granblue_id\" != '#{id}')" end def apply_includes(query, includes) included = includes.split(',') includes_condition = included.map { |id| includes(id) }.join(' AND ') query.where(includes_condition) end def apply_excludes(query, _excludes) characters_subquery = excluded_characters.select(1).arel summons_subquery = excluded_summons.select(1).arel weapons_subquery = excluded_weapons.select(1).arel query.where(characters_subquery.exists.not) .where(weapons_subquery.exists.not) .where(summons_subquery.exists.not) end def excluded_characters return unless params[:excludes] excluded = params[:excludes].split(',').filter { |id| id[0] == '3' } GridCharacter.joins(:object) .where(characters: { granblue_id: excluded }) .where('grid_characters.party_id = parties.id') end def excluded_summons return unless params[:excludes] excluded = params[:excludes].split(',').filter { |id| id[0] == '2' } GridSummon.joins(:object) .where(summons: { granblue_id: excluded }) .where('grid_summons.party_id = parties.id') end def excluded_weapons return unless params[:excludes] excluded = params[:excludes].split(',').filter { |id| id[0] == '1' } GridWeapon.joins(:object) .where(weapons: { granblue_id: excluded }) .where('grid_weapons.party_id = parties.id') end def fetch_parties(query) query.order(created_at: :desc) .paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE) .each { |party| party.favorited = current_user ? party.is_favorited(current_user) : false } end def calculate_count(query) query.count.values.sum end def calculate_total_pages(count) count.to_f / COLLECTION_PER_PAGE > 1 ? (count.to_f / COLLECTION_PER_PAGE).ceil : 1 end def render_party_json(parties, count, total_pages) render json: PartyBlueprint.render(parties, view: :collection, root: :results, meta: { count: count, total_pages: total_pages, per_page: COLLECTION_PER_PAGE }) end def user_quality 'user_id IS NOT NULL' unless request.params[:user_quality].blank? || request.params[:user_quality] == 'false' end def name_quality low_quality = [ 'Untitled', 'Remix of Untitled', 'Remix of Remix of Untitled', 'Remix of Remix of Remix of Untitled', 'Remix of Remix of Remix of Remix of Untitled', 'Remix of Remix of Remix of Remix of Remix of Untitled', '無題', '無題のリミックス', '無題のリミックスのリミックス', '無題のリミックスのリミックスのリミックス', '無題のリミックスのリミックスのリミックスのリミックス', '無題のリミックスのリミックスのリミックスのリミックスのリミックス' ] joined_names = low_quality.map { |name| "'#{name}'" }.join(',') return if request.params[:name_quality].blank? || request.params[:name_quality] == 'false' "name NOT IN (#{joined_names})" end def original 'source_party_id IS NULL' unless request.params['original'].blank? || request.params['original'] == 'false' end def id_to_table(id) case id[0] when '3' table = 'characters' when '2' table = 'summons' when '1' table = 'weapons' end table end def remixed_name(name) blanked_name = { en: name.blank? ? 'Untitled team' : name, ja: name.blank? ? '無名の編成' : name } if current_user case current_user.language when 'en' "Remix of #{blanked_name[:en]}" when 'ja' "#{blanked_name[:ja]}のリミックス" end else "Remix of #{blanked_name[:en]}" end end def set_from_slug @party = Party.where('shortcode = ?', params[:id]).first if @party @party.favorited = current_user && @party ? @party.is_favorited(current_user) : false else render_not_found_response('party') end end def set @party = Party.where('id = ?', params[:id]).first end def party_params return unless params[:party].present? params.require(:party).permit( :user_id, :local_id, :edit_key, :extra, :name, :description, :raid_id, :job_id, :accessory_id, :skill0_id, :skill1_id, :skill2_id, :skill3_id, :full_auto, :auto_guard, :auto_summon, :charge_attack, :clear_time, :button_count, :turn_count, :chain_count, :guidebook1_id, :guidebook2_id, :guidebook3_id ) end end end end