Skip to content

Commit c3309d1

Browse files
authored
feat: implement datadragon and player sync (#9)
* feat: implement datadragon and player sync 1. Data Dragon Service - Busca dados estáticos da Riot 2. Região Configurável - Não mais hardcoded, usa player.region 3. Champion Mapping - Funcionando corretamente agora 4. 8 Novos API Endpoints - Para dados da Riot 5. 5 Rake Tasks - Gerenciamento de cache e sync 6. Suite de Testes - Criada para SyncPlayerFromRiotJob * chore: add admin bypass ruleset
1 parent f775a2e commit c3309d1

14 files changed

Lines changed: 678 additions & 23 deletions

.github/branch-protection-ruleset.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,11 @@
4949
"type": "creation"
5050
}
5151
],
52-
"bypass_actors": []
52+
"bypass_actors": [
53+
{
54+
"actor_id": 1,
55+
"actor_type": "RepositoryRole",
56+
"bypass_mode": "always"
57+
}
58+
]
5359
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
module Api
2+
module V1
3+
class RiotDataController < BaseController
4+
skip_before_action :authenticate_request!, only: [:champions, :champion_details, :items, :version]
5+
6+
# GET /api/v1/riot-data/champions
7+
def champions
8+
service = DataDragonService.new
9+
champions = service.champion_id_map
10+
11+
render_success({
12+
champions: champions,
13+
count: champions.count
14+
})
15+
rescue DataDragonService::DataDragonError => e
16+
render_error('Failed to fetch champion data', :service_unavailable, details: e.message)
17+
end
18+
19+
# GET /api/v1/riot-data/champions/:champion_key
20+
def champion_details
21+
service = DataDragonService.new
22+
champion = service.champion_by_key(params[:champion_key])
23+
24+
if champion.present?
25+
render_success({
26+
champion: champion
27+
})
28+
else
29+
render_error('Champion not found', :not_found)
30+
end
31+
rescue DataDragonService::DataDragonError => e
32+
render_error('Failed to fetch champion details', :service_unavailable, details: e.message)
33+
end
34+
35+
# GET /api/v1/riot-data/all-champions
36+
def all_champions
37+
service = DataDragonService.new
38+
champions = service.all_champions
39+
40+
render_success({
41+
champions: champions,
42+
count: champions.count
43+
})
44+
rescue DataDragonService::DataDragonError => e
45+
render_error('Failed to fetch champions', :service_unavailable, details: e.message)
46+
end
47+
48+
# GET /api/v1/riot-data/items
49+
def items
50+
service = DataDragonService.new
51+
items = service.items
52+
53+
render_success({
54+
items: items,
55+
count: items.count
56+
})
57+
rescue DataDragonService::DataDragonError => e
58+
render_error('Failed to fetch items', :service_unavailable, details: e.message)
59+
end
60+
61+
# GET /api/v1/riot-data/summoner-spells
62+
def summoner_spells
63+
service = DataDragonService.new
64+
spells = service.summoner_spells
65+
66+
render_success({
67+
summoner_spells: spells,
68+
count: spells.count
69+
})
70+
rescue DataDragonService::DataDragonError => e
71+
render_error('Failed to fetch summoner spells', :service_unavailable, details: e.message)
72+
end
73+
74+
# GET /api/v1/riot-data/version
75+
def version
76+
service = DataDragonService.new
77+
version = service.latest_version
78+
79+
render_success({
80+
version: version
81+
})
82+
rescue DataDragonService::DataDragonError => e
83+
render_error('Failed to fetch version', :service_unavailable, details: e.message)
84+
end
85+
86+
# POST /api/v1/riot-data/clear-cache
87+
def clear_cache
88+
authorize :riot_data, :manage?
89+
90+
service = DataDragonService.new
91+
service.clear_cache!
92+
93+
log_user_action(
94+
action: 'clear_cache',
95+
entity_type: 'RiotData',
96+
entity_id: nil,
97+
details: { message: 'Data Dragon cache cleared' }
98+
)
99+
100+
render_success({
101+
message: 'Cache cleared successfully'
102+
})
103+
end
104+
105+
# POST /api/v1/riot-data/update-cache
106+
def update_cache
107+
authorize :riot_data, :manage?
108+
109+
service = DataDragonService.new
110+
service.clear_cache!
111+
112+
# Preload all data
113+
version = service.latest_version
114+
champions = service.champion_id_map
115+
items = service.items
116+
spells = service.summoner_spells
117+
118+
log_user_action(
119+
action: 'update_cache',
120+
entity_type: 'RiotData',
121+
entity_id: nil,
122+
details: {
123+
version: version,
124+
champions_count: champions.count,
125+
items_count: items.count,
126+
spells_count: spells.count
127+
}
128+
)
129+
130+
render_success({
131+
message: 'Cache updated successfully',
132+
version: version,
133+
data: {
134+
champions: champions.count,
135+
items: items.count,
136+
summoner_spells: spells.count
137+
}
138+
})
139+
rescue DataDragonService::DataDragonError => e
140+
render_error('Failed to update cache', :service_unavailable, details: e.message)
141+
end
142+
end
143+
end
144+
end

app/jobs/sync_player_from_riot_job.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def perform(player_id)
2020
end
2121

2222
begin
23-
region = 'br1' # TODO: Make this configurable per player
23+
# Use player's region or default to BR1
24+
region = player.region.presence&.downcase || 'br1'
2425

2526
# Fetch summoner data
2627
if player.riot_puuid.present?

app/jobs/sync_player_job.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,6 @@ def current_season
148148
end
149149

150150
def load_champion_id_map
151-
# This is a simplified version. In production, you would load this from Data Dragon
152-
# or cache it in Redis
153-
Rails.cache.fetch('riot:champion_id_map', expires_in: 1.week) do
154-
# Fetch from Data Dragon API or use a static file
155-
# For now, return an empty hash
156-
{}
157-
end
151+
DataDragonService.new.champion_id_map
158152
end
159153
end

app/jobs/sync_scouting_target_job.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ def should_update_peak?(target, new_tier, new_rank)
108108
end
109109

110110
def load_champion_id_map
111-
Rails.cache.fetch('riot:champion_id_map', expires_in: 1.week) do
112-
{}
113-
end
111+
DataDragonService.new.champion_id_map
114112
end
115113
end

app/policies/riot_data_policy.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class RiotDataPolicy < ApplicationPolicy
2+
def manage?
3+
user.admin_or_owner?
4+
end
5+
6+
class Scope < Scope
7+
def resolve
8+
scope.all
9+
end
10+
end
11+
end
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
class DataDragonService
2+
BASE_URL = 'https://ddragon.leagueoflegends.com'.freeze
3+
4+
class DataDragonError < StandardError; end
5+
6+
def initialize
7+
@latest_version = nil
8+
end
9+
10+
# Get the latest game version
11+
def latest_version
12+
@latest_version ||= fetch_latest_version
13+
end
14+
15+
# Get champion ID to name mapping
16+
def champion_id_map
17+
Rails.cache.fetch('riot:champion_id_map', expires_in: 1.week) do
18+
fetch_champion_data
19+
end
20+
end
21+
22+
# Get champion name to ID mapping (reverse)
23+
def champion_name_map
24+
Rails.cache.fetch('riot:champion_name_map', expires_in: 1.week) do
25+
champion_id_map.invert
26+
end
27+
end
28+
29+
# Get all champions data (full details)
30+
def all_champions
31+
Rails.cache.fetch('riot:all_champions', expires_in: 1.week) do
32+
fetch_all_champions_data
33+
end
34+
end
35+
36+
# Get specific champion data by key
37+
def champion_by_key(champion_key)
38+
all_champions[champion_key]
39+
end
40+
41+
# Get profile icons data
42+
def profile_icons
43+
Rails.cache.fetch('riot:profile_icons', expires_in: 1.week) do
44+
fetch_profile_icons
45+
end
46+
end
47+
48+
# Get summoner spells data
49+
def summoner_spells
50+
Rails.cache.fetch('riot:summoner_spells', expires_in: 1.week) do
51+
fetch_summoner_spells
52+
end
53+
end
54+
55+
# Get items data
56+
def items
57+
Rails.cache.fetch('riot:items', expires_in: 1.week) do
58+
fetch_items
59+
end
60+
end
61+
62+
# Clear all cached data
63+
def clear_cache!
64+
Rails.cache.delete('riot:champion_id_map')
65+
Rails.cache.delete('riot:champion_name_map')
66+
Rails.cache.delete('riot:all_champions')
67+
Rails.cache.delete('riot:profile_icons')
68+
Rails.cache.delete('riot:summoner_spells')
69+
Rails.cache.delete('riot:items')
70+
Rails.cache.delete('riot:latest_version')
71+
@latest_version = nil
72+
end
73+
74+
private
75+
76+
def fetch_latest_version
77+
cached_version = Rails.cache.read('riot:latest_version')
78+
return cached_version if cached_version.present?
79+
80+
url = "#{BASE_URL}/api/versions.json"
81+
response = make_request(url)
82+
versions = JSON.parse(response.body)
83+
84+
latest = versions.first
85+
Rails.cache.write('riot:latest_version', latest, expires_in: 1.day)
86+
latest
87+
rescue StandardError => e
88+
Rails.logger.error("Failed to fetch latest version: #{e.message}")
89+
# Fallback to a recent known version
90+
'14.1.1'
91+
end
92+
93+
def fetch_champion_data
94+
version = latest_version
95+
url = "#{BASE_URL}/cdn/#{version}/data/en_US/champion.json"
96+
97+
response = make_request(url)
98+
data = JSON.parse(response.body)
99+
100+
# Create mapping: champion_id (integer) => champion_name (string)
101+
champion_map = {}
102+
data['data'].each do |_key, champion|
103+
champion_id = champion['key'].to_i
104+
champion_name = champion['id'] # This is the champion name like "Aatrox"
105+
champion_map[champion_id] = champion_name
106+
end
107+
108+
champion_map
109+
rescue StandardError => e
110+
Rails.logger.error("Failed to fetch champion data: #{e.message}")
111+
{}
112+
end
113+
114+
def fetch_all_champions_data
115+
version = latest_version
116+
url = "#{BASE_URL}/cdn/#{version}/data/en_US/champion.json"
117+
118+
response = make_request(url)
119+
data = JSON.parse(response.body)
120+
121+
data['data']
122+
rescue StandardError => e
123+
Rails.logger.error("Failed to fetch all champions data: #{e.message}")
124+
{}
125+
end
126+
127+
def fetch_profile_icons
128+
version = latest_version
129+
url = "#{BASE_URL}/cdn/#{version}/data/en_US/profileicon.json"
130+
131+
response = make_request(url)
132+
data = JSON.parse(response.body)
133+
134+
data['data']
135+
rescue StandardError => e
136+
Rails.logger.error("Failed to fetch profile icons: #{e.message}")
137+
{}
138+
end
139+
140+
def fetch_summoner_spells
141+
version = latest_version
142+
url = "#{BASE_URL}/cdn/#{version}/data/en_US/summoner.json"
143+
144+
response = make_request(url)
145+
data = JSON.parse(response.body)
146+
147+
data['data']
148+
rescue StandardError => e
149+
Rails.logger.error("Failed to fetch summoner spells: #{e.message}")
150+
{}
151+
end
152+
153+
def fetch_items
154+
version = latest_version
155+
url = "#{BASE_URL}/cdn/#{version}/data/en_US/item.json"
156+
157+
response = make_request(url)
158+
data = JSON.parse(response.body)
159+
160+
data['data']
161+
rescue StandardError => e
162+
Rails.logger.error("Failed to fetch items: #{e.message}")
163+
{}
164+
end
165+
166+
def make_request(url)
167+
conn = Faraday.new do |f|
168+
f.request :retry, max: 3, interval: 0.5, backoff_factor: 2
169+
f.adapter Faraday.default_adapter
170+
end
171+
172+
response = conn.get(url) do |req|
173+
req.options.timeout = 10
174+
end
175+
176+
unless response.success?
177+
raise DataDragonError, "Request failed with status #{response.status}"
178+
end
179+
180+
response
181+
rescue Faraday::TimeoutError => e
182+
raise DataDragonError, "Request timeout: #{e.message}"
183+
rescue Faraday::Error => e
184+
raise DataDragonError, "Network error: #{e.message}"
185+
end
186+
end

0 commit comments

Comments
 (0)