257 lines
7.9 KiB
Ruby
257 lines
7.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class MatchesController < ApplicationController
|
|
before_action :set_match, only: %i[show update swap]
|
|
before_action :set_group, only: %i[create]
|
|
before_action :validate_params, only: %i[update]
|
|
before_action :set_swap_target, only: %i[swap]
|
|
before_action -> { require_owner! @match.owner }, only: %i[update swap]
|
|
before_action -> { require_owner! @group.owner }, only: %i[create]
|
|
before_action :set_tournament, only: %i[index]
|
|
before_action -> { require_writable_tournament!(@match.tournament) }, only: %i[update swap]
|
|
before_action -> { require_writable_tournament!(@group.tournament) }, only: %i[create]
|
|
|
|
# GET/tournaments/1/matches
|
|
def index
|
|
render json: TournamentMatchesPayload.build(@tournament, match_index_params)
|
|
end
|
|
|
|
# GET /matches/1
|
|
def show
|
|
render json: @match, include: %w[match_scores.points match_scores.team bets]
|
|
end
|
|
|
|
# POST /groups/:group_id/matches
|
|
def create
|
|
match = nil
|
|
synced_tournament = nil
|
|
|
|
Match.transaction do
|
|
TournamentTransactionLogger.lock_tournament!(@group.tournament)
|
|
match = build_decider_match
|
|
|
|
if match.errors.any?
|
|
render json: match.errors, status: :unprocessable_content
|
|
raise ActiveRecord::Rollback
|
|
elsif match.save
|
|
UpdateGroupsGroupScoresAndSave.call(group: @group)
|
|
group_scores = group_scores_for_log(@group)
|
|
TournamentTransactionLogger.record_decider_match_created!(match: match, user: current_user, group_scores: group_scores)
|
|
synced_tournament = @group.tournament
|
|
render json: match, include: %w[match_scores.team group], status: :created
|
|
else
|
|
render json: match.errors, status: :unprocessable_content
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
end
|
|
|
|
push_sync_if_needed!(synced_tournament) unless synced_tournament.nil?
|
|
TournamentLiveBroadcast.call(synced_tournament.id) unless synced_tournament.nil?
|
|
end
|
|
|
|
# PATCH/PUT /matches/1
|
|
def update
|
|
new_state = match_params['state']
|
|
synced_tournament = nil
|
|
@transaction_log_group_scores = []
|
|
@transaction_log_advanced_match_ids = []
|
|
|
|
Match.transaction do
|
|
synced_tournament = update_match_inside_transaction(new_state)
|
|
end
|
|
push_sync_if_needed!(synced_tournament) unless synced_tournament.nil?
|
|
TournamentLiveBroadcast.call(synced_tournament.id) unless synced_tournament.nil?
|
|
end
|
|
|
|
# PATCH /matches/1/swap
|
|
def swap
|
|
unless @match.swappable_with?(@swap_target)
|
|
render json: { error: 'Matches must be different group matches from the same group' }, status: :unprocessable_content
|
|
return
|
|
end
|
|
|
|
synced_tournament = nil
|
|
|
|
Match.transaction do
|
|
source_position = @match.position
|
|
target_position = @swap_target.position
|
|
|
|
@match.update!(position: target_position)
|
|
@swap_target.update!(position: source_position)
|
|
|
|
synced_tournament = @match.tournament
|
|
end
|
|
|
|
swapped_matches = Match.where(id: [@match.id, @swap_target.id]).order(:position)
|
|
render json: swapped_matches, each_serializer: ExtendedMatchSerializer, include: %w[match_scores.team bets stage group]
|
|
push_sync_if_needed!(synced_tournament)
|
|
TournamentLiveBroadcast.call(synced_tournament.id)
|
|
end
|
|
|
|
private
|
|
|
|
def handle_match_end
|
|
if @match.group_match?
|
|
group = @match.group
|
|
unless UpdateGroupsGroupScoresAndSave.call(group: group).success?
|
|
logger.warn "Updating groups group score failed for #{group}"
|
|
end
|
|
@transaction_log_group_scores = group_scores_for_log(group)
|
|
return
|
|
end
|
|
|
|
if @match.winner.nil?
|
|
render json: { error: 'Stopping undecided Matches isn\'t allowed in playoff stage' },
|
|
status: :unprocessable_content
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
|
|
result = PopulateMatchBelowAndSave.call(match: @match)
|
|
if result.success?
|
|
@transaction_log_advanced_match_ids = advanced_match_ids_for(result)
|
|
return
|
|
end
|
|
|
|
render json: { error: 'Moving Team one stage down failed' },
|
|
status: :unprocessable_content
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
|
|
def handle_match_stop
|
|
@match.match_scores.each do |match_score|
|
|
match_score.update!(points: 0, hidden_points: 0)
|
|
end
|
|
end
|
|
|
|
def update_group_scores
|
|
group = @match.group
|
|
if UpdateGroupsGroupScoresAndSave.call(group: group).success?
|
|
@transaction_log_group_scores = group_scores_for_log(group)
|
|
return
|
|
end
|
|
|
|
logger.warn "Updating groups group score failed for #{group}"
|
|
end
|
|
|
|
def update_match_inside_transaction(new_state)
|
|
tournament = @match.tournament
|
|
TournamentTransactionLogger.lock_tournament!(tournament)
|
|
@match.reload
|
|
validate_transition_inside_transaction(new_state)
|
|
|
|
old_state = @match.state
|
|
if @match.update(match_params)
|
|
record_successful_match_update(old_state, new_state)
|
|
render json: @match
|
|
tournament
|
|
else
|
|
render json: @match.errors, status: :unprocessable_content
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
end
|
|
|
|
def validate_transition_inside_transaction(new_state)
|
|
transition_error = match_transition_error(@match, new_state)
|
|
return if transition_error.nil?
|
|
|
|
render json: { error: transition_error }, status: :unprocessable_content
|
|
raise ActiveRecord::Rollback
|
|
end
|
|
|
|
def record_successful_match_update(old_state, new_state)
|
|
handle_match_end if new_state == 'finished'
|
|
handle_match_stop if new_state == 'not_started'
|
|
update_group_scores if @match.group_match? && %w[in_progress not_started].include?(new_state)
|
|
TournamentTransactionLogger.record_match_state_change!(
|
|
match: @match,
|
|
user: current_user,
|
|
old_state: old_state,
|
|
new_state: new_state,
|
|
details: {
|
|
group_scores: @transaction_log_group_scores,
|
|
advanced_match_ids: @transaction_log_advanced_match_ids
|
|
}
|
|
)
|
|
end
|
|
|
|
def validate_params
|
|
transition_error = match_transition_error(@match, match_params['state'])
|
|
return if transition_error.nil?
|
|
|
|
render json: { error: transition_error }, status: :unprocessable_content
|
|
end
|
|
|
|
def set_match
|
|
@match = Match.find(params[:id])
|
|
end
|
|
|
|
def set_tournament
|
|
@tournament = Tournament.find(params[:tournament_id])
|
|
end
|
|
|
|
def set_swap_target
|
|
@swap_target = Match.find_by(id: params[:other_match_id])
|
|
return unless @swap_target.nil?
|
|
|
|
render json: { error: 'Match not found' }, status: :not_found
|
|
end
|
|
|
|
def set_group
|
|
@group = Group.find(params[:group_id])
|
|
end
|
|
|
|
def match_params
|
|
params.slice(:state).permit!
|
|
end
|
|
|
|
def match_index_params
|
|
params.slice(:state, :stage_id, :stage_level, :position, :group_stage_position).permit!
|
|
end
|
|
|
|
def create_match_params
|
|
params.slice(:team_ids).permit(team_ids: [])
|
|
end
|
|
|
|
def build_decider_match
|
|
team_ids = create_match_params[:team_ids].to_a.map(&:to_i).uniq
|
|
teams = @group.teams.select { |team| team_ids.include?(team.id) }
|
|
|
|
Match.new(
|
|
group: @group,
|
|
state: :not_started,
|
|
position: (@group.matches.maximum(:position) || -1) + 1,
|
|
decider: true,
|
|
match_scores: teams.map { |team| MatchScore.new(team: team) }
|
|
).tap do |match|
|
|
if team_ids.size != 2 || teams.size != 2
|
|
match.errors.add(:team_ids, 'must contain exactly two teams from this group')
|
|
end
|
|
end
|
|
end
|
|
|
|
def match_transition_error(match, new_state)
|
|
case new_state
|
|
when 'in_progress'
|
|
'Match can\'t start in this state' unless match.not_started?
|
|
when 'finished'
|
|
'Match can\'t finish in this state' unless match.in_progress?
|
|
when 'not_started'
|
|
'Match can\'t stop in this state' unless match.in_progress?
|
|
else
|
|
'Invalid target state'
|
|
end
|
|
end
|
|
|
|
def group_scores_for_log(group)
|
|
group.group_scores.includes(:team).reload
|
|
end
|
|
|
|
def advanced_match_ids_for(result)
|
|
Array(result.object_to_save).flatten.grep(Match).reject { |match| match.id == @match.id }.map(&:id)
|
|
end
|
|
|
|
def push_sync_if_needed!(tournament)
|
|
TournamentSyncEnqueue.call(tournament)
|
|
end
|
|
end
|