From 9b2f2d1c306ebe1535f0db2ee05ef4394cd5068e Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 9 Apr 2023 19:08:04 -0700 Subject: [PATCH] Implements advanced filters (#90) * Add advanced filters Adds new filters to search: * Full auto * Charge attack * Auto guard * Number of weapons (user-selectable now) * Number of summons * Number of characters * Maximum number of turns * Maximum number of buttons * Maximum clear time * User quality (No anonymous users) * Name quality (No untitled teams) * Remixes (Only show original teams) * Update advanced filter params * Add default to party counter cache --- app/controllers/api/v1/parties_controller.rb | 78 ++++++++++++++- app/controllers/api/v1/users_controller.rb | 81 +++++++++++++++- ...add_default_to_counter_cache_on_parties.rb | 7 ++ db/schema.rb | 97 ++++++++++++++++--- 4 files changed, 241 insertions(+), 22 deletions(-) create mode 100644 db/migrate/20230321115930_add_default_to_counter_cache_on_parties.rb diff --git a/app/controllers/api/v1/parties_controller.rb b/app/controllers/api/v1/parties_controller.rb index a4f1221..5767706 100644 --- a/app/controllers/api/v1/parties_controller.rb +++ b/app/controllers/api/v1/parties_controller.rb @@ -8,6 +8,16 @@ module Api 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 @@ -73,12 +83,15 @@ module Api end def index - conditions = build_conditions(request.params) + conditions = build_conditions @parties = Party.joins(:weapons) .group('parties.id') .having('count(distinct grid_weapons.weapon_id) > 2') .where(conditions) + .where(name_quality) + .where(user_quality) + .where(original) .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 } @@ -99,11 +112,14 @@ module Api def favorites raise Api::V1::UnauthorizedError unless current_user - conditions = build_conditions(request.params) + conditions = build_conditions conditions[:favorites] = { user_id: current_user.id } @parties = Party.joins(:favorites) .where(conditions) + .where(name_quality) + .where(user_quality) + .where(original) .order('favorites.created_at DESC') .paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE) .each { |party| party.favorited = party.is_favorited(current_user) } @@ -127,20 +143,72 @@ module Api render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key end - def build_conditions(params) + def build_conditions + params = request.params + unless params['recency'].blank? start_time = (DateTime.current - params['recency'].to_i.seconds) .to_datetime.beginning_of_day end + min_characters_count = params['characters_count'].blank? ? DEFAULT_MIN_CHARACTERS : params['characters_count'].to_i + min_summons_count = params['summons_count'].blank? ? DEFAULT_MIN_SUMMONS : params['summons_count'].to_i + min_weapons_count = params['weapons_count'].blank? ? DEFAULT_MIN_WEAPONS : params['weapons_count'].to_i + max_clear_time = params['max_clear_time'].blank? ? DEFAULT_MAX_CLEAR_TIME : params['max_clear_time'].to_i + {}.tap do |hash| - hash[:element] = params['element'] unless params['element'].blank? + # Basic filters + hash[:element] = params['element'].to_i unless params['element'].blank? hash[:raid] = params['raid'] unless params['raid'].blank? hash[:created_at] = start_time..DateTime.current unless params['recency'].blank? - hash[:weapons_count] = 5..13 + + # Advanced filters: Team parameters + hash[:full_auto] = params['full_auto'].to_i unless params['full_auto'].blank? || params['full_auto'].to_i == -1 + hash[:auto_guard] = params['auto_guard'].to_i unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1 + hash[:charge_attack] = params['charge_attack'].to_i unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1 + + # Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1 + # How do we do the same for button count since that can reasonably be 1? + # hash[:turn_count] = params['turn_count'].to_i unless params['turn_count'].blank? || params['turn_count'].to_i <= 0 + # hash[:button_count] = params['button_count'].to_i unless params['button_count'].blank? + # hash[:clear_time] = 0..max_clear_time + + # Advanced filters: Object counts + hash[:characters_count] = min_characters_count..MAX_CHARACTERS + hash[:summons_count] = min_summons_count..MAX_SUMMONS + hash[:weapons_count] = min_weapons_count..MAX_WEAPONS end end + def original + "source_party_id IS NULL" unless request.params['original'].blank? || request.params['original'] == "false" + 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(',') + + "name NOT IN (#{joined_names})" unless request.params[:name_quality].blank? || request.params[:name_quality] == "false" + end + def remixed_name(name) blanked_name = { en: name.blank? ? 'Untitled team' : name, diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index 0ac50fe..d290c93 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -8,6 +8,16 @@ module Api before_action :set, except: %w[create check_email check_username] before_action :set_by_id, only: %w[info update] + 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 user = User.new(user_params) @@ -44,11 +54,23 @@ module Api render_not_found_response('user') else - conditions = build_conditions(request.params) + conditions = build_conditions conditions[:user_id] = @user.id + @parties = Party + .where(conditions) + .where(name_quality) + .where(user_quality) + .where(original) + .order(created_at: :desc) + .paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE) + .each { |party| party.favorited = party.is_favorited(current_user) } + parties = Party .where(conditions) + .where(name_quality) + .where(user_quality) + .where(original) .order(created_at: :desc) .paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE) .each do |party| @@ -81,19 +103,72 @@ module Api private - def build_conditions(params) + def build_conditions + params = request.params + unless params['recency'].blank? start_time = (DateTime.current - params['recency'].to_i.seconds) .to_datetime.beginning_of_day end + min_characters_count = params['characters_count'].blank? ? DEFAULT_MIN_CHARACTERS : params['characters_count'].to_i + min_summons_count = params['summons_count'].blank? ? DEFAULT_MIN_SUMMONS : params['summons_count'].to_i + min_weapons_count = params['weapons_count'].blank? ? DEFAULT_MIN_WEAPONS : params['weapons_count'].to_i + max_clear_time = params['max_clear_time'].blank? ? DEFAULT_MAX_CLEAR_TIME : params['max_clear_time'].to_i + {}.tap do |hash| - hash[:element] = params['element'] unless params['element'].blank? + # Basic filters + hash[:element] = params['element'].to_i unless params['element'].blank? hash[:raid] = params['raid'] unless params['raid'].blank? hash[:created_at] = start_time..DateTime.current unless params['recency'].blank? + + # Advanced filters: Team parameters + hash[:full_auto] = params['full_auto'].to_i unless params['full_auto'].blank? || params['full_auto'].to_i == -1 + hash[:auto_guard] = params['auto_guard'].to_i unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1 + hash[:charge_attack] = params['charge_attack'].to_i unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1 + + # Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1 + # How do we do the same for button count since that can reasonably be 1? + # hash[:turn_count] = params['turn_count'].to_i unless params['turn_count'].blank? || params['turn_count'].to_i <= 0 + # hash[:button_count] = params['button_count'].to_i unless params['button_count'].blank? + # hash[:clear_time] = 0..max_clear_time + + # Advanced filters: Object counts + hash[:characters_count] = min_characters_count..MAX_CHARACTERS + hash[:summons_count] = min_summons_count..MAX_SUMMONS + hash[:weapons_count] = min_weapons_count..MAX_WEAPONS end end + def original + "source_party_id IS NULL" unless params['original'].blank? || params['original'] == '0' + end + + def user_quality + "user_id IS NOT NULL" unless params[:user_quality].nil? || params[:user_quality] == "0" + 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(',') + + "name NOT IN (#{joined_names})" unless params[:name_quality].nil? || params[:name_quality] == "0" + end + # Specify whitelisted properties that can be modified. def set @user = User.where('username = ?', params[:id]).first diff --git a/db/migrate/20230321115930_add_default_to_counter_cache_on_parties.rb b/db/migrate/20230321115930_add_default_to_counter_cache_on_parties.rb new file mode 100644 index 0000000..098c054 --- /dev/null +++ b/db/migrate/20230321115930_add_default_to_counter_cache_on_parties.rb @@ -0,0 +1,7 @@ +class AddDefaultToCounterCacheOnParties < ActiveRecord::Migration[7.0] + def change + change_column_default :parties, :characters_count, 0 + change_column_default :parties, :weapons_count, 0 + change_column_default :parties, :summons_count, 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 6095c45..b0dc946 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,19 +10,62 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do +ActiveRecord::Schema[7.0].define(version: 2023_03_21_115930) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "pg_trgm" enable_extension "pgcrypto" enable_extension "plpgsql" - enable_extension "timescaledb" create_table "app_updates", primary_key: "updated_at", id: :datetime, force: :cascade do |t| t.string "update_type", null: false t.string "version" end + create_table "character_charge_attacks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "character_id" + t.string "name_en", null: false + t.string "name_jp", null: false + t.string "description_en", null: false + t.string "description_jp", null: false + t.integer "order", null: false + t.string "form" + t.uuid "effects", array: true + t.index ["character_id"], name: "index_character_charge_attacks_on_character_id" + end + + create_table "character_skills", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "character_id" + t.string "name_en", null: false + t.string "name_jp", null: false + t.string "description_en", null: false + t.string "description_jp", null: false + t.integer "type", null: false + t.integer "position", null: false + t.string "form" + t.integer "cooldown", default: 0, null: false + t.integer "lockout", default: 0, null: false + t.integer "duration", array: true + t.boolean "recast", default: false, null: false + t.integer "obtained_at", default: 1, null: false + t.uuid "effects", array: true + t.index ["character_id"], name: "index_character_skills_on_character_id" + end + + create_table "character_support_skills", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "character_id" + t.string "name_en", null: false + t.string "name_jp", null: false + t.string "description_en", null: false + t.string "description_jp", null: false + t.integer "position", null: false + t.integer "obtained_at" + t.boolean "emp", default: false, null: false + t.boolean "transcendence", default: false, null: false + t.uuid "effects", array: true + t.index ["character_id"], name: "index_character_support_skills_on_character_id" + end + create_table "characters", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "name_en" t.string "name_jp" @@ -50,10 +93,26 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.integer "max_hp_ulb" t.integer "max_atk_ulb" t.integer "character_id", default: [], null: false, array: true + t.string "nicknames_en", default: [], null: false, array: true + t.string "nicknames_jp", default: [], null: false, array: true t.index ["name_en"], name: "index_characters_on_name_en", opclass: :gin_trgm_ops, using: :gin end - create_table "data_migrations", primary_key: "version", id: :string, force: :cascade do |t| + create_table "effects", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "name_en", null: false + t.string "name_jp", null: false + t.string "description_en", null: false + t.string "description_jp", null: false + t.integer "accuracy_value" + t.string "accuracy_suffix" + t.string "accuracy_comparator" + t.jsonb "strength", array: true + t.integer "healing_cap" + t.boolean "duration_indefinite", default: false, null: false + t.integer "duration_value" + t.string "duration_unit" + t.string "notes_en" + t.string "notes_jp" end create_table "favorites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| @@ -74,12 +133,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.datetime "updated_at", null: false t.boolean "perpetuity", default: false, null: false t.integer "transcendence_step", default: 0, null: false - t.jsonb "ring1", default: { "modifier" => nil, "strength" => nil }, null: false - t.jsonb "ring2", default: { "modifier" => nil, "strength" => nil }, null: false - t.jsonb "ring3", default: { "modifier" => nil, "strength" => nil }, null: false - t.jsonb "ring4", default: { "modifier" => nil, "strength" => nil }, null: false - t.jsonb "earring", default: { "modifier" => nil, "strength" => nil }, null: false - t.jsonb "awakening", default: { "type" => 1, "level" => 1 }, null: false + t.jsonb "ring1", default: {"modifier"=>nil, "strength"=>nil}, null: false + t.jsonb "ring2", default: {"modifier"=>nil, "strength"=>nil}, null: false + t.jsonb "ring3", default: {"modifier"=>nil, "strength"=>nil}, null: false + t.jsonb "ring4", default: {"modifier"=>nil, "strength"=>nil}, null: false + t.jsonb "earring", default: {"modifier"=>nil, "strength"=>nil}, null: false + t.jsonb "awakening", default: {"type"=>1, "level"=>1}, null: false + t.boolean "skill0_enabled", default: true, null: false + t.boolean "skill1_enabled", default: true, null: false + t.boolean "skill2_enabled", default: true, null: false + t.boolean "skill3_enabled", default: true, null: false t.index ["character_id"], name: "index_grid_characters_on_character_id" t.index ["party_id"], name: "index_grid_characters_on_party_id" end @@ -94,6 +157,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "transcendence_step", default: 0, null: false + t.boolean "quick_summon", default: false, null: false t.index ["party_id"], name: "index_grid_summons_on_party_id" t.index ["summon_id"], name: "index_grid_summons_on_summon_id" end @@ -155,12 +219,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.integer "proficiency2" t.string "row" t.boolean "master_level", default: false, null: false - t.boolean "ultimate_mastery", default: false, null: false t.integer "order" t.uuid "base_job_id" t.string "granblue_id" t.boolean "accessory", default: false t.integer "accessory_type", default: 0 + t.boolean "ultimate_mastery", default: false, null: false t.index ["base_job_id"], name: "index_jobs_on_base_job_id" end @@ -212,10 +276,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.text "description" t.uuid "raid_id" t.integer "element" - t.integer "weapons_count" + t.integer "weapons_count", default: 0 t.uuid "job_id" t.integer "master_level" - t.integer "ultimate_mastery" t.uuid "skill1_id" t.uuid "skill2_id" t.uuid "skill3_id" @@ -229,10 +292,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.integer "turn_count" t.uuid "source_party_id" t.uuid "accessory_id" - t.integer "characters_count" - t.integer "summons_count" + t.integer "characters_count", default: 0 + t.integer "summons_count", default: 0 t.string "edit_key" t.uuid "local_id" + t.integer "ultimate_mastery" t.index ["accessory_id"], name: "index_parties_on_accessory_id" t.index ["job_id"], name: "index_parties_on_job_id" t.index ["skill0_id"], name: "index_parties_on_skill0_id" @@ -275,6 +339,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.boolean "xlb", default: false, null: false t.integer "max_atk_xlb" t.integer "max_hp_xlb" + t.string "nicknames_en", default: [], null: false, array: true + t.string "nicknames_jp", default: [], null: false, array: true t.index ["name_en"], name: "index_summons_on_name_en", opclass: :gin_trgm_ops, using: :gin end @@ -329,6 +395,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_103037) do t.boolean "awakening", default: true, null: false t.boolean "limit", default: false, null: false t.boolean "ax", default: false, null: false + t.integer "awakening_types", default: [], array: true + t.string "nicknames_en", default: [], null: false, array: true + t.string "nicknames_jp", default: [], null: false, array: true t.index ["name_en"], name: "index_weapons_on_name_en", opclass: :gin_trgm_ops, using: :gin end