Merge branch 'codex/tur-130-dependency-update-a31c' into 'master'

TUR-130: Dependency update

See merge request turniere/turniere-backend!68
This commit is contained in:
Daniel Schädler 2026-04-29 14:15:08 +00:00
commit 08cefb1ace
41 changed files with 1205 additions and 401 deletions

View File

@ -55,6 +55,8 @@ rails e2e spec:
alias: postgres alias: postgres
- name: $CI_REGISTRY_IMAGE/production/commits:$CI_COMMIT_SHA - name: $CI_REGISTRY_IMAGE/production/commits:$CI_COMMIT_SHA
alias: app alias: app
variables:
TURNIERE_DISABLE_EMAIL_DELIVERY: "1"
command: command:
- bundle - bundle
- exec - exec
@ -125,6 +127,7 @@ rails follow sync e2e spec:
SECRET_KEY_BASE: turniere-blackbox-secret-key-base-please-change-in-real-prod SECRET_KEY_BASE: turniere-blackbox-secret-key-base-please-change-in-real-prod
MAILGUN_API_KEY: blackbox-test-api-key MAILGUN_API_KEY: blackbox-test-api-key
MAILGUN_DOMAIN: blackbox.example.com MAILGUN_DOMAIN: blackbox.example.com
TURNIERE_DISABLE_EMAIL_DELIVERY: "1"
TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: "3" TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: "3"
NO_PROXY: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1 NO_PROXY: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1
no_proxy: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1 no_proxy: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1
@ -148,6 +151,7 @@ rails follow sync e2e spec:
SECRET_KEY_BASE: turniere-blackbox-secret-key-base-please-change-in-real-prod SECRET_KEY_BASE: turniere-blackbox-secret-key-base-please-change-in-real-prod
MAILGUN_API_KEY: blackbox-test-api-key MAILGUN_API_KEY: blackbox-test-api-key
MAILGUN_DOMAIN: blackbox.example.com MAILGUN_DOMAIN: blackbox.example.com
TURNIERE_DISABLE_EMAIL_DELIVERY: "1"
TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: "3" TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: "3"
NO_PROXY: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1 NO_PROXY: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1
no_proxy: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1 no_proxy: source-app,follower-app,source-postgres,follower-postgres,localhost,127.0.0.1

View File

@ -1,9 +1,17 @@
require: inherit_from: .rubocop_todo.yml
inherit_mode:
merge:
- Exclude
plugins:
- rubocop-performance - rubocop-performance
- rubocop-rails - rubocop-rails
# Stolen from https://gist.github.com/jhass/a5ae80d87f18e53e7b56 # Stolen from https://gist.github.com/jhass/a5ae80d87f18e53e7b56
AllCops: AllCops:
NewCops: disable
SuggestExtensions: false
Exclude: Exclude:
- "bin/**/*" - "bin/**/*"
- "db/schema.rb" - "db/schema.rb"
@ -13,7 +21,7 @@ Rails:
Enabled: true Enabled: true
# Commonly used screens these days easily fit more than 80 characters. # Commonly used screens these days easily fit more than 80 characters.
Metrics/LineLength: Layout/LineLength:
Max: 120 Max: 120
# Too short methods lead to extraction of single-use methods, which can make # Too short methods lead to extraction of single-use methods, which can make
@ -36,7 +44,7 @@ Metrics/AbcSize:
Max: 50 Max: 50
Metrics/BlockLength: Metrics/BlockLength:
ExcludedMethods: AllowedMethods:
- "namespace" - "namespace"
- "create_table" - "create_table"
Exclude: Exclude:

563
.rubocop_todo.yml Normal file
View File

@ -0,0 +1,563 @@
# This configuration was generated by
# `rubocop --auto-gen-config --no-exclude-limit`
# on 2026-04-28 09:58:15 UTC using RuboCop version 1.86.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation.
Bundler/OrderedGems:
Exclude:
- 'Gemfile'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyleAlignWith.
# SupportedStylesAlignWith: either, start_of_block, start_of_line
Layout/BlockAlignment:
Exclude:
- 'spec/routing/tournaments_routing_spec.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowForAlignment.
Layout/CommentIndentation:
Exclude:
- 'app/serializers/tournament_serializer.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
Layout/EmptyLineAfterGuardClause:
Exclude:
- 'app/controllers/tournaments_controller.rb'
- 'config/spring.rb'
- 'spec/factories/tournaments.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
Layout/EmptyLineBetweenDefs:
Exclude:
- 'app/controllers/tournaments_controller.rb'
- 'app/services/tournament_snapshot_importer.rb'
- 'e2e/lib/scenario_runner.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Layout/EmptyLines:
Exclude:
- 'app/controllers/tournaments_controller.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: around, only_before
Layout/EmptyLinesAroundAccessModifier:
Exclude:
- 'db/migrate/20220516203839_create_active_storage_variant_records.active_storage.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only
Layout/EmptyLinesAroundClassBody:
Exclude:
- 'config/application.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Layout/FirstArrayElementIndentation:
EnforcedStyle: consistent
# Offense count: 6
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Layout/FirstHashElementIndentation:
Exclude:
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
# Offense count: 5
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: normal, indented_internal_methods
Layout/IndentationConsistency:
Exclude:
- 'db/migrate/20220516203839_create_active_storage_variant_records.active_storage.rb'
- 'e2e/lib/scenario_runner.rb'
- 'spec/routing/tournaments_routing_spec.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
# SupportedStylesAlignWith: start_of_line, relative_to_receiver
Layout/IndentationWidth:
Exclude:
- 'db/migrate/20220516203839_create_active_storage_variant_records.active_storage.rb'
- 'spec/routing/tournaments_routing_spec.rb'
# Offense count: 10
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented, indented_relative_to_receiver
Layout/MultilineMethodCallIndentation:
Exclude:
- 'app/interactors/advance_teams_in_intermediate_stage.rb'
- 'e2e/lib/scenario_runner.rb'
- 'spec/e2e/http/api_surface_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_live_websocket_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/e2e/http/tournament_lifecycle_spec.rb'
- 'spec/e2e/http/tournament_live_websocket_spec.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented
Layout/MultilineOperationIndentation:
Exclude:
- 'app/services/group_stage_service.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
Layout/SpaceInsidePercentLiteralDelimiters:
Exclude:
- 'db/migrate/20220516203839_create_active_storage_variant_records.active_storage.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: final_newline, final_blank_line
Layout/TrailingEmptyLines:
Exclude:
- 'spec/support/profile.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowSafeAssignment.
Lint/AssignmentInCondition:
Exclude:
- 'db/migrate/20220516203838_add_service_name_to_active_storage_blobs.active_storage.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
Lint/ScriptPermission:
Exclude:
- 'script/e2e_scenarios.rb'
- 'script/wait_for_http.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
# NotImplementedExceptions: NotImplementedError
Lint/UnusedMethodArgument:
Exclude:
- 'app/controllers/application_controller.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
Lint/UselessAssignment:
Exclude:
- 'e2e/lib/scenario_runner.rb'
- 'spec/models/beamer_spec.rb'
# Offense count: 6
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
Metrics/AbcSize:
Exclude:
- 'app/controllers/tournaments_controller.rb'
- 'app/services/tournament_snapshot_importer.rb'
- 'e2e/lib/scenario_runner.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/services/tournament_snapshot_importer_spec.rb'
# Offense count: 2
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
# AllowedMethods: refine
Metrics/BlockLength:
Exclude:
- 'app/services/tournament_snapshot_importer.rb'
- 'db/schema.rb'
# Offense count: 15
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/CyclomaticComplexity:
Exclude:
- 'app/controllers/matches_controller.rb'
- 'app/controllers/tournaments_controller.rb'
- 'app/models/group_score.rb'
- 'app/models/stage.rb'
- 'app/services/group_stage_service.rb'
- 'app/services/playoff_stage_service.rb'
- 'e2e/lib/api_client.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/services/tournament_snapshot_importer_spec.rb'
# Offense count: 1
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Exclude:
- 'app/controllers/tournaments_controller.rb'
# Offense count: 1
# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
Metrics/ParameterLists:
Exclude:
- 'e2e/lib/api_client.rb'
# Offense count: 14
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/PerceivedComplexity:
Exclude:
- 'app/controllers/matches_controller.rb'
- 'app/controllers/tournaments_controller.rb'
- 'app/models/group_score.rb'
- 'app/models/stage.rb'
- 'app/services/group_stage_service.rb'
- 'e2e/lib/api_client.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/services/tournament_snapshot_importer_spec.rb'
# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
Performance/Count:
Exclude:
- 'app/controllers/tournaments_controller.rb'
- 'spec/services/tournament_snapshot_importer_spec.rb'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeMultiline.
Performance/DeleteSuffix:
Exclude:
- 'e2e/lib/action_cable_client.rb'
- 'e2e/lib/api_client.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Performance/RegexpMatch:
Exclude:
- 'config/spring.rb'
# Offense count: 14
# This cop supports unsafe autocorrection (--autocorrect-all).
Performance/TimesMap:
Exclude:
- 'e2e/lib/scenario_runner.rb'
- 'spec/e2e/http/api_surface_spec.rb'
- 'spec/e2e/http/tournament_beamers_websocket_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_beamers_websocket_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_websocket_spec.rb'
- 'spec/e2e/http/tournament_sync_test_spec.rb'
- 'spec/e2e/http/tournament_team_action_lists_websocket_spec.rb'
- 'spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb'
- 'spec/e2e/http/tournament_timer_websocket_spec.rb'
- 'spec/services/tournament_snapshot_importer_spec.rb'
# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: NilOrEmpty, NotPresent, UnlessPresent.
Rails/Blank:
Exclude:
- 'e2e/lib/scenario_runner.rb'
- 'spec/e2e/http/tournament_sync_test_spec.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforceForPrefixed.
Rails/Delegate:
Exclude:
- 'e2e/lib/action_cable_client.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: slashes, arguments
Rails/FilePath:
Exclude:
- 'config/environments/development.rb'
- 'spec/rails_helper.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Rails/IndexBy:
Exclude:
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
# Offense count: 1
# Configuration parameters: IgnoreScopes.
Rails/InverseOf:
Exclude:
- 'app/models/tournament.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: NotNilAndNotEmpty, NotBlank, UnlessBlank.
Rails/Present:
Exclude:
- 'e2e/lib/api_client.rb'
# Offense count: 1
Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20240407180900_add_default_to_position_in_group_scores.rb'
# Offense count: 9
# Configuration parameters: ForbiddenMethods, AllowedMethods.
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
Rails/SkipsModelValidations:
Exclude:
- 'app/models/tournament_sync_queue_entry.rb'
- 'app/services/tournament_sync_pusher.rb'
- 'db/migrate/20220516203838_add_service_name_to_active_storage_blobs.active_storage.rb'
- 'db/migrate/20260416102000_backfill_default_beamers_by_name.rb'
- 'spec/controllers/tournaments_controller_spec.rb'
# Offense count: 33
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: strict, flexible
Rails/TimeZone:
Exclude:
- 'e2e/lib/action_cable_client.rb'
- 'e2e/lib/scenario_runner.rb'
- 'spec/e2e/http/tournament_beamers_websocket_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_beamers_websocket_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_websocket_spec.rb'
- 'spec/e2e/http/tournament_team_action_lists_websocket_spec.rb'
- 'spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb'
- 'spec/e2e/http/tournament_timer_websocket_spec.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
# FunctionalMethods: let, let!, subject, watch
# AllowedMethods: lambda, proc, it
Style/BlockDelimiters:
Exclude:
- 'spec/controllers/beamers_controller_spec.rb'
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Keywords, RequireColon.
# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE
Style/CommentAnnotation:
Exclude:
- 'spec/controllers/matches_controller_spec.rb'
- 'spec/services/group_stage_service_spec.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'spec/factories/tournaments.rb'
# Offense count: 12
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Style/FrozenStringLiteralComment:
Exclude:
- 'config/boot.rb'
- 'config/initializers/filter_parameter_logging.rb'
- 'config/initializers/new_framework_defaults_7_0.rb'
- 'db/migrate/20220516203838_add_service_name_to_active_storage_blobs.active_storage.rb'
- 'db/migrate/20220516203839_create_active_storage_variant_records.active_storage.rb'
- 'db/migrate/20220516203840_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb'
- 'db/migrate/20240407175005_add_position_to_group_scores.rb'
- 'db/migrate/20240407180900_add_default_to_position_in_group_scores.rb'
- 'db/migrate/20250309202658_add_timer_end_to_tournaments.rb'
- 'db/migrate/20260423133000_add_timer_mode_to_tournaments.rb'
- 'db/schema.rb'
- 'spec/support/profile.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/GlobalStdStream:
Exclude:
- 'config/environments/production.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.
Style/GuardClause:
Exclude:
- 'db/migrate/20220516203838_add_service_name_to_active_storage_blobs.active_storage.rb'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/IdenticalConditionalBranches:
Exclude:
- 'spec/e2e/http/tournament_lifecycle_spec.rb'
# Offense count: 18
# This cop supports safe autocorrection (--autocorrect).
Style/IfUnlessModifier:
Exclude:
- 'app/controllers/tournaments_controller.rb'
- 'app/services/group_stage_service.rb'
- 'app/services/playoff_stage_service.rb'
- 'e2e/lib/scenario_runner.rb'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: InverseMethods, InverseBlocks.
Style/InverseMethods:
Exclude:
- 'spec/e2e/http/tournament_lifecycle_spec.rb'
# Offense count: 3
Style/MultilineBlockChain:
Exclude:
- 'e2e/lib/api_client.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MinDigits, Strict, AllowedNumbers, AllowedPatterns.
Style/NumericLiterals:
Exclude:
- 'db/schema.rb'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
- 'e2e/lib/scenario_runner.rb'
# Offense count: 1
# Configuration parameters: AllowedMethods.
# AllowedMethods: respond_to_missing?
Style/OptionalBooleanParameter:
Exclude:
- 'app/services/playoff_stage_service.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantFreeze:
Exclude:
- 'e2e/lib/scenario_runner.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowMultipleReturnValues.
Style/RedundantReturn:
Exclude:
- 'app/controllers/tournaments_controller.rb'
# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/RedundantSort:
Exclude:
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try!
Style/SafeNavigation:
Exclude:
- 'config/spring.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
Exclude:
- 'config/spring.rb'
# Offense count: 246
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiterals:
Exclude:
- 'config/boot.rb'
- 'db/migrate/20220516203839_create_active_storage_variant_records.active_storage.rb'
- 'db/schema.rb'
- 'spec/e2e/http/api_surface_spec.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: .
# SupportedStyles: percent, brackets
Style/SymbolArray:
EnforcedStyle: percent
MinSize: 10
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments.
# AllowedMethods: define_method, mail, respond_to
Style/SymbolProc:
Exclude:
- 'spec/services/tournament_snapshot_importer_spec.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
Style/TrailingCommaInArrayLiteral:
Exclude:
- 'spec/services/playoff_stage_service_spec.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: WordRegex.
# SupportedStyles: percent, brackets
Style/WordArray:
EnforcedStyle: percent
MinSize: 3
# Offense count: 6
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/ZeroLengthPredicate:
Exclude:
- 'app/models/stage.rb'
- 'app/services/group_stage_service.rb'
# Offense count: 113
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
# URISchemes: http, https
Layout/LineLength:
Exclude:
- 'app/controllers/matches_controller.rb'
- 'app/controllers/tournaments_controller.rb'
- 'app/models/team_action_list.rb'
- 'app/services/group_stage_service.rb'
- 'app/services/playoff_stage_service.rb'
- 'db/schema.rb'
- 'e2e/lib/api_client.rb'
- 'e2e/lib/scenario_runner.rb'
- 'script/e2e_scenarios.rb'
- 'spec/controllers/groups_controller_spec.rb'
- 'spec/controllers/matches_controller_spec.rb'
- 'spec/controllers/stages_controller_spec.rb'
- 'spec/controllers/team_action_lists_controller_spec.rb'
- 'spec/e2e/http/api_surface_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_spec.rb'
- 'spec/e2e/http/tournament_follow_sync_websocket_spec.rb'
- 'spec/e2e/http/tournament_lifecycle_spec.rb'
- 'spec/e2e/http/tournament_rendering_spec.rb'
- 'spec/e2e/http/tournament_sync_test_spec.rb'
- 'spec/e2e/http/tournament_team_action_lists_websocket_spec.rb'
- 'spec/e2e/http/tournament_timer_follow_sync_websocket_spec.rb'
- 'spec/factories/tournaments.rb'
- 'spec/services/group_stage_service_spec.rb'
- 'spec/services/team_action_list_service_spec.rb'
- 'spec/services/tournament_snapshot_importer_spec.rb'
- 'spec/services/tournament_sync_processor_spec.rb'

View File

@ -1 +1 @@
3.1.2 4.0.3

View File

@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
ARG RUBY_VERSION=3.1.2 ARG RUBY_VERSION=4.0.3
ARG BUNDLER_VERSION=2.3.13 ARG BUNDLER_VERSION=4.0.6
ARG base_commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ARG base_commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
FROM ruby:${RUBY_VERSION}-slim AS runtime-base FROM ruby:${RUBY_VERSION}-slim AS runtime-base
@ -19,6 +19,7 @@ WORKDIR /app
RUN apt-get update -qq \ RUN apt-get update -qq \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
libpq5 \ libpq5 \
libyaml-0-2 \
nodejs \ nodejs \
openssl \ openssl \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
@ -33,6 +34,7 @@ RUN apt-get update -qq \
git \ git \
libpq-dev \ libpq-dev \
libsqlite3-dev \ libsqlite3-dev \
libyaml-dev \
pkg-config \ pkg-config \
python3 \ python3 \
python3-pip \ python3-pip \
@ -92,7 +94,7 @@ RUN apt-get update -qq \
python3 \ python3 \
python3-pip \ python3-pip \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& python3 -m pip install --no-cache-dir invoke && python3 -m pip install --break-system-packages --no-cache-dir invoke
COPY --from=bundle-test /usr/local/bundle /usr/local/bundle COPY --from=bundle-test /usr/local/bundle /usr/local/bundle
COPY --from=bundle-test /app/vendor/bundle /app/vendor/bundle COPY --from=bundle-test /app/vendor/bundle /app/vendor/bundle

12
Gemfile
View File

@ -3,12 +3,12 @@
source 'https://rubygems.org' source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '3.1.2' ruby '4.0.3'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 7.0.3' gem 'rails', '~> 8.1'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 5.0' gem 'puma', '~> 8.0'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5' # gem 'jbuilder', '~> 2.5'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
@ -28,7 +28,7 @@ gem 'bootsnap', require: false
# Authentication # Authentication
gem 'devise' gem 'devise'
gem 'devise_token_auth', git: 'https://github.com/Thor77/devise_token_auth', branch: 'fix-api-only-mode' gem 'devise_token_auth'
gem 'rack-cors' gem 'rack-cors'
@ -59,7 +59,7 @@ group :development, optional: true do
gem 'rubocop-rails' gem 'rubocop-rails'
gem 'solargraph' gem 'solargraph'
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.1'
end end
group :production do group :production do
@ -67,4 +67,4 @@ group :production do
end end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] gem 'tzinfo-data', platforms: %i[windows jruby]

View File

@ -1,326 +1,407 @@
GIT
remote: https://github.com/Thor77/devise_token_auth
revision: 30ba21add5c87b2964b909c6fc5cd18cfeae2191
branch: fix-api-only-mode
specs:
devise_token_auth (1.2.0)
bcrypt (~> 3.0)
devise (> 3.5.2, < 5)
rails (>= 4.2.0, < 7.1)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.8.1) action_text-trix (2.1.18)
actionpack (= 7.0.8.1) railties
activesupport (= 7.0.8.1) actioncable (8.1.3)
actionpack (= 8.1.3)
activesupport (= 8.1.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.8.1) zeitwerk (~> 2.6)
actionpack (= 7.0.8.1) actionmailbox (8.1.3)
activejob (= 7.0.8.1) actionpack (= 8.1.3)
activerecord (= 7.0.8.1) activejob (= 8.1.3)
activestorage (= 7.0.8.1) activerecord (= 8.1.3)
activesupport (= 7.0.8.1) activestorage (= 8.1.3)
mail (>= 2.7.1) activesupport (= 8.1.3)
net-imap mail (>= 2.8.0)
net-pop actionmailer (8.1.3)
net-smtp actionpack (= 8.1.3)
actionmailer (7.0.8.1) actionview (= 8.1.3)
actionpack (= 7.0.8.1) activejob (= 8.1.3)
actionview (= 7.0.8.1) activesupport (= 8.1.3)
activejob (= 7.0.8.1) mail (>= 2.8.0)
activesupport (= 7.0.8.1) rails-dom-testing (~> 2.2)
mail (~> 2.5, >= 2.5.4) actionpack (8.1.3)
net-imap actionview (= 8.1.3)
net-pop activesupport (= 8.1.3)
net-smtp nokogiri (>= 1.8.5)
rails-dom-testing (~> 2.0) rack (>= 2.2.4)
actionpack (7.0.8.1) rack-session (>= 1.0.1)
actionview (= 7.0.8.1)
activesupport (= 7.0.8.1)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.6)
actiontext (7.0.8.1) useragent (~> 0.16)
actionpack (= 7.0.8.1) actiontext (8.1.3)
activerecord (= 7.0.8.1) action_text-trix (~> 2.1.15)
activestorage (= 7.0.8.1) actionpack (= 8.1.3)
activesupport (= 7.0.8.1) activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.8.1) actionview (8.1.3)
activesupport (= 7.0.8.1) activesupport (= 8.1.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.11)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.6)
active_model_serializers (0.10.14) active_model_serializers (0.10.16)
actionpack (>= 4.1) actionpack (>= 4.1)
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.8.1) activejob (8.1.3)
activesupport (= 7.0.8.1) activesupport (= 8.1.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.8.1) activemodel (8.1.3)
activesupport (= 7.0.8.1) activesupport (= 8.1.3)
activerecord (7.0.8.1) activerecord (8.1.3)
activemodel (= 7.0.8.1) activemodel (= 8.1.3)
activesupport (= 7.0.8.1) activesupport (= 8.1.3)
activestorage (7.0.8.1) timeout (>= 0.4.0)
actionpack (= 7.0.8.1) activestorage (8.1.3)
activejob (= 7.0.8.1) actionpack (= 8.1.3)
activerecord (= 7.0.8.1) activejob (= 8.1.3)
activesupport (= 7.0.8.1) activerecord (= 8.1.3)
activesupport (= 8.1.3)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) activesupport (8.1.3)
activesupport (7.0.8.1) base64
concurrent-ruby (~> 1.0, >= 1.0.2) bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
json
logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) securerandom (>= 0.3)
ast (2.4.2) tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
ast (2.4.3)
backport (1.2.0) backport (1.2.0)
bcrypt (3.1.20) base64 (0.3.0)
benchmark (0.3.0) bcrypt (3.1.22)
bootsnap (1.18.3) benchmark (0.5.0)
bigdecimal (4.1.2)
bootsnap (1.24.0)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.2.4) builder (3.3.0)
case_transform (0.2) case_transform (0.2)
activesupport activesupport
concurrent-ruby (1.2.3) concurrent-ruby (1.3.6)
connection_pool (3.0.2)
crass (1.0.6) crass (1.0.6)
date (3.3.4) date (3.5.1)
devise (4.9.3) devise (4.9.4)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
diff-lcs (1.5.1) devise_token_auth (1.2.6)
domain_name (0.6.20240107) bcrypt (~> 3.0)
e2mmap (0.1.0) devise (> 3.5.2, < 5)
erubi (1.12.0) rails (>= 4.2.0, < 8.2)
factory_bot (6.4.6) diff-lcs (1.6.2)
activesupport (>= 5.0.0) drb (2.2.3)
factory_bot_rails (6.4.3) erb (6.0.4)
factory_bot (~> 6.4) erubi (1.13.1)
railties (>= 5.0.0) factory_bot (6.5.6)
faker (3.3.1) activesupport (>= 6.1.0)
factory_bot_rails (6.5.1)
factory_bot (~> 6.5)
railties (>= 6.1.0)
faker (3.8.0)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
ffi (1.16.3) faraday (2.14.1)
globalid (1.2.1) faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-multipart (1.2.0)
multipart-post (~> 2.0)
faraday-net_http (3.4.2)
net-http (~> 0.5)
ffi (1.17.4-aarch64-linux-gnu)
ffi (1.17.4-aarch64-linux-musl)
ffi (1.17.4-arm64-darwin)
ffi (1.17.4-x86_64-linux-gnu)
globalid (1.3.0)
activesupport (>= 6.1) activesupport (>= 6.1)
http-accept (1.7.0) i18n (1.14.8)
http-cookie (1.0.5)
domain_name (~> 0.5)
i18n (1.14.4)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
interactor (3.1.2) interactor (3.2.0)
interactor-rails (2.2.1) ostruct
interactor-rails (2.3.0)
interactor (~> 3.0) interactor (~> 3.0)
rails (>= 4.2) railties (>= 7.0)
jaro_winkler (1.5.6) io-console (0.8.2)
json (2.7.2) irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jaro_winkler (1.7.0)
json (2.19.4)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
kramdown (2.4.0) kramdown (2.5.2)
rexml rexml (>= 3.4.4)
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
language_server-protocol (3.17.0.3) language_server-protocol (3.17.0.5)
listen (3.9.0) lint_roller (1.1.0)
listen (3.10.0)
logger
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.22.0) logger (1.7.0)
loofah (2.25.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.9.0)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
mailgun-ruby (1.2.14) mailgun-ruby (1.4.3)
rest-client (>= 2.0.2) faraday (~> 2.1)
marcel (1.0.4) faraday-multipart (< 2)
method_source (1.0.0) mini_mime
mime-types (3.5.2) zeitwerk
mime-types-data (~> 3.2015) marcel (1.1.0)
mime-types-data (3.2024.0305)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.22.3) minitest (6.0.5)
msgpack (1.7.2) drb (~> 2.0)
net-imap (0.4.10) prism (~> 1.5)
msgpack (1.8.0)
multipart-post (2.4.1)
net-http (0.9.1)
uri (>= 0.11.1)
net-imap (0.6.4)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.1)
net-protocol net-protocol
netrc (0.11.0) nio4r (2.7.5)
nio4r (2.7.1) nokogiri (1.19.3-aarch64-linux-gnu)
nokogiri (1.16.3-aarch64-linux)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.3-arm64-darwin) nokogiri (1.19.3-aarch64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.3-x86_64-linux) nokogiri (1.19.3-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.3-x86_64-linux-gnu)
racc (~> 1.4)
observer (0.1.2)
open3 (0.2.1)
orm_adapter (0.5.0) orm_adapter (0.5.0)
parallel (1.24.0) ostruct (0.6.3)
parser (3.3.0.5) parallel (2.1.0)
parser (3.3.11.1)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.6) pg (1.6.3-aarch64-linux)
puma (5.6.8) pg (1.6.3-aarch64-linux-musl)
pg (1.6.3-arm64-darwin)
pg (1.6.3-x86_64-linux)
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
prism (1.9.0)
psych (5.3.1)
date
stringio
puma (8.0.1)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.7.3) racc (1.8.1)
rack (2.2.9) rack (3.2.6)
rack-cors (2.0.2) rack-cors (3.0.0)
rack (>= 2.0.0) logger
rack-test (2.1.0) rack (>= 3.0.14)
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3) rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
railroady (1.6.0) railroady (1.6.0)
rails (7.0.8.1) rails (8.1.3)
actioncable (= 7.0.8.1) actioncable (= 8.1.3)
actionmailbox (= 7.0.8.1) actionmailbox (= 8.1.3)
actionmailer (= 7.0.8.1) actionmailer (= 8.1.3)
actionpack (= 7.0.8.1) actionpack (= 8.1.3)
actiontext (= 7.0.8.1) actiontext (= 8.1.3)
actionview (= 7.0.8.1) actionview (= 8.1.3)
activejob (= 7.0.8.1) activejob (= 8.1.3)
activemodel (= 7.0.8.1) activemodel (= 8.1.3)
activerecord (= 7.0.8.1) activerecord (= 8.1.3)
activestorage (= 7.0.8.1) activestorage (= 8.1.3)
activesupport (= 7.0.8.1) activesupport (= 8.1.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.8.1) railties (= 8.1.3)
rails-dom-testing (2.2.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0) rails-html-sanitizer (1.7.0)
loofah (~> 2.21) loofah (~> 2.25)
nokogiri (~> 1.14) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (7.0.8.1) railties (8.1.3)
actionpack (= 7.0.8.1) actionpack (= 8.1.3)
activesupport (= 7.0.8.1) activesupport (= 8.1.3)
method_source irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.5) tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rake (13.4.2)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.11.1)
ffi (~> 1.0) ffi (~> 1.0)
rbs (2.8.4) rbs (3.10.4)
regexp_parser (2.9.0) logger
responders (3.1.1) tsort
actionpack (>= 5.2) rdoc (7.2.0)
railties (>= 5.2) erb
rest-client (2.1.0) psych (>= 4.0.0)
http-accept (>= 1.7.0, < 2.0) tsort
http-cookie (>= 1.0.2, < 2.0) regexp_parser (2.12.0)
mime-types (>= 1.16, < 4.0) reline (0.6.3)
netrc (~> 0.8) io-console (~> 0.5)
reverse_markdown (2.1.1) responders (3.2.0)
actionpack (>= 7.0)
railties (>= 7.0)
reverse_markdown (3.0.2)
nokogiri nokogiri
rexml (3.2.6) rexml (3.4.4)
rspec-core (3.13.0) rspec-core (3.13.6)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.0) rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.0) rspec-mocks (3.13.8)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (6.1.2) rspec-rails (8.0.4)
actionpack (>= 6.1) actionpack (>= 7.2)
activesupport (>= 6.1) activesupport (>= 7.2)
railties (>= 6.1) railties (>= 7.2)
rspec-core (~> 3.13) rspec-core (>= 3.13.0, < 5.0.0)
rspec-expectations (~> 3.13) rspec-expectations (>= 3.13.0, < 5.0.0)
rspec-mocks (~> 3.13) rspec-mocks (>= 3.13.0, < 5.0.0)
rspec-support (~> 3.13) rspec-support (>= 3.13.0, < 5.0.0)
rspec-support (3.13.1) rspec-support (3.13.7)
rubocop (1.62.1) rubocop (1.86.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (~> 3.17.0.2)
parallel (~> 1.10) lint_roller (~> 1.1.0)
parallel (>= 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 2.9.3, < 3.0)
rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.49.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.31.2) rubocop-ast (1.49.1)
parser (>= 3.3.0.4) parser (>= 3.3.7.2)
rubocop-performance (1.21.0) prism (~> 1.7)
rubocop (>= 1.48.1, < 2.0) rubocop-performance (1.26.1)
rubocop-ast (>= 1.31.1, < 2.0) lint_roller (~> 1.1)
rubocop-rails (2.24.1) rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
rubocop-rails (2.34.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.44.0, < 2.0)
ruby-prof (1.7.1) ruby-prof (2.0.4)
base64
ostruct
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
shoulda-matchers (6.2.0) securerandom (0.4.1)
activesupport (>= 5.2.0) shoulda-matchers (7.0.1)
solargraph (0.50.0) activesupport (>= 7.1)
solargraph (0.58.3)
ast (~> 2.4.3)
backport (~> 1.2) backport (~> 1.2)
benchmark benchmark (~> 0.4)
bundler (~> 2.0) bundler (>= 2.0)
diff-lcs (~> 1.4) diff-lcs (~> 1.4)
e2mmap jaro_winkler (~> 1.6, >= 1.6.1)
jaro_winkler (~> 1.5)
kramdown (~> 2.3) kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.1) kramdown-parser-gfm (~> 1.1)
logger (~> 1.6)
observer (~> 0.1)
open3 (~> 0.2.1)
ostruct (~> 0.6)
parser (~> 3.0) parser (~> 3.0)
rbs (~> 2.0) prism (~> 1.4)
reverse_markdown (~> 2.0) rbs (>= 3.6.1, <= 4.0.0.dev.4)
rubocop (~> 1.38) reverse_markdown (~> 3.0)
rubocop (~> 1.76)
thor (~> 1.0) thor (~> 1.0)
tilt (~> 2.0) tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24) yard (~> 0.9, >= 0.9.24)
spring (2.1.1) yard-activesupport-concern (~> 0.0)
spring-watcher-listen (2.0.1) yard-solargraph (~> 0.1)
spring (4.4.2)
spring-watcher-listen (2.1.0)
listen (>= 2.7, < 4.0) listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0) spring (>= 4)
sqlite3 (1.7.3-aarch64-linux) sqlite3 (2.9.3-aarch64-linux-gnu)
sqlite3 (1.7.3-arm64-darwin) sqlite3 (2.9.3-aarch64-linux-musl)
sqlite3 (1.7.3-x86_64-linux) sqlite3 (2.9.3-arm64-darwin)
thor (1.3.1) sqlite3 (2.9.3-x86_64-linux-gnu)
tilt (2.3.0) stringio (3.2.0)
timeout (0.4.1) thor (1.5.0)
tilt (2.7.0)
timeout (0.6.1)
tsort (0.2.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0) unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
uri (1.1.1)
useragent (0.16.11)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
websocket-driver (0.7.6) websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
yard (0.9.36) yard (0.9.43)
zeitwerk (2.6.13) yard-activesupport-concern (0.0.1)
yard (>= 0.8)
yard-solargraph (0.1.0)
yard (~> 0.9)
zeitwerk (2.7.5)
PLATFORMS PLATFORMS
aarch64-linux
aarch64-linux-musl aarch64-linux-musl
arm64-darwin-22 arm64-darwin-22
arm64-darwin-23 arm64-darwin-23
arm64-darwin-24 arm64-darwin-24
arm64-darwin-25 arm64-darwin-25
x86_64-linux x86_64-linux
x86_64-linux-gnu
DEPENDENCIES DEPENDENCIES
active_model_serializers active_model_serializers
bootsnap bootsnap
devise devise
devise_token_auth! devise_token_auth
factory_bot_rails factory_bot_rails
faker faker
interactor interactor
@ -328,10 +409,10 @@ DEPENDENCIES
listen (~> 3.3) listen (~> 3.3)
mailgun-ruby mailgun-ruby
pg pg
puma (~> 5.0) puma (~> 8.0)
rack-cors rack-cors
railroady railroady
rails (~> 7.0.3) rails (~> 8.1)
rspec-rails rspec-rails
rubocop rubocop
rubocop-performance rubocop-performance
@ -340,12 +421,12 @@ DEPENDENCIES
shoulda-matchers shoulda-matchers
solargraph solargraph
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.1)
sqlite3 sqlite3
tzinfo-data tzinfo-data
RUBY VERSION RUBY VERSION
ruby 3.1.2p20 ruby 4.0.3
BUNDLED WITH BUNDLED WITH
2.3.7 4.0.6

View File

@ -60,6 +60,11 @@ class ApplicationController < ActionController::API
end end
def render_not_found_error(exception) def render_not_found_error(exception)
render json: { error: exception.to_s }, status: :not_found error = if exception.is_a?(ActiveRecord::RecordNotFound) && exception.model.present?
"Couldn't find #{exception.model} with '#{exception.primary_key}'=#{exception.id.inspect}"
else
'Record not found'
end
render json: { error: error }, status: :not_found
end end
end end

View File

@ -36,7 +36,7 @@ class BeamersController < ApplicationController
push_sync_if_needed! push_sync_if_needed!
render json: beamer, status: :created render json: beamer, status: :created
else else
render json: beamer.errors, status: :unprocessable_entity render json: beamer.errors, status: :unprocessable_content
end end
end end
@ -46,7 +46,7 @@ class BeamersController < ApplicationController
push_sync_if_needed! push_sync_if_needed!
render json: @beamer render json: @beamer
else else
render json: @beamer.errors, status: :unprocessable_entity render json: @beamer.errors, status: :unprocessable_content
end end
end end

View File

@ -25,6 +25,6 @@ class BetsController < ApplicationController
end end
def handle_user_service_error(exception) def handle_user_service_error(exception)
render json: { error: exception.message }, status: :unprocessable_entity render json: { error: exception.message }, status: :unprocessable_content
end end
end end

View File

@ -19,7 +19,7 @@ class MatchScoresController < ApplicationController
TournamentLiveBroadcast.call(@match_score.tournament.id) TournamentLiveBroadcast.call(@match_score.tournament.id)
render json: @match_score render json: @match_score
else else
render json: @match_score.errors, status: :unprocessable_entity render json: @match_score.errors, status: :unprocessable_content
end end
end end

View File

@ -26,14 +26,14 @@ class MatchesController < ApplicationController
match = build_decider_match match = build_decider_match
if match.errors.any? if match.errors.any?
render json: match.errors, status: :unprocessable_entity render json: match.errors, status: :unprocessable_content
elsif match.save elsif match.save
UpdateGroupsGroupScoresAndSave.call(group: @group) UpdateGroupsGroupScoresAndSave.call(group: @group)
push_sync_if_needed!(@group.tournament) push_sync_if_needed!(@group.tournament)
TournamentLiveBroadcast.call(@group.tournament.id) TournamentLiveBroadcast.call(@group.tournament.id)
render json: match, include: %w[match_scores.team group], status: :created render json: match, include: %w[match_scores.team group], status: :created
else else
render json: match.errors, status: :unprocessable_entity render json: match.errors, status: :unprocessable_content
end end
end end
@ -55,7 +55,7 @@ class MatchesController < ApplicationController
render json: @match render json: @match
synced_tournament = @match.tournament synced_tournament = @match.tournament
else else
render json: @match.errors, status: :unprocessable_entity render json: @match.errors, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end end
@ -66,7 +66,7 @@ class MatchesController < ApplicationController
# PATCH /matches/1/swap # PATCH /matches/1/swap
def swap def swap
unless @match.swappable_with?(@swap_target) unless @match.swappable_with?(@swap_target)
render json: { error: 'Matches must be different group matches from the same group' }, status: :unprocessable_entity render json: { error: 'Matches must be different group matches from the same group' }, status: :unprocessable_content
return return
end end
@ -102,27 +102,27 @@ class MatchesController < ApplicationController
if @match.winner.nil? if @match.winner.nil?
render json: { error: 'Stopping undecided Matches isn\'t allowed in playoff stage' }, render json: { error: 'Stopping undecided Matches isn\'t allowed in playoff stage' },
status: :unprocessable_entity status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
return if PopulateMatchBelowAndSave.call(match: @match).success? return if PopulateMatchBelowAndSave.call(match: @match).success?
render json: { error: 'Moving Team one stage down failed' }, render json: { error: 'Moving Team one stage down failed' },
status: :unprocessable_entity status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
def validate_params def validate_params
case match_params['state'] case match_params['state']
when 'in_progress' when 'in_progress'
render json: { error: 'Match can\'t start in this state' }, status: :unprocessable_entity \ render json: { error: 'Match can\'t start in this state' }, status: :unprocessable_content \
unless @match.not_started? unless @match.not_started?
when 'finished' when 'finished'
render json: { error: 'Match can\'t finish in this state' }, status: :unprocessable_entity \ render json: { error: 'Match can\'t finish in this state' }, status: :unprocessable_content \
unless @match.in_progress? unless @match.in_progress?
else else
render json: { error: 'Invalid target state' }, status: :unprocessable_entity render json: { error: 'Invalid target state' }, status: :unprocessable_content
end end
end end

View File

@ -15,7 +15,7 @@ class StagesController < ApplicationController
def update def update
if stage_params[:state] == 'finished' if stage_params[:state] == 'finished'
unless @stage.state == 'in_progress' unless @stage.state == 'in_progress'
render json: { error: 'Only running group stages can be finished' }, status: :unprocessable_entity render json: { error: 'Only running group stages can be finished' }, status: :unprocessable_content
return return
end end
@ -27,7 +27,7 @@ class StagesController < ApplicationController
render json: @stage render json: @stage
synced_tournament = @stage.tournament synced_tournament = @stage.tournament
else else
render json: @stage.errors, status: :unprocessable_entity render json: @stage.errors, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end end
@ -36,7 +36,7 @@ class StagesController < ApplicationController
else else
render json: { render json: {
error: 'The state attribute may only be changed to finished' error: 'The state attribute may only be changed to finished'
}, status: :unprocessable_entity }, status: :unprocessable_content
end end
end end
@ -46,14 +46,14 @@ class StagesController < ApplicationController
unless @stage.over? unless @stage.over?
render json: { render json: {
error: 'Group Stage still has some matches that are not over yet. Finish them to generate playoffs' error: 'Group Stage still has some matches that are not over yet. Finish them to generate playoffs'
}, status: :unprocessable_entity }, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
@stage.groups.each do |group| @stage.groups.each do |group|
next if UpdateGroupsGroupScoresAndSave.call(group: group).success? next if UpdateGroupsGroupScoresAndSave.call(group: group).success?
render json: { error: 'Updating group scores failed' }, status: :unprocessable_entity render json: { error: 'Updating group scores failed' }, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
@ -62,14 +62,14 @@ class StagesController < ApplicationController
render json: { render json: {
error: 'Playoffs cannot be generated yet because a decider match is required for an advancing place', error: 'Playoffs cannot be generated yet because a decider match is required for an advancing place',
blocking_ties: blocking_ties blocking_ties: blocking_ties
}, status: :unprocessable_entity }, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
return if AddPlayoffsToTournamentAndSave.call(tournament: @stage.tournament, return if AddPlayoffsToTournamentAndSave.call(tournament: @stage.tournament,
teams: GroupStageService.get_advancing_teams(@stage)).success? teams: GroupStageService.get_advancing_teams(@stage)).success?
render json: { error: 'Generating group stage failed' }, status: :unprocessable_entity render json: { error: 'Generating group stage failed' }, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end

View File

@ -15,7 +15,7 @@ class TeamActionItemsController < ApplicationController
) )
render json: @team_action_item render json: @team_action_item
else else
render json: @team_action_item.errors, status: :unprocessable_entity render json: @team_action_item.errors, status: :unprocessable_content
end end
end end

View File

@ -20,9 +20,9 @@ class TeamActionListsController < ApplicationController
render json: team_action_list, status: :created render json: team_action_list, status: :created
rescue TeamActionListService::InvalidSourceError, TeamActionListService::EmptySourceError => e rescue TeamActionListService::InvalidSourceError, TeamActionListService::EmptySourceError => e
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_content
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
render json: e.record.errors, status: :unprocessable_entity render json: e.record.errors, status: :unprocessable_content
end end
# PATCH /tournaments/:tournament_id/team_action_lists/:key/teams/:team_id # PATCH /tournaments/:tournament_id/team_action_lists/:key/teams/:team_id
@ -33,7 +33,7 @@ class TeamActionListsController < ApplicationController
) )
render json: @team_action_item render json: @team_action_item
else else
render json: @team_action_item.errors, status: :unprocessable_entity render json: @team_action_item.errors, status: :unprocessable_content
end end
end end

View File

@ -17,7 +17,7 @@ class TeamsController < ApplicationController
push_sync_if_needed!(@team.tournament) push_sync_if_needed!(@team.tournament)
render json: @team render json: @team
else else
render json: @team.errors, status: :unprocessable_entity render json: @team.errors, status: :unprocessable_content
end end
end end

View File

@ -67,7 +67,7 @@ class TournamentsController < ApplicationController
if tournament.save if tournament.save
render json: tournament, status: :created, location: tournament render json: tournament, status: :created, location: tournament
else else
render json: tournament.errors, status: :unprocessable_entity render json: tournament.errors, status: :unprocessable_content
end end
return return
end end
@ -90,14 +90,14 @@ class TournamentsController < ApplicationController
end end
# validate tournament # validate tournament
unless tournament.valid? unless tournament.valid?
render json: tournament.errors, status: :unprocessable_entity render json: tournament.errors, status: :unprocessable_content
return return
end end
# return appropriate result # return appropriate result
if result.success? if result.success?
render json: result.tournament, status: :created, location: result.tournament render json: result.tournament, status: :created, location: result.tournament
else else
render json: { error: 'Tournament generation failed' }, status: :unprocessable_entity render json: { error: 'Tournament generation failed' }, status: :unprocessable_content
end end
end end
@ -116,7 +116,7 @@ class TournamentsController < ApplicationController
synced_tournament = @tournament synced_tournament = @tournament
render json: @tournament render json: @tournament
else else
render json: @tournament.errors, status: :unprocessable_entity render json: @tournament.errors, status: :unprocessable_content
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end end
@ -139,7 +139,7 @@ class TournamentsController < ApplicationController
push_sync_if_needed!(@tournament) push_sync_if_needed!(@tournament)
render json: @tournament render json: @tournament
else else
render json: @tournament.errors, status: :unprocessable_entity render json: @tournament.errors, status: :unprocessable_content
end end
end end
@ -148,14 +148,14 @@ class TournamentsController < ApplicationController
TournamentLiveBroadcast.call(@tournament.id) TournamentLiveBroadcast.call(@tournament.id)
render json: @tournament, include: '**' render json: @tournament, include: '**'
rescue KeyError, ArgumentError => e rescue KeyError, ArgumentError => e
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_content
end end
def test_sync def test_sync
unless @tournament.sync_push_enabled? unless @tournament.sync_push_enabled?
return render json: { return render json: {
error: 'Tournament sync test requires writable tournament with sync_target_url and sync_auth_token' error: 'Tournament sync test requires writable tournament with sync_target_url and sync_auth_token'
}, status: :unprocessable_entity }, status: :unprocessable_content
end end
TournamentSyncPusher.push!(@tournament) TournamentSyncPusher.push!(@tournament)
@ -276,7 +276,7 @@ class TournamentsController < ApplicationController
teams = params['teams'] teams = params['teams']
return if teams.is_a?(Array) && teams.reject { |t| t.is_a? ActionController::Parameters }.count.zero? return if teams.is_a?(Array) && teams.reject { |t| t.is_a? ActionController::Parameters }.count.zero?
render json: { error: 'Invalid teams array' }, status: :unprocessable_entity render json: { error: 'Invalid teams array' }, status: :unprocessable_content
end end
def only_playoff_teams_amount_changed def only_playoff_teams_amount_changed
@ -307,14 +307,14 @@ class TournamentsController < ApplicationController
render json: { render json: {
error: 'playoff_teams_amount, instant_finalists_amount and intermediate_round_participants_amount don\'t match' error: 'playoff_teams_amount, instant_finalists_amount and intermediate_round_participants_amount don\'t match'
}, status: :unprocessable_entity }, status: :unprocessable_content
end end
def sync_state_params def sync_state_params
params.require(:snapshot).permit!.to_h params.require(:snapshot).permit!.to_h
end end
def authorize_sync_request! def authorize_sync_request!
return render json: { error: 'Tournament is not configured to accept sync pushes' }, status: :unprocessable_entity unless @tournament.sync_accepts_push? return render json: { error: 'Tournament is not configured to accept sync pushes' }, status: :unprocessable_content unless @tournament.sync_accepts_push?
token = request.authorization.to_s.delete_prefix('Bearer ').presence || request.headers['X-Tournament-Sync-Token'].to_s token = request.authorization.to_s.delete_prefix('Bearer ').presence || request.headers['X-Tournament-Sync-Token'].to_s
return render json: { error: 'Missing sync token' }, status: :unauthorized if token.blank? return render json: { error: 'Missing sync token' }, status: :unauthorized if token.blank?
@ -448,12 +448,12 @@ class TournamentsController < ApplicationController
return 'timer_reason_text is required for custom timer_reason' return 'timer_reason_text is required for custom timer_reason'
end end
if timer_reason != 'custom' && timer_reason_text.present? return unless timer_reason != 'custom' && timer_reason_text.present?
'timer_reason_text is only allowed when timer_reason is custom'
end 'timer_reason_text is only allowed when timer_reason is custom'
end end
def render_timer_validation_error(message) def render_timer_validation_error(message)
render json: { error: message }, status: :unprocessable_entity render json: { error: message }, status: :unprocessable_content
end end
end end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Match < ApplicationRecord class Match < ApplicationRecord
enum state: { single_team: 0, not_ready: 1, not_started: 2, in_progress: 3, finished: 4, enum :state, { single_team: 0, not_ready: 1, not_started: 2, in_progress: 3, finished: 4,
undecided: 5 } undecided: 5 }
belongs_to :stage, optional: true belongs_to :stage, optional: true
belongs_to :group, optional: true belongs_to :group, optional: true

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Stage < ApplicationRecord class Stage < ApplicationRecord
enum state: { playoff_stage: 0, intermediate_stage: 1, in_progress: 2, finished: 3 } enum :state, { playoff_stage: 0, intermediate_stage: 1, in_progress: 2, finished: 3 }
belongs_to :tournament belongs_to :tournament
has_many :matches, dependent: :destroy has_many :matches, dependent: :destroy

View File

@ -7,6 +7,7 @@ class Tournament < ApplicationRecord
TIMER_REASONS = %w[tournament_start playoff_start match_end custom].freeze TIMER_REASONS = %w[tournament_start playoff_start match_end custom].freeze
belongs_to :user belongs_to :user
belongs_to :owner, class_name: 'User', foreign_key: :user_id
has_many :teams, dependent: :destroy has_many :teams, dependent: :destroy
has_many :stages, dependent: :destroy has_many :stages, dependent: :destroy
has_many :beamers, dependent: :destroy has_many :beamers, dependent: :destroy
@ -22,8 +23,6 @@ class Tournament < ApplicationRecord
validate :playoff_teams_amount_is_positive_power_of_two validate :playoff_teams_amount_is_positive_power_of_two
validate :timer_reason_text_matches_reason validate :timer_reason_text_matches_reason
alias_attribute :owner, :user
after_initialize :generate_code after_initialize :generate_code
after_create_commit :ensure_default_beamer! after_create_commit :ensure_default_beamer!
before_validation :normalize_timer_reason before_validation :normalize_timer_reason

View File

@ -50,7 +50,5 @@ module TurniereBackend
from: 'noreply@turnie.re' from: 'noreply@turnie.re'
} }
# Active Record
config.active_record.legacy_connection_handling = false
end end
end end

View File

@ -79,12 +79,16 @@ Rails.application.configure do
# Mail # Mail
config.action_mailer.raise_delivery_errors = true config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_url_options = { host: 'api.turnie.re' } config.action_mailer.default_url_options = { host: 'api.turnie.re' }
config.action_mailer.delivery_method = :mailgun if ENV['TURNIERE_DISABLE_EMAIL_DELIVERY'].present?
config.action_mailer.mailgun_settings = { config.action_mailer.perform_deliveries = false
api_key: ENV.fetch('MAILGUN_API_KEY'), else
domain: ENV.fetch('MAILGUN_DOMAIN'), config.action_mailer.delivery_method = :mailgun
api_host: 'api.eu.mailgun.net' # Uncomment this line for EU region domains config.action_mailer.mailgun_settings = {
} api_key: ENV.fetch('MAILGUN_API_KEY'),
domain: ENV.fetch('MAILGUN_DOMAIN'),
api_host: 'api.eu.mailgun.net' # Uncomment this line for EU region domains
}
end
# Logging to STDOUT (running in docker by default) # Logging to STDOUT (running in docker by default)
config.logger = Logger.new(STDOUT) config.logger = Logger.new(STDOUT)

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
# Devise 4.9 still passes route option hashes to Rails' `resource` helper
# positionally. Rails 8.1 accepts that with deprecation warnings and Rails 8.2
# removes it, so keep the same Devise routes but pass those options as keywords.
module DeviseRoutesRails8Compat
def devise_session(mapping, controllers)
resource :session, only: [], controller: controllers[:sessions], path: '' do
get :new, path: mapping.path_names[:sign_in], as: 'new'
post :create, path: mapping.path_names[:sign_in]
match :destroy, path: mapping.path_names[:sign_out], as: 'destroy', via: mapping.sign_out_via
end
end
def devise_password(mapping, controllers)
resource :password,
only: [:new, :create, :edit, :update],
path: mapping.path_names[:password],
controller: controllers[:passwords]
end
def devise_confirmation(mapping, controllers)
resource :confirmation,
only: [:new, :create, :show],
path: mapping.path_names[:confirmation],
controller: controllers[:confirmations]
end
def devise_unlock(mapping, controllers)
return unless mapping.to.unlock_strategy_enabled?(:email)
resource :unlock,
only: [:new, :create, :show],
path: mapping.path_names[:unlock],
controller: controllers[:unlocks]
end
def devise_registration(mapping, controllers)
resource :registration,
only: [:new, :create, :edit, :update, :destroy],
path: mapping.path_names[:registration],
path_names: {
new: mapping.path_names[:sign_up],
edit: mapping.path_names[:edit],
cancel: mapping.path_names[:cancel]
},
controller: controllers[:registrations] do
get :cancel
end
end
end
ActionDispatch::Routing::Mapper.prepend(DeviseRoutesRails8Compat)

View File

@ -48,5 +48,6 @@ DeviseTokenAuth.setup do |config|
# do so by enabling this flag. NOTE: This feature is highly experimental! # do so by enabling this flag. NOTE: This feature is highly experimental!
# config.enable_standard_devise_support = false # config.enable_standard_devise_support = false
config.bypass_sign_in = false
config.default_confirm_success_url = 'https://turnie.re' config.default_confirm_success_url = 'https://turnie.re'
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddTimerReasonToTournaments < ActiveRecord::Migration[7.0] class AddTimerReasonToTournaments < ActiveRecord::Migration[7.0]
def change def change
add_column :tournaments, :timer_reason, :string add_column :tournaments, :timer_reason, :string

View File

@ -10,96 +10,96 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do ActiveRecord::Schema[8.1].define(version: 2026_04_28_120000) do
create_table "beamers", force: :cascade do |t| create_table "beamers", force: :cascade do |t|
t.string "name", null: false t.json "config", default: {}, null: false
t.datetime "created_at", null: false
t.string "display_state", default: "during_game", null: false t.string "display_state", default: "during_game", null: false
t.boolean "is_default", default: false t.boolean "is_default", default: false
t.integer "tournament_id", null: false t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "sync_source_id" t.integer "sync_source_id"
t.json "config", default: {}, null: false t.integer "tournament_id", null: false
t.datetime "updated_at", null: false
t.index ["tournament_id", "name"], name: "index_beamers_on_tournament_id_and_name", unique: true 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" t.index ["tournament_id"], name: "index_beamers_on_tournament_id"
end end
create_table "bets", force: :cascade do |t| create_table "bets", force: :cascade do |t|
t.integer "user_id", null: false t.datetime "created_at", precision: nil, null: false
t.integer "match_id", null: false t.integer "match_id", null: false
t.integer "team_id" t.integer "team_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.integer "user_id", null: false
t.index ["match_id"], name: "index_bets_on_match_id" t.index ["match_id"], name: "index_bets_on_match_id"
t.index ["team_id"], name: "index_bets_on_team_id" t.index ["team_id"], name: "index_bets_on_team_id"
t.index ["user_id"], name: "index_bets_on_user_id" t.index ["user_id"], name: "index_bets_on_user_id"
end end
create_table "group_scores", force: :cascade do |t| 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 "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.integer "group_id", null: false
t.integer "group_points", default: 0
t.integer "position", default: 0 t.integer "position", default: 0
t.integer "received_points", default: 0
t.integer "scored_points", default: 0
t.integer "sync_source_id" t.integer "sync_source_id"
t.integer "team_id", null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["group_id"], name: "index_group_scores_on_group_id" t.index ["group_id"], name: "index_group_scores_on_group_id"
t.index ["team_id"], name: "index_group_scores_on_team_id" t.index ["team_id"], name: "index_group_scores_on_team_id"
end end
create_table "groups", force: :cascade do |t| create_table "groups", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.integer "number" t.integer "number"
t.integer "stage_id", null: false 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.integer "sync_source_id"
t.datetime "updated_at", precision: nil, null: false
t.index ["stage_id"], name: "index_groups_on_stage_id" t.index ["stage_id"], name: "index_groups_on_stage_id"
end end
create_table "match_scores", force: :cascade do |t| 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 "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "hidden_points", default: 0, null: false t.integer "hidden_points", default: 0, null: false
t.integer "match_id", null: false
t.integer "points", default: 0
t.integer "sync_source_id" t.integer "sync_source_id"
t.integer "team_id", null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["match_id"], name: "index_match_scores_on_match_id" t.index ["match_id"], name: "index_match_scores_on_match_id"
t.index ["team_id"], name: "index_match_scores_on_team_id" t.index ["team_id"], name: "index_match_scores_on_team_id"
end end
create_table "matches", force: :cascade do |t| create_table "matches", force: :cascade do |t|
t.integer "state", default: 0 t.datetime "created_at", precision: nil, null: false
t.boolean "decider", default: false, null: false
t.integer "group_id"
t.integer "position" t.integer "position"
t.integer "stage_id" t.integer "stage_id"
t.integer "group_id" t.integer "state", default: 0
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.integer "sync_source_id"
t.datetime "updated_at", precision: nil, null: false
t.index ["group_id"], name: "index_matches_on_group_id" t.index ["group_id"], name: "index_matches_on_group_id"
t.index ["stage_id"], name: "index_matches_on_stage_id" t.index ["stage_id"], name: "index_matches_on_stage_id"
end end
create_table "stages", force: :cascade do |t| create_table "stages", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.integer "level" t.integer "level"
t.integer "state", default: 0 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.integer "sync_source_id"
t.integer "tournament_id", null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["tournament_id"], name: "index_stages_on_tournament_id" t.index ["tournament_id"], name: "index_stages_on_tournament_id"
end end
create_table "team_action_items", force: :cascade do |t| 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.boolean "completed", default: false, null: false
t.datetime "completed_at" t.datetime "completed_at"
t.integer "sync_source_id"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.integer "sync_source_id"
t.integer "team_action_list_id", null: false
t.integer "team_id", null: false
t.datetime "updated_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 ["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", "team_id"], name: "index_team_action_items_on_team_action_list_id_and_team_id", unique: true
@ -108,13 +108,13 @@ ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do
end end
create_table "team_action_lists", force: :cascade do |t| 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 "action_name", null: false
t.datetime "created_at", null: false
t.string "key", null: false
t.string "name", null: false
t.string "source", null: false t.string "source", null: false
t.integer "sync_source_id" t.integer "sync_source_id"
t.datetime "created_at", null: false t.integer "tournament_id", null: false
t.datetime "updated_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 ["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", "key"], name: "index_team_action_lists_on_tournament_id_and_key", unique: true
@ -122,25 +122,25 @@ ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do
end end
create_table "teams", force: :cascade do |t| create_table "teams", force: :cascade do |t|
t.string "name"
t.integer "tournament_id"
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.string "name"
t.integer "sync_source_id" t.integer "sync_source_id"
t.integer "tournament_id"
t.datetime "updated_at", precision: nil, null: false
t.index ["tournament_id"], name: "index_teams_on_tournament_id" t.index ["tournament_id"], name: "index_teams_on_tournament_id"
end end
create_table "tournament_sync_queue_entries", force: :cascade do |t| 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.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 "created_at", null: false
t.datetime "last_attempt_at"
t.string "last_error"
t.string "lock_token"
t.datetime "locked_at"
t.datetime "next_attempt_at", null: false
t.text "snapshot_json", null: false
t.string "status", default: "pending", null: false
t.integer "tournament_id", null: false
t.datetime "updated_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 ["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 ["status"], name: "index_tournament_sync_queue_entries_on_status"
@ -148,53 +148,53 @@ ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do
end end
create_table "tournaments", force: :cascade do |t| create_table "tournaments", force: :cascade do |t|
t.string "name", null: false
t.string "code", null: false t.string "code", null: false
t.datetime "created_at", precision: nil, null: false
t.string "description" t.string "description"
t.boolean "public", default: true
t.integer "playoff_teams_amount", default: 0
t.integer "instant_finalists_amount", default: 0 t.integer "instant_finalists_amount", default: 0
t.integer "intermediate_round_participants_amount", default: 0 t.integer "intermediate_round_participants_amount", default: 0
t.integer "user_id", null: false t.string "name", null: false
t.datetime "created_at", precision: nil, null: false t.integer "playoff_teams_amount", default: 0
t.datetime "updated_at", precision: nil, null: false t.boolean "public", default: true
t.datetime "timestamp"
t.boolean "read_only_mode", default: false, null: false t.boolean "read_only_mode", default: false, null: false
t.string "sync_target_url"
t.string "sync_auth_token" 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.datetime "sync_last_imported_snapshot_at"
t.string "sync_last_push_error"
t.datetime "sync_last_pushed_at"
t.integer "sync_source_tournament_id"
t.string "sync_target_url"
t.string "timer_mode" t.string "timer_mode"
t.datetime "timestamp"
t.string "timer_reason" t.string "timer_reason"
t.text "timer_reason_text" t.text "timer_reason_text"
t.datetime "updated_at", precision: nil, null: false
t.integer "user_id", null: false
t.index ["code"], name: "index_tournaments_on_code", unique: true t.index ["code"], name: "index_tournaments_on_code", unique: true
t.index ["user_id"], name: "index_tournaments_on_user_id" t.index ["user_id"], name: "index_tournaments_on_user_id"
end end
create_table "users", force: :cascade do |t| 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.boolean "allow_password_change", default: false
t.datetime "remember_created_at", precision: nil t.datetime "confirmation_sent_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.string "confirmation_token"
t.datetime "confirmed_at", precision: nil 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 "created_at", precision: nil, null: false
t.datetime "current_sign_in_at", precision: nil
t.string "current_sign_in_ip"
t.string "email"
t.string "encrypted_password", default: "", null: false
t.datetime "last_sign_in_at", precision: nil
t.string "last_sign_in_ip"
t.string "provider", default: "email", null: false
t.datetime "remember_created_at", precision: nil
t.datetime "reset_password_sent_at", precision: nil
t.string "reset_password_token"
t.integer "sign_in_count", default: 0, null: false
t.text "tokens"
t.string "uid", default: "", null: false
t.string "unconfirmed_email"
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.string "username"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
t.index ["provider"], name: "index_users_on_provider" t.index ["provider"], name: "index_users_on_provider"

View File

@ -0,0 +1,64 @@
# Rails 8 Dependency Update Notes
## Scope
This update moves the backend runtime to Ruby 4.0.3, Bundler 4.0.6, Rails 8.1,
Puma 8, and the released `devise_token_auth` gem.
The project no longer uses the Thor77 `devise_token_auth` fork. The released
gem currently supports this stack through `devise_token_auth` 1.2.6 with
`devise` 4.9.4.
Because this is an API-only app without session middleware,
`DeviseTokenAuth.bypass_sign_in` must stay disabled. With the gem default
enabled, authenticated token requests call Devise session bypass code and fail
in API-only production. Disabled mode still authenticates token requests with
`store: false`.
## Docker Versioning
Container versioning still uses the shared pipeline `base_commit` build arg.
The production and test images burn that value into `GIT_COMMIT_SHA` at build
time.
Do not set `GIT_COMMIT_SHA` as a runtime service variable in CI or deployment.
Runtime overrides can make `/version` report a different SHA than the image was
built from.
## Email Delivery In Blackbox Runs
`TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY` is a local blackbox compose variable.
It maps to the app runtime variable `TURNIERE_DISABLE_EMAIL_DELIVERY`.
Blackbox production E2E uses real production mode but fake Mailgun credentials.
Registration sends a confirmation email, so blackbox runs disable delivery to
avoid calling Mailgun while still keeping the production confirmation flow.
Normal production deployments should not set `TURNIERE_DISABLE_EMAIL_DELIVERY`
unless email delivery is intentionally disabled.
## Schema Diff
Rails 8.1 dumps columns in a different order than Rails 7. That creates a large
`db/schema.rb` diff, but it is ordering churn, not dropped columns.
Relative to current `master`, no existing schema columns are removed by the
dependency update. The timer reason work on `master` adds:
- `tournaments.timer_reason`
- `tournaments.timer_reason_text`
## API And Frontend Compatibility
Known frontend-facing API changes are additive:
- tournament payloads include `timer_reason`
- tournament payloads include `timer_reason_text`
- timer endpoints accept and return those same fields
Existing frontend code can ignore these fields and keep using `timestamp` and
`timer_mode`. Frontend changes are only needed if the UI should show or edit
timer reasons.
No deployment configuration change is required beyond building/running the new
image with the existing shared pipeline build metadata.

View File

@ -43,6 +43,7 @@ services:
SECRET_KEY_BASE: ${TURNIERE_BLACKBOX_SECRET_KEY_BASE:-turniere-blackbox-secret-key-base-please-change-in-real-prod} SECRET_KEY_BASE: ${TURNIERE_BLACKBOX_SECRET_KEY_BASE:-turniere-blackbox-secret-key-base-please-change-in-real-prod}
MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key} MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key}
MAILGUN_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com} MAILGUN_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com}
TURNIERE_DISABLE_EMAIL_DELIVERY: ${TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY:-1}
TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: ${TURNIERE_BLACKBOX_TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS:-3} TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: ${TURNIERE_BLACKBOX_TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS:-3}
RAILS_LOG_TO_STDOUT: "1" RAILS_LOG_TO_STDOUT: "1"
RAILS_SERVE_STATIC_FILES: "1" RAILS_SERVE_STATIC_FILES: "1"
@ -64,6 +65,7 @@ services:
SECRET_KEY_BASE: ${TURNIERE_BLACKBOX_SECRET_KEY_BASE:-turniere-blackbox-secret-key-base-please-change-in-real-prod} SECRET_KEY_BASE: ${TURNIERE_BLACKBOX_SECRET_KEY_BASE:-turniere-blackbox-secret-key-base-please-change-in-real-prod}
MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key} MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key}
MAILGUN_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com} MAILGUN_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com}
TURNIERE_DISABLE_EMAIL_DELIVERY: ${TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY:-1}
TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: ${TURNIERE_BLACKBOX_TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS:-3} TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS: ${TURNIERE_BLACKBOX_TOURNAMENT_SYNC_HTTP_TIMEOUT_SECONDS:-3}
RAILS_LOG_TO_STDOUT: "1" RAILS_LOG_TO_STDOUT: "1"
RAILS_SERVE_STATIC_FILES: "1" RAILS_SERVE_STATIC_FILES: "1"

View File

@ -25,6 +25,7 @@ services:
SECRET_KEY_BASE: ${TURNIERE_BLACKBOX_SECRET_KEY_BASE:-turniere-blackbox-secret-key-base-please-change-in-real-prod} SECRET_KEY_BASE: ${TURNIERE_BLACKBOX_SECRET_KEY_BASE:-turniere-blackbox-secret-key-base-please-change-in-real-prod}
MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key} MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key}
MAILGUN_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com} MAILGUN_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com}
TURNIERE_DISABLE_EMAIL_DELIVERY: ${TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY:-1}
RAILS_LOG_TO_STDOUT: "1" RAILS_LOG_TO_STDOUT: "1"
RAILS_SERVE_STATIC_FILES: "1" RAILS_SERVE_STATIC_FILES: "1"
ports: ports:

View File

@ -4,5 +4,9 @@ module AuthHelpers
def apply_authentication_headers_for(user) def apply_authentication_headers_for(user)
user_headers = user.create_new_auth_token user_headers = user.create_new_auth_token
request.headers.merge!(user_headers) request.headers.merge!(user_headers)
return unless respond_to?(:controller)
allow(controller).to receive(:authenticate_user!).and_return(true)
allow(controller).to receive(:current_user).and_return(user)
end end
end end

View File

@ -95,14 +95,14 @@ RSpec.describe BeamersController, type: :controller do
post :create, params: { post :create, params: {
tournament_id: @tournament.to_param, name: 'bad', display_state: 'invalid' tournament_id: @tournament.to_param, name: 'bad', display_state: 'invalid'
} }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'rejects duplicate name in same tournament' do it 'rejects duplicate name in same tournament' do
post :create, params: { post :create, params: {
tournament_id: @tournament.to_param, name: @beamer.name, display_state: 'during_game' tournament_id: @tournament.to_param, name: @beamer.name, display_state: 'during_game'
} }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
@ -157,7 +157,7 @@ RSpec.describe BeamersController, type: :controller do
tournament_id: @tournament.to_param, id: @beamer.to_param, tournament_id: @tournament.to_param, id: @beamer.to_param,
display_state: 'bogus' display_state: 'bogus'
} }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end

View File

@ -62,7 +62,7 @@ RSpec.describe MatchScoresController, type: :controller do
it 'returns unprocessable entity' do it 'returns unprocessable entity' do
put :update, params: { id: @match_score.to_param }.merge(valid_update) put :update, params: { id: @match_score.to_param }.merge(valid_update)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end

View File

@ -120,7 +120,7 @@ RSpec.describe MatchesController, type: :controller do
it 'rejects the request' do it 'rejects the request' do
post :create, params: { group_id: group.to_param, team_ids: [group.teams.first.id] } post :create, params: { group_id: group.to_param, team_ids: [group.teams.first.id] }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end
@ -167,7 +167,7 @@ RSpec.describe MatchesController, type: :controller do
patch :swap, params: { id: first_match.to_param, other_match_id: foreign_match.to_param } patch :swap, params: { id: first_match.to_param, other_match_id: foreign_match.to_param }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq('Matches must be different group matches from the same group') expect(deserialize_response(response)[:error]).to eq('Matches must be different group matches from the same group')
end end
@ -288,7 +288,7 @@ RSpec.describe MatchesController, type: :controller do
end end
it 'returns an unprocessable entity response' do it 'returns an unprocessable entity response' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'doesn\'t change the matches status' do it 'doesn\'t change the matches status' do
@ -321,7 +321,7 @@ RSpec.describe MatchesController, type: :controller do
it 'returns unprocessable entity' do it 'returns unprocessable entity' do
put :update, params: { id: @running_playoff_match.to_param }.merge(finished) put :update, params: { id: @running_playoff_match.to_param }.merge(finished)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
@ -341,7 +341,7 @@ RSpec.describe MatchesController, type: :controller do
allow_any_instance_of(Match).to receive(:winner).and_return(@running_playoff_match.match_scores.first.team) allow_any_instance_of(Match).to receive(:winner).and_return(@running_playoff_match.match_scores.first.team)
put :update, params: { id: @running_playoff_match.to_param }.merge(finished) put :update, params: { id: @running_playoff_match.to_param }.merge(finished)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end
@ -375,14 +375,14 @@ RSpec.describe MatchesController, type: :controller do
context 'with invalid params' do context 'with invalid params' do
it 'renders an unprocessable entity response' do it 'renders an unprocessable entity response' do
put :update, params: { id: @match.to_param }.merge(invalid_update) put :update, params: { id: @match.to_param }.merge(invalid_update)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
context 'with senseless params' do context 'with senseless params' do
it 'renders an unprocessable entity response' do it 'renders an unprocessable entity response' do
put :update, params: { id: @match.to_param }.merge(senseless_update) put :update, params: { id: @match.to_param }.merge(senseless_update)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end
@ -416,7 +416,7 @@ RSpec.describe MatchesController, type: :controller do
context 'with invalid params' do context 'with invalid params' do
it 'renders an unprocessable entity response' do it 'renders an unprocessable entity response' do
put :update, params: { id: @not_ready_playoff_match.to_param }.merge(invalid_update) put :update, params: { id: @not_ready_playoff_match.to_param }.merge(invalid_update)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end

View File

@ -122,7 +122,7 @@ RSpec.describe StagesController, type: :controller do
end end
it 'it returns unprocessable entity' do it 'it returns unprocessable entity' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'returns the correct error' do it 'returns the correct error' do
@ -145,7 +145,7 @@ RSpec.describe StagesController, type: :controller do
end end
it 'it returns unprocessable entity' do it 'it returns unprocessable entity' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'returns the correct error' do it 'returns the correct error' do
@ -164,7 +164,7 @@ RSpec.describe StagesController, type: :controller do
end end
it 'it returns unprocessable entity' do it 'it returns unprocessable entity' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'returns the correct error' do it 'returns the correct error' do
@ -197,7 +197,7 @@ RSpec.describe StagesController, type: :controller do
end end
it 'returns unprocessable entity' do it 'returns unprocessable entity' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'returns a helpful decider error' do it 'returns a helpful decider error' do
@ -233,7 +233,7 @@ RSpec.describe StagesController, type: :controller do
end end
it 'returns unprocessable entity' do it 'returns unprocessable entity' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'returns a helpful decider error' do it 'returns a helpful decider error' do

View File

@ -47,7 +47,7 @@ RSpec.describe TeamActionListsController, type: :controller do
it 'returns unprocessable entity for unsupported source' do it 'returns unprocessable entity for unsupported source' do
post :create, params: create_params.merge(source: 'unknown_source') post :create, params: create_params.merge(source: 'unknown_source')
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq('Unsupported team action list source: unknown_source') expect(deserialize_response(response)[:error]).to eq('Unsupported team action list source: unknown_source')
end end
@ -57,7 +57,7 @@ RSpec.describe TeamActionListsController, type: :controller do
post :create, params: create_params.merge(tournament_id: empty_tournament.to_param) post :create, params: create_params.merge(tournament_id: empty_tournament.to_param)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq('Source all_tournament_teams does not currently contain any teams') expect(deserialize_response(response)[:error]).to eq('Source all_tournament_teams does not currently contain any teams')
end end
@ -71,7 +71,7 @@ RSpec.describe TeamActionListsController, type: :controller do
post :create, params: create_params post :create, params: create_params
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end

View File

@ -308,21 +308,21 @@ RSpec.describe TournamentsController, type: :controller do
it 'rejects timestamp_seconds for countup mode' do it 'rejects timestamp_seconds for countup mode' do
patch :set_timer, 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(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq('timestamp_seconds is only allowed for countdown mode') expect(deserialize_response(response)[:error]).to eq('timestamp_seconds is only allowed for countdown mode')
end end
it 'rejects invalid timer reason values' do it 'rejects invalid timer reason values' do
patch :set_timer, params: { id: @tournament.to_param, timestamp_seconds: 120, timer_reason: 'ceremony' } patch :set_timer, params: { id: @tournament.to_param, timestamp_seconds: 120, timer_reason: 'ceremony' }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq('Invalid timer_reason') expect(deserialize_response(response)[:error]).to eq('Invalid timer_reason')
end end
it 'requires timer_reason_text for custom timer reason' do it 'requires timer_reason_text for custom timer reason' do
patch :set_timer, params: { id: @tournament.to_param, timestamp_seconds: 120, timer_reason: 'custom' } patch :set_timer, params: { id: @tournament.to_param, timestamp_seconds: 120, timer_reason: 'custom' }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq('timer_reason_text is required for custom timer_reason') expect(deserialize_response(response)[:error]).to eq('timer_reason_text is required for custom timer_reason')
end end
@ -334,7 +334,7 @@ RSpec.describe TournamentsController, type: :controller do
timer_reason_text: 'Extra info' timer_reason_text: 'Extra info'
} }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]).to eq( expect(deserialize_response(response)[:error]).to eq(
'timer_reason_text is only allowed when timer_reason is custom' 'timer_reason_text is only allowed when timer_reason is custom'
) )
@ -466,7 +466,7 @@ RSpec.describe TournamentsController, type: :controller do
context 'playoff_teams_amount unacceptable' do context 'playoff_teams_amount unacceptable' do
shared_examples_for 'wrong playoff_teams_amount' do shared_examples_for 'wrong playoff_teams_amount' do
it 'fails' do it 'fails' do
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'returns the correct error message' do it 'returns the correct error message' do
expect(deserialize_response(response)[:playoff_teams_amount].first) expect(deserialize_response(response)[:playoff_teams_amount].first)
@ -522,7 +522,7 @@ RSpec.describe TournamentsController, type: :controller do
data = create_group_tournament_data data = create_group_tournament_data
data[:teams].pop data[:teams].pop
post :create, params: data post :create, params: data
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
@ -540,7 +540,7 @@ RSpec.describe TournamentsController, type: :controller do
context 'with invalid parameters' do context 'with invalid parameters' do
it 'renders an unprocessable entity response' do it 'renders an unprocessable entity response' do
put :create, params: { teams: [1, 2, 3] } put :create, params: { teams: [1, 2, 3] }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
@ -549,7 +549,7 @@ RSpec.describe TournamentsController, type: :controller do
data = create_group_tournament_data data = create_group_tournament_data
data[:teams] = [{ group: 1 }, { group: 1 }, { group: 2 }, { group: 2 }] data[:teams] = [{ group: 1 }, { group: 1 }, { group: 2 }, { group: 2 }]
post :create, params: data post :create, params: data
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end
@ -637,18 +637,18 @@ RSpec.describe TournamentsController, type: :controller do
it 'fails when only instant_finalists_amount is changed' do it 'fails when only instant_finalists_amount is changed' do
put :update, params: { id: @filled_tournament.to_param }.merge(instant_finalists_amount: 29) put :update, params: { id: @filled_tournament.to_param }.merge(instant_finalists_amount: 29)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'fails when only intermediate_round_participants_amount is changed' do it 'fails when only intermediate_round_participants_amount is changed' do
put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29) put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'fails when parameters don\'t match' do it 'fails when parameters don\'t match' do
put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29, put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29,
instant_finalists_amount: 32) instant_finalists_amount: 32)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'succeeds when all three are changed correctly' do it 'succeeds when all three are changed correctly' do
@ -667,7 +667,7 @@ RSpec.describe TournamentsController, type: :controller do
it 'returns unprocessable entity' do it 'returns unprocessable entity' do
put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8) put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
it 'doesn\'t change playoff_teams_amount' do it 'doesn\'t change playoff_teams_amount' do
@ -720,7 +720,7 @@ RSpec.describe TournamentsController, type: :controller do
it 'fails when playoff_teams_amount is higher than the amount of teams participating' do it 'fails when playoff_teams_amount is higher than the amount of teams participating' do
put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 783) put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 783)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
end end
end end
end end
@ -863,7 +863,7 @@ RSpec.describe TournamentsController, type: :controller do
it 'rejects sync test without complete leader configuration' do it 'rejects sync test without complete leader configuration' do
post :test_sync, params: { id: @tournament.to_param } post :test_sync, params: { id: @tournament.to_param }
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(deserialize_response(response)[:error]) expect(deserialize_response(response)[:error])
.to eq('Tournament sync test requires writable tournament with sync_target_url and sync_auth_token') .to eq('Tournament sync test requires writable tournament with sync_target_url and sync_auth_token')
end end

View File

@ -52,7 +52,7 @@ RSpec.describe 'Backend API surface HTTP E2E' do
}) })
login = TurniereE2E::ApiClient.new(base_url: base_url).login!(email: email, password: password) login = TurniereE2E::ApiClient.new(base_url: base_url).login!(email: email, password: password)
expect([500, *200..299]).to include(registration[:status]) expect(registration[:status]).to be_between(200, 299)
expect(registration[:json]).not_to eq({}) expect(registration[:json]).not_to eq({})
expect(login[:status]).to eq(401) expect(login[:status]).to eq(401)
end end
@ -154,9 +154,9 @@ RSpec.describe 'Backend API surface HTTP E2E' do
timer_reason: 'ceremony' timer_reason: 'ceremony'
}) })
timer_custom_without_text = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timer_custom_without_text = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: {
timestamp_seconds: 120, timestamp_seconds: 120,
timer_reason: 'custom' timer_reason: 'custom'
}) })
timer_invalid_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timer_invalid_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: {
timestamp_seconds: 'nope' timestamp_seconds: 'nope'
}) })

View File

@ -5,5 +5,6 @@ FactoryBot.define do
sequence(:username) { |n| "user#{n}" } sequence(:username) { |n| "user#{n}" }
sequence(:email) { |n| "user#{n}@example.com" } sequence(:email) { |n| "user#{n}@example.com" }
password { 'password123' } password { 'password123' }
confirmed_at { Time.current }
end end
end end

View File

@ -36,7 +36,7 @@ rescue ActiveRecord::PendingMigrationError => e
end end
RSpec.configure do |config| RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures" config.fixture_paths = ["#{::Rails.root}/spec/fixtures"]
# Run only focused tests # Run only focused tests
# TODO REVERT ME # TODO REVERT ME

View File

@ -80,6 +80,7 @@ RSpec.describe TournamentSnapshotImporter do
end end
end end
# rubocop:disable Metrics/MethodLength
def create_large_source_tournament! def create_large_source_tournament!
tournament = create( tournament = create(
:tournament, :tournament,
@ -142,6 +143,7 @@ RSpec.describe TournamentSnapshotImporter do
) )
tournament.reload tournament.reload
end end
# rubocop:enable Metrics/MethodLength
def create_group_round_robin!(group:, teams:) def create_group_round_robin!(group:, teams:)
pairings = [ pairings = [
@ -190,6 +192,7 @@ RSpec.describe TournamentSnapshotImporter do
end end
end end
# rubocop:disable Metrics/MethodLength
def sync_signature(tournament) def sync_signature(tournament)
{ {
name: tournament.name, name: tournament.name,
@ -244,6 +247,7 @@ RSpec.describe TournamentSnapshotImporter do
end end
} }
end end
# rubocop:enable Metrics/MethodLength
def match_signature(match) def match_signature(match)
{ {

View File

@ -189,6 +189,7 @@ def _compose_env(
TURNIERE_BLACKBOX_SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE, TURNIERE_BLACKBOX_SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE,
TURNIERE_BLACKBOX_MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY, TURNIERE_BLACKBOX_MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY,
TURNIERE_BLACKBOX_MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN, TURNIERE_BLACKBOX_MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN,
TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY="1",
TURNIERE_E2E_BASE_URL=BLACKBOX_INTERNAL_BASE_URL, TURNIERE_E2E_BASE_URL=BLACKBOX_INTERNAL_BASE_URL,
TURNIERE_E2E_EXPECT_PROFILING=expect_profiling, TURNIERE_E2E_EXPECT_PROFILING=expect_profiling,
TURNIERE_E2E_EMAIL=E2E_EMAIL, TURNIERE_E2E_EMAIL=E2E_EMAIL,
@ -220,6 +221,7 @@ def _follow_compose_env(
TURNIERE_BLACKBOX_SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE, TURNIERE_BLACKBOX_SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE,
TURNIERE_BLACKBOX_MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY, TURNIERE_BLACKBOX_MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY,
TURNIERE_BLACKBOX_MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN, TURNIERE_BLACKBOX_MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN,
TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY="1",
TURNIERE_SOURCE_E2E_BASE_URL=BLACKBOX_SOURCE_INTERNAL_BASE_URL, TURNIERE_SOURCE_E2E_BASE_URL=BLACKBOX_SOURCE_INTERNAL_BASE_URL,
TURNIERE_FOLLOWER_E2E_BASE_URL=BLACKBOX_FOLLOWER_INTERNAL_BASE_URL, TURNIERE_FOLLOWER_E2E_BASE_URL=BLACKBOX_FOLLOWER_INTERNAL_BASE_URL,
TURNIERE_E2E_EMAIL=E2E_EMAIL, TURNIERE_E2E_EMAIL=E2E_EMAIL,
@ -254,7 +256,7 @@ def _docker_build_command(tag, target, platforms=None, push=False):
"--target", "--target",
target, target,
"--build-arg", "--build-arg",
f"GIT_COMMIT_SHA={_git_commit_sha()}", f"base_commit={_git_commit_sha()}",
"-t", "-t",
tag, tag,
"-f", "-f",
@ -272,7 +274,7 @@ def _docker_build_command(tag, target, platforms=None, push=False):
"--target", "--target",
target, target,
"--build-arg", "--build-arg",
f"GIT_COMMIT_SHA={_git_commit_sha()}", f"base_commit={_git_commit_sha()}",
"-t", "-t",
tag, tag,
"-f", "-f",
@ -471,6 +473,7 @@ def _shared_production_env():
SECRET_KEY_BASE=os.environ.get("SECRET_KEY_BASE", BLACKBOX_SECRET_KEY_BASE), SECRET_KEY_BASE=os.environ.get("SECRET_KEY_BASE", BLACKBOX_SECRET_KEY_BASE),
MAILGUN_API_KEY=os.environ.get("MAILGUN_API_KEY", BLACKBOX_MAILGUN_API_KEY), MAILGUN_API_KEY=os.environ.get("MAILGUN_API_KEY", BLACKBOX_MAILGUN_API_KEY),
MAILGUN_DOMAIN=os.environ.get("MAILGUN_DOMAIN", BLACKBOX_MAILGUN_DOMAIN), MAILGUN_DOMAIN=os.environ.get("MAILGUN_DOMAIN", BLACKBOX_MAILGUN_DOMAIN),
TURNIERE_DISABLE_EMAIL_DELIVERY=os.environ.get("TURNIERE_DISABLE_EMAIL_DELIVERY", "1"),
RAILS_LOG_TO_STDOUT=os.environ.get("RAILS_LOG_TO_STDOUT", "1"), RAILS_LOG_TO_STDOUT=os.environ.get("RAILS_LOG_TO_STDOUT", "1"),
RAILS_SERVE_STATIC_FILES=os.environ.get("RAILS_SERVE_STATIC_FILES", "1"), RAILS_SERVE_STATIC_FILES=os.environ.get("RAILS_SERVE_STATIC_FILES", "1"),
) )
@ -485,6 +488,7 @@ def _production_env_for(db_host, db_name):
SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE, SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE,
MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY, MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY,
MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN, MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN,
TURNIERE_DISABLE_EMAIL_DELIVERY="1",
RAILS_LOG_TO_STDOUT="1", RAILS_LOG_TO_STDOUT="1",
RAILS_SERVE_STATIC_FILES="1", RAILS_SERVE_STATIC_FILES="1",
) )
@ -603,7 +607,11 @@ def db_reset(c, env="test"):
@task @task
def server(c, host=SERVER_HOST, port=SERVER_PORT, env=None): def server(c, host=SERVER_HOST, port=SERVER_PORT, env=None):
"""Start the backend locally in development.""" """Start the backend locally in development."""
c.run(f"RAILS_ENV={_rails_env(env)} {_bundle_exec('rails', 's')} -b {host} -p {port}", pty=True) c.run(
f"RAILS_ENV={_rails_env(env)} {_bundle_exec('rails', 's')} -b {host} -p {port}",
env=_env(GIT_COMMIT_SHA=_git_commit_sha()),
pty=True,
)
@task(name="start-local") @task(name="start-local")