fix(tournaments): keep owner data out metadata

Tournament metadata remains optional and tournament-scoped.
This commit is contained in:
Daniel Schädler 2026-05-04 14:55:48 +02:00
parent 9b10cc12d2
commit 794f835148
12 changed files with 4 additions and 57 deletions

View File

@ -285,7 +285,7 @@ class TournamentsController < ApplicationController
params.slice( params.slice(
:name, :description, :public, :teams, :group_stage, :playoff_teams_amount, :name, :description, :public, :teams, :group_stage, :playoff_teams_amount,
:instant_finalists_amount, :intermediate_round_participants_amount, :instant_finalists_amount, :intermediate_round_participants_amount,
:location, :starts_at, :ends_at, :organizer_name, :contact_email, :website_url, :location, :starts_at, :ends_at, :website_url,
:read_only_mode, :sync_target_url, :sync_auth_token :read_only_mode, :sync_target_url, :sync_auth_token
).permit! ).permit!
end end

View File

@ -6,7 +6,7 @@ require 'openssl'
class Tournament < ApplicationRecord class Tournament < ApplicationRecord
TIMER_MODES = %w[countdown countup].freeze TIMER_MODES = %w[countdown countup].freeze
TIMER_REASONS = %w[tournament_start playoff_start match_end custom].freeze TIMER_REASONS = %w[tournament_start playoff_start match_end custom].freeze
METADATA_STRING_ATTRIBUTES = %i[location organizer_name contact_email website_url].freeze METADATA_STRING_ATTRIBUTES = %i[location website_url].freeze
belongs_to :user belongs_to :user
belongs_to :owner, class_name: 'User', foreign_key: :user_id belongs_to :owner, class_name: 'User', foreign_key: :user_id
@ -22,7 +22,6 @@ class Tournament < ApplicationRecord
validates :timer_reason, inclusion: { in: TIMER_REASONS }, allow_nil: true validates :timer_reason, inclusion: { in: TIMER_REASONS }, allow_nil: true
validates :sync_target_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true validates :sync_target_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true
validates :website_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true validates :website_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true
validates :contact_email, format: URI::MailTo::EMAIL_REGEXP, allow_blank: true
validate :sync_configuration_is_complete validate :sync_configuration_is_complete
validate :playoff_teams_amount_is_positive_power_of_two validate :playoff_teams_amount_is_positive_power_of_two

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class SimpleTournamentSerializer < ApplicationSerializer class SimpleTournamentSerializer < ApplicationSerializer
attributes :name, :code, :public, :location, :organizer_name, :contact_email, :website_url attributes :name, :code, :public, :location, :website_url
attribute :starts_at do attribute :starts_at do
object.starts_at&.iso8601 object.starts_at&.iso8601

View File

@ -7,8 +7,6 @@ class TournamentSnapshotImporter
description description
public public
location location
organizer_name
contact_email
website_url website_url
playoff_teams_amount playoff_teams_amount
instant_finalists_amount instant_finalists_amount

View File

@ -12,8 +12,6 @@ class TournamentSyncSchema
location location
starts_at starts_at
ends_at ends_at
organizer_name
contact_email
website_url website_url
playoff_teams_amount playoff_teams_amount
instant_finalists_amount instant_finalists_amount

View File

@ -6,8 +6,6 @@ class AddMetadataToTournaments < ActiveRecord::Migration[7.0]
t.string :location t.string :location
t.datetime :starts_at t.datetime :starts_at
t.datetime :ends_at t.datetime :ends_at
t.string :organizer_name
t.string :contact_email
t.string :website_url t.string :website_url
end end
end end

View File

@ -163,7 +163,6 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_04_100000) do
create_table "tournaments", force: :cascade do |t| create_table "tournaments", force: :cascade do |t|
t.string "code", null: false t.string "code", null: false
t.string "contact_email"
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.string "description" t.string "description"
t.datetime "ends_at" t.datetime "ends_at"
@ -171,7 +170,6 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_04_100000) do
t.integer "intermediate_round_participants_amount", default: 0 t.integer "intermediate_round_participants_amount", default: 0
t.string "location" t.string "location"
t.string "name", null: false t.string "name", null: false
t.string "organizer_name"
t.integer "playoff_teams_amount", default: 0 t.integer "playoff_teams_amount", default: 0
t.boolean "public", default: true t.boolean "public", default: true
t.boolean "read_only_mode", default: false, null: false t.boolean "read_only_mode", default: false, null: false

View File

@ -105,7 +105,7 @@ RSpec.describe TournamentsController, type: :controller do
expected_keys = %i[ expected_keys = %i[
id name code public description playoff_teams_amount id name code public description playoff_teams_amount
instant_finalists_amount intermediate_round_participants_amount instant_finalists_amount intermediate_round_participants_amount
location starts_at ends_at organizer_name contact_email website_url location starts_at ends_at website_url
timestamp timer_mode timer_reason timer_reason_text owner_username stages teams team_action_lists timestamp timer_mode timer_reason timer_reason_text owner_username stages teams team_action_lists
] ]
expect(json.keys).to match_array(expected_keys) expect(json.keys).to match_array(expected_keys)
@ -416,8 +416,6 @@ RSpec.describe TournamentsController, type: :controller do
location: 'Stadtpark Halle', location: 'Stadtpark Halle',
starts_at: starts_at, starts_at: starts_at,
ends_at: ends_at, ends_at: ends_at,
organizer_name: 'BPWSTR Crew',
contact_email: 'orga@example.com',
website_url: 'https://example.com/tournaments/summer-cup' website_url: 'https://example.com/tournaments/summer-cup'
) )
@ -426,8 +424,6 @@ RSpec.describe TournamentsController, type: :controller do
expect(tournament.location).to eq('Stadtpark Halle') expect(tournament.location).to eq('Stadtpark Halle')
expect(tournament.starts_at.iso8601).to eq(Time.iso8601(starts_at).utc.iso8601) expect(tournament.starts_at.iso8601).to eq(Time.iso8601(starts_at).utc.iso8601)
expect(tournament.ends_at.iso8601).to eq(Time.iso8601(ends_at).utc.iso8601) expect(tournament.ends_at.iso8601).to eq(Time.iso8601(ends_at).utc.iso8601)
expect(tournament.organizer_name).to eq('BPWSTR Crew')
expect(tournament.contact_email).to eq('orga@example.com')
expect(tournament.website_url).to eq('https://example.com/tournaments/summer-cup') expect(tournament.website_url).to eq('https://example.com/tournaments/summer-cup')
end end
@ -620,8 +616,6 @@ RSpec.describe TournamentsController, type: :controller do
location: 'Arena Nord', location: 'Arena Nord',
starts_at: starts_at, starts_at: starts_at,
ends_at: ends_at, ends_at: ends_at,
organizer_name: 'Turniere Ops',
contact_email: 'ops@example.com',
website_url: 'https://example.com/events/arena-nord' website_url: 'https://example.com/events/arena-nord'
} }
@ -631,8 +625,6 @@ RSpec.describe TournamentsController, type: :controller do
expect(@tournament.location).to eq('Arena Nord') expect(@tournament.location).to eq('Arena Nord')
expect(@tournament.starts_at.iso8601).to eq(Time.iso8601(starts_at).utc.iso8601) expect(@tournament.starts_at.iso8601).to eq(Time.iso8601(starts_at).utc.iso8601)
expect(@tournament.ends_at.iso8601).to eq(Time.iso8601(ends_at).utc.iso8601) expect(@tournament.ends_at.iso8601).to eq(Time.iso8601(ends_at).utc.iso8601)
expect(@tournament.organizer_name).to eq('Turniere Ops')
expect(@tournament.contact_email).to eq('ops@example.com')
expect(@tournament.website_url).to eq('https://example.com/events/arena-nord') expect(@tournament.website_url).to eq('https://example.com/events/arena-nord')
expect(json[:starts_at]).to eq(@tournament.starts_at.iso8601) expect(json[:starts_at]).to eq(@tournament.starts_at.iso8601)
expect(json[:ends_at]).to eq(@tournament.ends_at.iso8601) expect(json[:ends_at]).to eq(@tournament.ends_at.iso8601)
@ -643,14 +635,12 @@ RSpec.describe TournamentsController, type: :controller do
id: @tournament.to_param, id: @tournament.to_param,
starts_at: '2030-06-01T18:00:00+02:00', starts_at: '2030-06-01T18:00:00+02:00',
ends_at: '2030-06-01T10:00:00+02:00', ends_at: '2030-06-01T10:00:00+02:00',
contact_email: 'not-an-email',
website_url: 'notaurl' website_url: 'notaurl'
} }
expect(response).to have_http_status(:unprocessable_content) expect(response).to have_http_status(:unprocessable_content)
json = deserialize_response(response) json = deserialize_response(response)
expect(json[:ends_at]).to include('must be after starts_at') expect(json[:ends_at]).to include('must be after starts_at')
expect(json[:contact_email]).to include('is invalid')
expect(json[:website_url]).to include('is invalid') expect(json[:website_url]).to include('is invalid')
end end
@ -880,8 +870,6 @@ RSpec.describe TournamentsController, type: :controller do
location: 'Synced Arena', location: 'Synced Arena',
starts_at: '2030-06-01T10:00:00+02:00', starts_at: '2030-06-01T10:00:00+02:00',
ends_at: '2030-06-01T18:00:00+02:00', ends_at: '2030-06-01T18:00:00+02:00',
organizer_name: 'Synced Crew',
contact_email: 'sync@example.com',
website_url: 'https://example.com/synced', website_url: 'https://example.com/synced',
playoff_teams_amount: 4, playoff_teams_amount: 4,
instant_finalists_amount: 4, instant_finalists_amount: 4,
@ -903,8 +891,6 @@ RSpec.describe TournamentsController, type: :controller do
expect(@tournament.location).to eq('Synced Arena') expect(@tournament.location).to eq('Synced Arena')
expect(@tournament.starts_at.iso8601).to eq(Time.iso8601('2030-06-01T10:00:00+02:00').utc.iso8601) expect(@tournament.starts_at.iso8601).to eq(Time.iso8601('2030-06-01T10:00:00+02:00').utc.iso8601)
expect(@tournament.ends_at.iso8601).to eq(Time.iso8601('2030-06-01T18:00:00+02:00').utc.iso8601) expect(@tournament.ends_at.iso8601).to eq(Time.iso8601('2030-06-01T18:00:00+02:00').utc.iso8601)
expect(@tournament.organizer_name).to eq('Synced Crew')
expect(@tournament.contact_email).to eq('sync@example.com')
expect(@tournament.website_url).to eq('https://example.com/synced') expect(@tournament.website_url).to eq('https://example.com/synced')
expect(@tournament.teams.pluck(:name)).to eq(['Alpha']) expect(@tournament.teams.pluck(:name)).to eq(['Alpha'])
expect(deserialize_response(response)).not_to have_key(:sync_auth_token) expect(deserialize_response(response)).not_to have_key(:sync_auth_token)

View File

@ -85,8 +85,6 @@ RSpec.describe 'Backend API surface HTTP E2E' do
expect(full_show.dig(:json, :location)).to start_with('Venue') expect(full_show.dig(:json, :location)).to start_with('Venue')
expect(full_show.dig(:json, :starts_at)).to eq('2030-06-01T08:00:00Z') expect(full_show.dig(:json, :starts_at)).to eq('2030-06-01T08:00:00Z')
expect(full_show.dig(:json, :ends_at)).to eq('2030-06-01T16:00:00Z') expect(full_show.dig(:json, :ends_at)).to eq('2030-06-01T16:00:00Z')
expect(full_show.dig(:json, :organizer_name)).to eq('Turniere E2E')
expect(full_show.dig(:json, :contact_email)).to eq('events@example.com')
expect(full_show.dig(:json, :website_url)).to eq('https://example.com/turniere-e2e') expect(full_show.dig(:json, :website_url)).to eq('https://example.com/turniere-e2e')
expect(invalid_index[:status]).to eq(400) expect(invalid_index[:status]).to eq(400)
@ -128,8 +126,6 @@ RSpec.describe 'Backend API surface HTTP E2E' do
location: 'Updated E2E Arena', location: 'Updated E2E Arena',
starts_at: '2030-07-01T10:00:00+02:00', starts_at: '2030-07-01T10:00:00+02:00',
ends_at: '2030-07-01T18:00:00+02:00', ends_at: '2030-07-01T18:00:00+02:00',
organizer_name: 'Updated E2E Org',
contact_email: 'updated-events@example.com',
website_url: 'https://example.com/updated-turniere-e2e', website_url: 'https://example.com/updated-turniere-e2e',
playoff_teams_amount: 8, playoff_teams_amount: 8,
instant_finalists_amount: 8, instant_finalists_amount: 8,
@ -187,8 +183,6 @@ RSpec.describe 'Backend API surface HTTP E2E' do
expect(update_response.dig(:json, :location)).to eq('Updated E2E Arena') expect(update_response.dig(:json, :location)).to eq('Updated E2E Arena')
expect(update_response.dig(:json, :starts_at)).to eq('2030-07-01T08:00:00Z') expect(update_response.dig(:json, :starts_at)).to eq('2030-07-01T08:00:00Z')
expect(update_response.dig(:json, :ends_at)).to eq('2030-07-01T16:00:00Z') expect(update_response.dig(:json, :ends_at)).to eq('2030-07-01T16:00:00Z')
expect(update_response.dig(:json, :organizer_name)).to eq('Updated E2E Org')
expect(update_response.dig(:json, :contact_email)).to eq('updated-events@example.com')
expect(update_response.dig(:json, :website_url)).to eq('https://example.com/updated-turniere-e2e') expect(update_response.dig(:json, :website_url)).to eq('https://example.com/updated-turniere-e2e')
expect(auto_recalc_response[:status]).to eq(200) expect(auto_recalc_response[:status]).to eq(200)
@ -532,8 +526,6 @@ RSpec.describe 'Backend API surface HTTP E2E' do
location: "Venue #{name_prefix}", location: "Venue #{name_prefix}",
starts_at: '2030-06-01T10:00:00+02:00', starts_at: '2030-06-01T10:00:00+02:00',
ends_at: '2030-06-01T18:00:00+02:00', ends_at: '2030-06-01T18:00:00+02:00',
organizer_name: 'Turniere E2E',
contact_email: 'events@example.com',
website_url: 'https://example.com/turniere-e2e', website_url: 'https://example.com/turniere-e2e',
group_stage: true, group_stage: true,
playoff_teams_amount: 8, playoff_teams_amount: 8,
@ -594,8 +586,6 @@ RSpec.describe 'Backend API surface HTTP E2E' do
location: "Venue #{name_prefix}", location: "Venue #{name_prefix}",
starts_at: '2030-06-01T10:00:00+02:00', starts_at: '2030-06-01T10:00:00+02:00',
ends_at: '2030-06-01T18:00:00+02:00', ends_at: '2030-06-01T18:00:00+02:00',
organizer_name: 'Turniere E2E',
contact_email: 'events@example.com',
website_url: 'https://example.com/turniere-e2e', website_url: 'https://example.com/turniere-e2e',
teams: 4.times.map do |index| teams: 4.times.map do |index|
{ name: "#{name_prefix.tr(' ', '')}-S#{index + 1}" } { name: "#{name_prefix.tr(' ', '')}-S#{index + 1}" }

View File

@ -114,8 +114,6 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
location: 'Source Updated Arena', location: 'Source Updated Arena',
starts_at: '2030-07-01T10:00:00+02:00', starts_at: '2030-07-01T10:00:00+02:00',
ends_at: '2030-07-01T18:00:00+02:00', ends_at: '2030-07-01T18:00:00+02:00',
organizer_name: 'Updated Sync Crew',
contact_email: 'updated-sync@example.com',
website_url: 'https://example.com/updated-sync-source', website_url: 'https://example.com/updated-sync-source',
playoff_teams_amount: 4, playoff_teams_amount: 4,
instant_finalists_amount: 4, instant_finalists_amount: 4,
@ -242,8 +240,6 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
location: "Source Venue #{name_prefix}", location: "Source Venue #{name_prefix}",
starts_at: '2030-06-01T10:00:00+02:00', starts_at: '2030-06-01T10:00:00+02:00',
ends_at: '2030-06-01T18:00:00+02:00', ends_at: '2030-06-01T18:00:00+02:00',
organizer_name: 'Sync Source Crew',
contact_email: 'sync-source@example.com',
website_url: 'https://example.com/sync-source', website_url: 'https://example.com/sync-source',
group_stage: true, group_stage: true,
playoff_teams_amount: 8, playoff_teams_amount: 8,
@ -270,8 +266,6 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
location: "Noise Venue #{name_prefix}", location: "Noise Venue #{name_prefix}",
starts_at: '2030-06-01T10:00:00+02:00', starts_at: '2030-06-01T10:00:00+02:00',
ends_at: '2030-06-01T18:00:00+02:00', ends_at: '2030-06-01T18:00:00+02:00',
organizer_name: 'Noise Crew',
contact_email: 'noise@example.com',
website_url: 'https://example.com/noise', website_url: 'https://example.com/noise',
teams: 4.times.map do |index| teams: 4.times.map do |index|
{ name: "#{name_prefix.tr(' ', '')}-S#{index + 1}" } { name: "#{name_prefix.tr(' ', '')}-S#{index + 1}" }
@ -291,8 +285,6 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
location: "Follower Venue #{name_prefix}", location: "Follower Venue #{name_prefix}",
starts_at: '2030-05-01T10:00:00+02:00', starts_at: '2030-05-01T10:00:00+02:00',
ends_at: '2030-05-01T18:00:00+02:00', ends_at: '2030-05-01T18:00:00+02:00',
organizer_name: 'Follower Crew',
contact_email: 'follower@example.com',
website_url: 'https://example.com/follower', website_url: 'https://example.com/follower',
read_only_mode: true, read_only_mode: true,
sync_auth_token: sync_token sync_auth_token: sync_token
@ -527,8 +519,6 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
location: tournament[:location], location: tournament[:location],
starts_at: tournament[:starts_at], starts_at: tournament[:starts_at],
ends_at: tournament[:ends_at], ends_at: tournament[:ends_at],
organizer_name: tournament[:organizer_name],
contact_email: tournament[:contact_email],
website_url: tournament[:website_url], website_url: tournament[:website_url],
playoff_teams_amount: tournament.fetch(:playoff_teams_amount), playoff_teams_amount: tournament.fetch(:playoff_teams_amount),
instant_finalists_amount: tournament.fetch(:instant_finalists_amount), instant_finalists_amount: tournament.fetch(:instant_finalists_amount),

View File

@ -37,8 +37,6 @@ RSpec.describe 'Tournament sync test HTTP E2E' do
location: 'Sync Test Arena', location: 'Sync Test Arena',
starts_at: '2030-08-01T10:00:00+02:00', starts_at: '2030-08-01T10:00:00+02:00',
ends_at: '2030-08-01T18:00:00+02:00', ends_at: '2030-08-01T18:00:00+02:00',
organizer_name: 'Sync Test Org',
contact_email: 'sync-test@example.com',
website_url: 'https://example.com/sync-test' website_url: 'https://example.com/sync-test'
}) })
expect(rename[:status]).to eq(200) expect(rename[:status]).to eq(200)
@ -61,8 +59,6 @@ RSpec.describe 'Tournament sync test HTTP E2E' do
expect(after_follower.fetch(:location)).to eq(after_leader.fetch(:location)) expect(after_follower.fetch(:location)).to eq(after_leader.fetch(:location))
expect(after_follower.fetch(:starts_at)).to eq(after_leader.fetch(:starts_at)) expect(after_follower.fetch(:starts_at)).to eq(after_leader.fetch(:starts_at))
expect(after_follower.fetch(:ends_at)).to eq(after_leader.fetch(:ends_at)) expect(after_follower.fetch(:ends_at)).to eq(after_leader.fetch(:ends_at))
expect(after_follower.fetch(:organizer_name)).to eq(after_leader.fetch(:organizer_name))
expect(after_follower.fetch(:contact_email)).to eq(after_leader.fetch(:contact_email))
expect(after_follower.fetch(:website_url)).to eq(after_leader.fetch(:website_url)) expect(after_follower.fetch(:website_url)).to eq(after_leader.fetch(:website_url))
expect(after_follower.fetch(:teams).map { |team| team.fetch(:name) }) expect(after_follower.fetch(:teams).map { |team| team.fetch(:name) })
.to match_array(after_leader.fetch(:teams).map { |team| team.fetch(:name) }) .to match_array(after_leader.fetch(:teams).map { |team| team.fetch(:name) })

View File

@ -10,8 +10,6 @@ RSpec.describe TournamentSnapshotImporter do
location: 'Court 4', location: 'Court 4',
starts_at: Time.utc(2026, 4, 24, 9, 0, 0), starts_at: Time.utc(2026, 4, 24, 9, 0, 0),
ends_at: Time.utc(2026, 4, 24, 18, 30, 0), ends_at: Time.utc(2026, 4, 24, 18, 30, 0),
organizer_name: 'Sync Ops',
contact_email: 'sync-ops@example.com',
website_url: 'https://example.com/sync-cup', website_url: 'https://example.com/sync-cup',
timestamp: Time.utc(2026, 4, 24, 10, 15, 0), timestamp: Time.utc(2026, 4, 24, 10, 15, 0),
timer_mode: 'countdown', timer_mode: 'countdown',
@ -37,8 +35,6 @@ RSpec.describe TournamentSnapshotImporter do
expect(follower.location).to eq(source.location) expect(follower.location).to eq(source.location)
expect(follower.starts_at).to eq(source.starts_at) expect(follower.starts_at).to eq(source.starts_at)
expect(follower.ends_at).to eq(source.ends_at) expect(follower.ends_at).to eq(source.ends_at)
expect(follower.organizer_name).to eq(source.organizer_name)
expect(follower.contact_email).to eq(source.contact_email)
expect(follower.website_url).to eq(source.website_url) expect(follower.website_url).to eq(source.website_url)
expect(follower.timer_mode).to eq(source.timer_mode) expect(follower.timer_mode).to eq(source.timer_mode)
expect(follower.timer_reason).to eq(source.timer_reason) expect(follower.timer_reason).to eq(source.timer_reason)
@ -212,8 +208,6 @@ RSpec.describe TournamentSnapshotImporter do
location: tournament.location, location: tournament.location,
starts_at: tournament.starts_at&.iso8601, starts_at: tournament.starts_at&.iso8601,
ends_at: tournament.ends_at&.iso8601, ends_at: tournament.ends_at&.iso8601,
organizer_name: tournament.organizer_name,
contact_email: tournament.contact_email,
website_url: tournament.website_url, website_url: tournament.website_url,
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,