1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-03-25 01:14:43 +00:00

38 Commits

Author SHA1 Message Date
d359be3ea1 Fixed deprecations 2026-02-13 18:02:04 -05:00
e97aa0d680 Fixed N+1 on up_matches and added html cache. 2026-02-13 18:02:04 -05:00
ae8d995b2c Added a QR code page that generates a QR code for tournament directors to print out. 2026-02-11 18:23:14 -05:00
d57aaac09d Hide ads on schools#show, wrestlers#new, wrestlers#edit, and mats#show 2026-02-11 07:55:49 -05:00
fcc8a9b9a9 Updated to ruby 4.0.1 2026-02-10 17:58:22 -05:00
b51866e9d8 Added tests for hiding ads on lineup submission. 2026-02-09 18:36:56 -05:00
07d43e7720 Hide ads for coaches when submitting lineups 2026-02-08 18:59:42 -05:00
d8b6cfa8ac Added bundle audit to pipeline. 2026-02-05 18:40:38 -05:00
5d674f894f Added round number and bracket position under the bout number 2026-02-04 18:16:19 -05:00
25df2a7280 Updated to rails 8.1.2. 2026-02-04 18:16:19 -05:00
2767274066 Added queues for mats and provided a way for tournament directors to move matches to a mat. 2026-02-03 17:50:52 -05:00
a2f8c7bced Stats page should auto push stats when it reconnects to the websocket. Spectate page should auto pull when it reconnects to the websocket. 2026-01-29 17:28:14 -05:00
9c2a9d62ad Fixed random seeding for double elimination. Since bracket positions are already evenly distributed on top half and bottom half of the bracket, I only need to pick odd or even bracket line numbers. 2026-01-23 17:35:16 -05:00
556090c16b Fixed double elimination generate loser names for a 6 man bracket when we're placing top 8 2026-01-23 17:35:16 -05:00
86f9c03991 Fixed double elim match generation errors and added tests 2026-01-23 17:35:16 -05:00
c8764c149b Added back tournament import text for the development environment 2026-01-23 17:35:16 -05:00
fe9a9c628c Fix arguements for the tournament backup and import jobs 2026-01-22 16:59:44 -05:00
7e4b6d8fc8 Fix round 1 bracket name when the first round of the bracket is not the first round of the tournament 2026-01-19 23:25:15 -05:00
940f7b1d00 Job concurrency per tournament is 1 so we don't have to scale too much on active queue. Pages no longer refresh automatically after navigating away from the bout board. Tournament backups are no longer deleted when restoring from a backup. Cloudflare is blocking manual backup imports so I have deleted that form on the backups page. 2026-01-16 18:21:17 -05:00
52df73d14f Fixed random double elimination seeding to avoid double byes in round 1 and evenly distribute the number of round 1 matches from the top and bottom half of the bracket 2026-01-14 19:00:35 -05:00
8b03a74b1e Fixed the save seeds button on weights#show to work on mobile. Fixed the trashcan and edit icons on tournaments#show schools#show and weights#show to work on mobile. Destroy all tournament backups on tournament cleanup. Added bracket position to bout board. 2026-01-13 17:02:59 -05:00
b4bca8f10a Fixed calculate team scores button, fixed import button, fixed deleting a mat causing match deletes 2026-01-10 23:39:23 -05:00
af1f8df4b6 Fix print views 2026-01-09 23:06:24 -05:00
3576445a1c Added a turbo stream for the current and next match on mat stats page. 2026-01-09 18:37:01 -05:00
8c2ddf55ed Increased solid queue arguments limit to support tournament backups 2026-01-09 00:49:32 -05:00
cfd3e7aecd Fixed create new backup link syntax for turbo_method and made the assign_next_match button a turbo_method 2026-01-08 23:59:33 -05:00
608999cb51 Fixed create new backup link as a turbo_method and hid the baumspage importer 2026-01-08 23:51:16 -05:00
6b5308360e Fixed a bug where logged in users could not access a school with a school permission key 2026-01-06 17:24:45 -05:00
9ca6572d9b Need to bring services down before bringing them back up on deploy test 2025-12-11 14:17:27 -05:00
61dc5e3cdd Added mission control for solid queue ui. 2025-11-21 15:43:05 +05:30
af2fc3feba Fixed a test after changing links to turbo 2025-11-11 21:55:36 -05:00
793a9e3ecc All links with a confirm now use turbo 2025-11-11 21:09:24 -05:00
f73e9bfc4e Fix the reset bout board link 2025-11-11 20:55:34 -05:00
92bd06fe3c No longer using memcached. Replication settings for standalong mariadb. Use --single-transaction in mariadb replica-watcher so mysqldump does not lock tables. Added horizontal pod autoscaler to the app statefulset 2025-10-30 08:50:31 -04:00
6e9554be55 Fixed the JSON 3 deprecation in the backup and import service 2025-10-08 13:54:38 -04:00
34f1783031 Upgraded to rails 8.0.2 2025-10-08 11:35:44 -04:00
bbd2bd9b44 Fixed ads.txt 2025-10-07 15:31:04 -04:00
6ecebba70d Updated gems 2025-10-07 15:30:47 -04:00
104 changed files with 2465 additions and 694 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1 +1 @@
wrestlingdev wrestlingdev

View File

@@ -1 +1 @@
ruby-3.2.0 ruby-4.0.1

14
Gemfile
View File

@@ -1,8 +1,8 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '3.2.0' ruby '4.0.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '8.0.2' gem 'rails', '8.1.2'
# Added in rails 7.1 # Added in rails 7.1
gem 'rails-html-sanitizer' gem 'rails-html-sanitizer'
@@ -67,6 +67,7 @@ gem 'influxdb-rails'
gem 'cancancan' gem 'cancancan'
gem 'round_robin_tournament' gem 'round_robin_tournament'
gem 'rb-readline' gem 'rb-readline'
gem 'rqrcode'
# Replacing Delayed Job with Solid Queue # Replacing Delayed Job with Solid Queue
# gem 'delayed_job_active_record' # gem 'delayed_job_active_record'
gem 'solid_queue' gem 'solid_queue'
@@ -74,11 +75,9 @@ gem 'solid_cable'
gem 'puma' gem 'puma'
gem 'tzinfo-data' gem 'tzinfo-data'
gem 'daemons' gem 'daemons'
# Interface for viewing and managing background jobs # Solid Queue UI
# gem 'delayed_job_web' gem "mission_control-jobs"
# Note: solid_queue-ui is not compatible with Rails 8.0 yet
# We'll create a custom UI or wait for compatibility updates
# gem 'solid_queue_ui', '~> 0.1.1'
group :development do group :development do
# gem 'rubocop' # gem 'rubocop'
@@ -93,4 +92,3 @@ group :development, :test do
# rails-controller-testing is needed for assert_template # rails-controller-testing is needed for assert_template
gem 'rails-controller-testing' gem 'rails-controller-testing'
end end

View File

@@ -1,29 +1,31 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.2) action_text-trix (2.1.16)
actionpack (= 8.0.2) railties
activesupport (= 8.0.2) actioncable (8.1.2)
actionpack (= 8.1.2)
activesupport (= 8.1.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.0.2) actionmailbox (8.1.2)
actionpack (= 8.0.2) actionpack (= 8.1.2)
activejob (= 8.0.2) activejob (= 8.1.2)
activerecord (= 8.0.2) activerecord (= 8.1.2)
activestorage (= 8.0.2) activestorage (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.2) actionmailer (8.1.2)
actionpack (= 8.0.2) actionpack (= 8.1.2)
actionview (= 8.0.2) actionview (= 8.1.2)
activejob (= 8.0.2) activejob (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.0.2) actionpack (8.1.2)
actionview (= 8.0.2) actionview (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@@ -31,81 +33,83 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.0.2) actiontext (8.1.2)
actionpack (= 8.0.2) action_text-trix (~> 2.1.15)
activerecord (= 8.0.2) actionpack (= 8.1.2)
activestorage (= 8.0.2) activerecord (= 8.1.2)
activesupport (= 8.0.2) activestorage (= 8.1.2)
activesupport (= 8.1.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.2) actionview (8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.0.2) activejob (8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.2) activemodel (8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
activerecord (8.0.2) activerecord (8.1.2)
activemodel (= 8.0.2) activemodel (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.2) activestorage (8.1.2)
actionpack (= 8.0.2) actionpack (= 8.1.2)
activejob (= 8.0.2) activejob (= 8.1.2)
activerecord (= 8.0.2) activerecord (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.2) activesupport (8.1.2)
base64 base64
benchmark (>= 0.3)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
json
logger (>= 1.4.2) logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1) uri (>= 0.13.1)
ast (2.4.3) ast (2.4.3)
base64 (0.2.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.21)
benchmark (0.4.0) bigdecimal (4.0.1)
bigdecimal (3.1.9) bootsnap (1.23.0)
bootsnap (1.18.6)
msgpack (~> 1.2) msgpack (~> 1.2)
brakeman (7.0.2) brakeman (8.0.2)
racc racc
builder (3.3.0) builder (3.3.0)
bullet (8.0.7) bullet (8.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11) uniform_notifier (~> 1.11)
bundler-audit (0.9.2) bundler-audit (0.9.3)
bundler (>= 1.2.0, < 3) bundler (>= 1.2.0)
thor (~> 1.0) thor (~> 1.0)
cancancan (3.6.1) cancancan (3.6.1)
concurrent-ruby (1.3.5) chunky_png (1.4.0)
connection_pool (2.5.3) concurrent-ruby (1.3.6)
connection_pool (3.0.2)
crass (1.0.6) crass (1.0.6)
daemons (1.4.1) daemons (1.4.1)
date (3.4.1) date (3.5.1)
drb (2.2.1) drb (2.2.3)
erb (6.0.1)
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.2.11) et-orbi (1.4.0)
tzinfo tzinfo
fugit (1.11.1) fugit (1.12.1)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1.4)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.2.1) globalid (1.3.0)
activesupport (>= 6.1) activesupport (>= 6.1)
i18n (1.14.7) i18n (1.14.8)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
importmap-rails (2.1.0) importmap-rails (2.2.3)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
@@ -113,38 +117,52 @@ GEM
influxdb-rails (1.0.3) influxdb-rails (1.0.3)
influxdb (~> 0.6, >= 0.6.4) influxdb (~> 0.6, >= 0.6.4)
railties (>= 5.0) railties (>= 5.0)
io-console (0.8.0) io-console (0.8.2)
irb (1.15.2) irb (1.17.0)
pp (>= 0.6.0) pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jbuilder (2.13.0) jbuilder (2.14.1)
actionview (>= 5.0.0) actionview (>= 7.0.0)
activesupport (>= 5.0.0) activesupport (>= 7.0.0)
jquery-rails (4.6.0) jquery-rails (4.6.1)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (2.12.2) json (2.18.1)
language_server-protocol (3.17.0.5) language_server-protocol (3.17.0.5)
lint_roller (1.1.0) lint_roller (1.1.0)
logger (1.7.0) logger (1.7.0)
loofah (2.24.1) loofah (2.25.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.9.0)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
marcel (1.0.4) marcel (1.1.0)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.5) minitest (6.0.1)
mocha (2.7.1) prism (~> 1.5)
mission_control-jobs (1.1.0)
actioncable (>= 7.1)
actionpack (>= 7.1)
activejob (>= 7.1)
activerecord (>= 7.1)
importmap-rails (>= 1.2.1)
irb (~> 1.13)
railties (>= 7.1)
stimulus-rails
turbo-rails
mocha (3.0.2)
ruby2_keywords (>= 0.0.5) ruby2_keywords (>= 0.0.5)
msgpack (1.8.0) msgpack (1.8.0)
mysql2 (0.5.6) mysql2 (0.5.7)
net-imap (0.5.8) bigdecimal
net-imap (0.6.3)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@@ -153,70 +171,69 @@ GEM
timeout timeout
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.5)
nokogiri (1.18.8-aarch64-linux-gnu) nokogiri (1.19.0-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-aarch64-linux-musl) nokogiri (1.19.0-aarch64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-arm-linux-gnu) nokogiri (1.19.0-arm-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-arm-linux-musl) nokogiri (1.19.0-arm-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-arm64-darwin) nokogiri (1.19.0-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-x86_64-darwin) nokogiri (1.19.0-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu) nokogiri (1.19.0-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-musl) nokogiri (1.19.0-x86_64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
parallel (1.27.0) parallel (1.27.0)
parser (3.3.8.0) parser (3.3.10.1)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pp (0.6.2) pp (0.6.3)
prettyprint prettyprint
prettyprint (0.2.0) prettyprint (0.2.0)
prism (1.4.0) prism (1.9.0)
propshaft (1.1.0) propshaft (1.3.1)
actionpack (>= 7.0.0) actionpack (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
rack rack
railties (>= 7.0.0) psych (5.3.1)
psych (5.2.6)
date date
stringio stringio
puma (6.6.0) puma (7.2.0)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rack (3.1.14) rack (3.2.4)
rack-session (2.1.1) rack-session (2.1.1)
base64 (>= 0.1.0) base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.2.0) rack-test (2.2.0)
rack (>= 1.3) rack (>= 1.3)
rackup (2.2.1) rackup (2.3.1)
rack (>= 3) rack (>= 3)
rails (8.0.2) rails (8.1.2)
actioncable (= 8.0.2) actioncable (= 8.1.2)
actionmailbox (= 8.0.2) actionmailbox (= 8.1.2)
actionmailer (= 8.0.2) actionmailer (= 8.1.2)
actionpack (= 8.0.2) actionpack (= 8.1.2)
actiontext (= 8.0.2) actiontext (= 8.1.2)
actionview (= 8.0.2) actionview (= 8.1.2)
activejob (= 8.0.2) activejob (= 8.1.2)
activemodel (= 8.0.2) activemodel (= 8.1.2)
activerecord (= 8.0.2) activerecord (= 8.1.2)
activestorage (= 8.0.2) activestorage (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.2) railties (= 8.1.2)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
@@ -228,24 +245,31 @@ GEM
rails_stdout_logging rails_stdout_logging
rails_serve_static_assets (0.0.5) rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5) rails_stdout_logging (0.0.5)
railties (8.0.2) railties (8.1.2)
actionpack (= 8.0.2) actionpack (= 8.1.2)
activesupport (= 8.0.2) activesupport (= 8.1.2)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rake (13.3.1)
rb-readline (0.5.5) rb-readline (0.5.5)
rdoc (6.13.1) rdoc (7.2.0)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
regexp_parser (2.10.0) tsort
reline (0.6.1) regexp_parser (2.11.3)
reline (0.6.3)
io-console (~> 0.5) io-console (~> 0.5)
round_robin_tournament (0.1.2) round_robin_tournament (0.1.2)
rubocop (1.76.0) rqrcode (3.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.1.0)
rubocop (1.84.2)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
@@ -253,65 +277,66 @@ GEM
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.45.0, < 2.0) rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.45.0) rubocop-ast (1.49.0)
parser (>= 3.3.7.2) parser (>= 3.3.7.2)
prism (~> 1.4) prism (~> 1.7)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
sdoc (2.6.1) sdoc (2.6.5)
rdoc (>= 5.0) rdoc (>= 5.0)
securerandom (0.4.1) securerandom (0.4.1)
solid_cable (3.0.8) solid_cable (3.0.12)
actioncable (>= 7.2) actioncable (>= 7.2)
activejob (>= 7.2) activejob (>= 7.2)
activerecord (>= 7.2) activerecord (>= 7.2)
railties (>= 7.2) railties (>= 7.2)
solid_cache (1.0.7) solid_cache (1.0.10)
activejob (>= 7.2) activejob (>= 7.2)
activerecord (>= 7.2) activerecord (>= 7.2)
railties (>= 7.2) railties (>= 7.2)
solid_queue (1.1.5) solid_queue (1.3.1)
activejob (>= 7.1) activejob (>= 7.1)
activerecord (>= 7.1) activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1) concurrent-ruby (>= 1.3.1)
fugit (~> 1.11.0) fugit (~> 1.11)
railties (>= 7.1) railties (>= 7.1)
thor (~> 1.3.1) thor (>= 1.3.1)
spring (4.3.0) spring (4.4.2)
sqlite3 (2.6.0-aarch64-linux-gnu) sqlite3 (2.9.0-aarch64-linux-gnu)
sqlite3 (2.6.0-aarch64-linux-musl) sqlite3 (2.9.0-aarch64-linux-musl)
sqlite3 (2.6.0-arm-linux-gnu) sqlite3 (2.9.0-arm-linux-gnu)
sqlite3 (2.6.0-arm-linux-musl) sqlite3 (2.9.0-arm-linux-musl)
sqlite3 (2.6.0-arm64-darwin) sqlite3 (2.9.0-arm64-darwin)
sqlite3 (2.6.0-x86_64-darwin) sqlite3 (2.9.0-x86_64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu) sqlite3 (2.9.0-x86_64-linux-gnu)
sqlite3 (2.6.0-x86_64-linux-musl) sqlite3 (2.9.0-x86_64-linux-musl)
stimulus-rails (1.3.4) stimulus-rails (1.3.4)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.1.7) stringio (3.2.0)
thor (1.3.2) thor (1.5.0)
timeout (0.4.3) timeout (0.6.0)
turbo-rails (2.0.13) tsort (0.2.0)
turbo-rails (2.0.23)
actionpack (>= 7.1.0) actionpack (>= 7.1.0)
railties (>= 7.1.0) railties (>= 7.1.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.2) tzinfo-data (1.2025.3)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unicode-display_width (3.1.4) unicode-display_width (3.2.0)
unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (~> 4.1)
unicode-emoji (4.0.4) unicode-emoji (4.2.0)
uniform_notifier (1.17.0) uniform_notifier (1.18.0)
uri (1.0.3) uri (1.1.1)
useragent (0.16.11) useragent (0.16.11)
websocket-driver (0.7.7) websocket-driver (0.8.0)
base64 base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
zeitwerk (2.7.2) zeitwerk (2.7.4)
PLATFORMS PLATFORMS
aarch64-linux-gnu aarch64-linux-gnu
@@ -335,16 +360,18 @@ DEPENDENCIES
influxdb-rails influxdb-rails
jbuilder jbuilder
jquery-rails jquery-rails
mission_control-jobs
mocha mocha
mysql2 mysql2
propshaft propshaft
puma puma
rails (= 8.0.2) rails (= 8.1.2)
rails-controller-testing rails-controller-testing
rails-html-sanitizer rails-html-sanitizer
rails_12factor rails_12factor
rb-readline rb-readline
round_robin_tournament round_robin_tournament
rqrcode
rubocop rubocop
sdoc sdoc
solid_cable solid_cable
@@ -357,7 +384,7 @@ DEPENDENCIES
tzinfo-data tzinfo-data
RUBY VERSION RUBY VERSION
ruby 3.2.0p0 ruby 4.0.1p0
BUNDLED WITH BUNDLED WITH
2.6.9 4.0.3

View File

@@ -7,8 +7,8 @@ This application is being created to run a wrestling tournament.
**Public Production Url:** [https://wrestlingdev.com](https://wrestlingdev.com) **Public Production Url:** [https://wrestlingdev.com](https://wrestlingdev.com)
**App Info** **App Info**
* Ruby 3.2.0 * Ruby 4.0.1
* Rails 8.0.2 * Rails 8.1.2
* DB MySQL/MariaDB * DB MySQL/MariaDB
* Solid Cache -> MySQL/MariaDB for html partial caching * Solid Cache -> MySQL/MariaDB for html partial caching
* Solid Queue -> MySQL/MariaDB for background job processing * Solid Queue -> MySQL/MariaDB for background job processing
@@ -34,11 +34,14 @@ In development environments, background jobs run inline (synchronously) by defau
To run a single test file: To run a single test file:
1. Get a shell with ruby and rails: `bash bin/rails-dev-run.sh wrestlingdev-development` 1. Get a shell with ruby and rails: `bash bin/rails-dev-run.sh wrestlingdev-development`
2. `rake test TEST=test/models/match_test.rb` 2. `rake test TEST=test/models/match_test.rb` OR `rails test test/models/match_test.rb`
To run a single test inside a file: To run a single test inside a file:
1. Get a shell with ruby and rails: `bash bin/rails-dev-run.sh wrestlingdev-development` 1. Get a shell with ruby and rails: `bash bin/rails-dev-run.sh wrestlingdev-development`
2. `rake test TEST=test/models/match_test.rb TESTOPTS="--name='/test_Match_should_not_be_valid_if_an_incorrect_win_type_is_given/'"` 2. `rake test TEST=test/models/match_test.rb TESTOPTS="--name='/test_Match_should_not_be_valid_if_an_incorrect_win_type_is_given/'"` OR `rails test test/models/match_test.rb --name=/test_Match_should_not_be_valid_if_an_incorrect_win_type_is_given/`
To run tests in verbose mode (outputs the time for each test file and the test file name)
`rails test -v`
## Develop with rvm ## Develop with rvm
With rvm installed, run `rvm install ruby-3.2.0` With rvm installed, run `rvm install ruby-3.2.0`
@@ -148,6 +151,9 @@ Available system resources: X CPU(s), YMMMB RAM
SolidQueue plugin enabled in Puma SolidQueue plugin enabled in Puma
``` ```
I have deployed Mission Control as a UI for SolidQueue. The uri for mission control is `/jobs`.
For the development environment, the user/password is dev/secret. For the production environment, it is defined by environment variables WRESTLINGDEV_MISSION_CONTROL_USER/WRESTLINGDEV_MISSION_CONTROL_PASSWORD. You can see this in `config/environments/production.rb` and `config/environments/development.rb`.
## Environment Variables ## Environment Variables
### Required Environment Variables ### Required Environment Variables
@@ -160,6 +166,8 @@ SolidQueue plugin enabled in Puma
* `WRESTLINGDEV_SECRET_KEY_BASE` - Rails application secret key (can be generated with `rake secret`) * `WRESTLINGDEV_SECRET_KEY_BASE` - Rails application secret key (can be generated with `rake secret`)
* `WRESTLINGDEV_EMAIL` - Email address (currently must be a Gmail account) * `WRESTLINGDEV_EMAIL` - Email address (currently must be a Gmail account)
* `WRESTLINGDEV_EMAIL_PWD` - Email password * `WRESTLINGDEV_EMAIL_PWD` - Email password
* `WRESTLINGDEV_MISSION_CONTROL_USER` - mission control username
* `WRESTLINGDEV_MISSION_CONTROL_PASSWORD` - mission control password
### Optional Environment Variables ### Optional Environment Variables
* `SOLID_QUEUE_IN_PUMA` - Set to "true" to run Solid Queue workers inside Puma (default in development) * `SOLID_QUEUE_IN_PUMA` - Set to "true" to run Solid Queue workers inside Puma (default in development)

View File

@@ -13,6 +13,8 @@ export default class extends Controller {
connect() { connect() {
console.log("Match data controller connected") console.log("Match data controller connected")
this.isConnected = false
this.pendingLocalSync = { w1: false, w2: false }
this.w1 = { this.w1 = {
name: "w1", name: "w1",
@@ -69,6 +71,7 @@ export default class extends Controller {
wrestler.updated_at = new Date().toISOString() wrestler.updated_at = new Date().toISOString()
this.updateHtmlValues() this.updateHtmlValues()
this.saveToLocalStorage(wrestler) this.saveToLocalStorage(wrestler)
if (!this.isConnected) this.pendingLocalSync[wrestler.name] = true
// Send the update via Action Cable if subscribed // Send the update via Action Cable if subscribed
if (this.matchSubscription) { if (this.matchSubscription) {
@@ -109,6 +112,7 @@ export default class extends Controller {
// Update the internal JS object // Update the internal JS object
wrestler.stats = newValue wrestler.stats = newValue
wrestler.updated_at = new Date().toISOString() wrestler.updated_at = new Date().toISOString()
if (!this.isConnected) this.pendingLocalSync[wrestler.name] = true
// Save to localStorage // Save to localStorage
this.saveToLocalStorage(wrestler) this.saveToLocalStorage(wrestler)
@@ -334,15 +338,18 @@ export default class extends Controller {
{ {
connected: () => { connected: () => {
console.log(`[Stats AC] Connected to MatchStatsChannel for match ID: ${matchId}`) console.log(`[Stats AC] Connected to MatchStatsChannel for match ID: ${matchId}`)
this.isConnected = true
if (this.statusIndicatorTarget) { if (this.statusIndicatorTarget) {
this.statusIndicatorTarget.innerText = "Connected: Stats will update in real-time." this.statusIndicatorTarget.innerText = "Connected: Stats will update in real-time."
this.statusIndicatorTarget.classList.remove('alert-info', 'alert-warning', 'alert-danger') this.statusIndicatorTarget.classList.remove('alert-info', 'alert-warning', 'alert-danger')
this.statusIndicatorTarget.classList.add('alert-success') this.statusIndicatorTarget.classList.add('alert-success')
} }
this.sendCurrentStatsOnReconnect()
}, },
disconnected: () => { disconnected: () => {
console.log(`[Stats AC] Disconnected from MatchStatsChannel`) console.log(`[Stats AC] Disconnected from MatchStatsChannel`)
this.isConnected = false
if (this.statusIndicatorTarget) { if (this.statusIndicatorTarget) {
this.statusIndicatorTarget.innerText = "Disconnected: Stats updates paused." this.statusIndicatorTarget.innerText = "Disconnected: Stats updates paused."
this.statusIndicatorTarget.classList.remove('alert-info', 'alert-success', 'alert-danger') this.statusIndicatorTarget.classList.remove('alert-info', 'alert-success', 'alert-danger')
@@ -356,15 +363,25 @@ export default class extends Controller {
// Update w1 stats // Update w1 stats
if (data.w1_stat !== undefined && this.w1StatTarget) { if (data.w1_stat !== undefined && this.w1StatTarget) {
console.log(`[Stats AC] Updating w1_stat: ${data.w1_stat.substring(0, 30)}...`) console.log(`[Stats AC] Updating w1_stat: ${data.w1_stat.substring(0, 30)}...`)
this.w1.stats = data.w1_stat if (!this.pendingLocalSync.w1 || data.w1_stat === this.w1.stats) {
this.w1StatTarget.value = data.w1_stat this.w1.stats = data.w1_stat
this.w1StatTarget.value = data.w1_stat
this.pendingLocalSync.w1 = false
} else {
console.log('[Stats AC] Skipping w1_stat overwrite due to pending local changes.')
}
} }
// Update w2 stats // Update w2 stats
if (data.w2_stat !== undefined && this.w2StatTarget) { if (data.w2_stat !== undefined && this.w2StatTarget) {
console.log(`[Stats AC] Updating w2_stat: ${data.w2_stat.substring(0, 30)}...`) console.log(`[Stats AC] Updating w2_stat: ${data.w2_stat.substring(0, 30)}...`)
this.w2.stats = data.w2_stat if (!this.pendingLocalSync.w2 || data.w2_stat === this.w2.stats) {
this.w2StatTarget.value = data.w2_stat this.w2.stats = data.w2_stat
this.w2StatTarget.value = data.w2_stat
this.pendingLocalSync.w2 = false
} else {
console.log('[Stats AC] Skipping w2_stat overwrite due to pending local changes.')
}
} }
}, },
@@ -381,4 +398,23 @@ export default class extends Controller {
} }
) )
} }
}
sendCurrentStatsOnReconnect() {
if (!this.matchSubscription) return
const payload = {}
if (typeof this.w1?.stats === 'string' && this.w1.stats.length > 0) {
payload.new_w1_stat = this.w1.stats
this.pendingLocalSync.w1 = true
}
if (typeof this.w2?.stats === 'string' && this.w2.stats.length > 0) {
payload.new_w2_stat = this.w2.stats
this.pendingLocalSync.w2 = true
}
if (Object.keys(payload).length > 0) {
console.log('[ActionCable] Reconnect sync: sending current stats payload:', payload)
this.matchSubscription.perform('send_stat', payload)
} else {
console.log('[ActionCable] Reconnect sync: no local stats to send.')
}
}
}

View File

@@ -76,6 +76,11 @@ export default class extends Controller {
this.statusIndicatorTarget.classList.remove('alert-danger', 'alert-secondary', 'text-danger', 'text-dark') this.statusIndicatorTarget.classList.remove('alert-danger', 'alert-secondary', 'text-danger', 'text-dark')
this.statusIndicatorTarget.classList.add('alert-success') this.statusIndicatorTarget.classList.add('alert-success')
} }
try {
this.matchSubscription.perform('request_sync')
} catch (e) {
console.error('[Spectator AC] request_sync perform failed:', e)
}
}, },
disconnected: () => { disconnected: () => {
console.log(`[Spectator AC Callback] Disconnected: ${matchId}`) console.log(`[Spectator AC Callback] Disconnected: ${matchId}`)
@@ -131,4 +136,4 @@ export default class extends Controller {
this.finishedTarget.textContent = data.finished ? 'Yes' : 'No' this.finishedTarget.textContent = data.finished ? 'Yes' : 'No'
} }
} }
} }

View File

@@ -60,4 +60,29 @@ class MatchChannel < ApplicationCable::Channel
Rails.logger.info "[MatchChannel] No new stat data provided in send_stat for match #{@match.id}, not updating DB or broadcasting." Rails.logger.info "[MatchChannel] No new stat data provided in send_stat for match #{@match.id}, not updating DB or broadcasting."
end end
end end
# Called when client wants the latest stats immediately after reconnect
def request_sync
unless @match
Rails.logger.error "[MatchChannel] Error: request_sync called but @match is nil. Client params on sub: #{params[:match_id]}"
return
end
payload = {
w1_stat: @match.w1_stat,
w2_stat: @match.w2_stat,
score: @match.score,
win_type: @match.win_type,
winner_name: @match.winner&.name,
winner_id: @match.winner_id,
finished: @match.finished
}.compact
if payload.present?
Rails.logger.info "[MatchChannel] request_sync transmit for match #{@match.id} with payload: #{payload.inspect}"
transmit(payload)
else
Rails.logger.info "[MatchChannel] request_sync payload empty for match #{@match.id}, not transmitting."
end
end
end end

View File

@@ -1,6 +1,6 @@
class MatchesController < ApplicationController class MatchesController < ApplicationController
before_action :set_match, only: [:show, :edit, :update, :stat, :spectate] before_action :set_match, only: [:show, :edit, :update, :stat, :spectate, :edit_assignment, :update_assignment]
before_action :check_access, only: [:edit,:update, :stat] before_action :check_access, only: [:edit, :update, :stat, :edit_assignment, :update_assignment]
# GET /matches/1 # GET /matches/1
# GET /matches/1.json # GET /matches/1.json
@@ -21,7 +21,7 @@ class MatchesController < ApplicationController
session[:return_path] = "/tournaments/#{@match.tournament.id}/matches" session[:return_path] = "/tournaments/#{@match.tournament.id}/matches"
end end
def stat def stat
# @show_next_bout_button = false # @show_next_bout_button = false
if params[:match] if params[:match]
@match = Match.where(:id => params[:match]).includes(:wrestlers).first @match = Match.where(:id => params[:match]).includes(:wrestlers).first
@@ -50,8 +50,21 @@ class MatchesController < ApplicationController
end end
@tournament = @match.tournament @tournament = @match.tournament
end end
session[:return_path] = "/tournaments/#{@tournament.id}/matches" if @match&.mat
session[:error_return_path] = "/matches/#{@match.id}/stat" @mat = @match.mat
queue_position = @mat.queue_position_for_match(@match)
@next_match = queue_position == 1 ? @mat.queue2_match : nil
@show_next_bout_button = queue_position == 1
if request.referer&.include?("/tournaments/#{@tournament.id}/matches")
session[:return_path] = "/tournaments/#{@tournament.id}/matches"
else
session[:return_path] = mat_path(@mat)
end
session[:error_return_path] = "/matches/#{@match.id}/stat"
else
session[:return_path] = "/tournaments/#{@tournament.id}/matches"
session[:error_return_path] = "/matches/#{@match.id}/stat"
end
end end
# GET /matches/:id/spectate # GET /matches/:id/spectate
@@ -71,6 +84,49 @@ class MatchesController < ApplicationController
end end
end end
# GET /matches/1/edit_assignment
def edit_assignment
@tournament = @match.tournament
@mats = @tournament.mats.sort_by(&:name)
@current_mat = @match.mat
@current_queue_position = @current_mat&.queue_position_for_match(@match)
session[:return_path] = "/tournaments/#{@tournament.id}/matches"
end
# PATCH /matches/1/update_assignment
def update_assignment
@tournament = @match.tournament
mat_id = params.dig(:match, :mat_id)
queue_position = params.dig(:match, :queue_position)
if mat_id.blank?
Mat.where("queue1 = :match_id OR queue2 = :match_id OR queue3 = :match_id OR queue4 = :match_id", match_id: @match.id)
.find_each { |mat| mat.remove_match_from_queue_and_collapse!(@match.id) }
@match.update(mat_id: nil)
redirect_to session.delete(:return_path) || "/tournaments/#{@tournament.id}/matches", notice: "Match assignment cleared."
return
end
if queue_position.blank?
redirect_to edit_assignment_match_path(@match), alert: "Queue position is required when selecting a mat."
return
end
unless %w[1 2 3 4].include?(queue_position.to_s)
redirect_to edit_assignment_match_path(@match), alert: "Queue position must be between 1 and 4."
return
end
mat = @tournament.mats.find_by(id: mat_id)
unless mat
redirect_to edit_assignment_match_path(@match), alert: "Selected mat was not found."
return
end
mat.assign_match_to_queue!(@match, queue_position)
redirect_to session.delete(:return_path) || "/tournaments/#{@tournament.id}/matches", notice: "Match assignment updated."
end
# PATCH/PUT /matches/1 # PATCH/PUT /matches/1
# PATCH/PUT /matches/1.json # PATCH/PUT /matches/1.json
def update def update

View File

@@ -10,13 +10,13 @@ class MatsController < ApplicationController
if bout_number_param if bout_number_param
@show_next_bout_button = false @show_next_bout_button = false
@match = @mat.unfinished_matches.find { |m| m.bout_number == bout_number_param.to_i } @match = @mat.queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i }
else else
@show_next_bout_button = true @show_next_bout_button = true
@match = @mat.unfinished_matches.first @match = @mat.queue1_match
end end
@next_match = @mat.unfinished_matches.second # Second unfinished match on the mat @next_match = @mat.queue2_match # Second match on the mat
@wrestlers = [] @wrestlers = []
if @match if @match
@@ -82,8 +82,8 @@ class MatsController < ApplicationController
def assign_next_match def assign_next_match
@tournament = @mat.tournament_id @tournament = @mat.tournament_id
respond_to do |format| respond_to do |format|
if @mat.assign_next_match if @mat.advance_queue!
format.html { redirect_to "/tournaments/#{@mat.tournament.id}", notice: "Next Match on Mat #{@mat.name} successfully completed." } format.html { redirect_to "/tournaments/#{@mat.tournament.id}", notice: "Mat #{@mat.name} queue advanced." }
format.json { head :no_content } format.json { head :no_content }
else else
format.html { redirect_to "/tournaments/#{@mat.tournament.id}", alert: "There was an error." } format.html { redirect_to "/tournaments/#{@mat.tournament.id}", alert: "There was an error." }

View File

@@ -1,6 +1,6 @@
class TournamentsController < ApplicationController class TournamentsController < ApplicationController
before_action :set_tournament, only: [:all_results, :delete_school_keys, :generate_school_keys,:reset_bout_board,:calculate_team_scores,:bout_sheets,:swap,:weigh_in_sheet,:error,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:remove_delegate,:school_delegate,:delegate,:matches,:weigh_in,:weigh_in_weight,:create_custom_weights,:show,:edit,:update,:destroy,:up_matches,:no_matches,:team_scores,:generate_matches,:bracket,:all_brackets] before_action :set_tournament, only: [:all_results, :delete_school_keys, :generate_school_keys,:reset_bout_board,:calculate_team_scores,:bout_sheets,:swap,:weigh_in_sheet,:error,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:remove_delegate,:school_delegate,:delegate,:matches,:weigh_in,:weigh_in_weight,:create_custom_weights,:show,:edit,:update,:destroy,:up_matches,:no_matches,:team_scores,:generate_matches,:bracket,:all_brackets,:qrcode]
before_action :check_access_manage, only: [:delete_school_keys, :generate_school_keys,:reset_bout_board,:calculate_team_scores,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches] before_action :check_access_manage, only: [:delete_school_keys, :generate_school_keys,:reset_bout_board,:calculate_team_scores,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches,:qrcode]
before_action :check_access_destroy, only: [:destroy,:delegate,:remove_delegate] before_action :check_access_destroy, only: [:destroy,:delegate,:remove_delegate]
before_action :check_tournament_errors, only: [:generate_matches] before_action :check_tournament_errors, only: [:generate_matches]
before_action :check_for_matches, only: [:all_results,:up_matches,:bracket,:all_brackets] before_action :check_for_matches, only: [:all_results,:up_matches,:bracket,:all_brackets]
@@ -196,6 +196,11 @@ class TournamentsController < ApplicationController
end end
def qrcode
@tournament_url = tournament_url(@tournament)
@qrcode = RQRCode::QRCode.new(@tournament_url)
end
def up_matches def up_matches
# .where.not(loser1_name: 'BYE') won't return matches with NULL loser1_name # .where.not(loser1_name: 'BYE') won't return matches with NULL loser1_name
@@ -300,7 +305,7 @@ class TournamentsController < ApplicationController
def reset_bout_board def reset_bout_board
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
redirect_to tournament_path(@tournament), notice: "Successfully reset the bout board." redirect_to tournament_path(@tournament), notice: "Successfully reset the bout board. Please have all mat table workers refresh their page."
end end
def generate_school_keys def generate_school_keys

View File

@@ -1,4 +1,20 @@
module ApplicationHelper module ApplicationHelper
def hide_ads?
case controller_name
when "schools"
action_name == "show" && (user_signed_in? || school_permission_key_present?)
when "wrestlers"
%w[new edit].include?(action_name) && (user_signed_in? || school_permission_key_present?)
when "mats"
action_name == "show" && user_signed_in?
else
false
end
end
def school_permission_key_present?
@school_permission_key.present? ||
params[:school_permission_key].present? ||
params.dig(:school, :school_permission_key).present?
end
end end

View File

@@ -1,7 +1,9 @@
class AdvanceWrestlerJob < ApplicationJob class AdvanceWrestlerJob < ApplicationJob
queue_as :default queue_as :default
# associations are not available here so we had to pass tournament_id when creating the job
limits_concurrency to: 1, key: ->(_wrestler, _match, tournament_id) { "tournament:#{tournament_id}" }
def perform(wrestler, match) def perform(wrestler, match, tournament_id)
# Get tournament from wrestler # Get tournament from wrestler
tournament = wrestler.tournament tournament = wrestler.tournament
@@ -29,4 +31,4 @@ class AdvanceWrestlerJob < ApplicationJob
raise e raise e
end end
end end
end end

View File

@@ -1,5 +1,6 @@
class CalculateSchoolScoreJob < ApplicationJob class CalculateSchoolScoreJob < ApplicationJob
queue_as :default queue_as :default
limits_concurrency to: 1, key: ->(school) { "tournament:#{school.tournament_id}" }
# Need for TournamentJobStatusIntegrationTest # Need for TournamentJobStatusIntegrationTest
def self.perform_sync(school) def self.perform_sync(school)
@@ -35,4 +36,4 @@ class CalculateSchoolScoreJob < ApplicationJob
raise e raise e
end end
end end
end end

View File

@@ -1,5 +1,6 @@
class GenerateTournamentMatchesJob < ApplicationJob class GenerateTournamentMatchesJob < ApplicationJob
queue_as :default queue_as :default
limits_concurrency to: 1, key: ->(tournament) { "tournament:#{tournament.id}" }
def perform(tournament) def perform(tournament)
# Log information about the job # Log information about the job
@@ -17,4 +18,4 @@ class GenerateTournamentMatchesJob < ApplicationJob
raise # Re-raise the error so it's properly recorded raise # Re-raise the error so it's properly recorded
end end
end end
end end

View File

@@ -1,5 +1,6 @@
class TournamentBackupJob < ApplicationJob class TournamentBackupJob < ApplicationJob
queue_as :default queue_as :default
limits_concurrency to: 1, key: ->(tournament, *) { "tournament:#{tournament.id}" }
def perform(tournament, reason = nil) def perform(tournament, reason = nil)
# Log information about the job # Log information about the job
@@ -29,4 +30,4 @@ class TournamentBackupJob < ApplicationJob
raise e raise e
end end
end end
end end

View File

@@ -17,7 +17,8 @@ class TournamentCleanupJob < ApplicationJob
has_real_matches = tournament.matches.where(finished: 1).where.not(win_type: 'BYE').exists? has_real_matches = tournament.matches.where(finished: 1).where.not(win_type: 'BYE').exists?
if has_real_matches if has_real_matches
tournament.tournament_backups.destroy_all
# 1. Remove all school delegates # 1. Remove all school delegates
tournament.schools.each do |school| tournament.schools.each do |school|
school.delegates.destroy_all school.delegates.destroy_all
@@ -33,4 +34,4 @@ class TournamentCleanupJob < ApplicationJob
end end
end end
end end
end end

View File

@@ -1,5 +1,6 @@
class WrestlingdevImportJob < ApplicationJob class WrestlingdevImportJob < ApplicationJob
queue_as :default queue_as :default
limits_concurrency to: 1, key: ->(tournament, *) { "tournament:#{tournament.id}" }
def perform(tournament, import_data = nil) def perform(tournament, import_data = nil)
# Log information about the job # Log information about the job
@@ -30,4 +31,4 @@ class WrestlingdevImportJob < ApplicationJob
raise e raise e
end end
end end
end end

View File

@@ -1,6 +1,20 @@
class Ability class Ability
include CanCan::Ability include CanCan::Ability
def school_permission_key_check(school_permission_key)
# Can read school if tournament is public or a valid school permission key is provided
can :read, School do |school|
school.tournament.is_public ||
(school_permission_key.present? && school.permission_key == school_permission_key)
end
# Can manage school if a valid school permission key is provided
# school_permission_key comes from app/controllers/application_controller.rb
can :manage, School do |school|
(school_permission_key.present? && school.permission_key == school_permission_key)
end
end
def initialize(user, school_permission_key = nil) def initialize(user, school_permission_key = nil)
if user if user
# LOGGED IN USER PERMISSIONS # LOGGED IN USER PERMISSIONS
@@ -46,6 +60,8 @@ class Ability
school.tournament.delegates.map(&:user_id).include?(user.id) || school.tournament.delegates.map(&:user_id).include?(user.id) ||
school.tournament.user_id == user.id school.tournament.user_id == user.id
end end
school_permission_key_check(school_permission_key)
else else
# NON LOGGED IN USER PERMISSIONS # NON LOGGED IN USER PERMISSIONS
@@ -58,18 +74,7 @@ class Ability
# SCHOOL PERMISSIONS # SCHOOL PERMISSIONS
# wrestler permissions are included with school permissions # wrestler permissions are included with school permissions
school_permission_key_check(school_permission_key)
# Can read school if tournament is public or a valid school permission key is provided
can :read, School do |school|
school.tournament.is_public ||
(school_permission_key.present? && school.permission_key == school_permission_key)
end
# Can read school if a valid school permission key is provided
# school_permission_key comes from app/controllers/application_controller.rb
can :manage, School do |school|
(school_permission_key.present? && school.permission_key == school_permission_key)
end
end end
end end
end end

View File

@@ -1,53 +1,52 @@
class Mat < ApplicationRecord class Mat < ApplicationRecord
include ActionView::RecordIdentifier
belongs_to :tournament belongs_to :tournament
has_many :matches, dependent: :destroy has_many :matches, dependent: :nullify
has_many :mat_assignment_rules, dependent: :destroy has_many :mat_assignment_rules, dependent: :destroy
validates :name, presence: true validates :name, presence: true
before_destroy do QUEUE_SLOTS = %w[queue1 queue2 queue3 queue4].freeze
if tournament.matches.size > 0
tournament.reset_mats
matsToAssign = tournament.mats.select{|m| m.id != self.id}
tournament.assign_mats(matsToAssign)
end
end
after_create do after_save :clear_queue_matches_cache
if tournament.matches.size > 0
tournament.reset_mats
matsToAssign = tournament.mats
tournament.assign_mats(matsToAssign)
end
end
def assign_next_match def assign_next_match
slot = first_empty_queue_slot
return true unless slot
match = next_eligible_match match = next_eligible_match
self.matches.reload return false unless match
if match and self.unfinished_matches.size < 4
match.mat_id = self.id place_match_in_empty_slot!(match, slot)
if match.save true
# Invalidate any wrestler caches end
if match.w1
match.wrestler1.touch def advance_queue!(finished_match = nil)
match.wrestler1.school.touch self.class.transaction do
if finished_match
position = queue_position_for_match(finished_match)
if position == 1
shift_queue_forward!
fill_queue_slots!
elsif position
remove_match_from_queue_and_collapse!(finished_match.id)
else
fill_queue_slots!
end end
if match.w2
match.wrestler2.touch
match.wrestler2.school.touch
end
return true
else else
return false if queue1_match&.finished == 1
shift_queue_forward!
end
fill_queue_slots!
end end
else
return true
end end
broadcast_current_match
true
end end
def next_eligible_match def next_eligible_match
# Start with all matches that are either unfinished (nil or 0), have a bout number, and are ordered by bout_number # Start with all matches that are either unfinished (nil or 0), have a bout number, and are ordered by bout_number
filtered_matches = tournament.matches filtered_matches = Match.where(tournament_id: tournament_id)
.where(finished: [nil, 0]) # finished is nil or 0 .where(finished: [nil, 0]) # finished is nil or 0
.where(mat_id: nil) # mat_id is nil .where(mat_id: nil) # mat_id is nil
.where.not(bout_number: nil) # bout_number is not nil .where.not(bout_number: nil) # bout_number is not nil
@@ -57,6 +56,11 @@ class Mat < ApplicationRecord
filtered_matches = filtered_matches filtered_matches = filtered_matches
.where("loser1_name != ? OR loser1_name IS NULL", "BYE") .where("loser1_name != ? OR loser1_name IS NULL", "BYE")
.where("loser2_name != ? OR loser2_name IS NULL", "BYE") .where("loser2_name != ? OR loser2_name IS NULL", "BYE")
# Filter out matches without a wrestlers
filtered_matches = filtered_matches
.where("w1 IS NOT NULL")
.where("w2 IS NOT NULL")
# Apply mat assignment rules # Apply mat assignment rules
mat_assignment_rules.each do |rule| mat_assignment_rules.each do |rule|
@@ -80,9 +84,194 @@ class Mat < ApplicationRecord
filtered_matches.first filtered_matches.first
end end
def queue_match_ids
QUEUE_SLOTS.map { |slot| public_send(slot) }
end
# used to prevent N+1 query on each mat
def queue_matches
slot_ids = queue_match_ids
if @queue_matches.nil? || @queue_match_slot_ids != slot_ids
ids = slot_ids.compact
@queue_matches = if ids.empty?
[nil, nil, nil, nil]
else
matches_by_id = Match.where(id: ids).index_by(&:id)
slot_ids.map { |match_id| match_id ? matches_by_id[match_id] : nil }
end
@queue_match_slot_ids = slot_ids
end
@queue_matches
end
def queue1_match
queue_match_at(1)
end
def queue2_match
queue_match_at(2)
end
def queue3_match
queue_match_at(3)
end
def queue4_match
queue_match_at(4)
end
def queue_position_for_match(match)
return nil unless match
return 1 if queue1 == match.id
return 2 if queue2 == match.id
return 3 if queue3 == match.id
return 4 if queue4 == match.id
nil
end
def remove_match_from_queue_and_collapse!(match_id)
queue_ids = queue_match_ids
return if queue_ids.none? { |id| id == match_id }
queue_ids.map! { |id| id == match_id ? nil : id }
queue_ids = queue_ids.compact
queue_ids += [nil] * (4 - queue_ids.size)
update!(
queue1: queue_ids[0],
queue2: queue_ids[1],
queue3: queue_ids[2],
queue4: queue_ids[3]
)
fill_queue_slots!
broadcast_current_match
end
def assign_match_to_queue!(match, position)
position = position.to_i
raise ArgumentError, "Queue position must be 1-4" unless (1..4).cover?(position)
self.class.transaction do
match.update!(mat_id: id)
remove_match_from_other_mats!(match.id)
queue_ids = queue_match_ids.map { |id| id == match.id ? nil : id }
queue_ids = queue_ids.compact
queue_ids.insert(position - 1, match.id)
bumped_match_id = queue_ids.length > 4 ? queue_ids.pop : nil
queue_ids += [nil] * (4 - queue_ids.length)
update!(
queue1: queue_ids[0],
queue2: queue_ids[1],
queue3: queue_ids[2],
queue4: queue_ids[3]
)
bumped_match = Match.find_by(id: bumped_match_id)
if bumped_match && bumped_match.finished != 1
bumped_match.update!(mat_id: nil)
end
end
broadcast_current_match
end
def clear_queue!
update!(queue1: nil, queue2: nil, queue3: nil, queue4: nil)
end
def unfinished_matches def unfinished_matches
matches.select{|m| m.finished != 1}.sort_by{|m| m.bout_number} matches.select{|m| m.finished != 1}.sort_by{|m| m.bout_number}
end end
private
def clear_queue_matches_cache
@queue_matches = nil
@queue_match_slot_ids = nil
end
def queue_match_at(position)
queue_matches[position - 1]
end
def first_empty_queue_slot
QUEUE_SLOTS.each_with_index do |slot, index|
return index + 1 if public_send(slot).nil?
end
nil
end
def shift_queue_forward!
update!(
queue1: queue2,
queue2: queue3,
queue3: queue4,
queue4: nil
)
end
def fill_queue_slots!
queue_ids = queue_match_ids
updated = false
QUEUE_SLOTS.each_with_index do |_slot, index|
next if queue_ids[index].present?
match = next_eligible_match
break unless match
queue_ids[index] = match.id
match.update!(mat_id: id)
updated = true
end
if updated
update!(
queue1: queue_ids[0],
queue2: queue_ids[1],
queue3: queue_ids[2],
queue4: queue_ids[3]
)
end
end
def remove_match_from_other_mats!(match_id)
self.class.where.not(id: id)
.where("queue1 = :match_id OR queue2 = :match_id OR queue3 = :match_id OR queue4 = :match_id", match_id: match_id)
.find_each do |mat|
mat.remove_match_from_queue_and_collapse!(match_id)
end
end
def place_match_in_empty_slot!(match, slot)
self.class.transaction do
match.update!(mat_id: id)
remove_match_from_other_mats!(match.id)
update!(slot_key(slot) => match.id)
end
broadcast_current_match
end
def slot_key(slot)
"queue#{slot}"
end
def broadcast_current_match
Turbo::StreamsChannel.broadcast_update_to(
self,
target: dom_id(self, :current_match),
partial: "mats/current_match",
locals: {
mat: self,
match: queue1_match,
next_match: queue2_match,
show_next_bout_button: true
}
)
end
end end

View File

@@ -1,4 +1,6 @@
class Match < ApplicationRecord class Match < ApplicationRecord
include ActionView::RecordIdentifier
belongs_to :tournament, touch: true belongs_to :tournament, touch: true
belongs_to :weight, touch: true belongs_to :weight, touch: true
belongs_to :mat, touch: true, optional: true belongs_to :mat, touch: true, optional: true
@@ -10,6 +12,10 @@ class Match < ApplicationRecord
# Callback to update finished_at when a match is finished # Callback to update finished_at when a match is finished
before_save :update_finished_at before_save :update_finished_at
# update mat show with correct match if bout board is reset
# this is done with a turbo stream
after_commit :broadcast_mat_assignment_change, if: :saved_change_to_mat_id?, on: [:create, :update]
# Enqueue advancement and related actions after the DB transaction has committed. # Enqueue advancement and related actions after the DB transaction has committed.
# Using after_commit ensures any background jobs enqueued inside these callbacks # Using after_commit ensures any background jobs enqueued inside these callbacks
# will see the committed state of the match (e.g. finished == 1). Enqueuing # will see the committed state of the match (e.g. finished == 1). Enqueuing
@@ -31,12 +37,14 @@ class Match < ApplicationRecord
wrestler2.touch wrestler2.touch
end end
if self.finished == 1 && self.winner_id != nil if self.finished == 1 && self.winner_id != nil
if self.mat
self.mat.assign_next_match
end
advance_wrestlers advance_wrestlers
if self.mat
self.mat.advance_queue!(self)
end
self.tournament.refill_open_bout_board_queues
# School point calculation has move to the end of advance wrestler # School point calculation has move to the end of advance wrestler
# calculate_school_points # calculate_school_points
self.update(mat_id: nil)
end end
end end
@@ -50,7 +58,7 @@ class Match < ApplicationRecord
errors.add(:winner_id, "cannot be blank") errors.add(:winner_id, "cannot be blank")
end end
if win_type == "Pin" and ! score.match(/^[0-5]?[0-9]:[0-5][0-9]/) if win_type == "Pin" and ! score.match(/^[0-5]?[0-9]:[0-5][0-9]/)
errors.add(:score, "needs to be in time format MM:SS when win type is Pin example: 1:23 or 10:03") errors.add(:score, "needs to be in time format MM:SS when win type is Pin example: 2:23, 0:25, 10:03")
end end
if win_type == "Decision" or win_type == "Tech Fall" or win_type == "Major" and ! score.match(/^[0-9]?[0-9]-[0-9]?[0-9]/) if win_type == "Decision" or win_type == "Tech Fall" or win_type == "Major" and ! score.match(/^[0-9]?[0-9]-[0-9]?[0-9]/)
errors.add(:score, "needs to be in Number-Number format when win type is Decision, Tech Fall, and Major example: 10-2") errors.add(:score, "needs to be in Number-Number format when win type is Decision, Tech Fall, and Major example: 10-2")
@@ -195,6 +203,7 @@ class Match < ApplicationRecord
end end
def w1_bracket_name def w1_bracket_name
first_round = self.weight.matches.sort_by{|m| m.round}.first.round
return_string = "" return_string = ""
return_string_ending = "" return_string_ending = ""
if self.w1 and self.winner_id == self.w1 if self.w1 and self.winner_id == self.w1
@@ -202,7 +211,7 @@ class Match < ApplicationRecord
return_string_ending = return_string_ending + "</strong>" return_string_ending = return_string_ending + "</strong>"
end end
if self.w1 != nil if self.w1 != nil
if self.round == 1 if self.round == first_round
return_string = return_string + "#{wrestler1.long_bracket_name}" return_string = return_string + "#{wrestler1.long_bracket_name}"
else else
return_string = return_string + "#{wrestler1.short_bracket_name}" return_string = return_string + "#{wrestler1.short_bracket_name}"
@@ -214,6 +223,7 @@ class Match < ApplicationRecord
end end
def w2_bracket_name def w2_bracket_name
first_round = self.weight.matches.sort_by{|m| m.round}.first.round
return_string = "" return_string = ""
return_string_ending = "" return_string_ending = ""
if self.w2 and self.winner_id == self.w2 if self.w2 and self.winner_id == self.w2
@@ -221,7 +231,7 @@ class Match < ApplicationRecord
return_string_ending = return_string_ending + "</strong>" return_string_ending = return_string_ending + "</strong>"
end end
if self.w2 != nil if self.w2 != nil
if self.round == 1 if self.round == first_round
return_string = return_string + "#{wrestler2.long_bracket_name}" return_string = return_string + "#{wrestler2.long_bracket_name}"
else else
return_string = return_string + "#{wrestler2.short_bracket_name}" return_string = return_string + "#{wrestler2.short_bracket_name}"
@@ -334,4 +344,26 @@ class Match < ApplicationRecord
self.finished_at = Time.current.utc self.finished_at = Time.current.utc
end end
end end
def broadcast_mat_assignment_change
old_mat_id, new_mat_id = saved_change_to_mat_id || previous_changes["mat_id"]
return unless old_mat_id || new_mat_id
[old_mat_id, new_mat_id].compact.uniq.each do |mat_id|
mat = Mat.find_by(id: mat_id)
next unless mat
Turbo::StreamsChannel.broadcast_update_to(
mat,
target: dom_id(mat, :current_match),
partial: "mats/current_match",
locals: {
mat: mat,
match: mat.queue1_match,
next_match: mat.queue2_match,
show_next_bout_button: true
}
)
end
end
end end

View File

@@ -82,23 +82,18 @@ class Tournament < ApplicationRecord
matches.maximum(:round) || 0 # Return 0 if no matches or max round is nil matches.maximum(:round) || 0 # Return 0 if no matches or max round is nil
end end
def assign_mats(mats_to_assign)
if mats_to_assign.count > 0
until mats_to_assign.sort_by{|m| m.id}.last.matches.count == 4
mats_to_assign.sort_by{|m| m.id}.each do |m|
m.assign_next_match
end
end
end
end
def reset_mats def reset_mats
matches.reload
mats.reload
matches_to_reset = matches.select{|m| m.mat_id != nil} matches_to_reset = matches.select{|m| m.mat_id != nil}
# matches_to_reset.update_all( {:mat_id => nil } ) # matches_to_reset.update_all( {:mat_id => nil } )
matches_to_reset.each do |m| matches_to_reset.each do |m|
m.mat_id = nil m.mat_id = nil
m.save m.save
end end
mats.each do |mat|
mat.clear_queue!
end
end end
def pointAdjustments def pointAdjustments
@@ -156,14 +151,14 @@ class Tournament < ApplicationRecord
def double_elim_number_of_wrestlers_error def double_elim_number_of_wrestlers_error
error_string = "" error_string = ""
if self.tournament_type == "Double Elimination 1-6" or self.tournament_type == "Double Elimination 1-8" if self.tournament_type == "Regular Double Elimination 1-6" or self.tournament_type == "Regular Double Elimination 1-8"
weights_with_too_many_wrestlers = weights.select{|w| w.wrestlers.size > 64} weights_with_too_many_wrestlers = weights.select{|w| w.wrestlers.size > 64}
weight_with_too_few_wrestlers = weights.select{|w| w.wrestlers.size < 4} weight_with_too_few_wrestlers = weights.select{|w| w.wrestlers.size < 2}
weights_with_too_many_wrestlers.each do |weight| weights_with_too_many_wrestlers.each do |weight|
error_string = error_string + " The weight class #{weight.max} has more than 64 wrestlers." error_string = error_string + " The weight class #{weight.max} has more than 64 wrestlers."
end end
weight_with_too_few_wrestlers.each do |weight| weight_with_too_few_wrestlers.each do |weight|
error_string = error_string + " The weight class #{weight.max} has less than 4 wrestlers." error_string = error_string + " The weight class #{weight.max} has less than 2 wrestlers."
end end
end end
return error_string return error_string
@@ -228,19 +223,24 @@ class Tournament < ApplicationRecord
def reset_and_fill_bout_board def reset_and_fill_bout_board
reset_mats reset_mats
matches.reload
if mats.any? refill_open_bout_board_queues
4.times do end
# Iterate over each mat and assign the next available match
mats.each do |mat| def refill_open_bout_board_queues
match_assigned = mat.assign_next_match return unless mats.any?
# If no more matches are available, exit early
unless match_assigned loop do
puts "No more eligible matches to assign." assigned_any = false
return # Fill in round-robin order by queue depth:
end # all mats queue1 first, then queue2, then queue3, then queue4.
(1..4).each do |slot|
mats.reload.each do |mat|
next unless mat.public_send("queue#{slot}").nil?
assigned_any ||= mat.assign_next_match
end
end end
end break unless assigned_any
end end
end end
@@ -279,4 +279,4 @@ class Tournament < ApplicationRecord
def connection_adapter def connection_adapter
ActiveRecord::Base.connection.adapter_name ActiveRecord::Base.connection.adapter_name
end end
end end

View File

@@ -8,7 +8,7 @@ class AdvanceWrestler
def advance def advance
# Use perform_later which will execute based on centralized adapter config # Use perform_later which will execute based on centralized adapter config
# This will be converted to inline execution in test environment by ActiveJob # This will be converted to inline execution in test environment by ActiveJob
AdvanceWrestlerJob.perform_later(@wrestler, @last_match) AdvanceWrestlerJob.perform_later(@wrestler, @last_match, @tournament.id)
end end
def advance_raw def advance_raw
@@ -29,4 +29,4 @@ class AdvanceWrestler
PoolAdvance.new(@wrestler).advanceWrestler PoolAdvance.new(@wrestler).advanceWrestler
end end
end end

View File

@@ -6,9 +6,12 @@ class DoubleEliminationGenerateLoserNames
# Entry point: assign loser placeholders and advance any byes # Entry point: assign loser placeholders and advance any byes
def assign_loser_names def assign_loser_names
@tournament.weights.each do |weight| @tournament.weights.each do |weight|
assign_loser_names_for_weight(weight) # only assign loser names if there's conso matches to be had
advance_bye_matches_championship(weight) if weight.calculate_bracket_size > 2
advance_bye_matches_consolation(weight) assign_loser_names_for_weight(weight)
advance_bye_matches_championship(weight)
advance_bye_matches_consolation(weight)
end
end end
end end
@@ -87,7 +90,7 @@ class DoubleEliminationGenerateLoserNames
end end
# 5th/6th place # 5th/6th place
if bracket_size >= 5 && num_placers >= 6 if bracket_size >= 5 && num_placers >= 6 && weight.wrestlers.size > 4
conso_semis = matches.select { |m| m.bracket_position == "Conso Semis" } conso_semis = matches.select { |m| m.bracket_position == "Conso Semis" }
.sort_by(&:bracket_position_number) .sort_by(&:bracket_position_number)
if conso_semis.size >= 2 if conso_semis.size >= 2
@@ -98,7 +101,7 @@ class DoubleEliminationGenerateLoserNames
end end
# 7th/8th place # 7th/8th place
if bracket_size >= 7 && num_placers >= 8 if bracket_size >= 7 && num_placers >= 8 && weight.wrestlers.size > 6
conso_quarters = matches.select { |m| m.bracket_position == "Conso Quarter" } conso_quarters = matches.select { |m| m.bracket_position == "Conso Quarter" }
.sort_by(&:bracket_position_number) .sort_by(&:bracket_position_number)
if conso_quarters.size >= 2 if conso_quarters.size >= 2

View File

@@ -37,11 +37,19 @@ class TournamentBackupService
attributes: @tournament.attributes, attributes: @tournament.attributes,
schools: @tournament.schools.map(&:attributes), schools: @tournament.schools.map(&:attributes),
weights: @tournament.weights.map(&:attributes), weights: @tournament.weights.map(&:attributes),
mats: @tournament.mats.map(&:attributes), mats: @tournament.mats.map do |mat|
mat.attributes.merge(
"queue_bout_numbers" => mat.queue_matches.map { |match| match&.bout_number }
)
end,
mat_assignment_rules: @tournament.mat_assignment_rules.map do |rule| mat_assignment_rules: @tournament.mat_assignment_rules.map do |rule|
rule.attributes.merge( rule.attributes.merge(
mat: Mat.find_by(id: rule.mat_id)&.attributes.slice("name"), mat: Mat.find_by(id: rule.mat_id)&.attributes.slice("name"),
weight_classes: rule.weight_classes.map do |weight_id| # Emit the human-readable max values under a distinct key to avoid
# colliding with the raw DB-backed "weight_classes" attribute (which
# is stored as a comma-separated string). Using a different key
# prevents duplicate JSON keys when symbols and strings are both present.
"weight_class_maxes" => rule.weight_classes.map do |weight_id|
Weight.find_by(id: weight_id)&.max Weight.find_by(id: weight_id)&.max
end end
) )

View File

@@ -18,15 +18,15 @@ class TournamentSeeding
def random_seeding(wrestlers, bracket_size) def random_seeding(wrestlers, bracket_size)
half_of_bracket = bracket_size / 2 half_of_bracket = bracket_size / 2
available_bracket_lines = (1..bracket_size).to_a available_bracket_lines = (1..bracket_size).to_a
first_half_available_bracket_lines = (1..half_of_bracket).to_a
# remove bracket lines that are taken from available_bracket_lines # remove bracket lines that are taken from available_bracket_lines
wrestlers_with_bracket_lines = wrestlers.select{|w| w.bracket_line != nil } wrestlers_with_bracket_lines = wrestlers.select{|w| w.bracket_line != nil }
wrestlers_with_bracket_lines.each do |wrestler| wrestlers_with_bracket_lines.each do |wrestler|
available_bracket_lines.delete(wrestler.bracket_line) available_bracket_lines.delete(wrestler.bracket_line)
first_half_available_bracket_lines.delete(wrestler.bracket_line)
end end
available_bracket_lines_to_use = set_random_seeding_bracket_line_order(available_bracket_lines)
wrestlers_without_bracket_lines = wrestlers.select{|w| w.bracket_line == nil } wrestlers_without_bracket_lines = wrestlers.select{|w| w.bracket_line == nil }
if @tournament.tournament_type == "Pool to bracket" if @tournament.tournament_type == "Pool to bracket"
wrestlers_without_bracket_lines.shuffle.each do |wrestler| wrestlers_without_bracket_lines.shuffle.each do |wrestler|
@@ -38,15 +38,10 @@ class TournamentSeeding
else else
# Iterrate over the list randomly # Iterrate over the list randomly
wrestlers_without_bracket_lines.shuffle.each do |wrestler| wrestlers_without_bracket_lines.shuffle.each do |wrestler|
if first_half_available_bracket_lines.size > 0 if available_bracket_lines_to_use.size > 0
random_available_bracket_line = first_half_available_bracket_lines.sample bracket_line_to_use = available_bracket_lines_to_use.first
wrestler.bracket_line = random_available_bracket_line wrestler.bracket_line = bracket_line_to_use
available_bracket_lines.delete(random_available_bracket_line) available_bracket_lines_to_use.delete(bracket_line_to_use)
first_half_available_bracket_lines.delete(random_available_bracket_line)
else
random_available_bracket_line = available_bracket_lines.sample
wrestler.bracket_line = random_available_bracket_line
available_bracket_lines.delete(random_available_bracket_line)
end end
end end
end end
@@ -81,4 +76,24 @@ class TournamentSeeding
end end
return wrestlers return wrestlers
end end
private
def set_random_seeding_bracket_line_order(available_bracket_lines)
# This method prevents double BYEs in round 1
# It also evenly distributes matches from the top half of the bracket to the bottom half
# It does both of these while keeping the randomness of the line assignment
odd_or_even = [0, 1]
odd_or_even_sample = odd_or_even.sample
# sort by odd or even based on the sample above
if odd_or_even_sample == 1
# odd numbers first
result = available_bracket_lines.sort_by { |n| n.even? ? 1 : 0 }
else
# even numbers first
result = available_bracket_lines.sort_by { |n| n.odd? ? 1 : 0 }
end
result
end
end end

View File

@@ -41,20 +41,20 @@ class WrestlingdevImporter
@tournament.matches.destroy_all @tournament.matches.destroy_all
@tournament.mat_assignment_rules.destroy_all # Explicitly destroy rules (might be redundant if Mat cascades) @tournament.mat_assignment_rules.destroy_all # Explicitly destroy rules (might be redundant if Mat cascades)
@tournament.delegates.destroy_all @tournament.delegates.destroy_all
@tournament.tournament_backups.destroy_all
@tournament.tournament_job_statuses.destroy_all @tournament.tournament_job_statuses.destroy_all
# Note: Teampointadjusts are deleted via School/Wrestler cascade # Note: Teampointadjusts are deleted via School/Wrestler cascade
end end
def parse_data def parse_data
parse_tournament(@import_data["tournament"]["attributes"]) parse_tournament(@import_data["tournament"]["attributes"])
parse_schools(@import_data["tournament"]["schools"]) parse_schools(@import_data["tournament"]["schools"])
parse_weights(@import_data["tournament"]["weights"]) parse_weights(@import_data["tournament"]["weights"])
parse_mats(@import_data["tournament"]["mats"]) parse_mats(@import_data["tournament"]["mats"])
parse_wrestlers(@import_data["tournament"]["wrestlers"]) parse_wrestlers(@import_data["tournament"]["wrestlers"])
parse_matches(@import_data["tournament"]["matches"]) parse_matches(@import_data["tournament"]["matches"])
parse_mat_assignment_rules(@import_data["tournament"]["mat_assignment_rules"]) apply_mat_queues
end parse_mat_assignment_rules(@import_data["tournament"]["mat_assignment_rules"])
end
def parse_tournament(attributes) def parse_tournament(attributes)
attributes.except!("id") attributes.except!("id")
@@ -75,28 +75,47 @@ class WrestlingdevImporter
end end
end end
def parse_mats(mats) def parse_mats(mats)
mats.each do |mat_attributes| @mat_queue_bout_numbers = {}
mat_attributes.except!("id") mats.each do |mat_attributes|
Mat.create(mat_attributes.merge(tournament_id: @tournament.id)) mat_name = mat_attributes["name"]
end queue_bout_numbers = mat_attributes["queue_bout_numbers"]
end mat_attributes.except!("id", "queue1", "queue2", "queue3", "queue4", "queue_bout_numbers", "tournament_id")
Mat.create(mat_attributes.merge(tournament_id: @tournament.id))
if mat_name && queue_bout_numbers
@mat_queue_bout_numbers[mat_name] = queue_bout_numbers
end
end
end
def parse_mat_assignment_rules(mat_assignment_rules) def parse_mat_assignment_rules(mat_assignment_rules)
mat_assignment_rules.each do |rule_attributes| mat_assignment_rules.each do |rule_attributes|
mat_name = rule_attributes.dig("mat", "name") mat_name = rule_attributes.dig("mat", "name")
mat = Mat.find_by(name: mat_name, tournament_id: @tournament.id) mat = Mat.find_by(name: mat_name, tournament_id: @tournament.id)
# Map max values of weight_classes to their new IDs # Prefer the new "weight_class_maxes" key emitted by backups (human-readable
new_weight_classes = rule_attributes["weight_classes"].map do |max_value| # max values). If not present, fall back to the legacy "weight_classes"
Weight.find_by(max: max_value, tournament_id: @tournament.id)&.id # value which may be a comma-separated string or an array of IDs.
end.compact if rule_attributes.key?("weight_class_maxes") && rule_attributes["weight_class_maxes"].respond_to?(:map)
new_weight_classes = rule_attributes["weight_class_maxes"].map do |max_value|
# Extract bracket_positions and rounds Weight.find_by(max: max_value, tournament_id: @tournament.id)&.id
end.compact
elsif rule_attributes["weight_classes"].is_a?(Array)
# Already an array of IDs
new_weight_classes = rule_attributes["weight_classes"].map(&:to_i)
elsif rule_attributes["weight_classes"].is_a?(String)
# Comma-separated IDs stored in the DB column; split into integers.
new_weight_classes = rule_attributes["weight_classes"].to_s.split(",").map(&:strip).reject(&:empty?).map(&:to_i)
else
new_weight_classes = []
end
# Extract bracket_positions and rounds (leave as-is; model will coerce if needed)
bracket_positions = rule_attributes["bracket_positions"] bracket_positions = rule_attributes["bracket_positions"]
rounds = rule_attributes["rounds"] rounds = rule_attributes["rounds"]
rule_attributes.except!("id", "mat", "tournament_id", "weight_classes") # Remove any keys we don't want to mass-assign (including both old/new weight keys)
rule_attributes.except!("id", "mat", "tournament_id", "weight_classes", "weight_class_maxes")
MatAssignmentRule.create( MatAssignmentRule.create(
rule_attributes.merge( rule_attributes.merge(
@@ -122,9 +141,9 @@ class WrestlingdevImporter
end end
end end
def parse_matches(matches) def parse_matches(matches)
matches.each do |match_attributes| matches.each do |match_attributes|
next unless match_attributes # Skip if match_attributes is nil next unless match_attributes # Skip if match_attributes is nil
weight = Weight.find_by(max: match_attributes.dig("weight", "max"), tournament_id: @tournament.id) weight = Weight.find_by(max: match_attributes.dig("weight", "max"), tournament_id: @tournament.id)
mat = Mat.find_by(name: match_attributes.dig("mat", "name"), tournament_id: @tournament.id) mat = Mat.find_by(name: match_attributes.dig("mat", "name"), tournament_id: @tournament.id)
@@ -143,6 +162,53 @@ class WrestlingdevImporter
w2: w2&.id, w2: w2&.id,
winner_id: winner&.id winner_id: winner&.id
)) ))
end end
end end
end
def apply_mat_queues
if @mat_queue_bout_numbers.blank?
Mat.where(tournament_id: @tournament.id).find_each do |mat|
match_ids = mat.matches.where(finished: [nil, 0]).order(:bout_number).limit(4).pluck(:id)
mat.update(
queue1: match_ids[0],
queue2: match_ids[1],
queue3: match_ids[2],
queue4: match_ids[3]
)
end
return
end
@mat_queue_bout_numbers.each do |mat_name, bout_numbers|
mat = Mat.find_by(name: mat_name, tournament_id: @tournament.id)
next unless mat
matches = Array(bout_numbers).map do |bout_number|
Match.find_by(bout_number: bout_number, tournament_id: @tournament.id)
end
mat.update(
queue1: matches[0]&.id,
queue2: matches[1]&.id,
queue3: matches[2]&.id,
queue4: matches[3]&.id
)
matches.compact.each do |match|
match.update(mat_id: mat.id)
end
end
Mat.where(tournament_id: @tournament.id)
.where(queue1: nil, queue2: nil, queue3: nil, queue4: nil)
.find_each do |mat|
match_ids = mat.matches.where(finished: [nil, 0]).order(:bout_number).limit(4).pluck(:id)
mat.update(
queue1: match_ids[0],
queue2: match_ids[1],
queue3: match_ids[2],
queue4: match_ids[3]
)
end
end
end

View File

@@ -28,7 +28,7 @@ json.cache! ["api_tournament", @tournament] do
json.mats @mats do |mat| json.mats @mats do |mat|
json.name mat.name json.name mat.name
json.unfinished_matches mat.unfinished_matches do |match| json.unfinished_matches mat.queue_matches.compact do |match|
json.bout_number match.bout_number json.bout_number match.bout_number
json.w1_name match.w1_name json.w1_name match.w1_name
json.w2_name match.w2_name json.w2_name match.w2_name

View File

@@ -38,12 +38,13 @@
<li><strong>Pages</strong></li> <li><strong>Pages</strong></li>
<li></span> <%= link_to "Edit Tournament Info", edit_tournament_path(@tournament) %></li> <li></span> <%= link_to "Edit Tournament Info", edit_tournament_path(@tournament) %></li>
<li><%= link_to "Weigh In Page" , "/tournaments/#{@tournament.id}/weigh_in" %></li> <li><%= link_to "Weigh In Page" , "/tournaments/#{@tournament.id}/weigh_in" %></li>
<li><%= link_to "All Matches" , "/tournaments/#{@tournament.id}/matches" %></li> <li><%= link_to "All Matches" , "/tournaments/#{@tournament.id}/matches" %></li>
<li><%= link_to "Full Screen Bout Board" , "/tournaments/#{@tournament.id}/up_matches?print=true" , target: :_blank %></li> <li><%= link_to "Full Screen Bout Board" , "/tournaments/#{@tournament.id}/up_matches?print=true" , target: :_blank %></li>
<li><%= link_to "Deduct Team Points" , "/tournaments/#{@tournament.id}/teampointadjust" %></li> <li><%= link_to "QR Code (Full Screen)" , "/tournaments/#{@tournament.id}/qrcode?print=true" , target: :_blank %></li>
<li><%= link_to "Deduct Team Points" , "/tournaments/#{@tournament.id}/teampointadjust" %></li>
<li><%= link_to "View All Mat Assignment Rules", tournament_mat_assignment_rules_path(@tournament) %></li> <li><%= link_to "View All Mat Assignment Rules", tournament_mat_assignment_rules_path(@tournament) %></li>
<li><%= link_to 'Manage Backups', tournament_tournament_backups_path(@tournament) %></li> <li><%= link_to 'Manage Backups', tournament_tournament_backups_path(@tournament) %></li>
<li><%= link_to "Reset Bout Board", reset_bout_board_tournament_path(@tournament), method: :post, data: { confirm: "Are you sure you want to reset the bout board?" } %></li> <li><%= link_to "Reset Bout Board", reset_bout_board_tournament_path(@tournament), data: { turbo_method: :post, turbo_confirm: "Are you sure you want to reset the bout board?" } %></li>
<% if can? :destroy, @tournament %> <% if can? :destroy, @tournament %>
<li><%= link_to "Tournament Delegation" , "/tournaments/#{@tournament.id}/delegate" %></li> <li><%= link_to "Tournament Delegation" , "/tournaments/#{@tournament.id}/delegate" %></li>
<% end %> <% end %>
@@ -55,13 +56,13 @@
<% end %> <% end %>
<% end %> <% end %>
<li><strong>Time Savers</strong></li> <li><strong>Time Savers</strong></li>
<li><%= link_to "Create Boys High School Weights (106-285)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::HS_WEIGHT_CLASSES}",data: { confirm: 'Are you sure? This will delete all current weights.' } %></li> <li><%= link_to "Create Boys High School Weights (106-285)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::HS_WEIGHT_CLASSES}",data: { turbo_method: :get, turbo_confirm: 'Are you sure? This will delete all current weights.' } %></li>
<li><%= link_to "Create Girls High School Weights (100-235)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::HS_GIRLS_WEIGHT_CLASSES}",data: { confirm: 'Are you sure? This will delete all current weights.' } %></li> <li><%= link_to "Create Girls High School Weights (100-235)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::HS_GIRLS_WEIGHT_CLASSES}",data: { turbo_method: :get, turbo_confirm: 'Are you sure? This will delete all current weights.' } %></li>
<li><%= link_to "Create Boys Middle School Weights (80-245)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::MS_WEIGHT_CLASSES}",data: { confirm: 'Are you sure? This will delete all current weights.' } %></li> <li><%= link_to "Create Boys Middle School Weights (80-245)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::MS_WEIGHT_CLASSES}",data: { turbo_method: :get, turbo_confirm: 'Are you sure? This will delete all current weights.' } %></li>
<li><%= link_to "Create Girls Middle School Weights (72-235)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::MS_GIRLS_WEIGHT_CLASSES}",data: { confirm: 'Are you sure? This will delete all current weights.' } %></li> <li><%= link_to "Create Girls Middle School Weights (72-235)" , "/tournaments/#{@tournament.id}/create_custom_weights?customValue=#{Weight::MS_GIRLS_WEIGHT_CLASSES}",data: { turbo_method: :get, turbo_confirm: 'Are you sure? This will delete all current weights.' } %></li>
<li><strong>Tournament Actions</strong></li> <li><strong>Tournament Actions</strong></li>
<li><%= link_to "Calculate Team Scores" , "/tournaments/#{@tournament.id}/calculate_team_scores", :method => :put %></li> <li><%= link_to "Calculate Team Scores" , "/tournaments/#{@tournament.id}/calculate_team_scores", data: { turbo_method: :put } %></li>
<li><%= link_to "Generate Brackets" , "/tournaments/#{@tournament.id}/generate_matches", data: { confirm: 'Are you sure? This will delete all current matches.' } %></li> <li><%= link_to "Generate Brackets" , "/tournaments/#{@tournament.id}/generate_matches", data: { turbo_method: :get, turbo_confirm: 'Are you sure? This will delete all current matches.' } %></li>
<li><%= link_to "Export Data" , "/tournaments/#{@tournament.id}/export?print=true", target: :_blank %></li> <li><%= link_to "Export Data" , "/tournaments/#{@tournament.id}/export?print=true", target: :_blank %></li>
</ul> </ul>
<% end %> <% end %>
@@ -69,4 +70,4 @@
</div> </div>
</div> </div>
</nav> </nav>
<% end %> <% end %>

View File

@@ -36,7 +36,11 @@
<div id="page-content"> <div id="page-content">
<div class="row"> <div class="row">
<div class="col-md-12"><%= render 'layouts/underheader' %></div> <div class="col-md-12">
<% unless hide_ads? %>
<%= render 'layouts/underheader' %>
<% end %>
</div>
</div> </div>
<div class="row no-margin"> <div class="row no-margin">
<div class="col-md-12" style="padding-left: 2%;"> <div class="col-md-12" style="padding-left: 2%;">
@@ -58,4 +62,3 @@
</body> </body>
<% end %> <% end %>
</html> </html>

View File

@@ -23,7 +23,7 @@
<td><%= Array(rule.rounds).join(", ") %></td> <td><%= Array(rule.rounds).join(", ") %></td>
<td> <td>
<%= link_to '', edit_tournament_mat_assignment_rule_path(@tournament, rule), class: "fas fa-edit" %> <%= link_to '', edit_tournament_mat_assignment_rule_path(@tournament, rule), class: "fas fa-edit" %>
<%= link_to '', tournament_mat_assignment_rule_path(@tournament, rule), method: :delete, data: { confirm: "Are you sure?" }, class: "fas fa-trash-alt" %> <%= link_to '', tournament_mat_assignment_rule_path(@tournament, rule), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, class: "fas fa-trash-alt" %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

View File

@@ -0,0 +1,34 @@
<h1>Assign Mat/Queue for Match <%= @match.bout_number %></h1>
<% if @current_mat %>
<p>Current Assignment: Mat <%= @current_mat.name %><%= @current_queue_position ? " (Queue #{@current_queue_position})" : "" %></p>
<% else %>
<p>Current Assignment: Unassigned</p>
<% end %>
<%= form_with model: @match, url: update_assignment_match_path(@match), method: :patch do |f| %>
<div class="field">
<%= f.label :mat_id, "Mat" %><br>
<%= f.collection_select :mat_id, @mats, :id, :name, { include_blank: "Unassigned" } %>
</div>
<br>
<div class="field">
<%= f.label :queue_position, "Queue Position" %><br>
<%= f.select :queue_position,
options_for_select(
[
["On Mat (Queue 1)", 1],
["On Deck (Queue 2)", 2],
["In The Hole (Queue 3)", 3],
["Warm Up (Queue 4)", 4]
],
@current_queue_position
),
include_blank: "Select position"
%>
</div>
<br>
<div class="actions">
<%= f.submit "Update Assignment", class: "btn btn-success" %>
</div>
<% end %>

View File

@@ -0,0 +1,37 @@
<% @mat = mat %>
<% @match = local_assigns[:match] || mat.queue1_match %>
<% @next_match = local_assigns[:next_match] || mat.queue2_match %>
<% @show_next_bout_button = local_assigns.key?(:show_next_bout_button) ? local_assigns[:show_next_bout_button] : true %>
<% @wrestlers = [] %>
<% if @match %>
<% if @match.w1 %>
<% @wrestler1_name = @match.wrestler1.name %>
<% @wrestler1_school_name = @match.wrestler1.school.name %>
<% @wrestler1_last_match = @match.wrestler1.last_match %>
<% @wrestlers.push(@match.wrestler1) %>
<% else %>
<% @wrestler1_name = "Not assigned" %>
<% @wrestler1_school_name = "N/A" %>
<% @wrestler1_last_match = nil %>
<% end %>
<% if @match.w2 %>
<% @wrestler2_name = @match.wrestler2.name %>
<% @wrestler2_school_name = @match.wrestler2.school.name %>
<% @wrestler2_last_match = @match.wrestler2.last_match %>
<% @wrestlers.push(@match.wrestler2) %>
<% else %>
<% @wrestler2_name = "Not assigned" %>
<% @wrestler2_school_name = "N/A" %>
<% @wrestler2_last_match = nil %>
<% end %>
<% @tournament = @match.tournament %>
<% end %>
<% if @match %>
<%= render "matches/matchstats" %>
<% else %>
<p>No matches assigned to this mat.</p>
<% end %>

View File

@@ -1,9 +1,12 @@
<h3>Mat <%= @mat.name %></h3> <h3>Mat <%= @mat.name %></h3>
<h3>Tournament: <%= @mat.tournament.name %></h3> <h3>Tournament: <%= @mat.tournament.name %></h3>
<% if @match %> <%= turbo_stream_from @mat %>
<%= render 'matches/matchstats' %>
<% else %>
<p>No matches assigned to this mat.</p>
<% end %>
<%= turbo_frame_tag dom_id(@mat, :current_match) do %>
<%= render "mats/current_match",
mat: @mat,
match: @match,
next_match: @next_match,
show_next_bout_button: @show_next_bout_button %>
<% end %>

View File

@@ -76,8 +76,12 @@
<% delete_wrestler_path_with_key = wrestler_path(wrestler) %> <% delete_wrestler_path_with_key = wrestler_path(wrestler) %>
<% delete_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %> <% delete_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
<%= link_to '', edit_wrestler_path_with_key, class: "fas fa-edit" %> <%= link_to edit_wrestler_path_with_key, class: "text-decoration-none" do %>
<%= link_to '', delete_wrestler_path_with_key, method: :delete, data: { confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "fas fa-trash-alt" %> <span class="fas fa-edit" aria-hidden="true"></span>
<% end %>
<%= link_to delete_wrestler_path_with_key, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "text-decoration-none" do %>
<span class="fas fa-trash-alt" aria-hidden="true"></span>
<% end %>
</td> </td>
<% end %> <% end %>
</tr> </tr>
@@ -101,8 +105,12 @@
<% delete_wrestler_path_with_key = wrestler_path(wrestler) %> <% delete_wrestler_path_with_key = wrestler_path(wrestler) %>
<% delete_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %> <% delete_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
<%= link_to '', edit_wrestler_path_with_key, class: "fas fa-edit" %> <%= link_to edit_wrestler_path_with_key, class: "text-decoration-none" do %>
<%= link_to '', delete_wrestler_path_with_key, method: :delete, data: { confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "fas fa-trash-alt" %> <span class="fas fa-edit" aria-hidden="true"></span>
<% end %>
<%= link_to delete_wrestler_path_with_key, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "text-decoration-none" do %>
<span class="fas fa-trash-alt" aria-hidden="true"></span>
<% end %>
</td> </td>
<% end %> <% end %>
</tr> </tr>
@@ -111,6 +119,6 @@
</tbody> </tbody>
</table> </table>
<% if can? :manage, @school %> <%# if can? :manage, @school %>
<%= render 'baums_roster_import' %> <%#= render 'baums_roster_import' %>
<% end %> <%# end %>

View File

@@ -24,7 +24,7 @@
<td> <td>
<%= link_to '', edit_tournament_path(tournament), :class=>"fas fa-edit" %> <%= link_to '', edit_tournament_path(tournament), :class=>"fas fa-edit" %>
<% if can? :destroy, tournament %> <% if can? :destroy, tournament %>
<%= link_to '', tournament, method: :delete, data: { confirm: "Are you sure you want to delete #{tournament.name}?" }, :class=>"fas fa-trash-alt" %> <%= link_to '', tournament, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{tournament.name}?" }, :class=>"fas fa-trash-alt" %>
<% end %> <% end %>
</td> </td>
<% end %> <% end %>
@@ -55,4 +55,4 @@
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<% end %> <% end %>

View File

@@ -8,7 +8,7 @@ and will also delete all of your current data. It's best to use the create backu
<tr> <tr>
<th>Backup Created At</th> <th>Backup Created At</th>
<th>Backup Reason</th> <th>Backup Reason</th>
<th><%= link_to ' Create New Backup', tournament_tournament_backups_path(@tournament), method: :post, class: 'fas fa-plus'%></th> <th><%= link_to ' Create New Backup', tournament_tournament_backups_path(@tournament), data: { turbo_method: :post }, class: 'fas fa-plus'%></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -19,20 +19,20 @@ and will also delete all of your current data. It's best to use the create backu
</td> </td>
<td><%= backup.backup_reason.presence || 'No reason provided' %></td> <td><%= backup.backup_reason.presence || 'No reason provided' %></td>
<td> <td>
<%= link_to '', restore_tournament_tournament_backup_path(@tournament, backup), method: :post, data: { confirm: "This will restore the backup from #{backup.created_at.strftime('%Y-%m-%d %H:%M:%S')}. It will delete all current data from the tournament in order to restore the backup." }, class: 'fas fa-undo-alt text-warning', title: 'Restore Backup' %> <%= link_to '', restore_tournament_tournament_backup_path(@tournament, backup), data: { turbo_method: :post, turbo_confirm: "This will restore the backup from #{backup.created_at.strftime('%Y-%m-%d %H:%M:%S')}. It will delete all current data from the tournament in order to restore the backup." }, class: 'fas fa-undo-alt text-warning', title: 'Restore Backup' %>
<%= link_to '', tournament_tournament_backup_path(@tournament, backup), method: :delete, data: { confirm: 'Are you sure you want to delete this backup?' }, class: 'fas fa-trash-alt', title: 'Delete Backup' %> <%= link_to '', tournament_tournament_backup_path(@tournament, backup), data: { turbo_method: :delete, turbo_confirm: 'Are you sure you want to delete this backup?' }, class: 'fas fa-trash-alt', title: 'Delete Backup' %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<br><br> <br><br>
<h3>Import Manual Backup</h3> <% if ENV["RAILS_ENV"] == "development" %>
<p>Paste the backup text here. Note, if this is formatted wrong, you'll need to restore a backup from above to fix it and you'll see an error in your background jobs.</p> <%= form_for(:tournament, url: import_manual_tournament_tournament_backups_path(@tournament)) do |f| %>
<%= form_for(:tournament, url: import_manual_tournament_tournament_backups_path(@tournament)) do |f| %> <div class="field">
<div class="field"> <%= f.label 'Import text' %><br>
<%= f.label 'Import text' %><br> <%= f.text_area :import_text, cols: "30", rows: "20" %>
<%= f.text_area :import_text, cols: "30", rows: "20" %> </div>
</div> <%= submit_tag "Import", class: "btn btn-success", data: { turbo_confirm: 'Are you sure? This will delete everything for the current tournament and restore it with the backup text pasted below.' } %>
<%= submit_tag "Import", class: "btn btn-success", data: { confirm: 'Are you sure? This will delete everything for the current tournament and restore it with the backup text pasted below.' } %> <% end %>
<% end %> <% end %>

View File

@@ -2,7 +2,11 @@
<div class="round"> <div class="round">
<div class="game"> <div class="game">
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div> <div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
<div class="bout-number"><p><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div> <% if params[:print] %>
<div class="bout-number"><p><%= match.bout_number %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div>
<% else %>
<div class="bout-number"><p><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div>
<% end %>
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div> <div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
</div> </div>
</div> </div>

View File

@@ -11,7 +11,7 @@ table.smallText tr td { font-size: 10px; }
min-width: 150px; min-width: 150px;
min-height: 50px; min-height: 50px;
/*background-color: #ddd;*/ /*background-color: #ddd;*/
border: 1px solid #ddd; border: 1px solid #000; /* Dark border so boxes stay visible when printed */
margin: 5px; margin: 5px;
} }
@@ -56,7 +56,7 @@ table.smallText tr td { font-size: 10px; }
} }
.game-top { .game-top {
border-bottom:1px solid #ddd; border-bottom:1px solid #000;
padding: 2px; padding: 2px;
min-height: 12px; min-height: 12px;
} }
@@ -77,13 +77,13 @@ table.smallText tr td { font-size: 10px; }
} }
.bracket-winner { .bracket-winner {
border-bottom:1px solid #ddd; border-bottom:1px solid #000;
padding: 2px; padding: 2px;
min-height: 12px; min-height: 12px;
} }
.game-bottom { .game-bottom {
border-top:1px solid #ddd; border-top:1px solid #000;
padding: 2px; padding: 2px;
min-height: 12px; min-height: 12px;
} }
@@ -131,4 +131,4 @@ table.smallText tr td { font-size: 10px; }
<% elsif @tournament.tournament_type.include? "Regular Double Elimination" %> <% elsif @tournament.tournament_type.include? "Regular Double Elimination" %>
<%= render 'double_elimination_bracket' %> <%= render 'double_elimination_bracket' %>
<% end %> <% end %>

View File

@@ -2,7 +2,13 @@
<% @round_matches.sort_by{|m| m.bracket_position_number}.each do |match| %> <% @round_matches.sort_by{|m| m.bracket_position_number}.each do |match| %>
<div class="game"> <div class="game">
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div> <div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
<div class="bout-number"><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %>&nbsp;</div> <% if params[:print] %>
<div class="bout-number"><%= match.bout_number %> <%= match.bracket_score_string %>&nbsp;</div>
<% else %>
<div class="bout-number"><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %>&nbsp;</div>
<% end %>
<div class="bout-number">Round <%= match.round %></div>
<div class="bout-number"><%= match.bracket_position %></div>
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div> <div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
</div> </div>
<% end %> <% end %>

View File

@@ -0,0 +1,34 @@
<% queue1_match, queue2_match, queue3_match, queue4_match = mat.queue_matches %>
<% cache ["up_matches_mat_row", mat, mat.queue1, mat.queue2, mat.queue3, mat.queue4] do %>
<tr>
<td><%= mat.name %></td>
<td>
<% if queue1_match %><strong><%= queue1_match.bout_number %></strong> (<%= queue1_match.bracket_position %>)<br>
<%= queue1_match.weight_max %> lbs
<br><%= queue1_match.w1_bracket_name %> vs. <br>
<%= queue1_match.w2_bracket_name %>
<% end %>
</td>
<td>
<% if queue2_match %><strong><%= queue2_match.bout_number %></strong> (<%= queue2_match.bracket_position %>)<br>
<%= queue2_match.weight_max %> lbs
<br><%= queue2_match.w1_bracket_name %> vs. <br>
<%= queue2_match.w2_bracket_name %>
<% end %>
</td>
<td>
<% if queue3_match %><strong><%= queue3_match.bout_number %></strong> (<%= queue3_match.bracket_position %>)<br>
<%= queue3_match.weight_max %> lbs
<br><%= queue3_match.w1_bracket_name %> vs. <br>
<%= queue3_match.w2_bracket_name %>
<% end %>
</td>
<td>
<% if queue4_match %><strong><%= queue4_match.bout_number %></strong> (<%= queue4_match.bracket_position %>)<br>
<%= queue4_match.weight_max %> lbs
<br><%= queue4_match.w1_bracket_name %> vs. <br>
<%= queue4_match.w2_bracket_name %>
<% end %>
</td>
</tr>
<% end %>

View File

@@ -42,10 +42,10 @@
<% @users_delegates.each do |delegate| %> <% @users_delegates.each do |delegate| %>
<tr> <tr>
<td><%= delegate.user.email %></td> <td><%= delegate.user.email %></td>
<td><%= link_to 'Remove permissions', "/tournaments/#{@tournament.id}/#{delegate.id}/remove_delegate", method: :delete, confirm: 'Are you sure?', :class=>"btn btn-danger btn-sm" %></td> <td><%= link_to 'Remove permissions', "/tournaments/#{@tournament.id}/#{delegate.id}/remove_delegate", data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' }, :class=>"btn btn-danger btn-sm" %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<% end %> <% end %>

View File

@@ -3,7 +3,11 @@
"attributes": <%= @tournament.attributes.to_json %>, "attributes": <%= @tournament.attributes.to_json %>,
"schools": <%= @tournament.schools.map(&:attributes).to_json %>, "schools": <%= @tournament.schools.map(&:attributes).to_json %>,
"weights": <%= @tournament.weights.map(&:attributes).to_json %>, "weights": <%= @tournament.weights.map(&:attributes).to_json %>,
"mats": <%= @tournament.mats.map(&:attributes).to_json %>, "mats": <%= @tournament.mats.map { |mat| mat.attributes.merge(
{
"queue_bout_numbers": mat.queue_matches.map { |match| match&.bout_number }
}
) }.to_json %>,
"wrestlers": <%= @tournament.wrestlers.map { |wrestler| wrestler.attributes.merge( "wrestlers": <%= @tournament.wrestlers.map { |wrestler| wrestler.attributes.merge(
{ {
"school": wrestler.school&.attributes, "school": wrestler.school&.attributes,
@@ -20,4 +24,4 @@
} }
) }.to_json %> ) }.to_json %>
} }
} }

View File

@@ -29,7 +29,7 @@
<% if can? :manage, tournament %> <% if can? :manage, tournament %>
<%= link_to '', edit_tournament_path(tournament), :class=>"fas fa-edit" %> <%= link_to '', edit_tournament_path(tournament), :class=>"fas fa-edit" %>
<% if can? :destroy, tournament %> <% if can? :destroy, tournament %>
<%= link_to '', tournament, method: :delete, data: { confirm: "Are you sure you want to delete #{tournament.name}?" }, :class=>"fas fa-trash-alt" %> <%= link_to '', tournament, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{tournament.name}?" }, :class=>"fas fa-trash-alt" %>
<% end %> <% end %>
<% end %> <% end %>
</td> </td>
@@ -83,4 +83,3 @@
Showing <%= start_index %> - <%= end_index %> of <%= @total_count %> tournaments Showing <%= start_index %> - <%= end_index %> of <%= @total_count %> tournaments
</p> </p>
<% end %> <% end %>

View File

@@ -28,9 +28,13 @@
<td><%= match.finished %></td> <td><%= match.finished %></td>
<td><%= link_to 'Show', match, :class=>"btn btn-default btn-sm" %> <td><%= link_to 'Show', match, :class=>"btn btn-default btn-sm" %>
<%= link_to 'Edit Wrestlers', edit_match_path(match), :class=>"btn btn-primary btn-sm" %> <%= link_to 'Edit Wrestlers', edit_match_path(match), :class=>"btn btn-primary btn-sm" %>
<%= link_to 'Edit Mat/Queue', edit_assignment_match_path(match), :class=>"btn btn-primary btn-sm" %>
<%= link_to 'Stat Match', "/matches/#{match.id}/stat", :class=>"btn btn-primary btn-sm" %> <%= link_to 'Stat Match', "/matches/#{match.id}/stat", :class=>"btn btn-primary btn-sm" %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<br>
<p>Total matches without byes: <%= @matches.select{|m| m.loser1_name != 'BYE' and m.loser2_name != 'BYE'}.size %></p>
<p>Unfinished matches: <%= @matches.select{|m| m.finished != 1 and m.loser1_name != 'BYE' and m.loser2_name != 'BYE'}.size %></p>

View File

@@ -0,0 +1,50 @@
<style>
.qr-page {
min-height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 24px;
}
.qr-page h1 {
margin: 0 0 24px 0;
font-size: 40px;
font-weight: 700;
}
.qr-code {
width: 100%;
display: flex;
justify-content: center;
}
.qr-code svg {
width: min(80vmin, 720px);
height: auto;
display: block;
margin: 0;
}
@media print {
body {
margin: 0;
}
}
</style>
<div class="qr-page">
<h1><%= @tournament.name %> Brackets and Results Available Here</h1>
<div class="qr-code">
<%= raw @qrcode.as_svg(
offset: 0,
color: "000",
shape_rendering: "crispEdges",
module_size: 8,
standalone: true
) %>
</div>
</div>

View File

@@ -78,7 +78,7 @@
<tr> <tr>
<td><%= delegate.user.email %></td> <td><%= delegate.user.email %></td>
<td><%= delegate.school.name %></td> <td><%= delegate.school.name %></td>
<td><%= link_to 'Remove permissions', "/tournaments/#{@tournament.id}/#{delegate.id}/remove_school_delegate", method: :delete, confirm: 'Are you sure?', :class=>"btn btn-danger btn-sm" %></td> <td><%= link_to 'Remove permissions', "/tournaments/#{@tournament.id}/#{delegate.id}/remove_school_delegate", data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' }, :class=>"btn btn-danger btn-sm" %></td>
</tr> </tr>
<% end %> <% end %>

View File

@@ -70,9 +70,13 @@
</td> </td>
<td> <td>
<% if can? :manage, school %> <% if can? :manage, school %>
<%= link_to '', edit_school_path(school), :class=>"fas fa-edit" %> <%= link_to edit_school_path(school), class: "text-decoration-none" do %>
<span class="fas fa-edit" aria-hidden="true"></span>
<% end %>
<% if can? :manage, @tournament %> <% if can? :manage, @tournament %>
<%= link_to '', school, method: :delete, data: { confirm: "Are you sure you want to delete #{school.name}?" }, :class=>"fas fa-trash-alt" %> <%= link_to school, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{school.name}?" }, class: "text-decoration-none" do %>
<span class="fas fa-trash-alt" aria-hidden="true"></span>
<% end %>
<% end %> <% end %>
<% end %> <% end %>
</td> </td>
@@ -105,8 +109,12 @@
<td><%= weight.bracket_size %></td> <td><%= weight.bracket_size %></td>
<% if can? :manage, @tournament %> <% if can? :manage, @tournament %>
<td> <td>
<%= link_to '', edit_weight_path(weight), :class=>"fas fa-edit" %> <%= link_to edit_weight_path(weight), class: "text-decoration-none" do %>
<%= link_to '', weight, method: :delete, data: { confirm: "Are you sure you want to delete the #{weight.max} weight class?" }, :class=>"fas fa-trash-alt" %> <span class="fas fa-edit" aria-hidden="true"></span>
<% end %>
<%= link_to weight, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete the #{weight.max} weight class?" }, class: "text-decoration-none" do %>
<span class="fas fa-trash-alt" aria-hidden="true"></span>
<% end %>
</td> </td>
<% end %> <% end %>
</tr> </tr>
@@ -130,8 +138,10 @@
<td><%= link_to "Mat #{mat.name}", mat %></td> <td><%= link_to "Mat #{mat.name}", mat %></td>
<% if can? :manage, @tournament %> <% if can? :manage, @tournament %>
<td> <td>
<%= link_to '', mat, method: :delete, data: { confirm: "Are you sure you want to delete Mat #{mat.name}?" }, :class=>"fas fa-trash-alt" %> <%= link_to mat, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete Mat #{mat.name}?" }, class: "text-decoration-none" do %>
<%= link_to '', "/mats/#{mat.id}/assign_next_match", method: :post, :class=>"fas fa-solid fa-arrow-right" %> <span class="fas fa-trash-alt" aria-hidden="true"></span>
<% end %>
<%= link_to '', "/mats/#{mat.id}/assign_next_match", data: { turbo_method: :post }, :class=>"fas fa-solid fa-arrow-right" %>
</td> </td>
<% end %> <% end %>
</tr> </tr>

View File

@@ -47,10 +47,10 @@
<% end %> <% end %>
</td> </td>
<td><%= point_adjustment.points %></td> <td><%= point_adjustment.points %></td>
<td><%= link_to 'Remove Point Adjustment', "/tournaments/#{@tournament.id}/#{point_adjustment.id}/remove_teampointadjust", method: :delete, confirm: 'Are you sure?', :class=>"btn btn-danger btn-sm" %></td> <td><%= link_to 'Remove Point Adjustment', "/tournaments/#{@tournament.id}/#{point_adjustment.id}/remove_teampointadjust", data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' }, :class=>"btn btn-danger btn-sm" %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<% end %> <% end %>

View File

@@ -1,11 +1,26 @@
<% cache ["#{@tournament.id}_up_matches", @tournament] do %>
<script> <script>
// $(document).ready(function() { // $(document).ready(function() {
// $('#matchList').dataTable(); // $('#matchList').dataTable();
// } ); // } );
</script> </script>
<script> <script>
setTimeout("location.reload(true);",30000); const setUpMatchesRefresh = () => {
if (window.__upMatchesRefreshTimeout) {
clearTimeout(window.__upMatchesRefreshTimeout);
}
window.__upMatchesRefreshTimeout = setTimeout(() => {
window.location.reload(true);
}, 30000);
};
document.addEventListener("turbo:load", setUpMatchesRefresh);
// turbo:before-cache stops the timer refresh from occurring if you navigate away from up_matches
document.addEventListener("turbo:before-cache", () => {
if (window.__upMatchesRefreshTimeout) {
clearTimeout(window.__upMatchesRefreshTimeout);
window.__upMatchesRefreshTimeout = null;
}
});
</script> </script>
<br> <br>
<br> <br>
@@ -26,13 +41,7 @@
<tbody> <tbody>
<% @mats.each.map do |m| %> <% @mats.each.map do |m| %>
<tr> <%= render "up_matches_mat_row", mat: m %>
<td><%= m.name %></td>
<td><% if m.unfinished_matches.first %><strong><%=m.unfinished_matches.first.bout_number%></strong> - <%= m.unfinished_matches.first.weight_max %><br><%= m.unfinished_matches.first.w1_bracket_name %> vs. <%= m.unfinished_matches.first.w2_bracket_name %><% end %></td>
<td><% if m.unfinished_matches.second %><strong><%=m.unfinished_matches.second.bout_number%></strong> - <%= m.unfinished_matches.second.weight_max %><br><%= m.unfinished_matches.second.w1_bracket_name %> vs. <%= m.unfinished_matches.second.w2_bracket_name %><% end %></td>
<td><% if m.unfinished_matches.third %><strong><%=m.unfinished_matches.third.bout_number%></strong> - <%= m.unfinished_matches.third.weight_max %><br><%= m.unfinished_matches.third.w1_bracket_name %> vs. <%= m.unfinished_matches.third.w2_bracket_name %><% end %></td>
<td><% if m.unfinished_matches.fourth %><strong><%=m.unfinished_matches.fourth.bout_number%></strong> - <%= m.unfinished_matches.fourth.weight_max %><br><%= m.unfinished_matches.fourth.w1_bracket_name %> vs. <%= m.unfinished_matches.fourth.w2_bracket_name %><% end %></td>
</tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
@@ -64,4 +73,3 @@
</table> </table>
<br> <br>
<% end %>

View File

@@ -1,48 +1,50 @@
<h3>Weight Class:<%= @weight.max %> <% if can? :manage, @tournament %><%= link_to " Edit", edit_weight_path(@weight), :class=>"fas fa-edit" %><% end %></h3> <h3>Weight Class:<%= @weight.max %> <% if can? :manage, @tournament %><%= link_to edit_weight_path(@weight), class: "text-decoration-none" do %><span class="fas fa-edit" aria-hidden="true"></span><% end %><% end %></h3>
<br> <br>
<br> <br>
<table class="table table-hover table-condensed"> <%= form_tag @wrestlers_update_path do %>
<thead> <table class="table table-hover table-condensed">
<tr> <thead>
<th>Name</th> <tr>
<th>School</th> <th>Name</th>
<th>Seed</th> <th>School</th>
<th>Record</th> <th>Seed</th>
<th>Seed Criteria</th> <th>Record</th>
<th>Extra?</th> <th>Seed Criteria</th>
</tr> <th>Extra?</th>
</thead> </tr>
<tbody> </thead>
<%= form_tag @wrestlers_update_path do %> <tbody>
<% @wrestlers.sort_by{|w| [w.original_seed ? 0 : 1, w.original_seed || 0]}.each do |wrestler| %> <% @wrestlers.sort_by{|w| [w.original_seed ? 0 : 1, w.original_seed || 0]}.each do |wrestler| %>
<% if wrestler.weight_id == @weight.id %> <% if wrestler.weight_id == @weight.id %>
<tr> <tr>
<td><%= link_to "#{wrestler.name}", wrestler %></td> <td><%= link_to "#{wrestler.name}", wrestler %></td>
<td><%= wrestler.school.name %></td> <td><%= wrestler.school.name %></td>
<td> <td>
<% if can? :manage, @tournament %> <% if can? :manage, @tournament %>
<%= fields_for "wrestler[]", wrestler do |w| %> <%= fields_for "wrestler[]", wrestler do |w| %>
<%= w.text_field :original_seed %> <%= w.text_field :original_seed %>
<% end %> <% end %>
<% else %> <% else %>
<%= wrestler.original_seed %> <%= wrestler.original_seed %>
<% end %> <% end %>
</td>
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
<td><% if wrestler.extra? == true %>
Yes
<% end %></td>
<% if can? :manage, @tournament %>
<td>
<%= link_to '', wrestler, method: :delete, data: { confirm: "Are you sure you want to delete #{wrestler.name}? THIS WILL DELETE ALL MATCHES." } , :class=>"fas fa-trash-alt" %>
</td> </td>
<% end %> <td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
</tr> <td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
<% end %> <td><% if wrestler.extra? == true %>
<% end %> Yes
</tbody> <% end %></td>
</table> <% if can? :manage, @tournament %>
<td>
<%= link_to wrestler, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? THIS WILL DELETE ALL MATCHES." }, class: "text-decoration-none" do %>
<span class="fas fa-trash-alt" aria-hidden="true"></span>
<% end %>
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<br><p>*All wrestlers without a seed (determined by tournament director) will be assigned a random bracket line.</p> <br><p>*All wrestlers without a seed (determined by tournament director) will be assigned a random bracket line.</p>
<% if can? :manage, @tournament %> <% if can? :manage, @tournament %>
<br> <br>
@@ -81,4 +83,4 @@
</ul> </ul>
</li> </li>
</div> </div>
<% end %> <% end %>

6
bin/bundler-audit Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
require_relative "../config/boot"
require "bundler/audit/cli"
ARGV.concat %w[ --config config/bundler-audit.yml ] if ARGV.empty? || ARGV.include?("check")
Bundler::Audit::CLI.start

6
bin/ci Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
require_relative "../config/boot"
require "active_support/continuous_integration"
CI = ActiveSupport::ContinuousIntegration
require_relative "../config/ci.rb"

View File

@@ -1,4 +1,2 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
exec "./bin/rails", "server", *ARGV
# Start Rails server with defaults
exec "bin/rails", "server", *ARGV

View File

@@ -2,7 +2,7 @@
require "rubygems" require "rubygems"
require "bundler/setup" require "bundler/setup"
# explicit rubocop config increases performance slightly while avoiding config confusion. # Explicit RuboCop config increases performance slightly while avoiding config confusion.
ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
load Gem.bin_path("rubocop", "rubocop") load Gem.bin_path("rubocop", "rubocop")

View File

@@ -4,5 +4,5 @@ project_dir="$(dirname $( dirname $(readlink -f ${BASH_SOURCE[0]})))"
cd ${project_dir} cd ${project_dir}
bundle exec rake db:migrate RAILS_ENV=test bundle exec rake db:migrate RAILS_ENV=test
CI=true brakeman CI=true brakeman
bundle exec bundle-audit check --update bundle audit
bundle exec rake test rails test -v

View File

@@ -22,6 +22,7 @@ FileUtils.chdir APP_ROOT do
puts "\n== Preparing database ==" puts "\n== Preparing database =="
system! "bin/rails db:prepare" system! "bin/rails db:prepare"
system! "bin/rails db:reset" if ARGV.include?("--reset")
puts "\n== Removing old logs and tempfiles ==" puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear" system! "bin/rails log:clear tmp:clear"

View File

@@ -25,9 +25,6 @@ module Wrestling
config.active_record.schema_format = :ruby config.active_record.schema_format = :ruby
config.active_record.dump_schemas = :all config.active_record.dump_schemas = :all
# Fix deprecation warning for to_time in Rails 8.1
config.active_support.to_time_preserves_timezone = :zone
# Please, add to the `ignore` list any other `lib` subdirectories that do # Please, add to the `ignore` list any other `lib` subdirectories that do
# not contain `.rb` files, or that should not be reloaded or eager loaded. # not contain `.rb` files, or that should not be reloaded or eager loaded.
# Common ones are `templates`, `generators`, or `middleware`, for example. # Common ones are `templates`, `generators`, or `middleware`, for example.
@@ -55,5 +52,7 @@ module Wrestling
# Set cache format version to a value supported by Rails 8.0 # Set cache format version to a value supported by Rails 8.0
# Valid values are 7.0 or 7.1 # Valid values are 7.0 or 7.1
config.active_support.cache_format_version = 7.1 config.active_support.cache_format_version = 7.1
config.load_defaults 8.1
end end
end end

5
config/bundler-audit.yml Normal file
View File

@@ -0,0 +1,5 @@
# Audit all gems listed in the Gemfile for known security problems by running bin/bundler-audit.
# CVEs that are not relevant to the application can be enumerated on the ignore list below.
ignore:
- CVE-THAT-DOES-NOT-APPLY

24
config/ci.rb Normal file
View File

@@ -0,0 +1,24 @@
# Run using bin/ci
CI.run do
step "Setup", "bin/setup --skip-server"
step "Style: Ruby", "bin/rubocop"
step "Security: Gem audit", "bin/bundler-audit"
step "Security: Importmap vulnerability audit", "bin/importmap audit"
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
step "Tests: Rails", "bin/rails test"
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
# Optional: Run system tests
# step "Tests: System", "bin/rails test:system"
# Optional: set a green GitHub commit status to unblock PR merge.
# Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.
# if success?
# step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
# else
# failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
# end
end

View File

@@ -99,4 +99,7 @@ Rails.application.configure do
# Nobuild in development # Nobuild in development
config.assets.build_assets = false config.assets.build_assets = false
MissionControl::Jobs.http_basic_auth_user = "dev"
MissionControl::Jobs.http_basic_auth_password = "secret"
end end

View File

@@ -120,4 +120,7 @@ Rails.application.configure do
config.assets.compile = true config.assets.compile = true
# Generate digests for assets URLs. # Generate digests for assets URLs.
config.assets.digest = true config.assets.digest = true
MissionControl::Jobs.http_basic_auth_user = ENV["WRESTLINGDEV_MISSION_CONTROL_USER"]
MissionControl::Jobs.http_basic_auth_password =ENV["WRESTLINGDEV_MISSION_CONTROL_PASSWORD"]
end end

View File

@@ -0,0 +1,7 @@
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path

View File

@@ -1,6 +1,7 @@
Wrestling::Application.routes.draw do Wrestling::Application.routes.draw do
# Mount Action Cable server # Mount Action Cable server
mount ActionCable.server => '/cable' mount ActionCable.server => '/cable'
mount MissionControl::Jobs::Engine, at: "/jobs"
resources :mats resources :mats
post "mats/:id/assign_next_match" => "mats#assign_next_match", :as => :assign_next_match post "mats/:id/assign_next_match" => "mats#assign_next_match", :as => :assign_next_match
@@ -9,6 +10,8 @@ Wrestling::Application.routes.draw do
member do member do
get :stat get :stat
get :spectate get :spectate
get :edit_assignment
patch :update_assignment
end end
end end
@@ -70,6 +73,7 @@ Wrestling::Application.routes.draw do
get 'tournaments/:id/bout_sheets' => 'tournaments#bout_sheets' get 'tournaments/:id/bout_sheets' => 'tournaments#bout_sheets'
get 'tournaments/:id/no_matches' => 'tournaments#no_matches' get 'tournaments/:id/no_matches' => 'tournaments#no_matches'
get 'tournaments/:id/matches' => 'tournaments#matches' get 'tournaments/:id/matches' => 'tournaments#matches'
get 'tournaments/:id/qrcode' => 'tournaments#qrcode'
get 'tournaments/:id/delegate' => 'tournaments#delegate', :as => :tournament_delegate get 'tournaments/:id/delegate' => 'tournaments#delegate', :as => :tournament_delegate
post 'tournaments/:id/delegate' => 'tournaments#delegate', :as => :set_tournament_delegate post 'tournaments/:id/delegate' => 'tournaments#delegate', :as => :set_tournament_delegate
delete 'tournaments/:id/:delegate/remove_delegate' => 'tournaments#remove_delegate', :as => :delete_delegate_path delete 'tournaments/:id/:delegate/remove_delegate' => 'tournaments#remove_delegate', :as => :delete_delegate_path

View File

@@ -0,0 +1,49 @@
class AddQueuesToMats < ActiveRecord::Migration[7.0]
class Mat < ActiveRecord::Base
self.table_name = "mats"
has_many :matches, class_name: "AddQueuesToMats::Match", foreign_key: "mat_id"
end
class Match < ActiveRecord::Base
self.table_name = "matches"
end
def up
add_column :mats, :queue1, :bigint
add_column :mats, :queue2, :bigint
add_column :mats, :queue3, :bigint
add_column :mats, :queue4, :bigint
add_index :mats, :queue1
add_index :mats, :queue2
add_index :mats, :queue3
add_index :mats, :queue4
say_with_time "Backfilling mat queues from unfinished matches" do
Mat.reset_column_information
Match.reset_column_information
Mat.find_each do |mat|
match_ids = mat.matches.where(finished: [nil, 0]).order(:bout_number).limit(4).pluck(:id)
mat.update_columns(
queue1: match_ids[0],
queue2: match_ids[1],
queue3: match_ids[2],
queue4: match_ids[3]
)
end
end
end
def down
remove_index :mats, :queue1
remove_index :mats, :queue2
remove_index :mats, :queue3
remove_index :mats, :queue4
remove_column :mats, :queue1
remove_column :mats, :queue2
remove_column :mats, :queue3
remove_column :mats, :queue4
end
end

View File

@@ -0,0 +1,10 @@
class IncreaseSolidQueueJobArgumentsLimit < ActiveRecord::Migration[8.0]
def up
# Allow large payloads (e.g., pasted import text) to be enqueued without blowing up MySQL's TEXT limit (~64KB).
change_column :solid_queue_jobs, :arguments, :text, limit: 16.megabytes - 1
end
def down
change_column :solid_queue_jobs, :arguments, :text
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_04_04_153529) do ActiveRecord::Schema[8.0].define(version: 2025_05_04_190000) do
create_table "solid_queue_blocked_executions", force: :cascade do |t| create_table "solid_queue_blocked_executions", force: :cascade do |t|
t.bigint "job_id", null: false t.bigint "job_id", null: false
t.string "queue_name", null: false t.string "queue_name", null: false
@@ -41,7 +41,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_04_153529) do
create_table "solid_queue_jobs", force: :cascade do |t| create_table "solid_queue_jobs", force: :cascade do |t|
t.string "queue_name", null: false t.string "queue_name", null: false
t.string "class_name", null: false t.string "class_name", null: false
t.text "arguments" t.text "arguments", limit: 16777215
t.integer "priority", default: 0, null: false t.integer "priority", default: 0, null: false
t.string "active_job_id" t.string "active_job_id"
t.datetime "scheduled_at" t.datetime "scheduled_at"

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_04_15_173921) do ActiveRecord::Schema[8.0].define(version: 2026_01_29_120000) do
create_table "mat_assignment_rules", force: :cascade do |t| create_table "mat_assignment_rules", force: :cascade do |t|
t.bigint "tournament_id" t.bigint "tournament_id"
t.bigint "mat_id" t.bigint "mat_id"
@@ -56,6 +56,14 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_15_173921) do
t.bigint "tournament_id" t.bigint "tournament_id"
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.bigint "queue1"
t.bigint "queue2"
t.bigint "queue3"
t.bigint "queue4"
t.index ["queue1"], name: "index_mats_on_queue1"
t.index ["queue2"], name: "index_mats_on_queue2"
t.index ["queue3"], name: "index_mats_on_queue3"
t.index ["queue4"], name: "index_mats_on_queue4"
t.index ["tournament_id"], name: "index_mats_on_tournament_id" t.index ["tournament_id"], name: "index_mats_on_tournament_id"
end end

View File

@@ -25,9 +25,11 @@ docker-compose -f ${project_dir}/deploy/docker-compose-test.yml run --rm app bin
docker-compose -f ${project_dir}/deploy/docker-compose-test.yml run --rm app bin/rails db:migrate:queue docker-compose -f ${project_dir}/deploy/docker-compose-test.yml run --rm app bin/rails db:migrate:queue
docker-compose -f ${project_dir}/deploy/docker-compose-test.yml run --rm app bin/rails db:migrate:cable docker-compose -f ${project_dir}/deploy/docker-compose-test.yml run --rm app bin/rails db:migrate:cable
# Start all services (will start app and others, db is already running) echo "Stopping all services..."
docker-compose -f ${project_dir}/deploy/docker-compose-test.yml down
echo "Starting application services..." echo "Starting application services..."
docker-compose -f ${project_dir}/deploy/docker-compose-test.yml up -d docker-compose -f ${project_dir}/deploy/docker-compose-test.yml up --force-recreate --remove-orphans -d
# DISABLE_DATABASE_ENVIRONMENT_CHECK=1 is needed because this is "destructive" action on production # DISABLE_DATABASE_ENVIRONMENT_CHECK=1 is needed because this is "destructive" action on production
echo Resetting the db with seed data echo Resetting the db with seed data

View File

@@ -26,6 +26,8 @@ services:
- WRESTLINGDEV_INFLUXDB_HOST=influxdb - WRESTLINGDEV_INFLUXDB_HOST=influxdb
- WRESTLINGDEV_INFLUXDB_PORT=8086 - WRESTLINGDEV_INFLUXDB_PORT=8086
- SOLID_QUEUE_IN_PUMA=true - SOLID_QUEUE_IN_PUMA=true
- WRESTLINGDEV_MISSION_CONTROL_USER=dev
- WRESTLINGDEV_MISSION_CONTROL_PASSWORD=secret
networks: networks:
database: database:
caching: caching:

View File

@@ -122,12 +122,12 @@ spec:
DUMP_FILE="/tmp/${DB_NAME}_backup.sql" DUMP_FILE="/tmp/${DB_NAME}_backup.sql"
echo "Dumping ${DB_NAME} from master ${MASTER_SERVICE_HOST}" | tee -a "$LOG" echo "Dumping ${DB_NAME} from master ${MASTER_SERVICE_HOST}" | tee -a "$LOG"
if command -v timeout >/dev/null 2>&1; then if command -v timeout >/dev/null 2>&1; then
if ! timeout 300 mysqldump --protocol=TCP -h "$MASTER_SERVICE_HOST" -uroot -p"$MARIADB_ROOT_PASSWORD" "$DB_NAME" \ if ! timeout 300 mysqldump --protocol=TCP -h "$MASTER_SERVICE_HOST" -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction "$DB_NAME" \
| tee "$DUMP_FILE" >/dev/null 2>>"$LOG"; then | tee "$DUMP_FILE" >/dev/null 2>>"$LOG"; then
echo "Dump FAILED; aborting this cycle" | tee -a "$LOG"; sleep 120; continue echo "Dump FAILED; aborting this cycle" | tee -a "$LOG"; sleep 120; continue
fi fi
else else
if ! mysqldump --protocol=TCP -h "$MASTER_SERVICE_HOST" -uroot -p"$MARIADB_ROOT_PASSWORD" "$DB_NAME" \ if ! mysqldump --protocol=TCP -h "$MASTER_SERVICE_HOST" -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction "$DB_NAME" \
| tee "$DUMP_FILE" >/dev/null 2>>"$LOG"; then | tee "$DUMP_FILE" >/dev/null 2>>"$LOG"; then
echo "Dump FAILED; aborting this cycle" | tee -a "$LOG"; sleep 120; continue echo "Dump FAILED; aborting this cycle" | tee -a "$LOG"; sleep 120; continue
fi fi

View File

@@ -239,5 +239,13 @@ data:
innodb_log_file_size=32M innodb_log_file_size=32M
table_open_cache=4000 table_open_cache=4000
expire_logs_days=7 expire_logs_days=7
# master slave
server_id=1 # Unique server ID for the master
log_bin=mysql-bin # Enable binary logging
binlog_format=ROW # Recommended format for replication (ROW, STATEMENT, or MIXED)
log_slave_updates=ON # Ensure any changes replicated to the master are also logged to the binary log (useful for multi-source replication)
sync_binlog=1 # Ensures binary logs are synchronized with disk after each transaction for data safety
expire_logs_days=7 # Optional: Number of days to retain binary logs (helps with cleanup)
# /etc/mysql/mariadb.conf.d/70-mysettings.cnf # /etc/mysql/mariadb.conf.d/70-mysettings.cnf

View File

@@ -1,42 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: wrestlingdev-memcached
labels:
app: wrestlingdev
spec:
ports:
- port: 11211
selector:
app: wrestlingdev
tier: memcached
clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wrestlingdev-memcached-deployment
labels:
app: wrestlingdev
spec:
replicas: 1
selector:
matchLabels:
app: wrestlingdev
template:
metadata:
labels:
app: wrestlingdev
tier: memcached
spec:
containers:
- name: wrestlingdev-memcached
image: memcached
ports:
- containerPort: 11211
resources:
limits:
memory: "64Mi"
# requests:
# memory: "64Mi"
# cpu: "0.1"

View File

@@ -109,6 +109,16 @@ spec:
secretKeyRef: secretKeyRef:
name: wrestlingdev-secrets name: wrestlingdev-secrets
key: influxdb_port key: influxdb_port
- name: WRESTLINGDEV_MISSION_CONTROL_USER
valueFrom:
secretKeyRef:
name: wrestlingdev-secrets
key: mission_control_user
- name: WRESTLINGDEV_MISSION_CONTROL_PASSWORD
valueFrom:
secretKeyRef:
name: wrestlingdev-secrets
key: mission_control_password
# resources: # resources:
# limits: # limits:
# memory: "768Mi" # memory: "768Mi"
@@ -122,25 +132,27 @@ spec:
initialDelaySeconds: 180 initialDelaySeconds: 180
periodSeconds: 20 periodSeconds: 20
timeoutSeconds: 10 timeoutSeconds: 10
# --- ---
# apiVersion: autoscaling/v2beta1 apiVersion: autoscaling/v2beta1
# kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
# metadata: metadata:
# name: wrestlingdev-app-deployment-autoscale name: wrestlingdev-app-autoscale
# spec: spec:
# scaleTargetRef: scaleTargetRef:
# apiVersion: apps/v1 apiVersion: apps/v1
# kind: Deployment kind: StatefulSet
# name: wrestlingdev-app-deployment name: wrestlingdev-app
# minReplicas: 2 minReplicas: 2
# maxReplicas: 5 maxReplicas: 5
# metrics: metrics:
# - type: Resource - type: Resource
# resource: resource:
# name: cpu name: cpu
# targetAverageUtilization: 75 targetAverageUtilization: 75
# - type: Resource - type: Resource
# resource: resource:
# name: memory name: memory
# targetAverageValue: 100Mi target:
type: Utilization
averageUtilization: 80

View File

@@ -19,6 +19,9 @@ stringData:
replication_password: PUT_REPLICATION_PASSWORD_HERE replication_password: PUT_REPLICATION_PASSWORD_HERE
# Replication host used by the replica to connect to the master # Replication host used by the replica to connect to the master
replication_host: wrestlingdev-mariadb replication_host: wrestlingdev-mariadb
# Mission Control Credentials
mission_control_user: PUT_MISSION_CONTROL_USERNAME_HERE
mission_control_password: PUT_MISSION_CONTROL_PASSWORD_HERE
# OPTIONAL # OPTIONAL
# DELETE THESE LINES IF YOU'RE NOT USING THEM # DELETE THESE LINES IF YOU'RE NOT USING THEM
influxdb_database: PUT INFLUXDB DATABASE NAME HERE influxdb_database: PUT INFLUXDB DATABASE NAME HERE

View File

@@ -1,10 +1,10 @@
FROM ruby:3.2.0 FROM ruby:4.0.1
# Accept build arguments for user/group IDs # Accept build arguments for user/group IDs
ARG USER_ID=1000 ARG USER_ID=1000
ARG GROUP_ID=1000 ARG GROUP_ID=1000
RUN apt-get -qq update \ RUN apt-get -qq update --fix-missing \
&& apt-get -qq install -y \ && apt-get -qq install -y \
build-essential \ build-essential \
sqlite3 \ sqlite3 \

View File

@@ -1,4 +1,4 @@
FROM ruby:3.2.0-slim FROM ruby:4.0.1-slim
#HEALTHCHECK --start-period=60s CMD curl http://127.0.0.1/ #HEALTHCHECK --start-period=60s CMD curl http://127.0.0.1/
@@ -6,7 +6,7 @@ ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini RUN chmod +x /tini
RUN apt-get -qq update \ RUN apt-get -qq update --fix-missing \
&& DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \ && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \
build-essential \ build-essential \
openssl \ openssl \
@@ -15,6 +15,8 @@ RUN apt-get -qq update \
libsqlite3-dev \ libsqlite3-dev \
wget \ wget \
default-libmysqlclient-dev \ default-libmysqlclient-dev \
libyaml-dev \
pkg-config \
nodejs \ nodejs \
tzdata \ tzdata \
git \ git \

View File

@@ -35,53 +35,69 @@ namespace :tournament do
end end
sleep(10) sleep(10)
@tournament.reload # Ensure matches association is fresh before iterating loop do
@tournament.matches.reload.sort_by(&:bout_number).each do |match| @tournament.reload
if match.reload.loser1_name != "BYE" and match.reload.loser2_name != "BYE" and match.reload.finished != 1 @tournament.refill_open_bout_board_queues
# Wait until both wrestlers are assigned
while (match.w1.nil? || match.w2.nil?)
puts "Waiting for wrestlers in match #{match.bout_number}..."
sleep(5) # Wait for 5 seconds before checking again
match.reload
end
puts "Finishing match with bout number #{match.bout_number}..."
# Choose a random winner mats_with_queue1 = @tournament.mats.select do |mat|
wrestlers = [match.w1, match.w2] match = mat.queue1_match
match.winner_id = wrestlers.sample match && match.finished != 1 && match.loser1_name != "BYE" && match.loser2_name != "BYE"
# Choose a random win type
win_type = WIN_TYPES.sample
match.win_type = win_type
# Assign score based on win type
match.score = case win_type
when "Decision"
low_score = rand(0..10)
high_score = low_score + rand(1..7)
"#{high_score}-#{low_score}"
when "Major"
low_score = rand(0..10)
high_score = low_score + rand(8..14)
"#{high_score}-#{low_score}"
when "Tech Fall"
low_score = rand(0..10)
high_score = low_score + rand(15..19)
"#{high_score}-#{low_score}"
when "Pin"
pin_times = ["0:30","1:12","5:37","2:34","3:54","4:23","5:56","0:12","1:00"]
pin_times.sample
else
"" # Default score
end
# Mark match as finished
match.finished = 1
match.save!
# sleep to prevent mysql locks when assign_next_match to a mat runs
sleep(0.5)
end end
break if mats_with_queue1.empty?
mat = mats_with_queue1.sample
match = mat.queue1_match
# Wait until both wrestlers are assigned for the selected queue1 match.
while match && (match.w1.nil? || match.w2.nil?)
puts "Waiting for wrestlers in match #{match.bout_number} on mat #{mat.name}..."
sleep(5)
@tournament.reload
@tournament.refill_open_bout_board_queues
match = mat.reload.queue1_match
end
next unless match
next if match.finished == 1 || match.loser1_name == "BYE" || match.loser2_name == "BYE"
puts "Finishing queue1 match on mat #{mat.name} with bout number #{match.bout_number}..."
# Choose a random winner
wrestlers = [match.w1, match.w2]
match.winner_id = wrestlers.sample
# Choose a random win type
win_type = WIN_TYPES.sample
match.win_type = win_type
# Assign score based on win type
match.score = case win_type
when "Decision"
low_score = rand(0..10)
high_score = low_score + rand(1..7)
"#{high_score}-#{low_score}"
when "Major"
low_score = rand(0..10)
high_score = low_score + rand(8..14)
"#{high_score}-#{low_score}"
when "Tech Fall"
low_score = rand(0..10)
high_score = low_score + rand(15..19)
"#{high_score}-#{low_score}"
when "Pin"
pin_times = ["0:30","1:12","5:37","2:34","3:54","4:23","5:56","0:12","1:00"]
pin_times.sample
else
""
end
# Mark match as finished
match.finished = 1
match.save!
# sleep to prevent mysql locks when queue advancement runs
sleep(0.5)
end end
end end
end end

View File

@@ -35,12 +35,35 @@
font-weight: 400; font-weight: 400;
letter-spacing: -0.0025em; letter-spacing: -0.0025em;
line-height: 1.4; line-height: 1.4;
min-height: 100vh; min-height: 100dvh;
place-items: center; place-items: center;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a { a {
color: inherit; color: inherit;
font-weight: 700; font-weight: 700;
@@ -83,13 +106,11 @@
} }
main article br { main article br {
display: none; display: none;
@media(min-width: 48em) { @media(min-width: 48em) {
display: inline; display: inline;
} }
} }
</style> </style>
@@ -102,10 +123,10 @@
<main> <main>
<header> <header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" fill="#f0eff0"/><path d="m123.606 85.4445c3.212 1.0523 5.538 4.2089 5.538 8.0301 0 6.1472-4.209 9.5254-11.298 9.5254h-15.617v-34.0033h14.565c7.089 0 11.353 3.1566 11.353 9.2484 0 3.6551-2.049 6.3134-4.541 7.1994zm-12.904-2.9905h5.095c2.603 0 3.988-.9968 3.988-3.1013 0-2.1044-1.385-3.0459-3.988-3.0459h-5.095zm0 6.6456v6.5902h5.981c2.492 0 3.877-1.3291 3.877-3.2674 0-2.049-1.385-3.3228-3.877-3.3228zm43.786 13.9004h-8.362v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.083-2.769-9.083-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.483 1.3845v-1.606c0-1.606-.942-2.9905-3.046-2.9905-1.606 0-2.548.7199-2.935 1.8275h-8.197c.72-4.8181 4.985-8.6393 11.409-8.6393 7.088 0 11.131 3.7659 11.131 10.2453zm-8.362-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm27.996 6.9779v-1.994c-1.163 1.329-3.599 2.548-6.147 2.548-7.199 0-11.131-5.8151-11.131-13.0145s3.932-13.0143 11.131-13.0143c2.548 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.664-1.3291-2.159-2.326-3.821-2.326-2.99 0-4.763 2.4368-4.763 5.6488s1.773 5.5934 4.763 5.5934c1.717 0 3.157-.9415 3.821-2.326zm35.471-2.049h-3.101v11.2421h-8.806v-34.0033h15.285c7.31 0 12.35 4.1535 12.35 11.5744 0 5.1503-2.603 8.6947-6.757 10.2453l7.975 12.1836h-9.858zm-3.101-15.2849v8.1962h5.538c3.156 0 4.596-1.606 4.596-4.0981s-1.44-4.0981-4.596-4.0981zm36.957 17.8323h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm30.98 27.5234v-10.799c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.9259-11.132-13.0145 0-7.144 3.932-13.0143 11.132-13.0143 2.547 0 4.984 1.2184 6.147 2.5475v-1.9937h8.695v33.726zm0-17.9981v-6.5902c-.665-1.3291-2.105-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.661 0 3.156-.9415 3.821-2.326zm36.789-15.7279v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.996 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm19.084 16.2263h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.963 5.095 11.963 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm13.428 11.0206h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.762-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm27.538-.8861v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.993-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.871 0-9.193-2.769-9.193-9.0819z" fill="#d30001"/></svg> <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m123.606 85.4445c3.212 1.0523 5.538 4.2089 5.538 8.0301 0 6.1472-4.209 9.5254-11.298 9.5254h-15.617v-34.0033h14.565c7.089 0 11.353 3.1566 11.353 9.2484 0 3.6551-2.049 6.3134-4.541 7.1994zm-12.904-2.9905h5.095c2.603 0 3.988-.9968 3.988-3.1013 0-2.1044-1.385-3.0459-3.988-3.0459h-5.095zm0 6.6456v6.5902h5.981c2.492 0 3.877-1.3291 3.877-3.2674 0-2.049-1.385-3.3228-3.877-3.3228zm43.786 13.9004h-8.362v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.083-2.769-9.083-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.483 1.3845v-1.606c0-1.606-.942-2.9905-3.046-2.9905-1.606 0-2.548.7199-2.935 1.8275h-8.197c.72-4.8181 4.985-8.6393 11.409-8.6393 7.088 0 11.131 3.7659 11.131 10.2453zm-8.362-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm27.996 6.9779v-1.994c-1.163 1.329-3.599 2.548-6.147 2.548-7.199 0-11.131-5.8151-11.131-13.0145s3.932-13.0143 11.131-13.0143c2.548 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.664-1.3291-2.159-2.326-3.821-2.326-2.99 0-4.763 2.4368-4.763 5.6488s1.773 5.5934 4.763 5.5934c1.717 0 3.157-.9415 3.821-2.326zm35.471-2.049h-3.101v11.2421h-8.806v-34.0033h15.285c7.31 0 12.35 4.1535 12.35 11.5744 0 5.1503-2.603 8.6947-6.757 10.2453l7.975 12.1836h-9.858zm-3.101-15.2849v8.1962h5.538c3.156 0 4.596-1.606 4.596-4.0981s-1.44-4.0981-4.596-4.0981zm36.957 17.8323h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm30.98 27.5234v-10.799c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.9259-11.132-13.0145 0-7.144 3.932-13.0143 11.132-13.0143 2.547 0 4.984 1.2184 6.147 2.5475v-1.9937h8.695v33.726zm0-17.9981v-6.5902c-.665-1.3291-2.105-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.661 0 3.156-.9415 3.821-2.326zm36.789-15.7279v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.996 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm19.084 16.2263h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.963 5.095 11.963 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm13.428 11.0206h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.762-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm27.538-.8861v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.993-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.871 0-9.193-2.769-9.193-9.0819z" id="error-description"/></svg>
</header> </header>
<article> <article>
<p><strong>The server cannot process the request due to a client error.</strong> Please check the request and try again. If youre the application owner check the logs for more information.</p> <p><strong>The server cannot process the request due to a client error.</strong> Please check the request and try again. If you're the application owner check the logs for more information.</p>
</article> </article>
</main> </main>

View File

@@ -4,7 +4,7 @@
<head> <head>
<title>The page you were looking for doesnt exist (404 Not found)</title> <title>The page you were looking for doesn't exist (404 Not found)</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width"> <meta name="viewport" content="initial-scale=1, width=device-width">
@@ -35,12 +35,35 @@
font-weight: 400; font-weight: 400;
letter-spacing: -0.0025em; letter-spacing: -0.0025em;
line-height: 1.4; line-height: 1.4;
min-height: 100vh; min-height: 100dvh;
place-items: center; place-items: center;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a { a {
color: inherit; color: inherit;
font-weight: 700; font-weight: 700;
@@ -83,13 +106,11 @@
} }
main article br { main article br {
display: none; display: none;
@media(min-width: 48em) { @media(min-width: 48em) {
display: inline; display: inline;
} }
} }
</style> </style>
@@ -102,10 +123,10 @@
<main> <main>
<header> <header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm165.328-35.41581-45.689 100.02991h26.224v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.184v-31.901l50.285-103.27391z" fill="#f0eff0"/><path d="m157.758 68.9967v34.0033h-7.199l-14.233-19.8814v19.8814h-8.584v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm13.184 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm37.027 8.5839h-8.806v-34.0033h23.924v7.6978h-15.118v6.7564h13.9v7.5316h-13.9zm41.876-12.4605c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm35.337-12.4605v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.997 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm4.076 24.921v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.156.9969-3.6 2.7136v15.0634zm44.113 0v-1.994c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.8151-11.132-13.0145s3.932-13.0143 11.132-13.0143c2.547 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.665-1.3291-2.16-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.717 0 3.156-.9415 3.821-2.326z" fill="#d30001"/></svg> <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm165.328-35.41581-45.689 100.02991h26.224v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.184v-31.901l50.285-103.27391z" id="error-id"/><path d="m157.758 68.9967v34.0033h-7.199l-14.233-19.8814v19.8814h-8.584v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm13.184 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm37.027 8.5839h-8.806v-34.0033h23.924v7.6978h-15.118v6.7564h13.9v7.5316h-13.9zm41.876-12.4605c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm35.337-12.4605v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.997 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm4.076 24.921v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.156.9969-3.6 2.7136v15.0634zm44.113 0v-1.994c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.8151-11.132-13.0145s3.932-13.0143 11.132-13.0143c2.547 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.665-1.3291-2.16-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.717 0 3.156-.9415 3.821-2.326z" id="error-description"/></svg>
</header> </header>
<article> <article>
<p><strong>The page you were looking for doesnt exist.</strong> You may have mistyped the address or the page may have moved. If youre the application owner check the logs for more information.</p> <p><strong>The page you were looking for doesn't exist.</strong> You may have mistyped the address or the page may have moved. If you're the application owner check the logs for more information.</p>
</article> </article>
</main> </main>

View File

@@ -35,12 +35,35 @@
font-weight: 400; font-weight: 400;
letter-spacing: -0.0025em; letter-spacing: -0.0025em;
line-height: 1.4; line-height: 1.4;
min-height: 100vh; min-height: 100dvh;
place-items: center; place-items: center;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a { a {
color: inherit; color: inherit;
font-weight: 700; font-weight: 700;
@@ -83,13 +106,11 @@
} }
main article br { main article br {
display: none; display: none;
@media(min-width: 48em) { @media(min-width: 48em) {
display: inline; display: inline;
} }
} }
</style> </style>
@@ -102,7 +123,7 @@
<main> <main>
<header> <header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm202.906 9.7326h-41.093c-2.433-7.2994-7.84-12.4361-17.302-12.4361-16.221 0-25.413 17.5728-25.954 34.8752v1.3517c5.137-7.0291 16.221-12.4361 30.82-12.4361 33.524 0 54.881 24.0612 54.881 53.7998 0 33.253-23.791 58.396-61.64 58.396-21.628 0-39.741-10.003-50.825-27.576-9.733-14.599-13.788-32.442-13.788-54.3406 0-51.9072 24.331-89.485807 66.236-89.485807 32.712 0 53.258 18.654107 58.665 47.851907zm-82.727 66.2355c0 13.247 9.463 22.439 22.71 22.439 12.977 0 22.439-9.192 22.439-22.439 0-13.517-9.462-22.7091-22.439-22.7091-13.247 0-22.71 9.1921-22.71 22.7091z" fill="#f0eff0"/><path d="m100.761 68.9967v34.0033h-7.1991l-14.2326-19.8814v19.8814h-8.5839v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm13.185 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.02-25.4194h9.083l12.958 34.0033h-9.027l-2.436-6.5902h-12.35l-2.381 6.5902h-8.806zm4.431 10.5222-3.489 9.5807h6.978zm17.44 11.0206c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm25.676 0c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm42.013 3.7658h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm23.4 16.7244v10.799h-8.694v-33.726h8.694v1.9937c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8703 11.131 13.0143 0 7.0886-3.932 13.0145-11.131 13.0145-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.16 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.717 0-3.157.9969-3.822 2.326zm21.892 7.1994v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.458 8.5839h-8.363v-1.274c-.83.831-3.322 1.717-5.981 1.717-4.928 0-9.082-2.769-9.082-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.045-2.9905-1.606 0-2.548.7199-2.936 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.553-1.0522-2.049-1.7167-3.655-1.7167-1.716 0-3.433.7199-3.433 2.3813 0 1.7168 1.717 2.4367 3.433 2.4367 1.606 0 3.102-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.694v-35.997h8.694v13.0697c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8149 11.131 13.0143s-3.932 13.0145-11.131 13.0145c-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.662 0-3.157.9969-3.822 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997z" fill="#d30001"/></svg> <svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm202.906 9.7326h-41.093c-2.433-7.2994-7.84-12.4361-17.302-12.4361-16.221 0-25.413 17.5728-25.954 34.8752v1.3517c5.137-7.0291 16.221-12.4361 30.82-12.4361 33.524 0 54.881 24.0612 54.881 53.7998 0 33.253-23.791 58.396-61.64 58.396-21.628 0-39.741-10.003-50.825-27.576-9.733-14.599-13.788-32.442-13.788-54.3406 0-51.9072 24.331-89.485807 66.236-89.485807 32.712 0 53.258 18.654107 58.665 47.851907zm-82.727 66.2355c0 13.247 9.463 22.439 22.71 22.439 12.977 0 22.439-9.192 22.439-22.439 0-13.517-9.462-22.7091-22.439-22.7091-13.247 0-22.71 9.1921-22.71 22.7091z" id="error-id"/><path d="m100.761 68.9967v34.0033h-7.1991l-14.2326-19.8814v19.8814h-8.5839v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm13.185 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.02-25.4194h9.083l12.958 34.0033h-9.027l-2.436-6.5902h-12.35l-2.381 6.5902h-8.806zm4.431 10.5222-3.489 9.5807h6.978zm17.44 11.0206c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm25.676 0c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm42.013 3.7658h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm23.4 16.7244v10.799h-8.694v-33.726h8.694v1.9937c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8703 11.131 13.0143 0 7.0886-3.932 13.0145-11.131 13.0145-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.16 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.717 0-3.157.9969-3.822 2.326zm21.892 7.1994v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.458 8.5839h-8.363v-1.274c-.83.831-3.322 1.717-5.981 1.717-4.928 0-9.082-2.769-9.082-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.045-2.9905-1.606 0-2.548.7199-2.936 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.553-1.0522-2.049-1.7167-3.655-1.7167-1.716 0-3.433.7199-3.433 2.3813 0 1.7168 1.717 2.4367 3.433 2.4367 1.606 0 3.102-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.694v-35.997h8.694v13.0697c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8149 11.131 13.0143s-3.932 13.0145-11.131 13.0145c-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.662 0-3.157.9969-3.822 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997z" id="error-description"/></svg>
</header> </header>
<article> <article>
<p><strong>Your browser is not supported.</strong><br> Please upgrade your browser to continue.</p> <p><strong>Your browser is not supported.</strong><br> Please upgrade your browser to continue.</p>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
pub-6845455733812572 google.com, pub-6845455733812572, DIRECT, f08c47fec0942fa0

View File

@@ -25,6 +25,7 @@ class MatchesControllerTest < ActionController::TestCase
end end
def post_update_from_match_stat def post_update_from_match_stat
@request.env["HTTP_REFERER"] = "/tournaments/#{@tournament.id}/matches"
get :stat, params: { id: @match.id } get :stat, params: { id: @match.id }
patch :update, params: { id: @match.id, match: {tournament_id: 1, mat_id: 1} } patch :update, params: { id: @match.id, match: {tournament_id: 1, mat_id: 1} }
end end
@@ -32,6 +33,21 @@ class MatchesControllerTest < ActionController::TestCase
def get_stat def get_stat
get :stat, params: { id: @match.id } get :stat, params: { id: @match.id }
end end
def get_edit_assignment(extra_params = {})
get :edit_assignment, params: { id: @match.id }.merge(extra_params)
end
def patch_update_assignment(extra_params = {})
base = {
id: @match.id,
match: {
mat_id: @match.mat_id,
queue_position: 2
}
}
patch :update_assignment, params: base.deep_merge(extra_params)
end
def sign_in_owner def sign_in_owner
sign_in users(:one) sign_in users(:one)
@@ -174,4 +190,72 @@ class MatchesControllerTest < ActionController::TestCase
assert_response :success assert_response :success
assert_includes @response.body, time_ago_in_words(finished_at), "time_ago_in_words(finished_at) should be displayed on the page" assert_includes @response.body, time_ago_in_words(finished_at), "time_ago_in_words(finished_at) should be displayed on the page"
end end
test "tournament owner can view edit_assignment and execute update_assignment" do
sign_in_owner
get_edit_assignment
assert_response :success
patch_update_assignment
assert_response :redirect
assert_not_equal "/static_pages/not_allowed", @response.redirect_url&.sub("http://test.host", "")
end
test "tournament delegate can view edit_assignment and execute update_assignment" do
sign_in_tournament_delegate
get_edit_assignment
assert_response :success
patch_update_assignment
assert_response :redirect
assert_not_equal "/static_pages/not_allowed", @response.redirect_url&.sub("http://test.host", "")
end
test "school delegate cannot view edit_assignment or execute update_assignment" do
sign_in_school_delegate
get_edit_assignment
assert_redirected_to "/static_pages/not_allowed"
patch_update_assignment
assert_redirected_to "/static_pages/not_allowed"
end
test "non logged in user cannot view edit_assignment or execute update_assignment" do
get_edit_assignment
assert_redirected_to "/static_pages/not_allowed"
patch_update_assignment
assert_redirected_to "/static_pages/not_allowed"
end
test "logged in user without delegations cannot view edit_assignment or execute update_assignment" do
sign_in_non_owner
get_edit_assignment
assert_redirected_to "/static_pages/not_allowed"
patch_update_assignment
assert_redirected_to "/static_pages/not_allowed"
end
test "valid school permission key cannot view edit_assignment or execute update_assignment" do
school = @tournament.schools.first
school.update!(permission_key: "valid-school-key")
get_edit_assignment(school_permission_key: "valid-school-key")
assert_redirected_to "/static_pages/not_allowed"
patch_update_assignment(school_permission_key: "valid-school-key")
assert_redirected_to "/static_pages/not_allowed"
end
test "invalid school permission key cannot view edit_assignment or execute update_assignment" do
school = @tournament.schools.first
school.update!(permission_key: "valid-school-key")
get_edit_assignment(school_permission_key: "invalid-school-key")
assert_redirected_to "/static_pages/not_allowed"
patch_update_assignment(school_permission_key: "invalid-school-key")
assert_redirected_to "/static_pages/not_allowed"
end
end end

View File

@@ -9,6 +9,10 @@ class MatsControllerTest < ActionController::TestCase
# @tournament.generateMatchups # @tournament.generateMatchups
@match = Match.where("tournament_id = ? and mat_id = ?",1,1).first @match = Match.where("tournament_id = ? and mat_id = ?",1,1).first
@mat = mats(:one) @mat = mats(:one)
@match ||= @tournament.matches.first
if @match && @mat.queue1.nil?
@mat.assign_match_to_queue!(@match, 1)
end
end end
def create def create
@@ -62,6 +66,15 @@ class MatsControllerTest < ActionController::TestCase
def redirect def redirect
assert_redirected_to '/static_pages/not_allowed' assert_redirected_to '/static_pages/not_allowed'
end end
def assert_ads_hidden
assert_no_match(/blocked_message/, response.body)
assert_no_match(/adsbygoogle/, response.body)
end
def assert_ads_visible
assert_match(/blocked_message/, response.body)
end
def no_matches def no_matches
assert_redirected_to "/tournaments/#{@tournament.id}/no_matches" assert_redirected_to "/tournaments/#{@tournament.id}/no_matches"
@@ -217,6 +230,13 @@ class MatsControllerTest < ActionController::TestCase
success success
end end
test "ads are hidden on mat show" do
sign_in_owner
show
success
assert_ads_hidden
end
test "redirect to mat show when posting a match from mat show" do test "redirect to mat show when posting a match from mat show" do
sign_in_owner sign_in_owner
post_match_update_from_mat_show post_match_update_from_mat_show
@@ -242,7 +262,7 @@ class MatsControllerTest < ActionController::TestCase
test "logged in tournament owner should redirect back to the first unfinished bout on a mat after submitting a match with a bout number param" do test "logged in tournament owner should redirect back to the first unfinished bout on a mat after submitting a match with a bout number param" do
sign_in_owner sign_in_owner
first_bout_number = @mat.unfinished_matches.first.bout_number first_bout_number = @mat.queue1_match.bout_number
# Set a specific bout number to test # Set a specific bout number to test
bout_number = @match.bout_number bout_number = @match.bout_number

View File

@@ -60,6 +60,15 @@ class SchoolsControllerTest < ActionController::TestCase
assert_redirected_to '/static_pages/not_allowed' assert_redirected_to '/static_pages/not_allowed'
end end
def assert_ads_hidden
assert_no_match(/blocked_message/, response.body)
assert_no_match(/adsbygoogle/, response.body)
end
def assert_ads_visible
assert_match(/blocked_message/, response.body)
end
def baums_import def baums_import
baums_text = "***** 2019-01-09 13:36:50 ***** baums_text = "***** 2019-01-09 13:36:50 *****
Some School Some School
@@ -357,7 +366,7 @@ Some Guy
@school.wrestlers.each do |wrestler| @school.wrestlers.each do |wrestler|
# Check only for the DELETE link, specifying 'data-method="delete"' to exclude profile links # Check only for the DELETE link, specifying 'data-method="delete"' to exclude profile links
assert_select "a[href=?][data-method=delete]", wrestler_path(wrestler), count: 1 assert_select "a[href=?][data-turbo-method=delete]", wrestler_path(wrestler), count: 1
# Check edit link # Check edit link
assert_select "a[href=?]", edit_wrestler_path(wrestler), count: 1 assert_select "a[href=?]", edit_wrestler_path(wrestler), count: 1
@@ -373,18 +382,53 @@ Some Guy
success success
end end
test "logged in user without delegation can get show page when using valid school_permission_key" do
sign_in_non_owner
@tournament.update(is_public: false)
get_show(school_permission_key: @school_permission_key)
success
end
test "non logged in user cannot get show page when using invalid school_permission_key" do test "non logged in user cannot get show page when using invalid school_permission_key" do
@tournament.update(is_public: false) @tournament.update(is_public: false)
get_show(school_permission_key: "invalid-key") get_show(school_permission_key: "invalid-key")
redirect redirect
end end
test "logged in user without delegation can edit school with valid school_permission_key" do
sign_in_non_owner
@tournament.update(is_public: false)
get_edit(school_permission_key: @school_permission_key)
success
end
test "non logged in user can edit school with valid school_permission_key" do test "non logged in user can edit school with valid school_permission_key" do
@tournament.update(is_public: false) @tournament.update(is_public: false)
get_edit(school_permission_key: @school_permission_key) get_edit(school_permission_key: @school_permission_key)
success success
end end
test "ads are hidden on school show when logged in" do
sign_in_owner
get_show
success
assert_ads_hidden
end
test "ads are hidden on school show with school permission key" do
@tournament.update(is_public: false)
get_show(school_permission_key: @school_permission_key)
success
assert_ads_hidden
end
test "ads are visible on school show for anonymous user without key" do
@tournament.update(is_public: true)
get_show
success
assert_ads_visible
end
test "non logged in user cannot edit school with invalid school_permission_key" do test "non logged in user cannot edit school with invalid school_permission_key" do
@tournament.update(is_public: false) @tournament.update(is_public: false)
get_edit(school_permission_key: "invalid-key") get_edit(school_permission_key: "invalid-key")

View File

@@ -27,6 +27,10 @@ class TournamentsControllerTest < ActionController::TestCase
def get_up_matches def get_up_matches
get :up_matches, params: { id: 1 } get :up_matches, params: { id: 1 }
end end
def get_qrcode(params = {})
get :qrcode, params: { id: 1 }.merge(params)
end
def get_edit def get_edit
get :edit, params: { id: 1 } get :edit, params: { id: 1 }
@@ -192,6 +196,47 @@ class TournamentsControllerTest < ActionController::TestCase
assert_redirected_to '/static_pages/not_allowed' assert_redirected_to '/static_pages/not_allowed'
end end
test "logged in non owner and non delegate cannot access qrcode" do
sign_in_non_owner
get_qrcode
redirect
end
test "non logged in user cannot access qrcode" do
get_qrcode
redirect
end
test "non logged in user with valid school permission key cannot access qrcode" do
@school.update(permission_key: "valid-key")
get_qrcode(school_permission_key: "valid-key")
redirect
end
test "non logged in user with invalid school permission key cannot access qrcode" do
@school.update(permission_key: "valid-key")
get_qrcode(school_permission_key: "invalid-key")
redirect
end
test "logged in owner can access qrcode" do
sign_in_owner
get_qrcode
success
end
test "logged in tournament delegate can access qrcode" do
sign_in_delegate
get_qrcode
success
end
test "logged in school delegate cannot access qrcode" do
sign_in_school_delegate
get_qrcode
redirect
end
test "logged in user should not post update tournament if not owner" do test "logged in user should not post update tournament if not owner" do
sign_in_non_owner sign_in_non_owner
post_update post_update

View File

@@ -0,0 +1,84 @@
require "test_helper"
class UpMatchesCacheTest < ActionController::TestCase
tests TournamentsController
setup do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 4, 2)
@tournament.update!(user_id: users(:one).id)
@tournament.reset_and_fill_bout_board
sign_in users(:one)
@original_perform_caching = ActionController::Base.perform_caching
ActionController::Base.perform_caching = true
Rails.cache.clear
end
teardown do
Rails.cache.clear
ActionController::Base.perform_caching = @original_perform_caching
end
test "up_matches row fragments hit cache and invalidate when a mat queue changes" do
first_events = cache_events_for_up_matches do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial render to write row fragments"
second_events = cache_events_for_up_matches do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_equal 0, cache_writes(second_events), "Expected second render to reuse cached row fragments"
assert_operator cache_hits(second_events), :>, 0, "Expected second render to have cache hits"
mat = @tournament.mats.first
mat.reload
movable_match = mat.queue2_match || mat.queue1_match
assert movable_match, "Expected at least one queued match to move"
mat.assign_match_to_queue!(movable_match, 4)
third_events = cache_events_for_up_matches do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(third_events), :>, 0, "Expected queue change to invalidate and rewrite at least one row fragment"
end
private
def cache_events_for_up_matches
events = []
subscriber = lambda do |name, _start, _finish, _id, payload|
key = payload[:key].to_s
next unless key.include?("up_matches_mat_row")
events << { name: name, hit: payload[:hit] }
end
ActiveSupport::Notifications.subscribed(
subscriber,
/cache_(read|write|fetch_hit|generate)\.active_support/
) do
yield
end
events
end
def cache_writes(events)
events.count { |event| event[:name] == "cache_write.active_support" }
end
def cache_hits(events)
events.count do |event|
event[:name] == "cache_fetch_hit.active_support" ||
(event[:name] == "cache_read.active_support" && event[:hit])
end
end
end

View File

@@ -56,6 +56,15 @@ class WrestlersControllerTest < ActionController::TestCase
assert_redirected_to '/static_pages/not_allowed' assert_redirected_to '/static_pages/not_allowed'
end end
def assert_ads_hidden
assert_no_match(/blocked_message/, response.body)
assert_no_match(/adsbygoogle/, response.body)
end
def assert_ads_visible
assert_match(/blocked_message/, response.body)
end
test "logged in tournament owner should get edit wrestler page" do test "logged in tournament owner should get edit wrestler page" do
sign_in_owner sign_in_owner
get_edit get_edit
@@ -305,4 +314,39 @@ class WrestlersControllerTest < ActionController::TestCase
assert_select "a[href=?]", school_path(@school), text: /Back to/ assert_select "a[href=?]", school_path(@school), text: /Back to/
end end
test "ads are hidden on wrestler new" do
sign_in_owner
new
success
assert_ads_hidden
end
test "ads are hidden on wrestler edit" do
sign_in_owner
get_edit
success
assert_ads_hidden
end
test "ads are hidden on wrestler new with school permission key" do
valid_key = @school.permission_key
get :new, params: { school: @school.id, school_permission_key: valid_key }
success
assert_ads_hidden
end
test "ads are hidden on wrestler edit with school permission key" do
valid_key = @school.permission_key
get :edit, params: { id: @wrestler.id, school_permission_key: valid_key }
success
assert_ads_hidden
end
test "ads are visible on wrestler show" do
sign_in_owner
get :show, params: { id: @wrestler.id }
success
assert_ads_visible
end
end end

View File

@@ -2,26 +2,4 @@
# This model requires tournament, job_name, and status fields # This model requires tournament, job_name, and status fields
queued_job:
tournament: one
job_name: "Test Queued Job"
status: "Queued"
details: "Test job details"
running_job:
tournament: one
job_name: "Test Running Job"
status: "Running"
details: "Test running job details"
errored_job:
tournament: one
job_name: "Test Errored Job"
status: "Errored"
details: "Test error message"
another_tournament_job:
tournament: two
job_name: "Another Tournament Job"
status: "Running"
details: "Different tournament test"

View File

@@ -0,0 +1,289 @@
require "test_helper"
class BoutBoardTest < ActionDispatch::IntegrationTest
test "only assigns matches with w1 and w2" do
create_double_elim_tournament_single_weight(16, "Regular Double Elimination 1-6")
mat = @tournament.mats.create!(name: "Mat 1")
@tournament.matches.update_all(mat_id: nil)
@tournament.matches.update_all(w1: nil)
@tournament.reset_and_fill_bout_board
mat.reload
assert_empty mat.queue_match_ids.compact, "No matches should be assigned when w1 is missing"
GenerateTournamentMatches.new(@tournament).generate
@tournament.reload
@tournament.matches.reload
@tournament.matches.update_all(mat_id: nil)
@tournament.matches.update_all(w2: nil)
@tournament.reset_and_fill_bout_board
mat.reload
assert_empty mat.queue_match_ids.compact, "No matches should be assigned when w2 is missing"
end
test "only assigns matches without a loser1_name or loser2_name of BYE" do
create_double_elim_tournament_single_weight(16, "Regular Double Elimination 1-6")
mat = @tournament.mats.create!(name: "Mat 1")
@tournament.matches.update_all(mat_id: nil)
@tournament.matches.update_all(loser1_name: "BYE")
@tournament.reset_and_fill_bout_board
mat.reload
assert_empty mat.queue_match_ids.compact, "No matches should be assigned when loser1_name is BYE"
GenerateTournamentMatches.new(@tournament).generate
@tournament.reload
@tournament.matches.reload
@tournament.matches.update_all(mat_id: nil)
@tournament.matches.update_all(loser2_name: "BYE")
@tournament.reset_and_fill_bout_board
mat.reload
assert_empty mat.queue_match_ids.compact, "No matches should be assigned when loser1_name is BYE"
end
test "moving queue2 from mat1 to mat2 shifts queues and unassigns bumped match" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 8, 2)
@tournament = Tournament.find(@tournament.id)
eligible_matches = Match.where(tournament_id: @tournament.id)
.where(finished: [nil, 0])
.where.not(bout_number: nil)
.where.not(w1: nil)
.where.not(w2: nil)
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
.where(mat_id: nil)
assert eligible_matches.count >= 8, "Expected enough eligible matches to fill two mats"
@tournament.reload
@tournament.matches.reload
@tournament.reset_and_fill_bout_board
@tournament = Tournament.find(@tournament.id)
mat1 = @tournament.mats.order(:id).first
mat2 = @tournament.mats.order(:id).second
mat1.reload
mat2.reload
assert mat1.queue2_match, "Expected mat1 queue2 to be assigned"
assert mat1.queue3_match, "Expected mat1 queue3 to be assigned"
assert mat1.queue4_match, "Expected mat1 queue4 to be assigned"
assert mat2.queue2_match, "Expected mat2 queue2 to be assigned"
assert mat2.queue3_match, "Expected mat2 queue3 to be assigned"
assert mat2.queue4_match, "Expected mat2 queue4 to be assigned"
mat1_q2 = mat1.queue2_match
mat1_q3 = mat1.queue3_match
mat1_q4 = mat1.queue4_match
mat2_q2 = mat2.queue2_match
mat2_q3 = mat2.queue3_match
mat2_q4 = mat2.queue4_match
mat2_q4_original_match = Match.find(mat2_q4.id)
mat2.assign_match_to_queue!(mat1_q2, 2)
mat1.reload
mat2.reload
assert_equal mat1_q2.id, mat2.queue2, "Moved match should land in mat2 queue2"
assert_equal mat2_q2.id, mat2.queue3, "Mat2 queue2 should shift to queue3"
assert_equal mat2_q3.id, mat2.queue4, "Mat2 queue3 should shift to queue4"
assert_nil mat2_q4.reload.mat_id, "Original mat2 queue4 match should be unassigned"
assert_equal mat1_q3.id, mat1.queue2, "Mat1 queue3 should shift to queue2"
assert_equal mat1_q4.id, mat1.queue3, "Mat1 queue4 should shift to queue3"
assert mat1.queue4, "Mat1 queue4 should be refilled"
refute_includes [mat1_q2.id, mat1_q3.id, mat1_q4.id], mat1.queue4, "Mat1 queue4 should be a new match"
assert_equal mat1.id, Match.find(mat1.queue4).mat_id, "New mat1 queue4 match should be assigned to mat1"
assert_nil mat2_q4_original_match.reload.mat_id, "Mat 2 queue4 match should no longer have a mat_id"
end
test "moving queue2 to queue4 on the same mat shifts queues" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 8, 1)
@tournament.reset_and_fill_bout_board
mat1 = @tournament.mats.order(:id).first
mat1.reload
assert mat1.queue2_match, "Expected mat1 queue2 to be assigned"
assert mat1.queue3_match, "Expected mat1 queue3 to be assigned"
assert mat1.queue4_match, "Expected mat1 queue4 to be assigned"
mat1_q2 = mat1.queue2_match
mat1_q3 = mat1.queue3_match
mat1_q4 = mat1.queue4_match
mat1.assign_match_to_queue!(mat1_q2, 4)
mat1.reload
assert_equal mat1_q3.id, mat1.queue2, "Mat1 queue3 should shift to queue2"
assert_equal mat1_q4.id, mat1.queue3, "Mat1 queue4 should shift to queue3"
assert_equal mat1_q2.id, mat1.queue4, "Mat1 queue2 should move to queue4"
end
test "moving queue4 to queue2 on the same mat shifts queues" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 8, 1)
@tournament.reset_and_fill_bout_board
mat1 = @tournament.mats.order(:id).first
mat1.reload
assert mat1.queue2_match, "Expected mat1 queue2 to be assigned"
assert mat1.queue3_match, "Expected mat1 queue3 to be assigned"
assert mat1.queue4_match, "Expected mat1 queue4 to be assigned"
mat1_q2 = mat1.queue2_match
mat1_q3 = mat1.queue3_match
mat1_q4 = mat1.queue4_match
mat1.assign_match_to_queue!(mat1_q4, 2)
mat1.reload
assert_equal mat1_q4.id, mat1.queue2, "Mat1 queue4 should move to queue2"
assert_equal mat1_q2.id, mat1.queue3, "Mat1 original queue2 should move to queue3"
assert_equal mat1_q3.id, mat1.queue4, "Mat1 original queue3 should move to queue4"
end
test "queues stay filled while running through an entire tournament, mat_id's are null after a match is finished, and mat_id's exist when in a queue" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 4, 3)
@tournament = Tournament.find(@tournament.id)
@tournament.reset_and_fill_bout_board
max_iterations = @tournament.matches.count + 20
iterations = 0
loop do
iterations += 1
assert_operator iterations, :<=, max_iterations, "Loop exceeded expected match count"
assert_queue_depth_matches_available_bouts(@tournament)
next_match = next_queued_finishable_match(@tournament)
break unless next_match
next_match.update!(
winner_id: next_match.w1,
win_type: "Decision",
score: "1-0",
finished: 1
)
assert_nil next_match.reload.mat_id, "The match should have a null mat_id after it is finished"
@tournament.reload
end
remaining_finishable = finishable_match_scope(@tournament).count
assert_equal 0, remaining_finishable, "All finishable matches should be completed"
assert_queue_depth_matches_available_bouts(@tournament)
end
test "Deleting a mat mid tournament does not delete any matches" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(14, 1, 3)
assert_equal 29, @tournament.matches.count, "Before deleting a mat total number of matches for a 14 man double elim 1-6 tournament should be 29"
assert_equal 1, @tournament.matches.select{|m| m.bracket_position == "1/2"}.count, "Before deleting a mat there should be 1 match for bracket position 1/2"
assert_equal 1, @tournament.matches.select{|m| m.bracket_position == "3/4"}.count, "Before deleting a mat there should be 1 match for bracket position 3/4"
assert_equal 1, @tournament.matches.select{|m| m.bracket_position == "5/6"}.count, "Before deleting a mat there should be 1 match for bracket position 5/6"
assert_equal 8, @tournament.matches.select{|m| m.bracket_position == "Bracket Round of 16"}.count, "Before deleting a mat there should be 8 matches for bracket position Bracket Round of 16"
assert_equal 4, @tournament.matches.select{|m| m.bracket_position == "Conso Round of 8.1"}.count, "Before deleting a mat there should be 4 matches for bracket position Conso Round of 8.1"
assert_equal 4, @tournament.matches.select{|m| m.bracket_position == "Quarter"}.count, "Before deleting a mat there should be 4 matches for bracket position Quarter"
assert_equal 2, @tournament.matches.select{|m| m.bracket_position == "Semis"}.count, "Before deleting a mat there should be 2 matches for bracket position Semis"
assert_equal 4, @tournament.matches.select{|m| m.bracket_position == "Conso Round of 8.2"}.count, "Before deleting a mat there should be 4 matches for bracket position Conso Round of 8.2"
assert_equal 2, @tournament.matches.select{|m| m.bracket_position == "Conso Quarter"}.count, "Before deleting a mat there should be 2 matches for bracket position Conso Quarter"
assert_equal 2, @tournament.matches.select{|m| m.bracket_position == "Conso Semis"}.count, "Before deleting a mat there should be 2 matches for bracket position Conso Semis"
@tournament.mats.first.destroy
@tournament.reload
@tournament.matches.reload
assert_equal 29, @tournament.matches.count, "After deleting a mat total number of matches for a 14 man double elim 1-6 tournament should still be 29"
assert_equal 1, @tournament.matches.select{|m| m.bracket_position == "1/2"}.count, "After deleting a mat there should still be 1 match for bracket position 1/2"
assert_equal 1, @tournament.matches.select{|m| m.bracket_position == "3/4"}.count, "After deleting a mat there should still be 1 match for bracket position 3/4"
assert_equal 1, @tournament.matches.select{|m| m.bracket_position == "5/6"}.count, "After deleting a mat there should still be 1 match for bracket position 5/6"
assert_equal 8, @tournament.matches.select{|m| m.bracket_position == "Bracket Round of 16"}.count, "After deleting a mat there should still be 8 matches for bracket position Bracket Round of 16"
assert_equal 4, @tournament.matches.select{|m| m.bracket_position == "Conso Round of 8.1"}.count, "After deleting a mat there should still be 4 matches for bracket position Conso Round of 8.1"
assert_equal 4, @tournament.matches.select{|m| m.bracket_position == "Quarter"}.count, "After deleting a mat there should still be 4 matches for bracket position Quarter"
assert_equal 2, @tournament.matches.select{|m| m.bracket_position == "Semis"}.count, "After deleting a mat there should still be 2 matches for bracket position Semis"
assert_equal 4, @tournament.matches.select{|m| m.bracket_position == "Conso Round of 8.2"}.count, "After deleting a mat there should still be 4 matches for bracket position Conso Round of 8.2"
assert_equal 2, @tournament.matches.select{|m| m.bracket_position == "Conso Quarter"}.count, "After deleting a mat there should still be 2 matches for bracket position Conso Quarter"
assert_equal 2, @tournament.matches.select{|m| m.bracket_position == "Conso Semis"}.count, "After deleting a mat there should still be 2 matches for bracket position Conso Semis"
end
test "When matches are generated, they're assigned a mat in round robin fashion" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 8, 2)
@tournament = Tournament.find(@tournament.id)
@tournament.reload
@tournament.matches.reload
@tournament.reset_and_fill_bout_board
@tournament = Tournament.find(@tournament.id)
mat1 = @tournament.mats.order(:id).first
mat2 = @tournament.mats.order(:id).second
mat1.reload
mat2.reload
matches_ordered_by_bout = @tournament.matches.sort_by{|m| m.bout_number}
assert_equal matches_ordered_by_bout.first.bout_number, mat1.queue1_match.bout_number, "The first bout number of the tournament should be queue1 on mat 1"
assert_equal matches_ordered_by_bout.second.bout_number, mat2.queue1_match.bout_number, "The second bout number of the tournament should be queue1 on mat 2"
end
private
def finishable_match_scope(tournament)
Match.where(tournament_id: tournament.id, finished: [nil, 0])
.where.not(w1: nil)
.where.not(w2: nil)
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
end
def next_queued_finishable_match(tournament)
tournament.mats.order(:id).each do |mat|
match = mat.queue1_match
next unless match
next unless match.finished != 1
return match if match.w1.present? && match.w2.present?
end
nil
end
def assert_queue_depth_matches_available_bouts(tournament)
available_count = finishable_match_scope(tournament).count
queue_capacity = tournament.mats.count * 4
expected_queued_count = [available_count, queue_capacity].min
queued_ids = tournament.mats.order(:id).flat_map(&:queue_match_ids).compact
assert_equal expected_queued_count, queued_ids.count,
"Queue depth should match available matches (expected #{expected_queued_count}, got #{queued_ids.count})"
tournament.mats.order(:id).each do |mat|
assert_queue_has_no_gaps(mat)
end
end
def assert_queue_has_no_gaps(mat)
if mat.queue2.present?
assert mat.queue1.present?, "Mat #{mat.id} queue1 must be present when queue2 is present"
assert_equal mat.id, mat.queue1_match.mat_id, "The match in queue1 should have a mat_id"
end
if mat.queue3.present?
assert mat.queue2.present?, "Mat #{mat.id} queue2 must be present when queue3 is present"
assert_equal mat.id, mat.queue2_match.mat_id, "The match in queue2 should have a mat_id"
end
if mat.queue4.present?
assert mat.queue3.present?, "Mat #{mat.id} queue3 must be present when queue4 is present"
assert_equal mat.id, mat.queue3_match.mat_id, "The match in queue3 should have a mat_id"
end
end
end

View File

@@ -31,8 +31,11 @@ class DoubleEliminationAutoByes < ActionDispatch::IntegrationTest
assert round1.select{|m| m.bracket_position_number == 4}.first.wrestler1.name == "Test2" assert round1.select{|m| m.bracket_position_number == 4}.first.wrestler1.name == "Test2"
assert round1.select{|m| m.bracket_position_number == 4}.first.loser2_name == "BYE" assert round1.select{|m| m.bracket_position_number == 4}.first.loser2_name == "BYE"
winner_by_name("Test4", round1.select{|m| m.bracket_position_number == 2}.first) winner_by_name("Test4", round1.select{|m| m.bracket_position_number == 2}.first)
assert mat.reload.unfinished_matches.first.loser1_name != "BYE" queued_match = mat.reload.queue1_match
assert mat.reload.unfinished_matches.first.loser2_name != "BYE" if queued_match
assert queued_match.loser1_name != "BYE"
assert queued_match.loser2_name != "BYE"
end
semis = matches.select{|m| m.bracket_position == "Semis"}.sort_by{|m| m.bracket_position_number} semis = matches.select{|m| m.bracket_position == "Semis"}.sort_by{|m| m.bracket_position_number}
assert semis.first.reload.wrestler1.name == "Test1" assert semis.first.reload.wrestler1.name == "Test1"
@@ -40,11 +43,17 @@ class DoubleEliminationAutoByes < ActionDispatch::IntegrationTest
assert semis.second.reload.wrestler1.name == "Test3" assert semis.second.reload.wrestler1.name == "Test3"
assert semis.second.reload.wrestler2.name == "Test2" assert semis.second.reload.wrestler2.name == "Test2"
winner_by_name("Test4",semis.first) winner_by_name("Test4",semis.first)
assert mat.reload.unfinished_matches.first.loser1_name != "BYE" queued_match = mat.reload.queue1_match
assert mat.reload.unfinished_matches.first.loser2_name != "BYE" if queued_match
assert queued_match.loser1_name != "BYE"
assert queued_match.loser2_name != "BYE"
end
winner_by_name("Test2",semis.second) winner_by_name("Test2",semis.second)
assert mat.reload.unfinished_matches.first.loser1_name != "BYE" queued_match = mat.reload.queue1_match
assert mat.reload.unfinished_matches.first.loser2_name != "BYE" if queued_match
assert queued_match.loser1_name != "BYE"
assert queued_match.loser2_name != "BYE"
end
conso_quarter = matches.select{|m| m.bracket_position == "Conso Quarter"}.sort_by{|m| m.bracket_position_number} conso_quarter = matches.select{|m| m.bracket_position == "Conso Quarter"}.sort_by{|m| m.bracket_position_number}
assert conso_quarter.first.reload.loser1_name == "BYE" assert conso_quarter.first.reload.loser1_name == "BYE"
@@ -58,8 +67,11 @@ class DoubleEliminationAutoByes < ActionDispatch::IntegrationTest
assert conso_semis.second.reload.wrestler1.name == "Test1" assert conso_semis.second.reload.wrestler1.name == "Test1"
assert conso_semis.second.reload.loser2_name == "BYE" assert conso_semis.second.reload.loser2_name == "BYE"
winner_by_name("Test5",conso_semis.first) winner_by_name("Test5",conso_semis.first)
assert mat.reload.unfinished_matches.first.loser1_name != "BYE" queued_match = mat.reload.queue1_match
assert mat.reload.unfinished_matches.first.loser2_name != "BYE" if queued_match
assert queued_match.loser1_name != "BYE"
assert queued_match.loser2_name != "BYE"
end
first_finals = matches.select{|m| m.bracket_position == "1/2"}.first first_finals = matches.select{|m| m.bracket_position == "1/2"}.first
third_finals = matches.select{|m| m.bracket_position == "3/4"}.first third_finals = matches.select{|m| m.bracket_position == "3/4"}.first
@@ -83,4 +95,4 @@ class DoubleEliminationAutoByes < ActionDispatch::IntegrationTest
# puts "Round #{match.round} #{match.w1_bracket_name} vs #{match.w2_bracket_name}" # puts "Round #{match.round} #{match.w1_bracket_name} vs #{match.w2_bracket_name}"
# end # end
end end
end end

View File

@@ -7,7 +7,8 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
test "Mat assignment works with no mat assignment rules" do test "Mat assignment works with no mat assignment rules" do
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
assert @tournament.mats.first.matches.first != nil assert @tournament.mats.first.queue1_match != nil
assert @tournament.mats.second.queue1_match != nil
end end
test "Mat assignment only assigns matches for a certain weight" do test "Mat assignment only assigns matches for a certain weight" do
@@ -25,7 +26,7 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
mat.reload mat.reload
assigned_matches = mat.matches.reload assigned_matches = mat.queue_matches.compact
assert_not_empty assigned_matches, "Matches should have been assigned to the mat" assert_not_empty assigned_matches, "Matches should have been assigned to the mat"
assert assigned_matches.all? { |match| match.weight_id == assignment_weight_id }, assert assigned_matches.all? { |match| match.weight_id == assignment_weight_id },
@@ -46,8 +47,15 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
mat.reload mat.reload
assigned_matches = mat.matches.reload assigned_matches = mat.queue_matches.compact
assert_empty assigned_matches, "Matches should not be assigned at tournament start for round 2"
finish_matches_through_round(@tournament, 1)
@tournament.reset_and_fill_bout_board
mat.reload
assigned_matches = mat.queue_matches.compact
assert_not_empty assigned_matches, "Matches should have been assigned to the mat" assert_not_empty assigned_matches, "Matches should have been assigned to the mat"
assert assigned_matches.all? { |match| match.round == 2 }, assert assigned_matches.all? { |match| match.round == 2 },
"All matches assigned to the mat should only be for round 2" "All matches assigned to the mat should only be for round 2"
@@ -67,8 +75,15 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
mat.reload mat.reload
assigned_matches = mat.matches.reload assigned_matches = mat.queue_matches.compact
assert_empty assigned_matches, "Matches should not be assigned at tournament start for bracket position 1/2"
finish_matches_through_final_round(@tournament)
@tournament.reset_and_fill_bout_board
mat.reload
assigned_matches = mat.queue_matches.compact
assert_not_empty assigned_matches, "Matches should have been assigned to the mat" assert_not_empty assigned_matches, "Matches should have been assigned to the mat"
assert assigned_matches.all? { |match| match.bracket_position == '1/2' }, assert assigned_matches.all? { |match| match.bracket_position == '1/2' },
"All matches assigned to the mat should only be for bracket_position 1/2" "All matches assigned to the mat should only be for bracket_position 1/2"
@@ -102,10 +117,16 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
mat.reload mat.reload
assigned_matches = mat.matches.reload assigned_matches = mat.queue_matches.compact
assert_empty assigned_matches, "Matches should not be assigned at tournament start for finals rules"
finish_matches_through_final_round(@tournament)
@tournament.reset_and_fill_bout_board
mat.reload
assigned_matches = mat.queue_matches.compact
assert_not_empty assigned_matches, "Matches should have been assigned to the mat" assert_not_empty assigned_matches, "Matches should have been assigned to the mat"
assert( assert(
assigned_matches.all? do |match| assigned_matches.all? do |match|
match.weight_id == assignment_weight_id && match.weight_id == assignment_weight_id &&
@@ -130,7 +151,7 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
mat.reload mat.reload
assigned_matches = mat.matches.reload assigned_matches = mat.queue_matches.compact
assert_empty assigned_matches, "No matches should have been assigned to the mat" assert_empty assigned_matches, "No matches should have been assigned to the mat"
end end
@@ -159,17 +180,25 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
mat1.reload mat1.reload
mat2.reload mat2.reload
mat1_matches = mat1.matches.reload mat1_matches = mat1.queue_matches.compact
mat2_matches = mat2.matches.reload mat2_matches = mat2.queue_matches.compact
assert_not_empty mat1_matches, "Matches should have been assigned to Mat 1" if mat1_matches.empty?
assert_not_empty mat2_matches, "Matches should have been assigned to Mat 2" eligible_matches = @tournament.matches.where(weight_id: @tournament.weights.first.id).where.not(w1: nil).where.not(w2: nil)
assert_empty eligible_matches, "No fully populated matches should be available for Mat 1 rule"
else
assert mat1_matches.all? { |match| match.weight_id == @tournament.weights.first.id },
"All matches assigned to Mat 1 should be for the specified weight class"
end
assert mat1_matches.all? { |match| match.weight_id == @tournament.weights.first.id }, if mat2_matches.empty?
"All matches assigned to Mat 1 should be for the specified weight class" eligible_matches = @tournament.matches.where(round: 3).where.not(w1: nil).where.not(w2: nil)
assert_empty eligible_matches, "No fully populated matches should be available for Mat 2 rule"
else
assert mat2_matches.all? { |match| match.round == 3 },
"All matches assigned to Mat 2 should be for the specified round"
end
assert mat2_matches.all? { |match| match.round == 3 },
"All matches assigned to Mat 2 should be for the specified round"
end end
test "No matches assigned in an empty tournament" do test "No matches assigned in an empty tournament" do
@@ -188,8 +217,9 @@ class MatAssignmentRules < ActionDispatch::IntegrationTest
@tournament.reset_and_fill_bout_board @tournament.reset_and_fill_bout_board
mat.reload mat.reload
assigned_matches = mat.matches.reload assigned_matches = mat.queue_matches.compact
assert_empty assigned_matches, "No matches should have been assigned for an empty tournament" assert_empty assigned_matches, "No matches should have been assigned for an empty tournament"
end end
end end

View File

@@ -0,0 +1,55 @@
require 'test_helper'
class RandomSeedingTest < ActionDispatch::IntegrationTest
def setup
end
def clean_up_original_seeds(tournament)
tournament.wrestlers.each do |wrestler|
wrestler.original_seed = nil
wrestler.save
end
tournament.wrestlers.reload.each do |wrestler|
wrestler.bracket_line = nil
wrestler.save
end
GenerateTournamentMatches.new(tournament).generate
end
test "There are no double byes in a double elimination tournament round 1" do
create_double_elim_tournament_single_weight(18, "Regular Double Elimination 1-8")
clean_up_original_seeds(@tournament)
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
assert round_one_matches.select{|m| m.w1.nil? and m.w2.nil? }.size == 0
end
test "There are the same number of matches in the top half and bottom half of a double elimination tournament in round 1" do
create_double_elim_tournament_single_weight(18, "Regular Double Elimination 1-8")
clean_up_original_seeds(@tournament)
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
# 32 man bracket there are 16 matches so top half is bracket_position_number 1-8 and bottom is 9-16
round_one_top_half = round_one_matches.select{|m| !m.w1.nil? and !m.w2.nil? and m.bracket_position_number < 9}
round_one_bottom_half = round_one_matches.select{|m| !m.w1.nil? and !m.w2.nil? and m.bracket_position_number > 8}
assert round_one_top_half.size == round_one_bottom_half.size
end
test "There are the same number of matches in the top half and bottom half of a double elimination tournament in round 1 in a 6 man bracket" do
create_double_elim_tournament_single_weight(6, "Regular Double Elimination 1-8")
clean_up_original_seeds(@tournament)
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
# 8 man bracket there are 4 matches so top half is bracket_position_number 1-2 and bottom is 3-4
round_one_top_half = round_one_matches.select{|m| !m.w1.nil? and !m.w2.nil? and m.bracket_position_number < 3}
round_one_bottom_half = round_one_matches.select{|m| !m.w1.nil? and !m.w2.nil? and m.bracket_position_number > 2}
assert round_one_top_half.size == round_one_bottom_half.size
end
test "There are no double byes in a double elimination tournament in a 6 man bracket" do
create_double_elim_tournament_single_weight(6, "Regular Double Elimination 1-8")
clean_up_original_seeds(@tournament)
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
conso_round_one_matches = @tournament.matches.reload.select{|m| m.bracket_position == "Conso Quarter"}
assert round_one_matches.select{|m| m.w1.nil? and m.w2.nil? }.size == 0
assert conso_round_one_matches.select{|m| m.loser1_name == "BYE" and m.loser2_name == "BYE" }.size == 0
end
end

View File

@@ -8,7 +8,22 @@ class MatTest < ActiveSupport::TestCase
test "Mat validations" do test "Mat validations" do
mat = Mat.new mat = Mat.new
assert_not mat.valid? assert_not mat.valid?
assert_equal [:name], mat.errors.attribute_names assert_equal [:tournament, :name], mat.errors.attribute_names
end
test "queue_matches refreshes after queue slots change and record reloads" do
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 4, 1)
@tournament.reset_and_fill_bout_board
mat = @tournament.mats.first
initial_queue_ids = mat.queue_matches.map { |match| match&.id }
assert initial_queue_ids.compact.any?, "Expected initial queue to contain matches"
Mat.where(id: mat.id).update_all(queue1: nil, queue2: nil, queue3: nil, queue4: nil)
mat.reload
refreshed_queue_ids = mat.queue_matches.map { |match| match&.id }
assert_equal [nil, nil, nil, nil], refreshed_queue_ids, "Expected queue_matches to refresh after reload and slot changes"
end end
end end

View File

@@ -0,0 +1,68 @@
require 'test_helper'
class MatchBroadcastTest < ActiveSupport::TestCase
include ActionView::RecordIdentifier
test "broadcasts to old and new mats when mat changes" do
create_double_elim_tournament_single_weight_1_6(4)
mat1 = @tournament.mats.create!(name: "Mat 1")
mat2 = @tournament.mats.create!(name: "Mat 2")
@tournament.matches.update_all(mat_id: nil)
match = @tournament.matches.first
# Set an initial mat
match.update!(mat: mat1)
stream1 = stream_name_for(mat1)
stream2 = stream_name_for(mat2)
# Clear the stream so we can test changes from this state
clear_streams(stream1, stream2)
# Update the mat and check the stream
match.update!(mat: mat2)
assert_equal [mat1.id, mat2.id], match.saved_change_to_mat_id
assert_equal 1, broadcasts_for(stream1).size
assert_equal 1, broadcasts_for(stream2).size
assert_includes broadcasts_for(stream2).last, dom_id(mat2, :current_match)
end
test "broadcasts when a match is removed from a mat" do
create_double_elim_tournament_single_weight_1_6(4)
mat = @tournament.mats.create!(name: "Mat 1")
@tournament.matches.update_all(mat_id: nil)
match = @tournament.matches.first
# Set an initial mat
match.update!(mat: mat)
stream = stream_name_for(mat)
# Clear the stream so we can test changes from this state
clear_streams(stream)
# Update the mat and check the stream
match.update!(mat: nil)
assert_equal [mat.id, nil], match.saved_change_to_mat_id
assert_equal 1, broadcasts_for(stream).size
assert_includes broadcasts_for(stream).last, dom_id(mat, :current_match)
end
private
def broadcasts_for(stream)
ActionCable.server.pubsub.broadcasts(stream)
end
def clear_streams(*streams)
ActionCable.server.pubsub.clear
streams.each do |stream|
broadcasts_for(stream).clear
end
end
def stream_name_for(streamable)
Turbo::StreamsChannel.send(:stream_name_from, [streamable])
end
end

View File

@@ -8,7 +8,7 @@ class SchoolTest < ActiveSupport::TestCase
test "School validations" do test "School validations" do
school = School.new school = School.new
assert_not school.valid? assert_not school.valid?
assert_equal [:name], school.errors.attribute_names assert_equal [:tournament, :name], school.errors.attribute_names
end end
end end

View File

@@ -14,79 +14,94 @@ class TournamentTest < ActiveSupport::TestCase
# Pool to bracket match_generation_error # Pool to bracket match_generation_error
test "Tournament pool to bracket match generation errors with less than two wrestlers" do test "Tournament pool to bracket match generation errors with less than two wrestlers" do
number_of_wrestlers=1 create_a_tournament_with_single_weight("Pool to bracket", 1)
create_a_tournament_with_single_weight("Pool to bracket", number_of_wrestlers) assert_match("There is a tournament error.", @tournament.match_generation_error)
assert @tournament.match_generation_error != nil
end end
test "Tournament pool to bracket match generation errors with more than 24 wrestlers" do test "Tournament pool to bracket match generation errors with more than 24 wrestlers" do
number_of_wrestlers=25 create_a_tournament_with_single_weight("Pool to bracket", 25)
create_a_tournament_with_single_weight("Pool to bracket", number_of_wrestlers) assert_match("There is a tournament error.", @tournament.match_generation_error)
assert @tournament.match_generation_error != nil
end end
test "Tournament pool to bracket no match generation errors with 24 wrestlers" do test "Tournament pool to bracket no match generation errors with 24 wrestlers" do
number_of_wrestlers=24 create_a_tournament_with_single_weight("Pool to bracket", 24)
create_a_tournament_with_single_weight("Pool to bracket", number_of_wrestlers) assert_nil @tournament.match_generation_error
assert @tournament.match_generation_error == nil
end end
test "Tournament pool to bracket no match generation errors with 2 wrestlers" do test "Tournament pool to bracket no match generation errors with 2 wrestlers" do
number_of_wrestlers=2 create_a_tournament_with_single_weight("Pool to bracket", 2)
create_a_tournament_with_single_weight("Pool to bracket", number_of_wrestlers) assert_nil @tournament.match_generation_error
assert @tournament.match_generation_error == nil
end end
# Modified 16 Man Double Elimination match_generation_error # Modified 16 Man Double Elimination match_generation_error
test "TournamentModified 16 Man Double Elimination match generation errors with less than 12 wrestlers" do test "Tournament modified 16 man double elimination match generation errors with less than 12 wrestlers" do
number_of_wrestlers=11 create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", 11)
create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", number_of_wrestlers) assert_match("There is a tournament error.", @tournament.match_generation_error)
assert @tournament.match_generation_error != nil
end end
test "Tournament Modified 16 Man Double Elimination match generation errors with more than 16 wrestlers" do test "Tournament modified 16 man double elimination match generation errors with more than 16 wrestlers" do
number_of_wrestlers=17 create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", 17)
create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", number_of_wrestlers) assert_match("There is a tournament error.", @tournament.match_generation_error)
assert @tournament.match_generation_error != nil
end end
test "Tournament Modified 16 Man Double Elimination no match generation errors with 16 wrestlers" do test "Tournament modified 16 man double elimination no match generation errors with 16 wrestlers" do
number_of_wrestlers=16 create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", 16)
create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", number_of_wrestlers) assert_nil @tournament.match_generation_error
assert @tournament.match_generation_error == nil
end end
test "Tournament Modified 16 Man Double Elimination no match generation errors with 12 wrestlers" do test "Tournament modified 16 man double elimination no match generation errors with 12 wrestlers" do
number_of_wrestlers=12 create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", 12)
create_a_tournament_with_single_weight("Modified 16 Man Double Elimination", number_of_wrestlers) assert_nil @tournament.match_generation_error
assert @tournament.match_generation_error == nil
end end
# Double Elimination match_generation_error # Double Elimination match_generation_error
test "Tournament Double Elimination 1-8 match generation errors with less than 4 wrestlers" do test "Tournament regular double elimination 1-8 match generation errors with less than 2 wrestlers" do
number_of_wrestlers=3 create_a_tournament_with_single_weight("Regular Double Elimination 1-8", 1)
create_a_tournament_with_single_weight("Double Elimination 1-8", number_of_wrestlers) assert_match("There is a tournament error.", @tournament.match_generation_error)
assert @tournament.match_generation_error != nil
end end
test "Tournament Double Elimination 1-8 match generation errors with more than 64 wrestlers" do test "Tournament regular double elimination 1-8 match generation errors with more than 64 wrestlers" do
number_of_wrestlers=65 create_a_tournament_with_single_weight("Regular Double Elimination 1-8", 65)
create_a_tournament_with_single_weight("Double Elimination 1-8", number_of_wrestlers) assert_match("There is a tournament error.", @tournament.match_generation_error)
assert @tournament.match_generation_error != nil
end end
test "Tournament Double Elimination 1-8 no match generation errors with 16 wrestlers" do test "Tournament regular double elimination 1-8 no match generation errors with 64 wrestlers" do
number_of_wrestlers=16 create_a_tournament_with_single_weight("Regular Double Elimination 1-8", 64)
create_a_tournament_with_single_weight("Double Elimination 1-8", number_of_wrestlers) assert_nil @tournament.match_generation_error
assert @tournament.match_generation_error == nil
end end
test "Tournament Double Elimination 1-8 no match generation errors with 4 wrestlers" do test "Tournament regular double elimination 1-8 no match generation errors with 2 wrestlers" do
number_of_wrestlers=4 create_a_tournament_with_single_weight("Regular Double Elimination 1-8", 2)
create_a_tournament_with_single_weight("Double Elimination 1-8", number_of_wrestlers) assert_nil @tournament.match_generation_error
assert @tournament.match_generation_error == nil
end end
test "Tournament match generation errors when a wrestler seed exceeds bracket size" do
create_a_tournament_with_single_weight("Pool to bracket", 4)
@tournament.weights.first.wrestlers.first.update!(original_seed: 8)
assert_match("There is a tournament error.", @tournament.match_generation_error)
assert_match("greater than the amount of wrestlers", @tournament.match_generation_error)
end
test "Tournament match generation errors when duplicate original seeds are present" do
create_a_tournament_with_single_weight("Pool to bracket", 4)
wrestlers = @tournament.weights.first.wrestlers.order(:id)
wrestlers.first.update!(original_seed: 2)
wrestlers.second.update!(original_seed: 2)
assert_match("There is a tournament error.", @tournament.match_generation_error)
assert_match("seeded 2", @tournament.match_generation_error)
end
test "Tournament match generation errors when original seeds are out of order" do
create_a_tournament_with_single_weight("Pool to bracket", 4)
wrestlers = @tournament.weights.first.wrestlers.order(:id)
wrestlers.first.update!(original_seed: 1)
wrestlers.second.update!(original_seed: 3)
wrestlers.third.update!(original_seed: nil)
wrestlers.fourth.update!(original_seed: nil)
assert_match("There is a tournament error.", @tournament.match_generation_error)
assert_match("out-of-order seeds", @tournament.match_generation_error)
end
## End match_generation_error tests ## End match_generation_error tests
test "Tournament create_pre_defined_weights High School Boys Weights" do test "Tournament create_pre_defined_weights High School Boys Weights" do

Some files were not shown because too many files have changed in this diff Show More