diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02802aa..7a52e46 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,6 +55,8 @@ rails e2e spec: alias: postgres - name: $CI_REGISTRY_IMAGE/production/commits:$CI_COMMIT_SHA alias: app + variables: + TURNIERE_DISABLE_EMAIL_DELIVERY: "1" command: - bundle - exec @@ -125,6 +127,7 @@ rails follow sync e2e spec: SECRET_KEY_BASE: turniere-blackbox-secret-key-base-please-change-in-real-prod MAILGUN_API_KEY: blackbox-test-api-key MAILGUN_DOMAIN: blackbox.example.com + TURNIERE_DISABLE_EMAIL_DELIVERY: "1" 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 @@ -148,6 +151,7 @@ rails follow sync e2e spec: SECRET_KEY_BASE: turniere-blackbox-secret-key-base-please-change-in-real-prod MAILGUN_API_KEY: blackbox-test-api-key MAILGUN_DOMAIN: blackbox.example.com + TURNIERE_DISABLE_EMAIL_DELIVERY: "1" 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 diff --git a/.rubocop.yml b/.rubocop.yml index 472e0f0..5a901d6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,9 +1,17 @@ -require: +inherit_from: .rubocop_todo.yml + +inherit_mode: + merge: + - Exclude + +plugins: - rubocop-performance - rubocop-rails # Stolen from https://gist.github.com/jhass/a5ae80d87f18e53e7b56 AllCops: + NewCops: disable + SuggestExtensions: false Exclude: - "bin/**/*" - "db/schema.rb" @@ -13,7 +21,7 @@ Rails: Enabled: true # Commonly used screens these days easily fit more than 80 characters. -Metrics/LineLength: +Layout/LineLength: Max: 120 # Too short methods lead to extraction of single-use methods, which can make @@ -36,7 +44,7 @@ Metrics/AbcSize: Max: 50 Metrics/BlockLength: - ExcludedMethods: + AllowedMethods: - "namespace" - "create_table" Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..de039d0 --- /dev/null +++ b/.rubocop_todo.yml @@ -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' diff --git a/.ruby-version b/.ruby-version index ef538c2..c4e41f9 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.2 +4.0.3 diff --git a/Dockerfile b/Dockerfile index f44458b..e5eda29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1.7 -ARG RUBY_VERSION=3.1.2 -ARG BUNDLER_VERSION=2.3.13 +ARG RUBY_VERSION=4.0.3 +ARG BUNDLER_VERSION=4.0.6 ARG base_commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa FROM ruby:${RUBY_VERSION}-slim AS runtime-base @@ -19,6 +19,7 @@ WORKDIR /app RUN apt-get update -qq \ && apt-get install -y --no-install-recommends \ libpq5 \ + libyaml-0-2 \ nodejs \ openssl \ && rm -rf /var/lib/apt/lists/* \ @@ -33,6 +34,7 @@ RUN apt-get update -qq \ git \ libpq-dev \ libsqlite3-dev \ + libyaml-dev \ pkg-config \ python3 \ python3-pip \ @@ -92,7 +94,7 @@ RUN apt-get update -qq \ python3 \ python3-pip \ && 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 /app/vendor/bundle /app/vendor/bundle diff --git a/Gemfile b/Gemfile index 91dfd63..29fdec4 100644 --- a/Gemfile +++ b/Gemfile @@ -3,12 +3,12 @@ source 'https://rubygems.org' 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' -gem 'rails', '~> 7.0.3' +gem 'rails', '~> 8.1' # 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 # gem 'jbuilder', '~> 2.5' # Use ActiveModel has_secure_password @@ -28,7 +28,7 @@ gem 'bootsnap', require: false # Authentication 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' @@ -59,7 +59,7 @@ group :development, optional: true do gem 'rubocop-rails' gem 'solargraph' gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' + gem 'spring-watcher-listen', '~> 2.1' end group :production do @@ -67,4 +67,4 @@ group :production do end # 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] diff --git a/Gemfile.lock b/Gemfile.lock index 15088d9..b38a9d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 remote: https://rubygems.org/ specs: - actioncable (7.0.8.1) - actionpack (= 7.0.8.1) - activesupport (= 7.0.8.1) + action_text-trix (2.1.18) + railties + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.1) - actionpack (= 7.0.8.1) - activejob (= 7.0.8.1) - activerecord (= 7.0.8.1) - activestorage (= 7.0.8.1) - activesupport (= 7.0.8.1) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.0.8.1) - actionpack (= 7.0.8.1) - actionview (= 7.0.8.1) - activejob (= 7.0.8.1) - activesupport (= 7.0.8.1) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8.1) - actionview (= 7.0.8.1) - activesupport (= 7.0.8.1) - rack (~> 2.0, >= 2.2.4) + zeitwerk (~> 2.6) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) + mail (>= 2.8.0) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.1) - actionpack (= 7.0.8.1) - activerecord (= 7.0.8.1) - activestorage (= 7.0.8.1) - activesupport (= 7.0.8.1) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.1.3) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.1) - activesupport (= 7.0.8.1) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_model_serializers (0.10.14) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + active_model_serializers (0.10.16) actionpack (>= 4.1) activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (7.0.8.1) - activesupport (= 7.0.8.1) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (7.0.8.1) - activesupport (= 7.0.8.1) - activerecord (7.0.8.1) - activemodel (= 7.0.8.1) - activesupport (= 7.0.8.1) - activestorage (7.0.8.1) - actionpack (= 7.0.8.1) - activejob (= 7.0.8.1) - activerecord (= 7.0.8.1) - activesupport (= 7.0.8.1) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) + timeout (>= 0.4.0) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8.1) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (8.1.3) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - ast (2.4.2) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + ast (2.4.3) backport (1.2.0) - bcrypt (3.1.20) - benchmark (0.3.0) - bootsnap (1.18.3) + base64 (0.3.0) + bcrypt (3.1.22) + benchmark (0.5.0) + bigdecimal (4.1.2) + bootsnap (1.24.0) msgpack (~> 1.2) - builder (3.2.4) + builder (3.3.0) case_transform (0.2) activesupport - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) crass (1.0.6) - date (3.3.4) - devise (4.9.3) + date (3.5.1) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.5.1) - domain_name (0.6.20240107) - e2mmap (0.1.0) - erubi (1.12.0) - factory_bot (6.4.6) - activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) - railties (>= 5.0.0) - faker (3.3.1) + devise_token_auth (1.2.6) + bcrypt (~> 3.0) + devise (> 3.5.2, < 5) + rails (>= 4.2.0, < 8.2) + diff-lcs (1.6.2) + drb (2.2.3) + erb (6.0.4) + erubi (1.13.1) + factory_bot (6.5.6) + 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) - ffi (1.16.3) - globalid (1.2.1) + faraday (2.14.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) - http-accept (1.7.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - i18n (1.14.4) + i18n (1.14.8) concurrent-ruby (~> 1.0) - interactor (3.1.2) - interactor-rails (2.2.1) + interactor (3.2.0) + ostruct + interactor-rails (2.3.0) interactor (~> 3.0) - rails (>= 4.2) - jaro_winkler (1.5.6) - json (2.7.2) + railties (>= 7.0) + io-console (0.8.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) - kramdown (2.4.0) - rexml + kramdown (2.5.2) + rexml (>= 3.4.4) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - language_server-protocol (3.17.0.3) - listen (3.9.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.10.0) + logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.22.0) + logger (1.7.0) + loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - mailgun-ruby (1.2.14) - rest-client (>= 2.0.2) - marcel (1.0.4) - method_source (1.0.0) - mime-types (3.5.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2024.0305) + mailgun-ruby (1.4.3) + faraday (~> 2.1) + faraday-multipart (< 2) + mini_mime + zeitwerk + marcel (1.1.0) mini_mime (1.1.5) - minitest (5.22.3) - msgpack (1.7.2) - net-imap (0.4.10) + minitest (6.0.5) + drb (~> 2.0) + 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 net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.5.0) + net-smtp (0.5.1) net-protocol - netrc (0.11.0) - nio4r (2.7.1) - nokogiri (1.16.3-aarch64-linux) + nio4r (2.7.5) + nokogiri (1.19.3-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.16.3-arm64-darwin) + nokogiri (1.19.3-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.16.3-x86_64-linux) + nokogiri (1.19.3-arm64-darwin) 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) - parallel (1.24.0) - parser (3.3.0.5) + ostruct (0.6.3) + parallel (2.1.0) + parser (3.3.11.1) ast (~> 2.4.1) racc - pg (1.5.6) - puma (5.6.8) + pg (1.6.3-aarch64-linux) + 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) - racc (1.7.3) - rack (2.2.9) - rack-cors (2.0.2) - rack (>= 2.0.0) - rack-test (2.1.0) + racc (1.8.1) + rack (3.2.6) + rack-cors (3.0.0) + logger + 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) + rackup (2.3.1) + rack (>= 3) railroady (1.6.0) - rails (7.0.8.1) - actioncable (= 7.0.8.1) - actionmailbox (= 7.0.8.1) - actionmailer (= 7.0.8.1) - actionpack (= 7.0.8.1) - actiontext (= 7.0.8.1) - actionview (= 7.0.8.1) - activejob (= 7.0.8.1) - activemodel (= 7.0.8.1) - activerecord (= 7.0.8.1) - activestorage (= 7.0.8.1) - activesupport (= 7.0.8.1) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 7.0.8.1) - rails-dom-testing (2.2.0) + railties (= 8.1.3) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) - loofah (~> 2.21) - nokogiri (~> 1.14) - railties (7.0.8.1) - actionpack (= 7.0.8.1) - activesupport (= 7.0.8.1) - method_source + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) + 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 (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) + irb (~> 1.13) + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.2.1) + rake (13.4.2) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) - rbs (2.8.4) - regexp_parser (2.9.0) - responders (3.1.1) - actionpack (>= 5.2) - railties (>= 5.2) - rest-client (2.1.0) - http-accept (>= 1.7.0, < 2.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - reverse_markdown (2.1.1) + rbs (3.10.4) + logger + tsort + rdoc (7.2.0) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.12.0) + reline (0.6.3) + io-console (~> 0.5) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) + reverse_markdown (3.0.2) nokogiri - rexml (3.2.6) - rspec-core (3.13.0) + rexml (3.4.4) + rspec-core (3.13.6) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.2) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) - rspec-core (~> 3.13) - rspec-expectations (~> 3.13) - rspec-mocks (~> 3.13) - rspec-support (~> 3.13) - rspec-support (3.13.1) - rubocop (1.62.1) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) + rubocop (1.86.1) json (~> 2.3) - language_server-protocol (>= 3.17.0) - parallel (~> 1.10) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.31.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) - rubocop-performance (1.21.0) - rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.24.1) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.3) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - ruby-prof (1.7.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + ruby-prof (2.0.4) + base64 + ostruct ruby-progressbar (1.13.0) - shoulda-matchers (6.2.0) - activesupport (>= 5.2.0) - solargraph (0.50.0) + securerandom (0.4.1) + shoulda-matchers (7.0.1) + activesupport (>= 7.1) + solargraph (0.58.3) + ast (~> 2.4.3) backport (~> 1.2) - benchmark - bundler (~> 2.0) + benchmark (~> 0.4) + bundler (>= 2.0) diff-lcs (~> 1.4) - e2mmap - jaro_winkler (~> 1.5) + jaro_winkler (~> 1.6, >= 1.6.1) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + open3 (~> 0.2.1) + ostruct (~> 0.6) parser (~> 3.0) - rbs (~> 2.0) - reverse_markdown (~> 2.0) - rubocop (~> 1.38) + prism (~> 1.4) + rbs (>= 3.6.1, <= 4.0.0.dev.4) + reverse_markdown (~> 3.0) + rubocop (~> 1.76) thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) - spring (2.1.1) - spring-watcher-listen (2.0.1) + yard-activesupport-concern (~> 0.0) + yard-solargraph (~> 0.1) + spring (4.4.2) + spring-watcher-listen (2.1.0) listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sqlite3 (1.7.3-aarch64-linux) - sqlite3 (1.7.3-arm64-darwin) - sqlite3 (1.7.3-x86_64-linux) - thor (1.3.1) - tilt (2.3.0) - timeout (0.4.1) + spring (>= 4) + sqlite3 (2.9.3-aarch64-linux-gnu) + sqlite3 (2.9.3-aarch64-linux-musl) + sqlite3 (2.9.3-arm64-darwin) + sqlite3 (2.9.3-x86_64-linux-gnu) + stringio (3.2.0) + thor (1.5.0) + tilt (2.7.0) + timeout (0.6.1) + tsort (0.2.0) tzinfo (2.0.6) 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) rack (>= 2.0.9) - websocket-driver (0.7.6) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - yard (0.9.36) - zeitwerk (2.6.13) + yard (0.9.43) + yard-activesupport-concern (0.0.1) + yard (>= 0.8) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.5) PLATFORMS + aarch64-linux aarch64-linux-musl arm64-darwin-22 arm64-darwin-23 arm64-darwin-24 arm64-darwin-25 x86_64-linux + x86_64-linux-gnu DEPENDENCIES active_model_serializers bootsnap devise - devise_token_auth! + devise_token_auth factory_bot_rails faker interactor @@ -328,10 +409,10 @@ DEPENDENCIES listen (~> 3.3) mailgun-ruby pg - puma (~> 5.0) + puma (~> 8.0) rack-cors railroady - rails (~> 7.0.3) + rails (~> 8.1) rspec-rails rubocop rubocop-performance @@ -340,12 +421,12 @@ DEPENDENCIES shoulda-matchers solargraph spring - spring-watcher-listen (~> 2.0.0) + spring-watcher-listen (~> 2.1) sqlite3 tzinfo-data RUBY VERSION - ruby 3.1.2p20 + ruby 4.0.3 BUNDLED WITH - 2.3.7 + 4.0.6 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b92a017..97043de 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -60,6 +60,11 @@ class ApplicationController < ActionController::API end 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 diff --git a/app/controllers/beamers_controller.rb b/app/controllers/beamers_controller.rb index 5772eec..6c6c867 100644 --- a/app/controllers/beamers_controller.rb +++ b/app/controllers/beamers_controller.rb @@ -36,7 +36,7 @@ class BeamersController < ApplicationController push_sync_if_needed! render json: beamer, status: :created else - render json: beamer.errors, status: :unprocessable_entity + render json: beamer.errors, status: :unprocessable_content end end @@ -46,7 +46,7 @@ class BeamersController < ApplicationController push_sync_if_needed! render json: @beamer else - render json: @beamer.errors, status: :unprocessable_entity + render json: @beamer.errors, status: :unprocessable_content end end diff --git a/app/controllers/bets_controller.rb b/app/controllers/bets_controller.rb index 876bf56..dce34e8 100644 --- a/app/controllers/bets_controller.rb +++ b/app/controllers/bets_controller.rb @@ -25,6 +25,6 @@ class BetsController < ApplicationController end def handle_user_service_error(exception) - render json: { error: exception.message }, status: :unprocessable_entity + render json: { error: exception.message }, status: :unprocessable_content end end diff --git a/app/controllers/match_scores_controller.rb b/app/controllers/match_scores_controller.rb index 07f55bb..502da1f 100644 --- a/app/controllers/match_scores_controller.rb +++ b/app/controllers/match_scores_controller.rb @@ -19,7 +19,7 @@ class MatchScoresController < ApplicationController TournamentLiveBroadcast.call(@match_score.tournament.id) render json: @match_score else - render json: @match_score.errors, status: :unprocessable_entity + render json: @match_score.errors, status: :unprocessable_content end end diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index a2cdb01..ac40b8f 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -26,14 +26,14 @@ class MatchesController < ApplicationController match = build_decider_match if match.errors.any? - render json: match.errors, status: :unprocessable_entity + render json: match.errors, status: :unprocessable_content elsif match.save UpdateGroupsGroupScoresAndSave.call(group: @group) push_sync_if_needed!(@group.tournament) TournamentLiveBroadcast.call(@group.tournament.id) render json: match, include: %w[match_scores.team group], status: :created else - render json: match.errors, status: :unprocessable_entity + render json: match.errors, status: :unprocessable_content end end @@ -55,7 +55,7 @@ class MatchesController < ApplicationController render json: @match synced_tournament = @match.tournament else - render json: @match.errors, status: :unprocessable_entity + render json: @match.errors, status: :unprocessable_content raise ActiveRecord::Rollback end end @@ -66,7 +66,7 @@ class MatchesController < ApplicationController # PATCH /matches/1/swap def swap 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 end @@ -102,27 +102,27 @@ class MatchesController < ApplicationController if @match.winner.nil? render json: { error: 'Stopping undecided Matches isn\'t allowed in playoff stage' }, - status: :unprocessable_entity + status: :unprocessable_content raise ActiveRecord::Rollback end return if PopulateMatchBelowAndSave.call(match: @match).success? render json: { error: 'Moving Team one stage down failed' }, - status: :unprocessable_entity + status: :unprocessable_content raise ActiveRecord::Rollback end def validate_params case match_params['state'] 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? 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? else - render json: { error: 'Invalid target state' }, status: :unprocessable_entity + render json: { error: 'Invalid target state' }, status: :unprocessable_content end end diff --git a/app/controllers/stages_controller.rb b/app/controllers/stages_controller.rb index f6bdf0e..972b159 100644 --- a/app/controllers/stages_controller.rb +++ b/app/controllers/stages_controller.rb @@ -15,7 +15,7 @@ class StagesController < ApplicationController def update if stage_params[:state] == 'finished' 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 end @@ -27,7 +27,7 @@ class StagesController < ApplicationController render json: @stage synced_tournament = @stage.tournament else - render json: @stage.errors, status: :unprocessable_entity + render json: @stage.errors, status: :unprocessable_content raise ActiveRecord::Rollback end end @@ -36,7 +36,7 @@ class StagesController < ApplicationController else render json: { error: 'The state attribute may only be changed to finished' - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -46,14 +46,14 @@ class StagesController < ApplicationController unless @stage.over? render json: { 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 end @stage.groups.each do |group| 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 end @@ -62,14 +62,14 @@ class StagesController < ApplicationController render json: { error: 'Playoffs cannot be generated yet because a decider match is required for an advancing place', blocking_ties: blocking_ties - }, status: :unprocessable_entity + }, status: :unprocessable_content raise ActiveRecord::Rollback end return if AddPlayoffsToTournamentAndSave.call(tournament: @stage.tournament, 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 end diff --git a/app/controllers/team_action_items_controller.rb b/app/controllers/team_action_items_controller.rb index a63fd07..1ea7523 100644 --- a/app/controllers/team_action_items_controller.rb +++ b/app/controllers/team_action_items_controller.rb @@ -15,7 +15,7 @@ class TeamActionItemsController < ApplicationController ) render json: @team_action_item else - render json: @team_action_item.errors, status: :unprocessable_entity + render json: @team_action_item.errors, status: :unprocessable_content end end diff --git a/app/controllers/team_action_lists_controller.rb b/app/controllers/team_action_lists_controller.rb index 9938fa9..e355219 100644 --- a/app/controllers/team_action_lists_controller.rb +++ b/app/controllers/team_action_lists_controller.rb @@ -20,9 +20,9 @@ class TeamActionListsController < ApplicationController render json: team_action_list, status: :created 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 - render json: e.record.errors, status: :unprocessable_entity + render json: e.record.errors, status: :unprocessable_content end # PATCH /tournaments/:tournament_id/team_action_lists/:key/teams/:team_id @@ -33,7 +33,7 @@ class TeamActionListsController < ApplicationController ) render json: @team_action_item else - render json: @team_action_item.errors, status: :unprocessable_entity + render json: @team_action_item.errors, status: :unprocessable_content end end diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index 7b5d7b5..d6e07c4 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -17,7 +17,7 @@ class TeamsController < ApplicationController push_sync_if_needed!(@team.tournament) render json: @team else - render json: @team.errors, status: :unprocessable_entity + render json: @team.errors, status: :unprocessable_content end end diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 9478dc6..11cf0f2 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -67,7 +67,7 @@ class TournamentsController < ApplicationController if tournament.save render json: tournament, status: :created, location: tournament else - render json: tournament.errors, status: :unprocessable_entity + render json: tournament.errors, status: :unprocessable_content end return end @@ -90,14 +90,14 @@ class TournamentsController < ApplicationController end # validate tournament unless tournament.valid? - render json: tournament.errors, status: :unprocessable_entity + render json: tournament.errors, status: :unprocessable_content return end # return appropriate result if result.success? render json: result.tournament, status: :created, location: result.tournament else - render json: { error: 'Tournament generation failed' }, status: :unprocessable_entity + render json: { error: 'Tournament generation failed' }, status: :unprocessable_content end end @@ -116,7 +116,7 @@ class TournamentsController < ApplicationController synced_tournament = @tournament render json: @tournament else - render json: @tournament.errors, status: :unprocessable_entity + render json: @tournament.errors, status: :unprocessable_content raise ActiveRecord::Rollback end end @@ -139,7 +139,7 @@ class TournamentsController < ApplicationController push_sync_if_needed!(@tournament) render json: @tournament else - render json: @tournament.errors, status: :unprocessable_entity + render json: @tournament.errors, status: :unprocessable_content end end @@ -148,14 +148,14 @@ class TournamentsController < ApplicationController TournamentLiveBroadcast.call(@tournament.id) render json: @tournament, include: '**' rescue KeyError, ArgumentError => e - render json: { error: e.message }, status: :unprocessable_entity + render json: { error: e.message }, status: :unprocessable_content end def test_sync unless @tournament.sync_push_enabled? return render json: { error: 'Tournament sync test requires writable tournament with sync_target_url and sync_auth_token' - }, status: :unprocessable_entity + }, status: :unprocessable_content end TournamentSyncPusher.push!(@tournament) @@ -276,7 +276,7 @@ class TournamentsController < ApplicationController teams = params['teams'] 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 def only_playoff_teams_amount_changed @@ -307,14 +307,14 @@ class TournamentsController < ApplicationController render json: { error: 'playoff_teams_amount, instant_finalists_amount and intermediate_round_participants_amount don\'t match' - }, status: :unprocessable_entity + }, status: :unprocessable_content end def sync_state_params params.require(:snapshot).permit!.to_h end 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 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' end - if timer_reason != 'custom' && timer_reason_text.present? - 'timer_reason_text is only allowed when timer_reason is custom' - end + return unless timer_reason != 'custom' && timer_reason_text.present? + + 'timer_reason_text is only allowed when timer_reason is custom' end def render_timer_validation_error(message) - render json: { error: message }, status: :unprocessable_entity + render json: { error: message }, status: :unprocessable_content end end diff --git a/app/models/match.rb b/app/models/match.rb index af4adee..de64aec 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Match < ApplicationRecord - enum state: { single_team: 0, not_ready: 1, not_started: 2, in_progress: 3, finished: 4, - undecided: 5 } + enum :state, { single_team: 0, not_ready: 1, not_started: 2, in_progress: 3, finished: 4, + undecided: 5 } belongs_to :stage, optional: true belongs_to :group, optional: true diff --git a/app/models/stage.rb b/app/models/stage.rb index 3466c8e..fd78498 100644 --- a/app/models/stage.rb +++ b/app/models/stage.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true 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 has_many :matches, dependent: :destroy diff --git a/app/models/tournament.rb b/app/models/tournament.rb index 9decf6c..26653ee 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -7,6 +7,7 @@ class Tournament < ApplicationRecord TIMER_REASONS = %w[tournament_start playoff_start match_end custom].freeze belongs_to :user + belongs_to :owner, class_name: 'User', foreign_key: :user_id has_many :teams, dependent: :destroy has_many :stages, dependent: :destroy has_many :beamers, dependent: :destroy @@ -22,8 +23,6 @@ class Tournament < ApplicationRecord validate :playoff_teams_amount_is_positive_power_of_two validate :timer_reason_text_matches_reason - alias_attribute :owner, :user - after_initialize :generate_code after_create_commit :ensure_default_beamer! before_validation :normalize_timer_reason diff --git a/config/application.rb b/config/application.rb index bd16c7a..e6e619b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -50,7 +50,5 @@ module TurniereBackend from: 'noreply@turnie.re' } - # Active Record - config.active_record.legacy_connection_handling = false end end diff --git a/config/environments/production.rb b/config/environments/production.rb index 3c74f73..360a4b1 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -79,12 +79,16 @@ Rails.application.configure do # Mail config.action_mailer.raise_delivery_errors = true config.action_mailer.default_url_options = { host: 'api.turnie.re' } - config.action_mailer.delivery_method = :mailgun - 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 - } + if ENV['TURNIERE_DISABLE_EMAIL_DELIVERY'].present? + config.action_mailer.perform_deliveries = false + else + config.action_mailer.delivery_method = :mailgun + 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) config.logger = Logger.new(STDOUT) diff --git a/config/initializers/devise_routes_rails_8_compat.rb b/config/initializers/devise_routes_rails_8_compat.rb new file mode 100644 index 0000000..d460935 --- /dev/null +++ b/config/initializers/devise_routes_rails_8_compat.rb @@ -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) diff --git a/config/initializers/devise_token_auth.rb b/config/initializers/devise_token_auth.rb index bb64943..f0997ce 100644 --- a/config/initializers/devise_token_auth.rb +++ b/config/initializers/devise_token_auth.rb @@ -48,5 +48,6 @@ DeviseTokenAuth.setup do |config| # do so by enabling this flag. NOTE: This feature is highly experimental! # config.enable_standard_devise_support = false + config.bypass_sign_in = false config.default_confirm_success_url = 'https://turnie.re' end diff --git a/db/migrate/20260428120000_add_timer_reason_to_tournaments.rb b/db/migrate/20260428120000_add_timer_reason_to_tournaments.rb index 54cb911..f28bafe 100644 --- a/db/migrate/20260428120000_add_timer_reason_to_tournaments.rb +++ b/db/migrate/20260428120000_add_timer_reason_to_tournaments.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AddTimerReasonToTournaments < ActiveRecord::Migration[7.0] def change add_column :tournaments, :timer_reason, :string diff --git a/db/schema.rb b/db/schema.rb index 9ebcac7..85ae707 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,96 +10,96 @@ # # 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| - 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.boolean "is_default", default: false - t.integer "tournament_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "name", null: false 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: "index_beamers_on_tournament_id" end 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 "team_id" - t.datetime "created_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 ["team_id"], name: "index_bets_on_team_id" t.index ["user_id"], name: "index_bets_on_user_id" end create_table "group_scores", force: :cascade do |t| - t.integer "group_points", default: 0 - t.integer "scored_points", default: 0 - t.integer "received_points", default: 0 - t.integer "team_id", null: false - t.integer "group_id", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.integer "group_id", null: false + t.integer "group_points", 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 "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 ["team_id"], name: "index_group_scores_on_team_id" end create_table "groups", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false t.integer "number" t.integer "stage_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.integer "sync_source_id" + t.datetime "updated_at", precision: nil, null: false t.index ["stage_id"], name: "index_groups_on_stage_id" end create_table "match_scores", force: :cascade do |t| - t.integer "points", default: 0 - t.integer "match_id", null: false - t.integer "team_id", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.integer "hidden_points", default: 0, null: false + t.integer "match_id", null: false + t.integer "points", default: 0 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 ["team_id"], name: "index_match_scores_on_team_id" end 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 "stage_id" - t.integer "group_id" - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.boolean "decider", default: false, null: false + t.integer "state", default: 0 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 ["stage_id"], name: "index_matches_on_stage_id" end create_table "stages", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false t.integer "level" t.integer "state", default: 0 - t.integer "tournament_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.integer "sync_source_id" + t.integer "tournament_id", null: false + t.datetime "updated_at", precision: nil, null: false t.index ["tournament_id"], name: "index_stages_on_tournament_id" end create_table "team_action_items", force: :cascade do |t| - t.integer "team_action_list_id", null: false - t.integer "team_id", null: false t.boolean "completed", default: false, null: false t.datetime "completed_at" - t.integer "sync_source_id" t.datetime "created_at", null: false + t.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.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 @@ -108,13 +108,13 @@ ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do end create_table "team_action_lists", force: :cascade do |t| - t.integer "tournament_id", null: false - t.string "name", null: false - t.string "key", null: false t.string "action_name", null: false + t.datetime "created_at", null: false + t.string "key", null: false + t.string "name", null: false t.string "source", null: false t.integer "sync_source_id" - t.datetime "created_at", null: false + t.integer "tournament_id", null: false t.datetime "updated_at", null: false t.index ["sync_source_id"], name: "index_team_action_lists_on_sync_source_id" t.index ["tournament_id", "key"], name: "index_team_action_lists_on_tournament_id_and_key", unique: true @@ -122,25 +122,25 @@ ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do end create_table "teams", force: :cascade do |t| - t.string "name" - t.integer "tournament_id" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.string "name" 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" end create_table "tournament_sync_queue_entries", force: :cascade do |t| - t.integer "tournament_id", null: false - t.text "snapshot_json", null: false - t.datetime "next_attempt_at", null: false - t.datetime "last_attempt_at" - t.datetime "locked_at" - t.string "lock_token" t.integer "attempts", default: 0, null: false - t.string "status", default: "pending", null: false - t.string "last_error" t.datetime "created_at", null: false + t.datetime "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.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" @@ -148,53 +148,53 @@ ActiveRecord::Schema[7.0].define(version: 2026_04_28_120000) do end create_table "tournaments", force: :cascade do |t| - t.string "name", null: false t.string "code", null: false + t.datetime "created_at", precision: nil, null: false t.string "description" - t.boolean "public", default: true - t.integer "playoff_teams_amount", default: 0 t.integer "instant_finalists_amount", default: 0 t.integer "intermediate_round_participants_amount", default: 0 - t.integer "user_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.datetime "timestamp" + t.string "name", null: false + t.integer "playoff_teams_amount", default: 0 + t.boolean "public", default: true t.boolean "read_only_mode", default: false, null: false - t.string "sync_target_url" t.string "sync_auth_token" - t.integer "sync_source_tournament_id" - t.datetime "sync_last_pushed_at" - t.string "sync_last_push_error" t.datetime "sync_last_imported_snapshot_at" + t.string "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.datetime "timestamp" t.string "timer_reason" 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 ["user_id"], name: "index_tournaments_on_user_id" end create_table "users", force: :cascade do |t| - t.string "provider", default: "email", null: false - t.string "uid", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil t.boolean "allow_password_change", default: false - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0, null: false - t.datetime "current_sign_in_at", precision: nil - t.datetime "last_sign_in_at", precision: nil - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" + t.datetime "confirmation_sent_at", precision: nil t.string "confirmation_token" t.datetime "confirmed_at", precision: nil - t.datetime "confirmation_sent_at", precision: nil - t.string "unconfirmed_email" - t.string "username" - t.string "email" - t.text "tokens" t.datetime "created_at", precision: nil, null: false + t.datetime "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.string "username" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["provider"], name: "index_users_on_provider" diff --git a/doc/rails_8_dependency_update.md b/doc/rails_8_dependency_update.md new file mode 100644 index 0000000..d67bc1f --- /dev/null +++ b/doc/rails_8_dependency_update.md @@ -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. diff --git a/docker-compose.blackbox-follow.yml b/docker-compose.blackbox-follow.yml index cfb49d9..a192dc1 100644 --- a/docker-compose.blackbox-follow.yml +++ b/docker-compose.blackbox-follow.yml @@ -43,6 +43,7 @@ services: 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_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} RAILS_LOG_TO_STDOUT: "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} MAILGUN_API_KEY: ${TURNIERE_BLACKBOX_MAILGUN_API_KEY:-blackbox-test-api-key} 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} RAILS_LOG_TO_STDOUT: "1" RAILS_SERVE_STATIC_FILES: "1" diff --git a/docker-compose.blackbox.yml b/docker-compose.blackbox.yml index fae52d2..15e5d3b 100644 --- a/docker-compose.blackbox.yml +++ b/docker-compose.blackbox.yml @@ -25,6 +25,7 @@ services: 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_DOMAIN: ${TURNIERE_BLACKBOX_MAILGUN_DOMAIN:-blackbox.example.com} + TURNIERE_DISABLE_EMAIL_DELIVERY: ${TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY:-1} RAILS_LOG_TO_STDOUT: "1" RAILS_SERVE_STATIC_FILES: "1" ports: diff --git a/spec/auth_helpers.rb b/spec/auth_helpers.rb index ea55d1f..e564cce 100644 --- a/spec/auth_helpers.rb +++ b/spec/auth_helpers.rb @@ -4,5 +4,9 @@ module AuthHelpers def apply_authentication_headers_for(user) user_headers = user.create_new_auth_token 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 diff --git a/spec/controllers/beamers_controller_spec.rb b/spec/controllers/beamers_controller_spec.rb index 3a4fb46..5b58f0a 100644 --- a/spec/controllers/beamers_controller_spec.rb +++ b/spec/controllers/beamers_controller_spec.rb @@ -95,14 +95,14 @@ RSpec.describe BeamersController, type: :controller do post :create, params: { 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 it 'rejects duplicate name in same tournament' do post :create, params: { 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 @@ -157,7 +157,7 @@ RSpec.describe BeamersController, type: :controller do tournament_id: @tournament.to_param, id: @beamer.to_param, display_state: 'bogus' } - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end end diff --git a/spec/controllers/match_scores_controller_spec.rb b/spec/controllers/match_scores_controller_spec.rb index f9cec18..4a23db8 100644 --- a/spec/controllers/match_scores_controller_spec.rb +++ b/spec/controllers/match_scores_controller_spec.rb @@ -62,7 +62,7 @@ RSpec.describe MatchScoresController, type: :controller do it 'returns unprocessable entity' do 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 diff --git a/spec/controllers/matches_controller_spec.rb b/spec/controllers/matches_controller_spec.rb index 099f5b0..96253c2 100644 --- a/spec/controllers/matches_controller_spec.rb +++ b/spec/controllers/matches_controller_spec.rb @@ -120,7 +120,7 @@ RSpec.describe MatchesController, type: :controller do it 'rejects the request' do 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 @@ -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 } - 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') end @@ -288,7 +288,7 @@ RSpec.describe MatchesController, type: :controller do end it 'returns an unprocessable entity response' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'doesn\'t change the matches status' do @@ -321,7 +321,7 @@ RSpec.describe MatchesController, type: :controller do it 'returns unprocessable entity' do 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 @@ -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) 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 @@ -375,14 +375,14 @@ RSpec.describe MatchesController, type: :controller do context 'with invalid params' do it 'renders an unprocessable entity response' do 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 context 'with senseless params' do it 'renders an unprocessable entity response' do 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 @@ -416,7 +416,7 @@ RSpec.describe MatchesController, type: :controller do context 'with invalid params' do it 'renders an unprocessable entity response' do 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 diff --git a/spec/controllers/stages_controller_spec.rb b/spec/controllers/stages_controller_spec.rb index 9311ea7..bd7ba8c 100644 --- a/spec/controllers/stages_controller_spec.rb +++ b/spec/controllers/stages_controller_spec.rb @@ -122,7 +122,7 @@ RSpec.describe StagesController, type: :controller do end it 'it returns unprocessable entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'returns the correct error' do @@ -145,7 +145,7 @@ RSpec.describe StagesController, type: :controller do end it 'it returns unprocessable entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'returns the correct error' do @@ -164,7 +164,7 @@ RSpec.describe StagesController, type: :controller do end it 'it returns unprocessable entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'returns the correct error' do @@ -197,7 +197,7 @@ RSpec.describe StagesController, type: :controller do end it 'returns unprocessable entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'returns a helpful decider error' do @@ -233,7 +233,7 @@ RSpec.describe StagesController, type: :controller do end it 'returns unprocessable entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'returns a helpful decider error' do diff --git a/spec/controllers/team_action_lists_controller_spec.rb b/spec/controllers/team_action_lists_controller_spec.rb index 672d321..9a82319 100644 --- a/spec/controllers/team_action_lists_controller_spec.rb +++ b/spec/controllers/team_action_lists_controller_spec.rb @@ -47,7 +47,7 @@ RSpec.describe TeamActionListsController, type: :controller do it 'returns unprocessable entity for unsupported source' do 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') end @@ -57,7 +57,7 @@ RSpec.describe TeamActionListsController, type: :controller do 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') end @@ -71,7 +71,7 @@ RSpec.describe TeamActionListsController, type: :controller do post :create, params: create_params - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end end diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 4777793..89c32e5 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -308,21 +308,21 @@ RSpec.describe TournamentsController, type: :controller do it 'rejects timestamp_seconds for countup mode' do 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') end it 'rejects invalid timer reason values' do 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') end it 'requires timer_reason_text for custom timer reason' do 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') end @@ -334,7 +334,7 @@ RSpec.describe TournamentsController, type: :controller do 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( '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 shared_examples_for 'wrong playoff_teams_amount' do it 'fails' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'returns the correct error message' do expect(deserialize_response(response)[:playoff_teams_amount].first) @@ -522,7 +522,7 @@ RSpec.describe TournamentsController, type: :controller do data = create_group_tournament_data data[:teams].pop post :create, params: data - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end end @@ -540,7 +540,7 @@ RSpec.describe TournamentsController, type: :controller do context 'with invalid parameters' do it 'renders an unprocessable entity response' do 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 @@ -549,7 +549,7 @@ RSpec.describe TournamentsController, type: :controller do data = create_group_tournament_data data[:teams] = [{ group: 1 }, { group: 1 }, { group: 2 }, { group: 2 }] post :create, params: data - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end end end @@ -637,18 +637,18 @@ RSpec.describe TournamentsController, type: :controller do it 'fails when only instant_finalists_amount is changed' do 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 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) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'fails when parameters don\'t match' do put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29, instant_finalists_amount: 32) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) end it 'succeeds when all three are changed correctly' do @@ -667,7 +667,7 @@ RSpec.describe TournamentsController, type: :controller do it 'returns unprocessable entity' do 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 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 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 @@ -863,7 +863,7 @@ RSpec.describe TournamentsController, type: :controller do it 'rejects sync test without complete leader configuration' do 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]) .to eq('Tournament sync test requires writable tournament with sync_target_url and sync_auth_token') end diff --git a/spec/e2e/http/api_surface_spec.rb b/spec/e2e/http/api_surface_spec.rb index 61301d5..b63963c 100644 --- a/spec/e2e/http/api_surface_spec.rb +++ b/spec/e2e/http/api_surface_spec.rb @@ -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) - expect([500, *200..299]).to include(registration[:status]) + expect(registration[:status]).to be_between(200, 299) expect(registration[:json]).not_to eq({}) expect(login[:status]).to eq(401) end @@ -154,9 +154,9 @@ RSpec.describe 'Backend API surface HTTP E2E' do timer_reason: 'ceremony' }) timer_custom_without_text = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { - timestamp_seconds: 120, - timer_reason: 'custom' - }) + timestamp_seconds: 120, + timer_reason: 'custom' + }) timer_invalid_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/timer", body: { timestamp_seconds: 'nope' }) diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 9667dc8..e43b2b5 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -5,5 +5,6 @@ FactoryBot.define do sequence(:username) { |n| "user#{n}" } sequence(:email) { |n| "user#{n}@example.com" } password { 'password123' } + confirmed_at { Time.current } end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 15c095d..b8e1dc6 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -36,7 +36,7 @@ rescue ActiveRecord::PendingMigrationError => e end RSpec.configure do |config| # 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 # TODO REVERT ME diff --git a/spec/services/tournament_snapshot_importer_spec.rb b/spec/services/tournament_snapshot_importer_spec.rb index 14f3795..b86e6fe 100644 --- a/spec/services/tournament_snapshot_importer_spec.rb +++ b/spec/services/tournament_snapshot_importer_spec.rb @@ -80,6 +80,7 @@ RSpec.describe TournamentSnapshotImporter do end end + # rubocop:disable Metrics/MethodLength def create_large_source_tournament! tournament = create( :tournament, @@ -142,6 +143,7 @@ RSpec.describe TournamentSnapshotImporter do ) tournament.reload end + # rubocop:enable Metrics/MethodLength def create_group_round_robin!(group:, teams:) pairings = [ @@ -190,6 +192,7 @@ RSpec.describe TournamentSnapshotImporter do end end + # rubocop:disable Metrics/MethodLength def sync_signature(tournament) { name: tournament.name, @@ -244,6 +247,7 @@ RSpec.describe TournamentSnapshotImporter do end } end + # rubocop:enable Metrics/MethodLength def match_signature(match) { diff --git a/tasks.py b/tasks.py index 1dc45bd..299b6a3 100644 --- a/tasks.py +++ b/tasks.py @@ -189,6 +189,7 @@ def _compose_env( TURNIERE_BLACKBOX_SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE, TURNIERE_BLACKBOX_MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY, TURNIERE_BLACKBOX_MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN, + TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY="1", TURNIERE_E2E_BASE_URL=BLACKBOX_INTERNAL_BASE_URL, TURNIERE_E2E_EXPECT_PROFILING=expect_profiling, TURNIERE_E2E_EMAIL=E2E_EMAIL, @@ -220,6 +221,7 @@ def _follow_compose_env( TURNIERE_BLACKBOX_SECRET_KEY_BASE=BLACKBOX_SECRET_KEY_BASE, TURNIERE_BLACKBOX_MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY, TURNIERE_BLACKBOX_MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN, + TURNIERE_BLACKBOX_DISABLE_EMAIL_DELIVERY="1", TURNIERE_SOURCE_E2E_BASE_URL=BLACKBOX_SOURCE_INTERNAL_BASE_URL, TURNIERE_FOLLOWER_E2E_BASE_URL=BLACKBOX_FOLLOWER_INTERNAL_BASE_URL, TURNIERE_E2E_EMAIL=E2E_EMAIL, @@ -254,7 +256,7 @@ def _docker_build_command(tag, target, platforms=None, push=False): "--target", target, "--build-arg", - f"GIT_COMMIT_SHA={_git_commit_sha()}", + f"base_commit={_git_commit_sha()}", "-t", tag, "-f", @@ -272,7 +274,7 @@ def _docker_build_command(tag, target, platforms=None, push=False): "--target", target, "--build-arg", - f"GIT_COMMIT_SHA={_git_commit_sha()}", + f"base_commit={_git_commit_sha()}", "-t", tag, "-f", @@ -471,6 +473,7 @@ def _shared_production_env(): 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_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_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, MAILGUN_API_KEY=BLACKBOX_MAILGUN_API_KEY, MAILGUN_DOMAIN=BLACKBOX_MAILGUN_DOMAIN, + TURNIERE_DISABLE_EMAIL_DELIVERY="1", RAILS_LOG_TO_STDOUT="1", RAILS_SERVE_STATIC_FILES="1", ) @@ -603,7 +607,11 @@ def db_reset(c, env="test"): @task def server(c, host=SERVER_HOST, port=SERVER_PORT, env=None): """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")