diff --git a/README.md b/README.md index 41a113475..21dd6e7c5 100755 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ make docker-migrate When you start PokéAPI with the above Docker Compose setup, an [Hasura Engine](https://github.com/hasura/graphql-engine) server is started as well. It's possible to track all the PokeAPI tables and foreign keys by simply ```sh -# hasura cli needs to be installed and available in your $PATH: https://hasura.io/docs/latest/graphql/core/hasura-cli/install-hasura-cli.html +# hasura cli needs to be installed and available in your $PATH: https://hasura.io/docs/2.0/hasura-cli/install-hasura-cli # hasura cli's version has to greater than v2.48.1 make hasura-apply ``` diff --git a/data/v2/build.py b/data/v2/build.py index c0e845477..0644afbe7 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -452,6 +452,18 @@ def csv_record_to_objects(info): def _build_items(): + def csv_record_to_objects(info): + yield Currency(id=int(info[0]), name=info[1]) + + build_generic((Currency,), "currencies.csv", csv_record_to_objects) + + def csv_record_to_objects(info): + yield CurrencyName( + currency_id=int(info[0]), language_id=int(info[1]), name=info[2] + ) + + build_generic((CurrencyName,), "currency_names.csv", csv_record_to_objects) + def csv_record_to_objects(info): yield ItemPocket(id=int(info[0]), name=info[1]) @@ -499,7 +511,6 @@ def csv_record_to_objects(info): id=int(info[0]), name=info[1], item_category_id=int(info[2]), - cost=int(info[3]), fling_power=int(info[4]) if info[4] != "" else None, item_fling_effect_id=int(info[5]) if info[5] != "" else None, ) @@ -544,6 +555,24 @@ def csv_record_to_objects(info): build_generic((ItemGameIndex,), "item_game_indices.csv", csv_record_to_objects) + def csv_record_to_objects(info): + # Keep backward compatibility with 4-column files (no currency_id). + has_currency_id = len(info) >= 5 + purchase_price_index = 3 if has_currency_id else 2 + sell_price_index = 4 if has_currency_id else 3 + + yield ItemPrice( + item_id=int(info[0]), + version_group_id=int(info[1]), + currency_id=int(info[2]) if has_currency_id and info[2] else 1, + purchase_price=( + int(info[purchase_price_index]) if info[purchase_price_index] else None + ), + sell_price=int(info[sell_price_index]) if info[sell_price_index] else None, + ) + + build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) + def csv_record_to_objects(info): yield ItemFlavorText( item_id=int(info[0]), diff --git a/data/v2/csv/currencies.csv b/data/v2/csv/currencies.csv new file mode 100644 index 000000000..4ce4ea25f --- /dev/null +++ b/data/v2/csv/currencies.csv @@ -0,0 +1,20 @@ +id,identifier +1,poke-dollar +2,coin +3,volcanic-ash +4,poke-coupon +5,berry-powder +6,battle-point +7,sphere +8,castle-point +9,watt +10,athlete-point +11,dream-point +12,dream-world-berry +13,poke-mile +14,festival-coin +15,poke-bean +16,home-point +17,merit-point +18,league-point +19,blueberry-point diff --git a/data/v2/csv/currency_names.csv b/data/v2/csv/currency_names.csv new file mode 100644 index 000000000..5bd477917 --- /dev/null +++ b/data/v2/csv/currency_names.csv @@ -0,0 +1,20 @@ +id,language_id,name +1,9,Pokémon Dollar +2,9,Coin +3,9,Volcanic Ash +4,9,Poké Coupon +5,9,Berry Powder +6,9,Battle Point +7,9,Sphere +8,9,Castle Point +9,9,Watt +10,9,Athlete Point +11,9,Dream Point +12,9,Berry (Dream World) +13,9,Poké Mile +14,9,Festival Coin +15,9,Poké Bean +16,9,Pokémon HOME Point +17,9,Merit Point +18,9,League Point +19,9,Blueberry Point diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv new file mode 100644 index 000000000..b43ac17af --- /dev/null +++ b/data/v2/csv/item_prices.csv @@ -0,0 +1,292 @@ +item_id,version_group_id,currency_id,purchase_price,sell_price +1,1,1,,0 +1,2,1,,0 +1,3,1,,0 +1,4,1,,0 +1,28,1,,0 +1,29,1,,0 +2,1,1,1200,600 +2,2,1,1200,600 +2,3,1,1200,600 +2,4,1,1200,600 +2,5,1,1200,600 +2,6,1,1200,600 +2,7,1,1200,600 +2,8,1,1200,600 +2,9,1,1200,600 +2,10,1,1200,600 +2,11,1,1200,600 +2,14,1,1200,600 +2,15,1,1200,600 +2,16,1,1200,600 +2,17,1,800,400 +2,18,1,800,400 +2,19,1,500,250 +2,20,1,800,400 +2,23,1,1200,600 +2,24,1,600,150 +2,25,1,800,200 +2,28,1,1200,600 +2,29,1,1200,600 +2,30,1,600,150 +3,1,1,600,300 +3,2,1,600,300 +3,3,1,600,300 +3,4,1,600,300 +3,5,1,600,300 +3,6,1,600,300 +3,7,1,600,300 +3,8,1,600,300 +3,9,1,600,300 +3,10,1,600,300 +3,11,1,600,300 +3,14,1,600,300 +3,15,1,600,300 +3,16,1,600,300 +3,17,1,600,300 +3,18,1,600,300 +3,19,1,300,150 +3,20,1,600,300 +3,23,1,600,300 +3,24,1,300,75 +3,25,1,600,150 +3,28,1,600,300 +3,29,1,600,300 +3,30,1,300,75 +6,5,1,1000,500 +6,6,1,1000,500 +6,7,1,1000,500 +6,8,1,1000,500 +6,9,1,1000,500 +6,10,1,1000,500 +6,11,1,1000,500 +6,14,1,1000,500 +6,15,1,1000,500 +6,16,1,1000,500 +6,17,1,1000,500 +6,18,1,1000,500 +6,20,1,1000,500 +6,23,1,1000,500 +6,25,1,1000,250 +6,30,1,1000,250 +7,5,1,1000,500 +7,6,1,1000,500 +7,7,1,,500 +7,8,1,,500 +7,9,1,,500 +7,10,1,1000,500 +7,11,1,1000,500 +7,14,1,1000,500 +7,15,1,1000,500 +7,16,1,1000,500 +7,17,1,1000,500 +7,18,1,1000,500 +7,20,1,1000,500 +7,23,1,,500 +7,25,1,1000,250 +7,30,1,1000,250 +8,5,1,1000,500 +8,6,1,1000,500 +8,7,1,1000,500 +8,8,1,1000,500 +8,9,1,1000,500 +8,10,1,1000,500 +8,11,1,1000,500 +8,14,1,1000,500 +8,15,1,1000,500 +8,16,1,1000,500 +8,17,1,1000,500 +8,18,1,1000,500 +8,20,1,1000,500 +8,23,1,1000,500 +8,25,1,1000,250 +8,30,1,1000,250 +9,5,1,1000,500 +9,6,1,1000,500 +9,7,1,1000,500 +9,8,1,1000,500 +9,9,1,1000,500 +9,10,1,1000,500 +9,11,1,1000,500 +9,14,1,1000,500 +9,15,1,1000,500 +9,16,1,1000,500 +9,17,1,1000,500 +9,18,1,1000,500 +9,20,1,1000,500 +9,23,1,1000,500 +9,25,1,1000,250 +9,30,1,1000,250 +10,5,1,1000,500 +10,6,1,1000,500 +10,7,1,1000,500 +10,8,1,1000,500 +10,9,1,1000,500 +10,10,1,1000,500 +10,11,1,1000,500 +10,14,1,1000,500 +10,15,1,1000,500 +10,16,1,1000,500 +10,17,1,1000,500 +10,18,1,1000,500 +10,20,1,1000,500 +10,23,1,1000,500 +10,25,1,1000,250 +10,30,1,1000,250 +11,5,1,1000,500 +11,6,1,1000,500 +11,7,1,1000,500 +11,8,1,1000,500 +11,9,1,1000,500 +11,10,1,1000,500 +11,11,1,1000,500 +11,14,1,1000,500 +11,15,1,1000,500 +11,16,1,1000,500 +11,17,1,1000,500 +11,18,1,1000,500 +11,20,1,3000,1500 +11,23,1,1000,500 +11,25,1,3000,750 +11,30,1,3000,750 +12,5,1,,100 +12,6,1,,100 +12,7,1,,100 +12,8,1,,100 +12,9,1,,100 +12,10,1,,100 +12,15,1,200,100 +12,16,1,,100 +12,17,1,,10 +12,18,1,,10 +12,19,1,,50 +12,20,1,,10 +12,23,1,,100 +12,25,1,,5 +12,30,1,200,50 +13,8,1,1000,500 +13,9,1,1000,500 +13,10,1,1000,500 +13,11,1,1000,500 +13,14,1,1000,500 +13,15,1,1000,500 +13,16,1,1000,500 +13,17,1,1000,500 +13,18,1,1000,500 +13,20,1,1000,500 +13,23,1,1000,500 +13,25,1,1000,250 +13,30,1,1000,250 +14,8,1,300,150 +14,9,1,300,150 +14,10,1,300,150 +14,11,1,300,150 +14,14,1,300,150 +14,15,1,300,150 +14,16,1,300,150 +14,17,1,300,150 +14,18,1,300,150 +14,20,1,300,150 +14,23,1,300,150 +14,25,1,300,75 +14,30,1,300,75 +15,8,1,1000,500 +15,9,1,1000,500 +15,10,1,1000,500 +15,11,1,1000,500 +15,14,1,1000,500 +15,15,1,1000,500 +15,16,1,1000,500 +15,17,1,1000,500 +15,18,1,1000,500 +15,20,1,1000,500 +15,23,1,1000,500 +15,25,1,1000,250 +15,30,1,1000,250 +305,1,1,3000,1500 +305,2,1,3000,1500 +305,3,1,,1500 +305,4,1,,1500 +305,5,1,,1500 +305,6,1,,1500 +305,7,1,,1500 +305,16,1,5000, +305,20,1,40000, +305,23,1,,1500 +305,25,1,,400 +305,28,1,3000,1500 +305,29,1,3000,1500 +306,1,1,2000,1000 +306,2,1,2000,1000 +306,3,1,2000,1000 +306,4,1,2000,1000 +306,5,1,,1500 +306,6,1,,1500 +306,7,1,,1500 +306,8,1,,1500 +306,9,1,,1500 +306,10,1,,1500 +306,12,1,,1500 +306,13,1,,1500 +306,23,1,,1500 +306,25,1,,400 +449,3,1,,150 +449,4,1,,150 +449,10,1,,150 +449,11,1,,150 +449,14,1,,150 +449,15,1,,150 +449,16,1,,150 +450,3,1,,150 +450,4,1,,150 +450,10,1,,150 +450,11,1,,150 +450,14,1,,150 +450,15,1,,150 +450,16,1,,150 +451,3,1,,150 +451,4,1,,150 +451,10,1,,150 +451,11,1,,150 +451,14,1,,150 +451,15,1,,150 +451,16,1,,150 +452,3,1,,150 +452,4,1,,150 +452,10,1,,150 +452,11,1,,150 +452,14,1,,150 +452,15,1,,150 +452,16,1,,150 +452,24,1,120,30 +453,3,1,,150 +453,4,1,,150 +453,10,1,,150 +453,11,1,,150 +453,14,1,,150 +453,15,1,,150 +453,16,1,,150 +454,3,1,,150 +454,4,1,,150 +454,10,1,,150 +454,11,1,,150 +454,14,1,,150 +454,15,1,,150 +454,16,1,,150 +455,3,1,,150 +455,4,1,,150 +455,10,1,,150 +455,11,1,,150 +455,14,1,,150 +455,15,1,,150 +455,16,1,,150 +457,10,1,,150 +457,11,1,,150 +457,14,1,,150 +457,15,1,,150 +457,16,1,,150 +457,17,1,,150 +457,18,1,,150 +457,19,1,,150 +457,20,1,,150 +887,18,1,1000, diff --git a/graphql/v1beta/examples/go/pokemon.go b/graphql/v1beta/examples/go/pokemon.go index 7a1abf1f6..bd877ea90 100644 --- a/graphql/v1beta/examples/go/pokemon.go +++ b/graphql/v1beta/examples/go/pokemon.go @@ -75,7 +75,6 @@ query pokemon_details($name: String) { fireRedItems: pokemon_v2_pokemonitems(where: {pokemon_v2_version: {name: {_eq: "firered"}}}) { pokemon_v2_item { name - cost } rarity } diff --git a/graphql/v1beta/examples/node/pokemon.js b/graphql/v1beta/examples/node/pokemon.js index b11a8f598..a3fc772a5 100644 --- a/graphql/v1beta/examples/node/pokemon.js +++ b/graphql/v1beta/examples/node/pokemon.js @@ -93,7 +93,6 @@ function fetchPokemon_details(name="starmie") { fireRedItems: pokemon_v2_pokemonitems(where: {pokemon_v2_version: {name: {_eq: "firered"}}}) { pokemon_v2_item { name - cost } rarity } diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml new file mode 100644 index 000000000..b2b2688ca --- /dev/null +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml @@ -0,0 +1,25 @@ +table: + name: pokemon_v2_currency + schema: public +array_relationships: + - name: pokemon_v2_currencynames + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_currencyname + schema: public + - name: pokemon_v2_itemprices + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_itemprice + schema: public +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml new file mode 100644 index 000000000..5242b49bb --- /dev/null +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml @@ -0,0 +1,17 @@ +table: + name: pokemon_v2_currencyname + schema: public +object_relationships: + - name: pokemon_v2_currency + using: + foreign_key_constraint_on: currency_id + - name: pokemon_v2_language + using: + foreign_key_constraint_on: language_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml index 7e9fbc70d..13c8feeae 100644 --- a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml @@ -65,6 +65,13 @@ array_relationships: table: name: pokemon_v2_itemname schema: public + - name: pokemon_v2_itemprices + using: + foreign_key_constraint_on: + column: item_id + table: + name: pokemon_v2_itemprice + schema: public - name: pokemon_v2_itemsprites using: foreign_key_constraint_on: diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml new file mode 100644 index 000000000..947622b24 --- /dev/null +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml @@ -0,0 +1,20 @@ +table: + name: pokemon_v2_itemprice + schema: public +object_relationships: + - name: pokemon_v2_item + using: + foreign_key_constraint_on: item_id + - name: pokemon_v2_currency + using: + foreign_key_constraint_on: currency_id + - name: pokemon_v2_versiongroup + using: + foreign_key_constraint_on: version_group_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml index bead609fe..2e5f0990b 100644 --- a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml @@ -34,6 +34,13 @@ array_relationships: table: name: pokemon_v2_itemflavortext schema: public + - name: pokemon_v2_itemprices + using: + foreign_key_constraint_on: + column: version_group_id + table: + name: pokemon_v2_itemprice + schema: public - name: pokemon_v2_machines using: foreign_key_constraint_on: diff --git a/graphql/v1beta/metadata/databases/default/tables/tables.yaml b/graphql/v1beta/metadata/databases/default/tables/tables.yaml index 85ecc995e..4e0518181 100644 --- a/graphql/v1beta/metadata/databases/default/tables/tables.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/tables.yaml @@ -18,6 +18,8 @@ - "!include public_pokemon_v2_contesteffectflavortext.yaml" - "!include public_pokemon_v2_contesttype.yaml" - "!include public_pokemon_v2_contesttypename.yaml" +- "!include public_pokemon_v2_currency.yaml" +- "!include public_pokemon_v2_currencyname.yaml" - "!include public_pokemon_v2_egggroup.yaml" - "!include public_pokemon_v2_egggroupname.yaml" - "!include public_pokemon_v2_encounter.yaml" @@ -51,6 +53,7 @@ - "!include public_pokemon_v2_itemflingeffecteffecttext.yaml" - "!include public_pokemon_v2_itemgameindex.yaml" - "!include public_pokemon_v2_itemname.yaml" +- "!include public_pokemon_v2_itemprice.yaml" - "!include public_pokemon_v2_itempocket.yaml" - "!include public_pokemon_v2_itempocketname.yaml" - "!include public_pokemon_v2_itemsprites.yaml" diff --git a/graphql/v1beta2/examples/go/pokemon.go b/graphql/v1beta2/examples/go/pokemon.go index c3be49641..ef19a9ddc 100644 --- a/graphql/v1beta2/examples/go/pokemon.go +++ b/graphql/v1beta2/examples/go/pokemon.go @@ -75,7 +75,6 @@ query pokemon_details($name: String) { fireRedItems: pokemonitems(where: {version: {name: {_eq: "firered"}}}) { item { name - cost } rarity } diff --git a/graphql/v1beta2/examples/node/pokemon.js b/graphql/v1beta2/examples/node/pokemon.js index 7597f02f9..64d6f3844 100644 --- a/graphql/v1beta2/examples/node/pokemon.js +++ b/graphql/v1beta2/examples/node/pokemon.js @@ -93,7 +93,6 @@ function fetchPokemon_details(name="starmie") { fireRedItems: pokemonitems(where: {version: {name: {_eq: "firered"}}}) { item { name - cost } rarity } diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml new file mode 100644 index 000000000..b2b12e51c --- /dev/null +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml @@ -0,0 +1,34 @@ +table: + name: pokemon_v2_currency + schema: public +configuration: + column_config: {} + custom_column_names: {} + custom_name: currency + custom_root_fields: {} +array_relationships: + - name: currencynames + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_currencyname + schema: public + - name: itemprices + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_itemprice + schema: public +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true + query_root_fields: + - select + - select_aggregate + subscription_root_fields: [] diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml new file mode 100644 index 000000000..60424fed0 --- /dev/null +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml @@ -0,0 +1,26 @@ +table: + name: pokemon_v2_currencyname + schema: public +configuration: + column_config: {} + custom_column_names: {} + custom_name: currencyname + custom_root_fields: {} +object_relationships: + - name: currency + using: + foreign_key_constraint_on: currency_id + - name: language + using: + foreign_key_constraint_on: language_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true + query_root_fields: + - select + - select_aggregate + subscription_root_fields: [] diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml index f8589db37..1e174fc7d 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml @@ -70,6 +70,13 @@ array_relationships: table: name: pokemon_v2_itemname schema: public + - name: itemprices + using: + foreign_key_constraint_on: + column: item_id + table: + name: pokemon_v2_itemprice + schema: public - name: itemsprites using: foreign_key_constraint_on: diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml new file mode 100644 index 000000000..613702252 --- /dev/null +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml @@ -0,0 +1,25 @@ +table: + name: pokemon_v2_itemprice + schema: public +configuration: + column_config: {} + custom_column_names: {} + custom_name: itemprice + custom_root_fields: {} +object_relationships: + - name: item + using: + foreign_key_constraint_on: item_id + - name: currency + using: + foreign_key_constraint_on: currency_id + - name: versiongroup + using: + foreign_key_constraint_on: version_group_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml index 6bede0498..1c454f142 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml @@ -39,6 +39,13 @@ array_relationships: table: name: pokemon_v2_itemflavortext schema: public + - name: itemprices + using: + foreign_key_constraint_on: + column: version_group_id + table: + name: pokemon_v2_itemprice + schema: public - name: machines using: foreign_key_constraint_on: diff --git a/graphql/v1beta2/metadata/databases/default/tables/tables.yaml b/graphql/v1beta2/metadata/databases/default/tables/tables.yaml index 78b95c7c8..a166aed64 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/tables.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/tables.yaml @@ -18,6 +18,8 @@ - "!include public_pokemon_v2_contesteffectflavortext.yaml" - "!include public_pokemon_v2_contesttype.yaml" - "!include public_pokemon_v2_contesttypename.yaml" +- "!include public_pokemon_v2_currency.yaml" +- "!include public_pokemon_v2_currencyname.yaml" - "!include public_pokemon_v2_egggroup.yaml" - "!include public_pokemon_v2_egggroupname.yaml" - "!include public_pokemon_v2_encounter.yaml" @@ -51,6 +53,7 @@ - "!include public_pokemon_v2_itemflingeffecteffecttext.yaml" - "!include public_pokemon_v2_itemgameindex.yaml" - "!include public_pokemon_v2_itemname.yaml" +- "!include public_pokemon_v2_itemprice.yaml" - "!include public_pokemon_v2_itempocket.yaml" - "!include public_pokemon_v2_itempocketname.yaml" - "!include public_pokemon_v2_itemsprites.yaml" diff --git a/openapi.yml b/openapi.yml index 1b600bd4e..4175e13a0 100644 --- a/openapi.yml +++ b/openapi.yml @@ -4659,10 +4659,6 @@ components: name: type: string maxLength: 200 - cost: - type: - - integer - - 'null' fling_power: type: - integer @@ -4704,6 +4700,11 @@ components: items: $ref: '#/components/schemas/ItemGameIndex' readOnly: true + prices: + type: array + items: + $ref: '#/components/schemas/ItemPrice' + readOnly: true names: type: array items: @@ -4923,6 +4924,21 @@ components: required: - language - name + ItemPrice: + type: object + properties: + purchase_price: + type: + - integer + - 'null' + sell_price: + type: + - integer + - 'null' + version_group: + $ref: '#/components/schemas/VersionGroupSummary' + required: + - version_group ItemPocketDetail: type: object properties: @@ -7530,7 +7546,6 @@ components: - name - names - pokemon_entries - - region - version_groups PokedexName: type: object @@ -8638,7 +8653,6 @@ components: required: - id - locations - - main_generation - name - names - pokedexes diff --git a/pokemon_v2/api.py b/pokemon_v2/api.py index 590721839..c895646cc 100644 --- a/pokemon_v2/api.py +++ b/pokemon_v2/api.py @@ -434,6 +434,22 @@ class ItemPocketResource(PokeapiCommonViewset): list_serializer_class = ItemPocketSummarySerializer +@extend_schema( + description="Currencies used to buy items.", + summary="Get currency", + tags=["items"], +) +@extend_schema_view( + list=extend_schema( + summary="List currencies", + ) +) +class CurrencyResource(PokeapiCommonViewset): + queryset = Currency.objects.all() + serializer_class = CurrencyDetailSerializer + list_serializer_class = CurrencySummarySerializer + + @extend_schema( description="Languages for translations of API resource information.", summary="Get language", diff --git a/pokemon_v2/migrations/0026_itemprice.py b/pokemon_v2/migrations/0026_itemprice.py new file mode 100644 index 000000000..c6f773ea8 --- /dev/null +++ b/pokemon_v2/migrations/0026_itemprice.py @@ -0,0 +1,118 @@ +# Generated by Django 5.2.10 on 2026-03-07 00:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("pokemon_v2", "0025_pokemonstatpast"), + ] + + operations = [ + migrations.CreateModel( + name="Currency", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(db_index=True, max_length=200)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="ItemPrice", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("purchase_price", models.IntegerField(blank=True, null=True)), + ("sell_price", models.IntegerField(blank=True, null=True)), + ( + "currency", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.currency", + ), + ), + ( + "item", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.item", + ), + ), + ( + "version_group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.versiongroup", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="CurrencyName", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(db_index=True, max_length=200)), + ( + "currency", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.currency", + ), + ), + ( + "language", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_language", + to="pokemon_v2.language", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index 5fd7c2ae5..6038c51cf 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -57,6 +57,19 @@ class Meta: abstract = True +class HasCurrency(models.Model): + currency = models.ForeignKey( + "Currency", + blank=True, + null=True, + related_name="%(class)s", + on_delete=models.CASCADE, + ) + + class Meta: + abstract = True + + class HasSuperContestEffect(models.Model): super_contest_effect = models.ForeignKey( "SuperContestEffect", @@ -823,6 +836,14 @@ class EggGroupName(IsName, HasEggGroup): ################# +class Currency(HasName): + pass + + +class CurrencyName(IsName, HasCurrency, HasLanguage): + pass + + class ItemPocket(HasName): pass @@ -848,8 +869,6 @@ class ItemFlingEffectEffectText(HasLanguage, HasEffect, HasFlingEffect): class Item(HasName, HasItemCategory, HasFlingEffect): - cost = models.IntegerField(blank=True, null=True) - fling_power = models.IntegerField(blank=True, null=True) @@ -885,6 +904,11 @@ class ItemGameIndex(HasItem, HasGeneration, HasGameIndex): pass +class ItemPrice(HasItem, HasVersionGroup, HasCurrency): + purchase_price = models.IntegerField(blank=True, null=True) + sell_price = models.IntegerField(blank=True, null=True) + + class ItemSprites(HasItem): sprites = models.JSONField() diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index 636cdb20f..6e9373114 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -116,6 +116,12 @@ class Meta: fields = ("name", "url") +class CurrencySummarySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Currency + fields = ("name", "url") + + class ItemPocketSummarySerializer(serializers.HyperlinkedModelSerializer): class Meta: model = ItemPocket @@ -1755,6 +1761,27 @@ def get_attribute_items(self, obj): return items +########################### +# CURRENCY SERIALIZERS # +########################### + + +class CurrencyNameSerializer(serializers.ModelSerializer): + language = LanguageSummarySerializer() + + class Meta: + model = CurrencyName + fields = ("name", "language") + + +class CurrencyDetailSerializer(serializers.ModelSerializer): + names = CurrencyNameSerializer(many=True, read_only=True, source="currencyname") + + class Meta: + model = Currency + fields = ("id", "name", "names") + + ################################### # ITEM FLING EFFECT SERIALIZERS # ################################### @@ -1806,6 +1833,20 @@ class Meta: fields = ("game_index", "generation") +class ItemPriceSerializer(serializers.ModelSerializer): + currency = CurrencySummarySerializer() + version_group = VersionGroupSummarySerializer() + + class Meta: + model = ItemPrice + fields = ( + "purchase_price", + "sell_price", + "currency", + "version_group", + ) + + class ItemNameSerializer(serializers.ModelSerializer): language = LanguageSummarySerializer() @@ -1825,6 +1866,7 @@ class ItemDetailSerializer(serializers.ModelSerializer): game_indices = ItemGameIndexSerializer( many=True, read_only=True, source="itemgameindex" ) + prices = ItemPriceSerializer(many=True, read_only=True, source="itemprice") effect_entries = ItemEffectTextSerializer( many=True, read_only=True, source="itemeffecttext" ) @@ -1844,7 +1886,6 @@ class Meta: fields = ( "id", "name", - "cost", "fling_power", "fling_effect", "attributes", @@ -1852,6 +1893,7 @@ class Meta: "effect_entries", "flavor_text_entries", "game_indices", + "prices", "names", "held_by_pokemon", "sprites", diff --git a/pokemon_v2/tests.py b/pokemon_v2/tests.py index 6bed79fcd..887056323 100644 --- a/pokemon_v2/tests.py +++ b/pokemon_v2/tests.py @@ -331,13 +331,11 @@ def setup_item_data( item_category=None, item_fling_effect=None, name="itm", - cost=100, fling_power=100, ): item = Item.objects.create( name=name, item_category=item_category, - cost=cost, fling_power=fling_power, item_fling_effect=item_fling_effect, ) @@ -2714,7 +2712,6 @@ def test_item_api(self): # base params self.assertEqual(response.data["id"], item.pk) self.assertEqual(response.data["name"], item.name) - self.assertEqual(response.data["cost"], item.cost) self.assertEqual(response.data["fling_power"], item.fling_power) # name params self.assertEqual(response.data["names"][0]["name"], item_name.name) diff --git a/pokemon_v2/urls.py b/pokemon_v2/urls.py index fdd268419..08587665d 100644 --- a/pokemon_v2/urls.py +++ b/pokemon_v2/urls.py @@ -34,6 +34,7 @@ router.register(r"item-attribute", ItemAttributeResource) router.register(r"item-fling-effect", ItemFlingEffectResource) router.register(r"item-pocket", ItemPocketResource) +router.register(r"currency", CurrencyResource) router.register(r"language", LanguageResource) router.register(r"location", LocationResource) router.register(r"location-area", LocationAreaResource) diff --git a/scripts/scraping_pokeball_price.py b/scripts/scraping_pokeball_price.py new file mode 100644 index 000000000..7cbd7c0b2 --- /dev/null +++ b/scripts/scraping_pokeball_price.py @@ -0,0 +1,240 @@ +import requests +from bs4 import BeautifulSoup +import pandas as pd +import time +import re +from pathlib import Path + +CURRENCY_MAP = { + "poke-dollar": 1, + # "battle-point": 6, + # "league-point": 18, + # "blueberry-point": 19, +} + +# 1. Object Map for Item IDs (from PokeAPI's items.csv) +ITEM_MAP = { + "master-ball": 1, + "ultra-ball": 2, + "great-ball": 3, + "poke-ball": 4, + "safari-ball": 5, + "net-ball": 6, + "dive-ball": 7, + "nest-ball": 8, + "repeat-ball": 9, + "timer-ball": 10, + "luxury-ball": 11, + "premier-ball": 12, + "dusk-ball": 13, + "heal-ball": 14, + "quick-ball": 15, + "cherish-ball": 16, + "smoke-ball": 205, + "light-ball": 213, + "iron-ball": 255, + "lure-ball": 449, + "level-ball": 450, + "moon-ball": 451, + "heavy-ball": 452, + "fast-ball": 453, + "friend-ball": 454, + "love-ball": 455, + "park-ball": 456, + "sport-ball": 457, + "air-balloon": 584, + "dream-ball": 617, + "beast-ball": 887, + "left-poke-ball": 994, + "polished-mud-ball": 1031, + "strange-ball": 1663, + "koraidons-poke-ball": 1667, + "miraidons-poke-ball": 1668, + "academy-ball": 2031, + "marill-ball": 2033, + "yarn-ball": 2034, + "cyber-ball": 2035, + "blue-poke-ball-pick": 2044, + "exercise-ball": 2064, + "green-poke-ball-pick": 2084, + "red-poke-ball-pick": 2085, + "lastrange-ball": 2219, + "lapoke-ball": 2220, + "lagreat-ball": 2221, + "laultra-ball": 2222, + "laheavy-ball": 2223, + "laleaden-ball": 2224, + "lagigaton-ball": 2225, + "lafeather-ball": 2226, + "lawing-ball": 2227, + "lajet-ball": 2228, + "laorigin-ball": 2229, +} + +# 2. Version Group Mapping (from PokeAPI's version_groups.csv) +GAME_MAP = { + "RGBY": [1, 2, 28, 29], + "RBY": [1, 2], + "GSC": [3, 4], + "RSE": [5, 6], + "FRLG": [7], + "DPPt": [8, 9], + "HGSS": [10], + "BWB2W2": [11, 14], + "XY": [15], + "ORAS": [16], + "SM": [17], + "USUM": [18], + "PE": [19], + "SwSh": [20], + "BDSP": [23], + "LA": [24], + "SV": [25], + "ZA": [30], + "ColoXD": [12, 13], +} + +BASE_URL = "https://bulbapedia.bulbagarden.net" +START_URL = f"{BASE_URL}/wiki/Pok%C3%A9_Ball" + + +def normalize_name(name): + """Maps Bulbapedia names to our dictionary keys.""" + raw = name.lower().strip() + # Handle Hisui variants: 'Heavy Ball (Hisui)' -> 'laheavy-ball' + if "(hisui)" in raw: + clean = re.sub(r"[^a-z0-9\s]", "", raw.replace("(hisui)", "")) + return "la" + clean.strip().replace(" ", "-") + # Standard: 'Ultra Ball' -> 'ultra-ball' + clean = re.sub(r"[^a-z0-9\s-]", "", raw) + return clean.replace(" ", "-") + + +def get_poke_ball_links(): + print("Fetching Poké Ball list...") + res = requests.get(START_URL) + soup = BeautifulSoup(res.text, "html.parser") + balls = [] + + header = soup.find("span", id="Types_of_Pok.C3.A9_Balls") + table = header.find_next("table") + for row in table.find_all("tr")[1:]: + cols = row.find_all("td") + if len(cols) > 1: + a = cols[1].find("a") + if a: + name = a.text.strip() + identifier = normalize_name(name) + item_id = ITEM_MAP.get(identifier) + if item_id: + balls.append( + {"name": name, "item_id": item_id, "url": BASE_URL + a["href"]} + ) + return balls + + +def get_ball_prices(ball_url): + res = requests.get(ball_url) + soup = BeautifulSoup(res.text, "html.parser") + price_data = [] + + # Locate the Price section + header = soup.find("span", id="Price") + if not header: + return [] + + table = header.find_next("table") + for row in table.find_all("tr")[1:]: + cols = row.find_all("td") + if len(cols) >= 3: + game_text = cols[0].get_text(strip=True) + + # Clean prices: remove symbols, convert N/A to empty + buy = cols[1].get_text(strip=True).replace("$", "").replace(",", "") + sell = cols[2].get_text(strip=True).replace("$", "").replace(",", "") + + buy = None if buy.upper() == "N/A" or not buy else buy + sell = None if sell.upper() == "N/A" or not sell else sell + + if buy is None and sell is None: + continue + + # Cross-reference with GAME_MAP keys + v_ids = [] + for key, ids in GAME_MAP.items(): + if key in game_text: + v_ids.extend(ids) + + for v_id in set(v_ids): + price_data.append({"vg_id": v_id, "buy": buy, "sell": sell}) + + return price_data + + +def main(): + print("Starting Poké Ball price scraping...") + + output_path = Path(__file__).parent.parent / "data/v2/csv/item_prices.csv" + output_path.parent.mkdir(parents=True, exist_ok=True) + + results = [] + balls = get_poke_ball_links() + + for b in balls: + print(f"Scraping: {b['name']}...") + try: + prices = get_ball_prices(b["url"]) + for p in prices: + results.append( + { + # "name": b["name"], # enable this if you wanna keep the name in the output for easier debugging + "item_id": b["item_id"], + "version_group_id": p["vg_id"], + "currency_id": CURRENCY_MAP["poke-dollar"], + "purchase_price": p["buy"], + "sell_price": p["sell"], + } + ) + time.sleep(0.3) + except Exception as e: + print(f"Error on {b['name']}: {e}") + + if not results: + print("No new data found.") + return + + df_new = pd.DataFrame(results) + + # If file exists, merge with existing data + if output_path.exists(): + df_old = pd.read_csv(output_path) + df_combined = pd.concat([df_old, df_new], ignore_index=True) + else: + df_combined = df_new + + df_combined["purchase_price"] = pd.to_numeric( + df_combined["purchase_price"], errors="coerce" + ) + df_combined["sell_price"] = pd.to_numeric( + df_combined["sell_price"], errors="coerce" + ) + + # Deduplicate and Sort + df_final = df_combined.drop_duplicates( + subset=["item_id", "version_group_id", "currency_id"], keep="last" + ) + df_final = df_final.sort_values(by=["item_id", "version_group_id", "currency_id"]) + + df_final["purchase_price"] = df_final["purchase_price"].astype("Int64") + df_final["sell_price"] = df_final["sell_price"].astype("Int64") + + df_final["item_id"] = df_final["item_id"].astype(int) + df_final["version_group_id"] = df_final["version_group_id"].astype(int) + df_final["currency_id"] = df_final["currency_id"].astype(int) + + df_final.to_csv(output_path, index=False) + print(f"\nSuccess! File sorted and updated at: {output_path}") + + +if __name__ == "__main__": + main()