diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index f12b169..d2e4233 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -12,15 +12,15 @@ class TournamentsController < ApplicationController end before_action :set_tournament_for_show, only: %i[show] - before_action :set_tournament, only: %i[update destroy set_timer_end timer_end sync_state test_sync] - before_action :authenticate_user!, only: %i[create update destroy set_timer_end test_sync] - before_action -> { require_owner! @tournament.owner }, only: %i[update destroy set_timer_end test_sync] + before_action :set_tournament, only: %i[update destroy set_timer timer sync_state test_sync] + before_action :authenticate_user!, only: %i[create update destroy set_timer test_sync] + before_action -> { require_owner! @tournament.owner }, only: %i[update destroy set_timer test_sync] 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] + before_action :validate_set_timer_params, only: %i[set_timer] before_action :ensure_update_allowed!, only: %i[update] before_action :ensure_destroy_allowed!, only: %i[destroy] - before_action :ensure_timer_update_allowed!, only: %i[set_timer_end] + before_action :ensure_timer_update_allowed!, only: %i[set_timer] before_action :authorize_sync_request!, only: %i[sync_state] rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_error @@ -129,14 +129,14 @@ class TournamentsController < ApplicationController @tournament.destroy end - # GET /tournaments/:id/timer_end - def timer_end + # GET /tournaments/:id/timer + def timer render json: serialize_timer_state(@tournament) end - # PATCH /tournaments/:id/set_timer_end - def set_timer_end - if @tournament.update(timer_end_params) + # PATCH /tournaments/:id/timer + def set_timer + if @tournament.update(timer_params) push_sync_if_needed!(@tournament) render json: @tournament else @@ -171,9 +171,9 @@ class TournamentsController < ApplicationController private - def timer_end_params + def timer_params { - timer_end: params[:timer_end] || params[:timestamp], + timer_end: params[:timer_end], timer_mode: params[:timer_mode] } end @@ -332,15 +332,12 @@ class TournamentsController < ApplicationController TournamentSyncEnqueue.call(tournament) end - def validate_set_timer_end_params + def validate_set_timer_params timestamp = params[:timestamp] timestamp_seconds = params[:timestamp_seconds] timer_mode = params[:timer_mode] clear_timer = ActiveModel::Type::Boolean.new.cast(params[:clear_timer]) - timestamp = params[:timer_end] if timestamp.blank? && params[:timer_end].present? - timestamp_seconds = params[:timer_end_seconds] if timestamp_seconds.blank? && params[:timer_end_seconds].present? - if clear_timer if timestamp.present? || timestamp_seconds.present? || timer_mode.present? return render json: { error: 'clear_timer cannot be combined with timestamp values or timer_mode' }, diff --git a/app/services/tournament_snapshot_importer.rb b/app/services/tournament_snapshot_importer.rb index 4e1af83..3108d5e 100644 --- a/app/services/tournament_snapshot_importer.rb +++ b/app/services/tournament_snapshot_importer.rb @@ -67,7 +67,7 @@ class TournamentSnapshotImporter def update_tournament! root_attrs = snapshot.slice(*ROOT_ATTRIBUTES) root_attrs[:code] = tournament.code if code_taken_by_another_tournament?(root_attrs[:code]) - root_attrs[:timer_end] = parse_time(snapshot[:timestamp] || snapshot[:timer_end]) + root_attrs[:timer_end] = parse_time(snapshot[:timestamp]) root_attrs[:timer_mode] = snapshot[:timer_mode] root_attrs[:sync_source_tournament_id] = snapshot[:id] root_attrs[:sync_last_imported_snapshot_at] = snapshot_generated_at diff --git a/config/routes.rb b/config/routes.rb index 9c53ca4..7ef1030 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,8 +31,8 @@ Rails.application.routes.draw do end end member do - get :timer_end - patch :set_timer_end + get :timer + patch :timer, action: :set_timer patch :sync_state post :test_sync end diff --git a/doc/timer_api.md b/doc/timer_api.md index 3f170fd..dcb1169 100644 --- a/doc/timer_api.md +++ b/doc/timer_api.md @@ -24,7 +24,7 @@ Timer state is available in normal tournament payload: Dedicated timer endpoint also exists: -- `GET /tournaments/:id/timer_end` +- `GET /tournaments/:id/timer` Response shape: @@ -95,10 +95,7 @@ Follower tournaments in `read_only_mode` reject writes with `423`. Endpoint: -- `PATCH /tournaments/:id/set_timer_end` - -Current route name still says `set_timer_end`. -Payload field name is now `timestamp`. +- `PATCH /tournaments/:id/timer` ### Start countdown by duration diff --git a/e2e/lib/scenario_runner.rb b/e2e/lib/scenario_runner.rb index 9ad0682..eb67787 100644 --- a/e2e/lib/scenario_runner.rb +++ b/e2e/lib/scenario_runner.rb @@ -29,7 +29,7 @@ module TurniereE2E } timer_response = patch!( - "/tournaments/#{tournament.fetch(:id)}/set_timer_end", + "/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 600 } ) checkpoints[:timer_set] = { timestamp: timer_response.fetch(:timestamp) } diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 9ebbca2..dd263ca 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -220,12 +220,12 @@ RSpec.describe TournamentsController, type: :controller do end end - describe 'GET #timer_end' do + describe 'GET #timer' do it 'returns timer state in ISO 8601 format' do timer_end = Time.utc(2026, 4, 13, 18, 0, 0) @tournament.update!(timer_end: timer_end, timer_mode: 'countdown') - get :timer_end, params: { id: @tournament.to_param } + get :timer, params: { id: @tournament.to_param } expect(response).to be_successful expect(deserialize_response(response)[:timestamp]).to eq(timer_end.iso8601) @@ -233,7 +233,7 @@ RSpec.describe TournamentsController, type: :controller do end end - describe 'PATCH #set_timer_end' do + describe 'PATCH #set_timer' do before(:each) do apply_authentication_headers_for @user end @@ -241,7 +241,7 @@ RSpec.describe TournamentsController, type: :controller do it 'stores timezone-aware timestamp values without shifting them' do timestamp = '2030-04-13T18:00:00+02:00' - patch :set_timer_end, params: { id: @tournament.to_param, timestamp: timestamp } + patch :set_timer, params: { id: @tournament.to_param, timestamp: timestamp } expect(response).to be_successful expect(@tournament.reload.timer_end.iso8601).to eq(Time.iso8601(timestamp).utc.iso8601) @@ -252,7 +252,7 @@ RSpec.describe TournamentsController, type: :controller do it 'starts countup timer at current time when timer_mode=countup' do before_request = Time.zone.now - patch :set_timer_end, params: { id: @tournament.to_param, timer_mode: 'countup' } + patch :set_timer, params: { id: @tournament.to_param, timer_mode: 'countup' } expect(response).to be_successful expect(@tournament.reload.timer_mode).to eq('countup') @@ -263,7 +263,7 @@ RSpec.describe TournamentsController, type: :controller do it 'clears timer state when clear_timer=true' do @tournament.update!(timer_end: Time.utc(2030, 4, 13, 18, 0, 0), timer_mode: 'countdown') - patch :set_timer_end, params: { id: @tournament.to_param, clear_timer: true } + patch :set_timer, params: { id: @tournament.to_param, clear_timer: true } expect(response).to be_successful expect(@tournament.reload.timer_end).to be_nil @@ -273,7 +273,7 @@ RSpec.describe TournamentsController, type: :controller do end it 'rejects timestamp_seconds for countup mode' do - patch :set_timer_end, params: { id: @tournament.to_param, timer_mode: 'countup', timestamp_seconds: 120 } + patch :set_timer, params: { id: @tournament.to_param, timer_mode: 'countup', timestamp_seconds: 120 } expect(response).to have_http_status(:unprocessable_entity) expect(deserialize_response(response)[:error]).to eq('timestamp_seconds is only allowed for countdown mode') @@ -742,7 +742,7 @@ RSpec.describe TournamentsController, type: :controller do playoff_teams_amount: 4, instant_finalists_amount: 4, intermediate_round_participants_amount: 0, - timer_end: nil, + timestamp: nil, teams: [{ id: 7, name: 'Alpha' }], stages: [], groups: [], diff --git a/spec/e2e/http/api_surface_spec.rb b/spec/e2e/http/api_surface_spec.rb index 7db4ad6..aebfcaf 100644 --- a/spec/e2e/http/api_surface_spec.rb +++ b/spec/e2e/http/api_surface_spec.rb @@ -125,21 +125,21 @@ RSpec.describe 'Backend API surface HTTP E2E' do forbidden_update = other_client.patch("/tournaments/#{tournament.fetch(:id)}", body: { name: 'Not Allowed' }) unauthenticated_update = anonymous_client.patch("/tournaments/#{tournament.fetch(:id)}", body: { name: 'No Auth' }) - timer_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timestamp_seconds: 120 }) - timer_show = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/timer_end") - timer_countup = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timer_mode: 'countup' }) - timer_clear = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { clear_timer: true }) - timer_double = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { + timer_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 120 }) + timer_show = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/timer") + timer_countup = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timer_mode: 'countup' }) + timer_clear = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { clear_timer: true }) + timer_double = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 120, timestamp: (Time.now.utc + 240).iso8601 }) - timer_invalid_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timestamp_seconds: 'nope' }) - timer_past = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timestamp_seconds: 0 }) - timer_countup_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { + timer_invalid_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 'nope' }) + timer_past = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 0 }) + timer_countup_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timer_mode: 'countup', timestamp_seconds: 60 }) - timer_forbidden = other_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timestamp_seconds: 60 }) + timer_forbidden = other_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 60 }) destroy_response = owner_client.delete("/tournaments/#{tournament.fetch(:id)}") after_destroy = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}") diff --git a/spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb b/spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb index c8e43db..3b8c364 100644 --- a/spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb +++ b/spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb @@ -41,19 +41,19 @@ RSpec.describe 'Tournament timer follower sync websocket HTTP E2E' do expect(initial_payload.fetch('timestamp')).to be_nil expect(initial_payload.fetch('timer_mode')).to be_nil - countdown = source_client.patch("/tournaments/#{source.fetch(:id)}/set_timer_end", body: { timestamp_seconds: 120 }) + countdown = source_client.patch("/tournaments/#{source.fetch(:id)}/timer", body: { timestamp_seconds: 120 }) expect(countdown[:status]).to eq(200) countdown_payload = wait_for_timer_state!(cable_client, timer_mode: 'countdown') expect(countdown_payload.fetch('timestamp')).to eq(countdown.dig(:json, :timestamp)) - countup = source_client.patch("/tournaments/#{source.fetch(:id)}/set_timer_end", body: { timer_mode: 'countup' }) + countup = source_client.patch("/tournaments/#{source.fetch(:id)}/timer", body: { timer_mode: 'countup' }) expect(countup[:status]).to eq(200) countup_payload = wait_for_timer_state!(cable_client, timer_mode: 'countup') expect(countup_payload.fetch('timestamp')).to eq(countup.dig(:json, :timestamp)) - clear = source_client.patch("/tournaments/#{source.fetch(:id)}/set_timer_end", body: { clear_timer: true }) + clear = source_client.patch("/tournaments/#{source.fetch(:id)}/timer", body: { clear_timer: true }) expect(clear[:status]).to eq(200) cleared_payload = wait_for_timer_state!(cable_client, timer_mode: nil) diff --git a/spec/e2e/http/tournament_timer_websocket_spec.rb b/spec/e2e/http/tournament_timer_websocket_spec.rb index f9d423e..1431476 100644 --- a/spec/e2e/http/tournament_timer_websocket_spec.rb +++ b/spec/e2e/http/tournament_timer_websocket_spec.rb @@ -32,19 +32,19 @@ RSpec.describe 'Tournament timer websocket HTTP E2E' do expect(initial_payload.fetch('timestamp')).to be_nil expect(initial_payload.fetch('timer_mode')).to be_nil - countdown = client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timestamp_seconds: 120 }) + countdown = client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 120 }) expect(countdown[:status]).to eq(200) countdown_payload = wait_for_timer_state!(cable_client, timer_mode: 'countdown') expect(countdown_payload.fetch('timestamp')).to eq(countdown.dig(:json, :timestamp)) - countup = client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timer_mode: 'countup' }) + countup = client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timer_mode: 'countup' }) expect(countup[:status]).to eq(200) countup_payload = wait_for_timer_state!(cable_client, timer_mode: 'countup') expect(countup_payload.fetch('timestamp')).to eq(countup.dig(:json, :timestamp)) - clear = client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { clear_timer: true }) + clear = client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { clear_timer: true }) expect(clear[:status]).to eq(200) cleared_payload = wait_for_timer_state!(cable_client, timer_mode: nil)