refactor: rename timer db column

This commit is contained in:
Daniel Schädler 2026-04-24 11:39:26 +02:00
parent 13acbad55f
commit e9ef73c230
11 changed files with 251 additions and 21 deletions

View File

@ -173,14 +173,14 @@ class TournamentsController < ApplicationController
def timer_params def timer_params
{ {
timer_end: params[:timer_end], timestamp: params[:timestamp],
timer_mode: params[:timer_mode] timer_mode: params[:timer_mode]
} }
end end
def serialize_timer_state(tournament) def serialize_timer_state(tournament)
{ {
timestamp: tournament.timer_end&.iso8601, timestamp: tournament.timestamp&.iso8601,
timer_mode: tournament.timer_mode timer_mode: tournament.timer_mode
} }
end end
@ -344,7 +344,7 @@ class TournamentsController < ApplicationController
status: :unprocessable_entity status: :unprocessable_entity
end end
params[:timer_end] = nil params[:timestamp] = nil
params[:timer_mode] = nil params[:timer_mode] = nil
return return
end end
@ -372,7 +372,7 @@ class TournamentsController < ApplicationController
return render json: { error: 'Timestamp must be in the future for countdown mode' }, status: :unprocessable_entity if timestamp_seconds <= 0 return render json: { error: 'Timestamp must be in the future for countdown mode' }, status: :unprocessable_entity if timestamp_seconds <= 0
parsed_time = Time.zone.now + timestamp_seconds parsed_time = Time.zone.now + timestamp_seconds
params[:timer_end] = parsed_time params[:timestamp] = parsed_time
params[:timer_mode] = 'countdown' params[:timer_mode] = 'countdown'
elsif timestamp.present? elsif timestamp.present?
begin begin
@ -383,13 +383,13 @@ class TournamentsController < ApplicationController
return render json: { error: 'Timestamp must be in the future for countdown mode' }, status: :unprocessable_entity return render json: { error: 'Timestamp must be in the future for countdown mode' }, status: :unprocessable_entity
end end
params[:timer_end] = parsed_time params[:timestamp] = parsed_time
params[:timer_mode] = timer_mode.presence || 'countdown' params[:timer_mode] = timer_mode.presence || 'countdown'
rescue ArgumentError rescue ArgumentError
return render json: { error: 'Invalid timestamp format' }, status: :unprocessable_entity return render json: { error: 'Invalid timestamp format' }, status: :unprocessable_entity
end end
elsif timer_mode == 'countup' elsif timer_mode == 'countup'
params[:timer_end] = Time.zone.now params[:timestamp] = Time.zone.now
elsif timer_mode.present? elsif timer_mode.present?
return render json: { error: 'timer_mode requires timestamp, timestamp_seconds, or clear_timer' }, return render json: { error: 'timer_mode requires timestamp, timestamp_seconds, or clear_timer' },
status: :unprocessable_entity status: :unprocessable_entity

View File

@ -96,7 +96,7 @@ class Tournament < ApplicationRecord
end end
def saved_change_to_timer_state? def saved_change_to_timer_state?
saved_change_to_timer_end? || saved_change_to_timer_mode? saved_change_to_timestamp? || saved_change_to_timer_mode?
end end
def broadcast_timer_state_change def broadcast_timer_state_change

View File

@ -12,7 +12,7 @@ class TournamentSerializer < SimpleTournamentSerializer
has_many :team_action_lists has_many :team_action_lists
attribute :timestamp do attribute :timestamp do
object.timer_end&.iso8601 object.timestamp&.iso8601
end end
attribute :timer_mode attribute :timer_mode

View File

@ -22,7 +22,7 @@ class TournamentSnapshotBuilder
def build def build
payload = tournament.attributes.symbolize_keys.slice(*ROOT_ATTRIBUTES) payload = tournament.attributes.symbolize_keys.slice(*ROOT_ATTRIBUTES)
payload[:timestamp] = tournament.timer_end&.iso8601 payload[:timestamp] = tournament.timestamp&.iso8601
payload[:timer_mode] = tournament.timer_mode payload[:timer_mode] = tournament.timer_mode
payload[:sync_generated_at] = Time.current.iso8601(6) payload[:sync_generated_at] = Time.current.iso8601(6)
payload[:teams] = teams_payload payload[:teams] = teams_payload

View File

@ -67,7 +67,7 @@ class TournamentSnapshotImporter
def update_tournament! def update_tournament!
root_attrs = snapshot.slice(*ROOT_ATTRIBUTES) root_attrs = snapshot.slice(*ROOT_ATTRIBUTES)
root_attrs[:code] = tournament.code if code_taken_by_another_tournament?(root_attrs[:code]) root_attrs[:code] = tournament.code if code_taken_by_another_tournament?(root_attrs[:code])
root_attrs[:timer_end] = parse_time(snapshot[:timestamp]) root_attrs[:timestamp] = parse_time(snapshot[:timestamp])
root_attrs[:timer_mode] = snapshot[:timer_mode] root_attrs[:timer_mode] = snapshot[:timer_mode]
root_attrs[:sync_source_tournament_id] = snapshot[:id] root_attrs[:sync_source_tournament_id] = snapshot[:id]
root_attrs[:sync_last_imported_snapshot_at] = snapshot_generated_at root_attrs[:sync_last_imported_snapshot_at] = snapshot_generated_at

View File

@ -12,7 +12,7 @@ class TournamentSyncSchema
playoff_teams_amount playoff_teams_amount
instant_finalists_amount instant_finalists_amount
intermediate_round_participants_amount intermediate_round_participants_amount
timer_end timestamp
timer_mode timer_mode
].freeze, ].freeze,
ignored: %w[ ignored: %w[

View File

@ -6,7 +6,7 @@ class TournamentTimerBroadcastPayload
{ {
type: 'timer.updated', type: 'timer.updated',
tournament_id: tournament.id, tournament_id: tournament.id,
timestamp: tournament.timer_end&.iso8601, timestamp: tournament.timestamp&.iso8601,
timer_mode: tournament.timer_mode timer_mode: tournament.timer_mode
} }
end end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class RenameTimerEndToTimestampOnTournaments < ActiveRecord::Migration[7.0]
def change
rename_column :tournaments, :timer_end, :timestamp
end
end

223
db/schema.rb Normal file
View File

@ -0,0 +1,223 @@
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2026_04_24_120000) do
create_table "beamers", force: :cascade do |t|
t.string "name", null: false
t.string "display_state", default: "matches_all", null: false
t.boolean "is_default", default: false
t.string "qr_code_url"
t.string "logo_url"
t.integer "tournament_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "sync_source_id"
t.index ["tournament_id", "name"], name: "index_beamers_on_tournament_id_and_name", unique: true
t.index ["tournament_id"], name: "index_beamers_on_tournament_id"
end
create_table "bets", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "match_id", null: false
t.integer "team_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["match_id"], name: "index_bets_on_match_id"
t.index ["team_id"], name: "index_bets_on_team_id"
t.index ["user_id"], name: "index_bets_on_user_id"
end
create_table "group_scores", force: :cascade do |t|
t.integer "group_points", default: 0
t.integer "scored_points", default: 0
t.integer "received_points", default: 0
t.integer "team_id", null: false
t.integer "group_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "position", default: 0
t.integer "sync_source_id"
t.index ["group_id"], name: "index_group_scores_on_group_id"
t.index ["team_id"], name: "index_group_scores_on_team_id"
end
create_table "groups", force: :cascade do |t|
t.integer "number"
t.integer "stage_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "sync_source_id"
t.index ["stage_id"], name: "index_groups_on_stage_id"
end
create_table "match_scores", force: :cascade do |t|
t.integer "points", default: 0
t.integer "match_id", null: false
t.integer "team_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "hidden_points", default: 0, null: false
t.integer "sync_source_id"
t.index ["match_id"], name: "index_match_scores_on_match_id"
t.index ["team_id"], name: "index_match_scores_on_team_id"
end
create_table "matches", force: :cascade do |t|
t.integer "state", default: 0
t.integer "position"
t.integer "stage_id"
t.integer "group_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.boolean "decider", default: false, null: false
t.integer "sync_source_id"
t.index ["group_id"], name: "index_matches_on_group_id"
t.index ["stage_id"], name: "index_matches_on_stage_id"
end
create_table "stages", force: :cascade do |t|
t.integer "level"
t.integer "state", default: 0
t.integer "tournament_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "sync_source_id"
t.index ["tournament_id"], name: "index_stages_on_tournament_id"
end
create_table "team_action_items", force: :cascade do |t|
t.integer "team_action_list_id", null: false
t.integer "team_id", null: false
t.boolean "completed", default: false, null: false
t.datetime "completed_at"
t.integer "sync_source_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["sync_source_id"], name: "index_team_action_items_on_sync_source_id"
t.index ["team_action_list_id", "team_id"], name: "index_team_action_items_on_team_action_list_id_and_team_id", unique: true
t.index ["team_action_list_id"], name: "index_team_action_items_on_team_action_list_id"
t.index ["team_id"], name: "index_team_action_items_on_team_id"
end
create_table "team_action_lists", force: :cascade do |t|
t.integer "tournament_id", null: false
t.string "name", null: false
t.string "key", null: false
t.string "action_name", null: false
t.string "source", null: false
t.integer "sync_source_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["sync_source_id"], name: "index_team_action_lists_on_sync_source_id"
t.index ["tournament_id", "key"], name: "index_team_action_lists_on_tournament_id_and_key", unique: true
t.index ["tournament_id"], name: "index_team_action_lists_on_tournament_id"
end
create_table "teams", force: :cascade do |t|
t.string "name"
t.integer "tournament_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "sync_source_id"
t.index ["tournament_id"], name: "index_teams_on_tournament_id"
end
create_table "tournament_sync_queue_entries", force: :cascade do |t|
t.integer "tournament_id", null: false
t.text "snapshot_json", null: false
t.datetime "next_attempt_at", null: false
t.datetime "last_attempt_at"
t.datetime "locked_at"
t.string "lock_token"
t.integer "attempts", default: 0, null: false
t.string "status", default: "pending", null: false
t.string "last_error"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["next_attempt_at"], name: "index_tournament_sync_queue_entries_on_next_attempt_at"
t.index ["status"], name: "index_tournament_sync_queue_entries_on_status"
t.index ["tournament_id"], name: "index_tournament_sync_queue_entries_on_tournament_id", unique: true
end
create_table "tournaments", force: :cascade do |t|
t.string "name", null: false
t.string "code", null: false
t.string "description"
t.boolean "public", default: true
t.integer "playoff_teams_amount", default: 0
t.integer "instant_finalists_amount", default: 0
t.integer "intermediate_round_participants_amount", default: 0
t.integer "user_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "timestamp"
t.boolean "read_only_mode", default: false, null: false
t.string "sync_target_url"
t.string "sync_auth_token"
t.integer "sync_source_tournament_id"
t.datetime "sync_last_pushed_at"
t.string "sync_last_push_error"
t.datetime "sync_last_imported_snapshot_at"
t.string "timer_mode"
t.index ["code"], name: "index_tournaments_on_code", unique: true
t.index ["user_id"], name: "index_tournaments_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "provider", default: "email", null: false
t.string "uid", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at", precision: nil
t.boolean "allow_password_change", default: false
t.datetime "remember_created_at", precision: nil
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at", precision: nil
t.datetime "last_sign_in_at", precision: nil
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at", precision: nil
t.datetime "confirmation_sent_at", precision: nil
t.string "unconfirmed_email"
t.string "username"
t.string "email"
t.text "tokens"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["provider"], name: "index_users_on_provider"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["uid"], name: "index_users_on_uid", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
add_foreign_key "beamers", "tournaments", on_delete: :cascade
add_foreign_key "bets", "matches", on_delete: :cascade
add_foreign_key "bets", "teams", on_delete: :cascade
add_foreign_key "bets", "users", on_delete: :cascade
add_foreign_key "group_scores", "groups", on_delete: :cascade
add_foreign_key "group_scores", "teams", on_delete: :cascade
add_foreign_key "groups", "stages", on_delete: :cascade
add_foreign_key "match_scores", "matches", on_delete: :cascade
add_foreign_key "match_scores", "teams", on_delete: :cascade
add_foreign_key "matches", "groups", on_delete: :cascade
add_foreign_key "matches", "stages", on_delete: :cascade
add_foreign_key "stages", "tournaments", on_delete: :cascade
add_foreign_key "team_action_items", "team_action_lists", on_delete: :cascade
add_foreign_key "team_action_items", "teams", on_delete: :cascade
add_foreign_key "team_action_lists", "tournaments", on_delete: :cascade
add_foreign_key "teams", "tournaments", on_delete: :cascade
add_foreign_key "tournament_sync_queue_entries", "tournaments", on_delete: :cascade
add_foreign_key "tournaments", "users", on_delete: :cascade
end

View File

@ -222,13 +222,13 @@ RSpec.describe TournamentsController, type: :controller do
describe 'GET #timer' do describe 'GET #timer' do
it 'returns timer state in ISO 8601 format' do it 'returns timer state in ISO 8601 format' do
timer_end = Time.utc(2026, 4, 13, 18, 0, 0) timestamp = Time.utc(2026, 4, 13, 18, 0, 0)
@tournament.update!(timer_end: timer_end, timer_mode: 'countdown') @tournament.update!(timestamp: timestamp, timer_mode: 'countdown')
get :timer, params: { id: @tournament.to_param } get :timer, params: { id: @tournament.to_param }
expect(response).to be_successful expect(response).to be_successful
expect(deserialize_response(response)[:timestamp]).to eq(timer_end.iso8601) expect(deserialize_response(response)[:timestamp]).to eq(timestamp.iso8601)
expect(deserialize_response(response)[:timer_mode]).to eq('countdown') expect(deserialize_response(response)[:timer_mode]).to eq('countdown')
end end
end end
@ -244,8 +244,8 @@ RSpec.describe TournamentsController, type: :controller do
patch :set_timer, params: { id: @tournament.to_param, timestamp: timestamp } patch :set_timer, params: { id: @tournament.to_param, timestamp: timestamp }
expect(response).to be_successful expect(response).to be_successful
expect(@tournament.reload.timer_end.iso8601).to eq(Time.iso8601(timestamp).utc.iso8601) expect(@tournament.reload.timestamp.iso8601).to eq(Time.iso8601(timestamp).utc.iso8601)
expect(deserialize_response(response)[:timestamp]).to eq(@tournament.timer_end.iso8601) expect(deserialize_response(response)[:timestamp]).to eq(@tournament.timestamp.iso8601)
expect(deserialize_response(response)[:timer_mode]).to eq('countdown') expect(deserialize_response(response)[:timer_mode]).to eq('countdown')
end end
@ -256,17 +256,17 @@ RSpec.describe TournamentsController, type: :controller do
expect(response).to be_successful expect(response).to be_successful
expect(@tournament.reload.timer_mode).to eq('countup') expect(@tournament.reload.timer_mode).to eq('countup')
expect(@tournament.timer_end).to be_between(before_request - 1.second, Time.zone.now + 1.second) expect(@tournament.timestamp).to be_between(before_request - 1.second, Time.zone.now + 1.second)
expect(deserialize_response(response)[:timer_mode]).to eq('countup') expect(deserialize_response(response)[:timer_mode]).to eq('countup')
end end
it 'clears timer state when clear_timer=true' 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') @tournament.update!(timestamp: Time.utc(2030, 4, 13, 18, 0, 0), timer_mode: 'countdown')
patch :set_timer, 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(response).to be_successful
expect(@tournament.reload.timer_end).to be_nil expect(@tournament.reload.timestamp).to be_nil
expect(@tournament.timer_mode).to be_nil expect(@tournament.timer_mode).to be_nil
expect(deserialize_response(response)[:timestamp]).to be_nil expect(deserialize_response(response)[:timestamp]).to be_nil
expect(deserialize_response(response)[:timer_mode]).to be_nil expect(deserialize_response(response)[:timer_mode]).to be_nil

View File

@ -183,7 +183,7 @@ RSpec.describe TournamentSnapshotImporter do
playoff_teams_amount: tournament.playoff_teams_amount, playoff_teams_amount: tournament.playoff_teams_amount,
instant_finalists_amount: tournament.instant_finalists_amount, instant_finalists_amount: tournament.instant_finalists_amount,
intermediate_round_participants_amount: tournament.intermediate_round_participants_amount, intermediate_round_participants_amount: tournament.intermediate_round_participants_amount,
timestamp: tournament.timer_end&.iso8601, timestamp: tournament.timestamp&.iso8601,
timer_mode: tournament.timer_mode, timer_mode: tournament.timer_mode,
teams: tournament.teams.order(:name).map { |team| team.name }, teams: tournament.teams.order(:name).map { |team| team.name },
team_action_lists: tournament.team_action_lists.order(:key).map do |list| team_action_lists: tournament.team_action_lists.order(:key).map do |list|