1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-04-14 00:49:54 +00:00

22 Commits

Author SHA1 Message Date
dependabot[bot]
e3fb68a932 Bump bcrypt from 3.1.21 to 3.1.22
Bumps [bcrypt](https://github.com/bcrypt-ruby/bcrypt-ruby) from 3.1.21 to 3.1.22.
- [Release notes](https://github.com/bcrypt-ruby/bcrypt-ruby/releases)
- [Changelog](https://github.com/bcrypt-ruby/bcrypt-ruby/blob/master/CHANGELOG)
- [Commits](https://github.com/bcrypt-ruby/bcrypt-ruby/compare/v3.1.21...v3.1.22)

---
updated-dependencies:
- dependency-name: bcrypt
  dependency-version: 3.1.22
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 18:58:35 +00:00
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
74 changed files with 2074 additions and 520 deletions

View File

@@ -1 +1 @@
wrestlingdev wrestlingdev

View File

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

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.3' gem 'rails', '8.1.2'
# Added in rails 7.1 # Added in rails 7.1
gem 'rails-html-sanitizer' gem 'rails-html-sanitizer'
@@ -40,7 +40,7 @@ gem 'sdoc', :group => :doc
gem 'spring', :group => :development gem 'spring', :group => :development
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7' gem 'bcrypt', '~> 3.1.22'
# Use unicorn as the app server # Use unicorn as the app server
# gem 'unicorn' # gem 'unicorn'
@@ -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'
@@ -91,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.3) action_text-trix (2.1.16)
actionpack (= 8.0.3) railties
activesupport (= 8.0.3) 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.3) actionmailbox (8.1.2)
actionpack (= 8.0.3) actionpack (= 8.1.2)
activejob (= 8.0.3) activejob (= 8.1.2)
activerecord (= 8.0.3) activerecord (= 8.1.2)
activestorage (= 8.0.3) activestorage (= 8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.3) actionmailer (8.1.2)
actionpack (= 8.0.3) actionpack (= 8.1.2)
actionview (= 8.0.3) actionview (= 8.1.2)
activejob (= 8.0.3) activejob (= 8.1.2)
activesupport (= 8.0.3) 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.3) actionpack (8.1.2)
actionview (= 8.0.3) actionview (= 8.1.2)
activesupport (= 8.0.3) 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,42 +33,43 @@ 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.3) actiontext (8.1.2)
actionpack (= 8.0.3) action_text-trix (~> 2.1.15)
activerecord (= 8.0.3) actionpack (= 8.1.2)
activestorage (= 8.0.3) activerecord (= 8.1.2)
activesupport (= 8.0.3) 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.3) actionview (8.1.2)
activesupport (= 8.0.3) 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.3) activejob (8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.3) activemodel (8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
activerecord (8.0.3) activerecord (8.1.2)
activemodel (= 8.0.3) activemodel (= 8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.3) activestorage (8.1.2)
actionpack (= 8.0.3) actionpack (= 8.1.2)
activejob (= 8.0.3) activejob (= 8.1.2)
activerecord (= 8.0.3) activerecord (= 8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.3) 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)
@@ -74,39 +77,39 @@ GEM
uri (>= 0.13.1) uri (>= 0.13.1)
ast (2.4.3) ast (2.4.3)
base64 (0.3.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.22)
benchmark (0.4.1) bigdecimal (4.0.1)
bigdecimal (3.3.0) bootsnap (1.23.0)
bootsnap (1.18.6)
msgpack (~> 1.2) msgpack (~> 1.2)
brakeman (7.1.0) brakeman (8.0.2)
racc racc
builder (3.3.0) builder (3.3.0)
bullet (8.0.8) 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.4) 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.3) drb (2.2.3)
erb (5.0.3) erb (6.0.1)
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.4.0) et-orbi (1.4.0)
tzinfo tzinfo
fugit (1.11.2) fugit (1.12.1)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1.4)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.3.0) 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.2.2) 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)
@@ -114,33 +117,36 @@ 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.1) 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.14.1) jbuilder (2.14.1)
actionview (>= 7.0.0) actionview (>= 7.0.0)
activesupport (>= 7.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.15.1) 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.1.0) marcel (1.1.0)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.5) minitest (6.0.1)
prism (~> 1.5)
mission_control-jobs (1.1.0) mission_control-jobs (1.1.0)
actioncable (>= 7.1) actioncable (>= 7.1)
actionpack (>= 7.1) actionpack (>= 7.1)
@@ -151,12 +157,12 @@ GEM
railties (>= 7.1) railties (>= 7.1)
stimulus-rails stimulus-rails
turbo-rails turbo-rails
mocha (2.7.1) 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.7) mysql2 (0.5.7)
bigdecimal bigdecimal
net-imap (0.5.12) net-imap (0.6.3)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@@ -165,64 +171,64 @@ 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.10-aarch64-linux-gnu) nokogiri (1.19.0-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-aarch64-linux-musl) nokogiri (1.19.0-aarch64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-arm-linux-gnu) nokogiri (1.19.0-arm-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-arm-linux-musl) nokogiri (1.19.0-arm-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-arm64-darwin) nokogiri (1.19.0-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-x86_64-darwin) nokogiri (1.19.0-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-x86_64-linux-gnu) nokogiri (1.19.0-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.10-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.9.0) parser (3.3.10.1)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pp (0.6.3) pp (0.6.3)
prettyprint prettyprint
prettyprint (0.2.0) prettyprint (0.2.0)
prism (1.5.1) prism (1.9.0)
propshaft (1.3.1) propshaft (1.3.1)
actionpack (>= 7.0.0) actionpack (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
rack rack
psych (5.2.6) psych (5.3.1)
date date
stringio stringio
puma (7.0.4) 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.2.2) 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.3) rails (8.1.2)
actioncable (= 8.0.3) actioncable (= 8.1.2)
actionmailbox (= 8.0.3) actionmailbox (= 8.1.2)
actionmailer (= 8.0.3) actionmailer (= 8.1.2)
actionpack (= 8.0.3) actionpack (= 8.1.2)
actiontext (= 8.0.3) actiontext (= 8.1.2)
actionview (= 8.0.3) actionview (= 8.1.2)
activejob (= 8.0.3) activejob (= 8.1.2)
activemodel (= 8.0.3) activemodel (= 8.1.2)
activerecord (= 8.0.3) activerecord (= 8.1.2)
activestorage (= 8.0.3) activestorage (= 8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.3) 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)
@@ -239,9 +245,9 @@ 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.3) railties (8.1.2)
actionpack (= 8.0.3) actionpack (= 8.1.2)
activesupport (= 8.0.3) activesupport (= 8.1.2)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@@ -249,17 +255,21 @@ GEM
tsort (>= 0.2) tsort (>= 0.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.3.0) rake (13.3.1)
rb-readline (0.5.5) rb-readline (0.5.5)
rdoc (6.15.0) rdoc (7.2.0)
erb erb
psych (>= 4.0.0) psych (>= 4.0.0)
tsort tsort
regexp_parser (2.11.3) regexp_parser (2.11.3)
reline (0.6.2) 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.81.1) 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)
@@ -267,15 +277,15 @@ 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.47.1, < 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.47.1) 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.4) sdoc (2.6.5)
rdoc (>= 5.0) rdoc (>= 5.0)
securerandom (0.4.1) securerandom (0.4.1)
solid_cable (3.0.12) solid_cable (3.0.12)
@@ -283,50 +293,50 @@ GEM
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.2.1) 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.4.0) spring (4.4.2)
sqlite3 (2.7.4-aarch64-linux-gnu) sqlite3 (2.9.0-aarch64-linux-gnu)
sqlite3 (2.7.4-aarch64-linux-musl) sqlite3 (2.9.0-aarch64-linux-musl)
sqlite3 (2.7.4-arm-linux-gnu) sqlite3 (2.9.0-arm-linux-gnu)
sqlite3 (2.7.4-arm-linux-musl) sqlite3 (2.9.0-arm-linux-musl)
sqlite3 (2.7.4-arm64-darwin) sqlite3 (2.9.0-arm64-darwin)
sqlite3 (2.7.4-x86_64-darwin) sqlite3 (2.9.0-x86_64-darwin)
sqlite3 (2.7.4-x86_64-linux-gnu) sqlite3 (2.9.0-x86_64-linux-gnu)
sqlite3 (2.7.4-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.4.0) thor (1.5.0)
timeout (0.4.3) timeout (0.6.0)
tsort (0.2.0) tsort (0.2.0)
turbo-rails (2.0.17) 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.2.0) unicode-display_width (3.2.0)
unicode-emoji (~> 4.1) unicode-emoji (~> 4.1)
unicode-emoji (4.1.0) unicode-emoji (4.2.0)
uniform_notifier (1.18.0) uniform_notifier (1.18.0)
uri (1.0.4) uri (1.1.1)
useragent (0.16.11) useragent (0.16.11)
websocket-driver (0.8.0) 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.3) zeitwerk (2.7.4)
PLATFORMS PLATFORMS
aarch64-linux-gnu aarch64-linux-gnu
@@ -339,7 +349,7 @@ PLATFORMS
x86_64-linux-musl x86_64-linux-musl
DEPENDENCIES DEPENDENCIES
bcrypt (~> 3.1.7) bcrypt (~> 3.1.22)
bootsnap bootsnap
brakeman brakeman
bullet bullet
@@ -355,12 +365,13 @@ DEPENDENCIES
mysql2 mysql2
propshaft propshaft
puma puma
rails (= 8.0.3) 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
@@ -373,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`
@@ -149,7 +152,7 @@ SolidQueue plugin enabled in Puma
``` ```
I have deployed Mission Control as a UI for SolidQueue. The uri for mission control is `/jobs`. 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. 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

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

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,53 +1,52 @@
class Mat < ApplicationRecord class Mat < ApplicationRecord
include ActionView::RecordIdentifier
belongs_to :tournament belongs_to :tournament
has_many :matches, dependent: :nullify 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

@@ -37,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
@@ -201,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
@@ -208,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}"
@@ -220,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
@@ -227,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}"
@@ -350,13 +354,13 @@ class Match < ApplicationRecord
next unless mat next unless mat
Turbo::StreamsChannel.broadcast_update_to( Turbo::StreamsChannel.broadcast_update_to(
mat, mat,
target: dom_id(mat, :current_match), target: dom_id(mat, :current_match),
partial: "mats/current_match", partial: "mats/current_match",
locals: { locals: {
mat: mat, mat: mat,
match: mat.unfinished_matches.first, match: mat.queue1_match,
next_match: mat.unfinished_matches.second, next_match: mat.queue2_match,
show_next_bout_button: true show_next_bout_button: true
} }
) )

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,7 +37,11 @@ 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"),

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,12 +75,18 @@ 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|
@@ -135,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)
@@ -156,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,9 +38,10 @@
<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), data: { turbo_method: :post, turbo_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>

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

@@ -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

@@ -1,6 +1,6 @@
<% @mat = mat %> <% @mat = mat %>
<% @match = local_assigns[:match] || mat.unfinished_matches.first %> <% @match = local_assigns[:match] || mat.queue1_match %>
<% @next_match = local_assigns[:next_match] || mat.unfinished_matches.second %> <% @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 %> <% @show_next_bout_button = local_assigns.key?(:show_next_bout_button) ? local_assigns[:show_next_bout_button] : true %>
<% @wrestlers = [] %> <% @wrestlers = [] %>

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, data: { turbo_method: :delete, turbo_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, data: { turbo_method: :delete, turbo_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>

View File

@@ -27,12 +27,12 @@ and will also delete all of your current data. It's best to use the create backu
</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: { turbo_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

@@ -7,6 +7,8 @@
<% else %> <% else %>
<div class="bout-number"><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %>&nbsp;</div> <div class="bout-number"><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %>&nbsp;</div>
<% end %> <% 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

@@ -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

@@ -28,6 +28,7 @@
<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>
@@ -36,4 +37,4 @@
</table> </table>
<br> <br>
<p>Total matches without byes: <%= @matches.select{|m| m.loser1_name != 'BYE' and m.loser2_name != 'BYE'}.size %></p> <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> <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

@@ -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, data: { turbo_method: :delete, turbo_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, data: { turbo_method: :delete, turbo_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,7 +138,9 @@
<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, data: { turbo_method: :delete, turbo_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 %>
<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" %> <%= link_to '', "/mats/#{mat.id}/assign_next_match", data: { turbo_method: :post }, :class=>"fas fa-solid fa-arrow-right" %>
</td> </td>
<% 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, data: { turbo_method: :delete, turbo_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>

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

@@ -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.
@@ -56,6 +53,6 @@ module Wrestling
# 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.0 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

@@ -10,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
@@ -71,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

@@ -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

@@ -1,4 +1,4 @@
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

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/
@@ -15,6 +15,8 @@ RUN apt-get -qq update --fix-missing \
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

@@ -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
@@ -399,6 +408,27 @@ Some Guy
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

@@ -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

@@ -10,5 +10,20 @@ class MatTest < ActiveSupport::TestCase
assert_not mat.valid? assert_not mat.valid?
assert_equal [:tournament, :name], mat.errors.attribute_names assert_equal [:tournament, :name], mat.errors.attribute_names
end 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

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

View File

@@ -376,6 +376,27 @@ class ActiveSupport::TestCase
Match.where("(w1 = ? OR w2 = ?) AND (w1 = ? OR w2 = ?)",translate_name_to_id(wrestler1_name), translate_name_to_id(wrestler1_name), translate_name_to_id(wrestler2_name),translate_name_to_id(wrestler2_name)).first Match.where("(w1 = ? OR w2 = ?) AND (w1 = ? OR w2 = ?)",translate_name_to_id(wrestler1_name), translate_name_to_id(wrestler1_name), translate_name_to_id(wrestler2_name),translate_name_to_id(wrestler2_name)).first
end end
def finish_matches_through_round(tournament, max_round)
tournament.matches.reload.select { |match| match.round && match.round <= max_round }.each do |match|
next if match.finished == 1
winner_id = match.w1 || match.w2
next unless winner_id
match.update!(
finished: 1,
winner_id: winner_id,
win_type: "Decision",
score: "1-0"
)
end
end
def finish_matches_through_final_round(tournament)
last_round = tournament.matches.maximum(:round)
return unless last_round
finish_matches_through_round(tournament, last_round - 1)
end
end end
# Add support for controller tests # Add support for controller tests

View File

@@ -8,13 +8,12 @@ class MatsCurrentMatchPartialTest < ActionView::TestCase
mat = @tournament.mats.create!(name: "Mat 1") mat = @tournament.mats.create!(name: "Mat 1")
match = @tournament.matches.first match = @tournament.matches.first
match.update!(mat: mat) mat.assign_match_to_queue!(match, 1)
render partial: "mats/current_match", locals: { mat: mat } render partial: "mats/current_match", locals: { mat: mat }
assert_includes rendered, "Bout" assert_includes rendered, "Bout"
assert_includes rendered, match.bout_number.to_s assert_includes rendered, match.bout_number.to_s
assert_includes rendered, mat.name
end end
test "renders friendly message when no matches assigned" do test "renders friendly message when no matches assigned" do