diff --git a/Gemfile.lock b/Gemfile.lock index d40cadd..0779004 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -328,6 +328,7 @@ PLATFORMS aarch64-linux-musl arm64-darwin-22 arm64-darwin-23 + arm64-darwin-24 x86_64-linux DEPENDENCIES diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index 2acf65f..826043c 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -15,6 +15,8 @@ class MatchesController < ApplicationController elsif match_params['state'] == 'upcoming' # for every group within the tournament find the match with the lowest position that is of state 'not_started' upcoming_matches = @tournament.stages.find_by(level: -1)&.groups&.map { |g| g.matches.select { |m| m.state == 'not_started' }.min_by(&:position) } + # filter out nil values (this may happen if one of the groups already has no upcoming matches) + upcoming_matches = upcoming_matches.reject(&:nil?) # if there are none, the group stage is over, so we have to look into the playoff stages if upcoming_matches.nil? next_level = 0 @@ -34,14 +36,12 @@ class MatchesController < ApplicationController m.state == match_params['state'] end end - render json: matches, each_serializer: ExtendedMatchSerializer, include: [ - 'match_scores.team', 'bets', 'stage', 'group' - ] + render json: matches, each_serializer: ExtendedMatchSerializer, include: %w[match_scores.team bets stage group] end # GET /matches/1 def show - render json: @match, include: ['match_scores.points', 'match_scores.team', 'bets'] + render json: @match, include: %w[match_scores.points match_scores.team bets] end # PATCH/PUT /matches/1 diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index a40c6bf..2e33843 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true class TournamentsController < ApplicationController - before_action :set_tournament, only: %i[show update destroy] - before_action :authenticate_user!, only: %i[create update destroy] - before_action -> { require_owner! @tournament.owner }, only: %i[update destroy] + before_action :set_tournament, only: %i[show update destroy set_timer_end timer_end] + before_action :authenticate_user!, only: %i[create update destroy set_timer_end] + before_action -> { require_owner! @tournament.owner }, only: %i[update destroy set_timer_end] before_action :validate_create_params, only: %i[create] before_action :validate_update_params, only: %i[update] + before_action :validate_set_timer_end_params, only: %i[set_timer_end] rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_error # GET /tournaments @@ -93,8 +94,27 @@ class TournamentsController < ApplicationController @tournament.destroy end + # GET /tournaments/:id/timer_end + def timer_end + render json: { timer_end: @tournament.timer_end } + end + + # PATCH /tournaments/:id/set_timer_end + def set_timer_end + if @tournament.update(timer_end_params) + render json: @tournament + else + render json: @tournament.errors, status: :unprocessable_entity + end + end + + private + def timer_end_params + { timer_end: params[:timer_end] } + end + def organize_teams_in_groups(teams) # each team gets put into a array of teams depending on the group specified in team[:group] teams.group_by { |team| team['group'] }.values.map do |group| @@ -158,3 +178,39 @@ class TournamentsController < ApplicationController }, status: :unprocessable_entity end end + +def validate_set_timer_end_params + timer_end = params[:timer_end] + timer_end_seconds = params[:timer_end_seconds] + + # throw error if both timer_end and timer_end_seconds are present + if timer_end.present? && timer_end_seconds.present? + return render json: { error: 'Only one of timer_end or timer_end_seconds is allowed' }, status: :unprocessable_entity + end + + if timer_end_seconds.present? + begin + timer_end_seconds = Integer(timer_end_seconds) + rescue ArgumentError + return render json: { error: 'Invalid seconds format' }, status: :unprocessable_entity + end + + return render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity if timer_end_seconds <= 0 + + parsed_time = Time.zone.now + timer_end_seconds + params[:timer_end] = parsed_time + elsif timer_end.present? + begin + parsed_time = Time.zone.parse(timer_end) + if parsed_time.nil? + return render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + elsif !parsed_time.future? + return render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity + end + rescue ArgumentError + return render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + end + else + return render json: { error: 'Timer end is required' }, status: :unprocessable_entity + end +end diff --git a/app/serializers/tournament_serializer.rb b/app/serializers/tournament_serializer.rb index df02e38..751aa4f 100644 --- a/app/serializers/tournament_serializer.rb +++ b/app/serializers/tournament_serializer.rb @@ -2,7 +2,7 @@ class TournamentSerializer < SimpleTournamentSerializer attributes :description, :playoff_teams_amount, - :instant_finalists_amount, :intermediate_round_participants_amount + :instant_finalists_amount, :intermediate_round_participants_amount, :timer_end has_many :stages attribute :owner_username do diff --git a/config/routes.rb b/config/routes.rb index 101e869..9a6cf02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,10 @@ Rails.application.routes.draw do resources :tournaments do resources :statistics, only: %i[index] resources :matches, only: %i[index] + member do + get :timer_end + patch :set_timer_end + end end resources :match_scores, only: %i[show update] resources :groups, only: %i[show] diff --git a/db/migrate/20250309202658_add_timer_end_to_tournaments.rb b/db/migrate/20250309202658_add_timer_end_to_tournaments.rb new file mode 100644 index 0000000..1762bec --- /dev/null +++ b/db/migrate/20250309202658_add_timer_end_to_tournaments.rb @@ -0,0 +1,5 @@ +class AddTimerEndToTournaments < ActiveRecord::Migration[7.0] + def change + add_column :tournaments, :timer_end, :datetime + end +end diff --git a/spec/controllers/matches_controller_spec.rb b/spec/controllers/matches_controller_spec.rb index ec9c725..1beabe4 100644 --- a/spec/controllers/matches_controller_spec.rb +++ b/spec/controllers/matches_controller_spec.rb @@ -43,6 +43,7 @@ RSpec.describe MatchesController, type: :controller do expect(body.empty?).to be true end end + # TODO add test for upcoming once there is test data for a "valid" group stage end describe 'GET #show' do diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 1663157..918c39d 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -102,6 +102,9 @@ RSpec.describe TournamentsController, type: :controller do get :show, params: { id: @tournament.to_param } json = deserialize_response(response) expect(json[:id].to_i).to eq(@tournament.id) + expected_keys = %i[id name code public description playoff_teams_amount instant_finalists_amount intermediate_round_participants_amount timer_end owner_username stages teams] + expect(json.keys).to match_array(expected_keys) + expect(json).to eq(TournamentSerializer.new(@tournament).as_json) expect(json[:name]).to eq(@tournament.name) expect(json[:description]).to eq(@tournament.description) expect(json[:public]).to eq(@tournament.public) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 18608f5..15c095d 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -39,6 +39,7 @@ RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/spec/fixtures" # Run only focused tests + # TODO REVERT ME config.filter_run_when_matching :focus # If you're not using ActiveRecord, or you'd prefer not to run each of your