Skip to content

Commit cf332da

Browse files
committed
feat: implement inhouse
1 parent f8d9144 commit cf332da

File tree

6 files changed

+231
-23
lines changed

6 files changed

+231
-23
lines changed

app/modules/inhouses/controllers/inhouses_controller.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,87 @@ module Controllers
2121
class InhousesController < Api::V1::BaseController
2222
before_action :set_inhouse, only: %i[join balance_teams record_game close]
2323

24+
# GET /api/v1/inhouse/ladder
25+
# Returns per-player win/loss/win-rate aggregated across all done sessions.
26+
def ladder
27+
authorize Inhouse
28+
29+
rows = InhouseParticipation
30+
.joins(:inhouse, :player)
31+
.where(inhouses: { organization_id: current_organization.id, status: 'done' })
32+
.where.not(team: 'none')
33+
.group(:player_id)
34+
.select(
35+
'player_id',
36+
'SUM(wins) AS total_wins',
37+
'SUM(losses) AS total_losses'
38+
)
39+
.to_a
40+
41+
player_ids = rows.map(&:player_id)
42+
players_by_id = current_organization.players
43+
.where(id: player_ids)
44+
.index_by(&:id)
45+
46+
entries = rows.map do |row|
47+
player = players_by_id[row.player_id]
48+
next unless player
49+
50+
total = row.total_wins.to_i + row.total_losses.to_i
51+
win_rate = total.zero? ? 0.0 : (row.total_wins.to_f / total * 100).round(1)
52+
53+
{
54+
player_id: row.player_id,
55+
player_name: player.summoner_name,
56+
role: player.role,
57+
wins: row.total_wins.to_i,
58+
losses: row.total_losses.to_i,
59+
total_games: total,
60+
win_rate: win_rate
61+
}
62+
end.compact
63+
64+
entries.sort_by! { |e| [-e[:wins], e[:losses]] }
65+
entries.each_with_index { |e, i| e[:rank] = i + 1 }
66+
67+
render_success({ entries: entries, total: entries.size })
68+
end
69+
70+
# GET /api/v1/inhouse/sessions
71+
# Returns paginated history of completed inhouse sessions with summary.
72+
def sessions
73+
authorize Inhouse
74+
75+
inhouses = current_organization.inhouses.history.recent
76+
.includes(:inhouse_participations)
77+
78+
page = (params[:page] || 1).to_i
79+
per_page = [(params[:per_page] || 10).to_i, 50].min
80+
inhouses = inhouses.page(page).per(per_page)
81+
82+
sessions = inhouses.map do |ih|
83+
{
84+
id: ih.id,
85+
games_played: ih.games_played,
86+
blue_wins: ih.blue_wins,
87+
red_wins: ih.red_wins,
88+
player_count: ih.inhouse_participations.size,
89+
formation_mode: nil,
90+
created_at: ih.created_at,
91+
closed_at: ih.updated_at
92+
}
93+
end
94+
95+
render_success({
96+
sessions: sessions,
97+
meta: {
98+
current_page: inhouses.current_page,
99+
total_pages: inhouses.total_pages,
100+
total_count: inhouses.total_count
101+
}
102+
})
103+
end
104+
24105
# GET /api/v1/inhouse/inhouses
25106
# Returns paginated history of completed inhouse sessions.
26107
# Pass ?all=true to include active ones too.

app/modules/matchmaking/serializers/scrim_request_serializer.rb

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,16 @@ class ScrimRequestSerializer < Blueprinter::Base
22
identifier :id
33

44
fields :status, :game, :message, :proposed_at, :expires_at,
5+
:games_planned, :draft_type,
56
:requesting_scrim_id, :target_scrim_id,
67
:created_at, :updated_at
78

89
field :requesting_organization do |req|
9-
{
10-
id: req.requesting_organization.id,
11-
name: req.requesting_organization.name,
12-
slug: req.requesting_organization.slug,
13-
region: req.requesting_organization.region
14-
}
10+
serialize_org(req.requesting_organization)
1511
end
1612

1713
field :target_organization do |req|
18-
{
19-
id: req.target_organization.id,
20-
name: req.target_organization.name,
21-
slug: req.target_organization.slug,
22-
region: req.target_organization.region
23-
}
14+
serialize_org(req.target_organization)
2415
end
2516

2617
field :pending do |req|
@@ -30,4 +21,47 @@ class ScrimRequestSerializer < Blueprinter::Base
3021
field :expired do |req|
3122
req.expired?
3223
end
24+
25+
class << self
26+
TIER_SCORE = {
27+
'CHALLENGER' => 9, 'GRANDMASTER' => 8, 'MASTER' => 7,
28+
'DIAMOND' => 6, 'EMERALD' => 5, 'PLATINUM' => 4,
29+
'GOLD' => 3, 'SILVER' => 2, 'BRONZE' => 1
30+
}.freeze
31+
32+
TIER_LABEL = {
33+
9 => 'Challenger', 8 => 'Grandmaster', 7 => 'Master',
34+
6 => 'Diamond', 5 => 'Emerald', 4 => 'Platinum',
35+
3 => 'Gold', 2 => 'Silver', 1 => 'Bronze', 0 => 'Iron'
36+
}.freeze
37+
38+
def serialize_org(org)
39+
players = org.players.active.select(:summoner_name, :role, :solo_queue_tier)
40+
41+
scores = players.map { |p| TIER_SCORE[p.solo_queue_tier.to_s.upcase] || 0 }
42+
avg_score = scores.empty? ? 0 : (scores.sum.to_f / scores.size).round
43+
avg_tier = TIER_LABEL[avg_score] || 'Iron'
44+
45+
{
46+
id: org.id,
47+
name: org.name,
48+
slug: org.slug,
49+
region: org.region,
50+
tier: org.tier,
51+
public_tagline: org.public_tagline,
52+
discord_server: org.discord_invite_url,
53+
scrims_won: org.scrims_won || 0,
54+
scrims_lost: org.scrims_lost || 0,
55+
total_scrims: org.total_scrims || 0,
56+
avg_tier: avg_tier,
57+
roster: players.map do |p|
58+
{
59+
summoner_name: p.summoner_name,
60+
role: p.role,
61+
tier: p.solo_queue_tier
62+
}
63+
end
64+
}
65+
end
66+
end
3367
end

app/modules/scrims/controllers/scrims_controller.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ def show
8383

8484
# POST /api/v1/scrims
8585
def create
86-
# Check scrim creation limit
8786
unless current_organization.can_create_scrim?
8887
return render json: {
8988
error: 'Scrim Limit Reached',
@@ -93,6 +92,16 @@ def create
9392

9493
scrim = current_organization.scrims.new(scrim_params)
9594

95+
opponent_name = params.dig(:scrim, :opponent_team_name).to_s.strip
96+
if opponent_name.present?
97+
tag = opponent_name.split.map(&:first).join.upcase.first(5)
98+
opponent = OpponentTeam.find_or_initialize_by(name: opponent_name)
99+
opponent.tag ||= tag
100+
opponent.region ||= current_organization.region
101+
opponent.save
102+
scrim.opponent_team = opponent
103+
end
104+
96105
if scrim.save
97106
render json: { data: ScrimSerializer.new(scrim).as_json }, status: :created
98107
else

app/modules/scrims/serializers/scrim_serializer.rb

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,82 @@ def timestamps_fields
5454

5555
def detailed_attributes
5656
{
57-
match_id: @scrim.match_id,
57+
match_id: @scrim.match_id,
5858
pre_game_notes: @scrim.pre_game_notes,
5959
post_game_notes: @scrim.post_game_notes,
60-
game_results: @scrim.game_results,
61-
objectives: @scrim.objectives,
62-
outcomes: @scrim.outcomes,
63-
objectives_met: @scrim.objectives_met?
60+
game_results: @scrim.game_results,
61+
objectives: @scrim.objectives,
62+
outcomes: @scrim.outcomes,
63+
objectives_met: @scrim.objectives_met?,
64+
opponent_detail: opponent_detail,
65+
head_to_head: head_to_head
66+
}
67+
end
68+
69+
TIER_SCORE = {
70+
'CHALLENGER' => 9, 'GRANDMASTER' => 8, 'MASTER' => 7,
71+
'DIAMOND' => 6, 'EMERALD' => 5, 'PLATINUM' => 4,
72+
'GOLD' => 3, 'SILVER' => 2, 'BRONZE' => 1
73+
}.freeze
74+
75+
TIER_LABEL = {
76+
9 => 'Challenger', 8 => 'Grandmaster', 7 => 'Master',
77+
6 => 'Diamond', 5 => 'Emerald', 4 => 'Platinum',
78+
3 => 'Gold', 2 => 'Silver', 1 => 'Bronze', 0 => 'Iron'
79+
}.freeze
80+
81+
def opponent_detail
82+
return nil unless @scrim.opponent_team
83+
84+
t = @scrim.opponent_team
85+
86+
# Try to find the registered Organization with the same name
87+
org = Organization.unscoped.find_by(name: t.name)
88+
roster, avg_tier = org_roster_and_avg(org)
89+
90+
{
91+
league: t.league,
92+
discord_server: t.discord_server || org&.discord_invite_url,
93+
known_players: Array(t.known_players),
94+
playstyle_notes: t.playstyle_notes,
95+
strengths: Array(t.strengths),
96+
weaknesses: Array(t.weaknesses),
97+
roster: roster,
98+
avg_tier: avg_tier
99+
}
100+
end
101+
102+
def org_roster_and_avg(org)
103+
return [[], nil] unless org
104+
105+
players = org.players.active.select(:summoner_name, :role, :solo_queue_tier)
106+
scores = players.map { |p| TIER_SCORE[p.solo_queue_tier.to_s.upcase] || 0 }
107+
avg = scores.empty? ? nil : TIER_LABEL[(scores.sum.to_f / scores.size).round]
108+
109+
roster = players.map { |p| { summoner_name: p.summoner_name, role: p.role, tier: p.solo_queue_tier } }
110+
[roster, avg]
111+
end
112+
113+
def head_to_head
114+
return nil unless @scrim.opponent_team_id
115+
116+
past = Scrim.unscoped
117+
.where(organization_id: @scrim.organization_id,
118+
opponent_team_id: @scrim.opponent_team_id)
119+
.where.not(id: @scrim.id)
120+
.where.not(games_completed: nil)
121+
.where('games_completed >= games_planned')
122+
.order(scheduled_at: :desc)
123+
.limit(10)
124+
.to_a
125+
126+
wins = past.count { |s| s.win_rate.to_f >= 50 }
127+
losses = past.count - wins
128+
129+
{
130+
wins: wins,
131+
losses: losses,
132+
total: past.count
64133
}
65134
end
66135

@@ -76,12 +145,16 @@ def calendar_attributes
76145
def opponent_team_summary
77146
return nil unless @scrim.opponent_team
78147

148+
t = @scrim.opponent_team
79149
{
80-
id: @scrim.opponent_team.id,
81-
name: @scrim.opponent_team.name,
82-
tag: @scrim.opponent_team.tag,
83-
tier: @scrim.opponent_team.tier,
84-
logo_url: @scrim.opponent_team.logo_url
150+
id: t.id,
151+
name: t.name,
152+
tag: t.tag,
153+
tier: t.tier,
154+
region: t.region,
155+
scrims_won: t.scrims_won || 0,
156+
scrims_lost: t.scrims_lost || 0,
157+
logo_url: t.logo_url
85158
}
86159
end
87160

app/policies/inhouse_policy.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ def active?
1414
user.present?
1515
end
1616

17+
def ladder?
18+
user.present?
19+
end
20+
21+
def sessions?
22+
user.present?
23+
end
24+
1725
def create?
1826
coach?
1927
end

config/routes.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@
287287

288288
# Inhouse Module — internal practice sessions between org's own players
289289
scope '/inhouse', as: 'inhouse' do
290+
get 'ladder', to: '/inhouses/controllers/inhouses#ladder'
291+
get 'sessions', to: '/inhouses/controllers/inhouses#sessions'
292+
290293
resources :inhouses, controller: '/inhouses/controllers/inhouses', only: %i[index create] do
291294
collection do
292295
get :active

0 commit comments

Comments
 (0)