diff --git a/Gemfile b/Gemfile index 547f399..9f9acb1 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ gem 'omniauth-bnet', '~> 2.0.0' # This gem provides a mitigation against CVE-2015-9284 gem 'omniauth-rails_csrf_protection', '~> 0.1.2' # A Ruby wrapper around Blizzard's Game Data and Profile APIs -gem 'rbattlenet', '~> 2.2.4', git: 'https://github.com/Dainii/rbattlenet' +gem 'rbattlenet', '~> 2.2.5', git: 'https://github.com/Dainii/rbattlenet' # A gem that provides Rails integration for the Sentry error logger gem 'sentry-rails', '~> 4.4.0' gem 'sentry-ruby', '~> 4.4.1' diff --git a/Gemfile.lock b/Gemfile.lock index 6e6d199..303098a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ GIT remote: https://github.com/Dainii/rbattlenet - revision: aa174b4fa9e22c1ff77caf168b4442325c0ae3e6 + revision: 0b430de01052dd66430c50cd391570ba81d6d699 specs: - rbattlenet (2.2.4) + rbattlenet (2.2.5) require_all typhoeus (~> 1.1) @@ -115,7 +115,7 @@ GEM railties (>= 3.2) e2mmap (0.1.0) erubi (1.10.0) - ethon (0.13.0) + ethon (0.14.0) ffi (>= 1.15.0) faraday (1.4.1) faraday-excon (~> 1.1) @@ -411,7 +411,7 @@ DEPENDENCIES rails (~> 6.1.3, >= 6.1.3.1) rails-erd rails-i18n (~> 6.0.0) - rbattlenet (~> 2.2.4)! + rbattlenet (~> 2.2.5)! redis (~> 4.2.5) rspec-rails rubocop diff --git a/app/models/wow_item.rb b/app/models/wow_item.rb new file mode 100644 index 0000000..eb7656c --- /dev/null +++ b/app/models/wow_item.rb @@ -0,0 +1,12 @@ +class WowItem < ApplicationRecord + extend Mobility + translates :name + + belongs_to :wow_item_class + belongs_to :wow_item_sub_class + belongs_to :wow_item_inventory_type + belongs_to :wow_item_quality + + validates :name, presence: true + validates :item_id, presence: true, uniqueness: true +end diff --git a/app/models/wow_item_class.rb b/app/models/wow_item_class.rb new file mode 100644 index 0000000..54c8191 --- /dev/null +++ b/app/models/wow_item_class.rb @@ -0,0 +1,10 @@ +class WowItemClass < ApplicationRecord + extend Mobility + translates :name + + has_many :wow_item_sub_classes, dependent: :destroy + has_many :wow_items, dependent: :destroy + + validates :name, presence: true + validates :item_class_id, presence: true, uniqueness: true +end diff --git a/app/models/wow_item_inventory_type.rb b/app/models/wow_item_inventory_type.rb new file mode 100644 index 0000000..971963a --- /dev/null +++ b/app/models/wow_item_inventory_type.rb @@ -0,0 +1,9 @@ +class WowItemInventoryType < ApplicationRecord + extend Mobility + translates :name + + has_many :wow_items, dependent: :destroy + + validates :name, presence: true + validates :item_inventory_type, presence: true, uniqueness: true +end diff --git a/app/models/wow_item_quality.rb b/app/models/wow_item_quality.rb new file mode 100644 index 0000000..70ac09f --- /dev/null +++ b/app/models/wow_item_quality.rb @@ -0,0 +1,9 @@ +class WowItemQuality < ApplicationRecord + extend Mobility + translates :name + + has_many :wow_items, dependent: :destroy + + validates :name, presence: true + validates :item_quality_type, presence: true, uniqueness: true +end diff --git a/app/models/wow_item_sub_class.rb b/app/models/wow_item_sub_class.rb new file mode 100644 index 0000000..fbdb817 --- /dev/null +++ b/app/models/wow_item_sub_class.rb @@ -0,0 +1,9 @@ +class WowItemSubClass < ApplicationRecord + extend Mobility + translates :display_name, :verbose_name + + belongs_to :wow_item_class + has_many :wow_items, dependent: :destroy + + validates :item_sub_class_id, presence: true, uniqueness: { scope: :wow_item_class } +end diff --git a/app/workers/wow_character_positions_worker.rb b/app/workers/wow_character_positions_worker.rb index 4604b58..c2c2ab9 100644 --- a/app/workers/wow_character_positions_worker.rb +++ b/app/workers/wow_character_positions_worker.rb @@ -6,7 +6,11 @@ class WowCharacterPositionsWorker < WowSidekiqWorker # Protected data RBattlenet.set_options(locale: 'all') - params = { character_id: wow_character_id, realm_id: wow_character.wow_realm.realm_id, token: wow_character.user.token } + params = { + character_id: wow_character_id, + realm_id: wow_character.wow_realm.realm_id, + token: wow_character.user.token + } result = RBattlenet::Wow::Profile::ProtectedSummary.find(params) return unless result.status_code == 200 diff --git a/app/workers/wow_item_class_detail_worker.rb b/app/workers/wow_item_class_detail_worker.rb new file mode 100644 index 0000000..90fd998 --- /dev/null +++ b/app/workers/wow_item_class_detail_worker.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class WowItemClassDetailWorker < WowSidekiqWorker + def perform(item_class_id) + return unless (wow_item_class = WowItemClass.find_by(item_class_id: item_class_id)) + + RBattlenet.set_options(locale: 'all') + result = RBattlenet::Wow::ItemClass.find(item_class_id) + + return unless result.status_code == 200 + + result.item_subclasses.each do |item_subclass| + wow_item_sub_class = wow_item_class.wow_item_sub_classes.create_with(wow_item_class: wow_item_class).find_or_create_by(item_sub_class_id: item_subclass.id) + + WowItemSubClassDetailWorker.perform_async(wow_item_class.item_class_id, wow_item_sub_class.item_sub_class_id) if wow_item_sub_class.persisted? + end + end +end diff --git a/app/workers/wow_item_classes_worker.rb b/app/workers/wow_item_classes_worker.rb new file mode 100644 index 0000000..97e73d5 --- /dev/null +++ b/app/workers/wow_item_classes_worker.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class WowItemClassesWorker < WowSidekiqWorker + def perform + RBattlenet.authenticate(client_id: ENV['BLIZZARD_API_CLIENT_ID'], client_secret: ENV['BLIZZARD_API_CLIENT_SECRET']) + RBattlenet.set_options(locale: 'all') + result = RBattlenet::Wow::ItemClass.all + + return unless result.status_code == 200 + + result.item_classes.each do |item_class| + wow_item_class = WowItemClass.find_or_initialize_by(item_class_id: item_class.id) + + # Localisation data + locales.each do |locale| + Mobility.with_locale(locale[0]) { wow_item_class.name = item_class.name[locale[1]] } + end + + wow_item_class.save + + WowItemClassDetailWorker.perform_async(wow_item_class.item_class_id) if wow_item_class.persisted? + end + end +end diff --git a/app/workers/wow_item_sub_class_detail_worker.rb b/app/workers/wow_item_sub_class_detail_worker.rb new file mode 100644 index 0000000..689fe02 --- /dev/null +++ b/app/workers/wow_item_sub_class_detail_worker.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class WowItemSubClassDetailWorker < WowSidekiqWorker + def perform(item_class_id, item_sub_class_id) + return unless (wow_item_sub_class = WowItemSubClass.joins(:wow_item_class).where(item_sub_class_id: item_sub_class_id, wow_item_class: { item_class_id: item_class_id }).first) + + RBattlenet.set_options(locale: 'all') + params = { + class_id: item_class_id, + id: item_sub_class_id + } + result = RBattlenet::Wow::ItemSubclass.find(params) + + return unless result.status_code == 200 + + # Localisation data + locales.each do |locale| + Mobility.with_locale(locale[0]) do + wow_item_sub_class.display_name = result.display_name[locale[1]] if result.display_name + wow_item_sub_class.verbose_name = result.verbose_name[locale[1]] if result.verbose_name + end + end + + wow_item_sub_class.save + end +end diff --git a/app/workers/wow_items_worker.rb b/app/workers/wow_items_worker.rb new file mode 100644 index 0000000..80fa13c --- /dev/null +++ b/app/workers/wow_items_worker.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +class WowItemsWorker < WowSidekiqWorker + def perform(item_id, batch_size) + RBattlenet.authenticate(client_id: ENV['BLIZZARD_API_CLIENT_ID'], client_secret: ENV['BLIZZARD_API_CLIENT_SECRET']) + RBattlenet.set_options(locale: 'all') + + params = { + _page: 1, + _pageSize: batch_size, + orderby: 'id', + filters: { id: "[#{item_id},]" } + } + result = RBattlenet::Wow::Search::Item.find(params) + + return unless result.status_code == 200 + + result.results&.each do |item| + wow_item = WowItem.find_or_initialize_by(item_id: item.data.id) + + wow_item.level = item.data.level if item.data.level + wow_item.required_level = item.data.required_level if item.data.required_level + wow_item.sell_price = item.data.sell_price if item.data.sell_price + wow_item.purchase_price = item.data.purchase_price if item.data.purchase_price + wow_item.is_equippable = item.data.is_equippable if item.data.is_equippable + wow_item.is_stackable = item.data.is_stackable if item.data.is_stackable + wow_item.media_id = item.data.media.id if item.data.media.id + wow_item.max_count = item.data.max_count if item.data.max_count + + wow_item.wow_item_class = WowItemClass.find_by(item_class_id: item.data.item_class.id) + wow_item.wow_item_sub_class = WowItemSubClass.joins(:wow_item_class).where(item_sub_class_id: item.data.item_subclass.id, wow_item_class: { item_class_id: item.data.item_class.id }).first + + wow_item.wow_item_quality = find_or_create_wow_item_quality(item.data.quality) + wow_item.wow_item_inventory_type = find_or_create_wow_item_inventory_type(item.data.inventory_type) + + # Localisation data + locales.each do |locale| + Mobility.with_locale(locale[0]) do + wow_item.name = item.data.name[locale[1]] + end + end + + wow_item.save + end + + # Create a new job for the next batch + WowItemsWorker.perform_async(result.results.last.data.id + 1, batch_size) unless result.results.count.zero? + end + + private + + def find_or_create_wow_item_quality(item_quality) + wow_item_quality = WowItemQuality.find_or_initialize_by(item_quality_type: item_quality.type) + + # Localisation data + locales.each do |locale| + Mobility.with_locale(locale[0]) do + wow_item_quality.name = item_quality.name[locale[1]] + end + end + + wow_item_quality.save + + wow_item_quality.persisted? ? wow_item_quality : nil + end + + def find_or_create_wow_item_inventory_type(item_inventory_type) + wow_item_inventory_type = WowItemInventoryType.find_or_initialize_by(item_inventory_type: item_inventory_type.type) + + # Localisation data + locales.each do |locale| + Mobility.with_locale(locale[0]) do + wow_item_inventory_type.name = item_inventory_type.name[locale[1]] + end + end + + wow_item_inventory_type.save + + wow_item_inventory_type.persisted? ? wow_item_inventory_type : nil + end +end diff --git a/db/migrate/20210528185500_create_wow_item_classes.rb b/db/migrate/20210528185500_create_wow_item_classes.rb new file mode 100644 index 0000000..e92b4fb --- /dev/null +++ b/db/migrate/20210528185500_create_wow_item_classes.rb @@ -0,0 +1,12 @@ +class CreateWowItemClasses < ActiveRecord::Migration[6.1] + def change + create_table :wow_item_classes do |t| + t.integer :item_class_id + t.jsonb :name + + t.timestamps + end + + add_index :wow_item_classes, :item_class_id, unique: true + end +end diff --git a/db/migrate/20210528185610_create_wow_item_sub_classes.rb b/db/migrate/20210528185610_create_wow_item_sub_classes.rb new file mode 100644 index 0000000..455bf80 --- /dev/null +++ b/db/migrate/20210528185610_create_wow_item_sub_classes.rb @@ -0,0 +1,14 @@ +class CreateWowItemSubClasses < ActiveRecord::Migration[6.1] + def change + create_table :wow_item_sub_classes do |t| + t.integer :item_sub_class_id + t.jsonb :display_name + t.jsonb :verbose_name + t.belongs_to :wow_item_class + + t.timestamps + end + + add_index :wow_item_sub_classes, :item_sub_class_id + end +end diff --git a/db/migrate/20210528185708_create_wow_item_qualities.rb b/db/migrate/20210528185708_create_wow_item_qualities.rb new file mode 100644 index 0000000..6253eff --- /dev/null +++ b/db/migrate/20210528185708_create_wow_item_qualities.rb @@ -0,0 +1,12 @@ +class CreateWowItemQualities < ActiveRecord::Migration[6.1] + def change + create_table :wow_item_qualities do |t| + t.jsonb :name + t.string :type + + t.timestamps + end + + add_index :wow_item_qualities, :type, unique: true + end +end diff --git a/db/migrate/20210528185731_create_wow_item_inventory_types.rb b/db/migrate/20210528185731_create_wow_item_inventory_types.rb new file mode 100644 index 0000000..3046edd --- /dev/null +++ b/db/migrate/20210528185731_create_wow_item_inventory_types.rb @@ -0,0 +1,12 @@ +class CreateWowItemInventoryTypes < ActiveRecord::Migration[6.1] + def change + create_table :wow_item_inventory_types do |t| + t.jsonb :name + t.string :type + + t.timestamps + end + + add_index :wow_item_inventory_types, :type, unique: true + end +end diff --git a/db/migrate/20210528185954_create_wow_items.rb b/db/migrate/20210528185954_create_wow_items.rb new file mode 100644 index 0000000..cc091d7 --- /dev/null +++ b/db/migrate/20210528185954_create_wow_items.rb @@ -0,0 +1,24 @@ +class CreateWowItems < ActiveRecord::Migration[6.1] + def change + create_table :wow_items do |t| + t.jsonb :name + t.integer :level + t.integer :required_level + t.integer :sell_price + t.boolean :is_equippable + t.integer :media_id + t.integer :max_count + t.integer :purchase_price + t.integer :item_id + t.boolean :is_stackable + t.belongs_to :wow_item_class + t.belongs_to :wow_item_sub_class + t.belongs_to :wow_item_quality + t.belongs_to :wow_item_inventory_type + + t.timestamps + end + + add_index :wow_items, :item_id, unique: true + end +end diff --git a/db/migrate/20210528214602_rename_type_columns.rb b/db/migrate/20210528214602_rename_type_columns.rb new file mode 100644 index 0000000..68250de --- /dev/null +++ b/db/migrate/20210528214602_rename_type_columns.rb @@ -0,0 +1,6 @@ +class RenameTypeColumns < ActiveRecord::Migration[6.1] + def change + rename_column :wow_item_qualities, :type, :item_quality_type + rename_column :wow_item_inventory_types, :type, :item_inventory_type + end +end diff --git a/db/schema.rb b/db/schema.rb index 6a32e73..107c5e5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_05_28_125553) do +ActiveRecord::Schema.define(version: 2021_05_28_214602) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -194,6 +194,65 @@ ActiveRecord::Schema.define(version: 2021_05_28_125553) do t.index ["wow_realm_id"], name: "index_wow_guilds_on_wow_realm_id" end + create_table "wow_item_classes", force: :cascade do |t| + t.integer "item_class_id" + t.jsonb "name" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["item_class_id"], name: "index_wow_item_classes_on_item_class_id", unique: true + end + + create_table "wow_item_inventory_types", force: :cascade do |t| + t.jsonb "name" + t.string "item_inventory_type" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["item_inventory_type"], name: "index_wow_item_inventory_types_on_item_inventory_type", unique: true + end + + create_table "wow_item_qualities", force: :cascade do |t| + t.jsonb "name" + t.string "item_quality_type" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["item_quality_type"], name: "index_wow_item_qualities_on_item_quality_type", unique: true + end + + create_table "wow_item_sub_classes", force: :cascade do |t| + t.integer "item_sub_class_id" + t.jsonb "display_name" + t.jsonb "verbose_name" + t.bigint "wow_item_class_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["item_sub_class_id"], name: "index_wow_item_sub_classes_on_item_sub_class_id" + t.index ["wow_item_class_id"], name: "index_wow_item_sub_classes_on_wow_item_class_id" + end + + create_table "wow_items", force: :cascade do |t| + t.jsonb "name" + t.integer "level" + t.integer "required_level" + t.integer "sell_price" + t.boolean "is_equippable" + t.integer "media_id" + t.integer "max_count" + t.integer "purchase_price" + t.integer "item_id" + t.boolean "is_stackable" + t.bigint "wow_item_class_id" + t.bigint "wow_item_sub_class_id" + t.bigint "wow_item_quality_id" + t.bigint "wow_item_inventory_type_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["item_id"], name: "index_wow_items_on_item_id", unique: true + t.index ["wow_item_class_id"], name: "index_wow_items_on_wow_item_class_id" + t.index ["wow_item_inventory_type_id"], name: "index_wow_items_on_wow_item_inventory_type_id" + t.index ["wow_item_quality_id"], name: "index_wow_items_on_wow_item_quality_id" + t.index ["wow_item_sub_class_id"], name: "index_wow_items_on_wow_item_sub_class_id" + end + create_table "wow_mounts", force: :cascade do |t| t.jsonb "name" t.string "source_type" diff --git a/spec/models/wow_item_class_spec.rb b/spec/models/wow_item_class_spec.rb new file mode 100644 index 0000000..5ebf4c0 --- /dev/null +++ b/spec/models/wow_item_class_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe WowItemClass, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/wow_item_inventory_type_spec.rb b/spec/models/wow_item_inventory_type_spec.rb new file mode 100644 index 0000000..9a71837 --- /dev/null +++ b/spec/models/wow_item_inventory_type_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe WowItemInventoryType, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/wow_item_quality_spec.rb b/spec/models/wow_item_quality_spec.rb new file mode 100644 index 0000000..35e6d6e --- /dev/null +++ b/spec/models/wow_item_quality_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe WowItemQuality, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/wow_item_spec.rb b/spec/models/wow_item_spec.rb new file mode 100644 index 0000000..843f22a --- /dev/null +++ b/spec/models/wow_item_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe WowItem, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/wow_item_sub_class_spec.rb b/spec/models/wow_item_sub_class_spec.rb new file mode 100644 index 0000000..16188e5 --- /dev/null +++ b/spec/models/wow_item_sub_class_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe WowItemSubClass, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end